diff --git a/.travis.yml b/.travis.yml index 3df8b22..14de334 100644 --- a/.travis.yml +++ b/.travis.yml @@ -110,3 +110,43 @@ on: all_branches: true condition: $TRAVIS_TAG + # Deploy config for latest release + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: dist + upload-dir: "release" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: docs + upload-dir: "release/docs" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: coverage + upload-dir: "release/coverage" + on: + all_branches: true + condition: $TRAVIS_TAG \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index 3df8b22..14de334 100644 --- a/.travis.yml +++ b/.travis.yml @@ -110,3 +110,43 @@ on: all_branches: true condition: $TRAVIS_TAG + # Deploy config for latest release + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: dist + upload-dir: "release" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: docs + upload-dir: "release/docs" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: coverage + upload-dir: "release/coverage" + on: + all_branches: true + condition: $TRAVIS_TAG \ No newline at end of file diff --git a/README.md b/README.md index d26fadd..3b85f85 100644 --- a/README.md +++ b/README.md @@ -36,22 +36,14 @@ - Wiki: Other misc tutorials and resources are [on the Wiki](https://github.com/pixijs/pixi.js/wiki/Resources). ### Community ### -- Forums: Check out the [forum] (http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow] (http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. +- Forums: Check out the [forum](http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow](http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. - Inspiration: Check out the [gallery](http://www.pixijs.com/gallery) to see some of the amazing things people have created! - Chat: You can join us on [Gitter](https://gitter.im/pixijs/pixi.js) To chat about Pixi. We also now have a Slack channel. If you would like to join it please Send me an email (mat@goodboydigital.com) and I will invite you in. ### Setup ### -It's easy to get started with Pixi.js! Simply grab the pre-built versions from here: - -Release Branch - Nice and stable Pixi.js -- Unminified: [http://pixijs.download/release/pixi.js] -- Minified: [http://pixijs.download/release/pixi.min.js] - -Develop Branch - The bleeding edge version of Pixi.js -- Unminified: [http://pixijs.download/dev/pixi.js] -- Minified: [http://pixijs.download/dev/pixi.min.js] +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. @@ -70,10 +62,10 @@ #### CDN Install (via cdnjs) ```html - + ``` -_Note: `4.2.2` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ +_Note: `4.4.3` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ ### Demos ### @@ -130,7 +122,7 @@ // and the root stage PIXI.Container. var app = new PIXI.Application(); -// The application will create a canvas element for you that you +// The application will create a canvas element for you that you // can then insert into the DOM. document.body.appendChild(app.view); @@ -150,7 +142,7 @@ // Add the bunny to the scene we are building. app.stage.addChild(bunny); - + // Listen for frame updates app.ticker.add(function() { // each frame we spin the bunny around a bit diff --git a/.travis.yml b/.travis.yml index 3df8b22..14de334 100644 --- a/.travis.yml +++ b/.travis.yml @@ -110,3 +110,43 @@ on: all_branches: true condition: $TRAVIS_TAG + # Deploy config for latest release + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: dist + upload-dir: "release" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: docs + upload-dir: "release/docs" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: coverage + upload-dir: "release/coverage" + on: + all_branches: true + condition: $TRAVIS_TAG \ No newline at end of file diff --git a/README.md b/README.md index d26fadd..3b85f85 100644 --- a/README.md +++ b/README.md @@ -36,22 +36,14 @@ - Wiki: Other misc tutorials and resources are [on the Wiki](https://github.com/pixijs/pixi.js/wiki/Resources). ### Community ### -- Forums: Check out the [forum] (http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow] (http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. +- Forums: Check out the [forum](http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow](http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. - Inspiration: Check out the [gallery](http://www.pixijs.com/gallery) to see some of the amazing things people have created! - Chat: You can join us on [Gitter](https://gitter.im/pixijs/pixi.js) To chat about Pixi. We also now have a Slack channel. If you would like to join it please Send me an email (mat@goodboydigital.com) and I will invite you in. ### Setup ### -It's easy to get started with Pixi.js! Simply grab the pre-built versions from here: - -Release Branch - Nice and stable Pixi.js -- Unminified: [http://pixijs.download/release/pixi.js] -- Minified: [http://pixijs.download/release/pixi.min.js] - -Develop Branch - The bleeding edge version of Pixi.js -- Unminified: [http://pixijs.download/dev/pixi.js] -- Minified: [http://pixijs.download/dev/pixi.min.js] +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. @@ -70,10 +62,10 @@ #### CDN Install (via cdnjs) ```html - + ``` -_Note: `4.2.2` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ +_Note: `4.4.3` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ ### Demos ### @@ -130,7 +122,7 @@ // and the root stage PIXI.Container. var app = new PIXI.Application(); -// The application will create a canvas element for you that you +// The application will create a canvas element for you that you // can then insert into the DOM. document.body.appendChild(app.view); @@ -150,7 +142,7 @@ // Add the bunny to the scene we are building. app.stage.addChild(bunny); - + // Listen for frame updates app.ticker.add(function() { // each frame we spin the bunny around a bit diff --git a/bower.json b/bower.json deleted file mode 100644 index 41b668c..0000000 --- a/bower.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "pixi.js", - "main": "dist/pixi.min.js", - "ignore": [ - "**/.*", - "docs", - "node_modules", - "bower_components", - "test", - "scripts", - "bower.json", - "inch.json" - ], - "dependencies": { - }, - "devDependencies": { - } -} diff --git a/.travis.yml b/.travis.yml index 3df8b22..14de334 100644 --- a/.travis.yml +++ b/.travis.yml @@ -110,3 +110,43 @@ on: all_branches: true condition: $TRAVIS_TAG + # Deploy config for latest release + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: dist + upload-dir: "release" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: docs + upload-dir: "release/docs" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: coverage + upload-dir: "release/coverage" + on: + all_branches: true + condition: $TRAVIS_TAG \ No newline at end of file diff --git a/README.md b/README.md index d26fadd..3b85f85 100644 --- a/README.md +++ b/README.md @@ -36,22 +36,14 @@ - Wiki: Other misc tutorials and resources are [on the Wiki](https://github.com/pixijs/pixi.js/wiki/Resources). ### Community ### -- Forums: Check out the [forum] (http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow] (http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. +- Forums: Check out the [forum](http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow](http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. - Inspiration: Check out the [gallery](http://www.pixijs.com/gallery) to see some of the amazing things people have created! - Chat: You can join us on [Gitter](https://gitter.im/pixijs/pixi.js) To chat about Pixi. We also now have a Slack channel. If you would like to join it please Send me an email (mat@goodboydigital.com) and I will invite you in. ### Setup ### -It's easy to get started with Pixi.js! Simply grab the pre-built versions from here: - -Release Branch - Nice and stable Pixi.js -- Unminified: [http://pixijs.download/release/pixi.js] -- Minified: [http://pixijs.download/release/pixi.min.js] - -Develop Branch - The bleeding edge version of Pixi.js -- Unminified: [http://pixijs.download/dev/pixi.js] -- Minified: [http://pixijs.download/dev/pixi.min.js] +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. @@ -70,10 +62,10 @@ #### CDN Install (via cdnjs) ```html - + ``` -_Note: `4.2.2` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ +_Note: `4.4.3` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ ### Demos ### @@ -130,7 +122,7 @@ // and the root stage PIXI.Container. var app = new PIXI.Application(); -// The application will create a canvas element for you that you +// The application will create a canvas element for you that you // can then insert into the DOM. document.body.appendChild(app.view); @@ -150,7 +142,7 @@ // Add the bunny to the scene we are building. app.stage.addChild(bunny); - + // Listen for frame updates app.ticker.add(function() { // each frame we spin the bunny around a bit diff --git a/bower.json b/bower.json deleted file mode 100644 index 41b668c..0000000 --- a/bower.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "pixi.js", - "main": "dist/pixi.min.js", - "ignore": [ - "**/.*", - "docs", - "node_modules", - "bower_components", - "test", - "scripts", - "bower.json", - "inch.json" - ], - "dependencies": { - }, - "devDependencies": { - } -} diff --git a/package.json b/package.json index d35cbb2..308469f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.3.5", + "version": "4.4.4", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ @@ -40,11 +40,11 @@ "lib": "babel src --out-dir lib -s", "predocs": "rimraf docs/**", "docs": "jsdoc -c scripts/jsdoc.conf.json -R README.md", - "publish:patch": "npm version patch --no-git-tag-version && npm publish", - "publish:minor": "npm version minor --no-git-tag-version && npm publish", - "publish:major": "npm version major --no-git-tag-version && npm publish", + "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", - "postpublish": "node scripts/release.js" + "postpublish": "git push && git push --tags" }, "files": [ "dist/", @@ -72,7 +72,6 @@ "electron": "^1.4.15", "eslint": "^3.5.0", "floss": "^2.0.1", - "gh-pages": "^0.11.0", "jaguarjs-jsdoc": "^1.0.1", "js-md5": "^0.4.1", "jsdoc": "^3.4.2", diff --git a/.travis.yml b/.travis.yml index 3df8b22..14de334 100644 --- a/.travis.yml +++ b/.travis.yml @@ -110,3 +110,43 @@ on: all_branches: true condition: $TRAVIS_TAG + # Deploy config for latest release + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: dist + upload-dir: "release" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: docs + upload-dir: "release/docs" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: coverage + upload-dir: "release/coverage" + on: + all_branches: true + condition: $TRAVIS_TAG \ No newline at end of file diff --git a/README.md b/README.md index d26fadd..3b85f85 100644 --- a/README.md +++ b/README.md @@ -36,22 +36,14 @@ - Wiki: Other misc tutorials and resources are [on the Wiki](https://github.com/pixijs/pixi.js/wiki/Resources). ### Community ### -- Forums: Check out the [forum] (http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow] (http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. +- Forums: Check out the [forum](http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow](http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. - Inspiration: Check out the [gallery](http://www.pixijs.com/gallery) to see some of the amazing things people have created! - Chat: You can join us on [Gitter](https://gitter.im/pixijs/pixi.js) To chat about Pixi. We also now have a Slack channel. If you would like to join it please Send me an email (mat@goodboydigital.com) and I will invite you in. ### Setup ### -It's easy to get started with Pixi.js! Simply grab the pre-built versions from here: - -Release Branch - Nice and stable Pixi.js -- Unminified: [http://pixijs.download/release/pixi.js] -- Minified: [http://pixijs.download/release/pixi.min.js] - -Develop Branch - The bleeding edge version of Pixi.js -- Unminified: [http://pixijs.download/dev/pixi.js] -- Minified: [http://pixijs.download/dev/pixi.min.js] +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. @@ -70,10 +62,10 @@ #### CDN Install (via cdnjs) ```html - + ``` -_Note: `4.2.2` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ +_Note: `4.4.3` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ ### Demos ### @@ -130,7 +122,7 @@ // and the root stage PIXI.Container. var app = new PIXI.Application(); -// The application will create a canvas element for you that you +// The application will create a canvas element for you that you // can then insert into the DOM. document.body.appendChild(app.view); @@ -150,7 +142,7 @@ // Add the bunny to the scene we are building. app.stage.addChild(bunny); - + // Listen for frame updates app.ticker.add(function() { // each frame we spin the bunny around a bit diff --git a/bower.json b/bower.json deleted file mode 100644 index 41b668c..0000000 --- a/bower.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "pixi.js", - "main": "dist/pixi.min.js", - "ignore": [ - "**/.*", - "docs", - "node_modules", - "bower_components", - "test", - "scripts", - "bower.json", - "inch.json" - ], - "dependencies": { - }, - "devDependencies": { - } -} diff --git a/package.json b/package.json index d35cbb2..308469f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.3.5", + "version": "4.4.4", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ @@ -40,11 +40,11 @@ "lib": "babel src --out-dir lib -s", "predocs": "rimraf docs/**", "docs": "jsdoc -c scripts/jsdoc.conf.json -R README.md", - "publish:patch": "npm version patch --no-git-tag-version && npm publish", - "publish:minor": "npm version minor --no-git-tag-version && npm publish", - "publish:major": "npm version major --no-git-tag-version && npm publish", + "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", - "postpublish": "node scripts/release.js" + "postpublish": "git push && git push --tags" }, "files": [ "dist/", @@ -72,7 +72,6 @@ "electron": "^1.4.15", "eslint": "^3.5.0", "floss": "^2.0.1", - "gh-pages": "^0.11.0", "jaguarjs-jsdoc": "^1.0.1", "js-md5": "^0.4.1", "jsdoc": "^3.4.2", diff --git a/scripts/jsdoc.conf.json b/scripts/jsdoc.conf.json index 553e61c..3f52180 100644 --- a/scripts/jsdoc.conf.json +++ b/scripts/jsdoc.conf.json @@ -43,7 +43,7 @@ }, "markdown" : { "parser" : "gfm", - "hardwrap" : true + "hardwrap" : false }, "opts": { "encoding" : "utf8", diff --git a/.travis.yml b/.travis.yml index 3df8b22..14de334 100644 --- a/.travis.yml +++ b/.travis.yml @@ -110,3 +110,43 @@ on: all_branches: true condition: $TRAVIS_TAG + # Deploy config for latest release + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: dist + upload-dir: "release" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: docs + upload-dir: "release/docs" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: coverage + upload-dir: "release/coverage" + on: + all_branches: true + condition: $TRAVIS_TAG \ No newline at end of file diff --git a/README.md b/README.md index d26fadd..3b85f85 100644 --- a/README.md +++ b/README.md @@ -36,22 +36,14 @@ - Wiki: Other misc tutorials and resources are [on the Wiki](https://github.com/pixijs/pixi.js/wiki/Resources). ### Community ### -- Forums: Check out the [forum] (http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow] (http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. +- Forums: Check out the [forum](http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow](http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. - Inspiration: Check out the [gallery](http://www.pixijs.com/gallery) to see some of the amazing things people have created! - Chat: You can join us on [Gitter](https://gitter.im/pixijs/pixi.js) To chat about Pixi. We also now have a Slack channel. If you would like to join it please Send me an email (mat@goodboydigital.com) and I will invite you in. ### Setup ### -It's easy to get started with Pixi.js! Simply grab the pre-built versions from here: - -Release Branch - Nice and stable Pixi.js -- Unminified: [http://pixijs.download/release/pixi.js] -- Minified: [http://pixijs.download/release/pixi.min.js] - -Develop Branch - The bleeding edge version of Pixi.js -- Unminified: [http://pixijs.download/dev/pixi.js] -- Minified: [http://pixijs.download/dev/pixi.min.js] +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. @@ -70,10 +62,10 @@ #### CDN Install (via cdnjs) ```html - + ``` -_Note: `4.2.2` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ +_Note: `4.4.3` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ ### Demos ### @@ -130,7 +122,7 @@ // and the root stage PIXI.Container. var app = new PIXI.Application(); -// The application will create a canvas element for you that you +// The application will create a canvas element for you that you // can then insert into the DOM. document.body.appendChild(app.view); @@ -150,7 +142,7 @@ // Add the bunny to the scene we are building. app.stage.addChild(bunny); - + // Listen for frame updates app.ticker.add(function() { // each frame we spin the bunny around a bit diff --git a/bower.json b/bower.json deleted file mode 100644 index 41b668c..0000000 --- a/bower.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "pixi.js", - "main": "dist/pixi.min.js", - "ignore": [ - "**/.*", - "docs", - "node_modules", - "bower_components", - "test", - "scripts", - "bower.json", - "inch.json" - ], - "dependencies": { - }, - "devDependencies": { - } -} diff --git a/package.json b/package.json index d35cbb2..308469f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.3.5", + "version": "4.4.4", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ @@ -40,11 +40,11 @@ "lib": "babel src --out-dir lib -s", "predocs": "rimraf docs/**", "docs": "jsdoc -c scripts/jsdoc.conf.json -R README.md", - "publish:patch": "npm version patch --no-git-tag-version && npm publish", - "publish:minor": "npm version minor --no-git-tag-version && npm publish", - "publish:major": "npm version major --no-git-tag-version && npm publish", + "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", - "postpublish": "node scripts/release.js" + "postpublish": "git push && git push --tags" }, "files": [ "dist/", @@ -72,7 +72,6 @@ "electron": "^1.4.15", "eslint": "^3.5.0", "floss": "^2.0.1", - "gh-pages": "^0.11.0", "jaguarjs-jsdoc": "^1.0.1", "js-md5": "^0.4.1", "jsdoc": "^3.4.2", diff --git a/scripts/jsdoc.conf.json b/scripts/jsdoc.conf.json index 553e61c..3f52180 100644 --- a/scripts/jsdoc.conf.json +++ b/scripts/jsdoc.conf.json @@ -43,7 +43,7 @@ }, "markdown" : { "parser" : "gfm", - "hardwrap" : true + "hardwrap" : false }, "opts": { "encoding" : "utf8", diff --git a/scripts/release.js b/scripts/release.js deleted file mode 100644 index 156620c..0000000 --- a/scripts/release.js +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env node -'use strict'; - -// Publish script to push releases of the bin files -// the normally are gitignored -const ghpages = require('gh-pages'); -const path = require('path'); -const packageInfo = require(path.join(__dirname, '..', 'package.json')); - -const options = { - src: [ - 'dist/**/*', - 'lib/**/*', - 'src/**/*', - 'scripts/*', - 'scripts/renders/*', - 'test/**/*', - '*.json', - '*.md', - 'LICENSE', - '.eslintrc', - '.editorconfig', - '.travis.yml', - '.babelrc', - ], - dotfiles: true, - branch: 'release', - message: packageInfo.version, - logger: console.log.bind(console), -}; - -ghpages.publish(process.cwd(), options, (err) => -{ - if (err) - { - console.log(err); - process.exit(1); - - return; - } - - process.exit(0); -}); diff --git a/.travis.yml b/.travis.yml index 3df8b22..14de334 100644 --- a/.travis.yml +++ b/.travis.yml @@ -110,3 +110,43 @@ on: all_branches: true condition: $TRAVIS_TAG + # Deploy config for latest release + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: dist + upload-dir: "release" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: docs + upload-dir: "release/docs" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: coverage + upload-dir: "release/coverage" + on: + all_branches: true + condition: $TRAVIS_TAG \ No newline at end of file diff --git a/README.md b/README.md index d26fadd..3b85f85 100644 --- a/README.md +++ b/README.md @@ -36,22 +36,14 @@ - Wiki: Other misc tutorials and resources are [on the Wiki](https://github.com/pixijs/pixi.js/wiki/Resources). ### Community ### -- Forums: Check out the [forum] (http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow] (http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. +- Forums: Check out the [forum](http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow](http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. - Inspiration: Check out the [gallery](http://www.pixijs.com/gallery) to see some of the amazing things people have created! - Chat: You can join us on [Gitter](https://gitter.im/pixijs/pixi.js) To chat about Pixi. We also now have a Slack channel. If you would like to join it please Send me an email (mat@goodboydigital.com) and I will invite you in. ### Setup ### -It's easy to get started with Pixi.js! Simply grab the pre-built versions from here: - -Release Branch - Nice and stable Pixi.js -- Unminified: [http://pixijs.download/release/pixi.js] -- Minified: [http://pixijs.download/release/pixi.min.js] - -Develop Branch - The bleeding edge version of Pixi.js -- Unminified: [http://pixijs.download/dev/pixi.js] -- Minified: [http://pixijs.download/dev/pixi.min.js] +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. @@ -70,10 +62,10 @@ #### CDN Install (via cdnjs) ```html - + ``` -_Note: `4.2.2` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ +_Note: `4.4.3` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ ### Demos ### @@ -130,7 +122,7 @@ // and the root stage PIXI.Container. var app = new PIXI.Application(); -// The application will create a canvas element for you that you +// The application will create a canvas element for you that you // can then insert into the DOM. document.body.appendChild(app.view); @@ -150,7 +142,7 @@ // Add the bunny to the scene we are building. app.stage.addChild(bunny); - + // Listen for frame updates app.ticker.add(function() { // each frame we spin the bunny around a bit diff --git a/bower.json b/bower.json deleted file mode 100644 index 41b668c..0000000 --- a/bower.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "pixi.js", - "main": "dist/pixi.min.js", - "ignore": [ - "**/.*", - "docs", - "node_modules", - "bower_components", - "test", - "scripts", - "bower.json", - "inch.json" - ], - "dependencies": { - }, - "devDependencies": { - } -} diff --git a/package.json b/package.json index d35cbb2..308469f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.3.5", + "version": "4.4.4", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ @@ -40,11 +40,11 @@ "lib": "babel src --out-dir lib -s", "predocs": "rimraf docs/**", "docs": "jsdoc -c scripts/jsdoc.conf.json -R README.md", - "publish:patch": "npm version patch --no-git-tag-version && npm publish", - "publish:minor": "npm version minor --no-git-tag-version && npm publish", - "publish:major": "npm version major --no-git-tag-version && npm publish", + "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", - "postpublish": "node scripts/release.js" + "postpublish": "git push && git push --tags" }, "files": [ "dist/", @@ -72,7 +72,6 @@ "electron": "^1.4.15", "eslint": "^3.5.0", "floss": "^2.0.1", - "gh-pages": "^0.11.0", "jaguarjs-jsdoc": "^1.0.1", "js-md5": "^0.4.1", "jsdoc": "^3.4.2", diff --git a/scripts/jsdoc.conf.json b/scripts/jsdoc.conf.json index 553e61c..3f52180 100644 --- a/scripts/jsdoc.conf.json +++ b/scripts/jsdoc.conf.json @@ -43,7 +43,7 @@ }, "markdown" : { "parser" : "gfm", - "hardwrap" : true + "hardwrap" : false }, "opts": { "encoding" : "utf8", diff --git a/scripts/release.js b/scripts/release.js deleted file mode 100644 index 156620c..0000000 --- a/scripts/release.js +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env node -'use strict'; - -// Publish script to push releases of the bin files -// the normally are gitignored -const ghpages = require('gh-pages'); -const path = require('path'); -const packageInfo = require(path.join(__dirname, '..', 'package.json')); - -const options = { - src: [ - 'dist/**/*', - 'lib/**/*', - 'src/**/*', - 'scripts/*', - 'scripts/renders/*', - 'test/**/*', - '*.json', - '*.md', - 'LICENSE', - '.eslintrc', - '.editorconfig', - '.travis.yml', - '.babelrc', - ], - dotfiles: true, - branch: 'release', - message: packageInfo.version, - logger: console.log.bind(console), -}; - -ghpages.publish(process.cwd(), options, (err) => -{ - if (err) - { - console.log(err); - process.exit(1); - - return; - } - - process.exit(0); -}); diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index f16bcb1..220427a 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -21,7 +21,7 @@ const DIV_HOOK_ZINDEX = 2; /** - * The Accessibility manager reacreates the ability to tab and and have content read by screen + * The Accessibility manager recreates the ability to tab and and have content read by screen * readers. This is very important as it can possibly help people with disabilities access pixi * content. * diff --git a/.travis.yml b/.travis.yml index 3df8b22..14de334 100644 --- a/.travis.yml +++ b/.travis.yml @@ -110,3 +110,43 @@ on: all_branches: true condition: $TRAVIS_TAG + # Deploy config for latest release + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: dist + upload-dir: "release" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: docs + upload-dir: "release/docs" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: coverage + upload-dir: "release/coverage" + on: + all_branches: true + condition: $TRAVIS_TAG \ No newline at end of file diff --git a/README.md b/README.md index d26fadd..3b85f85 100644 --- a/README.md +++ b/README.md @@ -36,22 +36,14 @@ - Wiki: Other misc tutorials and resources are [on the Wiki](https://github.com/pixijs/pixi.js/wiki/Resources). ### Community ### -- Forums: Check out the [forum] (http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow] (http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. +- Forums: Check out the [forum](http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow](http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. - Inspiration: Check out the [gallery](http://www.pixijs.com/gallery) to see some of the amazing things people have created! - Chat: You can join us on [Gitter](https://gitter.im/pixijs/pixi.js) To chat about Pixi. We also now have a Slack channel. If you would like to join it please Send me an email (mat@goodboydigital.com) and I will invite you in. ### Setup ### -It's easy to get started with Pixi.js! Simply grab the pre-built versions from here: - -Release Branch - Nice and stable Pixi.js -- Unminified: [http://pixijs.download/release/pixi.js] -- Minified: [http://pixijs.download/release/pixi.min.js] - -Develop Branch - The bleeding edge version of Pixi.js -- Unminified: [http://pixijs.download/dev/pixi.js] -- Minified: [http://pixijs.download/dev/pixi.min.js] +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. @@ -70,10 +62,10 @@ #### CDN Install (via cdnjs) ```html - + ``` -_Note: `4.2.2` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ +_Note: `4.4.3` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ ### Demos ### @@ -130,7 +122,7 @@ // and the root stage PIXI.Container. var app = new PIXI.Application(); -// The application will create a canvas element for you that you +// The application will create a canvas element for you that you // can then insert into the DOM. document.body.appendChild(app.view); @@ -150,7 +142,7 @@ // Add the bunny to the scene we are building. app.stage.addChild(bunny); - + // Listen for frame updates app.ticker.add(function() { // each frame we spin the bunny around a bit diff --git a/bower.json b/bower.json deleted file mode 100644 index 41b668c..0000000 --- a/bower.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "pixi.js", - "main": "dist/pixi.min.js", - "ignore": [ - "**/.*", - "docs", - "node_modules", - "bower_components", - "test", - "scripts", - "bower.json", - "inch.json" - ], - "dependencies": { - }, - "devDependencies": { - } -} diff --git a/package.json b/package.json index d35cbb2..308469f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.3.5", + "version": "4.4.4", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ @@ -40,11 +40,11 @@ "lib": "babel src --out-dir lib -s", "predocs": "rimraf docs/**", "docs": "jsdoc -c scripts/jsdoc.conf.json -R README.md", - "publish:patch": "npm version patch --no-git-tag-version && npm publish", - "publish:minor": "npm version minor --no-git-tag-version && npm publish", - "publish:major": "npm version major --no-git-tag-version && npm publish", + "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", - "postpublish": "node scripts/release.js" + "postpublish": "git push && git push --tags" }, "files": [ "dist/", @@ -72,7 +72,6 @@ "electron": "^1.4.15", "eslint": "^3.5.0", "floss": "^2.0.1", - "gh-pages": "^0.11.0", "jaguarjs-jsdoc": "^1.0.1", "js-md5": "^0.4.1", "jsdoc": "^3.4.2", diff --git a/scripts/jsdoc.conf.json b/scripts/jsdoc.conf.json index 553e61c..3f52180 100644 --- a/scripts/jsdoc.conf.json +++ b/scripts/jsdoc.conf.json @@ -43,7 +43,7 @@ }, "markdown" : { "parser" : "gfm", - "hardwrap" : true + "hardwrap" : false }, "opts": { "encoding" : "utf8", diff --git a/scripts/release.js b/scripts/release.js deleted file mode 100644 index 156620c..0000000 --- a/scripts/release.js +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env node -'use strict'; - -// Publish script to push releases of the bin files -// the normally are gitignored -const ghpages = require('gh-pages'); -const path = require('path'); -const packageInfo = require(path.join(__dirname, '..', 'package.json')); - -const options = { - src: [ - 'dist/**/*', - 'lib/**/*', - 'src/**/*', - 'scripts/*', - 'scripts/renders/*', - 'test/**/*', - '*.json', - '*.md', - 'LICENSE', - '.eslintrc', - '.editorconfig', - '.travis.yml', - '.babelrc', - ], - dotfiles: true, - branch: 'release', - message: packageInfo.version, - logger: console.log.bind(console), -}; - -ghpages.publish(process.cwd(), options, (err) => -{ - if (err) - { - console.log(err); - process.exit(1); - - return; - } - - process.exit(0); -}); diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index f16bcb1..220427a 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -21,7 +21,7 @@ const DIV_HOOK_ZINDEX = 2; /** - * The Accessibility manager reacreates the ability to tab and and have content read by screen + * The Accessibility manager recreates the ability to tab and and have content read by screen * readers. This is very important as it can possibly help people with disabilities access pixi * content. * diff --git a/src/accessibility/index.js b/src/accessibility/index.js index 568cfb1..0ae108f 100644 --- a/src/accessibility/index.js +++ b/src/accessibility/index.js @@ -1,4 +1,9 @@ /** + * This namespace contains a renderer plugin for interaction accessibility for end-users + * with physical impairments which require screen-renders, keyboard navigation, etc. + * + * Do not instantiate this plugin directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. * @namespace PIXI.accessibility */ export { default as accessibleTarget } from './accessibleTarget'; diff --git a/.travis.yml b/.travis.yml index 3df8b22..14de334 100644 --- a/.travis.yml +++ b/.travis.yml @@ -110,3 +110,43 @@ on: all_branches: true condition: $TRAVIS_TAG + # Deploy config for latest release + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: dist + upload-dir: "release" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: docs + upload-dir: "release/docs" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: coverage + upload-dir: "release/coverage" + on: + all_branches: true + condition: $TRAVIS_TAG \ No newline at end of file diff --git a/README.md b/README.md index d26fadd..3b85f85 100644 --- a/README.md +++ b/README.md @@ -36,22 +36,14 @@ - Wiki: Other misc tutorials and resources are [on the Wiki](https://github.com/pixijs/pixi.js/wiki/Resources). ### Community ### -- Forums: Check out the [forum] (http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow] (http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. +- Forums: Check out the [forum](http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow](http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. - Inspiration: Check out the [gallery](http://www.pixijs.com/gallery) to see some of the amazing things people have created! - Chat: You can join us on [Gitter](https://gitter.im/pixijs/pixi.js) To chat about Pixi. We also now have a Slack channel. If you would like to join it please Send me an email (mat@goodboydigital.com) and I will invite you in. ### Setup ### -It's easy to get started with Pixi.js! Simply grab the pre-built versions from here: - -Release Branch - Nice and stable Pixi.js -- Unminified: [http://pixijs.download/release/pixi.js] -- Minified: [http://pixijs.download/release/pixi.min.js] - -Develop Branch - The bleeding edge version of Pixi.js -- Unminified: [http://pixijs.download/dev/pixi.js] -- Minified: [http://pixijs.download/dev/pixi.min.js] +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. @@ -70,10 +62,10 @@ #### CDN Install (via cdnjs) ```html - + ``` -_Note: `4.2.2` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ +_Note: `4.4.3` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ ### Demos ### @@ -130,7 +122,7 @@ // and the root stage PIXI.Container. var app = new PIXI.Application(); -// The application will create a canvas element for you that you +// The application will create a canvas element for you that you // can then insert into the DOM. document.body.appendChild(app.view); @@ -150,7 +142,7 @@ // Add the bunny to the scene we are building. app.stage.addChild(bunny); - + // Listen for frame updates app.ticker.add(function() { // each frame we spin the bunny around a bit diff --git a/bower.json b/bower.json deleted file mode 100644 index 41b668c..0000000 --- a/bower.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "pixi.js", - "main": "dist/pixi.min.js", - "ignore": [ - "**/.*", - "docs", - "node_modules", - "bower_components", - "test", - "scripts", - "bower.json", - "inch.json" - ], - "dependencies": { - }, - "devDependencies": { - } -} diff --git a/package.json b/package.json index d35cbb2..308469f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.3.5", + "version": "4.4.4", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ @@ -40,11 +40,11 @@ "lib": "babel src --out-dir lib -s", "predocs": "rimraf docs/**", "docs": "jsdoc -c scripts/jsdoc.conf.json -R README.md", - "publish:patch": "npm version patch --no-git-tag-version && npm publish", - "publish:minor": "npm version minor --no-git-tag-version && npm publish", - "publish:major": "npm version major --no-git-tag-version && npm publish", + "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", - "postpublish": "node scripts/release.js" + "postpublish": "git push && git push --tags" }, "files": [ "dist/", @@ -72,7 +72,6 @@ "electron": "^1.4.15", "eslint": "^3.5.0", "floss": "^2.0.1", - "gh-pages": "^0.11.0", "jaguarjs-jsdoc": "^1.0.1", "js-md5": "^0.4.1", "jsdoc": "^3.4.2", diff --git a/scripts/jsdoc.conf.json b/scripts/jsdoc.conf.json index 553e61c..3f52180 100644 --- a/scripts/jsdoc.conf.json +++ b/scripts/jsdoc.conf.json @@ -43,7 +43,7 @@ }, "markdown" : { "parser" : "gfm", - "hardwrap" : true + "hardwrap" : false }, "opts": { "encoding" : "utf8", diff --git a/scripts/release.js b/scripts/release.js deleted file mode 100644 index 156620c..0000000 --- a/scripts/release.js +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env node -'use strict'; - -// Publish script to push releases of the bin files -// the normally are gitignored -const ghpages = require('gh-pages'); -const path = require('path'); -const packageInfo = require(path.join(__dirname, '..', 'package.json')); - -const options = { - src: [ - 'dist/**/*', - 'lib/**/*', - 'src/**/*', - 'scripts/*', - 'scripts/renders/*', - 'test/**/*', - '*.json', - '*.md', - 'LICENSE', - '.eslintrc', - '.editorconfig', - '.travis.yml', - '.babelrc', - ], - dotfiles: true, - branch: 'release', - message: packageInfo.version, - logger: console.log.bind(console), -}; - -ghpages.publish(process.cwd(), options, (err) => -{ - if (err) - { - console.log(err); - process.exit(1); - - return; - } - - process.exit(0); -}); diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index f16bcb1..220427a 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -21,7 +21,7 @@ const DIV_HOOK_ZINDEX = 2; /** - * The Accessibility manager reacreates the ability to tab and and have content read by screen + * The Accessibility manager recreates the ability to tab and and have content read by screen * readers. This is very important as it can possibly help people with disabilities access pixi * content. * diff --git a/src/accessibility/index.js b/src/accessibility/index.js index 568cfb1..0ae108f 100644 --- a/src/accessibility/index.js +++ b/src/accessibility/index.js @@ -1,4 +1,9 @@ /** + * This namespace contains a renderer plugin for interaction accessibility for end-users + * with physical impairments which require screen-renders, keyboard navigation, etc. + * + * Do not instantiate this plugin directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. * @namespace PIXI.accessibility */ export { default as accessibleTarget } from './accessibleTarget'; diff --git a/src/core/Application.js b/src/core/Application.js index 5340578..2fcc729 100644 --- a/src/core/Application.js +++ b/src/core/Application.js @@ -1,6 +1,8 @@ import { autoDetectRenderer } from './autoDetectRenderer'; import Container from './display/Container'; import { shared, Ticker } from './ticker'; +import settings from './settings'; +import { UPDATE_PRIORITY } from './const'; /** * Convenience class to create a new PIXI application. @@ -22,28 +24,52 @@ */ export default class Application { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experience unexplained flickering try setting this to true. - * @param {boolean} [useSharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedLoader=false] - `true` to use PIXI.loaders.shared, `false` to create new Loader. */ - constructor(width, height, options, noWebGL, useSharedTicker = false) + constructor(options, arg2, arg3, arg4, arg5) { + // Support for constructor(width, height, options, noWebGL, useSharedTicker) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + forceCanvas: !!arg4, + sharedTicker: !!arg5, + }, arg3); + } + + /** + * The default options, so we mixin functionality later. + * @member {object} + * @protected + */ + this._options = options = Object.assign({ + sharedTicker: false, + forceCanvas: false, + sharedLoader: false, + }, options); + /** * WebGL renderer if available, otherwise CanvasRenderer * @member {PIXI.WebGLRenderer|PIXI.CanvasRenderer} */ - this.renderer = autoDetectRenderer(width, height, options, noWebGL); + this.renderer = autoDetectRenderer(options); /** * The root display container that's rendered. @@ -63,7 +89,7 @@ * @member {PIXI.ticker.Ticker} * @default PIXI.ticker.shared */ - this.ticker = useSharedTicker ? shared : new Ticker(); + this.ticker = options.sharedTicker ? shared : new Ticker(); // Start the rendering this.start(); @@ -78,7 +104,7 @@ this._ticker = ticker; if (ticker) { - ticker.add(this.render, this); + ticker.add(this.render, this, UPDATE_PRIORITY.LOW); } } get ticker() // eslint-disable-line require-jsdoc @@ -136,13 +162,18 @@ */ destroy(removeView) { - this.stop(); + const oldTicker = this._ticker; + this.ticker = null; + oldTicker.destroy(); + this.stage.destroy(); this.stage = null; this.renderer.destroy(removeView); this.renderer = null; + + this._options = null; } } diff --git a/.travis.yml b/.travis.yml index 3df8b22..14de334 100644 --- a/.travis.yml +++ b/.travis.yml @@ -110,3 +110,43 @@ on: all_branches: true condition: $TRAVIS_TAG + # Deploy config for latest release + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: dist + upload-dir: "release" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: docs + upload-dir: "release/docs" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: coverage + upload-dir: "release/coverage" + on: + all_branches: true + condition: $TRAVIS_TAG \ No newline at end of file diff --git a/README.md b/README.md index d26fadd..3b85f85 100644 --- a/README.md +++ b/README.md @@ -36,22 +36,14 @@ - Wiki: Other misc tutorials and resources are [on the Wiki](https://github.com/pixijs/pixi.js/wiki/Resources). ### Community ### -- Forums: Check out the [forum] (http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow] (http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. +- Forums: Check out the [forum](http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow](http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. - Inspiration: Check out the [gallery](http://www.pixijs.com/gallery) to see some of the amazing things people have created! - Chat: You can join us on [Gitter](https://gitter.im/pixijs/pixi.js) To chat about Pixi. We also now have a Slack channel. If you would like to join it please Send me an email (mat@goodboydigital.com) and I will invite you in. ### Setup ### -It's easy to get started with Pixi.js! Simply grab the pre-built versions from here: - -Release Branch - Nice and stable Pixi.js -- Unminified: [http://pixijs.download/release/pixi.js] -- Minified: [http://pixijs.download/release/pixi.min.js] - -Develop Branch - The bleeding edge version of Pixi.js -- Unminified: [http://pixijs.download/dev/pixi.js] -- Minified: [http://pixijs.download/dev/pixi.min.js] +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. @@ -70,10 +62,10 @@ #### CDN Install (via cdnjs) ```html - + ``` -_Note: `4.2.2` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ +_Note: `4.4.3` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ ### Demos ### @@ -130,7 +122,7 @@ // and the root stage PIXI.Container. var app = new PIXI.Application(); -// The application will create a canvas element for you that you +// The application will create a canvas element for you that you // can then insert into the DOM. document.body.appendChild(app.view); @@ -150,7 +142,7 @@ // Add the bunny to the scene we are building. app.stage.addChild(bunny); - + // Listen for frame updates app.ticker.add(function() { // each frame we spin the bunny around a bit diff --git a/bower.json b/bower.json deleted file mode 100644 index 41b668c..0000000 --- a/bower.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "pixi.js", - "main": "dist/pixi.min.js", - "ignore": [ - "**/.*", - "docs", - "node_modules", - "bower_components", - "test", - "scripts", - "bower.json", - "inch.json" - ], - "dependencies": { - }, - "devDependencies": { - } -} diff --git a/package.json b/package.json index d35cbb2..308469f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.3.5", + "version": "4.4.4", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ @@ -40,11 +40,11 @@ "lib": "babel src --out-dir lib -s", "predocs": "rimraf docs/**", "docs": "jsdoc -c scripts/jsdoc.conf.json -R README.md", - "publish:patch": "npm version patch --no-git-tag-version && npm publish", - "publish:minor": "npm version minor --no-git-tag-version && npm publish", - "publish:major": "npm version major --no-git-tag-version && npm publish", + "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", - "postpublish": "node scripts/release.js" + "postpublish": "git push && git push --tags" }, "files": [ "dist/", @@ -72,7 +72,6 @@ "electron": "^1.4.15", "eslint": "^3.5.0", "floss": "^2.0.1", - "gh-pages": "^0.11.0", "jaguarjs-jsdoc": "^1.0.1", "js-md5": "^0.4.1", "jsdoc": "^3.4.2", diff --git a/scripts/jsdoc.conf.json b/scripts/jsdoc.conf.json index 553e61c..3f52180 100644 --- a/scripts/jsdoc.conf.json +++ b/scripts/jsdoc.conf.json @@ -43,7 +43,7 @@ }, "markdown" : { "parser" : "gfm", - "hardwrap" : true + "hardwrap" : false }, "opts": { "encoding" : "utf8", diff --git a/scripts/release.js b/scripts/release.js deleted file mode 100644 index 156620c..0000000 --- a/scripts/release.js +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env node -'use strict'; - -// Publish script to push releases of the bin files -// the normally are gitignored -const ghpages = require('gh-pages'); -const path = require('path'); -const packageInfo = require(path.join(__dirname, '..', 'package.json')); - -const options = { - src: [ - 'dist/**/*', - 'lib/**/*', - 'src/**/*', - 'scripts/*', - 'scripts/renders/*', - 'test/**/*', - '*.json', - '*.md', - 'LICENSE', - '.eslintrc', - '.editorconfig', - '.travis.yml', - '.babelrc', - ], - dotfiles: true, - branch: 'release', - message: packageInfo.version, - logger: console.log.bind(console), -}; - -ghpages.publish(process.cwd(), options, (err) => -{ - if (err) - { - console.log(err); - process.exit(1); - - return; - } - - process.exit(0); -}); diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index f16bcb1..220427a 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -21,7 +21,7 @@ const DIV_HOOK_ZINDEX = 2; /** - * The Accessibility manager reacreates the ability to tab and and have content read by screen + * The Accessibility manager recreates the ability to tab and and have content read by screen * readers. This is very important as it can possibly help people with disabilities access pixi * content. * diff --git a/src/accessibility/index.js b/src/accessibility/index.js index 568cfb1..0ae108f 100644 --- a/src/accessibility/index.js +++ b/src/accessibility/index.js @@ -1,4 +1,9 @@ /** + * This namespace contains a renderer plugin for interaction accessibility for end-users + * with physical impairments which require screen-renders, keyboard navigation, etc. + * + * Do not instantiate this plugin directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. * @namespace PIXI.accessibility */ export { default as accessibleTarget } from './accessibleTarget'; diff --git a/src/core/Application.js b/src/core/Application.js index 5340578..2fcc729 100644 --- a/src/core/Application.js +++ b/src/core/Application.js @@ -1,6 +1,8 @@ import { autoDetectRenderer } from './autoDetectRenderer'; import Container from './display/Container'; import { shared, Ticker } from './ticker'; +import settings from './settings'; +import { UPDATE_PRIORITY } from './const'; /** * Convenience class to create a new PIXI application. @@ -22,28 +24,52 @@ */ export default class Application { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experience unexplained flickering try setting this to true. - * @param {boolean} [useSharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedLoader=false] - `true` to use PIXI.loaders.shared, `false` to create new Loader. */ - constructor(width, height, options, noWebGL, useSharedTicker = false) + constructor(options, arg2, arg3, arg4, arg5) { + // Support for constructor(width, height, options, noWebGL, useSharedTicker) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + forceCanvas: !!arg4, + sharedTicker: !!arg5, + }, arg3); + } + + /** + * The default options, so we mixin functionality later. + * @member {object} + * @protected + */ + this._options = options = Object.assign({ + sharedTicker: false, + forceCanvas: false, + sharedLoader: false, + }, options); + /** * WebGL renderer if available, otherwise CanvasRenderer * @member {PIXI.WebGLRenderer|PIXI.CanvasRenderer} */ - this.renderer = autoDetectRenderer(width, height, options, noWebGL); + this.renderer = autoDetectRenderer(options); /** * The root display container that's rendered. @@ -63,7 +89,7 @@ * @member {PIXI.ticker.Ticker} * @default PIXI.ticker.shared */ - this.ticker = useSharedTicker ? shared : new Ticker(); + this.ticker = options.sharedTicker ? shared : new Ticker(); // Start the rendering this.start(); @@ -78,7 +104,7 @@ this._ticker = ticker; if (ticker) { - ticker.add(this.render, this); + ticker.add(this.render, this, UPDATE_PRIORITY.LOW); } } get ticker() // eslint-disable-line require-jsdoc @@ -136,13 +162,18 @@ */ destroy(removeView) { - this.stop(); + const oldTicker = this._ticker; + this.ticker = null; + oldTicker.destroy(); + this.stage.destroy(); this.stage = null; this.renderer.destroy(removeView); this.renderer = null; + + this._options = null; } } diff --git a/src/core/autoDetectRenderer.js b/src/core/autoDetectRenderer.js index 8960425..9ec0364 100644 --- a/src/core/autoDetectRenderer.js +++ b/src/core/autoDetectRenderer.js @@ -2,6 +2,7 @@ import CanvasRenderer from './renderers/canvas/CanvasRenderer'; import WebGLRenderer from './renderers/webgl/WebGLRenderer'; +// eslint-disable-next-line valid-jsdoc /** * This helper function will automatically detect which renderer you should be using. * WebGL is the preferred renderer as it is a lot faster. If webGL is not supported by @@ -9,24 +10,32 @@ * * @memberof PIXI * @function autoDetectRenderer - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @return {PIXI.WebGLRenderer|PIXI.CanvasRenderer} Returns WebGL renderer if available, otherwise CanvasRenderer */ -export function autoDetectRenderer(width = 800, height = 600, options, noWebGL) +export function autoDetectRenderer(options, arg1, arg2, arg3) { - if (!noWebGL && utils.isWebGLSupported()) + // Backward-compatible support for noWebGL option + let forceCanvas = options && options.forceCanvas; + + if (arg3 !== undefined) { - return new WebGLRenderer(width, height, options); + forceCanvas = arg3; } - return new CanvasRenderer(width, height, options); + if (!forceCanvas && utils.isWebGLSupported()) + { + return new WebGLRenderer(options, arg1, arg2); + } + + return new CanvasRenderer(options, arg1, arg2); } diff --git a/.travis.yml b/.travis.yml index 3df8b22..14de334 100644 --- a/.travis.yml +++ b/.travis.yml @@ -110,3 +110,43 @@ on: all_branches: true condition: $TRAVIS_TAG + # Deploy config for latest release + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: dist + upload-dir: "release" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: docs + upload-dir: "release/docs" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: coverage + upload-dir: "release/coverage" + on: + all_branches: true + condition: $TRAVIS_TAG \ No newline at end of file diff --git a/README.md b/README.md index d26fadd..3b85f85 100644 --- a/README.md +++ b/README.md @@ -36,22 +36,14 @@ - Wiki: Other misc tutorials and resources are [on the Wiki](https://github.com/pixijs/pixi.js/wiki/Resources). ### Community ### -- Forums: Check out the [forum] (http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow] (http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. +- Forums: Check out the [forum](http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow](http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. - Inspiration: Check out the [gallery](http://www.pixijs.com/gallery) to see some of the amazing things people have created! - Chat: You can join us on [Gitter](https://gitter.im/pixijs/pixi.js) To chat about Pixi. We also now have a Slack channel. If you would like to join it please Send me an email (mat@goodboydigital.com) and I will invite you in. ### Setup ### -It's easy to get started with Pixi.js! Simply grab the pre-built versions from here: - -Release Branch - Nice and stable Pixi.js -- Unminified: [http://pixijs.download/release/pixi.js] -- Minified: [http://pixijs.download/release/pixi.min.js] - -Develop Branch - The bleeding edge version of Pixi.js -- Unminified: [http://pixijs.download/dev/pixi.js] -- Minified: [http://pixijs.download/dev/pixi.min.js] +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. @@ -70,10 +62,10 @@ #### CDN Install (via cdnjs) ```html - + ``` -_Note: `4.2.2` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ +_Note: `4.4.3` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ ### Demos ### @@ -130,7 +122,7 @@ // and the root stage PIXI.Container. var app = new PIXI.Application(); -// The application will create a canvas element for you that you +// The application will create a canvas element for you that you // can then insert into the DOM. document.body.appendChild(app.view); @@ -150,7 +142,7 @@ // Add the bunny to the scene we are building. app.stage.addChild(bunny); - + // Listen for frame updates app.ticker.add(function() { // each frame we spin the bunny around a bit diff --git a/bower.json b/bower.json deleted file mode 100644 index 41b668c..0000000 --- a/bower.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "pixi.js", - "main": "dist/pixi.min.js", - "ignore": [ - "**/.*", - "docs", - "node_modules", - "bower_components", - "test", - "scripts", - "bower.json", - "inch.json" - ], - "dependencies": { - }, - "devDependencies": { - } -} diff --git a/package.json b/package.json index d35cbb2..308469f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.3.5", + "version": "4.4.4", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ @@ -40,11 +40,11 @@ "lib": "babel src --out-dir lib -s", "predocs": "rimraf docs/**", "docs": "jsdoc -c scripts/jsdoc.conf.json -R README.md", - "publish:patch": "npm version patch --no-git-tag-version && npm publish", - "publish:minor": "npm version minor --no-git-tag-version && npm publish", - "publish:major": "npm version major --no-git-tag-version && npm publish", + "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", - "postpublish": "node scripts/release.js" + "postpublish": "git push && git push --tags" }, "files": [ "dist/", @@ -72,7 +72,6 @@ "electron": "^1.4.15", "eslint": "^3.5.0", "floss": "^2.0.1", - "gh-pages": "^0.11.0", "jaguarjs-jsdoc": "^1.0.1", "js-md5": "^0.4.1", "jsdoc": "^3.4.2", diff --git a/scripts/jsdoc.conf.json b/scripts/jsdoc.conf.json index 553e61c..3f52180 100644 --- a/scripts/jsdoc.conf.json +++ b/scripts/jsdoc.conf.json @@ -43,7 +43,7 @@ }, "markdown" : { "parser" : "gfm", - "hardwrap" : true + "hardwrap" : false }, "opts": { "encoding" : "utf8", diff --git a/scripts/release.js b/scripts/release.js deleted file mode 100644 index 156620c..0000000 --- a/scripts/release.js +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env node -'use strict'; - -// Publish script to push releases of the bin files -// the normally are gitignored -const ghpages = require('gh-pages'); -const path = require('path'); -const packageInfo = require(path.join(__dirname, '..', 'package.json')); - -const options = { - src: [ - 'dist/**/*', - 'lib/**/*', - 'src/**/*', - 'scripts/*', - 'scripts/renders/*', - 'test/**/*', - '*.json', - '*.md', - 'LICENSE', - '.eslintrc', - '.editorconfig', - '.travis.yml', - '.babelrc', - ], - dotfiles: true, - branch: 'release', - message: packageInfo.version, - logger: console.log.bind(console), -}; - -ghpages.publish(process.cwd(), options, (err) => -{ - if (err) - { - console.log(err); - process.exit(1); - - return; - } - - process.exit(0); -}); diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index f16bcb1..220427a 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -21,7 +21,7 @@ const DIV_HOOK_ZINDEX = 2; /** - * The Accessibility manager reacreates the ability to tab and and have content read by screen + * The Accessibility manager recreates the ability to tab and and have content read by screen * readers. This is very important as it can possibly help people with disabilities access pixi * content. * diff --git a/src/accessibility/index.js b/src/accessibility/index.js index 568cfb1..0ae108f 100644 --- a/src/accessibility/index.js +++ b/src/accessibility/index.js @@ -1,4 +1,9 @@ /** + * This namespace contains a renderer plugin for interaction accessibility for end-users + * with physical impairments which require screen-renders, keyboard navigation, etc. + * + * Do not instantiate this plugin directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. * @namespace PIXI.accessibility */ export { default as accessibleTarget } from './accessibleTarget'; diff --git a/src/core/Application.js b/src/core/Application.js index 5340578..2fcc729 100644 --- a/src/core/Application.js +++ b/src/core/Application.js @@ -1,6 +1,8 @@ import { autoDetectRenderer } from './autoDetectRenderer'; import Container from './display/Container'; import { shared, Ticker } from './ticker'; +import settings from './settings'; +import { UPDATE_PRIORITY } from './const'; /** * Convenience class to create a new PIXI application. @@ -22,28 +24,52 @@ */ export default class Application { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experience unexplained flickering try setting this to true. - * @param {boolean} [useSharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedLoader=false] - `true` to use PIXI.loaders.shared, `false` to create new Loader. */ - constructor(width, height, options, noWebGL, useSharedTicker = false) + constructor(options, arg2, arg3, arg4, arg5) { + // Support for constructor(width, height, options, noWebGL, useSharedTicker) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + forceCanvas: !!arg4, + sharedTicker: !!arg5, + }, arg3); + } + + /** + * The default options, so we mixin functionality later. + * @member {object} + * @protected + */ + this._options = options = Object.assign({ + sharedTicker: false, + forceCanvas: false, + sharedLoader: false, + }, options); + /** * WebGL renderer if available, otherwise CanvasRenderer * @member {PIXI.WebGLRenderer|PIXI.CanvasRenderer} */ - this.renderer = autoDetectRenderer(width, height, options, noWebGL); + this.renderer = autoDetectRenderer(options); /** * The root display container that's rendered. @@ -63,7 +89,7 @@ * @member {PIXI.ticker.Ticker} * @default PIXI.ticker.shared */ - this.ticker = useSharedTicker ? shared : new Ticker(); + this.ticker = options.sharedTicker ? shared : new Ticker(); // Start the rendering this.start(); @@ -78,7 +104,7 @@ this._ticker = ticker; if (ticker) { - ticker.add(this.render, this); + ticker.add(this.render, this, UPDATE_PRIORITY.LOW); } } get ticker() // eslint-disable-line require-jsdoc @@ -136,13 +162,18 @@ */ destroy(removeView) { - this.stop(); + const oldTicker = this._ticker; + this.ticker = null; + oldTicker.destroy(); + this.stage.destroy(); this.stage = null; this.renderer.destroy(removeView); this.renderer = null; + + this._options = null; } } diff --git a/src/core/autoDetectRenderer.js b/src/core/autoDetectRenderer.js index 8960425..9ec0364 100644 --- a/src/core/autoDetectRenderer.js +++ b/src/core/autoDetectRenderer.js @@ -2,6 +2,7 @@ import CanvasRenderer from './renderers/canvas/CanvasRenderer'; import WebGLRenderer from './renderers/webgl/WebGLRenderer'; +// eslint-disable-next-line valid-jsdoc /** * This helper function will automatically detect which renderer you should be using. * WebGL is the preferred renderer as it is a lot faster. If webGL is not supported by @@ -9,24 +10,32 @@ * * @memberof PIXI * @function autoDetectRenderer - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @return {PIXI.WebGLRenderer|PIXI.CanvasRenderer} Returns WebGL renderer if available, otherwise CanvasRenderer */ -export function autoDetectRenderer(width = 800, height = 600, options, noWebGL) +export function autoDetectRenderer(options, arg1, arg2, arg3) { - if (!noWebGL && utils.isWebGLSupported()) + // Backward-compatible support for noWebGL option + let forceCanvas = options && options.forceCanvas; + + if (arg3 !== undefined) { - return new WebGLRenderer(width, height, options); + forceCanvas = arg3; } - return new CanvasRenderer(width, height, options); + if (!forceCanvas && utils.isWebGLSupported()) + { + return new WebGLRenderer(options, arg1, arg2); + } + + return new CanvasRenderer(options, arg1, arg2); } diff --git a/src/core/const.js b/src/core/const.js index b9e99ae..2c68dee 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -341,3 +341,27 @@ LINEAR_VERTICAL: 0, LINEAR_HORIZONTAL: 1, }; + +/** + * Represents the update priorities used by internal PIXI classes when registered with + * the {@link PIXI.ticker.Ticker} object. Higher priority items are updated first and lower + * priority items, such as render, should go later. + * + * @static + * @constant + * @name UPDATE_PRIORITY + * @memberof PIXI + * @type {object} + * @property {number} INTERACTION=50 Highest priority, used for {@link PIXI.interaction.InteractionManager} + * @property {number} HIGH=25 High priority updating, {@link PIXI.VideoBaseTexture} and {@link PIXI.extras.AnimatedSprite} + * @property {number} NORMAL=0 Default priority for ticker events, see {@link PIXI.ticker.Ticker#add}. + * @property {number} LOW=-25 Low priority used for {@link PIXI.Application} rendering. + * @property {number} UTILITY=-50 Lowest priority used for {@link PIXI.prepare.BasePrepare} utility. + */ +export const UPDATE_PRIORITY = { + INTERACTION: 50, + HIGH: 25, + NORMAL: 0, + LOW: -25, + UTILITY: -50, +}; diff --git a/.travis.yml b/.travis.yml index 3df8b22..14de334 100644 --- a/.travis.yml +++ b/.travis.yml @@ -110,3 +110,43 @@ on: all_branches: true condition: $TRAVIS_TAG + # Deploy config for latest release + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: dist + upload-dir: "release" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: docs + upload-dir: "release/docs" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: coverage + upload-dir: "release/coverage" + on: + all_branches: true + condition: $TRAVIS_TAG \ No newline at end of file diff --git a/README.md b/README.md index d26fadd..3b85f85 100644 --- a/README.md +++ b/README.md @@ -36,22 +36,14 @@ - Wiki: Other misc tutorials and resources are [on the Wiki](https://github.com/pixijs/pixi.js/wiki/Resources). ### Community ### -- Forums: Check out the [forum] (http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow] (http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. +- Forums: Check out the [forum](http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow](http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. - Inspiration: Check out the [gallery](http://www.pixijs.com/gallery) to see some of the amazing things people have created! - Chat: You can join us on [Gitter](https://gitter.im/pixijs/pixi.js) To chat about Pixi. We also now have a Slack channel. If you would like to join it please Send me an email (mat@goodboydigital.com) and I will invite you in. ### Setup ### -It's easy to get started with Pixi.js! Simply grab the pre-built versions from here: - -Release Branch - Nice and stable Pixi.js -- Unminified: [http://pixijs.download/release/pixi.js] -- Minified: [http://pixijs.download/release/pixi.min.js] - -Develop Branch - The bleeding edge version of Pixi.js -- Unminified: [http://pixijs.download/dev/pixi.js] -- Minified: [http://pixijs.download/dev/pixi.min.js] +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. @@ -70,10 +62,10 @@ #### CDN Install (via cdnjs) ```html - + ``` -_Note: `4.2.2` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ +_Note: `4.4.3` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ ### Demos ### @@ -130,7 +122,7 @@ // and the root stage PIXI.Container. var app = new PIXI.Application(); -// The application will create a canvas element for you that you +// The application will create a canvas element for you that you // can then insert into the DOM. document.body.appendChild(app.view); @@ -150,7 +142,7 @@ // Add the bunny to the scene we are building. app.stage.addChild(bunny); - + // Listen for frame updates app.ticker.add(function() { // each frame we spin the bunny around a bit diff --git a/bower.json b/bower.json deleted file mode 100644 index 41b668c..0000000 --- a/bower.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "pixi.js", - "main": "dist/pixi.min.js", - "ignore": [ - "**/.*", - "docs", - "node_modules", - "bower_components", - "test", - "scripts", - "bower.json", - "inch.json" - ], - "dependencies": { - }, - "devDependencies": { - } -} diff --git a/package.json b/package.json index d35cbb2..308469f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.3.5", + "version": "4.4.4", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ @@ -40,11 +40,11 @@ "lib": "babel src --out-dir lib -s", "predocs": "rimraf docs/**", "docs": "jsdoc -c scripts/jsdoc.conf.json -R README.md", - "publish:patch": "npm version patch --no-git-tag-version && npm publish", - "publish:minor": "npm version minor --no-git-tag-version && npm publish", - "publish:major": "npm version major --no-git-tag-version && npm publish", + "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", - "postpublish": "node scripts/release.js" + "postpublish": "git push && git push --tags" }, "files": [ "dist/", @@ -72,7 +72,6 @@ "electron": "^1.4.15", "eslint": "^3.5.0", "floss": "^2.0.1", - "gh-pages": "^0.11.0", "jaguarjs-jsdoc": "^1.0.1", "js-md5": "^0.4.1", "jsdoc": "^3.4.2", diff --git a/scripts/jsdoc.conf.json b/scripts/jsdoc.conf.json index 553e61c..3f52180 100644 --- a/scripts/jsdoc.conf.json +++ b/scripts/jsdoc.conf.json @@ -43,7 +43,7 @@ }, "markdown" : { "parser" : "gfm", - "hardwrap" : true + "hardwrap" : false }, "opts": { "encoding" : "utf8", diff --git a/scripts/release.js b/scripts/release.js deleted file mode 100644 index 156620c..0000000 --- a/scripts/release.js +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env node -'use strict'; - -// Publish script to push releases of the bin files -// the normally are gitignored -const ghpages = require('gh-pages'); -const path = require('path'); -const packageInfo = require(path.join(__dirname, '..', 'package.json')); - -const options = { - src: [ - 'dist/**/*', - 'lib/**/*', - 'src/**/*', - 'scripts/*', - 'scripts/renders/*', - 'test/**/*', - '*.json', - '*.md', - 'LICENSE', - '.eslintrc', - '.editorconfig', - '.travis.yml', - '.babelrc', - ], - dotfiles: true, - branch: 'release', - message: packageInfo.version, - logger: console.log.bind(console), -}; - -ghpages.publish(process.cwd(), options, (err) => -{ - if (err) - { - console.log(err); - process.exit(1); - - return; - } - - process.exit(0); -}); diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index f16bcb1..220427a 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -21,7 +21,7 @@ const DIV_HOOK_ZINDEX = 2; /** - * The Accessibility manager reacreates the ability to tab and and have content read by screen + * The Accessibility manager recreates the ability to tab and and have content read by screen * readers. This is very important as it can possibly help people with disabilities access pixi * content. * diff --git a/src/accessibility/index.js b/src/accessibility/index.js index 568cfb1..0ae108f 100644 --- a/src/accessibility/index.js +++ b/src/accessibility/index.js @@ -1,4 +1,9 @@ /** + * This namespace contains a renderer plugin for interaction accessibility for end-users + * with physical impairments which require screen-renders, keyboard navigation, etc. + * + * Do not instantiate this plugin directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. * @namespace PIXI.accessibility */ export { default as accessibleTarget } from './accessibleTarget'; diff --git a/src/core/Application.js b/src/core/Application.js index 5340578..2fcc729 100644 --- a/src/core/Application.js +++ b/src/core/Application.js @@ -1,6 +1,8 @@ import { autoDetectRenderer } from './autoDetectRenderer'; import Container from './display/Container'; import { shared, Ticker } from './ticker'; +import settings from './settings'; +import { UPDATE_PRIORITY } from './const'; /** * Convenience class to create a new PIXI application. @@ -22,28 +24,52 @@ */ export default class Application { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experience unexplained flickering try setting this to true. - * @param {boolean} [useSharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedLoader=false] - `true` to use PIXI.loaders.shared, `false` to create new Loader. */ - constructor(width, height, options, noWebGL, useSharedTicker = false) + constructor(options, arg2, arg3, arg4, arg5) { + // Support for constructor(width, height, options, noWebGL, useSharedTicker) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + forceCanvas: !!arg4, + sharedTicker: !!arg5, + }, arg3); + } + + /** + * The default options, so we mixin functionality later. + * @member {object} + * @protected + */ + this._options = options = Object.assign({ + sharedTicker: false, + forceCanvas: false, + sharedLoader: false, + }, options); + /** * WebGL renderer if available, otherwise CanvasRenderer * @member {PIXI.WebGLRenderer|PIXI.CanvasRenderer} */ - this.renderer = autoDetectRenderer(width, height, options, noWebGL); + this.renderer = autoDetectRenderer(options); /** * The root display container that's rendered. @@ -63,7 +89,7 @@ * @member {PIXI.ticker.Ticker} * @default PIXI.ticker.shared */ - this.ticker = useSharedTicker ? shared : new Ticker(); + this.ticker = options.sharedTicker ? shared : new Ticker(); // Start the rendering this.start(); @@ -78,7 +104,7 @@ this._ticker = ticker; if (ticker) { - ticker.add(this.render, this); + ticker.add(this.render, this, UPDATE_PRIORITY.LOW); } } get ticker() // eslint-disable-line require-jsdoc @@ -136,13 +162,18 @@ */ destroy(removeView) { - this.stop(); + const oldTicker = this._ticker; + this.ticker = null; + oldTicker.destroy(); + this.stage.destroy(); this.stage = null; this.renderer.destroy(removeView); this.renderer = null; + + this._options = null; } } diff --git a/src/core/autoDetectRenderer.js b/src/core/autoDetectRenderer.js index 8960425..9ec0364 100644 --- a/src/core/autoDetectRenderer.js +++ b/src/core/autoDetectRenderer.js @@ -2,6 +2,7 @@ import CanvasRenderer from './renderers/canvas/CanvasRenderer'; import WebGLRenderer from './renderers/webgl/WebGLRenderer'; +// eslint-disable-next-line valid-jsdoc /** * This helper function will automatically detect which renderer you should be using. * WebGL is the preferred renderer as it is a lot faster. If webGL is not supported by @@ -9,24 +10,32 @@ * * @memberof PIXI * @function autoDetectRenderer - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @return {PIXI.WebGLRenderer|PIXI.CanvasRenderer} Returns WebGL renderer if available, otherwise CanvasRenderer */ -export function autoDetectRenderer(width = 800, height = 600, options, noWebGL) +export function autoDetectRenderer(options, arg1, arg2, arg3) { - if (!noWebGL && utils.isWebGLSupported()) + // Backward-compatible support for noWebGL option + let forceCanvas = options && options.forceCanvas; + + if (arg3 !== undefined) { - return new WebGLRenderer(width, height, options); + forceCanvas = arg3; } - return new CanvasRenderer(width, height, options); + if (!forceCanvas && utils.isWebGLSupported()) + { + return new WebGLRenderer(options, arg1, arg2); + } + + return new CanvasRenderer(options, arg1, arg2); } diff --git a/src/core/const.js b/src/core/const.js index b9e99ae..2c68dee 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -341,3 +341,27 @@ LINEAR_VERTICAL: 0, LINEAR_HORIZONTAL: 1, }; + +/** + * Represents the update priorities used by internal PIXI classes when registered with + * the {@link PIXI.ticker.Ticker} object. Higher priority items are updated first and lower + * priority items, such as render, should go later. + * + * @static + * @constant + * @name UPDATE_PRIORITY + * @memberof PIXI + * @type {object} + * @property {number} INTERACTION=50 Highest priority, used for {@link PIXI.interaction.InteractionManager} + * @property {number} HIGH=25 High priority updating, {@link PIXI.VideoBaseTexture} and {@link PIXI.extras.AnimatedSprite} + * @property {number} NORMAL=0 Default priority for ticker events, see {@link PIXI.ticker.Ticker#add}. + * @property {number} LOW=-25 Low priority used for {@link PIXI.Application} rendering. + * @property {number} UTILITY=-50 Lowest priority used for {@link PIXI.prepare.BasePrepare} utility. + */ +export const UPDATE_PRIORITY = { + INTERACTION: 50, + HIGH: 25, + NORMAL: 0, + LOW: -25, + UTILITY: -50, +}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 4e6c77d..decb42a 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -13,7 +13,6 @@ * * @class * @extends EventEmitter - * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ export default class DisplayObject extends EventEmitter @@ -113,6 +112,29 @@ * @private */ this._mask = null; + + /** + * If the object has been destroyed via destroy(). If true, it should not be used. + * + * @member {boolean} + * @private + * @readonly + */ + this._destroyed = false; + + /** + * Fired when this DisplayObject is added to a Container. + * + * @event PIXI.DisplayObject#added + * @param {PIXI.Container} container - The container added to. + */ + + /** + * Fired when this DisplayObject is removed from a Container. + * + * @event PIXI.DisplayObject#removed + * @param {PIXI.Container} container - The container removed from. + */ } /** @@ -403,6 +425,8 @@ this.interactive = false; this.interactiveChildren = false; + + this._destroyed = true; } /** diff --git a/.travis.yml b/.travis.yml index 3df8b22..14de334 100644 --- a/.travis.yml +++ b/.travis.yml @@ -110,3 +110,43 @@ on: all_branches: true condition: $TRAVIS_TAG + # Deploy config for latest release + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: dist + upload-dir: "release" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: docs + upload-dir: "release/docs" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: coverage + upload-dir: "release/coverage" + on: + all_branches: true + condition: $TRAVIS_TAG \ No newline at end of file diff --git a/README.md b/README.md index d26fadd..3b85f85 100644 --- a/README.md +++ b/README.md @@ -36,22 +36,14 @@ - Wiki: Other misc tutorials and resources are [on the Wiki](https://github.com/pixijs/pixi.js/wiki/Resources). ### Community ### -- Forums: Check out the [forum] (http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow] (http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. +- Forums: Check out the [forum](http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow](http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. - Inspiration: Check out the [gallery](http://www.pixijs.com/gallery) to see some of the amazing things people have created! - Chat: You can join us on [Gitter](https://gitter.im/pixijs/pixi.js) To chat about Pixi. We also now have a Slack channel. If you would like to join it please Send me an email (mat@goodboydigital.com) and I will invite you in. ### Setup ### -It's easy to get started with Pixi.js! Simply grab the pre-built versions from here: - -Release Branch - Nice and stable Pixi.js -- Unminified: [http://pixijs.download/release/pixi.js] -- Minified: [http://pixijs.download/release/pixi.min.js] - -Develop Branch - The bleeding edge version of Pixi.js -- Unminified: [http://pixijs.download/dev/pixi.js] -- Minified: [http://pixijs.download/dev/pixi.min.js] +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. @@ -70,10 +62,10 @@ #### CDN Install (via cdnjs) ```html - + ``` -_Note: `4.2.2` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ +_Note: `4.4.3` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ ### Demos ### @@ -130,7 +122,7 @@ // and the root stage PIXI.Container. var app = new PIXI.Application(); -// The application will create a canvas element for you that you +// The application will create a canvas element for you that you // can then insert into the DOM. document.body.appendChild(app.view); @@ -150,7 +142,7 @@ // Add the bunny to the scene we are building. app.stage.addChild(bunny); - + // Listen for frame updates app.ticker.add(function() { // each frame we spin the bunny around a bit diff --git a/bower.json b/bower.json deleted file mode 100644 index 41b668c..0000000 --- a/bower.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "pixi.js", - "main": "dist/pixi.min.js", - "ignore": [ - "**/.*", - "docs", - "node_modules", - "bower_components", - "test", - "scripts", - "bower.json", - "inch.json" - ], - "dependencies": { - }, - "devDependencies": { - } -} diff --git a/package.json b/package.json index d35cbb2..308469f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.3.5", + "version": "4.4.4", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ @@ -40,11 +40,11 @@ "lib": "babel src --out-dir lib -s", "predocs": "rimraf docs/**", "docs": "jsdoc -c scripts/jsdoc.conf.json -R README.md", - "publish:patch": "npm version patch --no-git-tag-version && npm publish", - "publish:minor": "npm version minor --no-git-tag-version && npm publish", - "publish:major": "npm version major --no-git-tag-version && npm publish", + "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", - "postpublish": "node scripts/release.js" + "postpublish": "git push && git push --tags" }, "files": [ "dist/", @@ -72,7 +72,6 @@ "electron": "^1.4.15", "eslint": "^3.5.0", "floss": "^2.0.1", - "gh-pages": "^0.11.0", "jaguarjs-jsdoc": "^1.0.1", "js-md5": "^0.4.1", "jsdoc": "^3.4.2", diff --git a/scripts/jsdoc.conf.json b/scripts/jsdoc.conf.json index 553e61c..3f52180 100644 --- a/scripts/jsdoc.conf.json +++ b/scripts/jsdoc.conf.json @@ -43,7 +43,7 @@ }, "markdown" : { "parser" : "gfm", - "hardwrap" : true + "hardwrap" : false }, "opts": { "encoding" : "utf8", diff --git a/scripts/release.js b/scripts/release.js deleted file mode 100644 index 156620c..0000000 --- a/scripts/release.js +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env node -'use strict'; - -// Publish script to push releases of the bin files -// the normally are gitignored -const ghpages = require('gh-pages'); -const path = require('path'); -const packageInfo = require(path.join(__dirname, '..', 'package.json')); - -const options = { - src: [ - 'dist/**/*', - 'lib/**/*', - 'src/**/*', - 'scripts/*', - 'scripts/renders/*', - 'test/**/*', - '*.json', - '*.md', - 'LICENSE', - '.eslintrc', - '.editorconfig', - '.travis.yml', - '.babelrc', - ], - dotfiles: true, - branch: 'release', - message: packageInfo.version, - logger: console.log.bind(console), -}; - -ghpages.publish(process.cwd(), options, (err) => -{ - if (err) - { - console.log(err); - process.exit(1); - - return; - } - - process.exit(0); -}); diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index f16bcb1..220427a 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -21,7 +21,7 @@ const DIV_HOOK_ZINDEX = 2; /** - * The Accessibility manager reacreates the ability to tab and and have content read by screen + * The Accessibility manager recreates the ability to tab and and have content read by screen * readers. This is very important as it can possibly help people with disabilities access pixi * content. * diff --git a/src/accessibility/index.js b/src/accessibility/index.js index 568cfb1..0ae108f 100644 --- a/src/accessibility/index.js +++ b/src/accessibility/index.js @@ -1,4 +1,9 @@ /** + * This namespace contains a renderer plugin for interaction accessibility for end-users + * with physical impairments which require screen-renders, keyboard navigation, etc. + * + * Do not instantiate this plugin directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. * @namespace PIXI.accessibility */ export { default as accessibleTarget } from './accessibleTarget'; diff --git a/src/core/Application.js b/src/core/Application.js index 5340578..2fcc729 100644 --- a/src/core/Application.js +++ b/src/core/Application.js @@ -1,6 +1,8 @@ import { autoDetectRenderer } from './autoDetectRenderer'; import Container from './display/Container'; import { shared, Ticker } from './ticker'; +import settings from './settings'; +import { UPDATE_PRIORITY } from './const'; /** * Convenience class to create a new PIXI application. @@ -22,28 +24,52 @@ */ export default class Application { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experience unexplained flickering try setting this to true. - * @param {boolean} [useSharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedLoader=false] - `true` to use PIXI.loaders.shared, `false` to create new Loader. */ - constructor(width, height, options, noWebGL, useSharedTicker = false) + constructor(options, arg2, arg3, arg4, arg5) { + // Support for constructor(width, height, options, noWebGL, useSharedTicker) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + forceCanvas: !!arg4, + sharedTicker: !!arg5, + }, arg3); + } + + /** + * The default options, so we mixin functionality later. + * @member {object} + * @protected + */ + this._options = options = Object.assign({ + sharedTicker: false, + forceCanvas: false, + sharedLoader: false, + }, options); + /** * WebGL renderer if available, otherwise CanvasRenderer * @member {PIXI.WebGLRenderer|PIXI.CanvasRenderer} */ - this.renderer = autoDetectRenderer(width, height, options, noWebGL); + this.renderer = autoDetectRenderer(options); /** * The root display container that's rendered. @@ -63,7 +89,7 @@ * @member {PIXI.ticker.Ticker} * @default PIXI.ticker.shared */ - this.ticker = useSharedTicker ? shared : new Ticker(); + this.ticker = options.sharedTicker ? shared : new Ticker(); // Start the rendering this.start(); @@ -78,7 +104,7 @@ this._ticker = ticker; if (ticker) { - ticker.add(this.render, this); + ticker.add(this.render, this, UPDATE_PRIORITY.LOW); } } get ticker() // eslint-disable-line require-jsdoc @@ -136,13 +162,18 @@ */ destroy(removeView) { - this.stop(); + const oldTicker = this._ticker; + this.ticker = null; + oldTicker.destroy(); + this.stage.destroy(); this.stage = null; this.renderer.destroy(removeView); this.renderer = null; + + this._options = null; } } diff --git a/src/core/autoDetectRenderer.js b/src/core/autoDetectRenderer.js index 8960425..9ec0364 100644 --- a/src/core/autoDetectRenderer.js +++ b/src/core/autoDetectRenderer.js @@ -2,6 +2,7 @@ import CanvasRenderer from './renderers/canvas/CanvasRenderer'; import WebGLRenderer from './renderers/webgl/WebGLRenderer'; +// eslint-disable-next-line valid-jsdoc /** * This helper function will automatically detect which renderer you should be using. * WebGL is the preferred renderer as it is a lot faster. If webGL is not supported by @@ -9,24 +10,32 @@ * * @memberof PIXI * @function autoDetectRenderer - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @return {PIXI.WebGLRenderer|PIXI.CanvasRenderer} Returns WebGL renderer if available, otherwise CanvasRenderer */ -export function autoDetectRenderer(width = 800, height = 600, options, noWebGL) +export function autoDetectRenderer(options, arg1, arg2, arg3) { - if (!noWebGL && utils.isWebGLSupported()) + // Backward-compatible support for noWebGL option + let forceCanvas = options && options.forceCanvas; + + if (arg3 !== undefined) { - return new WebGLRenderer(width, height, options); + forceCanvas = arg3; } - return new CanvasRenderer(width, height, options); + if (!forceCanvas && utils.isWebGLSupported()) + { + return new WebGLRenderer(options, arg1, arg2); + } + + return new CanvasRenderer(options, arg1, arg2); } diff --git a/src/core/const.js b/src/core/const.js index b9e99ae..2c68dee 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -341,3 +341,27 @@ LINEAR_VERTICAL: 0, LINEAR_HORIZONTAL: 1, }; + +/** + * Represents the update priorities used by internal PIXI classes when registered with + * the {@link PIXI.ticker.Ticker} object. Higher priority items are updated first and lower + * priority items, such as render, should go later. + * + * @static + * @constant + * @name UPDATE_PRIORITY + * @memberof PIXI + * @type {object} + * @property {number} INTERACTION=50 Highest priority, used for {@link PIXI.interaction.InteractionManager} + * @property {number} HIGH=25 High priority updating, {@link PIXI.VideoBaseTexture} and {@link PIXI.extras.AnimatedSprite} + * @property {number} NORMAL=0 Default priority for ticker events, see {@link PIXI.ticker.Ticker#add}. + * @property {number} LOW=-25 Low priority used for {@link PIXI.Application} rendering. + * @property {number} UTILITY=-50 Lowest priority used for {@link PIXI.prepare.BasePrepare} utility. + */ +export const UPDATE_PRIORITY = { + INTERACTION: 50, + HIGH: 25, + NORMAL: 0, + LOW: -25, + UTILITY: -50, +}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 4e6c77d..decb42a 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -13,7 +13,6 @@ * * @class * @extends EventEmitter - * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ export default class DisplayObject extends EventEmitter @@ -113,6 +112,29 @@ * @private */ this._mask = null; + + /** + * If the object has been destroyed via destroy(). If true, it should not be used. + * + * @member {boolean} + * @private + * @readonly + */ + this._destroyed = false; + + /** + * Fired when this DisplayObject is added to a Container. + * + * @event PIXI.DisplayObject#added + * @param {PIXI.Container} container - The container added to. + */ + + /** + * Fired when this DisplayObject is removed from a Container. + * + * @event PIXI.DisplayObject#removed + * @param {PIXI.Container} container - The container removed from. + */ } /** @@ -403,6 +425,8 @@ this.interactive = false; this.interactiveChildren = false; + + this._destroyed = true; } /** diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index aafdfe2..f787c4a 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -792,8 +792,8 @@ sprite.worldAlpha = this.worldAlpha * sprite.alpha; sprite.blendMode = this.blendMode; - sprite.texture._frame.width = rect.width; - sprite.texture._frame.height = rect.height; + sprite._texture._frame.width = rect.width; + sprite._texture._frame.height = rect.height; sprite.transform.worldTransform = this.transform.worldTransform; diff --git a/.travis.yml b/.travis.yml index 3df8b22..14de334 100644 --- a/.travis.yml +++ b/.travis.yml @@ -110,3 +110,43 @@ on: all_branches: true condition: $TRAVIS_TAG + # Deploy config for latest release + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: dist + upload-dir: "release" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: docs + upload-dir: "release/docs" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: coverage + upload-dir: "release/coverage" + on: + all_branches: true + condition: $TRAVIS_TAG \ No newline at end of file diff --git a/README.md b/README.md index d26fadd..3b85f85 100644 --- a/README.md +++ b/README.md @@ -36,22 +36,14 @@ - Wiki: Other misc tutorials and resources are [on the Wiki](https://github.com/pixijs/pixi.js/wiki/Resources). ### Community ### -- Forums: Check out the [forum] (http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow] (http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. +- Forums: Check out the [forum](http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow](http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. - Inspiration: Check out the [gallery](http://www.pixijs.com/gallery) to see some of the amazing things people have created! - Chat: You can join us on [Gitter](https://gitter.im/pixijs/pixi.js) To chat about Pixi. We also now have a Slack channel. If you would like to join it please Send me an email (mat@goodboydigital.com) and I will invite you in. ### Setup ### -It's easy to get started with Pixi.js! Simply grab the pre-built versions from here: - -Release Branch - Nice and stable Pixi.js -- Unminified: [http://pixijs.download/release/pixi.js] -- Minified: [http://pixijs.download/release/pixi.min.js] - -Develop Branch - The bleeding edge version of Pixi.js -- Unminified: [http://pixijs.download/dev/pixi.js] -- Minified: [http://pixijs.download/dev/pixi.min.js] +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. @@ -70,10 +62,10 @@ #### CDN Install (via cdnjs) ```html - + ``` -_Note: `4.2.2` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ +_Note: `4.4.3` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ ### Demos ### @@ -130,7 +122,7 @@ // and the root stage PIXI.Container. var app = new PIXI.Application(); -// The application will create a canvas element for you that you +// The application will create a canvas element for you that you // can then insert into the DOM. document.body.appendChild(app.view); @@ -150,7 +142,7 @@ // Add the bunny to the scene we are building. app.stage.addChild(bunny); - + // Listen for frame updates app.ticker.add(function() { // each frame we spin the bunny around a bit diff --git a/bower.json b/bower.json deleted file mode 100644 index 41b668c..0000000 --- a/bower.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "pixi.js", - "main": "dist/pixi.min.js", - "ignore": [ - "**/.*", - "docs", - "node_modules", - "bower_components", - "test", - "scripts", - "bower.json", - "inch.json" - ], - "dependencies": { - }, - "devDependencies": { - } -} diff --git a/package.json b/package.json index d35cbb2..308469f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.3.5", + "version": "4.4.4", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ @@ -40,11 +40,11 @@ "lib": "babel src --out-dir lib -s", "predocs": "rimraf docs/**", "docs": "jsdoc -c scripts/jsdoc.conf.json -R README.md", - "publish:patch": "npm version patch --no-git-tag-version && npm publish", - "publish:minor": "npm version minor --no-git-tag-version && npm publish", - "publish:major": "npm version major --no-git-tag-version && npm publish", + "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", - "postpublish": "node scripts/release.js" + "postpublish": "git push && git push --tags" }, "files": [ "dist/", @@ -72,7 +72,6 @@ "electron": "^1.4.15", "eslint": "^3.5.0", "floss": "^2.0.1", - "gh-pages": "^0.11.0", "jaguarjs-jsdoc": "^1.0.1", "js-md5": "^0.4.1", "jsdoc": "^3.4.2", diff --git a/scripts/jsdoc.conf.json b/scripts/jsdoc.conf.json index 553e61c..3f52180 100644 --- a/scripts/jsdoc.conf.json +++ b/scripts/jsdoc.conf.json @@ -43,7 +43,7 @@ }, "markdown" : { "parser" : "gfm", - "hardwrap" : true + "hardwrap" : false }, "opts": { "encoding" : "utf8", diff --git a/scripts/release.js b/scripts/release.js deleted file mode 100644 index 156620c..0000000 --- a/scripts/release.js +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env node -'use strict'; - -// Publish script to push releases of the bin files -// the normally are gitignored -const ghpages = require('gh-pages'); -const path = require('path'); -const packageInfo = require(path.join(__dirname, '..', 'package.json')); - -const options = { - src: [ - 'dist/**/*', - 'lib/**/*', - 'src/**/*', - 'scripts/*', - 'scripts/renders/*', - 'test/**/*', - '*.json', - '*.md', - 'LICENSE', - '.eslintrc', - '.editorconfig', - '.travis.yml', - '.babelrc', - ], - dotfiles: true, - branch: 'release', - message: packageInfo.version, - logger: console.log.bind(console), -}; - -ghpages.publish(process.cwd(), options, (err) => -{ - if (err) - { - console.log(err); - process.exit(1); - - return; - } - - process.exit(0); -}); diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index f16bcb1..220427a 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -21,7 +21,7 @@ const DIV_HOOK_ZINDEX = 2; /** - * The Accessibility manager reacreates the ability to tab and and have content read by screen + * The Accessibility manager recreates the ability to tab and and have content read by screen * readers. This is very important as it can possibly help people with disabilities access pixi * content. * diff --git a/src/accessibility/index.js b/src/accessibility/index.js index 568cfb1..0ae108f 100644 --- a/src/accessibility/index.js +++ b/src/accessibility/index.js @@ -1,4 +1,9 @@ /** + * This namespace contains a renderer plugin for interaction accessibility for end-users + * with physical impairments which require screen-renders, keyboard navigation, etc. + * + * Do not instantiate this plugin directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. * @namespace PIXI.accessibility */ export { default as accessibleTarget } from './accessibleTarget'; diff --git a/src/core/Application.js b/src/core/Application.js index 5340578..2fcc729 100644 --- a/src/core/Application.js +++ b/src/core/Application.js @@ -1,6 +1,8 @@ import { autoDetectRenderer } from './autoDetectRenderer'; import Container from './display/Container'; import { shared, Ticker } from './ticker'; +import settings from './settings'; +import { UPDATE_PRIORITY } from './const'; /** * Convenience class to create a new PIXI application. @@ -22,28 +24,52 @@ */ export default class Application { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experience unexplained flickering try setting this to true. - * @param {boolean} [useSharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedLoader=false] - `true` to use PIXI.loaders.shared, `false` to create new Loader. */ - constructor(width, height, options, noWebGL, useSharedTicker = false) + constructor(options, arg2, arg3, arg4, arg5) { + // Support for constructor(width, height, options, noWebGL, useSharedTicker) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + forceCanvas: !!arg4, + sharedTicker: !!arg5, + }, arg3); + } + + /** + * The default options, so we mixin functionality later. + * @member {object} + * @protected + */ + this._options = options = Object.assign({ + sharedTicker: false, + forceCanvas: false, + sharedLoader: false, + }, options); + /** * WebGL renderer if available, otherwise CanvasRenderer * @member {PIXI.WebGLRenderer|PIXI.CanvasRenderer} */ - this.renderer = autoDetectRenderer(width, height, options, noWebGL); + this.renderer = autoDetectRenderer(options); /** * The root display container that's rendered. @@ -63,7 +89,7 @@ * @member {PIXI.ticker.Ticker} * @default PIXI.ticker.shared */ - this.ticker = useSharedTicker ? shared : new Ticker(); + this.ticker = options.sharedTicker ? shared : new Ticker(); // Start the rendering this.start(); @@ -78,7 +104,7 @@ this._ticker = ticker; if (ticker) { - ticker.add(this.render, this); + ticker.add(this.render, this, UPDATE_PRIORITY.LOW); } } get ticker() // eslint-disable-line require-jsdoc @@ -136,13 +162,18 @@ */ destroy(removeView) { - this.stop(); + const oldTicker = this._ticker; + this.ticker = null; + oldTicker.destroy(); + this.stage.destroy(); this.stage = null; this.renderer.destroy(removeView); this.renderer = null; + + this._options = null; } } diff --git a/src/core/autoDetectRenderer.js b/src/core/autoDetectRenderer.js index 8960425..9ec0364 100644 --- a/src/core/autoDetectRenderer.js +++ b/src/core/autoDetectRenderer.js @@ -2,6 +2,7 @@ import CanvasRenderer from './renderers/canvas/CanvasRenderer'; import WebGLRenderer from './renderers/webgl/WebGLRenderer'; +// eslint-disable-next-line valid-jsdoc /** * This helper function will automatically detect which renderer you should be using. * WebGL is the preferred renderer as it is a lot faster. If webGL is not supported by @@ -9,24 +10,32 @@ * * @memberof PIXI * @function autoDetectRenderer - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @return {PIXI.WebGLRenderer|PIXI.CanvasRenderer} Returns WebGL renderer if available, otherwise CanvasRenderer */ -export function autoDetectRenderer(width = 800, height = 600, options, noWebGL) +export function autoDetectRenderer(options, arg1, arg2, arg3) { - if (!noWebGL && utils.isWebGLSupported()) + // Backward-compatible support for noWebGL option + let forceCanvas = options && options.forceCanvas; + + if (arg3 !== undefined) { - return new WebGLRenderer(width, height, options); + forceCanvas = arg3; } - return new CanvasRenderer(width, height, options); + if (!forceCanvas && utils.isWebGLSupported()) + { + return new WebGLRenderer(options, arg1, arg2); + } + + return new CanvasRenderer(options, arg1, arg2); } diff --git a/src/core/const.js b/src/core/const.js index b9e99ae..2c68dee 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -341,3 +341,27 @@ LINEAR_VERTICAL: 0, LINEAR_HORIZONTAL: 1, }; + +/** + * Represents the update priorities used by internal PIXI classes when registered with + * the {@link PIXI.ticker.Ticker} object. Higher priority items are updated first and lower + * priority items, such as render, should go later. + * + * @static + * @constant + * @name UPDATE_PRIORITY + * @memberof PIXI + * @type {object} + * @property {number} INTERACTION=50 Highest priority, used for {@link PIXI.interaction.InteractionManager} + * @property {number} HIGH=25 High priority updating, {@link PIXI.VideoBaseTexture} and {@link PIXI.extras.AnimatedSprite} + * @property {number} NORMAL=0 Default priority for ticker events, see {@link PIXI.ticker.Ticker#add}. + * @property {number} LOW=-25 Low priority used for {@link PIXI.Application} rendering. + * @property {number} UTILITY=-50 Lowest priority used for {@link PIXI.prepare.BasePrepare} utility. + */ +export const UPDATE_PRIORITY = { + INTERACTION: 50, + HIGH: 25, + NORMAL: 0, + LOW: -25, + UTILITY: -50, +}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 4e6c77d..decb42a 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -13,7 +13,6 @@ * * @class * @extends EventEmitter - * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ export default class DisplayObject extends EventEmitter @@ -113,6 +112,29 @@ * @private */ this._mask = null; + + /** + * If the object has been destroyed via destroy(). If true, it should not be used. + * + * @member {boolean} + * @private + * @readonly + */ + this._destroyed = false; + + /** + * Fired when this DisplayObject is added to a Container. + * + * @event PIXI.DisplayObject#added + * @param {PIXI.Container} container - The container added to. + */ + + /** + * Fired when this DisplayObject is removed from a Container. + * + * @event PIXI.DisplayObject#removed + * @param {PIXI.Container} container - The container removed from. + */ } /** @@ -403,6 +425,8 @@ this.interactive = false; this.interactiveChildren = false; + + this._destroyed = true; } /** diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index aafdfe2..f787c4a 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -792,8 +792,8 @@ sprite.worldAlpha = this.worldAlpha * sprite.alpha; sprite.blendMode = this.blendMode; - sprite.texture._frame.width = rect.width; - sprite.texture._frame.height = rect.height; + sprite._texture._frame.width = rect.width; + sprite._texture._frame.height = rect.height; sprite.transform.worldTransform = this.transform.worldTransform; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index a24c775..b9f192d 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -19,11 +19,12 @@ */ export default class SystemRenderer extends EventEmitter { + // eslint-disable-next-line valid-jsdoc /** * @param {string} system - The name of the system this renderer is for. - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -37,27 +38,31 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(system, screenWidth, screenHeight, options) + constructor(system, options, arg2, arg3) { super(); sayHello(system); - // prepare options - if (options) + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') { - for (const i in settings.RENDER_OPTIONS) - { - if (typeof options[i] === 'undefined') - { - options[i] = settings.RENDER_OPTIONS[i]; - } - } + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); } - else - { - options = settings.RENDER_OPTIONS; - } + + // Add the default render options + options = Object.assign({}, settings.RENDER_OPTIONS, options); + + /** + * The supplied constructor options. + * + * @member {Object} + * @readOnly + */ + this.options = options; /** * The type of the renderer. @@ -75,7 +80,7 @@ * * @member {PIXI.Rectangle} */ - this.screen = new Rectangle(0, 0, screenWidth || 800, screenHeight || 600); + this.screen = new Rectangle(0, 0, options.width, options.height); /** * The canvas element that everything is drawn to @@ -279,6 +284,8 @@ this.blendModes = null; + this.options = null; + this.preserveDrawingBuffer = false; this.clearBeforeRender = false; diff --git a/.travis.yml b/.travis.yml index 3df8b22..14de334 100644 --- a/.travis.yml +++ b/.travis.yml @@ -110,3 +110,43 @@ on: all_branches: true condition: $TRAVIS_TAG + # Deploy config for latest release + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: dist + upload-dir: "release" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: docs + upload-dir: "release/docs" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: coverage + upload-dir: "release/coverage" + on: + all_branches: true + condition: $TRAVIS_TAG \ No newline at end of file diff --git a/README.md b/README.md index d26fadd..3b85f85 100644 --- a/README.md +++ b/README.md @@ -36,22 +36,14 @@ - Wiki: Other misc tutorials and resources are [on the Wiki](https://github.com/pixijs/pixi.js/wiki/Resources). ### Community ### -- Forums: Check out the [forum] (http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow] (http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. +- Forums: Check out the [forum](http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow](http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. - Inspiration: Check out the [gallery](http://www.pixijs.com/gallery) to see some of the amazing things people have created! - Chat: You can join us on [Gitter](https://gitter.im/pixijs/pixi.js) To chat about Pixi. We also now have a Slack channel. If you would like to join it please Send me an email (mat@goodboydigital.com) and I will invite you in. ### Setup ### -It's easy to get started with Pixi.js! Simply grab the pre-built versions from here: - -Release Branch - Nice and stable Pixi.js -- Unminified: [http://pixijs.download/release/pixi.js] -- Minified: [http://pixijs.download/release/pixi.min.js] - -Develop Branch - The bleeding edge version of Pixi.js -- Unminified: [http://pixijs.download/dev/pixi.js] -- Minified: [http://pixijs.download/dev/pixi.min.js] +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. @@ -70,10 +62,10 @@ #### CDN Install (via cdnjs) ```html - + ``` -_Note: `4.2.2` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ +_Note: `4.4.3` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ ### Demos ### @@ -130,7 +122,7 @@ // and the root stage PIXI.Container. var app = new PIXI.Application(); -// The application will create a canvas element for you that you +// The application will create a canvas element for you that you // can then insert into the DOM. document.body.appendChild(app.view); @@ -150,7 +142,7 @@ // Add the bunny to the scene we are building. app.stage.addChild(bunny); - + // Listen for frame updates app.ticker.add(function() { // each frame we spin the bunny around a bit diff --git a/bower.json b/bower.json deleted file mode 100644 index 41b668c..0000000 --- a/bower.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "pixi.js", - "main": "dist/pixi.min.js", - "ignore": [ - "**/.*", - "docs", - "node_modules", - "bower_components", - "test", - "scripts", - "bower.json", - "inch.json" - ], - "dependencies": { - }, - "devDependencies": { - } -} diff --git a/package.json b/package.json index d35cbb2..308469f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.3.5", + "version": "4.4.4", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ @@ -40,11 +40,11 @@ "lib": "babel src --out-dir lib -s", "predocs": "rimraf docs/**", "docs": "jsdoc -c scripts/jsdoc.conf.json -R README.md", - "publish:patch": "npm version patch --no-git-tag-version && npm publish", - "publish:minor": "npm version minor --no-git-tag-version && npm publish", - "publish:major": "npm version major --no-git-tag-version && npm publish", + "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", - "postpublish": "node scripts/release.js" + "postpublish": "git push && git push --tags" }, "files": [ "dist/", @@ -72,7 +72,6 @@ "electron": "^1.4.15", "eslint": "^3.5.0", "floss": "^2.0.1", - "gh-pages": "^0.11.0", "jaguarjs-jsdoc": "^1.0.1", "js-md5": "^0.4.1", "jsdoc": "^3.4.2", diff --git a/scripts/jsdoc.conf.json b/scripts/jsdoc.conf.json index 553e61c..3f52180 100644 --- a/scripts/jsdoc.conf.json +++ b/scripts/jsdoc.conf.json @@ -43,7 +43,7 @@ }, "markdown" : { "parser" : "gfm", - "hardwrap" : true + "hardwrap" : false }, "opts": { "encoding" : "utf8", diff --git a/scripts/release.js b/scripts/release.js deleted file mode 100644 index 156620c..0000000 --- a/scripts/release.js +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env node -'use strict'; - -// Publish script to push releases of the bin files -// the normally are gitignored -const ghpages = require('gh-pages'); -const path = require('path'); -const packageInfo = require(path.join(__dirname, '..', 'package.json')); - -const options = { - src: [ - 'dist/**/*', - 'lib/**/*', - 'src/**/*', - 'scripts/*', - 'scripts/renders/*', - 'test/**/*', - '*.json', - '*.md', - 'LICENSE', - '.eslintrc', - '.editorconfig', - '.travis.yml', - '.babelrc', - ], - dotfiles: true, - branch: 'release', - message: packageInfo.version, - logger: console.log.bind(console), -}; - -ghpages.publish(process.cwd(), options, (err) => -{ - if (err) - { - console.log(err); - process.exit(1); - - return; - } - - process.exit(0); -}); diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index f16bcb1..220427a 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -21,7 +21,7 @@ const DIV_HOOK_ZINDEX = 2; /** - * The Accessibility manager reacreates the ability to tab and and have content read by screen + * The Accessibility manager recreates the ability to tab and and have content read by screen * readers. This is very important as it can possibly help people with disabilities access pixi * content. * diff --git a/src/accessibility/index.js b/src/accessibility/index.js index 568cfb1..0ae108f 100644 --- a/src/accessibility/index.js +++ b/src/accessibility/index.js @@ -1,4 +1,9 @@ /** + * This namespace contains a renderer plugin for interaction accessibility for end-users + * with physical impairments which require screen-renders, keyboard navigation, etc. + * + * Do not instantiate this plugin directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. * @namespace PIXI.accessibility */ export { default as accessibleTarget } from './accessibleTarget'; diff --git a/src/core/Application.js b/src/core/Application.js index 5340578..2fcc729 100644 --- a/src/core/Application.js +++ b/src/core/Application.js @@ -1,6 +1,8 @@ import { autoDetectRenderer } from './autoDetectRenderer'; import Container from './display/Container'; import { shared, Ticker } from './ticker'; +import settings from './settings'; +import { UPDATE_PRIORITY } from './const'; /** * Convenience class to create a new PIXI application. @@ -22,28 +24,52 @@ */ export default class Application { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experience unexplained flickering try setting this to true. - * @param {boolean} [useSharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedLoader=false] - `true` to use PIXI.loaders.shared, `false` to create new Loader. */ - constructor(width, height, options, noWebGL, useSharedTicker = false) + constructor(options, arg2, arg3, arg4, arg5) { + // Support for constructor(width, height, options, noWebGL, useSharedTicker) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + forceCanvas: !!arg4, + sharedTicker: !!arg5, + }, arg3); + } + + /** + * The default options, so we mixin functionality later. + * @member {object} + * @protected + */ + this._options = options = Object.assign({ + sharedTicker: false, + forceCanvas: false, + sharedLoader: false, + }, options); + /** * WebGL renderer if available, otherwise CanvasRenderer * @member {PIXI.WebGLRenderer|PIXI.CanvasRenderer} */ - this.renderer = autoDetectRenderer(width, height, options, noWebGL); + this.renderer = autoDetectRenderer(options); /** * The root display container that's rendered. @@ -63,7 +89,7 @@ * @member {PIXI.ticker.Ticker} * @default PIXI.ticker.shared */ - this.ticker = useSharedTicker ? shared : new Ticker(); + this.ticker = options.sharedTicker ? shared : new Ticker(); // Start the rendering this.start(); @@ -78,7 +104,7 @@ this._ticker = ticker; if (ticker) { - ticker.add(this.render, this); + ticker.add(this.render, this, UPDATE_PRIORITY.LOW); } } get ticker() // eslint-disable-line require-jsdoc @@ -136,13 +162,18 @@ */ destroy(removeView) { - this.stop(); + const oldTicker = this._ticker; + this.ticker = null; + oldTicker.destroy(); + this.stage.destroy(); this.stage = null; this.renderer.destroy(removeView); this.renderer = null; + + this._options = null; } } diff --git a/src/core/autoDetectRenderer.js b/src/core/autoDetectRenderer.js index 8960425..9ec0364 100644 --- a/src/core/autoDetectRenderer.js +++ b/src/core/autoDetectRenderer.js @@ -2,6 +2,7 @@ import CanvasRenderer from './renderers/canvas/CanvasRenderer'; import WebGLRenderer from './renderers/webgl/WebGLRenderer'; +// eslint-disable-next-line valid-jsdoc /** * This helper function will automatically detect which renderer you should be using. * WebGL is the preferred renderer as it is a lot faster. If webGL is not supported by @@ -9,24 +10,32 @@ * * @memberof PIXI * @function autoDetectRenderer - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @return {PIXI.WebGLRenderer|PIXI.CanvasRenderer} Returns WebGL renderer if available, otherwise CanvasRenderer */ -export function autoDetectRenderer(width = 800, height = 600, options, noWebGL) +export function autoDetectRenderer(options, arg1, arg2, arg3) { - if (!noWebGL && utils.isWebGLSupported()) + // Backward-compatible support for noWebGL option + let forceCanvas = options && options.forceCanvas; + + if (arg3 !== undefined) { - return new WebGLRenderer(width, height, options); + forceCanvas = arg3; } - return new CanvasRenderer(width, height, options); + if (!forceCanvas && utils.isWebGLSupported()) + { + return new WebGLRenderer(options, arg1, arg2); + } + + return new CanvasRenderer(options, arg1, arg2); } diff --git a/src/core/const.js b/src/core/const.js index b9e99ae..2c68dee 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -341,3 +341,27 @@ LINEAR_VERTICAL: 0, LINEAR_HORIZONTAL: 1, }; + +/** + * Represents the update priorities used by internal PIXI classes when registered with + * the {@link PIXI.ticker.Ticker} object. Higher priority items are updated first and lower + * priority items, such as render, should go later. + * + * @static + * @constant + * @name UPDATE_PRIORITY + * @memberof PIXI + * @type {object} + * @property {number} INTERACTION=50 Highest priority, used for {@link PIXI.interaction.InteractionManager} + * @property {number} HIGH=25 High priority updating, {@link PIXI.VideoBaseTexture} and {@link PIXI.extras.AnimatedSprite} + * @property {number} NORMAL=0 Default priority for ticker events, see {@link PIXI.ticker.Ticker#add}. + * @property {number} LOW=-25 Low priority used for {@link PIXI.Application} rendering. + * @property {number} UTILITY=-50 Lowest priority used for {@link PIXI.prepare.BasePrepare} utility. + */ +export const UPDATE_PRIORITY = { + INTERACTION: 50, + HIGH: 25, + NORMAL: 0, + LOW: -25, + UTILITY: -50, +}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 4e6c77d..decb42a 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -13,7 +13,6 @@ * * @class * @extends EventEmitter - * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ export default class DisplayObject extends EventEmitter @@ -113,6 +112,29 @@ * @private */ this._mask = null; + + /** + * If the object has been destroyed via destroy(). If true, it should not be used. + * + * @member {boolean} + * @private + * @readonly + */ + this._destroyed = false; + + /** + * Fired when this DisplayObject is added to a Container. + * + * @event PIXI.DisplayObject#added + * @param {PIXI.Container} container - The container added to. + */ + + /** + * Fired when this DisplayObject is removed from a Container. + * + * @event PIXI.DisplayObject#removed + * @param {PIXI.Container} container - The container removed from. + */ } /** @@ -403,6 +425,8 @@ this.interactive = false; this.interactiveChildren = false; + + this._destroyed = true; } /** diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index aafdfe2..f787c4a 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -792,8 +792,8 @@ sprite.worldAlpha = this.worldAlpha * sprite.alpha; sprite.blendMode = this.blendMode; - sprite.texture._frame.width = rect.width; - sprite.texture._frame.height = rect.height; + sprite._texture._frame.width = rect.width; + sprite._texture._frame.height = rect.height; sprite.transform.worldTransform = this.transform.worldTransform; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index a24c775..b9f192d 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -19,11 +19,12 @@ */ export default class SystemRenderer extends EventEmitter { + // eslint-disable-next-line valid-jsdoc /** * @param {string} system - The name of the system this renderer is for. - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -37,27 +38,31 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(system, screenWidth, screenHeight, options) + constructor(system, options, arg2, arg3) { super(); sayHello(system); - // prepare options - if (options) + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') { - for (const i in settings.RENDER_OPTIONS) - { - if (typeof options[i] === 'undefined') - { - options[i] = settings.RENDER_OPTIONS[i]; - } - } + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); } - else - { - options = settings.RENDER_OPTIONS; - } + + // Add the default render options + options = Object.assign({}, settings.RENDER_OPTIONS, options); + + /** + * The supplied constructor options. + * + * @member {Object} + * @readOnly + */ + this.options = options; /** * The type of the renderer. @@ -75,7 +80,7 @@ * * @member {PIXI.Rectangle} */ - this.screen = new Rectangle(0, 0, screenWidth || 800, screenHeight || 600); + this.screen = new Rectangle(0, 0, options.width, options.height); /** * The canvas element that everything is drawn to @@ -279,6 +284,8 @@ this.blendModes = null; + this.options = null; + this.preserveDrawingBuffer = false; this.clearBeforeRender = false; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index e84e49c..691caf0 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -17,10 +17,11 @@ */ export default class CanvasRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -34,9 +35,9 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('Canvas', screenWidth, screenHeight, options); + super('Canvas', options, arg2, arg3); this.type = RENDERER_TYPE.CANVAS; @@ -96,7 +97,19 @@ this.context = null; this.renderingToScreen = false; - this.resize(screenWidth, screenHeight); + this.resize(this.options.width, this.options.height); + + /** + * Fired after rendering finishes. + * + * @event PIXI.CanvasRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.CanvasRenderer#prerender + */ } /** @@ -299,4 +312,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.CanvasRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.CanvasExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.CanvasPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.CanvasRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(CanvasRenderer); diff --git a/.travis.yml b/.travis.yml index 3df8b22..14de334 100644 --- a/.travis.yml +++ b/.travis.yml @@ -110,3 +110,43 @@ on: all_branches: true condition: $TRAVIS_TAG + # Deploy config for latest release + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: dist + upload-dir: "release" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: docs + upload-dir: "release/docs" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: coverage + upload-dir: "release/coverage" + on: + all_branches: true + condition: $TRAVIS_TAG \ No newline at end of file diff --git a/README.md b/README.md index d26fadd..3b85f85 100644 --- a/README.md +++ b/README.md @@ -36,22 +36,14 @@ - Wiki: Other misc tutorials and resources are [on the Wiki](https://github.com/pixijs/pixi.js/wiki/Resources). ### Community ### -- Forums: Check out the [forum] (http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow] (http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. +- Forums: Check out the [forum](http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow](http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. - Inspiration: Check out the [gallery](http://www.pixijs.com/gallery) to see some of the amazing things people have created! - Chat: You can join us on [Gitter](https://gitter.im/pixijs/pixi.js) To chat about Pixi. We also now have a Slack channel. If you would like to join it please Send me an email (mat@goodboydigital.com) and I will invite you in. ### Setup ### -It's easy to get started with Pixi.js! Simply grab the pre-built versions from here: - -Release Branch - Nice and stable Pixi.js -- Unminified: [http://pixijs.download/release/pixi.js] -- Minified: [http://pixijs.download/release/pixi.min.js] - -Develop Branch - The bleeding edge version of Pixi.js -- Unminified: [http://pixijs.download/dev/pixi.js] -- Minified: [http://pixijs.download/dev/pixi.min.js] +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. @@ -70,10 +62,10 @@ #### CDN Install (via cdnjs) ```html - + ``` -_Note: `4.2.2` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ +_Note: `4.4.3` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ ### Demos ### @@ -130,7 +122,7 @@ // and the root stage PIXI.Container. var app = new PIXI.Application(); -// The application will create a canvas element for you that you +// The application will create a canvas element for you that you // can then insert into the DOM. document.body.appendChild(app.view); @@ -150,7 +142,7 @@ // Add the bunny to the scene we are building. app.stage.addChild(bunny); - + // Listen for frame updates app.ticker.add(function() { // each frame we spin the bunny around a bit diff --git a/bower.json b/bower.json deleted file mode 100644 index 41b668c..0000000 --- a/bower.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "pixi.js", - "main": "dist/pixi.min.js", - "ignore": [ - "**/.*", - "docs", - "node_modules", - "bower_components", - "test", - "scripts", - "bower.json", - "inch.json" - ], - "dependencies": { - }, - "devDependencies": { - } -} diff --git a/package.json b/package.json index d35cbb2..308469f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.3.5", + "version": "4.4.4", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ @@ -40,11 +40,11 @@ "lib": "babel src --out-dir lib -s", "predocs": "rimraf docs/**", "docs": "jsdoc -c scripts/jsdoc.conf.json -R README.md", - "publish:patch": "npm version patch --no-git-tag-version && npm publish", - "publish:minor": "npm version minor --no-git-tag-version && npm publish", - "publish:major": "npm version major --no-git-tag-version && npm publish", + "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", - "postpublish": "node scripts/release.js" + "postpublish": "git push && git push --tags" }, "files": [ "dist/", @@ -72,7 +72,6 @@ "electron": "^1.4.15", "eslint": "^3.5.0", "floss": "^2.0.1", - "gh-pages": "^0.11.0", "jaguarjs-jsdoc": "^1.0.1", "js-md5": "^0.4.1", "jsdoc": "^3.4.2", diff --git a/scripts/jsdoc.conf.json b/scripts/jsdoc.conf.json index 553e61c..3f52180 100644 --- a/scripts/jsdoc.conf.json +++ b/scripts/jsdoc.conf.json @@ -43,7 +43,7 @@ }, "markdown" : { "parser" : "gfm", - "hardwrap" : true + "hardwrap" : false }, "opts": { "encoding" : "utf8", diff --git a/scripts/release.js b/scripts/release.js deleted file mode 100644 index 156620c..0000000 --- a/scripts/release.js +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env node -'use strict'; - -// Publish script to push releases of the bin files -// the normally are gitignored -const ghpages = require('gh-pages'); -const path = require('path'); -const packageInfo = require(path.join(__dirname, '..', 'package.json')); - -const options = { - src: [ - 'dist/**/*', - 'lib/**/*', - 'src/**/*', - 'scripts/*', - 'scripts/renders/*', - 'test/**/*', - '*.json', - '*.md', - 'LICENSE', - '.eslintrc', - '.editorconfig', - '.travis.yml', - '.babelrc', - ], - dotfiles: true, - branch: 'release', - message: packageInfo.version, - logger: console.log.bind(console), -}; - -ghpages.publish(process.cwd(), options, (err) => -{ - if (err) - { - console.log(err); - process.exit(1); - - return; - } - - process.exit(0); -}); diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index f16bcb1..220427a 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -21,7 +21,7 @@ const DIV_HOOK_ZINDEX = 2; /** - * The Accessibility manager reacreates the ability to tab and and have content read by screen + * The Accessibility manager recreates the ability to tab and and have content read by screen * readers. This is very important as it can possibly help people with disabilities access pixi * content. * diff --git a/src/accessibility/index.js b/src/accessibility/index.js index 568cfb1..0ae108f 100644 --- a/src/accessibility/index.js +++ b/src/accessibility/index.js @@ -1,4 +1,9 @@ /** + * This namespace contains a renderer plugin for interaction accessibility for end-users + * with physical impairments which require screen-renders, keyboard navigation, etc. + * + * Do not instantiate this plugin directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. * @namespace PIXI.accessibility */ export { default as accessibleTarget } from './accessibleTarget'; diff --git a/src/core/Application.js b/src/core/Application.js index 5340578..2fcc729 100644 --- a/src/core/Application.js +++ b/src/core/Application.js @@ -1,6 +1,8 @@ import { autoDetectRenderer } from './autoDetectRenderer'; import Container from './display/Container'; import { shared, Ticker } from './ticker'; +import settings from './settings'; +import { UPDATE_PRIORITY } from './const'; /** * Convenience class to create a new PIXI application. @@ -22,28 +24,52 @@ */ export default class Application { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experience unexplained flickering try setting this to true. - * @param {boolean} [useSharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedLoader=false] - `true` to use PIXI.loaders.shared, `false` to create new Loader. */ - constructor(width, height, options, noWebGL, useSharedTicker = false) + constructor(options, arg2, arg3, arg4, arg5) { + // Support for constructor(width, height, options, noWebGL, useSharedTicker) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + forceCanvas: !!arg4, + sharedTicker: !!arg5, + }, arg3); + } + + /** + * The default options, so we mixin functionality later. + * @member {object} + * @protected + */ + this._options = options = Object.assign({ + sharedTicker: false, + forceCanvas: false, + sharedLoader: false, + }, options); + /** * WebGL renderer if available, otherwise CanvasRenderer * @member {PIXI.WebGLRenderer|PIXI.CanvasRenderer} */ - this.renderer = autoDetectRenderer(width, height, options, noWebGL); + this.renderer = autoDetectRenderer(options); /** * The root display container that's rendered. @@ -63,7 +89,7 @@ * @member {PIXI.ticker.Ticker} * @default PIXI.ticker.shared */ - this.ticker = useSharedTicker ? shared : new Ticker(); + this.ticker = options.sharedTicker ? shared : new Ticker(); // Start the rendering this.start(); @@ -78,7 +104,7 @@ this._ticker = ticker; if (ticker) { - ticker.add(this.render, this); + ticker.add(this.render, this, UPDATE_PRIORITY.LOW); } } get ticker() // eslint-disable-line require-jsdoc @@ -136,13 +162,18 @@ */ destroy(removeView) { - this.stop(); + const oldTicker = this._ticker; + this.ticker = null; + oldTicker.destroy(); + this.stage.destroy(); this.stage = null; this.renderer.destroy(removeView); this.renderer = null; + + this._options = null; } } diff --git a/src/core/autoDetectRenderer.js b/src/core/autoDetectRenderer.js index 8960425..9ec0364 100644 --- a/src/core/autoDetectRenderer.js +++ b/src/core/autoDetectRenderer.js @@ -2,6 +2,7 @@ import CanvasRenderer from './renderers/canvas/CanvasRenderer'; import WebGLRenderer from './renderers/webgl/WebGLRenderer'; +// eslint-disable-next-line valid-jsdoc /** * This helper function will automatically detect which renderer you should be using. * WebGL is the preferred renderer as it is a lot faster. If webGL is not supported by @@ -9,24 +10,32 @@ * * @memberof PIXI * @function autoDetectRenderer - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @return {PIXI.WebGLRenderer|PIXI.CanvasRenderer} Returns WebGL renderer if available, otherwise CanvasRenderer */ -export function autoDetectRenderer(width = 800, height = 600, options, noWebGL) +export function autoDetectRenderer(options, arg1, arg2, arg3) { - if (!noWebGL && utils.isWebGLSupported()) + // Backward-compatible support for noWebGL option + let forceCanvas = options && options.forceCanvas; + + if (arg3 !== undefined) { - return new WebGLRenderer(width, height, options); + forceCanvas = arg3; } - return new CanvasRenderer(width, height, options); + if (!forceCanvas && utils.isWebGLSupported()) + { + return new WebGLRenderer(options, arg1, arg2); + } + + return new CanvasRenderer(options, arg1, arg2); } diff --git a/src/core/const.js b/src/core/const.js index b9e99ae..2c68dee 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -341,3 +341,27 @@ LINEAR_VERTICAL: 0, LINEAR_HORIZONTAL: 1, }; + +/** + * Represents the update priorities used by internal PIXI classes when registered with + * the {@link PIXI.ticker.Ticker} object. Higher priority items are updated first and lower + * priority items, such as render, should go later. + * + * @static + * @constant + * @name UPDATE_PRIORITY + * @memberof PIXI + * @type {object} + * @property {number} INTERACTION=50 Highest priority, used for {@link PIXI.interaction.InteractionManager} + * @property {number} HIGH=25 High priority updating, {@link PIXI.VideoBaseTexture} and {@link PIXI.extras.AnimatedSprite} + * @property {number} NORMAL=0 Default priority for ticker events, see {@link PIXI.ticker.Ticker#add}. + * @property {number} LOW=-25 Low priority used for {@link PIXI.Application} rendering. + * @property {number} UTILITY=-50 Lowest priority used for {@link PIXI.prepare.BasePrepare} utility. + */ +export const UPDATE_PRIORITY = { + INTERACTION: 50, + HIGH: 25, + NORMAL: 0, + LOW: -25, + UTILITY: -50, +}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 4e6c77d..decb42a 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -13,7 +13,6 @@ * * @class * @extends EventEmitter - * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ export default class DisplayObject extends EventEmitter @@ -113,6 +112,29 @@ * @private */ this._mask = null; + + /** + * If the object has been destroyed via destroy(). If true, it should not be used. + * + * @member {boolean} + * @private + * @readonly + */ + this._destroyed = false; + + /** + * Fired when this DisplayObject is added to a Container. + * + * @event PIXI.DisplayObject#added + * @param {PIXI.Container} container - The container added to. + */ + + /** + * Fired when this DisplayObject is removed from a Container. + * + * @event PIXI.DisplayObject#removed + * @param {PIXI.Container} container - The container removed from. + */ } /** @@ -403,6 +425,8 @@ this.interactive = false; this.interactiveChildren = false; + + this._destroyed = true; } /** diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index aafdfe2..f787c4a 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -792,8 +792,8 @@ sprite.worldAlpha = this.worldAlpha * sprite.alpha; sprite.blendMode = this.blendMode; - sprite.texture._frame.width = rect.width; - sprite.texture._frame.height = rect.height; + sprite._texture._frame.width = rect.width; + sprite._texture._frame.height = rect.height; sprite.transform.worldTransform = this.transform.worldTransform; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index a24c775..b9f192d 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -19,11 +19,12 @@ */ export default class SystemRenderer extends EventEmitter { + // eslint-disable-next-line valid-jsdoc /** * @param {string} system - The name of the system this renderer is for. - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -37,27 +38,31 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(system, screenWidth, screenHeight, options) + constructor(system, options, arg2, arg3) { super(); sayHello(system); - // prepare options - if (options) + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') { - for (const i in settings.RENDER_OPTIONS) - { - if (typeof options[i] === 'undefined') - { - options[i] = settings.RENDER_OPTIONS[i]; - } - } + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); } - else - { - options = settings.RENDER_OPTIONS; - } + + // Add the default render options + options = Object.assign({}, settings.RENDER_OPTIONS, options); + + /** + * The supplied constructor options. + * + * @member {Object} + * @readOnly + */ + this.options = options; /** * The type of the renderer. @@ -75,7 +80,7 @@ * * @member {PIXI.Rectangle} */ - this.screen = new Rectangle(0, 0, screenWidth || 800, screenHeight || 600); + this.screen = new Rectangle(0, 0, options.width, options.height); /** * The canvas element that everything is drawn to @@ -279,6 +284,8 @@ this.blendModes = null; + this.options = null; + this.preserveDrawingBuffer = false; this.clearBeforeRender = false; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index e84e49c..691caf0 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -17,10 +17,11 @@ */ export default class CanvasRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -34,9 +35,9 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('Canvas', screenWidth, screenHeight, options); + super('Canvas', options, arg2, arg3); this.type = RENDERER_TYPE.CANVAS; @@ -96,7 +97,19 @@ this.context = null; this.renderingToScreen = false; - this.resize(screenWidth, screenHeight); + this.resize(this.options.width, this.options.height); + + /** + * Fired after rendering finishes. + * + * @event PIXI.CanvasRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.CanvasRenderer#prerender + */ } /** @@ -299,4 +312,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.CanvasRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.CanvasExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.CanvasPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.CanvasRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index e1cf7cd..f6fe123 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -31,11 +31,12 @@ */ export default class WebGLRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** * - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -55,9 +56,9 @@ * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('WebGL', screenWidth, screenHeight, options); + super('WebGL', options, arg2, arg3); /** * The type of this renderer as a standardised const @@ -89,12 +90,6 @@ }; - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ this._backgroundColorRgba[3] = this.transparent ? 0 : 1; this.globalUniforms = new UniformGroup({ @@ -117,6 +112,13 @@ this.initPlugins(); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ if(options.context) { this.context.initFromContext(options.context); @@ -169,6 +171,25 @@ } return this; + + /** + * Fired after rendering finishes. + * + * @event PIXI.WebGLRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.WebGLRenderer#prerender + */ + + /** + * Fired when the WebGL context is set. + * + * @event PIXI.WebGLRenderer#context + * @param {WebGLRenderingContext} gl - WebGL context. + */ } /** @@ -295,4 +316,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.WebGLRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.WebGLRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(WebGLRenderer); diff --git a/.travis.yml b/.travis.yml index 3df8b22..14de334 100644 --- a/.travis.yml +++ b/.travis.yml @@ -110,3 +110,43 @@ on: all_branches: true condition: $TRAVIS_TAG + # Deploy config for latest release + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: dist + upload-dir: "release" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: docs + upload-dir: "release/docs" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: coverage + upload-dir: "release/coverage" + on: + all_branches: true + condition: $TRAVIS_TAG \ No newline at end of file diff --git a/README.md b/README.md index d26fadd..3b85f85 100644 --- a/README.md +++ b/README.md @@ -36,22 +36,14 @@ - Wiki: Other misc tutorials and resources are [on the Wiki](https://github.com/pixijs/pixi.js/wiki/Resources). ### Community ### -- Forums: Check out the [forum] (http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow] (http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. +- Forums: Check out the [forum](http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow](http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. - Inspiration: Check out the [gallery](http://www.pixijs.com/gallery) to see some of the amazing things people have created! - Chat: You can join us on [Gitter](https://gitter.im/pixijs/pixi.js) To chat about Pixi. We also now have a Slack channel. If you would like to join it please Send me an email (mat@goodboydigital.com) and I will invite you in. ### Setup ### -It's easy to get started with Pixi.js! Simply grab the pre-built versions from here: - -Release Branch - Nice and stable Pixi.js -- Unminified: [http://pixijs.download/release/pixi.js] -- Minified: [http://pixijs.download/release/pixi.min.js] - -Develop Branch - The bleeding edge version of Pixi.js -- Unminified: [http://pixijs.download/dev/pixi.js] -- Minified: [http://pixijs.download/dev/pixi.min.js] +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. @@ -70,10 +62,10 @@ #### CDN Install (via cdnjs) ```html - + ``` -_Note: `4.2.2` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ +_Note: `4.4.3` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ ### Demos ### @@ -130,7 +122,7 @@ // and the root stage PIXI.Container. var app = new PIXI.Application(); -// The application will create a canvas element for you that you +// The application will create a canvas element for you that you // can then insert into the DOM. document.body.appendChild(app.view); @@ -150,7 +142,7 @@ // Add the bunny to the scene we are building. app.stage.addChild(bunny); - + // Listen for frame updates app.ticker.add(function() { // each frame we spin the bunny around a bit diff --git a/bower.json b/bower.json deleted file mode 100644 index 41b668c..0000000 --- a/bower.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "pixi.js", - "main": "dist/pixi.min.js", - "ignore": [ - "**/.*", - "docs", - "node_modules", - "bower_components", - "test", - "scripts", - "bower.json", - "inch.json" - ], - "dependencies": { - }, - "devDependencies": { - } -} diff --git a/package.json b/package.json index d35cbb2..308469f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.3.5", + "version": "4.4.4", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ @@ -40,11 +40,11 @@ "lib": "babel src --out-dir lib -s", "predocs": "rimraf docs/**", "docs": "jsdoc -c scripts/jsdoc.conf.json -R README.md", - "publish:patch": "npm version patch --no-git-tag-version && npm publish", - "publish:minor": "npm version minor --no-git-tag-version && npm publish", - "publish:major": "npm version major --no-git-tag-version && npm publish", + "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", - "postpublish": "node scripts/release.js" + "postpublish": "git push && git push --tags" }, "files": [ "dist/", @@ -72,7 +72,6 @@ "electron": "^1.4.15", "eslint": "^3.5.0", "floss": "^2.0.1", - "gh-pages": "^0.11.0", "jaguarjs-jsdoc": "^1.0.1", "js-md5": "^0.4.1", "jsdoc": "^3.4.2", diff --git a/scripts/jsdoc.conf.json b/scripts/jsdoc.conf.json index 553e61c..3f52180 100644 --- a/scripts/jsdoc.conf.json +++ b/scripts/jsdoc.conf.json @@ -43,7 +43,7 @@ }, "markdown" : { "parser" : "gfm", - "hardwrap" : true + "hardwrap" : false }, "opts": { "encoding" : "utf8", diff --git a/scripts/release.js b/scripts/release.js deleted file mode 100644 index 156620c..0000000 --- a/scripts/release.js +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env node -'use strict'; - -// Publish script to push releases of the bin files -// the normally are gitignored -const ghpages = require('gh-pages'); -const path = require('path'); -const packageInfo = require(path.join(__dirname, '..', 'package.json')); - -const options = { - src: [ - 'dist/**/*', - 'lib/**/*', - 'src/**/*', - 'scripts/*', - 'scripts/renders/*', - 'test/**/*', - '*.json', - '*.md', - 'LICENSE', - '.eslintrc', - '.editorconfig', - '.travis.yml', - '.babelrc', - ], - dotfiles: true, - branch: 'release', - message: packageInfo.version, - logger: console.log.bind(console), -}; - -ghpages.publish(process.cwd(), options, (err) => -{ - if (err) - { - console.log(err); - process.exit(1); - - return; - } - - process.exit(0); -}); diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index f16bcb1..220427a 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -21,7 +21,7 @@ const DIV_HOOK_ZINDEX = 2; /** - * The Accessibility manager reacreates the ability to tab and and have content read by screen + * The Accessibility manager recreates the ability to tab and and have content read by screen * readers. This is very important as it can possibly help people with disabilities access pixi * content. * diff --git a/src/accessibility/index.js b/src/accessibility/index.js index 568cfb1..0ae108f 100644 --- a/src/accessibility/index.js +++ b/src/accessibility/index.js @@ -1,4 +1,9 @@ /** + * This namespace contains a renderer plugin for interaction accessibility for end-users + * with physical impairments which require screen-renders, keyboard navigation, etc. + * + * Do not instantiate this plugin directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. * @namespace PIXI.accessibility */ export { default as accessibleTarget } from './accessibleTarget'; diff --git a/src/core/Application.js b/src/core/Application.js index 5340578..2fcc729 100644 --- a/src/core/Application.js +++ b/src/core/Application.js @@ -1,6 +1,8 @@ import { autoDetectRenderer } from './autoDetectRenderer'; import Container from './display/Container'; import { shared, Ticker } from './ticker'; +import settings from './settings'; +import { UPDATE_PRIORITY } from './const'; /** * Convenience class to create a new PIXI application. @@ -22,28 +24,52 @@ */ export default class Application { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experience unexplained flickering try setting this to true. - * @param {boolean} [useSharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedLoader=false] - `true` to use PIXI.loaders.shared, `false` to create new Loader. */ - constructor(width, height, options, noWebGL, useSharedTicker = false) + constructor(options, arg2, arg3, arg4, arg5) { + // Support for constructor(width, height, options, noWebGL, useSharedTicker) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + forceCanvas: !!arg4, + sharedTicker: !!arg5, + }, arg3); + } + + /** + * The default options, so we mixin functionality later. + * @member {object} + * @protected + */ + this._options = options = Object.assign({ + sharedTicker: false, + forceCanvas: false, + sharedLoader: false, + }, options); + /** * WebGL renderer if available, otherwise CanvasRenderer * @member {PIXI.WebGLRenderer|PIXI.CanvasRenderer} */ - this.renderer = autoDetectRenderer(width, height, options, noWebGL); + this.renderer = autoDetectRenderer(options); /** * The root display container that's rendered. @@ -63,7 +89,7 @@ * @member {PIXI.ticker.Ticker} * @default PIXI.ticker.shared */ - this.ticker = useSharedTicker ? shared : new Ticker(); + this.ticker = options.sharedTicker ? shared : new Ticker(); // Start the rendering this.start(); @@ -78,7 +104,7 @@ this._ticker = ticker; if (ticker) { - ticker.add(this.render, this); + ticker.add(this.render, this, UPDATE_PRIORITY.LOW); } } get ticker() // eslint-disable-line require-jsdoc @@ -136,13 +162,18 @@ */ destroy(removeView) { - this.stop(); + const oldTicker = this._ticker; + this.ticker = null; + oldTicker.destroy(); + this.stage.destroy(); this.stage = null; this.renderer.destroy(removeView); this.renderer = null; + + this._options = null; } } diff --git a/src/core/autoDetectRenderer.js b/src/core/autoDetectRenderer.js index 8960425..9ec0364 100644 --- a/src/core/autoDetectRenderer.js +++ b/src/core/autoDetectRenderer.js @@ -2,6 +2,7 @@ import CanvasRenderer from './renderers/canvas/CanvasRenderer'; import WebGLRenderer from './renderers/webgl/WebGLRenderer'; +// eslint-disable-next-line valid-jsdoc /** * This helper function will automatically detect which renderer you should be using. * WebGL is the preferred renderer as it is a lot faster. If webGL is not supported by @@ -9,24 +10,32 @@ * * @memberof PIXI * @function autoDetectRenderer - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @return {PIXI.WebGLRenderer|PIXI.CanvasRenderer} Returns WebGL renderer if available, otherwise CanvasRenderer */ -export function autoDetectRenderer(width = 800, height = 600, options, noWebGL) +export function autoDetectRenderer(options, arg1, arg2, arg3) { - if (!noWebGL && utils.isWebGLSupported()) + // Backward-compatible support for noWebGL option + let forceCanvas = options && options.forceCanvas; + + if (arg3 !== undefined) { - return new WebGLRenderer(width, height, options); + forceCanvas = arg3; } - return new CanvasRenderer(width, height, options); + if (!forceCanvas && utils.isWebGLSupported()) + { + return new WebGLRenderer(options, arg1, arg2); + } + + return new CanvasRenderer(options, arg1, arg2); } diff --git a/src/core/const.js b/src/core/const.js index b9e99ae..2c68dee 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -341,3 +341,27 @@ LINEAR_VERTICAL: 0, LINEAR_HORIZONTAL: 1, }; + +/** + * Represents the update priorities used by internal PIXI classes when registered with + * the {@link PIXI.ticker.Ticker} object. Higher priority items are updated first and lower + * priority items, such as render, should go later. + * + * @static + * @constant + * @name UPDATE_PRIORITY + * @memberof PIXI + * @type {object} + * @property {number} INTERACTION=50 Highest priority, used for {@link PIXI.interaction.InteractionManager} + * @property {number} HIGH=25 High priority updating, {@link PIXI.VideoBaseTexture} and {@link PIXI.extras.AnimatedSprite} + * @property {number} NORMAL=0 Default priority for ticker events, see {@link PIXI.ticker.Ticker#add}. + * @property {number} LOW=-25 Low priority used for {@link PIXI.Application} rendering. + * @property {number} UTILITY=-50 Lowest priority used for {@link PIXI.prepare.BasePrepare} utility. + */ +export const UPDATE_PRIORITY = { + INTERACTION: 50, + HIGH: 25, + NORMAL: 0, + LOW: -25, + UTILITY: -50, +}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 4e6c77d..decb42a 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -13,7 +13,6 @@ * * @class * @extends EventEmitter - * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ export default class DisplayObject extends EventEmitter @@ -113,6 +112,29 @@ * @private */ this._mask = null; + + /** + * If the object has been destroyed via destroy(). If true, it should not be used. + * + * @member {boolean} + * @private + * @readonly + */ + this._destroyed = false; + + /** + * Fired when this DisplayObject is added to a Container. + * + * @event PIXI.DisplayObject#added + * @param {PIXI.Container} container - The container added to. + */ + + /** + * Fired when this DisplayObject is removed from a Container. + * + * @event PIXI.DisplayObject#removed + * @param {PIXI.Container} container - The container removed from. + */ } /** @@ -403,6 +425,8 @@ this.interactive = false; this.interactiveChildren = false; + + this._destroyed = true; } /** diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index aafdfe2..f787c4a 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -792,8 +792,8 @@ sprite.worldAlpha = this.worldAlpha * sprite.alpha; sprite.blendMode = this.blendMode; - sprite.texture._frame.width = rect.width; - sprite.texture._frame.height = rect.height; + sprite._texture._frame.width = rect.width; + sprite._texture._frame.height = rect.height; sprite.transform.worldTransform = this.transform.worldTransform; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index a24c775..b9f192d 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -19,11 +19,12 @@ */ export default class SystemRenderer extends EventEmitter { + // eslint-disable-next-line valid-jsdoc /** * @param {string} system - The name of the system this renderer is for. - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -37,27 +38,31 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(system, screenWidth, screenHeight, options) + constructor(system, options, arg2, arg3) { super(); sayHello(system); - // prepare options - if (options) + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') { - for (const i in settings.RENDER_OPTIONS) - { - if (typeof options[i] === 'undefined') - { - options[i] = settings.RENDER_OPTIONS[i]; - } - } + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); } - else - { - options = settings.RENDER_OPTIONS; - } + + // Add the default render options + options = Object.assign({}, settings.RENDER_OPTIONS, options); + + /** + * The supplied constructor options. + * + * @member {Object} + * @readOnly + */ + this.options = options; /** * The type of the renderer. @@ -75,7 +80,7 @@ * * @member {PIXI.Rectangle} */ - this.screen = new Rectangle(0, 0, screenWidth || 800, screenHeight || 600); + this.screen = new Rectangle(0, 0, options.width, options.height); /** * The canvas element that everything is drawn to @@ -279,6 +284,8 @@ this.blendModes = null; + this.options = null; + this.preserveDrawingBuffer = false; this.clearBeforeRender = false; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index e84e49c..691caf0 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -17,10 +17,11 @@ */ export default class CanvasRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -34,9 +35,9 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('Canvas', screenWidth, screenHeight, options); + super('Canvas', options, arg2, arg3); this.type = RENDERER_TYPE.CANVAS; @@ -96,7 +97,19 @@ this.context = null; this.renderingToScreen = false; - this.resize(screenWidth, screenHeight); + this.resize(this.options.width, this.options.height); + + /** + * Fired after rendering finishes. + * + * @event PIXI.CanvasRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.CanvasRenderer#prerender + */ } /** @@ -299,4 +312,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.CanvasRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.CanvasExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.CanvasPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.CanvasRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index e1cf7cd..f6fe123 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -31,11 +31,12 @@ */ export default class WebGLRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** * - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -55,9 +56,9 @@ * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('WebGL', screenWidth, screenHeight, options); + super('WebGL', options, arg2, arg3); /** * The type of this renderer as a standardised const @@ -89,12 +90,6 @@ }; - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ this._backgroundColorRgba[3] = this.transparent ? 0 : 1; this.globalUniforms = new UniformGroup({ @@ -117,6 +112,13 @@ this.initPlugins(); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ if(options.context) { this.context.initFromContext(options.context); @@ -169,6 +171,25 @@ } return this; + + /** + * Fired after rendering finishes. + * + * @event PIXI.WebGLRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.WebGLRenderer#prerender + */ + + /** + * Fired when the WebGL context is set. + * + * @event PIXI.WebGLRenderer#context + * @param {WebGLRenderingContext} gl - WebGL context. + */ } /** @@ -295,4 +316,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.WebGLRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.WebGLRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(WebGLRenderer); diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index 4eb6128..fa3d883 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -47,6 +47,14 @@ * @member {boolean} */ this.enabled = true; + + /** + * If enabled, pixi will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; } /** diff --git a/.travis.yml b/.travis.yml index 3df8b22..14de334 100644 --- a/.travis.yml +++ b/.travis.yml @@ -110,3 +110,43 @@ on: all_branches: true condition: $TRAVIS_TAG + # Deploy config for latest release + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: dist + upload-dir: "release" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: docs + upload-dir: "release/docs" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: coverage + upload-dir: "release/coverage" + on: + all_branches: true + condition: $TRAVIS_TAG \ No newline at end of file diff --git a/README.md b/README.md index d26fadd..3b85f85 100644 --- a/README.md +++ b/README.md @@ -36,22 +36,14 @@ - Wiki: Other misc tutorials and resources are [on the Wiki](https://github.com/pixijs/pixi.js/wiki/Resources). ### Community ### -- Forums: Check out the [forum] (http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow] (http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. +- Forums: Check out the [forum](http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow](http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. - Inspiration: Check out the [gallery](http://www.pixijs.com/gallery) to see some of the amazing things people have created! - Chat: You can join us on [Gitter](https://gitter.im/pixijs/pixi.js) To chat about Pixi. We also now have a Slack channel. If you would like to join it please Send me an email (mat@goodboydigital.com) and I will invite you in. ### Setup ### -It's easy to get started with Pixi.js! Simply grab the pre-built versions from here: - -Release Branch - Nice and stable Pixi.js -- Unminified: [http://pixijs.download/release/pixi.js] -- Minified: [http://pixijs.download/release/pixi.min.js] - -Develop Branch - The bleeding edge version of Pixi.js -- Unminified: [http://pixijs.download/dev/pixi.js] -- Minified: [http://pixijs.download/dev/pixi.min.js] +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. @@ -70,10 +62,10 @@ #### CDN Install (via cdnjs) ```html - + ``` -_Note: `4.2.2` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ +_Note: `4.4.3` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ ### Demos ### @@ -130,7 +122,7 @@ // and the root stage PIXI.Container. var app = new PIXI.Application(); -// The application will create a canvas element for you that you +// The application will create a canvas element for you that you // can then insert into the DOM. document.body.appendChild(app.view); @@ -150,7 +142,7 @@ // Add the bunny to the scene we are building. app.stage.addChild(bunny); - + // Listen for frame updates app.ticker.add(function() { // each frame we spin the bunny around a bit diff --git a/bower.json b/bower.json deleted file mode 100644 index 41b668c..0000000 --- a/bower.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "pixi.js", - "main": "dist/pixi.min.js", - "ignore": [ - "**/.*", - "docs", - "node_modules", - "bower_components", - "test", - "scripts", - "bower.json", - "inch.json" - ], - "dependencies": { - }, - "devDependencies": { - } -} diff --git a/package.json b/package.json index d35cbb2..308469f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.3.5", + "version": "4.4.4", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ @@ -40,11 +40,11 @@ "lib": "babel src --out-dir lib -s", "predocs": "rimraf docs/**", "docs": "jsdoc -c scripts/jsdoc.conf.json -R README.md", - "publish:patch": "npm version patch --no-git-tag-version && npm publish", - "publish:minor": "npm version minor --no-git-tag-version && npm publish", - "publish:major": "npm version major --no-git-tag-version && npm publish", + "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", - "postpublish": "node scripts/release.js" + "postpublish": "git push && git push --tags" }, "files": [ "dist/", @@ -72,7 +72,6 @@ "electron": "^1.4.15", "eslint": "^3.5.0", "floss": "^2.0.1", - "gh-pages": "^0.11.0", "jaguarjs-jsdoc": "^1.0.1", "js-md5": "^0.4.1", "jsdoc": "^3.4.2", diff --git a/scripts/jsdoc.conf.json b/scripts/jsdoc.conf.json index 553e61c..3f52180 100644 --- a/scripts/jsdoc.conf.json +++ b/scripts/jsdoc.conf.json @@ -43,7 +43,7 @@ }, "markdown" : { "parser" : "gfm", - "hardwrap" : true + "hardwrap" : false }, "opts": { "encoding" : "utf8", diff --git a/scripts/release.js b/scripts/release.js deleted file mode 100644 index 156620c..0000000 --- a/scripts/release.js +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env node -'use strict'; - -// Publish script to push releases of the bin files -// the normally are gitignored -const ghpages = require('gh-pages'); -const path = require('path'); -const packageInfo = require(path.join(__dirname, '..', 'package.json')); - -const options = { - src: [ - 'dist/**/*', - 'lib/**/*', - 'src/**/*', - 'scripts/*', - 'scripts/renders/*', - 'test/**/*', - '*.json', - '*.md', - 'LICENSE', - '.eslintrc', - '.editorconfig', - '.travis.yml', - '.babelrc', - ], - dotfiles: true, - branch: 'release', - message: packageInfo.version, - logger: console.log.bind(console), -}; - -ghpages.publish(process.cwd(), options, (err) => -{ - if (err) - { - console.log(err); - process.exit(1); - - return; - } - - process.exit(0); -}); diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index f16bcb1..220427a 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -21,7 +21,7 @@ const DIV_HOOK_ZINDEX = 2; /** - * The Accessibility manager reacreates the ability to tab and and have content read by screen + * The Accessibility manager recreates the ability to tab and and have content read by screen * readers. This is very important as it can possibly help people with disabilities access pixi * content. * diff --git a/src/accessibility/index.js b/src/accessibility/index.js index 568cfb1..0ae108f 100644 --- a/src/accessibility/index.js +++ b/src/accessibility/index.js @@ -1,4 +1,9 @@ /** + * This namespace contains a renderer plugin for interaction accessibility for end-users + * with physical impairments which require screen-renders, keyboard navigation, etc. + * + * Do not instantiate this plugin directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. * @namespace PIXI.accessibility */ export { default as accessibleTarget } from './accessibleTarget'; diff --git a/src/core/Application.js b/src/core/Application.js index 5340578..2fcc729 100644 --- a/src/core/Application.js +++ b/src/core/Application.js @@ -1,6 +1,8 @@ import { autoDetectRenderer } from './autoDetectRenderer'; import Container from './display/Container'; import { shared, Ticker } from './ticker'; +import settings from './settings'; +import { UPDATE_PRIORITY } from './const'; /** * Convenience class to create a new PIXI application. @@ -22,28 +24,52 @@ */ export default class Application { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experience unexplained flickering try setting this to true. - * @param {boolean} [useSharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedLoader=false] - `true` to use PIXI.loaders.shared, `false` to create new Loader. */ - constructor(width, height, options, noWebGL, useSharedTicker = false) + constructor(options, arg2, arg3, arg4, arg5) { + // Support for constructor(width, height, options, noWebGL, useSharedTicker) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + forceCanvas: !!arg4, + sharedTicker: !!arg5, + }, arg3); + } + + /** + * The default options, so we mixin functionality later. + * @member {object} + * @protected + */ + this._options = options = Object.assign({ + sharedTicker: false, + forceCanvas: false, + sharedLoader: false, + }, options); + /** * WebGL renderer if available, otherwise CanvasRenderer * @member {PIXI.WebGLRenderer|PIXI.CanvasRenderer} */ - this.renderer = autoDetectRenderer(width, height, options, noWebGL); + this.renderer = autoDetectRenderer(options); /** * The root display container that's rendered. @@ -63,7 +89,7 @@ * @member {PIXI.ticker.Ticker} * @default PIXI.ticker.shared */ - this.ticker = useSharedTicker ? shared : new Ticker(); + this.ticker = options.sharedTicker ? shared : new Ticker(); // Start the rendering this.start(); @@ -78,7 +104,7 @@ this._ticker = ticker; if (ticker) { - ticker.add(this.render, this); + ticker.add(this.render, this, UPDATE_PRIORITY.LOW); } } get ticker() // eslint-disable-line require-jsdoc @@ -136,13 +162,18 @@ */ destroy(removeView) { - this.stop(); + const oldTicker = this._ticker; + this.ticker = null; + oldTicker.destroy(); + this.stage.destroy(); this.stage = null; this.renderer.destroy(removeView); this.renderer = null; + + this._options = null; } } diff --git a/src/core/autoDetectRenderer.js b/src/core/autoDetectRenderer.js index 8960425..9ec0364 100644 --- a/src/core/autoDetectRenderer.js +++ b/src/core/autoDetectRenderer.js @@ -2,6 +2,7 @@ import CanvasRenderer from './renderers/canvas/CanvasRenderer'; import WebGLRenderer from './renderers/webgl/WebGLRenderer'; +// eslint-disable-next-line valid-jsdoc /** * This helper function will automatically detect which renderer you should be using. * WebGL is the preferred renderer as it is a lot faster. If webGL is not supported by @@ -9,24 +10,32 @@ * * @memberof PIXI * @function autoDetectRenderer - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @return {PIXI.WebGLRenderer|PIXI.CanvasRenderer} Returns WebGL renderer if available, otherwise CanvasRenderer */ -export function autoDetectRenderer(width = 800, height = 600, options, noWebGL) +export function autoDetectRenderer(options, arg1, arg2, arg3) { - if (!noWebGL && utils.isWebGLSupported()) + // Backward-compatible support for noWebGL option + let forceCanvas = options && options.forceCanvas; + + if (arg3 !== undefined) { - return new WebGLRenderer(width, height, options); + forceCanvas = arg3; } - return new CanvasRenderer(width, height, options); + if (!forceCanvas && utils.isWebGLSupported()) + { + return new WebGLRenderer(options, arg1, arg2); + } + + return new CanvasRenderer(options, arg1, arg2); } diff --git a/src/core/const.js b/src/core/const.js index b9e99ae..2c68dee 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -341,3 +341,27 @@ LINEAR_VERTICAL: 0, LINEAR_HORIZONTAL: 1, }; + +/** + * Represents the update priorities used by internal PIXI classes when registered with + * the {@link PIXI.ticker.Ticker} object. Higher priority items are updated first and lower + * priority items, such as render, should go later. + * + * @static + * @constant + * @name UPDATE_PRIORITY + * @memberof PIXI + * @type {object} + * @property {number} INTERACTION=50 Highest priority, used for {@link PIXI.interaction.InteractionManager} + * @property {number} HIGH=25 High priority updating, {@link PIXI.VideoBaseTexture} and {@link PIXI.extras.AnimatedSprite} + * @property {number} NORMAL=0 Default priority for ticker events, see {@link PIXI.ticker.Ticker#add}. + * @property {number} LOW=-25 Low priority used for {@link PIXI.Application} rendering. + * @property {number} UTILITY=-50 Lowest priority used for {@link PIXI.prepare.BasePrepare} utility. + */ +export const UPDATE_PRIORITY = { + INTERACTION: 50, + HIGH: 25, + NORMAL: 0, + LOW: -25, + UTILITY: -50, +}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 4e6c77d..decb42a 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -13,7 +13,6 @@ * * @class * @extends EventEmitter - * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ export default class DisplayObject extends EventEmitter @@ -113,6 +112,29 @@ * @private */ this._mask = null; + + /** + * If the object has been destroyed via destroy(). If true, it should not be used. + * + * @member {boolean} + * @private + * @readonly + */ + this._destroyed = false; + + /** + * Fired when this DisplayObject is added to a Container. + * + * @event PIXI.DisplayObject#added + * @param {PIXI.Container} container - The container added to. + */ + + /** + * Fired when this DisplayObject is removed from a Container. + * + * @event PIXI.DisplayObject#removed + * @param {PIXI.Container} container - The container removed from. + */ } /** @@ -403,6 +425,8 @@ this.interactive = false; this.interactiveChildren = false; + + this._destroyed = true; } /** diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index aafdfe2..f787c4a 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -792,8 +792,8 @@ sprite.worldAlpha = this.worldAlpha * sprite.alpha; sprite.blendMode = this.blendMode; - sprite.texture._frame.width = rect.width; - sprite.texture._frame.height = rect.height; + sprite._texture._frame.width = rect.width; + sprite._texture._frame.height = rect.height; sprite.transform.worldTransform = this.transform.worldTransform; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index a24c775..b9f192d 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -19,11 +19,12 @@ */ export default class SystemRenderer extends EventEmitter { + // eslint-disable-next-line valid-jsdoc /** * @param {string} system - The name of the system this renderer is for. - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -37,27 +38,31 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(system, screenWidth, screenHeight, options) + constructor(system, options, arg2, arg3) { super(); sayHello(system); - // prepare options - if (options) + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') { - for (const i in settings.RENDER_OPTIONS) - { - if (typeof options[i] === 'undefined') - { - options[i] = settings.RENDER_OPTIONS[i]; - } - } + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); } - else - { - options = settings.RENDER_OPTIONS; - } + + // Add the default render options + options = Object.assign({}, settings.RENDER_OPTIONS, options); + + /** + * The supplied constructor options. + * + * @member {Object} + * @readOnly + */ + this.options = options; /** * The type of the renderer. @@ -75,7 +80,7 @@ * * @member {PIXI.Rectangle} */ - this.screen = new Rectangle(0, 0, screenWidth || 800, screenHeight || 600); + this.screen = new Rectangle(0, 0, options.width, options.height); /** * The canvas element that everything is drawn to @@ -279,6 +284,8 @@ this.blendModes = null; + this.options = null; + this.preserveDrawingBuffer = false; this.clearBeforeRender = false; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index e84e49c..691caf0 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -17,10 +17,11 @@ */ export default class CanvasRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -34,9 +35,9 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('Canvas', screenWidth, screenHeight, options); + super('Canvas', options, arg2, arg3); this.type = RENDERER_TYPE.CANVAS; @@ -96,7 +97,19 @@ this.context = null; this.renderingToScreen = false; - this.resize(screenWidth, screenHeight); + this.resize(this.options.width, this.options.height); + + /** + * Fired after rendering finishes. + * + * @event PIXI.CanvasRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.CanvasRenderer#prerender + */ } /** @@ -299,4 +312,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.CanvasRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.CanvasExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.CanvasPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.CanvasRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index e1cf7cd..f6fe123 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -31,11 +31,12 @@ */ export default class WebGLRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** * - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -55,9 +56,9 @@ * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('WebGL', screenWidth, screenHeight, options); + super('WebGL', options, arg2, arg3); /** * The type of this renderer as a standardised const @@ -89,12 +90,6 @@ }; - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ this._backgroundColorRgba[3] = this.transparent ? 0 : 1; this.globalUniforms = new UniformGroup({ @@ -117,6 +112,13 @@ this.initPlugins(); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ if(options.context) { this.context.initFromContext(options.context); @@ -169,6 +171,25 @@ } return this; + + /** + * Fired after rendering finishes. + * + * @event PIXI.WebGLRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.WebGLRenderer#prerender + */ + + /** + * Fired when the WebGL context is set. + * + * @event PIXI.WebGLRenderer#context + * @param {WebGLRenderingContext} gl - WebGL context. + */ } /** @@ -295,4 +316,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.WebGLRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.WebGLRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(WebGLRenderer); diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index 4eb6128..fa3d883 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -47,6 +47,14 @@ * @member {boolean} */ this.enabled = true; + + /** + * If enabled, pixi will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; } /** diff --git a/src/core/renderers/webgl/systems/ContextSystem.js b/src/core/renderers/webgl/systems/ContextSystem.js index ff958c7..8df85ea 100644 --- a/src/core/renderers/webgl/systems/ContextSystem.js +++ b/src/core/renderers/webgl/systems/ContextSystem.js @@ -75,7 +75,8 @@ */ createContext(canvas, options) { - var gl = canvas.getContext('webgl', options) || + var gl = //canvas.getContext('webgl2', options) || + canvas.getContext('webgl', options) || canvas.getContext('experimental-webgl', options); if (!gl) diff --git a/.travis.yml b/.travis.yml index 3df8b22..14de334 100644 --- a/.travis.yml +++ b/.travis.yml @@ -110,3 +110,43 @@ on: all_branches: true condition: $TRAVIS_TAG + # Deploy config for latest release + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: dist + upload-dir: "release" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: docs + upload-dir: "release/docs" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: coverage + upload-dir: "release/coverage" + on: + all_branches: true + condition: $TRAVIS_TAG \ No newline at end of file diff --git a/README.md b/README.md index d26fadd..3b85f85 100644 --- a/README.md +++ b/README.md @@ -36,22 +36,14 @@ - Wiki: Other misc tutorials and resources are [on the Wiki](https://github.com/pixijs/pixi.js/wiki/Resources). ### Community ### -- Forums: Check out the [forum] (http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow] (http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. +- Forums: Check out the [forum](http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow](http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. - Inspiration: Check out the [gallery](http://www.pixijs.com/gallery) to see some of the amazing things people have created! - Chat: You can join us on [Gitter](https://gitter.im/pixijs/pixi.js) To chat about Pixi. We also now have a Slack channel. If you would like to join it please Send me an email (mat@goodboydigital.com) and I will invite you in. ### Setup ### -It's easy to get started with Pixi.js! Simply grab the pre-built versions from here: - -Release Branch - Nice and stable Pixi.js -- Unminified: [http://pixijs.download/release/pixi.js] -- Minified: [http://pixijs.download/release/pixi.min.js] - -Develop Branch - The bleeding edge version of Pixi.js -- Unminified: [http://pixijs.download/dev/pixi.js] -- Minified: [http://pixijs.download/dev/pixi.min.js] +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. @@ -70,10 +62,10 @@ #### CDN Install (via cdnjs) ```html - + ``` -_Note: `4.2.2` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ +_Note: `4.4.3` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ ### Demos ### @@ -130,7 +122,7 @@ // and the root stage PIXI.Container. var app = new PIXI.Application(); -// The application will create a canvas element for you that you +// The application will create a canvas element for you that you // can then insert into the DOM. document.body.appendChild(app.view); @@ -150,7 +142,7 @@ // Add the bunny to the scene we are building. app.stage.addChild(bunny); - + // Listen for frame updates app.ticker.add(function() { // each frame we spin the bunny around a bit diff --git a/bower.json b/bower.json deleted file mode 100644 index 41b668c..0000000 --- a/bower.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "pixi.js", - "main": "dist/pixi.min.js", - "ignore": [ - "**/.*", - "docs", - "node_modules", - "bower_components", - "test", - "scripts", - "bower.json", - "inch.json" - ], - "dependencies": { - }, - "devDependencies": { - } -} diff --git a/package.json b/package.json index d35cbb2..308469f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.3.5", + "version": "4.4.4", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ @@ -40,11 +40,11 @@ "lib": "babel src --out-dir lib -s", "predocs": "rimraf docs/**", "docs": "jsdoc -c scripts/jsdoc.conf.json -R README.md", - "publish:patch": "npm version patch --no-git-tag-version && npm publish", - "publish:minor": "npm version minor --no-git-tag-version && npm publish", - "publish:major": "npm version major --no-git-tag-version && npm publish", + "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", - "postpublish": "node scripts/release.js" + "postpublish": "git push && git push --tags" }, "files": [ "dist/", @@ -72,7 +72,6 @@ "electron": "^1.4.15", "eslint": "^3.5.0", "floss": "^2.0.1", - "gh-pages": "^0.11.0", "jaguarjs-jsdoc": "^1.0.1", "js-md5": "^0.4.1", "jsdoc": "^3.4.2", diff --git a/scripts/jsdoc.conf.json b/scripts/jsdoc.conf.json index 553e61c..3f52180 100644 --- a/scripts/jsdoc.conf.json +++ b/scripts/jsdoc.conf.json @@ -43,7 +43,7 @@ }, "markdown" : { "parser" : "gfm", - "hardwrap" : true + "hardwrap" : false }, "opts": { "encoding" : "utf8", diff --git a/scripts/release.js b/scripts/release.js deleted file mode 100644 index 156620c..0000000 --- a/scripts/release.js +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env node -'use strict'; - -// Publish script to push releases of the bin files -// the normally are gitignored -const ghpages = require('gh-pages'); -const path = require('path'); -const packageInfo = require(path.join(__dirname, '..', 'package.json')); - -const options = { - src: [ - 'dist/**/*', - 'lib/**/*', - 'src/**/*', - 'scripts/*', - 'scripts/renders/*', - 'test/**/*', - '*.json', - '*.md', - 'LICENSE', - '.eslintrc', - '.editorconfig', - '.travis.yml', - '.babelrc', - ], - dotfiles: true, - branch: 'release', - message: packageInfo.version, - logger: console.log.bind(console), -}; - -ghpages.publish(process.cwd(), options, (err) => -{ - if (err) - { - console.log(err); - process.exit(1); - - return; - } - - process.exit(0); -}); diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index f16bcb1..220427a 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -21,7 +21,7 @@ const DIV_HOOK_ZINDEX = 2; /** - * The Accessibility manager reacreates the ability to tab and and have content read by screen + * The Accessibility manager recreates the ability to tab and and have content read by screen * readers. This is very important as it can possibly help people with disabilities access pixi * content. * diff --git a/src/accessibility/index.js b/src/accessibility/index.js index 568cfb1..0ae108f 100644 --- a/src/accessibility/index.js +++ b/src/accessibility/index.js @@ -1,4 +1,9 @@ /** + * This namespace contains a renderer plugin for interaction accessibility for end-users + * with physical impairments which require screen-renders, keyboard navigation, etc. + * + * Do not instantiate this plugin directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. * @namespace PIXI.accessibility */ export { default as accessibleTarget } from './accessibleTarget'; diff --git a/src/core/Application.js b/src/core/Application.js index 5340578..2fcc729 100644 --- a/src/core/Application.js +++ b/src/core/Application.js @@ -1,6 +1,8 @@ import { autoDetectRenderer } from './autoDetectRenderer'; import Container from './display/Container'; import { shared, Ticker } from './ticker'; +import settings from './settings'; +import { UPDATE_PRIORITY } from './const'; /** * Convenience class to create a new PIXI application. @@ -22,28 +24,52 @@ */ export default class Application { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experience unexplained flickering try setting this to true. - * @param {boolean} [useSharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedLoader=false] - `true` to use PIXI.loaders.shared, `false` to create new Loader. */ - constructor(width, height, options, noWebGL, useSharedTicker = false) + constructor(options, arg2, arg3, arg4, arg5) { + // Support for constructor(width, height, options, noWebGL, useSharedTicker) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + forceCanvas: !!arg4, + sharedTicker: !!arg5, + }, arg3); + } + + /** + * The default options, so we mixin functionality later. + * @member {object} + * @protected + */ + this._options = options = Object.assign({ + sharedTicker: false, + forceCanvas: false, + sharedLoader: false, + }, options); + /** * WebGL renderer if available, otherwise CanvasRenderer * @member {PIXI.WebGLRenderer|PIXI.CanvasRenderer} */ - this.renderer = autoDetectRenderer(width, height, options, noWebGL); + this.renderer = autoDetectRenderer(options); /** * The root display container that's rendered. @@ -63,7 +89,7 @@ * @member {PIXI.ticker.Ticker} * @default PIXI.ticker.shared */ - this.ticker = useSharedTicker ? shared : new Ticker(); + this.ticker = options.sharedTicker ? shared : new Ticker(); // Start the rendering this.start(); @@ -78,7 +104,7 @@ this._ticker = ticker; if (ticker) { - ticker.add(this.render, this); + ticker.add(this.render, this, UPDATE_PRIORITY.LOW); } } get ticker() // eslint-disable-line require-jsdoc @@ -136,13 +162,18 @@ */ destroy(removeView) { - this.stop(); + const oldTicker = this._ticker; + this.ticker = null; + oldTicker.destroy(); + this.stage.destroy(); this.stage = null; this.renderer.destroy(removeView); this.renderer = null; + + this._options = null; } } diff --git a/src/core/autoDetectRenderer.js b/src/core/autoDetectRenderer.js index 8960425..9ec0364 100644 --- a/src/core/autoDetectRenderer.js +++ b/src/core/autoDetectRenderer.js @@ -2,6 +2,7 @@ import CanvasRenderer from './renderers/canvas/CanvasRenderer'; import WebGLRenderer from './renderers/webgl/WebGLRenderer'; +// eslint-disable-next-line valid-jsdoc /** * This helper function will automatically detect which renderer you should be using. * WebGL is the preferred renderer as it is a lot faster. If webGL is not supported by @@ -9,24 +10,32 @@ * * @memberof PIXI * @function autoDetectRenderer - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @return {PIXI.WebGLRenderer|PIXI.CanvasRenderer} Returns WebGL renderer if available, otherwise CanvasRenderer */ -export function autoDetectRenderer(width = 800, height = 600, options, noWebGL) +export function autoDetectRenderer(options, arg1, arg2, arg3) { - if (!noWebGL && utils.isWebGLSupported()) + // Backward-compatible support for noWebGL option + let forceCanvas = options && options.forceCanvas; + + if (arg3 !== undefined) { - return new WebGLRenderer(width, height, options); + forceCanvas = arg3; } - return new CanvasRenderer(width, height, options); + if (!forceCanvas && utils.isWebGLSupported()) + { + return new WebGLRenderer(options, arg1, arg2); + } + + return new CanvasRenderer(options, arg1, arg2); } diff --git a/src/core/const.js b/src/core/const.js index b9e99ae..2c68dee 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -341,3 +341,27 @@ LINEAR_VERTICAL: 0, LINEAR_HORIZONTAL: 1, }; + +/** + * Represents the update priorities used by internal PIXI classes when registered with + * the {@link PIXI.ticker.Ticker} object. Higher priority items are updated first and lower + * priority items, such as render, should go later. + * + * @static + * @constant + * @name UPDATE_PRIORITY + * @memberof PIXI + * @type {object} + * @property {number} INTERACTION=50 Highest priority, used for {@link PIXI.interaction.InteractionManager} + * @property {number} HIGH=25 High priority updating, {@link PIXI.VideoBaseTexture} and {@link PIXI.extras.AnimatedSprite} + * @property {number} NORMAL=0 Default priority for ticker events, see {@link PIXI.ticker.Ticker#add}. + * @property {number} LOW=-25 Low priority used for {@link PIXI.Application} rendering. + * @property {number} UTILITY=-50 Lowest priority used for {@link PIXI.prepare.BasePrepare} utility. + */ +export const UPDATE_PRIORITY = { + INTERACTION: 50, + HIGH: 25, + NORMAL: 0, + LOW: -25, + UTILITY: -50, +}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 4e6c77d..decb42a 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -13,7 +13,6 @@ * * @class * @extends EventEmitter - * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ export default class DisplayObject extends EventEmitter @@ -113,6 +112,29 @@ * @private */ this._mask = null; + + /** + * If the object has been destroyed via destroy(). If true, it should not be used. + * + * @member {boolean} + * @private + * @readonly + */ + this._destroyed = false; + + /** + * Fired when this DisplayObject is added to a Container. + * + * @event PIXI.DisplayObject#added + * @param {PIXI.Container} container - The container added to. + */ + + /** + * Fired when this DisplayObject is removed from a Container. + * + * @event PIXI.DisplayObject#removed + * @param {PIXI.Container} container - The container removed from. + */ } /** @@ -403,6 +425,8 @@ this.interactive = false; this.interactiveChildren = false; + + this._destroyed = true; } /** diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index aafdfe2..f787c4a 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -792,8 +792,8 @@ sprite.worldAlpha = this.worldAlpha * sprite.alpha; sprite.blendMode = this.blendMode; - sprite.texture._frame.width = rect.width; - sprite.texture._frame.height = rect.height; + sprite._texture._frame.width = rect.width; + sprite._texture._frame.height = rect.height; sprite.transform.worldTransform = this.transform.worldTransform; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index a24c775..b9f192d 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -19,11 +19,12 @@ */ export default class SystemRenderer extends EventEmitter { + // eslint-disable-next-line valid-jsdoc /** * @param {string} system - The name of the system this renderer is for. - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -37,27 +38,31 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(system, screenWidth, screenHeight, options) + constructor(system, options, arg2, arg3) { super(); sayHello(system); - // prepare options - if (options) + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') { - for (const i in settings.RENDER_OPTIONS) - { - if (typeof options[i] === 'undefined') - { - options[i] = settings.RENDER_OPTIONS[i]; - } - } + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); } - else - { - options = settings.RENDER_OPTIONS; - } + + // Add the default render options + options = Object.assign({}, settings.RENDER_OPTIONS, options); + + /** + * The supplied constructor options. + * + * @member {Object} + * @readOnly + */ + this.options = options; /** * The type of the renderer. @@ -75,7 +80,7 @@ * * @member {PIXI.Rectangle} */ - this.screen = new Rectangle(0, 0, screenWidth || 800, screenHeight || 600); + this.screen = new Rectangle(0, 0, options.width, options.height); /** * The canvas element that everything is drawn to @@ -279,6 +284,8 @@ this.blendModes = null; + this.options = null; + this.preserveDrawingBuffer = false; this.clearBeforeRender = false; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index e84e49c..691caf0 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -17,10 +17,11 @@ */ export default class CanvasRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -34,9 +35,9 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('Canvas', screenWidth, screenHeight, options); + super('Canvas', options, arg2, arg3); this.type = RENDERER_TYPE.CANVAS; @@ -96,7 +97,19 @@ this.context = null; this.renderingToScreen = false; - this.resize(screenWidth, screenHeight); + this.resize(this.options.width, this.options.height); + + /** + * Fired after rendering finishes. + * + * @event PIXI.CanvasRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.CanvasRenderer#prerender + */ } /** @@ -299,4 +312,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.CanvasRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.CanvasExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.CanvasPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.CanvasRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index e1cf7cd..f6fe123 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -31,11 +31,12 @@ */ export default class WebGLRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** * - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -55,9 +56,9 @@ * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('WebGL', screenWidth, screenHeight, options); + super('WebGL', options, arg2, arg3); /** * The type of this renderer as a standardised const @@ -89,12 +90,6 @@ }; - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ this._backgroundColorRgba[3] = this.transparent ? 0 : 1; this.globalUniforms = new UniformGroup({ @@ -117,6 +112,13 @@ this.initPlugins(); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ if(options.context) { this.context.initFromContext(options.context); @@ -169,6 +171,25 @@ } return this; + + /** + * Fired after rendering finishes. + * + * @event PIXI.WebGLRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.WebGLRenderer#prerender + */ + + /** + * Fired when the WebGL context is set. + * + * @event PIXI.WebGLRenderer#context + * @param {WebGLRenderingContext} gl - WebGL context. + */ } /** @@ -295,4 +316,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.WebGLRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.WebGLRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(WebGLRenderer); diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index 4eb6128..fa3d883 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -47,6 +47,14 @@ * @member {boolean} */ this.enabled = true; + + /** + * If enabled, pixi will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; } /** diff --git a/src/core/renderers/webgl/systems/ContextSystem.js b/src/core/renderers/webgl/systems/ContextSystem.js index ff958c7..8df85ea 100644 --- a/src/core/renderers/webgl/systems/ContextSystem.js +++ b/src/core/renderers/webgl/systems/ContextSystem.js @@ -75,7 +75,8 @@ */ createContext(canvas, options) { - var gl = canvas.getContext('webgl', options) || + var gl = //canvas.getContext('webgl2', options) || + canvas.getContext('webgl', options) || canvas.getContext('experimental-webgl', options); if (!gl) diff --git a/src/core/renderers/webgl/systems/FilterSystem.js b/src/core/renderers/webgl/systems/FilterSystem.js index f47335d..37e28d2 100644 --- a/src/core/renderers/webgl/systems/FilterSystem.js +++ b/src/core/renderers/webgl/systems/FilterSystem.js @@ -111,7 +111,7 @@ // TODO we should fit the rect around the transform.. } - else + else if (filters[0].autoFit) { sourceFrame.fit(filterData.stack[0].destinationFrame); } diff --git a/.travis.yml b/.travis.yml index 3df8b22..14de334 100644 --- a/.travis.yml +++ b/.travis.yml @@ -110,3 +110,43 @@ on: all_branches: true condition: $TRAVIS_TAG + # Deploy config for latest release + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: dist + upload-dir: "release" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: docs + upload-dir: "release/docs" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: coverage + upload-dir: "release/coverage" + on: + all_branches: true + condition: $TRAVIS_TAG \ No newline at end of file diff --git a/README.md b/README.md index d26fadd..3b85f85 100644 --- a/README.md +++ b/README.md @@ -36,22 +36,14 @@ - Wiki: Other misc tutorials and resources are [on the Wiki](https://github.com/pixijs/pixi.js/wiki/Resources). ### Community ### -- Forums: Check out the [forum] (http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow] (http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. +- Forums: Check out the [forum](http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow](http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. - Inspiration: Check out the [gallery](http://www.pixijs.com/gallery) to see some of the amazing things people have created! - Chat: You can join us on [Gitter](https://gitter.im/pixijs/pixi.js) To chat about Pixi. We also now have a Slack channel. If you would like to join it please Send me an email (mat@goodboydigital.com) and I will invite you in. ### Setup ### -It's easy to get started with Pixi.js! Simply grab the pre-built versions from here: - -Release Branch - Nice and stable Pixi.js -- Unminified: [http://pixijs.download/release/pixi.js] -- Minified: [http://pixijs.download/release/pixi.min.js] - -Develop Branch - The bleeding edge version of Pixi.js -- Unminified: [http://pixijs.download/dev/pixi.js] -- Minified: [http://pixijs.download/dev/pixi.min.js] +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. @@ -70,10 +62,10 @@ #### CDN Install (via cdnjs) ```html - + ``` -_Note: `4.2.2` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ +_Note: `4.4.3` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ ### Demos ### @@ -130,7 +122,7 @@ // and the root stage PIXI.Container. var app = new PIXI.Application(); -// The application will create a canvas element for you that you +// The application will create a canvas element for you that you // can then insert into the DOM. document.body.appendChild(app.view); @@ -150,7 +142,7 @@ // Add the bunny to the scene we are building. app.stage.addChild(bunny); - + // Listen for frame updates app.ticker.add(function() { // each frame we spin the bunny around a bit diff --git a/bower.json b/bower.json deleted file mode 100644 index 41b668c..0000000 --- a/bower.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "pixi.js", - "main": "dist/pixi.min.js", - "ignore": [ - "**/.*", - "docs", - "node_modules", - "bower_components", - "test", - "scripts", - "bower.json", - "inch.json" - ], - "dependencies": { - }, - "devDependencies": { - } -} diff --git a/package.json b/package.json index d35cbb2..308469f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.3.5", + "version": "4.4.4", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ @@ -40,11 +40,11 @@ "lib": "babel src --out-dir lib -s", "predocs": "rimraf docs/**", "docs": "jsdoc -c scripts/jsdoc.conf.json -R README.md", - "publish:patch": "npm version patch --no-git-tag-version && npm publish", - "publish:minor": "npm version minor --no-git-tag-version && npm publish", - "publish:major": "npm version major --no-git-tag-version && npm publish", + "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", - "postpublish": "node scripts/release.js" + "postpublish": "git push && git push --tags" }, "files": [ "dist/", @@ -72,7 +72,6 @@ "electron": "^1.4.15", "eslint": "^3.5.0", "floss": "^2.0.1", - "gh-pages": "^0.11.0", "jaguarjs-jsdoc": "^1.0.1", "js-md5": "^0.4.1", "jsdoc": "^3.4.2", diff --git a/scripts/jsdoc.conf.json b/scripts/jsdoc.conf.json index 553e61c..3f52180 100644 --- a/scripts/jsdoc.conf.json +++ b/scripts/jsdoc.conf.json @@ -43,7 +43,7 @@ }, "markdown" : { "parser" : "gfm", - "hardwrap" : true + "hardwrap" : false }, "opts": { "encoding" : "utf8", diff --git a/scripts/release.js b/scripts/release.js deleted file mode 100644 index 156620c..0000000 --- a/scripts/release.js +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env node -'use strict'; - -// Publish script to push releases of the bin files -// the normally are gitignored -const ghpages = require('gh-pages'); -const path = require('path'); -const packageInfo = require(path.join(__dirname, '..', 'package.json')); - -const options = { - src: [ - 'dist/**/*', - 'lib/**/*', - 'src/**/*', - 'scripts/*', - 'scripts/renders/*', - 'test/**/*', - '*.json', - '*.md', - 'LICENSE', - '.eslintrc', - '.editorconfig', - '.travis.yml', - '.babelrc', - ], - dotfiles: true, - branch: 'release', - message: packageInfo.version, - logger: console.log.bind(console), -}; - -ghpages.publish(process.cwd(), options, (err) => -{ - if (err) - { - console.log(err); - process.exit(1); - - return; - } - - process.exit(0); -}); diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index f16bcb1..220427a 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -21,7 +21,7 @@ const DIV_HOOK_ZINDEX = 2; /** - * The Accessibility manager reacreates the ability to tab and and have content read by screen + * The Accessibility manager recreates the ability to tab and and have content read by screen * readers. This is very important as it can possibly help people with disabilities access pixi * content. * diff --git a/src/accessibility/index.js b/src/accessibility/index.js index 568cfb1..0ae108f 100644 --- a/src/accessibility/index.js +++ b/src/accessibility/index.js @@ -1,4 +1,9 @@ /** + * This namespace contains a renderer plugin for interaction accessibility for end-users + * with physical impairments which require screen-renders, keyboard navigation, etc. + * + * Do not instantiate this plugin directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. * @namespace PIXI.accessibility */ export { default as accessibleTarget } from './accessibleTarget'; diff --git a/src/core/Application.js b/src/core/Application.js index 5340578..2fcc729 100644 --- a/src/core/Application.js +++ b/src/core/Application.js @@ -1,6 +1,8 @@ import { autoDetectRenderer } from './autoDetectRenderer'; import Container from './display/Container'; import { shared, Ticker } from './ticker'; +import settings from './settings'; +import { UPDATE_PRIORITY } from './const'; /** * Convenience class to create a new PIXI application. @@ -22,28 +24,52 @@ */ export default class Application { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experience unexplained flickering try setting this to true. - * @param {boolean} [useSharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedLoader=false] - `true` to use PIXI.loaders.shared, `false` to create new Loader. */ - constructor(width, height, options, noWebGL, useSharedTicker = false) + constructor(options, arg2, arg3, arg4, arg5) { + // Support for constructor(width, height, options, noWebGL, useSharedTicker) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + forceCanvas: !!arg4, + sharedTicker: !!arg5, + }, arg3); + } + + /** + * The default options, so we mixin functionality later. + * @member {object} + * @protected + */ + this._options = options = Object.assign({ + sharedTicker: false, + forceCanvas: false, + sharedLoader: false, + }, options); + /** * WebGL renderer if available, otherwise CanvasRenderer * @member {PIXI.WebGLRenderer|PIXI.CanvasRenderer} */ - this.renderer = autoDetectRenderer(width, height, options, noWebGL); + this.renderer = autoDetectRenderer(options); /** * The root display container that's rendered. @@ -63,7 +89,7 @@ * @member {PIXI.ticker.Ticker} * @default PIXI.ticker.shared */ - this.ticker = useSharedTicker ? shared : new Ticker(); + this.ticker = options.sharedTicker ? shared : new Ticker(); // Start the rendering this.start(); @@ -78,7 +104,7 @@ this._ticker = ticker; if (ticker) { - ticker.add(this.render, this); + ticker.add(this.render, this, UPDATE_PRIORITY.LOW); } } get ticker() // eslint-disable-line require-jsdoc @@ -136,13 +162,18 @@ */ destroy(removeView) { - this.stop(); + const oldTicker = this._ticker; + this.ticker = null; + oldTicker.destroy(); + this.stage.destroy(); this.stage = null; this.renderer.destroy(removeView); this.renderer = null; + + this._options = null; } } diff --git a/src/core/autoDetectRenderer.js b/src/core/autoDetectRenderer.js index 8960425..9ec0364 100644 --- a/src/core/autoDetectRenderer.js +++ b/src/core/autoDetectRenderer.js @@ -2,6 +2,7 @@ import CanvasRenderer from './renderers/canvas/CanvasRenderer'; import WebGLRenderer from './renderers/webgl/WebGLRenderer'; +// eslint-disable-next-line valid-jsdoc /** * This helper function will automatically detect which renderer you should be using. * WebGL is the preferred renderer as it is a lot faster. If webGL is not supported by @@ -9,24 +10,32 @@ * * @memberof PIXI * @function autoDetectRenderer - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @return {PIXI.WebGLRenderer|PIXI.CanvasRenderer} Returns WebGL renderer if available, otherwise CanvasRenderer */ -export function autoDetectRenderer(width = 800, height = 600, options, noWebGL) +export function autoDetectRenderer(options, arg1, arg2, arg3) { - if (!noWebGL && utils.isWebGLSupported()) + // Backward-compatible support for noWebGL option + let forceCanvas = options && options.forceCanvas; + + if (arg3 !== undefined) { - return new WebGLRenderer(width, height, options); + forceCanvas = arg3; } - return new CanvasRenderer(width, height, options); + if (!forceCanvas && utils.isWebGLSupported()) + { + return new WebGLRenderer(options, arg1, arg2); + } + + return new CanvasRenderer(options, arg1, arg2); } diff --git a/src/core/const.js b/src/core/const.js index b9e99ae..2c68dee 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -341,3 +341,27 @@ LINEAR_VERTICAL: 0, LINEAR_HORIZONTAL: 1, }; + +/** + * Represents the update priorities used by internal PIXI classes when registered with + * the {@link PIXI.ticker.Ticker} object. Higher priority items are updated first and lower + * priority items, such as render, should go later. + * + * @static + * @constant + * @name UPDATE_PRIORITY + * @memberof PIXI + * @type {object} + * @property {number} INTERACTION=50 Highest priority, used for {@link PIXI.interaction.InteractionManager} + * @property {number} HIGH=25 High priority updating, {@link PIXI.VideoBaseTexture} and {@link PIXI.extras.AnimatedSprite} + * @property {number} NORMAL=0 Default priority for ticker events, see {@link PIXI.ticker.Ticker#add}. + * @property {number} LOW=-25 Low priority used for {@link PIXI.Application} rendering. + * @property {number} UTILITY=-50 Lowest priority used for {@link PIXI.prepare.BasePrepare} utility. + */ +export const UPDATE_PRIORITY = { + INTERACTION: 50, + HIGH: 25, + NORMAL: 0, + LOW: -25, + UTILITY: -50, +}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 4e6c77d..decb42a 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -13,7 +13,6 @@ * * @class * @extends EventEmitter - * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ export default class DisplayObject extends EventEmitter @@ -113,6 +112,29 @@ * @private */ this._mask = null; + + /** + * If the object has been destroyed via destroy(). If true, it should not be used. + * + * @member {boolean} + * @private + * @readonly + */ + this._destroyed = false; + + /** + * Fired when this DisplayObject is added to a Container. + * + * @event PIXI.DisplayObject#added + * @param {PIXI.Container} container - The container added to. + */ + + /** + * Fired when this DisplayObject is removed from a Container. + * + * @event PIXI.DisplayObject#removed + * @param {PIXI.Container} container - The container removed from. + */ } /** @@ -403,6 +425,8 @@ this.interactive = false; this.interactiveChildren = false; + + this._destroyed = true; } /** diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index aafdfe2..f787c4a 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -792,8 +792,8 @@ sprite.worldAlpha = this.worldAlpha * sprite.alpha; sprite.blendMode = this.blendMode; - sprite.texture._frame.width = rect.width; - sprite.texture._frame.height = rect.height; + sprite._texture._frame.width = rect.width; + sprite._texture._frame.height = rect.height; sprite.transform.worldTransform = this.transform.worldTransform; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index a24c775..b9f192d 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -19,11 +19,12 @@ */ export default class SystemRenderer extends EventEmitter { + // eslint-disable-next-line valid-jsdoc /** * @param {string} system - The name of the system this renderer is for. - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -37,27 +38,31 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(system, screenWidth, screenHeight, options) + constructor(system, options, arg2, arg3) { super(); sayHello(system); - // prepare options - if (options) + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') { - for (const i in settings.RENDER_OPTIONS) - { - if (typeof options[i] === 'undefined') - { - options[i] = settings.RENDER_OPTIONS[i]; - } - } + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); } - else - { - options = settings.RENDER_OPTIONS; - } + + // Add the default render options + options = Object.assign({}, settings.RENDER_OPTIONS, options); + + /** + * The supplied constructor options. + * + * @member {Object} + * @readOnly + */ + this.options = options; /** * The type of the renderer. @@ -75,7 +80,7 @@ * * @member {PIXI.Rectangle} */ - this.screen = new Rectangle(0, 0, screenWidth || 800, screenHeight || 600); + this.screen = new Rectangle(0, 0, options.width, options.height); /** * The canvas element that everything is drawn to @@ -279,6 +284,8 @@ this.blendModes = null; + this.options = null; + this.preserveDrawingBuffer = false; this.clearBeforeRender = false; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index e84e49c..691caf0 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -17,10 +17,11 @@ */ export default class CanvasRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -34,9 +35,9 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('Canvas', screenWidth, screenHeight, options); + super('Canvas', options, arg2, arg3); this.type = RENDERER_TYPE.CANVAS; @@ -96,7 +97,19 @@ this.context = null; this.renderingToScreen = false; - this.resize(screenWidth, screenHeight); + this.resize(this.options.width, this.options.height); + + /** + * Fired after rendering finishes. + * + * @event PIXI.CanvasRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.CanvasRenderer#prerender + */ } /** @@ -299,4 +312,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.CanvasRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.CanvasExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.CanvasPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.CanvasRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index e1cf7cd..f6fe123 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -31,11 +31,12 @@ */ export default class WebGLRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** * - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -55,9 +56,9 @@ * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('WebGL', screenWidth, screenHeight, options); + super('WebGL', options, arg2, arg3); /** * The type of this renderer as a standardised const @@ -89,12 +90,6 @@ }; - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ this._backgroundColorRgba[3] = this.transparent ? 0 : 1; this.globalUniforms = new UniformGroup({ @@ -117,6 +112,13 @@ this.initPlugins(); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ if(options.context) { this.context.initFromContext(options.context); @@ -169,6 +171,25 @@ } return this; + + /** + * Fired after rendering finishes. + * + * @event PIXI.WebGLRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.WebGLRenderer#prerender + */ + + /** + * Fired when the WebGL context is set. + * + * @event PIXI.WebGLRenderer#context + * @param {WebGLRenderingContext} gl - WebGL context. + */ } /** @@ -295,4 +316,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.WebGLRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.WebGLRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(WebGLRenderer); diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index 4eb6128..fa3d883 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -47,6 +47,14 @@ * @member {boolean} */ this.enabled = true; + + /** + * If enabled, pixi will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; } /** diff --git a/src/core/renderers/webgl/systems/ContextSystem.js b/src/core/renderers/webgl/systems/ContextSystem.js index ff958c7..8df85ea 100644 --- a/src/core/renderers/webgl/systems/ContextSystem.js +++ b/src/core/renderers/webgl/systems/ContextSystem.js @@ -75,7 +75,8 @@ */ createContext(canvas, options) { - var gl = canvas.getContext('webgl', options) || + var gl = //canvas.getContext('webgl2', options) || + canvas.getContext('webgl', options) || canvas.getContext('experimental-webgl', options); if (!gl) diff --git a/src/core/renderers/webgl/systems/FilterSystem.js b/src/core/renderers/webgl/systems/FilterSystem.js index f47335d..37e28d2 100644 --- a/src/core/renderers/webgl/systems/FilterSystem.js +++ b/src/core/renderers/webgl/systems/FilterSystem.js @@ -111,7 +111,7 @@ // TODO we should fit the rect around the transform.. } - else + else if (filters[0].autoFit) { sourceFrame.fit(filterData.stack[0].destinationFrame); } diff --git a/src/core/renderers/webgl/systems/shader/shader/mapType.js b/src/core/renderers/webgl/systems/shader/shader/mapType.js index 581dace..917538a 100644 --- a/src/core/renderers/webgl/systems/shader/shader/mapType.js +++ b/src/core/renderers/webgl/systems/shader/shader/mapType.js @@ -15,7 +15,7 @@ } } - return GL_TABLE[type]; + return GL_TABLE[type]; }; var GL_TABLE = null; diff --git a/.travis.yml b/.travis.yml index 3df8b22..14de334 100644 --- a/.travis.yml +++ b/.travis.yml @@ -110,3 +110,43 @@ on: all_branches: true condition: $TRAVIS_TAG + # Deploy config for latest release + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: dist + upload-dir: "release" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: docs + upload-dir: "release/docs" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: coverage + upload-dir: "release/coverage" + on: + all_branches: true + condition: $TRAVIS_TAG \ No newline at end of file diff --git a/README.md b/README.md index d26fadd..3b85f85 100644 --- a/README.md +++ b/README.md @@ -36,22 +36,14 @@ - Wiki: Other misc tutorials and resources are [on the Wiki](https://github.com/pixijs/pixi.js/wiki/Resources). ### Community ### -- Forums: Check out the [forum] (http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow] (http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. +- Forums: Check out the [forum](http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow](http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. - Inspiration: Check out the [gallery](http://www.pixijs.com/gallery) to see some of the amazing things people have created! - Chat: You can join us on [Gitter](https://gitter.im/pixijs/pixi.js) To chat about Pixi. We also now have a Slack channel. If you would like to join it please Send me an email (mat@goodboydigital.com) and I will invite you in. ### Setup ### -It's easy to get started with Pixi.js! Simply grab the pre-built versions from here: - -Release Branch - Nice and stable Pixi.js -- Unminified: [http://pixijs.download/release/pixi.js] -- Minified: [http://pixijs.download/release/pixi.min.js] - -Develop Branch - The bleeding edge version of Pixi.js -- Unminified: [http://pixijs.download/dev/pixi.js] -- Minified: [http://pixijs.download/dev/pixi.min.js] +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. @@ -70,10 +62,10 @@ #### CDN Install (via cdnjs) ```html - + ``` -_Note: `4.2.2` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ +_Note: `4.4.3` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ ### Demos ### @@ -130,7 +122,7 @@ // and the root stage PIXI.Container. var app = new PIXI.Application(); -// The application will create a canvas element for you that you +// The application will create a canvas element for you that you // can then insert into the DOM. document.body.appendChild(app.view); @@ -150,7 +142,7 @@ // Add the bunny to the scene we are building. app.stage.addChild(bunny); - + // Listen for frame updates app.ticker.add(function() { // each frame we spin the bunny around a bit diff --git a/bower.json b/bower.json deleted file mode 100644 index 41b668c..0000000 --- a/bower.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "pixi.js", - "main": "dist/pixi.min.js", - "ignore": [ - "**/.*", - "docs", - "node_modules", - "bower_components", - "test", - "scripts", - "bower.json", - "inch.json" - ], - "dependencies": { - }, - "devDependencies": { - } -} diff --git a/package.json b/package.json index d35cbb2..308469f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.3.5", + "version": "4.4.4", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ @@ -40,11 +40,11 @@ "lib": "babel src --out-dir lib -s", "predocs": "rimraf docs/**", "docs": "jsdoc -c scripts/jsdoc.conf.json -R README.md", - "publish:patch": "npm version patch --no-git-tag-version && npm publish", - "publish:minor": "npm version minor --no-git-tag-version && npm publish", - "publish:major": "npm version major --no-git-tag-version && npm publish", + "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", - "postpublish": "node scripts/release.js" + "postpublish": "git push && git push --tags" }, "files": [ "dist/", @@ -72,7 +72,6 @@ "electron": "^1.4.15", "eslint": "^3.5.0", "floss": "^2.0.1", - "gh-pages": "^0.11.0", "jaguarjs-jsdoc": "^1.0.1", "js-md5": "^0.4.1", "jsdoc": "^3.4.2", diff --git a/scripts/jsdoc.conf.json b/scripts/jsdoc.conf.json index 553e61c..3f52180 100644 --- a/scripts/jsdoc.conf.json +++ b/scripts/jsdoc.conf.json @@ -43,7 +43,7 @@ }, "markdown" : { "parser" : "gfm", - "hardwrap" : true + "hardwrap" : false }, "opts": { "encoding" : "utf8", diff --git a/scripts/release.js b/scripts/release.js deleted file mode 100644 index 156620c..0000000 --- a/scripts/release.js +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env node -'use strict'; - -// Publish script to push releases of the bin files -// the normally are gitignored -const ghpages = require('gh-pages'); -const path = require('path'); -const packageInfo = require(path.join(__dirname, '..', 'package.json')); - -const options = { - src: [ - 'dist/**/*', - 'lib/**/*', - 'src/**/*', - 'scripts/*', - 'scripts/renders/*', - 'test/**/*', - '*.json', - '*.md', - 'LICENSE', - '.eslintrc', - '.editorconfig', - '.travis.yml', - '.babelrc', - ], - dotfiles: true, - branch: 'release', - message: packageInfo.version, - logger: console.log.bind(console), -}; - -ghpages.publish(process.cwd(), options, (err) => -{ - if (err) - { - console.log(err); - process.exit(1); - - return; - } - - process.exit(0); -}); diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index f16bcb1..220427a 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -21,7 +21,7 @@ const DIV_HOOK_ZINDEX = 2; /** - * The Accessibility manager reacreates the ability to tab and and have content read by screen + * The Accessibility manager recreates the ability to tab and and have content read by screen * readers. This is very important as it can possibly help people with disabilities access pixi * content. * diff --git a/src/accessibility/index.js b/src/accessibility/index.js index 568cfb1..0ae108f 100644 --- a/src/accessibility/index.js +++ b/src/accessibility/index.js @@ -1,4 +1,9 @@ /** + * This namespace contains a renderer plugin for interaction accessibility for end-users + * with physical impairments which require screen-renders, keyboard navigation, etc. + * + * Do not instantiate this plugin directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. * @namespace PIXI.accessibility */ export { default as accessibleTarget } from './accessibleTarget'; diff --git a/src/core/Application.js b/src/core/Application.js index 5340578..2fcc729 100644 --- a/src/core/Application.js +++ b/src/core/Application.js @@ -1,6 +1,8 @@ import { autoDetectRenderer } from './autoDetectRenderer'; import Container from './display/Container'; import { shared, Ticker } from './ticker'; +import settings from './settings'; +import { UPDATE_PRIORITY } from './const'; /** * Convenience class to create a new PIXI application. @@ -22,28 +24,52 @@ */ export default class Application { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experience unexplained flickering try setting this to true. - * @param {boolean} [useSharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedLoader=false] - `true` to use PIXI.loaders.shared, `false` to create new Loader. */ - constructor(width, height, options, noWebGL, useSharedTicker = false) + constructor(options, arg2, arg3, arg4, arg5) { + // Support for constructor(width, height, options, noWebGL, useSharedTicker) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + forceCanvas: !!arg4, + sharedTicker: !!arg5, + }, arg3); + } + + /** + * The default options, so we mixin functionality later. + * @member {object} + * @protected + */ + this._options = options = Object.assign({ + sharedTicker: false, + forceCanvas: false, + sharedLoader: false, + }, options); + /** * WebGL renderer if available, otherwise CanvasRenderer * @member {PIXI.WebGLRenderer|PIXI.CanvasRenderer} */ - this.renderer = autoDetectRenderer(width, height, options, noWebGL); + this.renderer = autoDetectRenderer(options); /** * The root display container that's rendered. @@ -63,7 +89,7 @@ * @member {PIXI.ticker.Ticker} * @default PIXI.ticker.shared */ - this.ticker = useSharedTicker ? shared : new Ticker(); + this.ticker = options.sharedTicker ? shared : new Ticker(); // Start the rendering this.start(); @@ -78,7 +104,7 @@ this._ticker = ticker; if (ticker) { - ticker.add(this.render, this); + ticker.add(this.render, this, UPDATE_PRIORITY.LOW); } } get ticker() // eslint-disable-line require-jsdoc @@ -136,13 +162,18 @@ */ destroy(removeView) { - this.stop(); + const oldTicker = this._ticker; + this.ticker = null; + oldTicker.destroy(); + this.stage.destroy(); this.stage = null; this.renderer.destroy(removeView); this.renderer = null; + + this._options = null; } } diff --git a/src/core/autoDetectRenderer.js b/src/core/autoDetectRenderer.js index 8960425..9ec0364 100644 --- a/src/core/autoDetectRenderer.js +++ b/src/core/autoDetectRenderer.js @@ -2,6 +2,7 @@ import CanvasRenderer from './renderers/canvas/CanvasRenderer'; import WebGLRenderer from './renderers/webgl/WebGLRenderer'; +// eslint-disable-next-line valid-jsdoc /** * This helper function will automatically detect which renderer you should be using. * WebGL is the preferred renderer as it is a lot faster. If webGL is not supported by @@ -9,24 +10,32 @@ * * @memberof PIXI * @function autoDetectRenderer - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @return {PIXI.WebGLRenderer|PIXI.CanvasRenderer} Returns WebGL renderer if available, otherwise CanvasRenderer */ -export function autoDetectRenderer(width = 800, height = 600, options, noWebGL) +export function autoDetectRenderer(options, arg1, arg2, arg3) { - if (!noWebGL && utils.isWebGLSupported()) + // Backward-compatible support for noWebGL option + let forceCanvas = options && options.forceCanvas; + + if (arg3 !== undefined) { - return new WebGLRenderer(width, height, options); + forceCanvas = arg3; } - return new CanvasRenderer(width, height, options); + if (!forceCanvas && utils.isWebGLSupported()) + { + return new WebGLRenderer(options, arg1, arg2); + } + + return new CanvasRenderer(options, arg1, arg2); } diff --git a/src/core/const.js b/src/core/const.js index b9e99ae..2c68dee 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -341,3 +341,27 @@ LINEAR_VERTICAL: 0, LINEAR_HORIZONTAL: 1, }; + +/** + * Represents the update priorities used by internal PIXI classes when registered with + * the {@link PIXI.ticker.Ticker} object. Higher priority items are updated first and lower + * priority items, such as render, should go later. + * + * @static + * @constant + * @name UPDATE_PRIORITY + * @memberof PIXI + * @type {object} + * @property {number} INTERACTION=50 Highest priority, used for {@link PIXI.interaction.InteractionManager} + * @property {number} HIGH=25 High priority updating, {@link PIXI.VideoBaseTexture} and {@link PIXI.extras.AnimatedSprite} + * @property {number} NORMAL=0 Default priority for ticker events, see {@link PIXI.ticker.Ticker#add}. + * @property {number} LOW=-25 Low priority used for {@link PIXI.Application} rendering. + * @property {number} UTILITY=-50 Lowest priority used for {@link PIXI.prepare.BasePrepare} utility. + */ +export const UPDATE_PRIORITY = { + INTERACTION: 50, + HIGH: 25, + NORMAL: 0, + LOW: -25, + UTILITY: -50, +}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 4e6c77d..decb42a 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -13,7 +13,6 @@ * * @class * @extends EventEmitter - * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ export default class DisplayObject extends EventEmitter @@ -113,6 +112,29 @@ * @private */ this._mask = null; + + /** + * If the object has been destroyed via destroy(). If true, it should not be used. + * + * @member {boolean} + * @private + * @readonly + */ + this._destroyed = false; + + /** + * Fired when this DisplayObject is added to a Container. + * + * @event PIXI.DisplayObject#added + * @param {PIXI.Container} container - The container added to. + */ + + /** + * Fired when this DisplayObject is removed from a Container. + * + * @event PIXI.DisplayObject#removed + * @param {PIXI.Container} container - The container removed from. + */ } /** @@ -403,6 +425,8 @@ this.interactive = false; this.interactiveChildren = false; + + this._destroyed = true; } /** diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index aafdfe2..f787c4a 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -792,8 +792,8 @@ sprite.worldAlpha = this.worldAlpha * sprite.alpha; sprite.blendMode = this.blendMode; - sprite.texture._frame.width = rect.width; - sprite.texture._frame.height = rect.height; + sprite._texture._frame.width = rect.width; + sprite._texture._frame.height = rect.height; sprite.transform.worldTransform = this.transform.worldTransform; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index a24c775..b9f192d 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -19,11 +19,12 @@ */ export default class SystemRenderer extends EventEmitter { + // eslint-disable-next-line valid-jsdoc /** * @param {string} system - The name of the system this renderer is for. - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -37,27 +38,31 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(system, screenWidth, screenHeight, options) + constructor(system, options, arg2, arg3) { super(); sayHello(system); - // prepare options - if (options) + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') { - for (const i in settings.RENDER_OPTIONS) - { - if (typeof options[i] === 'undefined') - { - options[i] = settings.RENDER_OPTIONS[i]; - } - } + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); } - else - { - options = settings.RENDER_OPTIONS; - } + + // Add the default render options + options = Object.assign({}, settings.RENDER_OPTIONS, options); + + /** + * The supplied constructor options. + * + * @member {Object} + * @readOnly + */ + this.options = options; /** * The type of the renderer. @@ -75,7 +80,7 @@ * * @member {PIXI.Rectangle} */ - this.screen = new Rectangle(0, 0, screenWidth || 800, screenHeight || 600); + this.screen = new Rectangle(0, 0, options.width, options.height); /** * The canvas element that everything is drawn to @@ -279,6 +284,8 @@ this.blendModes = null; + this.options = null; + this.preserveDrawingBuffer = false; this.clearBeforeRender = false; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index e84e49c..691caf0 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -17,10 +17,11 @@ */ export default class CanvasRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -34,9 +35,9 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('Canvas', screenWidth, screenHeight, options); + super('Canvas', options, arg2, arg3); this.type = RENDERER_TYPE.CANVAS; @@ -96,7 +97,19 @@ this.context = null; this.renderingToScreen = false; - this.resize(screenWidth, screenHeight); + this.resize(this.options.width, this.options.height); + + /** + * Fired after rendering finishes. + * + * @event PIXI.CanvasRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.CanvasRenderer#prerender + */ } /** @@ -299,4 +312,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.CanvasRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.CanvasExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.CanvasPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.CanvasRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index e1cf7cd..f6fe123 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -31,11 +31,12 @@ */ export default class WebGLRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** * - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -55,9 +56,9 @@ * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('WebGL', screenWidth, screenHeight, options); + super('WebGL', options, arg2, arg3); /** * The type of this renderer as a standardised const @@ -89,12 +90,6 @@ }; - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ this._backgroundColorRgba[3] = this.transparent ? 0 : 1; this.globalUniforms = new UniformGroup({ @@ -117,6 +112,13 @@ this.initPlugins(); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ if(options.context) { this.context.initFromContext(options.context); @@ -169,6 +171,25 @@ } return this; + + /** + * Fired after rendering finishes. + * + * @event PIXI.WebGLRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.WebGLRenderer#prerender + */ + + /** + * Fired when the WebGL context is set. + * + * @event PIXI.WebGLRenderer#context + * @param {WebGLRenderingContext} gl - WebGL context. + */ } /** @@ -295,4 +316,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.WebGLRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.WebGLRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(WebGLRenderer); diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index 4eb6128..fa3d883 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -47,6 +47,14 @@ * @member {boolean} */ this.enabled = true; + + /** + * If enabled, pixi will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; } /** diff --git a/src/core/renderers/webgl/systems/ContextSystem.js b/src/core/renderers/webgl/systems/ContextSystem.js index ff958c7..8df85ea 100644 --- a/src/core/renderers/webgl/systems/ContextSystem.js +++ b/src/core/renderers/webgl/systems/ContextSystem.js @@ -75,7 +75,8 @@ */ createContext(canvas, options) { - var gl = canvas.getContext('webgl', options) || + var gl = //canvas.getContext('webgl2', options) || + canvas.getContext('webgl', options) || canvas.getContext('experimental-webgl', options); if (!gl) diff --git a/src/core/renderers/webgl/systems/FilterSystem.js b/src/core/renderers/webgl/systems/FilterSystem.js index f47335d..37e28d2 100644 --- a/src/core/renderers/webgl/systems/FilterSystem.js +++ b/src/core/renderers/webgl/systems/FilterSystem.js @@ -111,7 +111,7 @@ // TODO we should fit the rect around the transform.. } - else + else if (filters[0].autoFit) { sourceFrame.fit(filterData.stack[0].destinationFrame); } diff --git a/src/core/renderers/webgl/systems/shader/shader/mapType.js b/src/core/renderers/webgl/systems/shader/shader/mapType.js index 581dace..917538a 100644 --- a/src/core/renderers/webgl/systems/shader/shader/mapType.js +++ b/src/core/renderers/webgl/systems/shader/shader/mapType.js @@ -15,7 +15,7 @@ } } - return GL_TABLE[type]; + return GL_TABLE[type]; }; var GL_TABLE = null; diff --git a/src/core/settings.js b/src/core/settings.js index 18c5877..38ef45c 100644 --- a/src/core/settings.js +++ b/src/core/settings.js @@ -2,6 +2,15 @@ import canUploadSameBuffer from './utils/canUploadSameBuffer'; /** + * User's customizable globals for overriding the default PIXI settings, such + * as a renderer's default resolution, framerate, float percision, etc. + * @example + * // Use the native window resolution as the default resolution + * // will support high-density displays when rendering + * PIXI.settings.RESOLUTION = window.devicePixelRatio. + * + * // Disable interpolation when scaling, will make texture be pixelated + * PIXI.settings.SCALE_MODE = PIXI.SCALE_MODES.NEAREST; * @namespace PIXI.settings */ export default { @@ -101,6 +110,9 @@ * @property {boolean} clearBeforeRender=true * @property {boolean} preserveDrawingBuffer=false * @property {boolean} roundPixels=false + * @property {number} width=800 + * @property {number} height=600 + * @property {boolean} legacy=false */ RENDER_OPTIONS: { view: null, @@ -112,6 +124,9 @@ clearBeforeRender: true, preserveDrawingBuffer: false, roundPixels: false, + width: 800, + height: 600, + legacy: false, }, /** diff --git a/.travis.yml b/.travis.yml index 3df8b22..14de334 100644 --- a/.travis.yml +++ b/.travis.yml @@ -110,3 +110,43 @@ on: all_branches: true condition: $TRAVIS_TAG + # Deploy config for latest release + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: dist + upload-dir: "release" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: docs + upload-dir: "release/docs" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: coverage + upload-dir: "release/coverage" + on: + all_branches: true + condition: $TRAVIS_TAG \ No newline at end of file diff --git a/README.md b/README.md index d26fadd..3b85f85 100644 --- a/README.md +++ b/README.md @@ -36,22 +36,14 @@ - Wiki: Other misc tutorials and resources are [on the Wiki](https://github.com/pixijs/pixi.js/wiki/Resources). ### Community ### -- Forums: Check out the [forum] (http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow] (http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. +- Forums: Check out the [forum](http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow](http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. - Inspiration: Check out the [gallery](http://www.pixijs.com/gallery) to see some of the amazing things people have created! - Chat: You can join us on [Gitter](https://gitter.im/pixijs/pixi.js) To chat about Pixi. We also now have a Slack channel. If you would like to join it please Send me an email (mat@goodboydigital.com) and I will invite you in. ### Setup ### -It's easy to get started with Pixi.js! Simply grab the pre-built versions from here: - -Release Branch - Nice and stable Pixi.js -- Unminified: [http://pixijs.download/release/pixi.js] -- Minified: [http://pixijs.download/release/pixi.min.js] - -Develop Branch - The bleeding edge version of Pixi.js -- Unminified: [http://pixijs.download/dev/pixi.js] -- Minified: [http://pixijs.download/dev/pixi.min.js] +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. @@ -70,10 +62,10 @@ #### CDN Install (via cdnjs) ```html - + ``` -_Note: `4.2.2` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ +_Note: `4.4.3` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ ### Demos ### @@ -130,7 +122,7 @@ // and the root stage PIXI.Container. var app = new PIXI.Application(); -// The application will create a canvas element for you that you +// The application will create a canvas element for you that you // can then insert into the DOM. document.body.appendChild(app.view); @@ -150,7 +142,7 @@ // Add the bunny to the scene we are building. app.stage.addChild(bunny); - + // Listen for frame updates app.ticker.add(function() { // each frame we spin the bunny around a bit diff --git a/bower.json b/bower.json deleted file mode 100644 index 41b668c..0000000 --- a/bower.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "pixi.js", - "main": "dist/pixi.min.js", - "ignore": [ - "**/.*", - "docs", - "node_modules", - "bower_components", - "test", - "scripts", - "bower.json", - "inch.json" - ], - "dependencies": { - }, - "devDependencies": { - } -} diff --git a/package.json b/package.json index d35cbb2..308469f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.3.5", + "version": "4.4.4", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ @@ -40,11 +40,11 @@ "lib": "babel src --out-dir lib -s", "predocs": "rimraf docs/**", "docs": "jsdoc -c scripts/jsdoc.conf.json -R README.md", - "publish:patch": "npm version patch --no-git-tag-version && npm publish", - "publish:minor": "npm version minor --no-git-tag-version && npm publish", - "publish:major": "npm version major --no-git-tag-version && npm publish", + "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", - "postpublish": "node scripts/release.js" + "postpublish": "git push && git push --tags" }, "files": [ "dist/", @@ -72,7 +72,6 @@ "electron": "^1.4.15", "eslint": "^3.5.0", "floss": "^2.0.1", - "gh-pages": "^0.11.0", "jaguarjs-jsdoc": "^1.0.1", "js-md5": "^0.4.1", "jsdoc": "^3.4.2", diff --git a/scripts/jsdoc.conf.json b/scripts/jsdoc.conf.json index 553e61c..3f52180 100644 --- a/scripts/jsdoc.conf.json +++ b/scripts/jsdoc.conf.json @@ -43,7 +43,7 @@ }, "markdown" : { "parser" : "gfm", - "hardwrap" : true + "hardwrap" : false }, "opts": { "encoding" : "utf8", diff --git a/scripts/release.js b/scripts/release.js deleted file mode 100644 index 156620c..0000000 --- a/scripts/release.js +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env node -'use strict'; - -// Publish script to push releases of the bin files -// the normally are gitignored -const ghpages = require('gh-pages'); -const path = require('path'); -const packageInfo = require(path.join(__dirname, '..', 'package.json')); - -const options = { - src: [ - 'dist/**/*', - 'lib/**/*', - 'src/**/*', - 'scripts/*', - 'scripts/renders/*', - 'test/**/*', - '*.json', - '*.md', - 'LICENSE', - '.eslintrc', - '.editorconfig', - '.travis.yml', - '.babelrc', - ], - dotfiles: true, - branch: 'release', - message: packageInfo.version, - logger: console.log.bind(console), -}; - -ghpages.publish(process.cwd(), options, (err) => -{ - if (err) - { - console.log(err); - process.exit(1); - - return; - } - - process.exit(0); -}); diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index f16bcb1..220427a 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -21,7 +21,7 @@ const DIV_HOOK_ZINDEX = 2; /** - * The Accessibility manager reacreates the ability to tab and and have content read by screen + * The Accessibility manager recreates the ability to tab and and have content read by screen * readers. This is very important as it can possibly help people with disabilities access pixi * content. * diff --git a/src/accessibility/index.js b/src/accessibility/index.js index 568cfb1..0ae108f 100644 --- a/src/accessibility/index.js +++ b/src/accessibility/index.js @@ -1,4 +1,9 @@ /** + * This namespace contains a renderer plugin for interaction accessibility for end-users + * with physical impairments which require screen-renders, keyboard navigation, etc. + * + * Do not instantiate this plugin directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. * @namespace PIXI.accessibility */ export { default as accessibleTarget } from './accessibleTarget'; diff --git a/src/core/Application.js b/src/core/Application.js index 5340578..2fcc729 100644 --- a/src/core/Application.js +++ b/src/core/Application.js @@ -1,6 +1,8 @@ import { autoDetectRenderer } from './autoDetectRenderer'; import Container from './display/Container'; import { shared, Ticker } from './ticker'; +import settings from './settings'; +import { UPDATE_PRIORITY } from './const'; /** * Convenience class to create a new PIXI application. @@ -22,28 +24,52 @@ */ export default class Application { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experience unexplained flickering try setting this to true. - * @param {boolean} [useSharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedLoader=false] - `true` to use PIXI.loaders.shared, `false` to create new Loader. */ - constructor(width, height, options, noWebGL, useSharedTicker = false) + constructor(options, arg2, arg3, arg4, arg5) { + // Support for constructor(width, height, options, noWebGL, useSharedTicker) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + forceCanvas: !!arg4, + sharedTicker: !!arg5, + }, arg3); + } + + /** + * The default options, so we mixin functionality later. + * @member {object} + * @protected + */ + this._options = options = Object.assign({ + sharedTicker: false, + forceCanvas: false, + sharedLoader: false, + }, options); + /** * WebGL renderer if available, otherwise CanvasRenderer * @member {PIXI.WebGLRenderer|PIXI.CanvasRenderer} */ - this.renderer = autoDetectRenderer(width, height, options, noWebGL); + this.renderer = autoDetectRenderer(options); /** * The root display container that's rendered. @@ -63,7 +89,7 @@ * @member {PIXI.ticker.Ticker} * @default PIXI.ticker.shared */ - this.ticker = useSharedTicker ? shared : new Ticker(); + this.ticker = options.sharedTicker ? shared : new Ticker(); // Start the rendering this.start(); @@ -78,7 +104,7 @@ this._ticker = ticker; if (ticker) { - ticker.add(this.render, this); + ticker.add(this.render, this, UPDATE_PRIORITY.LOW); } } get ticker() // eslint-disable-line require-jsdoc @@ -136,13 +162,18 @@ */ destroy(removeView) { - this.stop(); + const oldTicker = this._ticker; + this.ticker = null; + oldTicker.destroy(); + this.stage.destroy(); this.stage = null; this.renderer.destroy(removeView); this.renderer = null; + + this._options = null; } } diff --git a/src/core/autoDetectRenderer.js b/src/core/autoDetectRenderer.js index 8960425..9ec0364 100644 --- a/src/core/autoDetectRenderer.js +++ b/src/core/autoDetectRenderer.js @@ -2,6 +2,7 @@ import CanvasRenderer from './renderers/canvas/CanvasRenderer'; import WebGLRenderer from './renderers/webgl/WebGLRenderer'; +// eslint-disable-next-line valid-jsdoc /** * This helper function will automatically detect which renderer you should be using. * WebGL is the preferred renderer as it is a lot faster. If webGL is not supported by @@ -9,24 +10,32 @@ * * @memberof PIXI * @function autoDetectRenderer - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @return {PIXI.WebGLRenderer|PIXI.CanvasRenderer} Returns WebGL renderer if available, otherwise CanvasRenderer */ -export function autoDetectRenderer(width = 800, height = 600, options, noWebGL) +export function autoDetectRenderer(options, arg1, arg2, arg3) { - if (!noWebGL && utils.isWebGLSupported()) + // Backward-compatible support for noWebGL option + let forceCanvas = options && options.forceCanvas; + + if (arg3 !== undefined) { - return new WebGLRenderer(width, height, options); + forceCanvas = arg3; } - return new CanvasRenderer(width, height, options); + if (!forceCanvas && utils.isWebGLSupported()) + { + return new WebGLRenderer(options, arg1, arg2); + } + + return new CanvasRenderer(options, arg1, arg2); } diff --git a/src/core/const.js b/src/core/const.js index b9e99ae..2c68dee 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -341,3 +341,27 @@ LINEAR_VERTICAL: 0, LINEAR_HORIZONTAL: 1, }; + +/** + * Represents the update priorities used by internal PIXI classes when registered with + * the {@link PIXI.ticker.Ticker} object. Higher priority items are updated first and lower + * priority items, such as render, should go later. + * + * @static + * @constant + * @name UPDATE_PRIORITY + * @memberof PIXI + * @type {object} + * @property {number} INTERACTION=50 Highest priority, used for {@link PIXI.interaction.InteractionManager} + * @property {number} HIGH=25 High priority updating, {@link PIXI.VideoBaseTexture} and {@link PIXI.extras.AnimatedSprite} + * @property {number} NORMAL=0 Default priority for ticker events, see {@link PIXI.ticker.Ticker#add}. + * @property {number} LOW=-25 Low priority used for {@link PIXI.Application} rendering. + * @property {number} UTILITY=-50 Lowest priority used for {@link PIXI.prepare.BasePrepare} utility. + */ +export const UPDATE_PRIORITY = { + INTERACTION: 50, + HIGH: 25, + NORMAL: 0, + LOW: -25, + UTILITY: -50, +}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 4e6c77d..decb42a 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -13,7 +13,6 @@ * * @class * @extends EventEmitter - * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ export default class DisplayObject extends EventEmitter @@ -113,6 +112,29 @@ * @private */ this._mask = null; + + /** + * If the object has been destroyed via destroy(). If true, it should not be used. + * + * @member {boolean} + * @private + * @readonly + */ + this._destroyed = false; + + /** + * Fired when this DisplayObject is added to a Container. + * + * @event PIXI.DisplayObject#added + * @param {PIXI.Container} container - The container added to. + */ + + /** + * Fired when this DisplayObject is removed from a Container. + * + * @event PIXI.DisplayObject#removed + * @param {PIXI.Container} container - The container removed from. + */ } /** @@ -403,6 +425,8 @@ this.interactive = false; this.interactiveChildren = false; + + this._destroyed = true; } /** diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index aafdfe2..f787c4a 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -792,8 +792,8 @@ sprite.worldAlpha = this.worldAlpha * sprite.alpha; sprite.blendMode = this.blendMode; - sprite.texture._frame.width = rect.width; - sprite.texture._frame.height = rect.height; + sprite._texture._frame.width = rect.width; + sprite._texture._frame.height = rect.height; sprite.transform.worldTransform = this.transform.worldTransform; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index a24c775..b9f192d 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -19,11 +19,12 @@ */ export default class SystemRenderer extends EventEmitter { + // eslint-disable-next-line valid-jsdoc /** * @param {string} system - The name of the system this renderer is for. - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -37,27 +38,31 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(system, screenWidth, screenHeight, options) + constructor(system, options, arg2, arg3) { super(); sayHello(system); - // prepare options - if (options) + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') { - for (const i in settings.RENDER_OPTIONS) - { - if (typeof options[i] === 'undefined') - { - options[i] = settings.RENDER_OPTIONS[i]; - } - } + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); } - else - { - options = settings.RENDER_OPTIONS; - } + + // Add the default render options + options = Object.assign({}, settings.RENDER_OPTIONS, options); + + /** + * The supplied constructor options. + * + * @member {Object} + * @readOnly + */ + this.options = options; /** * The type of the renderer. @@ -75,7 +80,7 @@ * * @member {PIXI.Rectangle} */ - this.screen = new Rectangle(0, 0, screenWidth || 800, screenHeight || 600); + this.screen = new Rectangle(0, 0, options.width, options.height); /** * The canvas element that everything is drawn to @@ -279,6 +284,8 @@ this.blendModes = null; + this.options = null; + this.preserveDrawingBuffer = false; this.clearBeforeRender = false; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index e84e49c..691caf0 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -17,10 +17,11 @@ */ export default class CanvasRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -34,9 +35,9 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('Canvas', screenWidth, screenHeight, options); + super('Canvas', options, arg2, arg3); this.type = RENDERER_TYPE.CANVAS; @@ -96,7 +97,19 @@ this.context = null; this.renderingToScreen = false; - this.resize(screenWidth, screenHeight); + this.resize(this.options.width, this.options.height); + + /** + * Fired after rendering finishes. + * + * @event PIXI.CanvasRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.CanvasRenderer#prerender + */ } /** @@ -299,4 +312,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.CanvasRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.CanvasExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.CanvasPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.CanvasRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index e1cf7cd..f6fe123 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -31,11 +31,12 @@ */ export default class WebGLRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** * - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -55,9 +56,9 @@ * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('WebGL', screenWidth, screenHeight, options); + super('WebGL', options, arg2, arg3); /** * The type of this renderer as a standardised const @@ -89,12 +90,6 @@ }; - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ this._backgroundColorRgba[3] = this.transparent ? 0 : 1; this.globalUniforms = new UniformGroup({ @@ -117,6 +112,13 @@ this.initPlugins(); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ if(options.context) { this.context.initFromContext(options.context); @@ -169,6 +171,25 @@ } return this; + + /** + * Fired after rendering finishes. + * + * @event PIXI.WebGLRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.WebGLRenderer#prerender + */ + + /** + * Fired when the WebGL context is set. + * + * @event PIXI.WebGLRenderer#context + * @param {WebGLRenderingContext} gl - WebGL context. + */ } /** @@ -295,4 +316,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.WebGLRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.WebGLRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(WebGLRenderer); diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index 4eb6128..fa3d883 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -47,6 +47,14 @@ * @member {boolean} */ this.enabled = true; + + /** + * If enabled, pixi will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; } /** diff --git a/src/core/renderers/webgl/systems/ContextSystem.js b/src/core/renderers/webgl/systems/ContextSystem.js index ff958c7..8df85ea 100644 --- a/src/core/renderers/webgl/systems/ContextSystem.js +++ b/src/core/renderers/webgl/systems/ContextSystem.js @@ -75,7 +75,8 @@ */ createContext(canvas, options) { - var gl = canvas.getContext('webgl', options) || + var gl = //canvas.getContext('webgl2', options) || + canvas.getContext('webgl', options) || canvas.getContext('experimental-webgl', options); if (!gl) diff --git a/src/core/renderers/webgl/systems/FilterSystem.js b/src/core/renderers/webgl/systems/FilterSystem.js index f47335d..37e28d2 100644 --- a/src/core/renderers/webgl/systems/FilterSystem.js +++ b/src/core/renderers/webgl/systems/FilterSystem.js @@ -111,7 +111,7 @@ // TODO we should fit the rect around the transform.. } - else + else if (filters[0].autoFit) { sourceFrame.fit(filterData.stack[0].destinationFrame); } diff --git a/src/core/renderers/webgl/systems/shader/shader/mapType.js b/src/core/renderers/webgl/systems/shader/shader/mapType.js index 581dace..917538a 100644 --- a/src/core/renderers/webgl/systems/shader/shader/mapType.js +++ b/src/core/renderers/webgl/systems/shader/shader/mapType.js @@ -15,7 +15,7 @@ } } - return GL_TABLE[type]; + return GL_TABLE[type]; }; var GL_TABLE = null; diff --git a/src/core/settings.js b/src/core/settings.js index 18c5877..38ef45c 100644 --- a/src/core/settings.js +++ b/src/core/settings.js @@ -2,6 +2,15 @@ import canUploadSameBuffer from './utils/canUploadSameBuffer'; /** + * User's customizable globals for overriding the default PIXI settings, such + * as a renderer's default resolution, framerate, float percision, etc. + * @example + * // Use the native window resolution as the default resolution + * // will support high-density displays when rendering + * PIXI.settings.RESOLUTION = window.devicePixelRatio. + * + * // Disable interpolation when scaling, will make texture be pixelated + * PIXI.settings.SCALE_MODE = PIXI.SCALE_MODES.NEAREST; * @namespace PIXI.settings */ export default { @@ -101,6 +110,9 @@ * @property {boolean} clearBeforeRender=true * @property {boolean} preserveDrawingBuffer=false * @property {boolean} roundPixels=false + * @property {number} width=800 + * @property {number} height=600 + * @property {boolean} legacy=false */ RENDER_OPTIONS: { view: null, @@ -112,6 +124,9 @@ clearBeforeRender: true, preserveDrawingBuffer: false, roundPixels: false, + width: 800, + height: 600, + legacy: false, }, /** diff --git a/src/core/shader/Program.js b/src/core/shader/Program.js index c0b538b..07b4398 100644 --- a/src/core/shader/Program.js +++ b/src/core/shader/Program.js @@ -1,6 +1,6 @@ import extractUniformsFromSrc from './extractUniformsFromSrc'; import generateUniformsSync from './generateUniformsSync'; -import glCore from 'pixi-gl-core'; +import shaderUtils from '../renderers/webgl/systems/shader/shader'; import { ProgramCache } from '../utils'; import getTestContext from '../utils/getTestContext'; @@ -64,14 +64,17 @@ } else { - vertexSrc = glCore.shader.setPrecision(vertexSrc, 'mediump'); - fragmentSrc = glCore.shader.setPrecision(fragmentSrc, 'mediump'); + vertexSrc = shaderUtils.setPrecision(vertexSrc, 'mediump'); + fragmentSrc = shaderUtils.setPrecision(fragmentSrc, 'mediump'); - const program = glCore.shader.compileProgram(gl, vertexSrc, fragmentSrc); + const program = shaderUtils.compileProgram(gl, vertexSrc, fragmentSrc); + console.log("<<<<<<>>><<"); this.attributeData = this.getAttributeData(program, gl); this.uniformData = this.getUniformData(program, gl); + console.log(this.uniformData); + //gl.deleteProgram(program); } } @@ -95,13 +98,13 @@ for (let i = 0; i < totalAttributes; i++) { const attribData = gl.getActiveAttrib(program, i); - const type = glCore.shader.mapType(gl, attribData.type); + const type = shaderUtils.mapType(gl, attribData.type); /*eslint-disable */ const data = { type: type, name: attribData.name, - size: glCore.shader.mapSize(type), + size: shaderUtils.mapSize(type), location: 0, }; /*eslint-enable */ @@ -137,13 +140,14 @@ // TODO expose this as a prop? // const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); - const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); + //const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); for (let i = 0; i < totalUniforms; i++) { const uniformData = gl.getActiveUniform(program, i); const name = uniformData.name.replace(/\[.*?\]/, ''); - const type = glCore.shader.mapType(gl, uniformData.type); + const type = shaderUtils.mapType(gl, uniformData.type); + console.log('mapping ' + uniformData.type + ' to ' + type); // if (!name.match(maskRegex)) { @@ -151,7 +155,7 @@ uniforms[name] = { type: type, size: uniformData.size, - value: glCore.shader.defaultValue(type, uniformData.size), + value: shaderUtils.defaultValue(type, uniformData.size), }; /*eslint-enable */ } diff --git a/.travis.yml b/.travis.yml index 3df8b22..14de334 100644 --- a/.travis.yml +++ b/.travis.yml @@ -110,3 +110,43 @@ on: all_branches: true condition: $TRAVIS_TAG + # Deploy config for latest release + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: dist + upload-dir: "release" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: docs + upload-dir: "release/docs" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: coverage + upload-dir: "release/coverage" + on: + all_branches: true + condition: $TRAVIS_TAG \ No newline at end of file diff --git a/README.md b/README.md index d26fadd..3b85f85 100644 --- a/README.md +++ b/README.md @@ -36,22 +36,14 @@ - Wiki: Other misc tutorials and resources are [on the Wiki](https://github.com/pixijs/pixi.js/wiki/Resources). ### Community ### -- Forums: Check out the [forum] (http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow] (http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. +- Forums: Check out the [forum](http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow](http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. - Inspiration: Check out the [gallery](http://www.pixijs.com/gallery) to see some of the amazing things people have created! - Chat: You can join us on [Gitter](https://gitter.im/pixijs/pixi.js) To chat about Pixi. We also now have a Slack channel. If you would like to join it please Send me an email (mat@goodboydigital.com) and I will invite you in. ### Setup ### -It's easy to get started with Pixi.js! Simply grab the pre-built versions from here: - -Release Branch - Nice and stable Pixi.js -- Unminified: [http://pixijs.download/release/pixi.js] -- Minified: [http://pixijs.download/release/pixi.min.js] - -Develop Branch - The bleeding edge version of Pixi.js -- Unminified: [http://pixijs.download/dev/pixi.js] -- Minified: [http://pixijs.download/dev/pixi.min.js] +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. @@ -70,10 +62,10 @@ #### CDN Install (via cdnjs) ```html - + ``` -_Note: `4.2.2` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ +_Note: `4.4.3` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ ### Demos ### @@ -130,7 +122,7 @@ // and the root stage PIXI.Container. var app = new PIXI.Application(); -// The application will create a canvas element for you that you +// The application will create a canvas element for you that you // can then insert into the DOM. document.body.appendChild(app.view); @@ -150,7 +142,7 @@ // Add the bunny to the scene we are building. app.stage.addChild(bunny); - + // Listen for frame updates app.ticker.add(function() { // each frame we spin the bunny around a bit diff --git a/bower.json b/bower.json deleted file mode 100644 index 41b668c..0000000 --- a/bower.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "pixi.js", - "main": "dist/pixi.min.js", - "ignore": [ - "**/.*", - "docs", - "node_modules", - "bower_components", - "test", - "scripts", - "bower.json", - "inch.json" - ], - "dependencies": { - }, - "devDependencies": { - } -} diff --git a/package.json b/package.json index d35cbb2..308469f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.3.5", + "version": "4.4.4", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ @@ -40,11 +40,11 @@ "lib": "babel src --out-dir lib -s", "predocs": "rimraf docs/**", "docs": "jsdoc -c scripts/jsdoc.conf.json -R README.md", - "publish:patch": "npm version patch --no-git-tag-version && npm publish", - "publish:minor": "npm version minor --no-git-tag-version && npm publish", - "publish:major": "npm version major --no-git-tag-version && npm publish", + "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", - "postpublish": "node scripts/release.js" + "postpublish": "git push && git push --tags" }, "files": [ "dist/", @@ -72,7 +72,6 @@ "electron": "^1.4.15", "eslint": "^3.5.0", "floss": "^2.0.1", - "gh-pages": "^0.11.0", "jaguarjs-jsdoc": "^1.0.1", "js-md5": "^0.4.1", "jsdoc": "^3.4.2", diff --git a/scripts/jsdoc.conf.json b/scripts/jsdoc.conf.json index 553e61c..3f52180 100644 --- a/scripts/jsdoc.conf.json +++ b/scripts/jsdoc.conf.json @@ -43,7 +43,7 @@ }, "markdown" : { "parser" : "gfm", - "hardwrap" : true + "hardwrap" : false }, "opts": { "encoding" : "utf8", diff --git a/scripts/release.js b/scripts/release.js deleted file mode 100644 index 156620c..0000000 --- a/scripts/release.js +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env node -'use strict'; - -// Publish script to push releases of the bin files -// the normally are gitignored -const ghpages = require('gh-pages'); -const path = require('path'); -const packageInfo = require(path.join(__dirname, '..', 'package.json')); - -const options = { - src: [ - 'dist/**/*', - 'lib/**/*', - 'src/**/*', - 'scripts/*', - 'scripts/renders/*', - 'test/**/*', - '*.json', - '*.md', - 'LICENSE', - '.eslintrc', - '.editorconfig', - '.travis.yml', - '.babelrc', - ], - dotfiles: true, - branch: 'release', - message: packageInfo.version, - logger: console.log.bind(console), -}; - -ghpages.publish(process.cwd(), options, (err) => -{ - if (err) - { - console.log(err); - process.exit(1); - - return; - } - - process.exit(0); -}); diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index f16bcb1..220427a 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -21,7 +21,7 @@ const DIV_HOOK_ZINDEX = 2; /** - * The Accessibility manager reacreates the ability to tab and and have content read by screen + * The Accessibility manager recreates the ability to tab and and have content read by screen * readers. This is very important as it can possibly help people with disabilities access pixi * content. * diff --git a/src/accessibility/index.js b/src/accessibility/index.js index 568cfb1..0ae108f 100644 --- a/src/accessibility/index.js +++ b/src/accessibility/index.js @@ -1,4 +1,9 @@ /** + * This namespace contains a renderer plugin for interaction accessibility for end-users + * with physical impairments which require screen-renders, keyboard navigation, etc. + * + * Do not instantiate this plugin directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. * @namespace PIXI.accessibility */ export { default as accessibleTarget } from './accessibleTarget'; diff --git a/src/core/Application.js b/src/core/Application.js index 5340578..2fcc729 100644 --- a/src/core/Application.js +++ b/src/core/Application.js @@ -1,6 +1,8 @@ import { autoDetectRenderer } from './autoDetectRenderer'; import Container from './display/Container'; import { shared, Ticker } from './ticker'; +import settings from './settings'; +import { UPDATE_PRIORITY } from './const'; /** * Convenience class to create a new PIXI application. @@ -22,28 +24,52 @@ */ export default class Application { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experience unexplained flickering try setting this to true. - * @param {boolean} [useSharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedLoader=false] - `true` to use PIXI.loaders.shared, `false` to create new Loader. */ - constructor(width, height, options, noWebGL, useSharedTicker = false) + constructor(options, arg2, arg3, arg4, arg5) { + // Support for constructor(width, height, options, noWebGL, useSharedTicker) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + forceCanvas: !!arg4, + sharedTicker: !!arg5, + }, arg3); + } + + /** + * The default options, so we mixin functionality later. + * @member {object} + * @protected + */ + this._options = options = Object.assign({ + sharedTicker: false, + forceCanvas: false, + sharedLoader: false, + }, options); + /** * WebGL renderer if available, otherwise CanvasRenderer * @member {PIXI.WebGLRenderer|PIXI.CanvasRenderer} */ - this.renderer = autoDetectRenderer(width, height, options, noWebGL); + this.renderer = autoDetectRenderer(options); /** * The root display container that's rendered. @@ -63,7 +89,7 @@ * @member {PIXI.ticker.Ticker} * @default PIXI.ticker.shared */ - this.ticker = useSharedTicker ? shared : new Ticker(); + this.ticker = options.sharedTicker ? shared : new Ticker(); // Start the rendering this.start(); @@ -78,7 +104,7 @@ this._ticker = ticker; if (ticker) { - ticker.add(this.render, this); + ticker.add(this.render, this, UPDATE_PRIORITY.LOW); } } get ticker() // eslint-disable-line require-jsdoc @@ -136,13 +162,18 @@ */ destroy(removeView) { - this.stop(); + const oldTicker = this._ticker; + this.ticker = null; + oldTicker.destroy(); + this.stage.destroy(); this.stage = null; this.renderer.destroy(removeView); this.renderer = null; + + this._options = null; } } diff --git a/src/core/autoDetectRenderer.js b/src/core/autoDetectRenderer.js index 8960425..9ec0364 100644 --- a/src/core/autoDetectRenderer.js +++ b/src/core/autoDetectRenderer.js @@ -2,6 +2,7 @@ import CanvasRenderer from './renderers/canvas/CanvasRenderer'; import WebGLRenderer from './renderers/webgl/WebGLRenderer'; +// eslint-disable-next-line valid-jsdoc /** * This helper function will automatically detect which renderer you should be using. * WebGL is the preferred renderer as it is a lot faster. If webGL is not supported by @@ -9,24 +10,32 @@ * * @memberof PIXI * @function autoDetectRenderer - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @return {PIXI.WebGLRenderer|PIXI.CanvasRenderer} Returns WebGL renderer if available, otherwise CanvasRenderer */ -export function autoDetectRenderer(width = 800, height = 600, options, noWebGL) +export function autoDetectRenderer(options, arg1, arg2, arg3) { - if (!noWebGL && utils.isWebGLSupported()) + // Backward-compatible support for noWebGL option + let forceCanvas = options && options.forceCanvas; + + if (arg3 !== undefined) { - return new WebGLRenderer(width, height, options); + forceCanvas = arg3; } - return new CanvasRenderer(width, height, options); + if (!forceCanvas && utils.isWebGLSupported()) + { + return new WebGLRenderer(options, arg1, arg2); + } + + return new CanvasRenderer(options, arg1, arg2); } diff --git a/src/core/const.js b/src/core/const.js index b9e99ae..2c68dee 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -341,3 +341,27 @@ LINEAR_VERTICAL: 0, LINEAR_HORIZONTAL: 1, }; + +/** + * Represents the update priorities used by internal PIXI classes when registered with + * the {@link PIXI.ticker.Ticker} object. Higher priority items are updated first and lower + * priority items, such as render, should go later. + * + * @static + * @constant + * @name UPDATE_PRIORITY + * @memberof PIXI + * @type {object} + * @property {number} INTERACTION=50 Highest priority, used for {@link PIXI.interaction.InteractionManager} + * @property {number} HIGH=25 High priority updating, {@link PIXI.VideoBaseTexture} and {@link PIXI.extras.AnimatedSprite} + * @property {number} NORMAL=0 Default priority for ticker events, see {@link PIXI.ticker.Ticker#add}. + * @property {number} LOW=-25 Low priority used for {@link PIXI.Application} rendering. + * @property {number} UTILITY=-50 Lowest priority used for {@link PIXI.prepare.BasePrepare} utility. + */ +export const UPDATE_PRIORITY = { + INTERACTION: 50, + HIGH: 25, + NORMAL: 0, + LOW: -25, + UTILITY: -50, +}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 4e6c77d..decb42a 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -13,7 +13,6 @@ * * @class * @extends EventEmitter - * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ export default class DisplayObject extends EventEmitter @@ -113,6 +112,29 @@ * @private */ this._mask = null; + + /** + * If the object has been destroyed via destroy(). If true, it should not be used. + * + * @member {boolean} + * @private + * @readonly + */ + this._destroyed = false; + + /** + * Fired when this DisplayObject is added to a Container. + * + * @event PIXI.DisplayObject#added + * @param {PIXI.Container} container - The container added to. + */ + + /** + * Fired when this DisplayObject is removed from a Container. + * + * @event PIXI.DisplayObject#removed + * @param {PIXI.Container} container - The container removed from. + */ } /** @@ -403,6 +425,8 @@ this.interactive = false; this.interactiveChildren = false; + + this._destroyed = true; } /** diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index aafdfe2..f787c4a 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -792,8 +792,8 @@ sprite.worldAlpha = this.worldAlpha * sprite.alpha; sprite.blendMode = this.blendMode; - sprite.texture._frame.width = rect.width; - sprite.texture._frame.height = rect.height; + sprite._texture._frame.width = rect.width; + sprite._texture._frame.height = rect.height; sprite.transform.worldTransform = this.transform.worldTransform; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index a24c775..b9f192d 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -19,11 +19,12 @@ */ export default class SystemRenderer extends EventEmitter { + // eslint-disable-next-line valid-jsdoc /** * @param {string} system - The name of the system this renderer is for. - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -37,27 +38,31 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(system, screenWidth, screenHeight, options) + constructor(system, options, arg2, arg3) { super(); sayHello(system); - // prepare options - if (options) + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') { - for (const i in settings.RENDER_OPTIONS) - { - if (typeof options[i] === 'undefined') - { - options[i] = settings.RENDER_OPTIONS[i]; - } - } + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); } - else - { - options = settings.RENDER_OPTIONS; - } + + // Add the default render options + options = Object.assign({}, settings.RENDER_OPTIONS, options); + + /** + * The supplied constructor options. + * + * @member {Object} + * @readOnly + */ + this.options = options; /** * The type of the renderer. @@ -75,7 +80,7 @@ * * @member {PIXI.Rectangle} */ - this.screen = new Rectangle(0, 0, screenWidth || 800, screenHeight || 600); + this.screen = new Rectangle(0, 0, options.width, options.height); /** * The canvas element that everything is drawn to @@ -279,6 +284,8 @@ this.blendModes = null; + this.options = null; + this.preserveDrawingBuffer = false; this.clearBeforeRender = false; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index e84e49c..691caf0 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -17,10 +17,11 @@ */ export default class CanvasRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -34,9 +35,9 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('Canvas', screenWidth, screenHeight, options); + super('Canvas', options, arg2, arg3); this.type = RENDERER_TYPE.CANVAS; @@ -96,7 +97,19 @@ this.context = null; this.renderingToScreen = false; - this.resize(screenWidth, screenHeight); + this.resize(this.options.width, this.options.height); + + /** + * Fired after rendering finishes. + * + * @event PIXI.CanvasRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.CanvasRenderer#prerender + */ } /** @@ -299,4 +312,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.CanvasRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.CanvasExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.CanvasPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.CanvasRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index e1cf7cd..f6fe123 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -31,11 +31,12 @@ */ export default class WebGLRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** * - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -55,9 +56,9 @@ * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('WebGL', screenWidth, screenHeight, options); + super('WebGL', options, arg2, arg3); /** * The type of this renderer as a standardised const @@ -89,12 +90,6 @@ }; - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ this._backgroundColorRgba[3] = this.transparent ? 0 : 1; this.globalUniforms = new UniformGroup({ @@ -117,6 +112,13 @@ this.initPlugins(); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ if(options.context) { this.context.initFromContext(options.context); @@ -169,6 +171,25 @@ } return this; + + /** + * Fired after rendering finishes. + * + * @event PIXI.WebGLRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.WebGLRenderer#prerender + */ + + /** + * Fired when the WebGL context is set. + * + * @event PIXI.WebGLRenderer#context + * @param {WebGLRenderingContext} gl - WebGL context. + */ } /** @@ -295,4 +316,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.WebGLRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.WebGLRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(WebGLRenderer); diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index 4eb6128..fa3d883 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -47,6 +47,14 @@ * @member {boolean} */ this.enabled = true; + + /** + * If enabled, pixi will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; } /** diff --git a/src/core/renderers/webgl/systems/ContextSystem.js b/src/core/renderers/webgl/systems/ContextSystem.js index ff958c7..8df85ea 100644 --- a/src/core/renderers/webgl/systems/ContextSystem.js +++ b/src/core/renderers/webgl/systems/ContextSystem.js @@ -75,7 +75,8 @@ */ createContext(canvas, options) { - var gl = canvas.getContext('webgl', options) || + var gl = //canvas.getContext('webgl2', options) || + canvas.getContext('webgl', options) || canvas.getContext('experimental-webgl', options); if (!gl) diff --git a/src/core/renderers/webgl/systems/FilterSystem.js b/src/core/renderers/webgl/systems/FilterSystem.js index f47335d..37e28d2 100644 --- a/src/core/renderers/webgl/systems/FilterSystem.js +++ b/src/core/renderers/webgl/systems/FilterSystem.js @@ -111,7 +111,7 @@ // TODO we should fit the rect around the transform.. } - else + else if (filters[0].autoFit) { sourceFrame.fit(filterData.stack[0].destinationFrame); } diff --git a/src/core/renderers/webgl/systems/shader/shader/mapType.js b/src/core/renderers/webgl/systems/shader/shader/mapType.js index 581dace..917538a 100644 --- a/src/core/renderers/webgl/systems/shader/shader/mapType.js +++ b/src/core/renderers/webgl/systems/shader/shader/mapType.js @@ -15,7 +15,7 @@ } } - return GL_TABLE[type]; + return GL_TABLE[type]; }; var GL_TABLE = null; diff --git a/src/core/settings.js b/src/core/settings.js index 18c5877..38ef45c 100644 --- a/src/core/settings.js +++ b/src/core/settings.js @@ -2,6 +2,15 @@ import canUploadSameBuffer from './utils/canUploadSameBuffer'; /** + * User's customizable globals for overriding the default PIXI settings, such + * as a renderer's default resolution, framerate, float percision, etc. + * @example + * // Use the native window resolution as the default resolution + * // will support high-density displays when rendering + * PIXI.settings.RESOLUTION = window.devicePixelRatio. + * + * // Disable interpolation when scaling, will make texture be pixelated + * PIXI.settings.SCALE_MODE = PIXI.SCALE_MODES.NEAREST; * @namespace PIXI.settings */ export default { @@ -101,6 +110,9 @@ * @property {boolean} clearBeforeRender=true * @property {boolean} preserveDrawingBuffer=false * @property {boolean} roundPixels=false + * @property {number} width=800 + * @property {number} height=600 + * @property {boolean} legacy=false */ RENDER_OPTIONS: { view: null, @@ -112,6 +124,9 @@ clearBeforeRender: true, preserveDrawingBuffer: false, roundPixels: false, + width: 800, + height: 600, + legacy: false, }, /** diff --git a/src/core/shader/Program.js b/src/core/shader/Program.js index c0b538b..07b4398 100644 --- a/src/core/shader/Program.js +++ b/src/core/shader/Program.js @@ -1,6 +1,6 @@ import extractUniformsFromSrc from './extractUniformsFromSrc'; import generateUniformsSync from './generateUniformsSync'; -import glCore from 'pixi-gl-core'; +import shaderUtils from '../renderers/webgl/systems/shader/shader'; import { ProgramCache } from '../utils'; import getTestContext from '../utils/getTestContext'; @@ -64,14 +64,17 @@ } else { - vertexSrc = glCore.shader.setPrecision(vertexSrc, 'mediump'); - fragmentSrc = glCore.shader.setPrecision(fragmentSrc, 'mediump'); + vertexSrc = shaderUtils.setPrecision(vertexSrc, 'mediump'); + fragmentSrc = shaderUtils.setPrecision(fragmentSrc, 'mediump'); - const program = glCore.shader.compileProgram(gl, vertexSrc, fragmentSrc); + const program = shaderUtils.compileProgram(gl, vertexSrc, fragmentSrc); + console.log("<<<<<<>>><<"); this.attributeData = this.getAttributeData(program, gl); this.uniformData = this.getUniformData(program, gl); + console.log(this.uniformData); + //gl.deleteProgram(program); } } @@ -95,13 +98,13 @@ for (let i = 0; i < totalAttributes; i++) { const attribData = gl.getActiveAttrib(program, i); - const type = glCore.shader.mapType(gl, attribData.type); + const type = shaderUtils.mapType(gl, attribData.type); /*eslint-disable */ const data = { type: type, name: attribData.name, - size: glCore.shader.mapSize(type), + size: shaderUtils.mapSize(type), location: 0, }; /*eslint-enable */ @@ -137,13 +140,14 @@ // TODO expose this as a prop? // const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); - const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); + //const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); for (let i = 0; i < totalUniforms; i++) { const uniformData = gl.getActiveUniform(program, i); const name = uniformData.name.replace(/\[.*?\]/, ''); - const type = glCore.shader.mapType(gl, uniformData.type); + const type = shaderUtils.mapType(gl, uniformData.type); + console.log('mapping ' + uniformData.type + ' to ' + type); // if (!name.match(maskRegex)) { @@ -151,7 +155,7 @@ uniforms[name] = { type: type, size: uniformData.size, - value: glCore.shader.defaultValue(type, uniformData.size), + value: shaderUtils.defaultValue(type, uniformData.size), }; /*eslint-enable */ } diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index e387e99..b57d095 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -147,12 +147,12 @@ // so if _width is 0 then width was not set.. if (this._width) { - this.scale.x = sign(this.scale.x) * this._width / this.texture.orig.width; + this.scale.x = sign(this.scale.x) * this._width / this._texture.orig.width; } if (this._height) { - this.scale.y = sign(this.scale.y) * this._height / this.texture.orig.height; + this.scale.y = sign(this.scale.y) * this._height / this._texture.orig.height; } } diff --git a/.travis.yml b/.travis.yml index 3df8b22..14de334 100644 --- a/.travis.yml +++ b/.travis.yml @@ -110,3 +110,43 @@ on: all_branches: true condition: $TRAVIS_TAG + # Deploy config for latest release + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: dist + upload-dir: "release" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: docs + upload-dir: "release/docs" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: coverage + upload-dir: "release/coverage" + on: + all_branches: true + condition: $TRAVIS_TAG \ No newline at end of file diff --git a/README.md b/README.md index d26fadd..3b85f85 100644 --- a/README.md +++ b/README.md @@ -36,22 +36,14 @@ - Wiki: Other misc tutorials and resources are [on the Wiki](https://github.com/pixijs/pixi.js/wiki/Resources). ### Community ### -- Forums: Check out the [forum] (http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow] (http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. +- Forums: Check out the [forum](http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow](http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. - Inspiration: Check out the [gallery](http://www.pixijs.com/gallery) to see some of the amazing things people have created! - Chat: You can join us on [Gitter](https://gitter.im/pixijs/pixi.js) To chat about Pixi. We also now have a Slack channel. If you would like to join it please Send me an email (mat@goodboydigital.com) and I will invite you in. ### Setup ### -It's easy to get started with Pixi.js! Simply grab the pre-built versions from here: - -Release Branch - Nice and stable Pixi.js -- Unminified: [http://pixijs.download/release/pixi.js] -- Minified: [http://pixijs.download/release/pixi.min.js] - -Develop Branch - The bleeding edge version of Pixi.js -- Unminified: [http://pixijs.download/dev/pixi.js] -- Minified: [http://pixijs.download/dev/pixi.min.js] +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. @@ -70,10 +62,10 @@ #### CDN Install (via cdnjs) ```html - + ``` -_Note: `4.2.2` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ +_Note: `4.4.3` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ ### Demos ### @@ -130,7 +122,7 @@ // and the root stage PIXI.Container. var app = new PIXI.Application(); -// The application will create a canvas element for you that you +// The application will create a canvas element for you that you // can then insert into the DOM. document.body.appendChild(app.view); @@ -150,7 +142,7 @@ // Add the bunny to the scene we are building. app.stage.addChild(bunny); - + // Listen for frame updates app.ticker.add(function() { // each frame we spin the bunny around a bit diff --git a/bower.json b/bower.json deleted file mode 100644 index 41b668c..0000000 --- a/bower.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "pixi.js", - "main": "dist/pixi.min.js", - "ignore": [ - "**/.*", - "docs", - "node_modules", - "bower_components", - "test", - "scripts", - "bower.json", - "inch.json" - ], - "dependencies": { - }, - "devDependencies": { - } -} diff --git a/package.json b/package.json index d35cbb2..308469f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.3.5", + "version": "4.4.4", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ @@ -40,11 +40,11 @@ "lib": "babel src --out-dir lib -s", "predocs": "rimraf docs/**", "docs": "jsdoc -c scripts/jsdoc.conf.json -R README.md", - "publish:patch": "npm version patch --no-git-tag-version && npm publish", - "publish:minor": "npm version minor --no-git-tag-version && npm publish", - "publish:major": "npm version major --no-git-tag-version && npm publish", + "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", - "postpublish": "node scripts/release.js" + "postpublish": "git push && git push --tags" }, "files": [ "dist/", @@ -72,7 +72,6 @@ "electron": "^1.4.15", "eslint": "^3.5.0", "floss": "^2.0.1", - "gh-pages": "^0.11.0", "jaguarjs-jsdoc": "^1.0.1", "js-md5": "^0.4.1", "jsdoc": "^3.4.2", diff --git a/scripts/jsdoc.conf.json b/scripts/jsdoc.conf.json index 553e61c..3f52180 100644 --- a/scripts/jsdoc.conf.json +++ b/scripts/jsdoc.conf.json @@ -43,7 +43,7 @@ }, "markdown" : { "parser" : "gfm", - "hardwrap" : true + "hardwrap" : false }, "opts": { "encoding" : "utf8", diff --git a/scripts/release.js b/scripts/release.js deleted file mode 100644 index 156620c..0000000 --- a/scripts/release.js +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env node -'use strict'; - -// Publish script to push releases of the bin files -// the normally are gitignored -const ghpages = require('gh-pages'); -const path = require('path'); -const packageInfo = require(path.join(__dirname, '..', 'package.json')); - -const options = { - src: [ - 'dist/**/*', - 'lib/**/*', - 'src/**/*', - 'scripts/*', - 'scripts/renders/*', - 'test/**/*', - '*.json', - '*.md', - 'LICENSE', - '.eslintrc', - '.editorconfig', - '.travis.yml', - '.babelrc', - ], - dotfiles: true, - branch: 'release', - message: packageInfo.version, - logger: console.log.bind(console), -}; - -ghpages.publish(process.cwd(), options, (err) => -{ - if (err) - { - console.log(err); - process.exit(1); - - return; - } - - process.exit(0); -}); diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index f16bcb1..220427a 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -21,7 +21,7 @@ const DIV_HOOK_ZINDEX = 2; /** - * The Accessibility manager reacreates the ability to tab and and have content read by screen + * The Accessibility manager recreates the ability to tab and and have content read by screen * readers. This is very important as it can possibly help people with disabilities access pixi * content. * diff --git a/src/accessibility/index.js b/src/accessibility/index.js index 568cfb1..0ae108f 100644 --- a/src/accessibility/index.js +++ b/src/accessibility/index.js @@ -1,4 +1,9 @@ /** + * This namespace contains a renderer plugin for interaction accessibility for end-users + * with physical impairments which require screen-renders, keyboard navigation, etc. + * + * Do not instantiate this plugin directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. * @namespace PIXI.accessibility */ export { default as accessibleTarget } from './accessibleTarget'; diff --git a/src/core/Application.js b/src/core/Application.js index 5340578..2fcc729 100644 --- a/src/core/Application.js +++ b/src/core/Application.js @@ -1,6 +1,8 @@ import { autoDetectRenderer } from './autoDetectRenderer'; import Container from './display/Container'; import { shared, Ticker } from './ticker'; +import settings from './settings'; +import { UPDATE_PRIORITY } from './const'; /** * Convenience class to create a new PIXI application. @@ -22,28 +24,52 @@ */ export default class Application { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experience unexplained flickering try setting this to true. - * @param {boolean} [useSharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedLoader=false] - `true` to use PIXI.loaders.shared, `false` to create new Loader. */ - constructor(width, height, options, noWebGL, useSharedTicker = false) + constructor(options, arg2, arg3, arg4, arg5) { + // Support for constructor(width, height, options, noWebGL, useSharedTicker) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + forceCanvas: !!arg4, + sharedTicker: !!arg5, + }, arg3); + } + + /** + * The default options, so we mixin functionality later. + * @member {object} + * @protected + */ + this._options = options = Object.assign({ + sharedTicker: false, + forceCanvas: false, + sharedLoader: false, + }, options); + /** * WebGL renderer if available, otherwise CanvasRenderer * @member {PIXI.WebGLRenderer|PIXI.CanvasRenderer} */ - this.renderer = autoDetectRenderer(width, height, options, noWebGL); + this.renderer = autoDetectRenderer(options); /** * The root display container that's rendered. @@ -63,7 +89,7 @@ * @member {PIXI.ticker.Ticker} * @default PIXI.ticker.shared */ - this.ticker = useSharedTicker ? shared : new Ticker(); + this.ticker = options.sharedTicker ? shared : new Ticker(); // Start the rendering this.start(); @@ -78,7 +104,7 @@ this._ticker = ticker; if (ticker) { - ticker.add(this.render, this); + ticker.add(this.render, this, UPDATE_PRIORITY.LOW); } } get ticker() // eslint-disable-line require-jsdoc @@ -136,13 +162,18 @@ */ destroy(removeView) { - this.stop(); + const oldTicker = this._ticker; + this.ticker = null; + oldTicker.destroy(); + this.stage.destroy(); this.stage = null; this.renderer.destroy(removeView); this.renderer = null; + + this._options = null; } } diff --git a/src/core/autoDetectRenderer.js b/src/core/autoDetectRenderer.js index 8960425..9ec0364 100644 --- a/src/core/autoDetectRenderer.js +++ b/src/core/autoDetectRenderer.js @@ -2,6 +2,7 @@ import CanvasRenderer from './renderers/canvas/CanvasRenderer'; import WebGLRenderer from './renderers/webgl/WebGLRenderer'; +// eslint-disable-next-line valid-jsdoc /** * This helper function will automatically detect which renderer you should be using. * WebGL is the preferred renderer as it is a lot faster. If webGL is not supported by @@ -9,24 +10,32 @@ * * @memberof PIXI * @function autoDetectRenderer - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @return {PIXI.WebGLRenderer|PIXI.CanvasRenderer} Returns WebGL renderer if available, otherwise CanvasRenderer */ -export function autoDetectRenderer(width = 800, height = 600, options, noWebGL) +export function autoDetectRenderer(options, arg1, arg2, arg3) { - if (!noWebGL && utils.isWebGLSupported()) + // Backward-compatible support for noWebGL option + let forceCanvas = options && options.forceCanvas; + + if (arg3 !== undefined) { - return new WebGLRenderer(width, height, options); + forceCanvas = arg3; } - return new CanvasRenderer(width, height, options); + if (!forceCanvas && utils.isWebGLSupported()) + { + return new WebGLRenderer(options, arg1, arg2); + } + + return new CanvasRenderer(options, arg1, arg2); } diff --git a/src/core/const.js b/src/core/const.js index b9e99ae..2c68dee 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -341,3 +341,27 @@ LINEAR_VERTICAL: 0, LINEAR_HORIZONTAL: 1, }; + +/** + * Represents the update priorities used by internal PIXI classes when registered with + * the {@link PIXI.ticker.Ticker} object. Higher priority items are updated first and lower + * priority items, such as render, should go later. + * + * @static + * @constant + * @name UPDATE_PRIORITY + * @memberof PIXI + * @type {object} + * @property {number} INTERACTION=50 Highest priority, used for {@link PIXI.interaction.InteractionManager} + * @property {number} HIGH=25 High priority updating, {@link PIXI.VideoBaseTexture} and {@link PIXI.extras.AnimatedSprite} + * @property {number} NORMAL=0 Default priority for ticker events, see {@link PIXI.ticker.Ticker#add}. + * @property {number} LOW=-25 Low priority used for {@link PIXI.Application} rendering. + * @property {number} UTILITY=-50 Lowest priority used for {@link PIXI.prepare.BasePrepare} utility. + */ +export const UPDATE_PRIORITY = { + INTERACTION: 50, + HIGH: 25, + NORMAL: 0, + LOW: -25, + UTILITY: -50, +}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 4e6c77d..decb42a 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -13,7 +13,6 @@ * * @class * @extends EventEmitter - * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ export default class DisplayObject extends EventEmitter @@ -113,6 +112,29 @@ * @private */ this._mask = null; + + /** + * If the object has been destroyed via destroy(). If true, it should not be used. + * + * @member {boolean} + * @private + * @readonly + */ + this._destroyed = false; + + /** + * Fired when this DisplayObject is added to a Container. + * + * @event PIXI.DisplayObject#added + * @param {PIXI.Container} container - The container added to. + */ + + /** + * Fired when this DisplayObject is removed from a Container. + * + * @event PIXI.DisplayObject#removed + * @param {PIXI.Container} container - The container removed from. + */ } /** @@ -403,6 +425,8 @@ this.interactive = false; this.interactiveChildren = false; + + this._destroyed = true; } /** diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index aafdfe2..f787c4a 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -792,8 +792,8 @@ sprite.worldAlpha = this.worldAlpha * sprite.alpha; sprite.blendMode = this.blendMode; - sprite.texture._frame.width = rect.width; - sprite.texture._frame.height = rect.height; + sprite._texture._frame.width = rect.width; + sprite._texture._frame.height = rect.height; sprite.transform.worldTransform = this.transform.worldTransform; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index a24c775..b9f192d 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -19,11 +19,12 @@ */ export default class SystemRenderer extends EventEmitter { + // eslint-disable-next-line valid-jsdoc /** * @param {string} system - The name of the system this renderer is for. - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -37,27 +38,31 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(system, screenWidth, screenHeight, options) + constructor(system, options, arg2, arg3) { super(); sayHello(system); - // prepare options - if (options) + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') { - for (const i in settings.RENDER_OPTIONS) - { - if (typeof options[i] === 'undefined') - { - options[i] = settings.RENDER_OPTIONS[i]; - } - } + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); } - else - { - options = settings.RENDER_OPTIONS; - } + + // Add the default render options + options = Object.assign({}, settings.RENDER_OPTIONS, options); + + /** + * The supplied constructor options. + * + * @member {Object} + * @readOnly + */ + this.options = options; /** * The type of the renderer. @@ -75,7 +80,7 @@ * * @member {PIXI.Rectangle} */ - this.screen = new Rectangle(0, 0, screenWidth || 800, screenHeight || 600); + this.screen = new Rectangle(0, 0, options.width, options.height); /** * The canvas element that everything is drawn to @@ -279,6 +284,8 @@ this.blendModes = null; + this.options = null; + this.preserveDrawingBuffer = false; this.clearBeforeRender = false; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index e84e49c..691caf0 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -17,10 +17,11 @@ */ export default class CanvasRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -34,9 +35,9 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('Canvas', screenWidth, screenHeight, options); + super('Canvas', options, arg2, arg3); this.type = RENDERER_TYPE.CANVAS; @@ -96,7 +97,19 @@ this.context = null; this.renderingToScreen = false; - this.resize(screenWidth, screenHeight); + this.resize(this.options.width, this.options.height); + + /** + * Fired after rendering finishes. + * + * @event PIXI.CanvasRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.CanvasRenderer#prerender + */ } /** @@ -299,4 +312,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.CanvasRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.CanvasExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.CanvasPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.CanvasRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index e1cf7cd..f6fe123 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -31,11 +31,12 @@ */ export default class WebGLRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** * - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -55,9 +56,9 @@ * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('WebGL', screenWidth, screenHeight, options); + super('WebGL', options, arg2, arg3); /** * The type of this renderer as a standardised const @@ -89,12 +90,6 @@ }; - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ this._backgroundColorRgba[3] = this.transparent ? 0 : 1; this.globalUniforms = new UniformGroup({ @@ -117,6 +112,13 @@ this.initPlugins(); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ if(options.context) { this.context.initFromContext(options.context); @@ -169,6 +171,25 @@ } return this; + + /** + * Fired after rendering finishes. + * + * @event PIXI.WebGLRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.WebGLRenderer#prerender + */ + + /** + * Fired when the WebGL context is set. + * + * @event PIXI.WebGLRenderer#context + * @param {WebGLRenderingContext} gl - WebGL context. + */ } /** @@ -295,4 +316,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.WebGLRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.WebGLRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(WebGLRenderer); diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index 4eb6128..fa3d883 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -47,6 +47,14 @@ * @member {boolean} */ this.enabled = true; + + /** + * If enabled, pixi will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; } /** diff --git a/src/core/renderers/webgl/systems/ContextSystem.js b/src/core/renderers/webgl/systems/ContextSystem.js index ff958c7..8df85ea 100644 --- a/src/core/renderers/webgl/systems/ContextSystem.js +++ b/src/core/renderers/webgl/systems/ContextSystem.js @@ -75,7 +75,8 @@ */ createContext(canvas, options) { - var gl = canvas.getContext('webgl', options) || + var gl = //canvas.getContext('webgl2', options) || + canvas.getContext('webgl', options) || canvas.getContext('experimental-webgl', options); if (!gl) diff --git a/src/core/renderers/webgl/systems/FilterSystem.js b/src/core/renderers/webgl/systems/FilterSystem.js index f47335d..37e28d2 100644 --- a/src/core/renderers/webgl/systems/FilterSystem.js +++ b/src/core/renderers/webgl/systems/FilterSystem.js @@ -111,7 +111,7 @@ // TODO we should fit the rect around the transform.. } - else + else if (filters[0].autoFit) { sourceFrame.fit(filterData.stack[0].destinationFrame); } diff --git a/src/core/renderers/webgl/systems/shader/shader/mapType.js b/src/core/renderers/webgl/systems/shader/shader/mapType.js index 581dace..917538a 100644 --- a/src/core/renderers/webgl/systems/shader/shader/mapType.js +++ b/src/core/renderers/webgl/systems/shader/shader/mapType.js @@ -15,7 +15,7 @@ } } - return GL_TABLE[type]; + return GL_TABLE[type]; }; var GL_TABLE = null; diff --git a/src/core/settings.js b/src/core/settings.js index 18c5877..38ef45c 100644 --- a/src/core/settings.js +++ b/src/core/settings.js @@ -2,6 +2,15 @@ import canUploadSameBuffer from './utils/canUploadSameBuffer'; /** + * User's customizable globals for overriding the default PIXI settings, such + * as a renderer's default resolution, framerate, float percision, etc. + * @example + * // Use the native window resolution as the default resolution + * // will support high-density displays when rendering + * PIXI.settings.RESOLUTION = window.devicePixelRatio. + * + * // Disable interpolation when scaling, will make texture be pixelated + * PIXI.settings.SCALE_MODE = PIXI.SCALE_MODES.NEAREST; * @namespace PIXI.settings */ export default { @@ -101,6 +110,9 @@ * @property {boolean} clearBeforeRender=true * @property {boolean} preserveDrawingBuffer=false * @property {boolean} roundPixels=false + * @property {number} width=800 + * @property {number} height=600 + * @property {boolean} legacy=false */ RENDER_OPTIONS: { view: null, @@ -112,6 +124,9 @@ clearBeforeRender: true, preserveDrawingBuffer: false, roundPixels: false, + width: 800, + height: 600, + legacy: false, }, /** diff --git a/src/core/shader/Program.js b/src/core/shader/Program.js index c0b538b..07b4398 100644 --- a/src/core/shader/Program.js +++ b/src/core/shader/Program.js @@ -1,6 +1,6 @@ import extractUniformsFromSrc from './extractUniformsFromSrc'; import generateUniformsSync from './generateUniformsSync'; -import glCore from 'pixi-gl-core'; +import shaderUtils from '../renderers/webgl/systems/shader/shader'; import { ProgramCache } from '../utils'; import getTestContext from '../utils/getTestContext'; @@ -64,14 +64,17 @@ } else { - vertexSrc = glCore.shader.setPrecision(vertexSrc, 'mediump'); - fragmentSrc = glCore.shader.setPrecision(fragmentSrc, 'mediump'); + vertexSrc = shaderUtils.setPrecision(vertexSrc, 'mediump'); + fragmentSrc = shaderUtils.setPrecision(fragmentSrc, 'mediump'); - const program = glCore.shader.compileProgram(gl, vertexSrc, fragmentSrc); + const program = shaderUtils.compileProgram(gl, vertexSrc, fragmentSrc); + console.log("<<<<<<>>><<"); this.attributeData = this.getAttributeData(program, gl); this.uniformData = this.getUniformData(program, gl); + console.log(this.uniformData); + //gl.deleteProgram(program); } } @@ -95,13 +98,13 @@ for (let i = 0; i < totalAttributes; i++) { const attribData = gl.getActiveAttrib(program, i); - const type = glCore.shader.mapType(gl, attribData.type); + const type = shaderUtils.mapType(gl, attribData.type); /*eslint-disable */ const data = { type: type, name: attribData.name, - size: glCore.shader.mapSize(type), + size: shaderUtils.mapSize(type), location: 0, }; /*eslint-enable */ @@ -137,13 +140,14 @@ // TODO expose this as a prop? // const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); - const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); + //const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); for (let i = 0; i < totalUniforms; i++) { const uniformData = gl.getActiveUniform(program, i); const name = uniformData.name.replace(/\[.*?\]/, ''); - const type = glCore.shader.mapType(gl, uniformData.type); + const type = shaderUtils.mapType(gl, uniformData.type); + console.log('mapping ' + uniformData.type + ' to ' + type); // if (!name.match(maskRegex)) { @@ -151,7 +155,7 @@ uniforms[name] = { type: type, size: uniformData.size, - value: glCore.shader.defaultValue(type, uniformData.size), + value: shaderUtils.defaultValue(type, uniformData.size), }; /*eslint-enable */ } diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index e387e99..b57d095 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -147,12 +147,12 @@ // so if _width is 0 then width was not set.. if (this._width) { - this.scale.x = sign(this.scale.x) * this._width / this.texture.orig.width; + this.scale.x = sign(this.scale.x) * this._width / this._texture.orig.width; } if (this._height) { - this.scale.y = sign(this.scale.y) * this._height / this.texture.orig.height; + this.scale.y = sign(this.scale.y) * this._height / this._texture.orig.height; } } diff --git a/src/core/sprites/canvas/CanvasTinter.js b/src/core/sprites/canvas/CanvasTinter.js index 27e5e0c..fd9de5d 100644 --- a/src/core/sprites/canvas/CanvasTinter.js +++ b/src/core/sprites/canvas/CanvasTinter.js @@ -18,7 +18,7 @@ */ getTintedTexture: (sprite, color) => { - const texture = sprite.texture; + const texture = sprite._texture; color = CanvasTinter.roundColor(color); diff --git a/.travis.yml b/.travis.yml index 3df8b22..14de334 100644 --- a/.travis.yml +++ b/.travis.yml @@ -110,3 +110,43 @@ on: all_branches: true condition: $TRAVIS_TAG + # Deploy config for latest release + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: dist + upload-dir: "release" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: docs + upload-dir: "release/docs" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: coverage + upload-dir: "release/coverage" + on: + all_branches: true + condition: $TRAVIS_TAG \ No newline at end of file diff --git a/README.md b/README.md index d26fadd..3b85f85 100644 --- a/README.md +++ b/README.md @@ -36,22 +36,14 @@ - Wiki: Other misc tutorials and resources are [on the Wiki](https://github.com/pixijs/pixi.js/wiki/Resources). ### Community ### -- Forums: Check out the [forum] (http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow] (http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. +- Forums: Check out the [forum](http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow](http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. - Inspiration: Check out the [gallery](http://www.pixijs.com/gallery) to see some of the amazing things people have created! - Chat: You can join us on [Gitter](https://gitter.im/pixijs/pixi.js) To chat about Pixi. We also now have a Slack channel. If you would like to join it please Send me an email (mat@goodboydigital.com) and I will invite you in. ### Setup ### -It's easy to get started with Pixi.js! Simply grab the pre-built versions from here: - -Release Branch - Nice and stable Pixi.js -- Unminified: [http://pixijs.download/release/pixi.js] -- Minified: [http://pixijs.download/release/pixi.min.js] - -Develop Branch - The bleeding edge version of Pixi.js -- Unminified: [http://pixijs.download/dev/pixi.js] -- Minified: [http://pixijs.download/dev/pixi.min.js] +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. @@ -70,10 +62,10 @@ #### CDN Install (via cdnjs) ```html - + ``` -_Note: `4.2.2` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ +_Note: `4.4.3` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ ### Demos ### @@ -130,7 +122,7 @@ // and the root stage PIXI.Container. var app = new PIXI.Application(); -// The application will create a canvas element for you that you +// The application will create a canvas element for you that you // can then insert into the DOM. document.body.appendChild(app.view); @@ -150,7 +142,7 @@ // Add the bunny to the scene we are building. app.stage.addChild(bunny); - + // Listen for frame updates app.ticker.add(function() { // each frame we spin the bunny around a bit diff --git a/bower.json b/bower.json deleted file mode 100644 index 41b668c..0000000 --- a/bower.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "pixi.js", - "main": "dist/pixi.min.js", - "ignore": [ - "**/.*", - "docs", - "node_modules", - "bower_components", - "test", - "scripts", - "bower.json", - "inch.json" - ], - "dependencies": { - }, - "devDependencies": { - } -} diff --git a/package.json b/package.json index d35cbb2..308469f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.3.5", + "version": "4.4.4", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ @@ -40,11 +40,11 @@ "lib": "babel src --out-dir lib -s", "predocs": "rimraf docs/**", "docs": "jsdoc -c scripts/jsdoc.conf.json -R README.md", - "publish:patch": "npm version patch --no-git-tag-version && npm publish", - "publish:minor": "npm version minor --no-git-tag-version && npm publish", - "publish:major": "npm version major --no-git-tag-version && npm publish", + "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", - "postpublish": "node scripts/release.js" + "postpublish": "git push && git push --tags" }, "files": [ "dist/", @@ -72,7 +72,6 @@ "electron": "^1.4.15", "eslint": "^3.5.0", "floss": "^2.0.1", - "gh-pages": "^0.11.0", "jaguarjs-jsdoc": "^1.0.1", "js-md5": "^0.4.1", "jsdoc": "^3.4.2", diff --git a/scripts/jsdoc.conf.json b/scripts/jsdoc.conf.json index 553e61c..3f52180 100644 --- a/scripts/jsdoc.conf.json +++ b/scripts/jsdoc.conf.json @@ -43,7 +43,7 @@ }, "markdown" : { "parser" : "gfm", - "hardwrap" : true + "hardwrap" : false }, "opts": { "encoding" : "utf8", diff --git a/scripts/release.js b/scripts/release.js deleted file mode 100644 index 156620c..0000000 --- a/scripts/release.js +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env node -'use strict'; - -// Publish script to push releases of the bin files -// the normally are gitignored -const ghpages = require('gh-pages'); -const path = require('path'); -const packageInfo = require(path.join(__dirname, '..', 'package.json')); - -const options = { - src: [ - 'dist/**/*', - 'lib/**/*', - 'src/**/*', - 'scripts/*', - 'scripts/renders/*', - 'test/**/*', - '*.json', - '*.md', - 'LICENSE', - '.eslintrc', - '.editorconfig', - '.travis.yml', - '.babelrc', - ], - dotfiles: true, - branch: 'release', - message: packageInfo.version, - logger: console.log.bind(console), -}; - -ghpages.publish(process.cwd(), options, (err) => -{ - if (err) - { - console.log(err); - process.exit(1); - - return; - } - - process.exit(0); -}); diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index f16bcb1..220427a 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -21,7 +21,7 @@ const DIV_HOOK_ZINDEX = 2; /** - * The Accessibility manager reacreates the ability to tab and and have content read by screen + * The Accessibility manager recreates the ability to tab and and have content read by screen * readers. This is very important as it can possibly help people with disabilities access pixi * content. * diff --git a/src/accessibility/index.js b/src/accessibility/index.js index 568cfb1..0ae108f 100644 --- a/src/accessibility/index.js +++ b/src/accessibility/index.js @@ -1,4 +1,9 @@ /** + * This namespace contains a renderer plugin for interaction accessibility for end-users + * with physical impairments which require screen-renders, keyboard navigation, etc. + * + * Do not instantiate this plugin directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. * @namespace PIXI.accessibility */ export { default as accessibleTarget } from './accessibleTarget'; diff --git a/src/core/Application.js b/src/core/Application.js index 5340578..2fcc729 100644 --- a/src/core/Application.js +++ b/src/core/Application.js @@ -1,6 +1,8 @@ import { autoDetectRenderer } from './autoDetectRenderer'; import Container from './display/Container'; import { shared, Ticker } from './ticker'; +import settings from './settings'; +import { UPDATE_PRIORITY } from './const'; /** * Convenience class to create a new PIXI application. @@ -22,28 +24,52 @@ */ export default class Application { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experience unexplained flickering try setting this to true. - * @param {boolean} [useSharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedLoader=false] - `true` to use PIXI.loaders.shared, `false` to create new Loader. */ - constructor(width, height, options, noWebGL, useSharedTicker = false) + constructor(options, arg2, arg3, arg4, arg5) { + // Support for constructor(width, height, options, noWebGL, useSharedTicker) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + forceCanvas: !!arg4, + sharedTicker: !!arg5, + }, arg3); + } + + /** + * The default options, so we mixin functionality later. + * @member {object} + * @protected + */ + this._options = options = Object.assign({ + sharedTicker: false, + forceCanvas: false, + sharedLoader: false, + }, options); + /** * WebGL renderer if available, otherwise CanvasRenderer * @member {PIXI.WebGLRenderer|PIXI.CanvasRenderer} */ - this.renderer = autoDetectRenderer(width, height, options, noWebGL); + this.renderer = autoDetectRenderer(options); /** * The root display container that's rendered. @@ -63,7 +89,7 @@ * @member {PIXI.ticker.Ticker} * @default PIXI.ticker.shared */ - this.ticker = useSharedTicker ? shared : new Ticker(); + this.ticker = options.sharedTicker ? shared : new Ticker(); // Start the rendering this.start(); @@ -78,7 +104,7 @@ this._ticker = ticker; if (ticker) { - ticker.add(this.render, this); + ticker.add(this.render, this, UPDATE_PRIORITY.LOW); } } get ticker() // eslint-disable-line require-jsdoc @@ -136,13 +162,18 @@ */ destroy(removeView) { - this.stop(); + const oldTicker = this._ticker; + this.ticker = null; + oldTicker.destroy(); + this.stage.destroy(); this.stage = null; this.renderer.destroy(removeView); this.renderer = null; + + this._options = null; } } diff --git a/src/core/autoDetectRenderer.js b/src/core/autoDetectRenderer.js index 8960425..9ec0364 100644 --- a/src/core/autoDetectRenderer.js +++ b/src/core/autoDetectRenderer.js @@ -2,6 +2,7 @@ import CanvasRenderer from './renderers/canvas/CanvasRenderer'; import WebGLRenderer from './renderers/webgl/WebGLRenderer'; +// eslint-disable-next-line valid-jsdoc /** * This helper function will automatically detect which renderer you should be using. * WebGL is the preferred renderer as it is a lot faster. If webGL is not supported by @@ -9,24 +10,32 @@ * * @memberof PIXI * @function autoDetectRenderer - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @return {PIXI.WebGLRenderer|PIXI.CanvasRenderer} Returns WebGL renderer if available, otherwise CanvasRenderer */ -export function autoDetectRenderer(width = 800, height = 600, options, noWebGL) +export function autoDetectRenderer(options, arg1, arg2, arg3) { - if (!noWebGL && utils.isWebGLSupported()) + // Backward-compatible support for noWebGL option + let forceCanvas = options && options.forceCanvas; + + if (arg3 !== undefined) { - return new WebGLRenderer(width, height, options); + forceCanvas = arg3; } - return new CanvasRenderer(width, height, options); + if (!forceCanvas && utils.isWebGLSupported()) + { + return new WebGLRenderer(options, arg1, arg2); + } + + return new CanvasRenderer(options, arg1, arg2); } diff --git a/src/core/const.js b/src/core/const.js index b9e99ae..2c68dee 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -341,3 +341,27 @@ LINEAR_VERTICAL: 0, LINEAR_HORIZONTAL: 1, }; + +/** + * Represents the update priorities used by internal PIXI classes when registered with + * the {@link PIXI.ticker.Ticker} object. Higher priority items are updated first and lower + * priority items, such as render, should go later. + * + * @static + * @constant + * @name UPDATE_PRIORITY + * @memberof PIXI + * @type {object} + * @property {number} INTERACTION=50 Highest priority, used for {@link PIXI.interaction.InteractionManager} + * @property {number} HIGH=25 High priority updating, {@link PIXI.VideoBaseTexture} and {@link PIXI.extras.AnimatedSprite} + * @property {number} NORMAL=0 Default priority for ticker events, see {@link PIXI.ticker.Ticker#add}. + * @property {number} LOW=-25 Low priority used for {@link PIXI.Application} rendering. + * @property {number} UTILITY=-50 Lowest priority used for {@link PIXI.prepare.BasePrepare} utility. + */ +export const UPDATE_PRIORITY = { + INTERACTION: 50, + HIGH: 25, + NORMAL: 0, + LOW: -25, + UTILITY: -50, +}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 4e6c77d..decb42a 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -13,7 +13,6 @@ * * @class * @extends EventEmitter - * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ export default class DisplayObject extends EventEmitter @@ -113,6 +112,29 @@ * @private */ this._mask = null; + + /** + * If the object has been destroyed via destroy(). If true, it should not be used. + * + * @member {boolean} + * @private + * @readonly + */ + this._destroyed = false; + + /** + * Fired when this DisplayObject is added to a Container. + * + * @event PIXI.DisplayObject#added + * @param {PIXI.Container} container - The container added to. + */ + + /** + * Fired when this DisplayObject is removed from a Container. + * + * @event PIXI.DisplayObject#removed + * @param {PIXI.Container} container - The container removed from. + */ } /** @@ -403,6 +425,8 @@ this.interactive = false; this.interactiveChildren = false; + + this._destroyed = true; } /** diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index aafdfe2..f787c4a 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -792,8 +792,8 @@ sprite.worldAlpha = this.worldAlpha * sprite.alpha; sprite.blendMode = this.blendMode; - sprite.texture._frame.width = rect.width; - sprite.texture._frame.height = rect.height; + sprite._texture._frame.width = rect.width; + sprite._texture._frame.height = rect.height; sprite.transform.worldTransform = this.transform.worldTransform; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index a24c775..b9f192d 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -19,11 +19,12 @@ */ export default class SystemRenderer extends EventEmitter { + // eslint-disable-next-line valid-jsdoc /** * @param {string} system - The name of the system this renderer is for. - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -37,27 +38,31 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(system, screenWidth, screenHeight, options) + constructor(system, options, arg2, arg3) { super(); sayHello(system); - // prepare options - if (options) + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') { - for (const i in settings.RENDER_OPTIONS) - { - if (typeof options[i] === 'undefined') - { - options[i] = settings.RENDER_OPTIONS[i]; - } - } + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); } - else - { - options = settings.RENDER_OPTIONS; - } + + // Add the default render options + options = Object.assign({}, settings.RENDER_OPTIONS, options); + + /** + * The supplied constructor options. + * + * @member {Object} + * @readOnly + */ + this.options = options; /** * The type of the renderer. @@ -75,7 +80,7 @@ * * @member {PIXI.Rectangle} */ - this.screen = new Rectangle(0, 0, screenWidth || 800, screenHeight || 600); + this.screen = new Rectangle(0, 0, options.width, options.height); /** * The canvas element that everything is drawn to @@ -279,6 +284,8 @@ this.blendModes = null; + this.options = null; + this.preserveDrawingBuffer = false; this.clearBeforeRender = false; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index e84e49c..691caf0 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -17,10 +17,11 @@ */ export default class CanvasRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -34,9 +35,9 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('Canvas', screenWidth, screenHeight, options); + super('Canvas', options, arg2, arg3); this.type = RENDERER_TYPE.CANVAS; @@ -96,7 +97,19 @@ this.context = null; this.renderingToScreen = false; - this.resize(screenWidth, screenHeight); + this.resize(this.options.width, this.options.height); + + /** + * Fired after rendering finishes. + * + * @event PIXI.CanvasRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.CanvasRenderer#prerender + */ } /** @@ -299,4 +312,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.CanvasRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.CanvasExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.CanvasPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.CanvasRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index e1cf7cd..f6fe123 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -31,11 +31,12 @@ */ export default class WebGLRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** * - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -55,9 +56,9 @@ * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('WebGL', screenWidth, screenHeight, options); + super('WebGL', options, arg2, arg3); /** * The type of this renderer as a standardised const @@ -89,12 +90,6 @@ }; - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ this._backgroundColorRgba[3] = this.transparent ? 0 : 1; this.globalUniforms = new UniformGroup({ @@ -117,6 +112,13 @@ this.initPlugins(); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ if(options.context) { this.context.initFromContext(options.context); @@ -169,6 +171,25 @@ } return this; + + /** + * Fired after rendering finishes. + * + * @event PIXI.WebGLRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.WebGLRenderer#prerender + */ + + /** + * Fired when the WebGL context is set. + * + * @event PIXI.WebGLRenderer#context + * @param {WebGLRenderingContext} gl - WebGL context. + */ } /** @@ -295,4 +316,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.WebGLRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.WebGLRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(WebGLRenderer); diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index 4eb6128..fa3d883 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -47,6 +47,14 @@ * @member {boolean} */ this.enabled = true; + + /** + * If enabled, pixi will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; } /** diff --git a/src/core/renderers/webgl/systems/ContextSystem.js b/src/core/renderers/webgl/systems/ContextSystem.js index ff958c7..8df85ea 100644 --- a/src/core/renderers/webgl/systems/ContextSystem.js +++ b/src/core/renderers/webgl/systems/ContextSystem.js @@ -75,7 +75,8 @@ */ createContext(canvas, options) { - var gl = canvas.getContext('webgl', options) || + var gl = //canvas.getContext('webgl2', options) || + canvas.getContext('webgl', options) || canvas.getContext('experimental-webgl', options); if (!gl) diff --git a/src/core/renderers/webgl/systems/FilterSystem.js b/src/core/renderers/webgl/systems/FilterSystem.js index f47335d..37e28d2 100644 --- a/src/core/renderers/webgl/systems/FilterSystem.js +++ b/src/core/renderers/webgl/systems/FilterSystem.js @@ -111,7 +111,7 @@ // TODO we should fit the rect around the transform.. } - else + else if (filters[0].autoFit) { sourceFrame.fit(filterData.stack[0].destinationFrame); } diff --git a/src/core/renderers/webgl/systems/shader/shader/mapType.js b/src/core/renderers/webgl/systems/shader/shader/mapType.js index 581dace..917538a 100644 --- a/src/core/renderers/webgl/systems/shader/shader/mapType.js +++ b/src/core/renderers/webgl/systems/shader/shader/mapType.js @@ -15,7 +15,7 @@ } } - return GL_TABLE[type]; + return GL_TABLE[type]; }; var GL_TABLE = null; diff --git a/src/core/settings.js b/src/core/settings.js index 18c5877..38ef45c 100644 --- a/src/core/settings.js +++ b/src/core/settings.js @@ -2,6 +2,15 @@ import canUploadSameBuffer from './utils/canUploadSameBuffer'; /** + * User's customizable globals for overriding the default PIXI settings, such + * as a renderer's default resolution, framerate, float percision, etc. + * @example + * // Use the native window resolution as the default resolution + * // will support high-density displays when rendering + * PIXI.settings.RESOLUTION = window.devicePixelRatio. + * + * // Disable interpolation when scaling, will make texture be pixelated + * PIXI.settings.SCALE_MODE = PIXI.SCALE_MODES.NEAREST; * @namespace PIXI.settings */ export default { @@ -101,6 +110,9 @@ * @property {boolean} clearBeforeRender=true * @property {boolean} preserveDrawingBuffer=false * @property {boolean} roundPixels=false + * @property {number} width=800 + * @property {number} height=600 + * @property {boolean} legacy=false */ RENDER_OPTIONS: { view: null, @@ -112,6 +124,9 @@ clearBeforeRender: true, preserveDrawingBuffer: false, roundPixels: false, + width: 800, + height: 600, + legacy: false, }, /** diff --git a/src/core/shader/Program.js b/src/core/shader/Program.js index c0b538b..07b4398 100644 --- a/src/core/shader/Program.js +++ b/src/core/shader/Program.js @@ -1,6 +1,6 @@ import extractUniformsFromSrc from './extractUniformsFromSrc'; import generateUniformsSync from './generateUniformsSync'; -import glCore from 'pixi-gl-core'; +import shaderUtils from '../renderers/webgl/systems/shader/shader'; import { ProgramCache } from '../utils'; import getTestContext from '../utils/getTestContext'; @@ -64,14 +64,17 @@ } else { - vertexSrc = glCore.shader.setPrecision(vertexSrc, 'mediump'); - fragmentSrc = glCore.shader.setPrecision(fragmentSrc, 'mediump'); + vertexSrc = shaderUtils.setPrecision(vertexSrc, 'mediump'); + fragmentSrc = shaderUtils.setPrecision(fragmentSrc, 'mediump'); - const program = glCore.shader.compileProgram(gl, vertexSrc, fragmentSrc); + const program = shaderUtils.compileProgram(gl, vertexSrc, fragmentSrc); + console.log("<<<<<<>>><<"); this.attributeData = this.getAttributeData(program, gl); this.uniformData = this.getUniformData(program, gl); + console.log(this.uniformData); + //gl.deleteProgram(program); } } @@ -95,13 +98,13 @@ for (let i = 0; i < totalAttributes; i++) { const attribData = gl.getActiveAttrib(program, i); - const type = glCore.shader.mapType(gl, attribData.type); + const type = shaderUtils.mapType(gl, attribData.type); /*eslint-disable */ const data = { type: type, name: attribData.name, - size: glCore.shader.mapSize(type), + size: shaderUtils.mapSize(type), location: 0, }; /*eslint-enable */ @@ -137,13 +140,14 @@ // TODO expose this as a prop? // const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); - const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); + //const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); for (let i = 0; i < totalUniforms; i++) { const uniformData = gl.getActiveUniform(program, i); const name = uniformData.name.replace(/\[.*?\]/, ''); - const type = glCore.shader.mapType(gl, uniformData.type); + const type = shaderUtils.mapType(gl, uniformData.type); + console.log('mapping ' + uniformData.type + ' to ' + type); // if (!name.match(maskRegex)) { @@ -151,7 +155,7 @@ uniforms[name] = { type: type, size: uniformData.size, - value: glCore.shader.defaultValue(type, uniformData.size), + value: shaderUtils.defaultValue(type, uniformData.size), }; /*eslint-enable */ } diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index e387e99..b57d095 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -147,12 +147,12 @@ // so if _width is 0 then width was not set.. if (this._width) { - this.scale.x = sign(this.scale.x) * this._width / this.texture.orig.width; + this.scale.x = sign(this.scale.x) * this._width / this._texture.orig.width; } if (this._height) { - this.scale.y = sign(this.scale.y) * this._height / this.texture.orig.height; + this.scale.y = sign(this.scale.y) * this._height / this._texture.orig.height; } } diff --git a/src/core/sprites/canvas/CanvasTinter.js b/src/core/sprites/canvas/CanvasTinter.js index 27e5e0c..fd9de5d 100644 --- a/src/core/sprites/canvas/CanvasTinter.js +++ b/src/core/sprites/canvas/CanvasTinter.js @@ -18,7 +18,7 @@ */ getTintedTexture: (sprite, color) => { - const texture = sprite.texture; + const texture = sprite._texture; color = CanvasTinter.roundColor(color); diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 5a4edda..8b9b797 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -344,6 +344,7 @@ const texture = this._texture; const style = this._style; + const padding = style.trim ? 0 : style.padding; texture.baseTexture.valid = true; texture.baseTexture.resolution = this.resolution; @@ -353,11 +354,11 @@ texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - texture.trim.x = -style.padding; - texture.trim.y = -style.padding; + texture.trim.x = -padding; + texture.trim.y = -padding; - texture.orig.width = texture._frame.width - (style.padding * 2); - texture.orig.height = texture._frame.height - (style.padding * 2); + texture.orig.width = texture._frame.width - (padding * 2); + texture.orig.height = texture._frame.height - (padding * 2); // call sprite onTextureUpdate to update scale if _width or _height were set this._onTextureUpdate(); diff --git a/.travis.yml b/.travis.yml index 3df8b22..14de334 100644 --- a/.travis.yml +++ b/.travis.yml @@ -110,3 +110,43 @@ on: all_branches: true condition: $TRAVIS_TAG + # Deploy config for latest release + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: dist + upload-dir: "release" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: docs + upload-dir: "release/docs" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: coverage + upload-dir: "release/coverage" + on: + all_branches: true + condition: $TRAVIS_TAG \ No newline at end of file diff --git a/README.md b/README.md index d26fadd..3b85f85 100644 --- a/README.md +++ b/README.md @@ -36,22 +36,14 @@ - Wiki: Other misc tutorials and resources are [on the Wiki](https://github.com/pixijs/pixi.js/wiki/Resources). ### Community ### -- Forums: Check out the [forum] (http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow] (http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. +- Forums: Check out the [forum](http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow](http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. - Inspiration: Check out the [gallery](http://www.pixijs.com/gallery) to see some of the amazing things people have created! - Chat: You can join us on [Gitter](https://gitter.im/pixijs/pixi.js) To chat about Pixi. We also now have a Slack channel. If you would like to join it please Send me an email (mat@goodboydigital.com) and I will invite you in. ### Setup ### -It's easy to get started with Pixi.js! Simply grab the pre-built versions from here: - -Release Branch - Nice and stable Pixi.js -- Unminified: [http://pixijs.download/release/pixi.js] -- Minified: [http://pixijs.download/release/pixi.min.js] - -Develop Branch - The bleeding edge version of Pixi.js -- Unminified: [http://pixijs.download/dev/pixi.js] -- Minified: [http://pixijs.download/dev/pixi.min.js] +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. @@ -70,10 +62,10 @@ #### CDN Install (via cdnjs) ```html - + ``` -_Note: `4.2.2` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ +_Note: `4.4.3` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ ### Demos ### @@ -130,7 +122,7 @@ // and the root stage PIXI.Container. var app = new PIXI.Application(); -// The application will create a canvas element for you that you +// The application will create a canvas element for you that you // can then insert into the DOM. document.body.appendChild(app.view); @@ -150,7 +142,7 @@ // Add the bunny to the scene we are building. app.stage.addChild(bunny); - + // Listen for frame updates app.ticker.add(function() { // each frame we spin the bunny around a bit diff --git a/bower.json b/bower.json deleted file mode 100644 index 41b668c..0000000 --- a/bower.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "pixi.js", - "main": "dist/pixi.min.js", - "ignore": [ - "**/.*", - "docs", - "node_modules", - "bower_components", - "test", - "scripts", - "bower.json", - "inch.json" - ], - "dependencies": { - }, - "devDependencies": { - } -} diff --git a/package.json b/package.json index d35cbb2..308469f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.3.5", + "version": "4.4.4", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ @@ -40,11 +40,11 @@ "lib": "babel src --out-dir lib -s", "predocs": "rimraf docs/**", "docs": "jsdoc -c scripts/jsdoc.conf.json -R README.md", - "publish:patch": "npm version patch --no-git-tag-version && npm publish", - "publish:minor": "npm version minor --no-git-tag-version && npm publish", - "publish:major": "npm version major --no-git-tag-version && npm publish", + "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", - "postpublish": "node scripts/release.js" + "postpublish": "git push && git push --tags" }, "files": [ "dist/", @@ -72,7 +72,6 @@ "electron": "^1.4.15", "eslint": "^3.5.0", "floss": "^2.0.1", - "gh-pages": "^0.11.0", "jaguarjs-jsdoc": "^1.0.1", "js-md5": "^0.4.1", "jsdoc": "^3.4.2", diff --git a/scripts/jsdoc.conf.json b/scripts/jsdoc.conf.json index 553e61c..3f52180 100644 --- a/scripts/jsdoc.conf.json +++ b/scripts/jsdoc.conf.json @@ -43,7 +43,7 @@ }, "markdown" : { "parser" : "gfm", - "hardwrap" : true + "hardwrap" : false }, "opts": { "encoding" : "utf8", diff --git a/scripts/release.js b/scripts/release.js deleted file mode 100644 index 156620c..0000000 --- a/scripts/release.js +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env node -'use strict'; - -// Publish script to push releases of the bin files -// the normally are gitignored -const ghpages = require('gh-pages'); -const path = require('path'); -const packageInfo = require(path.join(__dirname, '..', 'package.json')); - -const options = { - src: [ - 'dist/**/*', - 'lib/**/*', - 'src/**/*', - 'scripts/*', - 'scripts/renders/*', - 'test/**/*', - '*.json', - '*.md', - 'LICENSE', - '.eslintrc', - '.editorconfig', - '.travis.yml', - '.babelrc', - ], - dotfiles: true, - branch: 'release', - message: packageInfo.version, - logger: console.log.bind(console), -}; - -ghpages.publish(process.cwd(), options, (err) => -{ - if (err) - { - console.log(err); - process.exit(1); - - return; - } - - process.exit(0); -}); diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index f16bcb1..220427a 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -21,7 +21,7 @@ const DIV_HOOK_ZINDEX = 2; /** - * The Accessibility manager reacreates the ability to tab and and have content read by screen + * The Accessibility manager recreates the ability to tab and and have content read by screen * readers. This is very important as it can possibly help people with disabilities access pixi * content. * diff --git a/src/accessibility/index.js b/src/accessibility/index.js index 568cfb1..0ae108f 100644 --- a/src/accessibility/index.js +++ b/src/accessibility/index.js @@ -1,4 +1,9 @@ /** + * This namespace contains a renderer plugin for interaction accessibility for end-users + * with physical impairments which require screen-renders, keyboard navigation, etc. + * + * Do not instantiate this plugin directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. * @namespace PIXI.accessibility */ export { default as accessibleTarget } from './accessibleTarget'; diff --git a/src/core/Application.js b/src/core/Application.js index 5340578..2fcc729 100644 --- a/src/core/Application.js +++ b/src/core/Application.js @@ -1,6 +1,8 @@ import { autoDetectRenderer } from './autoDetectRenderer'; import Container from './display/Container'; import { shared, Ticker } from './ticker'; +import settings from './settings'; +import { UPDATE_PRIORITY } from './const'; /** * Convenience class to create a new PIXI application. @@ -22,28 +24,52 @@ */ export default class Application { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experience unexplained flickering try setting this to true. - * @param {boolean} [useSharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedLoader=false] - `true` to use PIXI.loaders.shared, `false` to create new Loader. */ - constructor(width, height, options, noWebGL, useSharedTicker = false) + constructor(options, arg2, arg3, arg4, arg5) { + // Support for constructor(width, height, options, noWebGL, useSharedTicker) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + forceCanvas: !!arg4, + sharedTicker: !!arg5, + }, arg3); + } + + /** + * The default options, so we mixin functionality later. + * @member {object} + * @protected + */ + this._options = options = Object.assign({ + sharedTicker: false, + forceCanvas: false, + sharedLoader: false, + }, options); + /** * WebGL renderer if available, otherwise CanvasRenderer * @member {PIXI.WebGLRenderer|PIXI.CanvasRenderer} */ - this.renderer = autoDetectRenderer(width, height, options, noWebGL); + this.renderer = autoDetectRenderer(options); /** * The root display container that's rendered. @@ -63,7 +89,7 @@ * @member {PIXI.ticker.Ticker} * @default PIXI.ticker.shared */ - this.ticker = useSharedTicker ? shared : new Ticker(); + this.ticker = options.sharedTicker ? shared : new Ticker(); // Start the rendering this.start(); @@ -78,7 +104,7 @@ this._ticker = ticker; if (ticker) { - ticker.add(this.render, this); + ticker.add(this.render, this, UPDATE_PRIORITY.LOW); } } get ticker() // eslint-disable-line require-jsdoc @@ -136,13 +162,18 @@ */ destroy(removeView) { - this.stop(); + const oldTicker = this._ticker; + this.ticker = null; + oldTicker.destroy(); + this.stage.destroy(); this.stage = null; this.renderer.destroy(removeView); this.renderer = null; + + this._options = null; } } diff --git a/src/core/autoDetectRenderer.js b/src/core/autoDetectRenderer.js index 8960425..9ec0364 100644 --- a/src/core/autoDetectRenderer.js +++ b/src/core/autoDetectRenderer.js @@ -2,6 +2,7 @@ import CanvasRenderer from './renderers/canvas/CanvasRenderer'; import WebGLRenderer from './renderers/webgl/WebGLRenderer'; +// eslint-disable-next-line valid-jsdoc /** * This helper function will automatically detect which renderer you should be using. * WebGL is the preferred renderer as it is a lot faster. If webGL is not supported by @@ -9,24 +10,32 @@ * * @memberof PIXI * @function autoDetectRenderer - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @return {PIXI.WebGLRenderer|PIXI.CanvasRenderer} Returns WebGL renderer if available, otherwise CanvasRenderer */ -export function autoDetectRenderer(width = 800, height = 600, options, noWebGL) +export function autoDetectRenderer(options, arg1, arg2, arg3) { - if (!noWebGL && utils.isWebGLSupported()) + // Backward-compatible support for noWebGL option + let forceCanvas = options && options.forceCanvas; + + if (arg3 !== undefined) { - return new WebGLRenderer(width, height, options); + forceCanvas = arg3; } - return new CanvasRenderer(width, height, options); + if (!forceCanvas && utils.isWebGLSupported()) + { + return new WebGLRenderer(options, arg1, arg2); + } + + return new CanvasRenderer(options, arg1, arg2); } diff --git a/src/core/const.js b/src/core/const.js index b9e99ae..2c68dee 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -341,3 +341,27 @@ LINEAR_VERTICAL: 0, LINEAR_HORIZONTAL: 1, }; + +/** + * Represents the update priorities used by internal PIXI classes when registered with + * the {@link PIXI.ticker.Ticker} object. Higher priority items are updated first and lower + * priority items, such as render, should go later. + * + * @static + * @constant + * @name UPDATE_PRIORITY + * @memberof PIXI + * @type {object} + * @property {number} INTERACTION=50 Highest priority, used for {@link PIXI.interaction.InteractionManager} + * @property {number} HIGH=25 High priority updating, {@link PIXI.VideoBaseTexture} and {@link PIXI.extras.AnimatedSprite} + * @property {number} NORMAL=0 Default priority for ticker events, see {@link PIXI.ticker.Ticker#add}. + * @property {number} LOW=-25 Low priority used for {@link PIXI.Application} rendering. + * @property {number} UTILITY=-50 Lowest priority used for {@link PIXI.prepare.BasePrepare} utility. + */ +export const UPDATE_PRIORITY = { + INTERACTION: 50, + HIGH: 25, + NORMAL: 0, + LOW: -25, + UTILITY: -50, +}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 4e6c77d..decb42a 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -13,7 +13,6 @@ * * @class * @extends EventEmitter - * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ export default class DisplayObject extends EventEmitter @@ -113,6 +112,29 @@ * @private */ this._mask = null; + + /** + * If the object has been destroyed via destroy(). If true, it should not be used. + * + * @member {boolean} + * @private + * @readonly + */ + this._destroyed = false; + + /** + * Fired when this DisplayObject is added to a Container. + * + * @event PIXI.DisplayObject#added + * @param {PIXI.Container} container - The container added to. + */ + + /** + * Fired when this DisplayObject is removed from a Container. + * + * @event PIXI.DisplayObject#removed + * @param {PIXI.Container} container - The container removed from. + */ } /** @@ -403,6 +425,8 @@ this.interactive = false; this.interactiveChildren = false; + + this._destroyed = true; } /** diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index aafdfe2..f787c4a 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -792,8 +792,8 @@ sprite.worldAlpha = this.worldAlpha * sprite.alpha; sprite.blendMode = this.blendMode; - sprite.texture._frame.width = rect.width; - sprite.texture._frame.height = rect.height; + sprite._texture._frame.width = rect.width; + sprite._texture._frame.height = rect.height; sprite.transform.worldTransform = this.transform.worldTransform; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index a24c775..b9f192d 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -19,11 +19,12 @@ */ export default class SystemRenderer extends EventEmitter { + // eslint-disable-next-line valid-jsdoc /** * @param {string} system - The name of the system this renderer is for. - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -37,27 +38,31 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(system, screenWidth, screenHeight, options) + constructor(system, options, arg2, arg3) { super(); sayHello(system); - // prepare options - if (options) + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') { - for (const i in settings.RENDER_OPTIONS) - { - if (typeof options[i] === 'undefined') - { - options[i] = settings.RENDER_OPTIONS[i]; - } - } + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); } - else - { - options = settings.RENDER_OPTIONS; - } + + // Add the default render options + options = Object.assign({}, settings.RENDER_OPTIONS, options); + + /** + * The supplied constructor options. + * + * @member {Object} + * @readOnly + */ + this.options = options; /** * The type of the renderer. @@ -75,7 +80,7 @@ * * @member {PIXI.Rectangle} */ - this.screen = new Rectangle(0, 0, screenWidth || 800, screenHeight || 600); + this.screen = new Rectangle(0, 0, options.width, options.height); /** * The canvas element that everything is drawn to @@ -279,6 +284,8 @@ this.blendModes = null; + this.options = null; + this.preserveDrawingBuffer = false; this.clearBeforeRender = false; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index e84e49c..691caf0 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -17,10 +17,11 @@ */ export default class CanvasRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -34,9 +35,9 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('Canvas', screenWidth, screenHeight, options); + super('Canvas', options, arg2, arg3); this.type = RENDERER_TYPE.CANVAS; @@ -96,7 +97,19 @@ this.context = null; this.renderingToScreen = false; - this.resize(screenWidth, screenHeight); + this.resize(this.options.width, this.options.height); + + /** + * Fired after rendering finishes. + * + * @event PIXI.CanvasRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.CanvasRenderer#prerender + */ } /** @@ -299,4 +312,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.CanvasRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.CanvasExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.CanvasPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.CanvasRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index e1cf7cd..f6fe123 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -31,11 +31,12 @@ */ export default class WebGLRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** * - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -55,9 +56,9 @@ * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('WebGL', screenWidth, screenHeight, options); + super('WebGL', options, arg2, arg3); /** * The type of this renderer as a standardised const @@ -89,12 +90,6 @@ }; - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ this._backgroundColorRgba[3] = this.transparent ? 0 : 1; this.globalUniforms = new UniformGroup({ @@ -117,6 +112,13 @@ this.initPlugins(); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ if(options.context) { this.context.initFromContext(options.context); @@ -169,6 +171,25 @@ } return this; + + /** + * Fired after rendering finishes. + * + * @event PIXI.WebGLRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.WebGLRenderer#prerender + */ + + /** + * Fired when the WebGL context is set. + * + * @event PIXI.WebGLRenderer#context + * @param {WebGLRenderingContext} gl - WebGL context. + */ } /** @@ -295,4 +316,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.WebGLRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.WebGLRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(WebGLRenderer); diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index 4eb6128..fa3d883 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -47,6 +47,14 @@ * @member {boolean} */ this.enabled = true; + + /** + * If enabled, pixi will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; } /** diff --git a/src/core/renderers/webgl/systems/ContextSystem.js b/src/core/renderers/webgl/systems/ContextSystem.js index ff958c7..8df85ea 100644 --- a/src/core/renderers/webgl/systems/ContextSystem.js +++ b/src/core/renderers/webgl/systems/ContextSystem.js @@ -75,7 +75,8 @@ */ createContext(canvas, options) { - var gl = canvas.getContext('webgl', options) || + var gl = //canvas.getContext('webgl2', options) || + canvas.getContext('webgl', options) || canvas.getContext('experimental-webgl', options); if (!gl) diff --git a/src/core/renderers/webgl/systems/FilterSystem.js b/src/core/renderers/webgl/systems/FilterSystem.js index f47335d..37e28d2 100644 --- a/src/core/renderers/webgl/systems/FilterSystem.js +++ b/src/core/renderers/webgl/systems/FilterSystem.js @@ -111,7 +111,7 @@ // TODO we should fit the rect around the transform.. } - else + else if (filters[0].autoFit) { sourceFrame.fit(filterData.stack[0].destinationFrame); } diff --git a/src/core/renderers/webgl/systems/shader/shader/mapType.js b/src/core/renderers/webgl/systems/shader/shader/mapType.js index 581dace..917538a 100644 --- a/src/core/renderers/webgl/systems/shader/shader/mapType.js +++ b/src/core/renderers/webgl/systems/shader/shader/mapType.js @@ -15,7 +15,7 @@ } } - return GL_TABLE[type]; + return GL_TABLE[type]; }; var GL_TABLE = null; diff --git a/src/core/settings.js b/src/core/settings.js index 18c5877..38ef45c 100644 --- a/src/core/settings.js +++ b/src/core/settings.js @@ -2,6 +2,15 @@ import canUploadSameBuffer from './utils/canUploadSameBuffer'; /** + * User's customizable globals for overriding the default PIXI settings, such + * as a renderer's default resolution, framerate, float percision, etc. + * @example + * // Use the native window resolution as the default resolution + * // will support high-density displays when rendering + * PIXI.settings.RESOLUTION = window.devicePixelRatio. + * + * // Disable interpolation when scaling, will make texture be pixelated + * PIXI.settings.SCALE_MODE = PIXI.SCALE_MODES.NEAREST; * @namespace PIXI.settings */ export default { @@ -101,6 +110,9 @@ * @property {boolean} clearBeforeRender=true * @property {boolean} preserveDrawingBuffer=false * @property {boolean} roundPixels=false + * @property {number} width=800 + * @property {number} height=600 + * @property {boolean} legacy=false */ RENDER_OPTIONS: { view: null, @@ -112,6 +124,9 @@ clearBeforeRender: true, preserveDrawingBuffer: false, roundPixels: false, + width: 800, + height: 600, + legacy: false, }, /** diff --git a/src/core/shader/Program.js b/src/core/shader/Program.js index c0b538b..07b4398 100644 --- a/src/core/shader/Program.js +++ b/src/core/shader/Program.js @@ -1,6 +1,6 @@ import extractUniformsFromSrc from './extractUniformsFromSrc'; import generateUniformsSync from './generateUniformsSync'; -import glCore from 'pixi-gl-core'; +import shaderUtils from '../renderers/webgl/systems/shader/shader'; import { ProgramCache } from '../utils'; import getTestContext from '../utils/getTestContext'; @@ -64,14 +64,17 @@ } else { - vertexSrc = glCore.shader.setPrecision(vertexSrc, 'mediump'); - fragmentSrc = glCore.shader.setPrecision(fragmentSrc, 'mediump'); + vertexSrc = shaderUtils.setPrecision(vertexSrc, 'mediump'); + fragmentSrc = shaderUtils.setPrecision(fragmentSrc, 'mediump'); - const program = glCore.shader.compileProgram(gl, vertexSrc, fragmentSrc); + const program = shaderUtils.compileProgram(gl, vertexSrc, fragmentSrc); + console.log("<<<<<<>>><<"); this.attributeData = this.getAttributeData(program, gl); this.uniformData = this.getUniformData(program, gl); + console.log(this.uniformData); + //gl.deleteProgram(program); } } @@ -95,13 +98,13 @@ for (let i = 0; i < totalAttributes; i++) { const attribData = gl.getActiveAttrib(program, i); - const type = glCore.shader.mapType(gl, attribData.type); + const type = shaderUtils.mapType(gl, attribData.type); /*eslint-disable */ const data = { type: type, name: attribData.name, - size: glCore.shader.mapSize(type), + size: shaderUtils.mapSize(type), location: 0, }; /*eslint-enable */ @@ -137,13 +140,14 @@ // TODO expose this as a prop? // const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); - const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); + //const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); for (let i = 0; i < totalUniforms; i++) { const uniformData = gl.getActiveUniform(program, i); const name = uniformData.name.replace(/\[.*?\]/, ''); - const type = glCore.shader.mapType(gl, uniformData.type); + const type = shaderUtils.mapType(gl, uniformData.type); + console.log('mapping ' + uniformData.type + ' to ' + type); // if (!name.match(maskRegex)) { @@ -151,7 +155,7 @@ uniforms[name] = { type: type, size: uniformData.size, - value: glCore.shader.defaultValue(type, uniformData.size), + value: shaderUtils.defaultValue(type, uniformData.size), }; /*eslint-enable */ } diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index e387e99..b57d095 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -147,12 +147,12 @@ // so if _width is 0 then width was not set.. if (this._width) { - this.scale.x = sign(this.scale.x) * this._width / this.texture.orig.width; + this.scale.x = sign(this.scale.x) * this._width / this._texture.orig.width; } if (this._height) { - this.scale.y = sign(this.scale.y) * this._height / this.texture.orig.height; + this.scale.y = sign(this.scale.y) * this._height / this._texture.orig.height; } } diff --git a/src/core/sprites/canvas/CanvasTinter.js b/src/core/sprites/canvas/CanvasTinter.js index 27e5e0c..fd9de5d 100644 --- a/src/core/sprites/canvas/CanvasTinter.js +++ b/src/core/sprites/canvas/CanvasTinter.js @@ -18,7 +18,7 @@ */ getTintedTexture: (sprite, color) => { - const texture = sprite.texture; + const texture = sprite._texture; color = CanvasTinter.roundColor(color); diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 5a4edda..8b9b797 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -344,6 +344,7 @@ const texture = this._texture; const style = this._style; + const padding = style.trim ? 0 : style.padding; texture.baseTexture.valid = true; texture.baseTexture.resolution = this.resolution; @@ -353,11 +354,11 @@ texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - texture.trim.x = -style.padding; - texture.trim.y = -style.padding; + texture.trim.x = -padding; + texture.trim.y = -padding; - texture.orig.width = texture._frame.width - (style.padding * 2); - texture.orig.height = texture._frame.height - (style.padding * 2); + texture.orig.width = texture._frame.width - (padding * 2); + texture.orig.height = texture._frame.height - (padding * 2); // call sprite onTextureUpdate to update scale if _width or _height were set this._onTextureUpdate(); diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index 853da3d..8a9c34b 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -114,6 +114,38 @@ this.cacheId = null; this.validate(); + + /** + * Fired when a not-immediately-available source finishes loading. + * + * @protected + * @event PIXI.BaseTexture#loaded + * @param {PIXI.BaseTexture} baseTexture - Resource loaded. + */ + + /** + * Fired when a not-immediately-available source fails to load. + * + * @protected + * @event PIXI.BaseTexture#error + * @param {PIXI.BaseTexture} baseTexture - Resource errored. + */ + + /** + * Fired when BaseTexture is updated. + * + * @protected + * @event PIXI.BaseTexture#update + * @param {PIXI.BaseTexture} baseTexture - Instance of texture being updated. + */ + + /** + * Fired when BaseTexture is destroyed. + * + * @protected + * @event PIXI.BaseTexture#dispose + * @param {PIXI.BaseTexture} baseTexture - Instance of texture being destroyed. + */ } setResource(resource) diff --git a/.travis.yml b/.travis.yml index 3df8b22..14de334 100644 --- a/.travis.yml +++ b/.travis.yml @@ -110,3 +110,43 @@ on: all_branches: true condition: $TRAVIS_TAG + # Deploy config for latest release + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: dist + upload-dir: "release" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: docs + upload-dir: "release/docs" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: coverage + upload-dir: "release/coverage" + on: + all_branches: true + condition: $TRAVIS_TAG \ No newline at end of file diff --git a/README.md b/README.md index d26fadd..3b85f85 100644 --- a/README.md +++ b/README.md @@ -36,22 +36,14 @@ - Wiki: Other misc tutorials and resources are [on the Wiki](https://github.com/pixijs/pixi.js/wiki/Resources). ### Community ### -- Forums: Check out the [forum] (http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow] (http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. +- Forums: Check out the [forum](http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow](http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. - Inspiration: Check out the [gallery](http://www.pixijs.com/gallery) to see some of the amazing things people have created! - Chat: You can join us on [Gitter](https://gitter.im/pixijs/pixi.js) To chat about Pixi. We also now have a Slack channel. If you would like to join it please Send me an email (mat@goodboydigital.com) and I will invite you in. ### Setup ### -It's easy to get started with Pixi.js! Simply grab the pre-built versions from here: - -Release Branch - Nice and stable Pixi.js -- Unminified: [http://pixijs.download/release/pixi.js] -- Minified: [http://pixijs.download/release/pixi.min.js] - -Develop Branch - The bleeding edge version of Pixi.js -- Unminified: [http://pixijs.download/dev/pixi.js] -- Minified: [http://pixijs.download/dev/pixi.min.js] +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. @@ -70,10 +62,10 @@ #### CDN Install (via cdnjs) ```html - + ``` -_Note: `4.2.2` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ +_Note: `4.4.3` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ ### Demos ### @@ -130,7 +122,7 @@ // and the root stage PIXI.Container. var app = new PIXI.Application(); -// The application will create a canvas element for you that you +// The application will create a canvas element for you that you // can then insert into the DOM. document.body.appendChild(app.view); @@ -150,7 +142,7 @@ // Add the bunny to the scene we are building. app.stage.addChild(bunny); - + // Listen for frame updates app.ticker.add(function() { // each frame we spin the bunny around a bit diff --git a/bower.json b/bower.json deleted file mode 100644 index 41b668c..0000000 --- a/bower.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "pixi.js", - "main": "dist/pixi.min.js", - "ignore": [ - "**/.*", - "docs", - "node_modules", - "bower_components", - "test", - "scripts", - "bower.json", - "inch.json" - ], - "dependencies": { - }, - "devDependencies": { - } -} diff --git a/package.json b/package.json index d35cbb2..308469f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.3.5", + "version": "4.4.4", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ @@ -40,11 +40,11 @@ "lib": "babel src --out-dir lib -s", "predocs": "rimraf docs/**", "docs": "jsdoc -c scripts/jsdoc.conf.json -R README.md", - "publish:patch": "npm version patch --no-git-tag-version && npm publish", - "publish:minor": "npm version minor --no-git-tag-version && npm publish", - "publish:major": "npm version major --no-git-tag-version && npm publish", + "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", - "postpublish": "node scripts/release.js" + "postpublish": "git push && git push --tags" }, "files": [ "dist/", @@ -72,7 +72,6 @@ "electron": "^1.4.15", "eslint": "^3.5.0", "floss": "^2.0.1", - "gh-pages": "^0.11.0", "jaguarjs-jsdoc": "^1.0.1", "js-md5": "^0.4.1", "jsdoc": "^3.4.2", diff --git a/scripts/jsdoc.conf.json b/scripts/jsdoc.conf.json index 553e61c..3f52180 100644 --- a/scripts/jsdoc.conf.json +++ b/scripts/jsdoc.conf.json @@ -43,7 +43,7 @@ }, "markdown" : { "parser" : "gfm", - "hardwrap" : true + "hardwrap" : false }, "opts": { "encoding" : "utf8", diff --git a/scripts/release.js b/scripts/release.js deleted file mode 100644 index 156620c..0000000 --- a/scripts/release.js +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env node -'use strict'; - -// Publish script to push releases of the bin files -// the normally are gitignored -const ghpages = require('gh-pages'); -const path = require('path'); -const packageInfo = require(path.join(__dirname, '..', 'package.json')); - -const options = { - src: [ - 'dist/**/*', - 'lib/**/*', - 'src/**/*', - 'scripts/*', - 'scripts/renders/*', - 'test/**/*', - '*.json', - '*.md', - 'LICENSE', - '.eslintrc', - '.editorconfig', - '.travis.yml', - '.babelrc', - ], - dotfiles: true, - branch: 'release', - message: packageInfo.version, - logger: console.log.bind(console), -}; - -ghpages.publish(process.cwd(), options, (err) => -{ - if (err) - { - console.log(err); - process.exit(1); - - return; - } - - process.exit(0); -}); diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index f16bcb1..220427a 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -21,7 +21,7 @@ const DIV_HOOK_ZINDEX = 2; /** - * The Accessibility manager reacreates the ability to tab and and have content read by screen + * The Accessibility manager recreates the ability to tab and and have content read by screen * readers. This is very important as it can possibly help people with disabilities access pixi * content. * diff --git a/src/accessibility/index.js b/src/accessibility/index.js index 568cfb1..0ae108f 100644 --- a/src/accessibility/index.js +++ b/src/accessibility/index.js @@ -1,4 +1,9 @@ /** + * This namespace contains a renderer plugin for interaction accessibility for end-users + * with physical impairments which require screen-renders, keyboard navigation, etc. + * + * Do not instantiate this plugin directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. * @namespace PIXI.accessibility */ export { default as accessibleTarget } from './accessibleTarget'; diff --git a/src/core/Application.js b/src/core/Application.js index 5340578..2fcc729 100644 --- a/src/core/Application.js +++ b/src/core/Application.js @@ -1,6 +1,8 @@ import { autoDetectRenderer } from './autoDetectRenderer'; import Container from './display/Container'; import { shared, Ticker } from './ticker'; +import settings from './settings'; +import { UPDATE_PRIORITY } from './const'; /** * Convenience class to create a new PIXI application. @@ -22,28 +24,52 @@ */ export default class Application { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experience unexplained flickering try setting this to true. - * @param {boolean} [useSharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedLoader=false] - `true` to use PIXI.loaders.shared, `false` to create new Loader. */ - constructor(width, height, options, noWebGL, useSharedTicker = false) + constructor(options, arg2, arg3, arg4, arg5) { + // Support for constructor(width, height, options, noWebGL, useSharedTicker) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + forceCanvas: !!arg4, + sharedTicker: !!arg5, + }, arg3); + } + + /** + * The default options, so we mixin functionality later. + * @member {object} + * @protected + */ + this._options = options = Object.assign({ + sharedTicker: false, + forceCanvas: false, + sharedLoader: false, + }, options); + /** * WebGL renderer if available, otherwise CanvasRenderer * @member {PIXI.WebGLRenderer|PIXI.CanvasRenderer} */ - this.renderer = autoDetectRenderer(width, height, options, noWebGL); + this.renderer = autoDetectRenderer(options); /** * The root display container that's rendered. @@ -63,7 +89,7 @@ * @member {PIXI.ticker.Ticker} * @default PIXI.ticker.shared */ - this.ticker = useSharedTicker ? shared : new Ticker(); + this.ticker = options.sharedTicker ? shared : new Ticker(); // Start the rendering this.start(); @@ -78,7 +104,7 @@ this._ticker = ticker; if (ticker) { - ticker.add(this.render, this); + ticker.add(this.render, this, UPDATE_PRIORITY.LOW); } } get ticker() // eslint-disable-line require-jsdoc @@ -136,13 +162,18 @@ */ destroy(removeView) { - this.stop(); + const oldTicker = this._ticker; + this.ticker = null; + oldTicker.destroy(); + this.stage.destroy(); this.stage = null; this.renderer.destroy(removeView); this.renderer = null; + + this._options = null; } } diff --git a/src/core/autoDetectRenderer.js b/src/core/autoDetectRenderer.js index 8960425..9ec0364 100644 --- a/src/core/autoDetectRenderer.js +++ b/src/core/autoDetectRenderer.js @@ -2,6 +2,7 @@ import CanvasRenderer from './renderers/canvas/CanvasRenderer'; import WebGLRenderer from './renderers/webgl/WebGLRenderer'; +// eslint-disable-next-line valid-jsdoc /** * This helper function will automatically detect which renderer you should be using. * WebGL is the preferred renderer as it is a lot faster. If webGL is not supported by @@ -9,24 +10,32 @@ * * @memberof PIXI * @function autoDetectRenderer - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @return {PIXI.WebGLRenderer|PIXI.CanvasRenderer} Returns WebGL renderer if available, otherwise CanvasRenderer */ -export function autoDetectRenderer(width = 800, height = 600, options, noWebGL) +export function autoDetectRenderer(options, arg1, arg2, arg3) { - if (!noWebGL && utils.isWebGLSupported()) + // Backward-compatible support for noWebGL option + let forceCanvas = options && options.forceCanvas; + + if (arg3 !== undefined) { - return new WebGLRenderer(width, height, options); + forceCanvas = arg3; } - return new CanvasRenderer(width, height, options); + if (!forceCanvas && utils.isWebGLSupported()) + { + return new WebGLRenderer(options, arg1, arg2); + } + + return new CanvasRenderer(options, arg1, arg2); } diff --git a/src/core/const.js b/src/core/const.js index b9e99ae..2c68dee 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -341,3 +341,27 @@ LINEAR_VERTICAL: 0, LINEAR_HORIZONTAL: 1, }; + +/** + * Represents the update priorities used by internal PIXI classes when registered with + * the {@link PIXI.ticker.Ticker} object. Higher priority items are updated first and lower + * priority items, such as render, should go later. + * + * @static + * @constant + * @name UPDATE_PRIORITY + * @memberof PIXI + * @type {object} + * @property {number} INTERACTION=50 Highest priority, used for {@link PIXI.interaction.InteractionManager} + * @property {number} HIGH=25 High priority updating, {@link PIXI.VideoBaseTexture} and {@link PIXI.extras.AnimatedSprite} + * @property {number} NORMAL=0 Default priority for ticker events, see {@link PIXI.ticker.Ticker#add}. + * @property {number} LOW=-25 Low priority used for {@link PIXI.Application} rendering. + * @property {number} UTILITY=-50 Lowest priority used for {@link PIXI.prepare.BasePrepare} utility. + */ +export const UPDATE_PRIORITY = { + INTERACTION: 50, + HIGH: 25, + NORMAL: 0, + LOW: -25, + UTILITY: -50, +}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 4e6c77d..decb42a 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -13,7 +13,6 @@ * * @class * @extends EventEmitter - * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ export default class DisplayObject extends EventEmitter @@ -113,6 +112,29 @@ * @private */ this._mask = null; + + /** + * If the object has been destroyed via destroy(). If true, it should not be used. + * + * @member {boolean} + * @private + * @readonly + */ + this._destroyed = false; + + /** + * Fired when this DisplayObject is added to a Container. + * + * @event PIXI.DisplayObject#added + * @param {PIXI.Container} container - The container added to. + */ + + /** + * Fired when this DisplayObject is removed from a Container. + * + * @event PIXI.DisplayObject#removed + * @param {PIXI.Container} container - The container removed from. + */ } /** @@ -403,6 +425,8 @@ this.interactive = false; this.interactiveChildren = false; + + this._destroyed = true; } /** diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index aafdfe2..f787c4a 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -792,8 +792,8 @@ sprite.worldAlpha = this.worldAlpha * sprite.alpha; sprite.blendMode = this.blendMode; - sprite.texture._frame.width = rect.width; - sprite.texture._frame.height = rect.height; + sprite._texture._frame.width = rect.width; + sprite._texture._frame.height = rect.height; sprite.transform.worldTransform = this.transform.worldTransform; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index a24c775..b9f192d 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -19,11 +19,12 @@ */ export default class SystemRenderer extends EventEmitter { + // eslint-disable-next-line valid-jsdoc /** * @param {string} system - The name of the system this renderer is for. - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -37,27 +38,31 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(system, screenWidth, screenHeight, options) + constructor(system, options, arg2, arg3) { super(); sayHello(system); - // prepare options - if (options) + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') { - for (const i in settings.RENDER_OPTIONS) - { - if (typeof options[i] === 'undefined') - { - options[i] = settings.RENDER_OPTIONS[i]; - } - } + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); } - else - { - options = settings.RENDER_OPTIONS; - } + + // Add the default render options + options = Object.assign({}, settings.RENDER_OPTIONS, options); + + /** + * The supplied constructor options. + * + * @member {Object} + * @readOnly + */ + this.options = options; /** * The type of the renderer. @@ -75,7 +80,7 @@ * * @member {PIXI.Rectangle} */ - this.screen = new Rectangle(0, 0, screenWidth || 800, screenHeight || 600); + this.screen = new Rectangle(0, 0, options.width, options.height); /** * The canvas element that everything is drawn to @@ -279,6 +284,8 @@ this.blendModes = null; + this.options = null; + this.preserveDrawingBuffer = false; this.clearBeforeRender = false; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index e84e49c..691caf0 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -17,10 +17,11 @@ */ export default class CanvasRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -34,9 +35,9 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('Canvas', screenWidth, screenHeight, options); + super('Canvas', options, arg2, arg3); this.type = RENDERER_TYPE.CANVAS; @@ -96,7 +97,19 @@ this.context = null; this.renderingToScreen = false; - this.resize(screenWidth, screenHeight); + this.resize(this.options.width, this.options.height); + + /** + * Fired after rendering finishes. + * + * @event PIXI.CanvasRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.CanvasRenderer#prerender + */ } /** @@ -299,4 +312,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.CanvasRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.CanvasExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.CanvasPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.CanvasRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index e1cf7cd..f6fe123 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -31,11 +31,12 @@ */ export default class WebGLRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** * - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -55,9 +56,9 @@ * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('WebGL', screenWidth, screenHeight, options); + super('WebGL', options, arg2, arg3); /** * The type of this renderer as a standardised const @@ -89,12 +90,6 @@ }; - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ this._backgroundColorRgba[3] = this.transparent ? 0 : 1; this.globalUniforms = new UniformGroup({ @@ -117,6 +112,13 @@ this.initPlugins(); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ if(options.context) { this.context.initFromContext(options.context); @@ -169,6 +171,25 @@ } return this; + + /** + * Fired after rendering finishes. + * + * @event PIXI.WebGLRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.WebGLRenderer#prerender + */ + + /** + * Fired when the WebGL context is set. + * + * @event PIXI.WebGLRenderer#context + * @param {WebGLRenderingContext} gl - WebGL context. + */ } /** @@ -295,4 +316,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.WebGLRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.WebGLRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(WebGLRenderer); diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index 4eb6128..fa3d883 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -47,6 +47,14 @@ * @member {boolean} */ this.enabled = true; + + /** + * If enabled, pixi will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; } /** diff --git a/src/core/renderers/webgl/systems/ContextSystem.js b/src/core/renderers/webgl/systems/ContextSystem.js index ff958c7..8df85ea 100644 --- a/src/core/renderers/webgl/systems/ContextSystem.js +++ b/src/core/renderers/webgl/systems/ContextSystem.js @@ -75,7 +75,8 @@ */ createContext(canvas, options) { - var gl = canvas.getContext('webgl', options) || + var gl = //canvas.getContext('webgl2', options) || + canvas.getContext('webgl', options) || canvas.getContext('experimental-webgl', options); if (!gl) diff --git a/src/core/renderers/webgl/systems/FilterSystem.js b/src/core/renderers/webgl/systems/FilterSystem.js index f47335d..37e28d2 100644 --- a/src/core/renderers/webgl/systems/FilterSystem.js +++ b/src/core/renderers/webgl/systems/FilterSystem.js @@ -111,7 +111,7 @@ // TODO we should fit the rect around the transform.. } - else + else if (filters[0].autoFit) { sourceFrame.fit(filterData.stack[0].destinationFrame); } diff --git a/src/core/renderers/webgl/systems/shader/shader/mapType.js b/src/core/renderers/webgl/systems/shader/shader/mapType.js index 581dace..917538a 100644 --- a/src/core/renderers/webgl/systems/shader/shader/mapType.js +++ b/src/core/renderers/webgl/systems/shader/shader/mapType.js @@ -15,7 +15,7 @@ } } - return GL_TABLE[type]; + return GL_TABLE[type]; }; var GL_TABLE = null; diff --git a/src/core/settings.js b/src/core/settings.js index 18c5877..38ef45c 100644 --- a/src/core/settings.js +++ b/src/core/settings.js @@ -2,6 +2,15 @@ import canUploadSameBuffer from './utils/canUploadSameBuffer'; /** + * User's customizable globals for overriding the default PIXI settings, such + * as a renderer's default resolution, framerate, float percision, etc. + * @example + * // Use the native window resolution as the default resolution + * // will support high-density displays when rendering + * PIXI.settings.RESOLUTION = window.devicePixelRatio. + * + * // Disable interpolation when scaling, will make texture be pixelated + * PIXI.settings.SCALE_MODE = PIXI.SCALE_MODES.NEAREST; * @namespace PIXI.settings */ export default { @@ -101,6 +110,9 @@ * @property {boolean} clearBeforeRender=true * @property {boolean} preserveDrawingBuffer=false * @property {boolean} roundPixels=false + * @property {number} width=800 + * @property {number} height=600 + * @property {boolean} legacy=false */ RENDER_OPTIONS: { view: null, @@ -112,6 +124,9 @@ clearBeforeRender: true, preserveDrawingBuffer: false, roundPixels: false, + width: 800, + height: 600, + legacy: false, }, /** diff --git a/src/core/shader/Program.js b/src/core/shader/Program.js index c0b538b..07b4398 100644 --- a/src/core/shader/Program.js +++ b/src/core/shader/Program.js @@ -1,6 +1,6 @@ import extractUniformsFromSrc from './extractUniformsFromSrc'; import generateUniformsSync from './generateUniformsSync'; -import glCore from 'pixi-gl-core'; +import shaderUtils from '../renderers/webgl/systems/shader/shader'; import { ProgramCache } from '../utils'; import getTestContext from '../utils/getTestContext'; @@ -64,14 +64,17 @@ } else { - vertexSrc = glCore.shader.setPrecision(vertexSrc, 'mediump'); - fragmentSrc = glCore.shader.setPrecision(fragmentSrc, 'mediump'); + vertexSrc = shaderUtils.setPrecision(vertexSrc, 'mediump'); + fragmentSrc = shaderUtils.setPrecision(fragmentSrc, 'mediump'); - const program = glCore.shader.compileProgram(gl, vertexSrc, fragmentSrc); + const program = shaderUtils.compileProgram(gl, vertexSrc, fragmentSrc); + console.log("<<<<<<>>><<"); this.attributeData = this.getAttributeData(program, gl); this.uniformData = this.getUniformData(program, gl); + console.log(this.uniformData); + //gl.deleteProgram(program); } } @@ -95,13 +98,13 @@ for (let i = 0; i < totalAttributes; i++) { const attribData = gl.getActiveAttrib(program, i); - const type = glCore.shader.mapType(gl, attribData.type); + const type = shaderUtils.mapType(gl, attribData.type); /*eslint-disable */ const data = { type: type, name: attribData.name, - size: glCore.shader.mapSize(type), + size: shaderUtils.mapSize(type), location: 0, }; /*eslint-enable */ @@ -137,13 +140,14 @@ // TODO expose this as a prop? // const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); - const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); + //const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); for (let i = 0; i < totalUniforms; i++) { const uniformData = gl.getActiveUniform(program, i); const name = uniformData.name.replace(/\[.*?\]/, ''); - const type = glCore.shader.mapType(gl, uniformData.type); + const type = shaderUtils.mapType(gl, uniformData.type); + console.log('mapping ' + uniformData.type + ' to ' + type); // if (!name.match(maskRegex)) { @@ -151,7 +155,7 @@ uniforms[name] = { type: type, size: uniformData.size, - value: glCore.shader.defaultValue(type, uniformData.size), + value: shaderUtils.defaultValue(type, uniformData.size), }; /*eslint-enable */ } diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index e387e99..b57d095 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -147,12 +147,12 @@ // so if _width is 0 then width was not set.. if (this._width) { - this.scale.x = sign(this.scale.x) * this._width / this.texture.orig.width; + this.scale.x = sign(this.scale.x) * this._width / this._texture.orig.width; } if (this._height) { - this.scale.y = sign(this.scale.y) * this._height / this.texture.orig.height; + this.scale.y = sign(this.scale.y) * this._height / this._texture.orig.height; } } diff --git a/src/core/sprites/canvas/CanvasTinter.js b/src/core/sprites/canvas/CanvasTinter.js index 27e5e0c..fd9de5d 100644 --- a/src/core/sprites/canvas/CanvasTinter.js +++ b/src/core/sprites/canvas/CanvasTinter.js @@ -18,7 +18,7 @@ */ getTintedTexture: (sprite, color) => { - const texture = sprite.texture; + const texture = sprite._texture; color = CanvasTinter.roundColor(color); diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 5a4edda..8b9b797 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -344,6 +344,7 @@ const texture = this._texture; const style = this._style; + const padding = style.trim ? 0 : style.padding; texture.baseTexture.valid = true; texture.baseTexture.resolution = this.resolution; @@ -353,11 +354,11 @@ texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - texture.trim.x = -style.padding; - texture.trim.y = -style.padding; + texture.trim.x = -padding; + texture.trim.y = -padding; - texture.orig.width = texture._frame.width - (style.padding * 2); - texture.orig.height = texture._frame.height - (style.padding * 2); + texture.orig.width = texture._frame.width - (padding * 2); + texture.orig.height = texture._frame.height - (padding * 2); // call sprite onTextureUpdate to update scale if _width or _height were set this._onTextureUpdate(); diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index 853da3d..8a9c34b 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -114,6 +114,38 @@ this.cacheId = null; this.validate(); + + /** + * Fired when a not-immediately-available source finishes loading. + * + * @protected + * @event PIXI.BaseTexture#loaded + * @param {PIXI.BaseTexture} baseTexture - Resource loaded. + */ + + /** + * Fired when a not-immediately-available source fails to load. + * + * @protected + * @event PIXI.BaseTexture#error + * @param {PIXI.BaseTexture} baseTexture - Resource errored. + */ + + /** + * Fired when BaseTexture is updated. + * + * @protected + * @event PIXI.BaseTexture#update + * @param {PIXI.BaseTexture} baseTexture - Instance of texture being updated. + */ + + /** + * Fired when BaseTexture is destroyed. + * + * @protected + * @event PIXI.BaseTexture#dispose + * @param {PIXI.BaseTexture} baseTexture - Instance of texture being destroyed. + */ } setResource(resource) diff --git a/src/core/textures/Spritesheet.js b/src/core/textures/Spritesheet.js index d469c62..9b24f27 100644 --- a/src/core/textures/Spritesheet.js +++ b/src/core/textures/Spritesheet.js @@ -208,6 +208,7 @@ // lets also add the frame to pixi's global cache for fromFrame and fromImage functions TextureCache[i] = this.textures[i]; + this.textures[i].textureCacheId = i; } frameIndex++; diff --git a/.travis.yml b/.travis.yml index 3df8b22..14de334 100644 --- a/.travis.yml +++ b/.travis.yml @@ -110,3 +110,43 @@ on: all_branches: true condition: $TRAVIS_TAG + # Deploy config for latest release + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: dist + upload-dir: "release" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: docs + upload-dir: "release/docs" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: coverage + upload-dir: "release/coverage" + on: + all_branches: true + condition: $TRAVIS_TAG \ No newline at end of file diff --git a/README.md b/README.md index d26fadd..3b85f85 100644 --- a/README.md +++ b/README.md @@ -36,22 +36,14 @@ - Wiki: Other misc tutorials and resources are [on the Wiki](https://github.com/pixijs/pixi.js/wiki/Resources). ### Community ### -- Forums: Check out the [forum] (http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow] (http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. +- Forums: Check out the [forum](http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow](http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. - Inspiration: Check out the [gallery](http://www.pixijs.com/gallery) to see some of the amazing things people have created! - Chat: You can join us on [Gitter](https://gitter.im/pixijs/pixi.js) To chat about Pixi. We also now have a Slack channel. If you would like to join it please Send me an email (mat@goodboydigital.com) and I will invite you in. ### Setup ### -It's easy to get started with Pixi.js! Simply grab the pre-built versions from here: - -Release Branch - Nice and stable Pixi.js -- Unminified: [http://pixijs.download/release/pixi.js] -- Minified: [http://pixijs.download/release/pixi.min.js] - -Develop Branch - The bleeding edge version of Pixi.js -- Unminified: [http://pixijs.download/dev/pixi.js] -- Minified: [http://pixijs.download/dev/pixi.min.js] +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. @@ -70,10 +62,10 @@ #### CDN Install (via cdnjs) ```html - + ``` -_Note: `4.2.2` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ +_Note: `4.4.3` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ ### Demos ### @@ -130,7 +122,7 @@ // and the root stage PIXI.Container. var app = new PIXI.Application(); -// The application will create a canvas element for you that you +// The application will create a canvas element for you that you // can then insert into the DOM. document.body.appendChild(app.view); @@ -150,7 +142,7 @@ // Add the bunny to the scene we are building. app.stage.addChild(bunny); - + // Listen for frame updates app.ticker.add(function() { // each frame we spin the bunny around a bit diff --git a/bower.json b/bower.json deleted file mode 100644 index 41b668c..0000000 --- a/bower.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "pixi.js", - "main": "dist/pixi.min.js", - "ignore": [ - "**/.*", - "docs", - "node_modules", - "bower_components", - "test", - "scripts", - "bower.json", - "inch.json" - ], - "dependencies": { - }, - "devDependencies": { - } -} diff --git a/package.json b/package.json index d35cbb2..308469f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.3.5", + "version": "4.4.4", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ @@ -40,11 +40,11 @@ "lib": "babel src --out-dir lib -s", "predocs": "rimraf docs/**", "docs": "jsdoc -c scripts/jsdoc.conf.json -R README.md", - "publish:patch": "npm version patch --no-git-tag-version && npm publish", - "publish:minor": "npm version minor --no-git-tag-version && npm publish", - "publish:major": "npm version major --no-git-tag-version && npm publish", + "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", - "postpublish": "node scripts/release.js" + "postpublish": "git push && git push --tags" }, "files": [ "dist/", @@ -72,7 +72,6 @@ "electron": "^1.4.15", "eslint": "^3.5.0", "floss": "^2.0.1", - "gh-pages": "^0.11.0", "jaguarjs-jsdoc": "^1.0.1", "js-md5": "^0.4.1", "jsdoc": "^3.4.2", diff --git a/scripts/jsdoc.conf.json b/scripts/jsdoc.conf.json index 553e61c..3f52180 100644 --- a/scripts/jsdoc.conf.json +++ b/scripts/jsdoc.conf.json @@ -43,7 +43,7 @@ }, "markdown" : { "parser" : "gfm", - "hardwrap" : true + "hardwrap" : false }, "opts": { "encoding" : "utf8", diff --git a/scripts/release.js b/scripts/release.js deleted file mode 100644 index 156620c..0000000 --- a/scripts/release.js +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env node -'use strict'; - -// Publish script to push releases of the bin files -// the normally are gitignored -const ghpages = require('gh-pages'); -const path = require('path'); -const packageInfo = require(path.join(__dirname, '..', 'package.json')); - -const options = { - src: [ - 'dist/**/*', - 'lib/**/*', - 'src/**/*', - 'scripts/*', - 'scripts/renders/*', - 'test/**/*', - '*.json', - '*.md', - 'LICENSE', - '.eslintrc', - '.editorconfig', - '.travis.yml', - '.babelrc', - ], - dotfiles: true, - branch: 'release', - message: packageInfo.version, - logger: console.log.bind(console), -}; - -ghpages.publish(process.cwd(), options, (err) => -{ - if (err) - { - console.log(err); - process.exit(1); - - return; - } - - process.exit(0); -}); diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index f16bcb1..220427a 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -21,7 +21,7 @@ const DIV_HOOK_ZINDEX = 2; /** - * The Accessibility manager reacreates the ability to tab and and have content read by screen + * The Accessibility manager recreates the ability to tab and and have content read by screen * readers. This is very important as it can possibly help people with disabilities access pixi * content. * diff --git a/src/accessibility/index.js b/src/accessibility/index.js index 568cfb1..0ae108f 100644 --- a/src/accessibility/index.js +++ b/src/accessibility/index.js @@ -1,4 +1,9 @@ /** + * This namespace contains a renderer plugin for interaction accessibility for end-users + * with physical impairments which require screen-renders, keyboard navigation, etc. + * + * Do not instantiate this plugin directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. * @namespace PIXI.accessibility */ export { default as accessibleTarget } from './accessibleTarget'; diff --git a/src/core/Application.js b/src/core/Application.js index 5340578..2fcc729 100644 --- a/src/core/Application.js +++ b/src/core/Application.js @@ -1,6 +1,8 @@ import { autoDetectRenderer } from './autoDetectRenderer'; import Container from './display/Container'; import { shared, Ticker } from './ticker'; +import settings from './settings'; +import { UPDATE_PRIORITY } from './const'; /** * Convenience class to create a new PIXI application. @@ -22,28 +24,52 @@ */ export default class Application { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experience unexplained flickering try setting this to true. - * @param {boolean} [useSharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedLoader=false] - `true` to use PIXI.loaders.shared, `false` to create new Loader. */ - constructor(width, height, options, noWebGL, useSharedTicker = false) + constructor(options, arg2, arg3, arg4, arg5) { + // Support for constructor(width, height, options, noWebGL, useSharedTicker) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + forceCanvas: !!arg4, + sharedTicker: !!arg5, + }, arg3); + } + + /** + * The default options, so we mixin functionality later. + * @member {object} + * @protected + */ + this._options = options = Object.assign({ + sharedTicker: false, + forceCanvas: false, + sharedLoader: false, + }, options); + /** * WebGL renderer if available, otherwise CanvasRenderer * @member {PIXI.WebGLRenderer|PIXI.CanvasRenderer} */ - this.renderer = autoDetectRenderer(width, height, options, noWebGL); + this.renderer = autoDetectRenderer(options); /** * The root display container that's rendered. @@ -63,7 +89,7 @@ * @member {PIXI.ticker.Ticker} * @default PIXI.ticker.shared */ - this.ticker = useSharedTicker ? shared : new Ticker(); + this.ticker = options.sharedTicker ? shared : new Ticker(); // Start the rendering this.start(); @@ -78,7 +104,7 @@ this._ticker = ticker; if (ticker) { - ticker.add(this.render, this); + ticker.add(this.render, this, UPDATE_PRIORITY.LOW); } } get ticker() // eslint-disable-line require-jsdoc @@ -136,13 +162,18 @@ */ destroy(removeView) { - this.stop(); + const oldTicker = this._ticker; + this.ticker = null; + oldTicker.destroy(); + this.stage.destroy(); this.stage = null; this.renderer.destroy(removeView); this.renderer = null; + + this._options = null; } } diff --git a/src/core/autoDetectRenderer.js b/src/core/autoDetectRenderer.js index 8960425..9ec0364 100644 --- a/src/core/autoDetectRenderer.js +++ b/src/core/autoDetectRenderer.js @@ -2,6 +2,7 @@ import CanvasRenderer from './renderers/canvas/CanvasRenderer'; import WebGLRenderer from './renderers/webgl/WebGLRenderer'; +// eslint-disable-next-line valid-jsdoc /** * This helper function will automatically detect which renderer you should be using. * WebGL is the preferred renderer as it is a lot faster. If webGL is not supported by @@ -9,24 +10,32 @@ * * @memberof PIXI * @function autoDetectRenderer - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @return {PIXI.WebGLRenderer|PIXI.CanvasRenderer} Returns WebGL renderer if available, otherwise CanvasRenderer */ -export function autoDetectRenderer(width = 800, height = 600, options, noWebGL) +export function autoDetectRenderer(options, arg1, arg2, arg3) { - if (!noWebGL && utils.isWebGLSupported()) + // Backward-compatible support for noWebGL option + let forceCanvas = options && options.forceCanvas; + + if (arg3 !== undefined) { - return new WebGLRenderer(width, height, options); + forceCanvas = arg3; } - return new CanvasRenderer(width, height, options); + if (!forceCanvas && utils.isWebGLSupported()) + { + return new WebGLRenderer(options, arg1, arg2); + } + + return new CanvasRenderer(options, arg1, arg2); } diff --git a/src/core/const.js b/src/core/const.js index b9e99ae..2c68dee 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -341,3 +341,27 @@ LINEAR_VERTICAL: 0, LINEAR_HORIZONTAL: 1, }; + +/** + * Represents the update priorities used by internal PIXI classes when registered with + * the {@link PIXI.ticker.Ticker} object. Higher priority items are updated first and lower + * priority items, such as render, should go later. + * + * @static + * @constant + * @name UPDATE_PRIORITY + * @memberof PIXI + * @type {object} + * @property {number} INTERACTION=50 Highest priority, used for {@link PIXI.interaction.InteractionManager} + * @property {number} HIGH=25 High priority updating, {@link PIXI.VideoBaseTexture} and {@link PIXI.extras.AnimatedSprite} + * @property {number} NORMAL=0 Default priority for ticker events, see {@link PIXI.ticker.Ticker#add}. + * @property {number} LOW=-25 Low priority used for {@link PIXI.Application} rendering. + * @property {number} UTILITY=-50 Lowest priority used for {@link PIXI.prepare.BasePrepare} utility. + */ +export const UPDATE_PRIORITY = { + INTERACTION: 50, + HIGH: 25, + NORMAL: 0, + LOW: -25, + UTILITY: -50, +}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 4e6c77d..decb42a 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -13,7 +13,6 @@ * * @class * @extends EventEmitter - * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ export default class DisplayObject extends EventEmitter @@ -113,6 +112,29 @@ * @private */ this._mask = null; + + /** + * If the object has been destroyed via destroy(). If true, it should not be used. + * + * @member {boolean} + * @private + * @readonly + */ + this._destroyed = false; + + /** + * Fired when this DisplayObject is added to a Container. + * + * @event PIXI.DisplayObject#added + * @param {PIXI.Container} container - The container added to. + */ + + /** + * Fired when this DisplayObject is removed from a Container. + * + * @event PIXI.DisplayObject#removed + * @param {PIXI.Container} container - The container removed from. + */ } /** @@ -403,6 +425,8 @@ this.interactive = false; this.interactiveChildren = false; + + this._destroyed = true; } /** diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index aafdfe2..f787c4a 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -792,8 +792,8 @@ sprite.worldAlpha = this.worldAlpha * sprite.alpha; sprite.blendMode = this.blendMode; - sprite.texture._frame.width = rect.width; - sprite.texture._frame.height = rect.height; + sprite._texture._frame.width = rect.width; + sprite._texture._frame.height = rect.height; sprite.transform.worldTransform = this.transform.worldTransform; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index a24c775..b9f192d 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -19,11 +19,12 @@ */ export default class SystemRenderer extends EventEmitter { + // eslint-disable-next-line valid-jsdoc /** * @param {string} system - The name of the system this renderer is for. - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -37,27 +38,31 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(system, screenWidth, screenHeight, options) + constructor(system, options, arg2, arg3) { super(); sayHello(system); - // prepare options - if (options) + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') { - for (const i in settings.RENDER_OPTIONS) - { - if (typeof options[i] === 'undefined') - { - options[i] = settings.RENDER_OPTIONS[i]; - } - } + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); } - else - { - options = settings.RENDER_OPTIONS; - } + + // Add the default render options + options = Object.assign({}, settings.RENDER_OPTIONS, options); + + /** + * The supplied constructor options. + * + * @member {Object} + * @readOnly + */ + this.options = options; /** * The type of the renderer. @@ -75,7 +80,7 @@ * * @member {PIXI.Rectangle} */ - this.screen = new Rectangle(0, 0, screenWidth || 800, screenHeight || 600); + this.screen = new Rectangle(0, 0, options.width, options.height); /** * The canvas element that everything is drawn to @@ -279,6 +284,8 @@ this.blendModes = null; + this.options = null; + this.preserveDrawingBuffer = false; this.clearBeforeRender = false; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index e84e49c..691caf0 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -17,10 +17,11 @@ */ export default class CanvasRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -34,9 +35,9 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('Canvas', screenWidth, screenHeight, options); + super('Canvas', options, arg2, arg3); this.type = RENDERER_TYPE.CANVAS; @@ -96,7 +97,19 @@ this.context = null; this.renderingToScreen = false; - this.resize(screenWidth, screenHeight); + this.resize(this.options.width, this.options.height); + + /** + * Fired after rendering finishes. + * + * @event PIXI.CanvasRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.CanvasRenderer#prerender + */ } /** @@ -299,4 +312,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.CanvasRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.CanvasExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.CanvasPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.CanvasRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index e1cf7cd..f6fe123 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -31,11 +31,12 @@ */ export default class WebGLRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** * - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -55,9 +56,9 @@ * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('WebGL', screenWidth, screenHeight, options); + super('WebGL', options, arg2, arg3); /** * The type of this renderer as a standardised const @@ -89,12 +90,6 @@ }; - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ this._backgroundColorRgba[3] = this.transparent ? 0 : 1; this.globalUniforms = new UniformGroup({ @@ -117,6 +112,13 @@ this.initPlugins(); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ if(options.context) { this.context.initFromContext(options.context); @@ -169,6 +171,25 @@ } return this; + + /** + * Fired after rendering finishes. + * + * @event PIXI.WebGLRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.WebGLRenderer#prerender + */ + + /** + * Fired when the WebGL context is set. + * + * @event PIXI.WebGLRenderer#context + * @param {WebGLRenderingContext} gl - WebGL context. + */ } /** @@ -295,4 +316,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.WebGLRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.WebGLRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(WebGLRenderer); diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index 4eb6128..fa3d883 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -47,6 +47,14 @@ * @member {boolean} */ this.enabled = true; + + /** + * If enabled, pixi will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; } /** diff --git a/src/core/renderers/webgl/systems/ContextSystem.js b/src/core/renderers/webgl/systems/ContextSystem.js index ff958c7..8df85ea 100644 --- a/src/core/renderers/webgl/systems/ContextSystem.js +++ b/src/core/renderers/webgl/systems/ContextSystem.js @@ -75,7 +75,8 @@ */ createContext(canvas, options) { - var gl = canvas.getContext('webgl', options) || + var gl = //canvas.getContext('webgl2', options) || + canvas.getContext('webgl', options) || canvas.getContext('experimental-webgl', options); if (!gl) diff --git a/src/core/renderers/webgl/systems/FilterSystem.js b/src/core/renderers/webgl/systems/FilterSystem.js index f47335d..37e28d2 100644 --- a/src/core/renderers/webgl/systems/FilterSystem.js +++ b/src/core/renderers/webgl/systems/FilterSystem.js @@ -111,7 +111,7 @@ // TODO we should fit the rect around the transform.. } - else + else if (filters[0].autoFit) { sourceFrame.fit(filterData.stack[0].destinationFrame); } diff --git a/src/core/renderers/webgl/systems/shader/shader/mapType.js b/src/core/renderers/webgl/systems/shader/shader/mapType.js index 581dace..917538a 100644 --- a/src/core/renderers/webgl/systems/shader/shader/mapType.js +++ b/src/core/renderers/webgl/systems/shader/shader/mapType.js @@ -15,7 +15,7 @@ } } - return GL_TABLE[type]; + return GL_TABLE[type]; }; var GL_TABLE = null; diff --git a/src/core/settings.js b/src/core/settings.js index 18c5877..38ef45c 100644 --- a/src/core/settings.js +++ b/src/core/settings.js @@ -2,6 +2,15 @@ import canUploadSameBuffer from './utils/canUploadSameBuffer'; /** + * User's customizable globals for overriding the default PIXI settings, such + * as a renderer's default resolution, framerate, float percision, etc. + * @example + * // Use the native window resolution as the default resolution + * // will support high-density displays when rendering + * PIXI.settings.RESOLUTION = window.devicePixelRatio. + * + * // Disable interpolation when scaling, will make texture be pixelated + * PIXI.settings.SCALE_MODE = PIXI.SCALE_MODES.NEAREST; * @namespace PIXI.settings */ export default { @@ -101,6 +110,9 @@ * @property {boolean} clearBeforeRender=true * @property {boolean} preserveDrawingBuffer=false * @property {boolean} roundPixels=false + * @property {number} width=800 + * @property {number} height=600 + * @property {boolean} legacy=false */ RENDER_OPTIONS: { view: null, @@ -112,6 +124,9 @@ clearBeforeRender: true, preserveDrawingBuffer: false, roundPixels: false, + width: 800, + height: 600, + legacy: false, }, /** diff --git a/src/core/shader/Program.js b/src/core/shader/Program.js index c0b538b..07b4398 100644 --- a/src/core/shader/Program.js +++ b/src/core/shader/Program.js @@ -1,6 +1,6 @@ import extractUniformsFromSrc from './extractUniformsFromSrc'; import generateUniformsSync from './generateUniformsSync'; -import glCore from 'pixi-gl-core'; +import shaderUtils from '../renderers/webgl/systems/shader/shader'; import { ProgramCache } from '../utils'; import getTestContext from '../utils/getTestContext'; @@ -64,14 +64,17 @@ } else { - vertexSrc = glCore.shader.setPrecision(vertexSrc, 'mediump'); - fragmentSrc = glCore.shader.setPrecision(fragmentSrc, 'mediump'); + vertexSrc = shaderUtils.setPrecision(vertexSrc, 'mediump'); + fragmentSrc = shaderUtils.setPrecision(fragmentSrc, 'mediump'); - const program = glCore.shader.compileProgram(gl, vertexSrc, fragmentSrc); + const program = shaderUtils.compileProgram(gl, vertexSrc, fragmentSrc); + console.log("<<<<<<>>><<"); this.attributeData = this.getAttributeData(program, gl); this.uniformData = this.getUniformData(program, gl); + console.log(this.uniformData); + //gl.deleteProgram(program); } } @@ -95,13 +98,13 @@ for (let i = 0; i < totalAttributes; i++) { const attribData = gl.getActiveAttrib(program, i); - const type = glCore.shader.mapType(gl, attribData.type); + const type = shaderUtils.mapType(gl, attribData.type); /*eslint-disable */ const data = { type: type, name: attribData.name, - size: glCore.shader.mapSize(type), + size: shaderUtils.mapSize(type), location: 0, }; /*eslint-enable */ @@ -137,13 +140,14 @@ // TODO expose this as a prop? // const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); - const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); + //const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); for (let i = 0; i < totalUniforms; i++) { const uniformData = gl.getActiveUniform(program, i); const name = uniformData.name.replace(/\[.*?\]/, ''); - const type = glCore.shader.mapType(gl, uniformData.type); + const type = shaderUtils.mapType(gl, uniformData.type); + console.log('mapping ' + uniformData.type + ' to ' + type); // if (!name.match(maskRegex)) { @@ -151,7 +155,7 @@ uniforms[name] = { type: type, size: uniformData.size, - value: glCore.shader.defaultValue(type, uniformData.size), + value: shaderUtils.defaultValue(type, uniformData.size), }; /*eslint-enable */ } diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index e387e99..b57d095 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -147,12 +147,12 @@ // so if _width is 0 then width was not set.. if (this._width) { - this.scale.x = sign(this.scale.x) * this._width / this.texture.orig.width; + this.scale.x = sign(this.scale.x) * this._width / this._texture.orig.width; } if (this._height) { - this.scale.y = sign(this.scale.y) * this._height / this.texture.orig.height; + this.scale.y = sign(this.scale.y) * this._height / this._texture.orig.height; } } diff --git a/src/core/sprites/canvas/CanvasTinter.js b/src/core/sprites/canvas/CanvasTinter.js index 27e5e0c..fd9de5d 100644 --- a/src/core/sprites/canvas/CanvasTinter.js +++ b/src/core/sprites/canvas/CanvasTinter.js @@ -18,7 +18,7 @@ */ getTintedTexture: (sprite, color) => { - const texture = sprite.texture; + const texture = sprite._texture; color = CanvasTinter.roundColor(color); diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 5a4edda..8b9b797 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -344,6 +344,7 @@ const texture = this._texture; const style = this._style; + const padding = style.trim ? 0 : style.padding; texture.baseTexture.valid = true; texture.baseTexture.resolution = this.resolution; @@ -353,11 +354,11 @@ texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - texture.trim.x = -style.padding; - texture.trim.y = -style.padding; + texture.trim.x = -padding; + texture.trim.y = -padding; - texture.orig.width = texture._frame.width - (style.padding * 2); - texture.orig.height = texture._frame.height - (style.padding * 2); + texture.orig.width = texture._frame.width - (padding * 2); + texture.orig.height = texture._frame.height - (padding * 2); // call sprite onTextureUpdate to update scale if _width or _height were set this._onTextureUpdate(); diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index 853da3d..8a9c34b 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -114,6 +114,38 @@ this.cacheId = null; this.validate(); + + /** + * Fired when a not-immediately-available source finishes loading. + * + * @protected + * @event PIXI.BaseTexture#loaded + * @param {PIXI.BaseTexture} baseTexture - Resource loaded. + */ + + /** + * Fired when a not-immediately-available source fails to load. + * + * @protected + * @event PIXI.BaseTexture#error + * @param {PIXI.BaseTexture} baseTexture - Resource errored. + */ + + /** + * Fired when BaseTexture is updated. + * + * @protected + * @event PIXI.BaseTexture#update + * @param {PIXI.BaseTexture} baseTexture - Instance of texture being updated. + */ + + /** + * Fired when BaseTexture is destroyed. + * + * @protected + * @event PIXI.BaseTexture#dispose + * @param {PIXI.BaseTexture} baseTexture - Instance of texture being destroyed. + */ } setResource(resource) diff --git a/src/core/textures/Spritesheet.js b/src/core/textures/Spritesheet.js index d469c62..9b24f27 100644 --- a/src/core/textures/Spritesheet.js +++ b/src/core/textures/Spritesheet.js @@ -208,6 +208,7 @@ // lets also add the frame to pixi's global cache for fromFrame and fromImage functions TextureCache[i] = this.textures[i]; + this.textures[i].textureCacheId = i; } frameIndex++; diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 0a2c379..2213087 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -147,9 +147,9 @@ /** * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. * - * @event update - * @memberof PIXI.Texture# + * @event PIXI.Texture#update * @protected + * @param {PIXI.Texture} texture - Instance of texture being updated. */ this._updateID = 0; @@ -159,6 +159,15 @@ * @type {Object} */ this.transform = null; + + /** + * The id under which this Texture has been added to the texture cache. This is + * automatically set in certain cases, but may not always be accurate, particularly if + * the texture is in the cache under multiple ids. + * + * @member {string} + */ + this.textureCacheId = null; } /** @@ -225,11 +234,17 @@ this._uvs = null; this.trim = null; this.orig = null; + this.textureCacheId = null; this.valid = false; - this.off('dispose', this.dispose, this); - this.off('update', this.update, this); + for (const prop in TextureCache) + { + if (TextureCache[prop] === this) + { + delete TextureCache[prop]; + } + } } /** @@ -315,14 +330,15 @@ */ static fromLoader(source, imageUrl, name) { - // console.log('added from loader...') + // console.log('added from loader...') const resource = new ImageResource(source);//.from(imageUrl, crossorigin);// document.createElement('img'); - // console.log('base resource ' + resource.width); + // console.log('base resource ' + resource.width); const baseTexture = new BaseTexture(resource, settings.SCALE_MODE, getResolutionOfUrl(imageUrl)); + /// console.log('base width ' + baseTexture.width); const texture = new Texture(baseTexture); @@ -335,6 +351,7 @@ // lets also add the frame to pixi's global cache for fromFrame and fromImage fucntions BaseTextureCache[name] = baseTexture; TextureCache[name] = texture; + texture.textureCacheId = name; // also add references by url if they are different. if (name !== imageUrl) @@ -355,6 +372,10 @@ */ static addTextureToCache(texture, id) { + if (!texture.textureCacheId) + { + texture.textureCacheId = id; + } TextureCache[id] = texture; } @@ -495,6 +516,7 @@ */ Texture.EMPTY = new Texture(new BaseTexture()); removeAllHandlers(Texture.EMPTY); +removeAllHandlers(Texture.EMPTY.baseTexture); /** * A white texture of 10x10 size, used for graphics and other things @@ -505,3 +527,4 @@ */ Texture.WHITE = createWhiteTexture(); removeAllHandlers(Texture.WHITE); +removeAllHandlers(Texture.WHITE.baseTexture); diff --git a/.travis.yml b/.travis.yml index 3df8b22..14de334 100644 --- a/.travis.yml +++ b/.travis.yml @@ -110,3 +110,43 @@ on: all_branches: true condition: $TRAVIS_TAG + # Deploy config for latest release + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: dist + upload-dir: "release" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: docs + upload-dir: "release/docs" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: coverage + upload-dir: "release/coverage" + on: + all_branches: true + condition: $TRAVIS_TAG \ No newline at end of file diff --git a/README.md b/README.md index d26fadd..3b85f85 100644 --- a/README.md +++ b/README.md @@ -36,22 +36,14 @@ - Wiki: Other misc tutorials and resources are [on the Wiki](https://github.com/pixijs/pixi.js/wiki/Resources). ### Community ### -- Forums: Check out the [forum] (http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow] (http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. +- Forums: Check out the [forum](http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow](http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. - Inspiration: Check out the [gallery](http://www.pixijs.com/gallery) to see some of the amazing things people have created! - Chat: You can join us on [Gitter](https://gitter.im/pixijs/pixi.js) To chat about Pixi. We also now have a Slack channel. If you would like to join it please Send me an email (mat@goodboydigital.com) and I will invite you in. ### Setup ### -It's easy to get started with Pixi.js! Simply grab the pre-built versions from here: - -Release Branch - Nice and stable Pixi.js -- Unminified: [http://pixijs.download/release/pixi.js] -- Minified: [http://pixijs.download/release/pixi.min.js] - -Develop Branch - The bleeding edge version of Pixi.js -- Unminified: [http://pixijs.download/dev/pixi.js] -- Minified: [http://pixijs.download/dev/pixi.min.js] +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. @@ -70,10 +62,10 @@ #### CDN Install (via cdnjs) ```html - + ``` -_Note: `4.2.2` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ +_Note: `4.4.3` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ ### Demos ### @@ -130,7 +122,7 @@ // and the root stage PIXI.Container. var app = new PIXI.Application(); -// The application will create a canvas element for you that you +// The application will create a canvas element for you that you // can then insert into the DOM. document.body.appendChild(app.view); @@ -150,7 +142,7 @@ // Add the bunny to the scene we are building. app.stage.addChild(bunny); - + // Listen for frame updates app.ticker.add(function() { // each frame we spin the bunny around a bit diff --git a/bower.json b/bower.json deleted file mode 100644 index 41b668c..0000000 --- a/bower.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "pixi.js", - "main": "dist/pixi.min.js", - "ignore": [ - "**/.*", - "docs", - "node_modules", - "bower_components", - "test", - "scripts", - "bower.json", - "inch.json" - ], - "dependencies": { - }, - "devDependencies": { - } -} diff --git a/package.json b/package.json index d35cbb2..308469f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.3.5", + "version": "4.4.4", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ @@ -40,11 +40,11 @@ "lib": "babel src --out-dir lib -s", "predocs": "rimraf docs/**", "docs": "jsdoc -c scripts/jsdoc.conf.json -R README.md", - "publish:patch": "npm version patch --no-git-tag-version && npm publish", - "publish:minor": "npm version minor --no-git-tag-version && npm publish", - "publish:major": "npm version major --no-git-tag-version && npm publish", + "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", - "postpublish": "node scripts/release.js" + "postpublish": "git push && git push --tags" }, "files": [ "dist/", @@ -72,7 +72,6 @@ "electron": "^1.4.15", "eslint": "^3.5.0", "floss": "^2.0.1", - "gh-pages": "^0.11.0", "jaguarjs-jsdoc": "^1.0.1", "js-md5": "^0.4.1", "jsdoc": "^3.4.2", diff --git a/scripts/jsdoc.conf.json b/scripts/jsdoc.conf.json index 553e61c..3f52180 100644 --- a/scripts/jsdoc.conf.json +++ b/scripts/jsdoc.conf.json @@ -43,7 +43,7 @@ }, "markdown" : { "parser" : "gfm", - "hardwrap" : true + "hardwrap" : false }, "opts": { "encoding" : "utf8", diff --git a/scripts/release.js b/scripts/release.js deleted file mode 100644 index 156620c..0000000 --- a/scripts/release.js +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env node -'use strict'; - -// Publish script to push releases of the bin files -// the normally are gitignored -const ghpages = require('gh-pages'); -const path = require('path'); -const packageInfo = require(path.join(__dirname, '..', 'package.json')); - -const options = { - src: [ - 'dist/**/*', - 'lib/**/*', - 'src/**/*', - 'scripts/*', - 'scripts/renders/*', - 'test/**/*', - '*.json', - '*.md', - 'LICENSE', - '.eslintrc', - '.editorconfig', - '.travis.yml', - '.babelrc', - ], - dotfiles: true, - branch: 'release', - message: packageInfo.version, - logger: console.log.bind(console), -}; - -ghpages.publish(process.cwd(), options, (err) => -{ - if (err) - { - console.log(err); - process.exit(1); - - return; - } - - process.exit(0); -}); diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index f16bcb1..220427a 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -21,7 +21,7 @@ const DIV_HOOK_ZINDEX = 2; /** - * The Accessibility manager reacreates the ability to tab and and have content read by screen + * The Accessibility manager recreates the ability to tab and and have content read by screen * readers. This is very important as it can possibly help people with disabilities access pixi * content. * diff --git a/src/accessibility/index.js b/src/accessibility/index.js index 568cfb1..0ae108f 100644 --- a/src/accessibility/index.js +++ b/src/accessibility/index.js @@ -1,4 +1,9 @@ /** + * This namespace contains a renderer plugin for interaction accessibility for end-users + * with physical impairments which require screen-renders, keyboard navigation, etc. + * + * Do not instantiate this plugin directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. * @namespace PIXI.accessibility */ export { default as accessibleTarget } from './accessibleTarget'; diff --git a/src/core/Application.js b/src/core/Application.js index 5340578..2fcc729 100644 --- a/src/core/Application.js +++ b/src/core/Application.js @@ -1,6 +1,8 @@ import { autoDetectRenderer } from './autoDetectRenderer'; import Container from './display/Container'; import { shared, Ticker } from './ticker'; +import settings from './settings'; +import { UPDATE_PRIORITY } from './const'; /** * Convenience class to create a new PIXI application. @@ -22,28 +24,52 @@ */ export default class Application { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experience unexplained flickering try setting this to true. - * @param {boolean} [useSharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedLoader=false] - `true` to use PIXI.loaders.shared, `false` to create new Loader. */ - constructor(width, height, options, noWebGL, useSharedTicker = false) + constructor(options, arg2, arg3, arg4, arg5) { + // Support for constructor(width, height, options, noWebGL, useSharedTicker) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + forceCanvas: !!arg4, + sharedTicker: !!arg5, + }, arg3); + } + + /** + * The default options, so we mixin functionality later. + * @member {object} + * @protected + */ + this._options = options = Object.assign({ + sharedTicker: false, + forceCanvas: false, + sharedLoader: false, + }, options); + /** * WebGL renderer if available, otherwise CanvasRenderer * @member {PIXI.WebGLRenderer|PIXI.CanvasRenderer} */ - this.renderer = autoDetectRenderer(width, height, options, noWebGL); + this.renderer = autoDetectRenderer(options); /** * The root display container that's rendered. @@ -63,7 +89,7 @@ * @member {PIXI.ticker.Ticker} * @default PIXI.ticker.shared */ - this.ticker = useSharedTicker ? shared : new Ticker(); + this.ticker = options.sharedTicker ? shared : new Ticker(); // Start the rendering this.start(); @@ -78,7 +104,7 @@ this._ticker = ticker; if (ticker) { - ticker.add(this.render, this); + ticker.add(this.render, this, UPDATE_PRIORITY.LOW); } } get ticker() // eslint-disable-line require-jsdoc @@ -136,13 +162,18 @@ */ destroy(removeView) { - this.stop(); + const oldTicker = this._ticker; + this.ticker = null; + oldTicker.destroy(); + this.stage.destroy(); this.stage = null; this.renderer.destroy(removeView); this.renderer = null; + + this._options = null; } } diff --git a/src/core/autoDetectRenderer.js b/src/core/autoDetectRenderer.js index 8960425..9ec0364 100644 --- a/src/core/autoDetectRenderer.js +++ b/src/core/autoDetectRenderer.js @@ -2,6 +2,7 @@ import CanvasRenderer from './renderers/canvas/CanvasRenderer'; import WebGLRenderer from './renderers/webgl/WebGLRenderer'; +// eslint-disable-next-line valid-jsdoc /** * This helper function will automatically detect which renderer you should be using. * WebGL is the preferred renderer as it is a lot faster. If webGL is not supported by @@ -9,24 +10,32 @@ * * @memberof PIXI * @function autoDetectRenderer - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @return {PIXI.WebGLRenderer|PIXI.CanvasRenderer} Returns WebGL renderer if available, otherwise CanvasRenderer */ -export function autoDetectRenderer(width = 800, height = 600, options, noWebGL) +export function autoDetectRenderer(options, arg1, arg2, arg3) { - if (!noWebGL && utils.isWebGLSupported()) + // Backward-compatible support for noWebGL option + let forceCanvas = options && options.forceCanvas; + + if (arg3 !== undefined) { - return new WebGLRenderer(width, height, options); + forceCanvas = arg3; } - return new CanvasRenderer(width, height, options); + if (!forceCanvas && utils.isWebGLSupported()) + { + return new WebGLRenderer(options, arg1, arg2); + } + + return new CanvasRenderer(options, arg1, arg2); } diff --git a/src/core/const.js b/src/core/const.js index b9e99ae..2c68dee 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -341,3 +341,27 @@ LINEAR_VERTICAL: 0, LINEAR_HORIZONTAL: 1, }; + +/** + * Represents the update priorities used by internal PIXI classes when registered with + * the {@link PIXI.ticker.Ticker} object. Higher priority items are updated first and lower + * priority items, such as render, should go later. + * + * @static + * @constant + * @name UPDATE_PRIORITY + * @memberof PIXI + * @type {object} + * @property {number} INTERACTION=50 Highest priority, used for {@link PIXI.interaction.InteractionManager} + * @property {number} HIGH=25 High priority updating, {@link PIXI.VideoBaseTexture} and {@link PIXI.extras.AnimatedSprite} + * @property {number} NORMAL=0 Default priority for ticker events, see {@link PIXI.ticker.Ticker#add}. + * @property {number} LOW=-25 Low priority used for {@link PIXI.Application} rendering. + * @property {number} UTILITY=-50 Lowest priority used for {@link PIXI.prepare.BasePrepare} utility. + */ +export const UPDATE_PRIORITY = { + INTERACTION: 50, + HIGH: 25, + NORMAL: 0, + LOW: -25, + UTILITY: -50, +}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 4e6c77d..decb42a 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -13,7 +13,6 @@ * * @class * @extends EventEmitter - * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ export default class DisplayObject extends EventEmitter @@ -113,6 +112,29 @@ * @private */ this._mask = null; + + /** + * If the object has been destroyed via destroy(). If true, it should not be used. + * + * @member {boolean} + * @private + * @readonly + */ + this._destroyed = false; + + /** + * Fired when this DisplayObject is added to a Container. + * + * @event PIXI.DisplayObject#added + * @param {PIXI.Container} container - The container added to. + */ + + /** + * Fired when this DisplayObject is removed from a Container. + * + * @event PIXI.DisplayObject#removed + * @param {PIXI.Container} container - The container removed from. + */ } /** @@ -403,6 +425,8 @@ this.interactive = false; this.interactiveChildren = false; + + this._destroyed = true; } /** diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index aafdfe2..f787c4a 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -792,8 +792,8 @@ sprite.worldAlpha = this.worldAlpha * sprite.alpha; sprite.blendMode = this.blendMode; - sprite.texture._frame.width = rect.width; - sprite.texture._frame.height = rect.height; + sprite._texture._frame.width = rect.width; + sprite._texture._frame.height = rect.height; sprite.transform.worldTransform = this.transform.worldTransform; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index a24c775..b9f192d 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -19,11 +19,12 @@ */ export default class SystemRenderer extends EventEmitter { + // eslint-disable-next-line valid-jsdoc /** * @param {string} system - The name of the system this renderer is for. - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -37,27 +38,31 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(system, screenWidth, screenHeight, options) + constructor(system, options, arg2, arg3) { super(); sayHello(system); - // prepare options - if (options) + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') { - for (const i in settings.RENDER_OPTIONS) - { - if (typeof options[i] === 'undefined') - { - options[i] = settings.RENDER_OPTIONS[i]; - } - } + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); } - else - { - options = settings.RENDER_OPTIONS; - } + + // Add the default render options + options = Object.assign({}, settings.RENDER_OPTIONS, options); + + /** + * The supplied constructor options. + * + * @member {Object} + * @readOnly + */ + this.options = options; /** * The type of the renderer. @@ -75,7 +80,7 @@ * * @member {PIXI.Rectangle} */ - this.screen = new Rectangle(0, 0, screenWidth || 800, screenHeight || 600); + this.screen = new Rectangle(0, 0, options.width, options.height); /** * The canvas element that everything is drawn to @@ -279,6 +284,8 @@ this.blendModes = null; + this.options = null; + this.preserveDrawingBuffer = false; this.clearBeforeRender = false; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index e84e49c..691caf0 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -17,10 +17,11 @@ */ export default class CanvasRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -34,9 +35,9 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('Canvas', screenWidth, screenHeight, options); + super('Canvas', options, arg2, arg3); this.type = RENDERER_TYPE.CANVAS; @@ -96,7 +97,19 @@ this.context = null; this.renderingToScreen = false; - this.resize(screenWidth, screenHeight); + this.resize(this.options.width, this.options.height); + + /** + * Fired after rendering finishes. + * + * @event PIXI.CanvasRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.CanvasRenderer#prerender + */ } /** @@ -299,4 +312,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.CanvasRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.CanvasExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.CanvasPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.CanvasRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index e1cf7cd..f6fe123 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -31,11 +31,12 @@ */ export default class WebGLRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** * - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -55,9 +56,9 @@ * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('WebGL', screenWidth, screenHeight, options); + super('WebGL', options, arg2, arg3); /** * The type of this renderer as a standardised const @@ -89,12 +90,6 @@ }; - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ this._backgroundColorRgba[3] = this.transparent ? 0 : 1; this.globalUniforms = new UniformGroup({ @@ -117,6 +112,13 @@ this.initPlugins(); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ if(options.context) { this.context.initFromContext(options.context); @@ -169,6 +171,25 @@ } return this; + + /** + * Fired after rendering finishes. + * + * @event PIXI.WebGLRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.WebGLRenderer#prerender + */ + + /** + * Fired when the WebGL context is set. + * + * @event PIXI.WebGLRenderer#context + * @param {WebGLRenderingContext} gl - WebGL context. + */ } /** @@ -295,4 +316,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.WebGLRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.WebGLRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(WebGLRenderer); diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index 4eb6128..fa3d883 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -47,6 +47,14 @@ * @member {boolean} */ this.enabled = true; + + /** + * If enabled, pixi will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; } /** diff --git a/src/core/renderers/webgl/systems/ContextSystem.js b/src/core/renderers/webgl/systems/ContextSystem.js index ff958c7..8df85ea 100644 --- a/src/core/renderers/webgl/systems/ContextSystem.js +++ b/src/core/renderers/webgl/systems/ContextSystem.js @@ -75,7 +75,8 @@ */ createContext(canvas, options) { - var gl = canvas.getContext('webgl', options) || + var gl = //canvas.getContext('webgl2', options) || + canvas.getContext('webgl', options) || canvas.getContext('experimental-webgl', options); if (!gl) diff --git a/src/core/renderers/webgl/systems/FilterSystem.js b/src/core/renderers/webgl/systems/FilterSystem.js index f47335d..37e28d2 100644 --- a/src/core/renderers/webgl/systems/FilterSystem.js +++ b/src/core/renderers/webgl/systems/FilterSystem.js @@ -111,7 +111,7 @@ // TODO we should fit the rect around the transform.. } - else + else if (filters[0].autoFit) { sourceFrame.fit(filterData.stack[0].destinationFrame); } diff --git a/src/core/renderers/webgl/systems/shader/shader/mapType.js b/src/core/renderers/webgl/systems/shader/shader/mapType.js index 581dace..917538a 100644 --- a/src/core/renderers/webgl/systems/shader/shader/mapType.js +++ b/src/core/renderers/webgl/systems/shader/shader/mapType.js @@ -15,7 +15,7 @@ } } - return GL_TABLE[type]; + return GL_TABLE[type]; }; var GL_TABLE = null; diff --git a/src/core/settings.js b/src/core/settings.js index 18c5877..38ef45c 100644 --- a/src/core/settings.js +++ b/src/core/settings.js @@ -2,6 +2,15 @@ import canUploadSameBuffer from './utils/canUploadSameBuffer'; /** + * User's customizable globals for overriding the default PIXI settings, such + * as a renderer's default resolution, framerate, float percision, etc. + * @example + * // Use the native window resolution as the default resolution + * // will support high-density displays when rendering + * PIXI.settings.RESOLUTION = window.devicePixelRatio. + * + * // Disable interpolation when scaling, will make texture be pixelated + * PIXI.settings.SCALE_MODE = PIXI.SCALE_MODES.NEAREST; * @namespace PIXI.settings */ export default { @@ -101,6 +110,9 @@ * @property {boolean} clearBeforeRender=true * @property {boolean} preserveDrawingBuffer=false * @property {boolean} roundPixels=false + * @property {number} width=800 + * @property {number} height=600 + * @property {boolean} legacy=false */ RENDER_OPTIONS: { view: null, @@ -112,6 +124,9 @@ clearBeforeRender: true, preserveDrawingBuffer: false, roundPixels: false, + width: 800, + height: 600, + legacy: false, }, /** diff --git a/src/core/shader/Program.js b/src/core/shader/Program.js index c0b538b..07b4398 100644 --- a/src/core/shader/Program.js +++ b/src/core/shader/Program.js @@ -1,6 +1,6 @@ import extractUniformsFromSrc from './extractUniformsFromSrc'; import generateUniformsSync from './generateUniformsSync'; -import glCore from 'pixi-gl-core'; +import shaderUtils from '../renderers/webgl/systems/shader/shader'; import { ProgramCache } from '../utils'; import getTestContext from '../utils/getTestContext'; @@ -64,14 +64,17 @@ } else { - vertexSrc = glCore.shader.setPrecision(vertexSrc, 'mediump'); - fragmentSrc = glCore.shader.setPrecision(fragmentSrc, 'mediump'); + vertexSrc = shaderUtils.setPrecision(vertexSrc, 'mediump'); + fragmentSrc = shaderUtils.setPrecision(fragmentSrc, 'mediump'); - const program = glCore.shader.compileProgram(gl, vertexSrc, fragmentSrc); + const program = shaderUtils.compileProgram(gl, vertexSrc, fragmentSrc); + console.log("<<<<<<>>><<"); this.attributeData = this.getAttributeData(program, gl); this.uniformData = this.getUniformData(program, gl); + console.log(this.uniformData); + //gl.deleteProgram(program); } } @@ -95,13 +98,13 @@ for (let i = 0; i < totalAttributes; i++) { const attribData = gl.getActiveAttrib(program, i); - const type = glCore.shader.mapType(gl, attribData.type); + const type = shaderUtils.mapType(gl, attribData.type); /*eslint-disable */ const data = { type: type, name: attribData.name, - size: glCore.shader.mapSize(type), + size: shaderUtils.mapSize(type), location: 0, }; /*eslint-enable */ @@ -137,13 +140,14 @@ // TODO expose this as a prop? // const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); - const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); + //const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); for (let i = 0; i < totalUniforms; i++) { const uniformData = gl.getActiveUniform(program, i); const name = uniformData.name.replace(/\[.*?\]/, ''); - const type = glCore.shader.mapType(gl, uniformData.type); + const type = shaderUtils.mapType(gl, uniformData.type); + console.log('mapping ' + uniformData.type + ' to ' + type); // if (!name.match(maskRegex)) { @@ -151,7 +155,7 @@ uniforms[name] = { type: type, size: uniformData.size, - value: glCore.shader.defaultValue(type, uniformData.size), + value: shaderUtils.defaultValue(type, uniformData.size), }; /*eslint-enable */ } diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index e387e99..b57d095 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -147,12 +147,12 @@ // so if _width is 0 then width was not set.. if (this._width) { - this.scale.x = sign(this.scale.x) * this._width / this.texture.orig.width; + this.scale.x = sign(this.scale.x) * this._width / this._texture.orig.width; } if (this._height) { - this.scale.y = sign(this.scale.y) * this._height / this.texture.orig.height; + this.scale.y = sign(this.scale.y) * this._height / this._texture.orig.height; } } diff --git a/src/core/sprites/canvas/CanvasTinter.js b/src/core/sprites/canvas/CanvasTinter.js index 27e5e0c..fd9de5d 100644 --- a/src/core/sprites/canvas/CanvasTinter.js +++ b/src/core/sprites/canvas/CanvasTinter.js @@ -18,7 +18,7 @@ */ getTintedTexture: (sprite, color) => { - const texture = sprite.texture; + const texture = sprite._texture; color = CanvasTinter.roundColor(color); diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 5a4edda..8b9b797 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -344,6 +344,7 @@ const texture = this._texture; const style = this._style; + const padding = style.trim ? 0 : style.padding; texture.baseTexture.valid = true; texture.baseTexture.resolution = this.resolution; @@ -353,11 +354,11 @@ texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - texture.trim.x = -style.padding; - texture.trim.y = -style.padding; + texture.trim.x = -padding; + texture.trim.y = -padding; - texture.orig.width = texture._frame.width - (style.padding * 2); - texture.orig.height = texture._frame.height - (style.padding * 2); + texture.orig.width = texture._frame.width - (padding * 2); + texture.orig.height = texture._frame.height - (padding * 2); // call sprite onTextureUpdate to update scale if _width or _height were set this._onTextureUpdate(); diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index 853da3d..8a9c34b 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -114,6 +114,38 @@ this.cacheId = null; this.validate(); + + /** + * Fired when a not-immediately-available source finishes loading. + * + * @protected + * @event PIXI.BaseTexture#loaded + * @param {PIXI.BaseTexture} baseTexture - Resource loaded. + */ + + /** + * Fired when a not-immediately-available source fails to load. + * + * @protected + * @event PIXI.BaseTexture#error + * @param {PIXI.BaseTexture} baseTexture - Resource errored. + */ + + /** + * Fired when BaseTexture is updated. + * + * @protected + * @event PIXI.BaseTexture#update + * @param {PIXI.BaseTexture} baseTexture - Instance of texture being updated. + */ + + /** + * Fired when BaseTexture is destroyed. + * + * @protected + * @event PIXI.BaseTexture#dispose + * @param {PIXI.BaseTexture} baseTexture - Instance of texture being destroyed. + */ } setResource(resource) diff --git a/src/core/textures/Spritesheet.js b/src/core/textures/Spritesheet.js index d469c62..9b24f27 100644 --- a/src/core/textures/Spritesheet.js +++ b/src/core/textures/Spritesheet.js @@ -208,6 +208,7 @@ // lets also add the frame to pixi's global cache for fromFrame and fromImage functions TextureCache[i] = this.textures[i]; + this.textures[i].textureCacheId = i; } frameIndex++; diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 0a2c379..2213087 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -147,9 +147,9 @@ /** * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. * - * @event update - * @memberof PIXI.Texture# + * @event PIXI.Texture#update * @protected + * @param {PIXI.Texture} texture - Instance of texture being updated. */ this._updateID = 0; @@ -159,6 +159,15 @@ * @type {Object} */ this.transform = null; + + /** + * The id under which this Texture has been added to the texture cache. This is + * automatically set in certain cases, but may not always be accurate, particularly if + * the texture is in the cache under multiple ids. + * + * @member {string} + */ + this.textureCacheId = null; } /** @@ -225,11 +234,17 @@ this._uvs = null; this.trim = null; this.orig = null; + this.textureCacheId = null; this.valid = false; - this.off('dispose', this.dispose, this); - this.off('update', this.update, this); + for (const prop in TextureCache) + { + if (TextureCache[prop] === this) + { + delete TextureCache[prop]; + } + } } /** @@ -315,14 +330,15 @@ */ static fromLoader(source, imageUrl, name) { - // console.log('added from loader...') + // console.log('added from loader...') const resource = new ImageResource(source);//.from(imageUrl, crossorigin);// document.createElement('img'); - // console.log('base resource ' + resource.width); + // console.log('base resource ' + resource.width); const baseTexture = new BaseTexture(resource, settings.SCALE_MODE, getResolutionOfUrl(imageUrl)); + /// console.log('base width ' + baseTexture.width); const texture = new Texture(baseTexture); @@ -335,6 +351,7 @@ // lets also add the frame to pixi's global cache for fromFrame and fromImage fucntions BaseTextureCache[name] = baseTexture; TextureCache[name] = texture; + texture.textureCacheId = name; // also add references by url if they are different. if (name !== imageUrl) @@ -355,6 +372,10 @@ */ static addTextureToCache(texture, id) { + if (!texture.textureCacheId) + { + texture.textureCacheId = id; + } TextureCache[id] = texture; } @@ -495,6 +516,7 @@ */ Texture.EMPTY = new Texture(new BaseTexture()); removeAllHandlers(Texture.EMPTY); +removeAllHandlers(Texture.EMPTY.baseTexture); /** * A white texture of 10x10 size, used for graphics and other things @@ -505,3 +527,4 @@ */ Texture.WHITE = createWhiteTexture(); removeAllHandlers(Texture.WHITE); +removeAllHandlers(Texture.WHITE.baseTexture); diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index 59eb9fd..a9b9f51 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -1,6 +1,7 @@ import BaseTexture from './BaseTexture'; import { uid, BaseTextureCache } from '../utils'; -import * as ticker from '../ticker'; +import { shared } from '../ticker'; +import { UPDATE_PRIORITY } from '../const'; /** * A texture of a [playing] Video. @@ -125,7 +126,7 @@ if (!this._isAutoUpdating && this.autoUpdate) { - ticker.shared.add(this.update, this); + shared.add(this.update, this, UPDATE_PRIORITY.HIGH); this._isAutoUpdating = true; } } @@ -139,7 +140,7 @@ { if (this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); this._isAutoUpdating = false; } } @@ -187,7 +188,7 @@ { if (this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); } if (this.source && this.source._pixiId) @@ -281,12 +282,12 @@ if (!this._autoUpdate && this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); this._isAutoUpdating = false; } else if (this._autoUpdate && !this._isAutoUpdating) { - ticker.shared.add(this.update, this); + shared.add(this.update, this, UPDATE_PRIORITY.HIGH); this._isAutoUpdating = true; } } diff --git a/.travis.yml b/.travis.yml index 3df8b22..14de334 100644 --- a/.travis.yml +++ b/.travis.yml @@ -110,3 +110,43 @@ on: all_branches: true condition: $TRAVIS_TAG + # Deploy config for latest release + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: dist + upload-dir: "release" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: docs + upload-dir: "release/docs" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: coverage + upload-dir: "release/coverage" + on: + all_branches: true + condition: $TRAVIS_TAG \ No newline at end of file diff --git a/README.md b/README.md index d26fadd..3b85f85 100644 --- a/README.md +++ b/README.md @@ -36,22 +36,14 @@ - Wiki: Other misc tutorials and resources are [on the Wiki](https://github.com/pixijs/pixi.js/wiki/Resources). ### Community ### -- Forums: Check out the [forum] (http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow] (http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. +- Forums: Check out the [forum](http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow](http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. - Inspiration: Check out the [gallery](http://www.pixijs.com/gallery) to see some of the amazing things people have created! - Chat: You can join us on [Gitter](https://gitter.im/pixijs/pixi.js) To chat about Pixi. We also now have a Slack channel. If you would like to join it please Send me an email (mat@goodboydigital.com) and I will invite you in. ### Setup ### -It's easy to get started with Pixi.js! Simply grab the pre-built versions from here: - -Release Branch - Nice and stable Pixi.js -- Unminified: [http://pixijs.download/release/pixi.js] -- Minified: [http://pixijs.download/release/pixi.min.js] - -Develop Branch - The bleeding edge version of Pixi.js -- Unminified: [http://pixijs.download/dev/pixi.js] -- Minified: [http://pixijs.download/dev/pixi.min.js] +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. @@ -70,10 +62,10 @@ #### CDN Install (via cdnjs) ```html - + ``` -_Note: `4.2.2` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ +_Note: `4.4.3` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ ### Demos ### @@ -130,7 +122,7 @@ // and the root stage PIXI.Container. var app = new PIXI.Application(); -// The application will create a canvas element for you that you +// The application will create a canvas element for you that you // can then insert into the DOM. document.body.appendChild(app.view); @@ -150,7 +142,7 @@ // Add the bunny to the scene we are building. app.stage.addChild(bunny); - + // Listen for frame updates app.ticker.add(function() { // each frame we spin the bunny around a bit diff --git a/bower.json b/bower.json deleted file mode 100644 index 41b668c..0000000 --- a/bower.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "pixi.js", - "main": "dist/pixi.min.js", - "ignore": [ - "**/.*", - "docs", - "node_modules", - "bower_components", - "test", - "scripts", - "bower.json", - "inch.json" - ], - "dependencies": { - }, - "devDependencies": { - } -} diff --git a/package.json b/package.json index d35cbb2..308469f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.3.5", + "version": "4.4.4", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ @@ -40,11 +40,11 @@ "lib": "babel src --out-dir lib -s", "predocs": "rimraf docs/**", "docs": "jsdoc -c scripts/jsdoc.conf.json -R README.md", - "publish:patch": "npm version patch --no-git-tag-version && npm publish", - "publish:minor": "npm version minor --no-git-tag-version && npm publish", - "publish:major": "npm version major --no-git-tag-version && npm publish", + "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", - "postpublish": "node scripts/release.js" + "postpublish": "git push && git push --tags" }, "files": [ "dist/", @@ -72,7 +72,6 @@ "electron": "^1.4.15", "eslint": "^3.5.0", "floss": "^2.0.1", - "gh-pages": "^0.11.0", "jaguarjs-jsdoc": "^1.0.1", "js-md5": "^0.4.1", "jsdoc": "^3.4.2", diff --git a/scripts/jsdoc.conf.json b/scripts/jsdoc.conf.json index 553e61c..3f52180 100644 --- a/scripts/jsdoc.conf.json +++ b/scripts/jsdoc.conf.json @@ -43,7 +43,7 @@ }, "markdown" : { "parser" : "gfm", - "hardwrap" : true + "hardwrap" : false }, "opts": { "encoding" : "utf8", diff --git a/scripts/release.js b/scripts/release.js deleted file mode 100644 index 156620c..0000000 --- a/scripts/release.js +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env node -'use strict'; - -// Publish script to push releases of the bin files -// the normally are gitignored -const ghpages = require('gh-pages'); -const path = require('path'); -const packageInfo = require(path.join(__dirname, '..', 'package.json')); - -const options = { - src: [ - 'dist/**/*', - 'lib/**/*', - 'src/**/*', - 'scripts/*', - 'scripts/renders/*', - 'test/**/*', - '*.json', - '*.md', - 'LICENSE', - '.eslintrc', - '.editorconfig', - '.travis.yml', - '.babelrc', - ], - dotfiles: true, - branch: 'release', - message: packageInfo.version, - logger: console.log.bind(console), -}; - -ghpages.publish(process.cwd(), options, (err) => -{ - if (err) - { - console.log(err); - process.exit(1); - - return; - } - - process.exit(0); -}); diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index f16bcb1..220427a 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -21,7 +21,7 @@ const DIV_HOOK_ZINDEX = 2; /** - * The Accessibility manager reacreates the ability to tab and and have content read by screen + * The Accessibility manager recreates the ability to tab and and have content read by screen * readers. This is very important as it can possibly help people with disabilities access pixi * content. * diff --git a/src/accessibility/index.js b/src/accessibility/index.js index 568cfb1..0ae108f 100644 --- a/src/accessibility/index.js +++ b/src/accessibility/index.js @@ -1,4 +1,9 @@ /** + * This namespace contains a renderer plugin for interaction accessibility for end-users + * with physical impairments which require screen-renders, keyboard navigation, etc. + * + * Do not instantiate this plugin directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. * @namespace PIXI.accessibility */ export { default as accessibleTarget } from './accessibleTarget'; diff --git a/src/core/Application.js b/src/core/Application.js index 5340578..2fcc729 100644 --- a/src/core/Application.js +++ b/src/core/Application.js @@ -1,6 +1,8 @@ import { autoDetectRenderer } from './autoDetectRenderer'; import Container from './display/Container'; import { shared, Ticker } from './ticker'; +import settings from './settings'; +import { UPDATE_PRIORITY } from './const'; /** * Convenience class to create a new PIXI application. @@ -22,28 +24,52 @@ */ export default class Application { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experience unexplained flickering try setting this to true. - * @param {boolean} [useSharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedLoader=false] - `true` to use PIXI.loaders.shared, `false` to create new Loader. */ - constructor(width, height, options, noWebGL, useSharedTicker = false) + constructor(options, arg2, arg3, arg4, arg5) { + // Support for constructor(width, height, options, noWebGL, useSharedTicker) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + forceCanvas: !!arg4, + sharedTicker: !!arg5, + }, arg3); + } + + /** + * The default options, so we mixin functionality later. + * @member {object} + * @protected + */ + this._options = options = Object.assign({ + sharedTicker: false, + forceCanvas: false, + sharedLoader: false, + }, options); + /** * WebGL renderer if available, otherwise CanvasRenderer * @member {PIXI.WebGLRenderer|PIXI.CanvasRenderer} */ - this.renderer = autoDetectRenderer(width, height, options, noWebGL); + this.renderer = autoDetectRenderer(options); /** * The root display container that's rendered. @@ -63,7 +89,7 @@ * @member {PIXI.ticker.Ticker} * @default PIXI.ticker.shared */ - this.ticker = useSharedTicker ? shared : new Ticker(); + this.ticker = options.sharedTicker ? shared : new Ticker(); // Start the rendering this.start(); @@ -78,7 +104,7 @@ this._ticker = ticker; if (ticker) { - ticker.add(this.render, this); + ticker.add(this.render, this, UPDATE_PRIORITY.LOW); } } get ticker() // eslint-disable-line require-jsdoc @@ -136,13 +162,18 @@ */ destroy(removeView) { - this.stop(); + const oldTicker = this._ticker; + this.ticker = null; + oldTicker.destroy(); + this.stage.destroy(); this.stage = null; this.renderer.destroy(removeView); this.renderer = null; + + this._options = null; } } diff --git a/src/core/autoDetectRenderer.js b/src/core/autoDetectRenderer.js index 8960425..9ec0364 100644 --- a/src/core/autoDetectRenderer.js +++ b/src/core/autoDetectRenderer.js @@ -2,6 +2,7 @@ import CanvasRenderer from './renderers/canvas/CanvasRenderer'; import WebGLRenderer from './renderers/webgl/WebGLRenderer'; +// eslint-disable-next-line valid-jsdoc /** * This helper function will automatically detect which renderer you should be using. * WebGL is the preferred renderer as it is a lot faster. If webGL is not supported by @@ -9,24 +10,32 @@ * * @memberof PIXI * @function autoDetectRenderer - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @return {PIXI.WebGLRenderer|PIXI.CanvasRenderer} Returns WebGL renderer if available, otherwise CanvasRenderer */ -export function autoDetectRenderer(width = 800, height = 600, options, noWebGL) +export function autoDetectRenderer(options, arg1, arg2, arg3) { - if (!noWebGL && utils.isWebGLSupported()) + // Backward-compatible support for noWebGL option + let forceCanvas = options && options.forceCanvas; + + if (arg3 !== undefined) { - return new WebGLRenderer(width, height, options); + forceCanvas = arg3; } - return new CanvasRenderer(width, height, options); + if (!forceCanvas && utils.isWebGLSupported()) + { + return new WebGLRenderer(options, arg1, arg2); + } + + return new CanvasRenderer(options, arg1, arg2); } diff --git a/src/core/const.js b/src/core/const.js index b9e99ae..2c68dee 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -341,3 +341,27 @@ LINEAR_VERTICAL: 0, LINEAR_HORIZONTAL: 1, }; + +/** + * Represents the update priorities used by internal PIXI classes when registered with + * the {@link PIXI.ticker.Ticker} object. Higher priority items are updated first and lower + * priority items, such as render, should go later. + * + * @static + * @constant + * @name UPDATE_PRIORITY + * @memberof PIXI + * @type {object} + * @property {number} INTERACTION=50 Highest priority, used for {@link PIXI.interaction.InteractionManager} + * @property {number} HIGH=25 High priority updating, {@link PIXI.VideoBaseTexture} and {@link PIXI.extras.AnimatedSprite} + * @property {number} NORMAL=0 Default priority for ticker events, see {@link PIXI.ticker.Ticker#add}. + * @property {number} LOW=-25 Low priority used for {@link PIXI.Application} rendering. + * @property {number} UTILITY=-50 Lowest priority used for {@link PIXI.prepare.BasePrepare} utility. + */ +export const UPDATE_PRIORITY = { + INTERACTION: 50, + HIGH: 25, + NORMAL: 0, + LOW: -25, + UTILITY: -50, +}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 4e6c77d..decb42a 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -13,7 +13,6 @@ * * @class * @extends EventEmitter - * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ export default class DisplayObject extends EventEmitter @@ -113,6 +112,29 @@ * @private */ this._mask = null; + + /** + * If the object has been destroyed via destroy(). If true, it should not be used. + * + * @member {boolean} + * @private + * @readonly + */ + this._destroyed = false; + + /** + * Fired when this DisplayObject is added to a Container. + * + * @event PIXI.DisplayObject#added + * @param {PIXI.Container} container - The container added to. + */ + + /** + * Fired when this DisplayObject is removed from a Container. + * + * @event PIXI.DisplayObject#removed + * @param {PIXI.Container} container - The container removed from. + */ } /** @@ -403,6 +425,8 @@ this.interactive = false; this.interactiveChildren = false; + + this._destroyed = true; } /** diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index aafdfe2..f787c4a 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -792,8 +792,8 @@ sprite.worldAlpha = this.worldAlpha * sprite.alpha; sprite.blendMode = this.blendMode; - sprite.texture._frame.width = rect.width; - sprite.texture._frame.height = rect.height; + sprite._texture._frame.width = rect.width; + sprite._texture._frame.height = rect.height; sprite.transform.worldTransform = this.transform.worldTransform; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index a24c775..b9f192d 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -19,11 +19,12 @@ */ export default class SystemRenderer extends EventEmitter { + // eslint-disable-next-line valid-jsdoc /** * @param {string} system - The name of the system this renderer is for. - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -37,27 +38,31 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(system, screenWidth, screenHeight, options) + constructor(system, options, arg2, arg3) { super(); sayHello(system); - // prepare options - if (options) + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') { - for (const i in settings.RENDER_OPTIONS) - { - if (typeof options[i] === 'undefined') - { - options[i] = settings.RENDER_OPTIONS[i]; - } - } + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); } - else - { - options = settings.RENDER_OPTIONS; - } + + // Add the default render options + options = Object.assign({}, settings.RENDER_OPTIONS, options); + + /** + * The supplied constructor options. + * + * @member {Object} + * @readOnly + */ + this.options = options; /** * The type of the renderer. @@ -75,7 +80,7 @@ * * @member {PIXI.Rectangle} */ - this.screen = new Rectangle(0, 0, screenWidth || 800, screenHeight || 600); + this.screen = new Rectangle(0, 0, options.width, options.height); /** * The canvas element that everything is drawn to @@ -279,6 +284,8 @@ this.blendModes = null; + this.options = null; + this.preserveDrawingBuffer = false; this.clearBeforeRender = false; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index e84e49c..691caf0 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -17,10 +17,11 @@ */ export default class CanvasRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -34,9 +35,9 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('Canvas', screenWidth, screenHeight, options); + super('Canvas', options, arg2, arg3); this.type = RENDERER_TYPE.CANVAS; @@ -96,7 +97,19 @@ this.context = null; this.renderingToScreen = false; - this.resize(screenWidth, screenHeight); + this.resize(this.options.width, this.options.height); + + /** + * Fired after rendering finishes. + * + * @event PIXI.CanvasRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.CanvasRenderer#prerender + */ } /** @@ -299,4 +312,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.CanvasRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.CanvasExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.CanvasPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.CanvasRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index e1cf7cd..f6fe123 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -31,11 +31,12 @@ */ export default class WebGLRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** * - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -55,9 +56,9 @@ * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('WebGL', screenWidth, screenHeight, options); + super('WebGL', options, arg2, arg3); /** * The type of this renderer as a standardised const @@ -89,12 +90,6 @@ }; - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ this._backgroundColorRgba[3] = this.transparent ? 0 : 1; this.globalUniforms = new UniformGroup({ @@ -117,6 +112,13 @@ this.initPlugins(); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ if(options.context) { this.context.initFromContext(options.context); @@ -169,6 +171,25 @@ } return this; + + /** + * Fired after rendering finishes. + * + * @event PIXI.WebGLRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.WebGLRenderer#prerender + */ + + /** + * Fired when the WebGL context is set. + * + * @event PIXI.WebGLRenderer#context + * @param {WebGLRenderingContext} gl - WebGL context. + */ } /** @@ -295,4 +316,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.WebGLRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.WebGLRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(WebGLRenderer); diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index 4eb6128..fa3d883 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -47,6 +47,14 @@ * @member {boolean} */ this.enabled = true; + + /** + * If enabled, pixi will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; } /** diff --git a/src/core/renderers/webgl/systems/ContextSystem.js b/src/core/renderers/webgl/systems/ContextSystem.js index ff958c7..8df85ea 100644 --- a/src/core/renderers/webgl/systems/ContextSystem.js +++ b/src/core/renderers/webgl/systems/ContextSystem.js @@ -75,7 +75,8 @@ */ createContext(canvas, options) { - var gl = canvas.getContext('webgl', options) || + var gl = //canvas.getContext('webgl2', options) || + canvas.getContext('webgl', options) || canvas.getContext('experimental-webgl', options); if (!gl) diff --git a/src/core/renderers/webgl/systems/FilterSystem.js b/src/core/renderers/webgl/systems/FilterSystem.js index f47335d..37e28d2 100644 --- a/src/core/renderers/webgl/systems/FilterSystem.js +++ b/src/core/renderers/webgl/systems/FilterSystem.js @@ -111,7 +111,7 @@ // TODO we should fit the rect around the transform.. } - else + else if (filters[0].autoFit) { sourceFrame.fit(filterData.stack[0].destinationFrame); } diff --git a/src/core/renderers/webgl/systems/shader/shader/mapType.js b/src/core/renderers/webgl/systems/shader/shader/mapType.js index 581dace..917538a 100644 --- a/src/core/renderers/webgl/systems/shader/shader/mapType.js +++ b/src/core/renderers/webgl/systems/shader/shader/mapType.js @@ -15,7 +15,7 @@ } } - return GL_TABLE[type]; + return GL_TABLE[type]; }; var GL_TABLE = null; diff --git a/src/core/settings.js b/src/core/settings.js index 18c5877..38ef45c 100644 --- a/src/core/settings.js +++ b/src/core/settings.js @@ -2,6 +2,15 @@ import canUploadSameBuffer from './utils/canUploadSameBuffer'; /** + * User's customizable globals for overriding the default PIXI settings, such + * as a renderer's default resolution, framerate, float percision, etc. + * @example + * // Use the native window resolution as the default resolution + * // will support high-density displays when rendering + * PIXI.settings.RESOLUTION = window.devicePixelRatio. + * + * // Disable interpolation when scaling, will make texture be pixelated + * PIXI.settings.SCALE_MODE = PIXI.SCALE_MODES.NEAREST; * @namespace PIXI.settings */ export default { @@ -101,6 +110,9 @@ * @property {boolean} clearBeforeRender=true * @property {boolean} preserveDrawingBuffer=false * @property {boolean} roundPixels=false + * @property {number} width=800 + * @property {number} height=600 + * @property {boolean} legacy=false */ RENDER_OPTIONS: { view: null, @@ -112,6 +124,9 @@ clearBeforeRender: true, preserveDrawingBuffer: false, roundPixels: false, + width: 800, + height: 600, + legacy: false, }, /** diff --git a/src/core/shader/Program.js b/src/core/shader/Program.js index c0b538b..07b4398 100644 --- a/src/core/shader/Program.js +++ b/src/core/shader/Program.js @@ -1,6 +1,6 @@ import extractUniformsFromSrc from './extractUniformsFromSrc'; import generateUniformsSync from './generateUniformsSync'; -import glCore from 'pixi-gl-core'; +import shaderUtils from '../renderers/webgl/systems/shader/shader'; import { ProgramCache } from '../utils'; import getTestContext from '../utils/getTestContext'; @@ -64,14 +64,17 @@ } else { - vertexSrc = glCore.shader.setPrecision(vertexSrc, 'mediump'); - fragmentSrc = glCore.shader.setPrecision(fragmentSrc, 'mediump'); + vertexSrc = shaderUtils.setPrecision(vertexSrc, 'mediump'); + fragmentSrc = shaderUtils.setPrecision(fragmentSrc, 'mediump'); - const program = glCore.shader.compileProgram(gl, vertexSrc, fragmentSrc); + const program = shaderUtils.compileProgram(gl, vertexSrc, fragmentSrc); + console.log("<<<<<<>>><<"); this.attributeData = this.getAttributeData(program, gl); this.uniformData = this.getUniformData(program, gl); + console.log(this.uniformData); + //gl.deleteProgram(program); } } @@ -95,13 +98,13 @@ for (let i = 0; i < totalAttributes; i++) { const attribData = gl.getActiveAttrib(program, i); - const type = glCore.shader.mapType(gl, attribData.type); + const type = shaderUtils.mapType(gl, attribData.type); /*eslint-disable */ const data = { type: type, name: attribData.name, - size: glCore.shader.mapSize(type), + size: shaderUtils.mapSize(type), location: 0, }; /*eslint-enable */ @@ -137,13 +140,14 @@ // TODO expose this as a prop? // const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); - const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); + //const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); for (let i = 0; i < totalUniforms; i++) { const uniformData = gl.getActiveUniform(program, i); const name = uniformData.name.replace(/\[.*?\]/, ''); - const type = glCore.shader.mapType(gl, uniformData.type); + const type = shaderUtils.mapType(gl, uniformData.type); + console.log('mapping ' + uniformData.type + ' to ' + type); // if (!name.match(maskRegex)) { @@ -151,7 +155,7 @@ uniforms[name] = { type: type, size: uniformData.size, - value: glCore.shader.defaultValue(type, uniformData.size), + value: shaderUtils.defaultValue(type, uniformData.size), }; /*eslint-enable */ } diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index e387e99..b57d095 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -147,12 +147,12 @@ // so if _width is 0 then width was not set.. if (this._width) { - this.scale.x = sign(this.scale.x) * this._width / this.texture.orig.width; + this.scale.x = sign(this.scale.x) * this._width / this._texture.orig.width; } if (this._height) { - this.scale.y = sign(this.scale.y) * this._height / this.texture.orig.height; + this.scale.y = sign(this.scale.y) * this._height / this._texture.orig.height; } } diff --git a/src/core/sprites/canvas/CanvasTinter.js b/src/core/sprites/canvas/CanvasTinter.js index 27e5e0c..fd9de5d 100644 --- a/src/core/sprites/canvas/CanvasTinter.js +++ b/src/core/sprites/canvas/CanvasTinter.js @@ -18,7 +18,7 @@ */ getTintedTexture: (sprite, color) => { - const texture = sprite.texture; + const texture = sprite._texture; color = CanvasTinter.roundColor(color); diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 5a4edda..8b9b797 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -344,6 +344,7 @@ const texture = this._texture; const style = this._style; + const padding = style.trim ? 0 : style.padding; texture.baseTexture.valid = true; texture.baseTexture.resolution = this.resolution; @@ -353,11 +354,11 @@ texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - texture.trim.x = -style.padding; - texture.trim.y = -style.padding; + texture.trim.x = -padding; + texture.trim.y = -padding; - texture.orig.width = texture._frame.width - (style.padding * 2); - texture.orig.height = texture._frame.height - (style.padding * 2); + texture.orig.width = texture._frame.width - (padding * 2); + texture.orig.height = texture._frame.height - (padding * 2); // call sprite onTextureUpdate to update scale if _width or _height were set this._onTextureUpdate(); diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index 853da3d..8a9c34b 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -114,6 +114,38 @@ this.cacheId = null; this.validate(); + + /** + * Fired when a not-immediately-available source finishes loading. + * + * @protected + * @event PIXI.BaseTexture#loaded + * @param {PIXI.BaseTexture} baseTexture - Resource loaded. + */ + + /** + * Fired when a not-immediately-available source fails to load. + * + * @protected + * @event PIXI.BaseTexture#error + * @param {PIXI.BaseTexture} baseTexture - Resource errored. + */ + + /** + * Fired when BaseTexture is updated. + * + * @protected + * @event PIXI.BaseTexture#update + * @param {PIXI.BaseTexture} baseTexture - Instance of texture being updated. + */ + + /** + * Fired when BaseTexture is destroyed. + * + * @protected + * @event PIXI.BaseTexture#dispose + * @param {PIXI.BaseTexture} baseTexture - Instance of texture being destroyed. + */ } setResource(resource) diff --git a/src/core/textures/Spritesheet.js b/src/core/textures/Spritesheet.js index d469c62..9b24f27 100644 --- a/src/core/textures/Spritesheet.js +++ b/src/core/textures/Spritesheet.js @@ -208,6 +208,7 @@ // lets also add the frame to pixi's global cache for fromFrame and fromImage functions TextureCache[i] = this.textures[i]; + this.textures[i].textureCacheId = i; } frameIndex++; diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 0a2c379..2213087 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -147,9 +147,9 @@ /** * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. * - * @event update - * @memberof PIXI.Texture# + * @event PIXI.Texture#update * @protected + * @param {PIXI.Texture} texture - Instance of texture being updated. */ this._updateID = 0; @@ -159,6 +159,15 @@ * @type {Object} */ this.transform = null; + + /** + * The id under which this Texture has been added to the texture cache. This is + * automatically set in certain cases, but may not always be accurate, particularly if + * the texture is in the cache under multiple ids. + * + * @member {string} + */ + this.textureCacheId = null; } /** @@ -225,11 +234,17 @@ this._uvs = null; this.trim = null; this.orig = null; + this.textureCacheId = null; this.valid = false; - this.off('dispose', this.dispose, this); - this.off('update', this.update, this); + for (const prop in TextureCache) + { + if (TextureCache[prop] === this) + { + delete TextureCache[prop]; + } + } } /** @@ -315,14 +330,15 @@ */ static fromLoader(source, imageUrl, name) { - // console.log('added from loader...') + // console.log('added from loader...') const resource = new ImageResource(source);//.from(imageUrl, crossorigin);// document.createElement('img'); - // console.log('base resource ' + resource.width); + // console.log('base resource ' + resource.width); const baseTexture = new BaseTexture(resource, settings.SCALE_MODE, getResolutionOfUrl(imageUrl)); + /// console.log('base width ' + baseTexture.width); const texture = new Texture(baseTexture); @@ -335,6 +351,7 @@ // lets also add the frame to pixi's global cache for fromFrame and fromImage fucntions BaseTextureCache[name] = baseTexture; TextureCache[name] = texture; + texture.textureCacheId = name; // also add references by url if they are different. if (name !== imageUrl) @@ -355,6 +372,10 @@ */ static addTextureToCache(texture, id) { + if (!texture.textureCacheId) + { + texture.textureCacheId = id; + } TextureCache[id] = texture; } @@ -495,6 +516,7 @@ */ Texture.EMPTY = new Texture(new BaseTexture()); removeAllHandlers(Texture.EMPTY); +removeAllHandlers(Texture.EMPTY.baseTexture); /** * A white texture of 10x10 size, used for graphics and other things @@ -505,3 +527,4 @@ */ Texture.WHITE = createWhiteTexture(); removeAllHandlers(Texture.WHITE); +removeAllHandlers(Texture.WHITE.baseTexture); diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index 59eb9fd..a9b9f51 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -1,6 +1,7 @@ import BaseTexture from './BaseTexture'; import { uid, BaseTextureCache } from '../utils'; -import * as ticker from '../ticker'; +import { shared } from '../ticker'; +import { UPDATE_PRIORITY } from '../const'; /** * A texture of a [playing] Video. @@ -125,7 +126,7 @@ if (!this._isAutoUpdating && this.autoUpdate) { - ticker.shared.add(this.update, this); + shared.add(this.update, this, UPDATE_PRIORITY.HIGH); this._isAutoUpdating = true; } } @@ -139,7 +140,7 @@ { if (this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); this._isAutoUpdating = false; } } @@ -187,7 +188,7 @@ { if (this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); } if (this.source && this.source._pixiId) @@ -281,12 +282,12 @@ if (!this._autoUpdate && this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); this._isAutoUpdating = false; } else if (this._autoUpdate && !this._isAutoUpdating) { - ticker.shared.add(this.update, this); + shared.add(this.update, this, UPDATE_PRIORITY.HIGH); this._isAutoUpdating = true; } } diff --git a/src/core/ticker/Ticker.js b/src/core/ticker/Ticker.js index 17a1517..40df138 100644 --- a/src/core/ticker/Ticker.js +++ b/src/core/ticker/Ticker.js @@ -1,12 +1,10 @@ import settings from '../settings'; -import EventEmitter from 'eventemitter3'; - -// Internal event used by composed emitter -const TICK = 'tick'; +import { UPDATE_PRIORITY } from '../const'; +import TickerListener from './TickerListener'; /** * A Ticker class that runs an update loop that other objects listen to. - * This class is composed around an EventEmitter object to add listeners + * This class is composed around listeners * meant for execution on the next requested animation frame. * Animation frames are requested only when necessary, * e.g. When the ticker is started and the emitter has listeners. @@ -22,10 +20,11 @@ constructor() { /** - * Internal emitter used to fire 'tick' event + * The first listener. All new listeners added are chained on this. * @private + * @type {TickerListener} */ - this._emitter = new EventEmitter(); + this._head = new TickerListener(null, null, Infinity); /** * Internal current frame request ID @@ -131,7 +130,7 @@ // Invoke listeners now this.update(time); // Listener side effects may have modified ticker state. - if (this.started && this._requestId === null && this._emitter.listeners(TICK, true)) + if (this.started && this._requestId === null && this._head.next) { this._requestId = requestAnimationFrame(this._tick); } @@ -148,7 +147,7 @@ */ _requestIfNeeded() { - if (this._requestId === null && this._emitter.listeners(TICK, true)) + if (this._requestId === null && this._head.next) { // ensure callbacks get correct delta this.lastTime = performance.now(); @@ -193,35 +192,72 @@ } /** - * Calls {@link module:eventemitter3.EventEmitter#on} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. + * Register a handler for tick events. Calls continuously unless + * it is removed or the ticker is stopped. * * @param {Function} fn - The listener function to be added for updates * @param {Function} [context] - The listener context + * @param {number} [priority=PIXI.UPDATE_PRIORITY.NORMAL] - The priority for emitting * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - add(fn, context) + add(fn, context, priority = UPDATE_PRIORITY.NORMAL) { - this._emitter.on(TICK, fn, context); - - this._startIfPossible(); - - return this; + return this._addListener(new TickerListener(fn, context, priority)); } /** - * Calls {@link module:eventemitter3.EventEmitter#once} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. + * Add a handler for the tick event which is only execute once. * * @param {Function} fn - The listener function to be added for one update * @param {Function} [context] - The listener context + * @param {number} [priority=PIXI.UPDATE_PRIORITY.NORMAL] - The priority for emitting * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - addOnce(fn, context) + addOnce(fn, context, priority = UPDATE_PRIORITY.NORMAL) { - this._emitter.once(TICK, fn, context); + return this._addListener(new TickerListener(fn, context, priority, true)); + } + + /** + * Internally adds the event handler so that it can be sorted by priority. + * Priority allows certain handler (user, AnimatedSprite, Interaction) to be run + * before the rendering. + * + * @private + * @param {TickerListener} listener - Current listener being added. + * @returns {PIXI.ticker.Ticker} This instance of a ticker + */ + _addListener(listener) + { + // For attaching to head + let current = this._head.next; + let previous = this._head; + + // Add the first item + if (!current) + { + listener.connect(previous); + } + else + { + // Go from highest to lowest priority + while (current) + { + if (listener.priority >= current.priority) + { + listener.connect(previous); + break; + } + previous = current; + current = current.next; + } + + // Not yet connected + if (!listener.previous) + { + listener.connect(previous); + } + } this._startIfPossible(); @@ -229,19 +265,33 @@ } /** - * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. - * It checks if the emitter has listeners for 'tick' event. - * If it does, then it cancels the animation frame. + * Removes any handlers matching the function and context parameters. + * If no handlers are left after removing, then it cancels the animation frame. * - * @param {Function} [fn] - The listener function to be removed + * @param {Function} fn - The listener function to be removed * @param {Function} [context] - The listener context to be removed * @returns {PIXI.ticker.Ticker} This instance of a ticker */ remove(fn, context) { - this._emitter.off(TICK, fn, context); + let listener = this._head.next; - if (!this._emitter.listeners(TICK, true)) + while (listener) + { + // We found a match, lets remove it + // no break to delete all possible matches + // incase a listener was added 2+ times + if (listener.match(fn, context)) + { + listener = listener.destroy(); + } + else + { + listener = listener.next; + } + } + + if (!this._head.next) { this._cancelIfNeeded(); } @@ -276,6 +326,25 @@ } /** + * Destroy the ticker and don't use after this. Calling + * this method removes all references to internal events. + */ + destroy() + { + this.stop(); + + let listener = this._head.next; + + while (listener) + { + listener = listener.destroy(true); + } + + this._head.destroy(); + this._head = null; + } + + /** * Triggers an update. An update entails setting the * current {@link PIXI.ticker.Ticker#elapsedMS}, * the current {@link PIXI.ticker.Ticker#deltaTime}, @@ -320,8 +389,22 @@ this.deltaTime = elapsedMS * settings.TARGET_FPMS * this.speed; + // Cache a local reference, in-case ticker is destroyed + // during the emit, we can still check for head.next + const head = this._head; + // Invoke listeners added to internal emitter - this._emitter.emit(TICK, this.deltaTime); + let listener = head.next; + + while (listener) + { + listener = listener.emit(this.deltaTime); + } + + if (!head.next) + { + this._cancelIfNeeded(); + } } else { diff --git a/.travis.yml b/.travis.yml index 3df8b22..14de334 100644 --- a/.travis.yml +++ b/.travis.yml @@ -110,3 +110,43 @@ on: all_branches: true condition: $TRAVIS_TAG + # Deploy config for latest release + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: dist + upload-dir: "release" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: docs + upload-dir: "release/docs" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: coverage + upload-dir: "release/coverage" + on: + all_branches: true + condition: $TRAVIS_TAG \ No newline at end of file diff --git a/README.md b/README.md index d26fadd..3b85f85 100644 --- a/README.md +++ b/README.md @@ -36,22 +36,14 @@ - Wiki: Other misc tutorials and resources are [on the Wiki](https://github.com/pixijs/pixi.js/wiki/Resources). ### Community ### -- Forums: Check out the [forum] (http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow] (http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. +- Forums: Check out the [forum](http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow](http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. - Inspiration: Check out the [gallery](http://www.pixijs.com/gallery) to see some of the amazing things people have created! - Chat: You can join us on [Gitter](https://gitter.im/pixijs/pixi.js) To chat about Pixi. We also now have a Slack channel. If you would like to join it please Send me an email (mat@goodboydigital.com) and I will invite you in. ### Setup ### -It's easy to get started with Pixi.js! Simply grab the pre-built versions from here: - -Release Branch - Nice and stable Pixi.js -- Unminified: [http://pixijs.download/release/pixi.js] -- Minified: [http://pixijs.download/release/pixi.min.js] - -Develop Branch - The bleeding edge version of Pixi.js -- Unminified: [http://pixijs.download/dev/pixi.js] -- Minified: [http://pixijs.download/dev/pixi.min.js] +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. @@ -70,10 +62,10 @@ #### CDN Install (via cdnjs) ```html - + ``` -_Note: `4.2.2` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ +_Note: `4.4.3` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ ### Demos ### @@ -130,7 +122,7 @@ // and the root stage PIXI.Container. var app = new PIXI.Application(); -// The application will create a canvas element for you that you +// The application will create a canvas element for you that you // can then insert into the DOM. document.body.appendChild(app.view); @@ -150,7 +142,7 @@ // Add the bunny to the scene we are building. app.stage.addChild(bunny); - + // Listen for frame updates app.ticker.add(function() { // each frame we spin the bunny around a bit diff --git a/bower.json b/bower.json deleted file mode 100644 index 41b668c..0000000 --- a/bower.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "pixi.js", - "main": "dist/pixi.min.js", - "ignore": [ - "**/.*", - "docs", - "node_modules", - "bower_components", - "test", - "scripts", - "bower.json", - "inch.json" - ], - "dependencies": { - }, - "devDependencies": { - } -} diff --git a/package.json b/package.json index d35cbb2..308469f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.3.5", + "version": "4.4.4", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ @@ -40,11 +40,11 @@ "lib": "babel src --out-dir lib -s", "predocs": "rimraf docs/**", "docs": "jsdoc -c scripts/jsdoc.conf.json -R README.md", - "publish:patch": "npm version patch --no-git-tag-version && npm publish", - "publish:minor": "npm version minor --no-git-tag-version && npm publish", - "publish:major": "npm version major --no-git-tag-version && npm publish", + "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", - "postpublish": "node scripts/release.js" + "postpublish": "git push && git push --tags" }, "files": [ "dist/", @@ -72,7 +72,6 @@ "electron": "^1.4.15", "eslint": "^3.5.0", "floss": "^2.0.1", - "gh-pages": "^0.11.0", "jaguarjs-jsdoc": "^1.0.1", "js-md5": "^0.4.1", "jsdoc": "^3.4.2", diff --git a/scripts/jsdoc.conf.json b/scripts/jsdoc.conf.json index 553e61c..3f52180 100644 --- a/scripts/jsdoc.conf.json +++ b/scripts/jsdoc.conf.json @@ -43,7 +43,7 @@ }, "markdown" : { "parser" : "gfm", - "hardwrap" : true + "hardwrap" : false }, "opts": { "encoding" : "utf8", diff --git a/scripts/release.js b/scripts/release.js deleted file mode 100644 index 156620c..0000000 --- a/scripts/release.js +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env node -'use strict'; - -// Publish script to push releases of the bin files -// the normally are gitignored -const ghpages = require('gh-pages'); -const path = require('path'); -const packageInfo = require(path.join(__dirname, '..', 'package.json')); - -const options = { - src: [ - 'dist/**/*', - 'lib/**/*', - 'src/**/*', - 'scripts/*', - 'scripts/renders/*', - 'test/**/*', - '*.json', - '*.md', - 'LICENSE', - '.eslintrc', - '.editorconfig', - '.travis.yml', - '.babelrc', - ], - dotfiles: true, - branch: 'release', - message: packageInfo.version, - logger: console.log.bind(console), -}; - -ghpages.publish(process.cwd(), options, (err) => -{ - if (err) - { - console.log(err); - process.exit(1); - - return; - } - - process.exit(0); -}); diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index f16bcb1..220427a 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -21,7 +21,7 @@ const DIV_HOOK_ZINDEX = 2; /** - * The Accessibility manager reacreates the ability to tab and and have content read by screen + * The Accessibility manager recreates the ability to tab and and have content read by screen * readers. This is very important as it can possibly help people with disabilities access pixi * content. * diff --git a/src/accessibility/index.js b/src/accessibility/index.js index 568cfb1..0ae108f 100644 --- a/src/accessibility/index.js +++ b/src/accessibility/index.js @@ -1,4 +1,9 @@ /** + * This namespace contains a renderer plugin for interaction accessibility for end-users + * with physical impairments which require screen-renders, keyboard navigation, etc. + * + * Do not instantiate this plugin directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. * @namespace PIXI.accessibility */ export { default as accessibleTarget } from './accessibleTarget'; diff --git a/src/core/Application.js b/src/core/Application.js index 5340578..2fcc729 100644 --- a/src/core/Application.js +++ b/src/core/Application.js @@ -1,6 +1,8 @@ import { autoDetectRenderer } from './autoDetectRenderer'; import Container from './display/Container'; import { shared, Ticker } from './ticker'; +import settings from './settings'; +import { UPDATE_PRIORITY } from './const'; /** * Convenience class to create a new PIXI application. @@ -22,28 +24,52 @@ */ export default class Application { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experience unexplained flickering try setting this to true. - * @param {boolean} [useSharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedLoader=false] - `true` to use PIXI.loaders.shared, `false` to create new Loader. */ - constructor(width, height, options, noWebGL, useSharedTicker = false) + constructor(options, arg2, arg3, arg4, arg5) { + // Support for constructor(width, height, options, noWebGL, useSharedTicker) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + forceCanvas: !!arg4, + sharedTicker: !!arg5, + }, arg3); + } + + /** + * The default options, so we mixin functionality later. + * @member {object} + * @protected + */ + this._options = options = Object.assign({ + sharedTicker: false, + forceCanvas: false, + sharedLoader: false, + }, options); + /** * WebGL renderer if available, otherwise CanvasRenderer * @member {PIXI.WebGLRenderer|PIXI.CanvasRenderer} */ - this.renderer = autoDetectRenderer(width, height, options, noWebGL); + this.renderer = autoDetectRenderer(options); /** * The root display container that's rendered. @@ -63,7 +89,7 @@ * @member {PIXI.ticker.Ticker} * @default PIXI.ticker.shared */ - this.ticker = useSharedTicker ? shared : new Ticker(); + this.ticker = options.sharedTicker ? shared : new Ticker(); // Start the rendering this.start(); @@ -78,7 +104,7 @@ this._ticker = ticker; if (ticker) { - ticker.add(this.render, this); + ticker.add(this.render, this, UPDATE_PRIORITY.LOW); } } get ticker() // eslint-disable-line require-jsdoc @@ -136,13 +162,18 @@ */ destroy(removeView) { - this.stop(); + const oldTicker = this._ticker; + this.ticker = null; + oldTicker.destroy(); + this.stage.destroy(); this.stage = null; this.renderer.destroy(removeView); this.renderer = null; + + this._options = null; } } diff --git a/src/core/autoDetectRenderer.js b/src/core/autoDetectRenderer.js index 8960425..9ec0364 100644 --- a/src/core/autoDetectRenderer.js +++ b/src/core/autoDetectRenderer.js @@ -2,6 +2,7 @@ import CanvasRenderer from './renderers/canvas/CanvasRenderer'; import WebGLRenderer from './renderers/webgl/WebGLRenderer'; +// eslint-disable-next-line valid-jsdoc /** * This helper function will automatically detect which renderer you should be using. * WebGL is the preferred renderer as it is a lot faster. If webGL is not supported by @@ -9,24 +10,32 @@ * * @memberof PIXI * @function autoDetectRenderer - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @return {PIXI.WebGLRenderer|PIXI.CanvasRenderer} Returns WebGL renderer if available, otherwise CanvasRenderer */ -export function autoDetectRenderer(width = 800, height = 600, options, noWebGL) +export function autoDetectRenderer(options, arg1, arg2, arg3) { - if (!noWebGL && utils.isWebGLSupported()) + // Backward-compatible support for noWebGL option + let forceCanvas = options && options.forceCanvas; + + if (arg3 !== undefined) { - return new WebGLRenderer(width, height, options); + forceCanvas = arg3; } - return new CanvasRenderer(width, height, options); + if (!forceCanvas && utils.isWebGLSupported()) + { + return new WebGLRenderer(options, arg1, arg2); + } + + return new CanvasRenderer(options, arg1, arg2); } diff --git a/src/core/const.js b/src/core/const.js index b9e99ae..2c68dee 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -341,3 +341,27 @@ LINEAR_VERTICAL: 0, LINEAR_HORIZONTAL: 1, }; + +/** + * Represents the update priorities used by internal PIXI classes when registered with + * the {@link PIXI.ticker.Ticker} object. Higher priority items are updated first and lower + * priority items, such as render, should go later. + * + * @static + * @constant + * @name UPDATE_PRIORITY + * @memberof PIXI + * @type {object} + * @property {number} INTERACTION=50 Highest priority, used for {@link PIXI.interaction.InteractionManager} + * @property {number} HIGH=25 High priority updating, {@link PIXI.VideoBaseTexture} and {@link PIXI.extras.AnimatedSprite} + * @property {number} NORMAL=0 Default priority for ticker events, see {@link PIXI.ticker.Ticker#add}. + * @property {number} LOW=-25 Low priority used for {@link PIXI.Application} rendering. + * @property {number} UTILITY=-50 Lowest priority used for {@link PIXI.prepare.BasePrepare} utility. + */ +export const UPDATE_PRIORITY = { + INTERACTION: 50, + HIGH: 25, + NORMAL: 0, + LOW: -25, + UTILITY: -50, +}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 4e6c77d..decb42a 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -13,7 +13,6 @@ * * @class * @extends EventEmitter - * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ export default class DisplayObject extends EventEmitter @@ -113,6 +112,29 @@ * @private */ this._mask = null; + + /** + * If the object has been destroyed via destroy(). If true, it should not be used. + * + * @member {boolean} + * @private + * @readonly + */ + this._destroyed = false; + + /** + * Fired when this DisplayObject is added to a Container. + * + * @event PIXI.DisplayObject#added + * @param {PIXI.Container} container - The container added to. + */ + + /** + * Fired when this DisplayObject is removed from a Container. + * + * @event PIXI.DisplayObject#removed + * @param {PIXI.Container} container - The container removed from. + */ } /** @@ -403,6 +425,8 @@ this.interactive = false; this.interactiveChildren = false; + + this._destroyed = true; } /** diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index aafdfe2..f787c4a 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -792,8 +792,8 @@ sprite.worldAlpha = this.worldAlpha * sprite.alpha; sprite.blendMode = this.blendMode; - sprite.texture._frame.width = rect.width; - sprite.texture._frame.height = rect.height; + sprite._texture._frame.width = rect.width; + sprite._texture._frame.height = rect.height; sprite.transform.worldTransform = this.transform.worldTransform; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index a24c775..b9f192d 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -19,11 +19,12 @@ */ export default class SystemRenderer extends EventEmitter { + // eslint-disable-next-line valid-jsdoc /** * @param {string} system - The name of the system this renderer is for. - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -37,27 +38,31 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(system, screenWidth, screenHeight, options) + constructor(system, options, arg2, arg3) { super(); sayHello(system); - // prepare options - if (options) + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') { - for (const i in settings.RENDER_OPTIONS) - { - if (typeof options[i] === 'undefined') - { - options[i] = settings.RENDER_OPTIONS[i]; - } - } + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); } - else - { - options = settings.RENDER_OPTIONS; - } + + // Add the default render options + options = Object.assign({}, settings.RENDER_OPTIONS, options); + + /** + * The supplied constructor options. + * + * @member {Object} + * @readOnly + */ + this.options = options; /** * The type of the renderer. @@ -75,7 +80,7 @@ * * @member {PIXI.Rectangle} */ - this.screen = new Rectangle(0, 0, screenWidth || 800, screenHeight || 600); + this.screen = new Rectangle(0, 0, options.width, options.height); /** * The canvas element that everything is drawn to @@ -279,6 +284,8 @@ this.blendModes = null; + this.options = null; + this.preserveDrawingBuffer = false; this.clearBeforeRender = false; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index e84e49c..691caf0 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -17,10 +17,11 @@ */ export default class CanvasRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -34,9 +35,9 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('Canvas', screenWidth, screenHeight, options); + super('Canvas', options, arg2, arg3); this.type = RENDERER_TYPE.CANVAS; @@ -96,7 +97,19 @@ this.context = null; this.renderingToScreen = false; - this.resize(screenWidth, screenHeight); + this.resize(this.options.width, this.options.height); + + /** + * Fired after rendering finishes. + * + * @event PIXI.CanvasRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.CanvasRenderer#prerender + */ } /** @@ -299,4 +312,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.CanvasRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.CanvasExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.CanvasPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.CanvasRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index e1cf7cd..f6fe123 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -31,11 +31,12 @@ */ export default class WebGLRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** * - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -55,9 +56,9 @@ * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('WebGL', screenWidth, screenHeight, options); + super('WebGL', options, arg2, arg3); /** * The type of this renderer as a standardised const @@ -89,12 +90,6 @@ }; - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ this._backgroundColorRgba[3] = this.transparent ? 0 : 1; this.globalUniforms = new UniformGroup({ @@ -117,6 +112,13 @@ this.initPlugins(); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ if(options.context) { this.context.initFromContext(options.context); @@ -169,6 +171,25 @@ } return this; + + /** + * Fired after rendering finishes. + * + * @event PIXI.WebGLRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.WebGLRenderer#prerender + */ + + /** + * Fired when the WebGL context is set. + * + * @event PIXI.WebGLRenderer#context + * @param {WebGLRenderingContext} gl - WebGL context. + */ } /** @@ -295,4 +316,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.WebGLRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.WebGLRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(WebGLRenderer); diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index 4eb6128..fa3d883 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -47,6 +47,14 @@ * @member {boolean} */ this.enabled = true; + + /** + * If enabled, pixi will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; } /** diff --git a/src/core/renderers/webgl/systems/ContextSystem.js b/src/core/renderers/webgl/systems/ContextSystem.js index ff958c7..8df85ea 100644 --- a/src/core/renderers/webgl/systems/ContextSystem.js +++ b/src/core/renderers/webgl/systems/ContextSystem.js @@ -75,7 +75,8 @@ */ createContext(canvas, options) { - var gl = canvas.getContext('webgl', options) || + var gl = //canvas.getContext('webgl2', options) || + canvas.getContext('webgl', options) || canvas.getContext('experimental-webgl', options); if (!gl) diff --git a/src/core/renderers/webgl/systems/FilterSystem.js b/src/core/renderers/webgl/systems/FilterSystem.js index f47335d..37e28d2 100644 --- a/src/core/renderers/webgl/systems/FilterSystem.js +++ b/src/core/renderers/webgl/systems/FilterSystem.js @@ -111,7 +111,7 @@ // TODO we should fit the rect around the transform.. } - else + else if (filters[0].autoFit) { sourceFrame.fit(filterData.stack[0].destinationFrame); } diff --git a/src/core/renderers/webgl/systems/shader/shader/mapType.js b/src/core/renderers/webgl/systems/shader/shader/mapType.js index 581dace..917538a 100644 --- a/src/core/renderers/webgl/systems/shader/shader/mapType.js +++ b/src/core/renderers/webgl/systems/shader/shader/mapType.js @@ -15,7 +15,7 @@ } } - return GL_TABLE[type]; + return GL_TABLE[type]; }; var GL_TABLE = null; diff --git a/src/core/settings.js b/src/core/settings.js index 18c5877..38ef45c 100644 --- a/src/core/settings.js +++ b/src/core/settings.js @@ -2,6 +2,15 @@ import canUploadSameBuffer from './utils/canUploadSameBuffer'; /** + * User's customizable globals for overriding the default PIXI settings, such + * as a renderer's default resolution, framerate, float percision, etc. + * @example + * // Use the native window resolution as the default resolution + * // will support high-density displays when rendering + * PIXI.settings.RESOLUTION = window.devicePixelRatio. + * + * // Disable interpolation when scaling, will make texture be pixelated + * PIXI.settings.SCALE_MODE = PIXI.SCALE_MODES.NEAREST; * @namespace PIXI.settings */ export default { @@ -101,6 +110,9 @@ * @property {boolean} clearBeforeRender=true * @property {boolean} preserveDrawingBuffer=false * @property {boolean} roundPixels=false + * @property {number} width=800 + * @property {number} height=600 + * @property {boolean} legacy=false */ RENDER_OPTIONS: { view: null, @@ -112,6 +124,9 @@ clearBeforeRender: true, preserveDrawingBuffer: false, roundPixels: false, + width: 800, + height: 600, + legacy: false, }, /** diff --git a/src/core/shader/Program.js b/src/core/shader/Program.js index c0b538b..07b4398 100644 --- a/src/core/shader/Program.js +++ b/src/core/shader/Program.js @@ -1,6 +1,6 @@ import extractUniformsFromSrc from './extractUniformsFromSrc'; import generateUniformsSync from './generateUniformsSync'; -import glCore from 'pixi-gl-core'; +import shaderUtils from '../renderers/webgl/systems/shader/shader'; import { ProgramCache } from '../utils'; import getTestContext from '../utils/getTestContext'; @@ -64,14 +64,17 @@ } else { - vertexSrc = glCore.shader.setPrecision(vertexSrc, 'mediump'); - fragmentSrc = glCore.shader.setPrecision(fragmentSrc, 'mediump'); + vertexSrc = shaderUtils.setPrecision(vertexSrc, 'mediump'); + fragmentSrc = shaderUtils.setPrecision(fragmentSrc, 'mediump'); - const program = glCore.shader.compileProgram(gl, vertexSrc, fragmentSrc); + const program = shaderUtils.compileProgram(gl, vertexSrc, fragmentSrc); + console.log("<<<<<<>>><<"); this.attributeData = this.getAttributeData(program, gl); this.uniformData = this.getUniformData(program, gl); + console.log(this.uniformData); + //gl.deleteProgram(program); } } @@ -95,13 +98,13 @@ for (let i = 0; i < totalAttributes; i++) { const attribData = gl.getActiveAttrib(program, i); - const type = glCore.shader.mapType(gl, attribData.type); + const type = shaderUtils.mapType(gl, attribData.type); /*eslint-disable */ const data = { type: type, name: attribData.name, - size: glCore.shader.mapSize(type), + size: shaderUtils.mapSize(type), location: 0, }; /*eslint-enable */ @@ -137,13 +140,14 @@ // TODO expose this as a prop? // const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); - const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); + //const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); for (let i = 0; i < totalUniforms; i++) { const uniformData = gl.getActiveUniform(program, i); const name = uniformData.name.replace(/\[.*?\]/, ''); - const type = glCore.shader.mapType(gl, uniformData.type); + const type = shaderUtils.mapType(gl, uniformData.type); + console.log('mapping ' + uniformData.type + ' to ' + type); // if (!name.match(maskRegex)) { @@ -151,7 +155,7 @@ uniforms[name] = { type: type, size: uniformData.size, - value: glCore.shader.defaultValue(type, uniformData.size), + value: shaderUtils.defaultValue(type, uniformData.size), }; /*eslint-enable */ } diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index e387e99..b57d095 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -147,12 +147,12 @@ // so if _width is 0 then width was not set.. if (this._width) { - this.scale.x = sign(this.scale.x) * this._width / this.texture.orig.width; + this.scale.x = sign(this.scale.x) * this._width / this._texture.orig.width; } if (this._height) { - this.scale.y = sign(this.scale.y) * this._height / this.texture.orig.height; + this.scale.y = sign(this.scale.y) * this._height / this._texture.orig.height; } } diff --git a/src/core/sprites/canvas/CanvasTinter.js b/src/core/sprites/canvas/CanvasTinter.js index 27e5e0c..fd9de5d 100644 --- a/src/core/sprites/canvas/CanvasTinter.js +++ b/src/core/sprites/canvas/CanvasTinter.js @@ -18,7 +18,7 @@ */ getTintedTexture: (sprite, color) => { - const texture = sprite.texture; + const texture = sprite._texture; color = CanvasTinter.roundColor(color); diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 5a4edda..8b9b797 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -344,6 +344,7 @@ const texture = this._texture; const style = this._style; + const padding = style.trim ? 0 : style.padding; texture.baseTexture.valid = true; texture.baseTexture.resolution = this.resolution; @@ -353,11 +354,11 @@ texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - texture.trim.x = -style.padding; - texture.trim.y = -style.padding; + texture.trim.x = -padding; + texture.trim.y = -padding; - texture.orig.width = texture._frame.width - (style.padding * 2); - texture.orig.height = texture._frame.height - (style.padding * 2); + texture.orig.width = texture._frame.width - (padding * 2); + texture.orig.height = texture._frame.height - (padding * 2); // call sprite onTextureUpdate to update scale if _width or _height were set this._onTextureUpdate(); diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index 853da3d..8a9c34b 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -114,6 +114,38 @@ this.cacheId = null; this.validate(); + + /** + * Fired when a not-immediately-available source finishes loading. + * + * @protected + * @event PIXI.BaseTexture#loaded + * @param {PIXI.BaseTexture} baseTexture - Resource loaded. + */ + + /** + * Fired when a not-immediately-available source fails to load. + * + * @protected + * @event PIXI.BaseTexture#error + * @param {PIXI.BaseTexture} baseTexture - Resource errored. + */ + + /** + * Fired when BaseTexture is updated. + * + * @protected + * @event PIXI.BaseTexture#update + * @param {PIXI.BaseTexture} baseTexture - Instance of texture being updated. + */ + + /** + * Fired when BaseTexture is destroyed. + * + * @protected + * @event PIXI.BaseTexture#dispose + * @param {PIXI.BaseTexture} baseTexture - Instance of texture being destroyed. + */ } setResource(resource) diff --git a/src/core/textures/Spritesheet.js b/src/core/textures/Spritesheet.js index d469c62..9b24f27 100644 --- a/src/core/textures/Spritesheet.js +++ b/src/core/textures/Spritesheet.js @@ -208,6 +208,7 @@ // lets also add the frame to pixi's global cache for fromFrame and fromImage functions TextureCache[i] = this.textures[i]; + this.textures[i].textureCacheId = i; } frameIndex++; diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 0a2c379..2213087 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -147,9 +147,9 @@ /** * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. * - * @event update - * @memberof PIXI.Texture# + * @event PIXI.Texture#update * @protected + * @param {PIXI.Texture} texture - Instance of texture being updated. */ this._updateID = 0; @@ -159,6 +159,15 @@ * @type {Object} */ this.transform = null; + + /** + * The id under which this Texture has been added to the texture cache. This is + * automatically set in certain cases, but may not always be accurate, particularly if + * the texture is in the cache under multiple ids. + * + * @member {string} + */ + this.textureCacheId = null; } /** @@ -225,11 +234,17 @@ this._uvs = null; this.trim = null; this.orig = null; + this.textureCacheId = null; this.valid = false; - this.off('dispose', this.dispose, this); - this.off('update', this.update, this); + for (const prop in TextureCache) + { + if (TextureCache[prop] === this) + { + delete TextureCache[prop]; + } + } } /** @@ -315,14 +330,15 @@ */ static fromLoader(source, imageUrl, name) { - // console.log('added from loader...') + // console.log('added from loader...') const resource = new ImageResource(source);//.from(imageUrl, crossorigin);// document.createElement('img'); - // console.log('base resource ' + resource.width); + // console.log('base resource ' + resource.width); const baseTexture = new BaseTexture(resource, settings.SCALE_MODE, getResolutionOfUrl(imageUrl)); + /// console.log('base width ' + baseTexture.width); const texture = new Texture(baseTexture); @@ -335,6 +351,7 @@ // lets also add the frame to pixi's global cache for fromFrame and fromImage fucntions BaseTextureCache[name] = baseTexture; TextureCache[name] = texture; + texture.textureCacheId = name; // also add references by url if they are different. if (name !== imageUrl) @@ -355,6 +372,10 @@ */ static addTextureToCache(texture, id) { + if (!texture.textureCacheId) + { + texture.textureCacheId = id; + } TextureCache[id] = texture; } @@ -495,6 +516,7 @@ */ Texture.EMPTY = new Texture(new BaseTexture()); removeAllHandlers(Texture.EMPTY); +removeAllHandlers(Texture.EMPTY.baseTexture); /** * A white texture of 10x10 size, used for graphics and other things @@ -505,3 +527,4 @@ */ Texture.WHITE = createWhiteTexture(); removeAllHandlers(Texture.WHITE); +removeAllHandlers(Texture.WHITE.baseTexture); diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index 59eb9fd..a9b9f51 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -1,6 +1,7 @@ import BaseTexture from './BaseTexture'; import { uid, BaseTextureCache } from '../utils'; -import * as ticker from '../ticker'; +import { shared } from '../ticker'; +import { UPDATE_PRIORITY } from '../const'; /** * A texture of a [playing] Video. @@ -125,7 +126,7 @@ if (!this._isAutoUpdating && this.autoUpdate) { - ticker.shared.add(this.update, this); + shared.add(this.update, this, UPDATE_PRIORITY.HIGH); this._isAutoUpdating = true; } } @@ -139,7 +140,7 @@ { if (this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); this._isAutoUpdating = false; } } @@ -187,7 +188,7 @@ { if (this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); } if (this.source && this.source._pixiId) @@ -281,12 +282,12 @@ if (!this._autoUpdate && this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); this._isAutoUpdating = false; } else if (this._autoUpdate && !this._isAutoUpdating) { - ticker.shared.add(this.update, this); + shared.add(this.update, this, UPDATE_PRIORITY.HIGH); this._isAutoUpdating = true; } } diff --git a/src/core/ticker/Ticker.js b/src/core/ticker/Ticker.js index 17a1517..40df138 100644 --- a/src/core/ticker/Ticker.js +++ b/src/core/ticker/Ticker.js @@ -1,12 +1,10 @@ import settings from '../settings'; -import EventEmitter from 'eventemitter3'; - -// Internal event used by composed emitter -const TICK = 'tick'; +import { UPDATE_PRIORITY } from '../const'; +import TickerListener from './TickerListener'; /** * A Ticker class that runs an update loop that other objects listen to. - * This class is composed around an EventEmitter object to add listeners + * This class is composed around listeners * meant for execution on the next requested animation frame. * Animation frames are requested only when necessary, * e.g. When the ticker is started and the emitter has listeners. @@ -22,10 +20,11 @@ constructor() { /** - * Internal emitter used to fire 'tick' event + * The first listener. All new listeners added are chained on this. * @private + * @type {TickerListener} */ - this._emitter = new EventEmitter(); + this._head = new TickerListener(null, null, Infinity); /** * Internal current frame request ID @@ -131,7 +130,7 @@ // Invoke listeners now this.update(time); // Listener side effects may have modified ticker state. - if (this.started && this._requestId === null && this._emitter.listeners(TICK, true)) + if (this.started && this._requestId === null && this._head.next) { this._requestId = requestAnimationFrame(this._tick); } @@ -148,7 +147,7 @@ */ _requestIfNeeded() { - if (this._requestId === null && this._emitter.listeners(TICK, true)) + if (this._requestId === null && this._head.next) { // ensure callbacks get correct delta this.lastTime = performance.now(); @@ -193,35 +192,72 @@ } /** - * Calls {@link module:eventemitter3.EventEmitter#on} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. + * Register a handler for tick events. Calls continuously unless + * it is removed or the ticker is stopped. * * @param {Function} fn - The listener function to be added for updates * @param {Function} [context] - The listener context + * @param {number} [priority=PIXI.UPDATE_PRIORITY.NORMAL] - The priority for emitting * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - add(fn, context) + add(fn, context, priority = UPDATE_PRIORITY.NORMAL) { - this._emitter.on(TICK, fn, context); - - this._startIfPossible(); - - return this; + return this._addListener(new TickerListener(fn, context, priority)); } /** - * Calls {@link module:eventemitter3.EventEmitter#once} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. + * Add a handler for the tick event which is only execute once. * * @param {Function} fn - The listener function to be added for one update * @param {Function} [context] - The listener context + * @param {number} [priority=PIXI.UPDATE_PRIORITY.NORMAL] - The priority for emitting * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - addOnce(fn, context) + addOnce(fn, context, priority = UPDATE_PRIORITY.NORMAL) { - this._emitter.once(TICK, fn, context); + return this._addListener(new TickerListener(fn, context, priority, true)); + } + + /** + * Internally adds the event handler so that it can be sorted by priority. + * Priority allows certain handler (user, AnimatedSprite, Interaction) to be run + * before the rendering. + * + * @private + * @param {TickerListener} listener - Current listener being added. + * @returns {PIXI.ticker.Ticker} This instance of a ticker + */ + _addListener(listener) + { + // For attaching to head + let current = this._head.next; + let previous = this._head; + + // Add the first item + if (!current) + { + listener.connect(previous); + } + else + { + // Go from highest to lowest priority + while (current) + { + if (listener.priority >= current.priority) + { + listener.connect(previous); + break; + } + previous = current; + current = current.next; + } + + // Not yet connected + if (!listener.previous) + { + listener.connect(previous); + } + } this._startIfPossible(); @@ -229,19 +265,33 @@ } /** - * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. - * It checks if the emitter has listeners for 'tick' event. - * If it does, then it cancels the animation frame. + * Removes any handlers matching the function and context parameters. + * If no handlers are left after removing, then it cancels the animation frame. * - * @param {Function} [fn] - The listener function to be removed + * @param {Function} fn - The listener function to be removed * @param {Function} [context] - The listener context to be removed * @returns {PIXI.ticker.Ticker} This instance of a ticker */ remove(fn, context) { - this._emitter.off(TICK, fn, context); + let listener = this._head.next; - if (!this._emitter.listeners(TICK, true)) + while (listener) + { + // We found a match, lets remove it + // no break to delete all possible matches + // incase a listener was added 2+ times + if (listener.match(fn, context)) + { + listener = listener.destroy(); + } + else + { + listener = listener.next; + } + } + + if (!this._head.next) { this._cancelIfNeeded(); } @@ -276,6 +326,25 @@ } /** + * Destroy the ticker and don't use after this. Calling + * this method removes all references to internal events. + */ + destroy() + { + this.stop(); + + let listener = this._head.next; + + while (listener) + { + listener = listener.destroy(true); + } + + this._head.destroy(); + this._head = null; + } + + /** * Triggers an update. An update entails setting the * current {@link PIXI.ticker.Ticker#elapsedMS}, * the current {@link PIXI.ticker.Ticker#deltaTime}, @@ -320,8 +389,22 @@ this.deltaTime = elapsedMS * settings.TARGET_FPMS * this.speed; + // Cache a local reference, in-case ticker is destroyed + // during the emit, we can still check for head.next + const head = this._head; + // Invoke listeners added to internal emitter - this._emitter.emit(TICK, this.deltaTime); + let listener = head.next; + + while (listener) + { + listener = listener.emit(this.deltaTime); + } + + if (!head.next) + { + this._cancelIfNeeded(); + } } else { diff --git a/src/core/ticker/TickerListener.js b/src/core/ticker/TickerListener.js new file mode 100644 index 0000000..2bedb34 --- /dev/null +++ b/src/core/ticker/TickerListener.js @@ -0,0 +1,158 @@ +/** + * Internal class for handling the priority sorting of ticker handlers. + * + * @private + * @class + * @memberof PIXI.ticker + */ +export default class TickerListener +{ + /** + * Constructor + * + * @param {Function} fn - The listener function to be added for one update + * @param {Function} [context=null] - The listener context + * @param {number} [priority=0] - The priority for emitting + * @param {boolean} [once=false] - If the handler should fire once + */ + constructor(fn, context = null, priority = 0, once = false) + { + /** + * The handler function to execute. + * @member {Function} + */ + this.fn = fn; + + /** + * The calling to execute. + * @member {Function} + */ + this.context = context; + + /** + * The current priority. + * @member {number} + */ + this.priority = priority; + + /** + * If this should only execute once. + * @member {boolean} + */ + this.once = once; + + /** + * The next item in chain. + * @member {TickerListener} + */ + this.next = null; + + /** + * The previous item in chain. + * @member {TickerListener} + */ + this.previous = null; + + /** + * `true` if this listener has been destroyed already. + * @member {boolean} + * @private + */ + this._destroyed = false; + } + + /** + * Simple compare function to figure out if a function and context match. + * + * @param {Function} fn - The listener function to be added for one update + * @param {Function} context - The listener context + * @return {boolean} `true` if the listener match the arguments + */ + match(fn, context) + { + context = context || null; + + return this.fn === fn && this.context === context; + } + + /** + * Emit by calling the current function. + * @param {number} deltaTime - time since the last emit. + * @return {TickerListener} Next ticker + */ + emit(deltaTime) + { + if (this.context) + { + this.fn.call(this.context, deltaTime); + } + else + { + this.fn(deltaTime); + } + + if (this.once) + { + this.destroy(); + } + + const redirect = this.next; + + // Soft-destroying should remove + // the next reference + if (this._destroyed) + { + this.next = null; + } + + return redirect; + } + + /** + * Connect to the list. + * @param {TickerListener} previous - Input node, previous listener + */ + connect(previous) + { + this.previous = previous; + if (previous.next) + { + previous.next.previous = this; + } + this.next = previous.next; + previous.next = this; + } + + /** + * Destroy and don't use after this. + * @param {boolean} [hard = false] `true` to remove the `next` reference, this + * is considered a hard destroy. Soft destroy maintains the next reference. + * @return {TickerListener} The listener to redirect while emitting or removing. + */ + destroy(hard = false) + { + this._destroyed = true; + this.fn = null; + this.context = null; + + // Disconnect, hook up next and previous + if (this.previous) + { + this.previous.next = this.next; + } + + if (this.next) + { + this.next.previous = this.previous; + } + + // Redirect to the next item + const redirect = this.previous; + + // Remove references + this.next = hard ? null : redirect; + this.previous = null; + + return redirect; + } +} diff --git a/.travis.yml b/.travis.yml index 3df8b22..14de334 100644 --- a/.travis.yml +++ b/.travis.yml @@ -110,3 +110,43 @@ on: all_branches: true condition: $TRAVIS_TAG + # Deploy config for latest release + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: dist + upload-dir: "release" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: docs + upload-dir: "release/docs" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: coverage + upload-dir: "release/coverage" + on: + all_branches: true + condition: $TRAVIS_TAG \ No newline at end of file diff --git a/README.md b/README.md index d26fadd..3b85f85 100644 --- a/README.md +++ b/README.md @@ -36,22 +36,14 @@ - Wiki: Other misc tutorials and resources are [on the Wiki](https://github.com/pixijs/pixi.js/wiki/Resources). ### Community ### -- Forums: Check out the [forum] (http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow] (http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. +- Forums: Check out the [forum](http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow](http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. - Inspiration: Check out the [gallery](http://www.pixijs.com/gallery) to see some of the amazing things people have created! - Chat: You can join us on [Gitter](https://gitter.im/pixijs/pixi.js) To chat about Pixi. We also now have a Slack channel. If you would like to join it please Send me an email (mat@goodboydigital.com) and I will invite you in. ### Setup ### -It's easy to get started with Pixi.js! Simply grab the pre-built versions from here: - -Release Branch - Nice and stable Pixi.js -- Unminified: [http://pixijs.download/release/pixi.js] -- Minified: [http://pixijs.download/release/pixi.min.js] - -Develop Branch - The bleeding edge version of Pixi.js -- Unminified: [http://pixijs.download/dev/pixi.js] -- Minified: [http://pixijs.download/dev/pixi.min.js] +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. @@ -70,10 +62,10 @@ #### CDN Install (via cdnjs) ```html - + ``` -_Note: `4.2.2` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ +_Note: `4.4.3` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ ### Demos ### @@ -130,7 +122,7 @@ // and the root stage PIXI.Container. var app = new PIXI.Application(); -// The application will create a canvas element for you that you +// The application will create a canvas element for you that you // can then insert into the DOM. document.body.appendChild(app.view); @@ -150,7 +142,7 @@ // Add the bunny to the scene we are building. app.stage.addChild(bunny); - + // Listen for frame updates app.ticker.add(function() { // each frame we spin the bunny around a bit diff --git a/bower.json b/bower.json deleted file mode 100644 index 41b668c..0000000 --- a/bower.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "pixi.js", - "main": "dist/pixi.min.js", - "ignore": [ - "**/.*", - "docs", - "node_modules", - "bower_components", - "test", - "scripts", - "bower.json", - "inch.json" - ], - "dependencies": { - }, - "devDependencies": { - } -} diff --git a/package.json b/package.json index d35cbb2..308469f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.3.5", + "version": "4.4.4", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ @@ -40,11 +40,11 @@ "lib": "babel src --out-dir lib -s", "predocs": "rimraf docs/**", "docs": "jsdoc -c scripts/jsdoc.conf.json -R README.md", - "publish:patch": "npm version patch --no-git-tag-version && npm publish", - "publish:minor": "npm version minor --no-git-tag-version && npm publish", - "publish:major": "npm version major --no-git-tag-version && npm publish", + "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", - "postpublish": "node scripts/release.js" + "postpublish": "git push && git push --tags" }, "files": [ "dist/", @@ -72,7 +72,6 @@ "electron": "^1.4.15", "eslint": "^3.5.0", "floss": "^2.0.1", - "gh-pages": "^0.11.0", "jaguarjs-jsdoc": "^1.0.1", "js-md5": "^0.4.1", "jsdoc": "^3.4.2", diff --git a/scripts/jsdoc.conf.json b/scripts/jsdoc.conf.json index 553e61c..3f52180 100644 --- a/scripts/jsdoc.conf.json +++ b/scripts/jsdoc.conf.json @@ -43,7 +43,7 @@ }, "markdown" : { "parser" : "gfm", - "hardwrap" : true + "hardwrap" : false }, "opts": { "encoding" : "utf8", diff --git a/scripts/release.js b/scripts/release.js deleted file mode 100644 index 156620c..0000000 --- a/scripts/release.js +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env node -'use strict'; - -// Publish script to push releases of the bin files -// the normally are gitignored -const ghpages = require('gh-pages'); -const path = require('path'); -const packageInfo = require(path.join(__dirname, '..', 'package.json')); - -const options = { - src: [ - 'dist/**/*', - 'lib/**/*', - 'src/**/*', - 'scripts/*', - 'scripts/renders/*', - 'test/**/*', - '*.json', - '*.md', - 'LICENSE', - '.eslintrc', - '.editorconfig', - '.travis.yml', - '.babelrc', - ], - dotfiles: true, - branch: 'release', - message: packageInfo.version, - logger: console.log.bind(console), -}; - -ghpages.publish(process.cwd(), options, (err) => -{ - if (err) - { - console.log(err); - process.exit(1); - - return; - } - - process.exit(0); -}); diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index f16bcb1..220427a 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -21,7 +21,7 @@ const DIV_HOOK_ZINDEX = 2; /** - * The Accessibility manager reacreates the ability to tab and and have content read by screen + * The Accessibility manager recreates the ability to tab and and have content read by screen * readers. This is very important as it can possibly help people with disabilities access pixi * content. * diff --git a/src/accessibility/index.js b/src/accessibility/index.js index 568cfb1..0ae108f 100644 --- a/src/accessibility/index.js +++ b/src/accessibility/index.js @@ -1,4 +1,9 @@ /** + * This namespace contains a renderer plugin for interaction accessibility for end-users + * with physical impairments which require screen-renders, keyboard navigation, etc. + * + * Do not instantiate this plugin directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. * @namespace PIXI.accessibility */ export { default as accessibleTarget } from './accessibleTarget'; diff --git a/src/core/Application.js b/src/core/Application.js index 5340578..2fcc729 100644 --- a/src/core/Application.js +++ b/src/core/Application.js @@ -1,6 +1,8 @@ import { autoDetectRenderer } from './autoDetectRenderer'; import Container from './display/Container'; import { shared, Ticker } from './ticker'; +import settings from './settings'; +import { UPDATE_PRIORITY } from './const'; /** * Convenience class to create a new PIXI application. @@ -22,28 +24,52 @@ */ export default class Application { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experience unexplained flickering try setting this to true. - * @param {boolean} [useSharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedLoader=false] - `true` to use PIXI.loaders.shared, `false` to create new Loader. */ - constructor(width, height, options, noWebGL, useSharedTicker = false) + constructor(options, arg2, arg3, arg4, arg5) { + // Support for constructor(width, height, options, noWebGL, useSharedTicker) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + forceCanvas: !!arg4, + sharedTicker: !!arg5, + }, arg3); + } + + /** + * The default options, so we mixin functionality later. + * @member {object} + * @protected + */ + this._options = options = Object.assign({ + sharedTicker: false, + forceCanvas: false, + sharedLoader: false, + }, options); + /** * WebGL renderer if available, otherwise CanvasRenderer * @member {PIXI.WebGLRenderer|PIXI.CanvasRenderer} */ - this.renderer = autoDetectRenderer(width, height, options, noWebGL); + this.renderer = autoDetectRenderer(options); /** * The root display container that's rendered. @@ -63,7 +89,7 @@ * @member {PIXI.ticker.Ticker} * @default PIXI.ticker.shared */ - this.ticker = useSharedTicker ? shared : new Ticker(); + this.ticker = options.sharedTicker ? shared : new Ticker(); // Start the rendering this.start(); @@ -78,7 +104,7 @@ this._ticker = ticker; if (ticker) { - ticker.add(this.render, this); + ticker.add(this.render, this, UPDATE_PRIORITY.LOW); } } get ticker() // eslint-disable-line require-jsdoc @@ -136,13 +162,18 @@ */ destroy(removeView) { - this.stop(); + const oldTicker = this._ticker; + this.ticker = null; + oldTicker.destroy(); + this.stage.destroy(); this.stage = null; this.renderer.destroy(removeView); this.renderer = null; + + this._options = null; } } diff --git a/src/core/autoDetectRenderer.js b/src/core/autoDetectRenderer.js index 8960425..9ec0364 100644 --- a/src/core/autoDetectRenderer.js +++ b/src/core/autoDetectRenderer.js @@ -2,6 +2,7 @@ import CanvasRenderer from './renderers/canvas/CanvasRenderer'; import WebGLRenderer from './renderers/webgl/WebGLRenderer'; +// eslint-disable-next-line valid-jsdoc /** * This helper function will automatically detect which renderer you should be using. * WebGL is the preferred renderer as it is a lot faster. If webGL is not supported by @@ -9,24 +10,32 @@ * * @memberof PIXI * @function autoDetectRenderer - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @return {PIXI.WebGLRenderer|PIXI.CanvasRenderer} Returns WebGL renderer if available, otherwise CanvasRenderer */ -export function autoDetectRenderer(width = 800, height = 600, options, noWebGL) +export function autoDetectRenderer(options, arg1, arg2, arg3) { - if (!noWebGL && utils.isWebGLSupported()) + // Backward-compatible support for noWebGL option + let forceCanvas = options && options.forceCanvas; + + if (arg3 !== undefined) { - return new WebGLRenderer(width, height, options); + forceCanvas = arg3; } - return new CanvasRenderer(width, height, options); + if (!forceCanvas && utils.isWebGLSupported()) + { + return new WebGLRenderer(options, arg1, arg2); + } + + return new CanvasRenderer(options, arg1, arg2); } diff --git a/src/core/const.js b/src/core/const.js index b9e99ae..2c68dee 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -341,3 +341,27 @@ LINEAR_VERTICAL: 0, LINEAR_HORIZONTAL: 1, }; + +/** + * Represents the update priorities used by internal PIXI classes when registered with + * the {@link PIXI.ticker.Ticker} object. Higher priority items are updated first and lower + * priority items, such as render, should go later. + * + * @static + * @constant + * @name UPDATE_PRIORITY + * @memberof PIXI + * @type {object} + * @property {number} INTERACTION=50 Highest priority, used for {@link PIXI.interaction.InteractionManager} + * @property {number} HIGH=25 High priority updating, {@link PIXI.VideoBaseTexture} and {@link PIXI.extras.AnimatedSprite} + * @property {number} NORMAL=0 Default priority for ticker events, see {@link PIXI.ticker.Ticker#add}. + * @property {number} LOW=-25 Low priority used for {@link PIXI.Application} rendering. + * @property {number} UTILITY=-50 Lowest priority used for {@link PIXI.prepare.BasePrepare} utility. + */ +export const UPDATE_PRIORITY = { + INTERACTION: 50, + HIGH: 25, + NORMAL: 0, + LOW: -25, + UTILITY: -50, +}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 4e6c77d..decb42a 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -13,7 +13,6 @@ * * @class * @extends EventEmitter - * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ export default class DisplayObject extends EventEmitter @@ -113,6 +112,29 @@ * @private */ this._mask = null; + + /** + * If the object has been destroyed via destroy(). If true, it should not be used. + * + * @member {boolean} + * @private + * @readonly + */ + this._destroyed = false; + + /** + * Fired when this DisplayObject is added to a Container. + * + * @event PIXI.DisplayObject#added + * @param {PIXI.Container} container - The container added to. + */ + + /** + * Fired when this DisplayObject is removed from a Container. + * + * @event PIXI.DisplayObject#removed + * @param {PIXI.Container} container - The container removed from. + */ } /** @@ -403,6 +425,8 @@ this.interactive = false; this.interactiveChildren = false; + + this._destroyed = true; } /** diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index aafdfe2..f787c4a 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -792,8 +792,8 @@ sprite.worldAlpha = this.worldAlpha * sprite.alpha; sprite.blendMode = this.blendMode; - sprite.texture._frame.width = rect.width; - sprite.texture._frame.height = rect.height; + sprite._texture._frame.width = rect.width; + sprite._texture._frame.height = rect.height; sprite.transform.worldTransform = this.transform.worldTransform; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index a24c775..b9f192d 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -19,11 +19,12 @@ */ export default class SystemRenderer extends EventEmitter { + // eslint-disable-next-line valid-jsdoc /** * @param {string} system - The name of the system this renderer is for. - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -37,27 +38,31 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(system, screenWidth, screenHeight, options) + constructor(system, options, arg2, arg3) { super(); sayHello(system); - // prepare options - if (options) + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') { - for (const i in settings.RENDER_OPTIONS) - { - if (typeof options[i] === 'undefined') - { - options[i] = settings.RENDER_OPTIONS[i]; - } - } + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); } - else - { - options = settings.RENDER_OPTIONS; - } + + // Add the default render options + options = Object.assign({}, settings.RENDER_OPTIONS, options); + + /** + * The supplied constructor options. + * + * @member {Object} + * @readOnly + */ + this.options = options; /** * The type of the renderer. @@ -75,7 +80,7 @@ * * @member {PIXI.Rectangle} */ - this.screen = new Rectangle(0, 0, screenWidth || 800, screenHeight || 600); + this.screen = new Rectangle(0, 0, options.width, options.height); /** * The canvas element that everything is drawn to @@ -279,6 +284,8 @@ this.blendModes = null; + this.options = null; + this.preserveDrawingBuffer = false; this.clearBeforeRender = false; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index e84e49c..691caf0 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -17,10 +17,11 @@ */ export default class CanvasRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -34,9 +35,9 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('Canvas', screenWidth, screenHeight, options); + super('Canvas', options, arg2, arg3); this.type = RENDERER_TYPE.CANVAS; @@ -96,7 +97,19 @@ this.context = null; this.renderingToScreen = false; - this.resize(screenWidth, screenHeight); + this.resize(this.options.width, this.options.height); + + /** + * Fired after rendering finishes. + * + * @event PIXI.CanvasRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.CanvasRenderer#prerender + */ } /** @@ -299,4 +312,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.CanvasRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.CanvasExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.CanvasPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.CanvasRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index e1cf7cd..f6fe123 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -31,11 +31,12 @@ */ export default class WebGLRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** * - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -55,9 +56,9 @@ * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('WebGL', screenWidth, screenHeight, options); + super('WebGL', options, arg2, arg3); /** * The type of this renderer as a standardised const @@ -89,12 +90,6 @@ }; - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ this._backgroundColorRgba[3] = this.transparent ? 0 : 1; this.globalUniforms = new UniformGroup({ @@ -117,6 +112,13 @@ this.initPlugins(); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ if(options.context) { this.context.initFromContext(options.context); @@ -169,6 +171,25 @@ } return this; + + /** + * Fired after rendering finishes. + * + * @event PIXI.WebGLRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.WebGLRenderer#prerender + */ + + /** + * Fired when the WebGL context is set. + * + * @event PIXI.WebGLRenderer#context + * @param {WebGLRenderingContext} gl - WebGL context. + */ } /** @@ -295,4 +316,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.WebGLRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.WebGLRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(WebGLRenderer); diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index 4eb6128..fa3d883 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -47,6 +47,14 @@ * @member {boolean} */ this.enabled = true; + + /** + * If enabled, pixi will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; } /** diff --git a/src/core/renderers/webgl/systems/ContextSystem.js b/src/core/renderers/webgl/systems/ContextSystem.js index ff958c7..8df85ea 100644 --- a/src/core/renderers/webgl/systems/ContextSystem.js +++ b/src/core/renderers/webgl/systems/ContextSystem.js @@ -75,7 +75,8 @@ */ createContext(canvas, options) { - var gl = canvas.getContext('webgl', options) || + var gl = //canvas.getContext('webgl2', options) || + canvas.getContext('webgl', options) || canvas.getContext('experimental-webgl', options); if (!gl) diff --git a/src/core/renderers/webgl/systems/FilterSystem.js b/src/core/renderers/webgl/systems/FilterSystem.js index f47335d..37e28d2 100644 --- a/src/core/renderers/webgl/systems/FilterSystem.js +++ b/src/core/renderers/webgl/systems/FilterSystem.js @@ -111,7 +111,7 @@ // TODO we should fit the rect around the transform.. } - else + else if (filters[0].autoFit) { sourceFrame.fit(filterData.stack[0].destinationFrame); } diff --git a/src/core/renderers/webgl/systems/shader/shader/mapType.js b/src/core/renderers/webgl/systems/shader/shader/mapType.js index 581dace..917538a 100644 --- a/src/core/renderers/webgl/systems/shader/shader/mapType.js +++ b/src/core/renderers/webgl/systems/shader/shader/mapType.js @@ -15,7 +15,7 @@ } } - return GL_TABLE[type]; + return GL_TABLE[type]; }; var GL_TABLE = null; diff --git a/src/core/settings.js b/src/core/settings.js index 18c5877..38ef45c 100644 --- a/src/core/settings.js +++ b/src/core/settings.js @@ -2,6 +2,15 @@ import canUploadSameBuffer from './utils/canUploadSameBuffer'; /** + * User's customizable globals for overriding the default PIXI settings, such + * as a renderer's default resolution, framerate, float percision, etc. + * @example + * // Use the native window resolution as the default resolution + * // will support high-density displays when rendering + * PIXI.settings.RESOLUTION = window.devicePixelRatio. + * + * // Disable interpolation when scaling, will make texture be pixelated + * PIXI.settings.SCALE_MODE = PIXI.SCALE_MODES.NEAREST; * @namespace PIXI.settings */ export default { @@ -101,6 +110,9 @@ * @property {boolean} clearBeforeRender=true * @property {boolean} preserveDrawingBuffer=false * @property {boolean} roundPixels=false + * @property {number} width=800 + * @property {number} height=600 + * @property {boolean} legacy=false */ RENDER_OPTIONS: { view: null, @@ -112,6 +124,9 @@ clearBeforeRender: true, preserveDrawingBuffer: false, roundPixels: false, + width: 800, + height: 600, + legacy: false, }, /** diff --git a/src/core/shader/Program.js b/src/core/shader/Program.js index c0b538b..07b4398 100644 --- a/src/core/shader/Program.js +++ b/src/core/shader/Program.js @@ -1,6 +1,6 @@ import extractUniformsFromSrc from './extractUniformsFromSrc'; import generateUniformsSync from './generateUniformsSync'; -import glCore from 'pixi-gl-core'; +import shaderUtils from '../renderers/webgl/systems/shader/shader'; import { ProgramCache } from '../utils'; import getTestContext from '../utils/getTestContext'; @@ -64,14 +64,17 @@ } else { - vertexSrc = glCore.shader.setPrecision(vertexSrc, 'mediump'); - fragmentSrc = glCore.shader.setPrecision(fragmentSrc, 'mediump'); + vertexSrc = shaderUtils.setPrecision(vertexSrc, 'mediump'); + fragmentSrc = shaderUtils.setPrecision(fragmentSrc, 'mediump'); - const program = glCore.shader.compileProgram(gl, vertexSrc, fragmentSrc); + const program = shaderUtils.compileProgram(gl, vertexSrc, fragmentSrc); + console.log("<<<<<<>>><<"); this.attributeData = this.getAttributeData(program, gl); this.uniformData = this.getUniformData(program, gl); + console.log(this.uniformData); + //gl.deleteProgram(program); } } @@ -95,13 +98,13 @@ for (let i = 0; i < totalAttributes; i++) { const attribData = gl.getActiveAttrib(program, i); - const type = glCore.shader.mapType(gl, attribData.type); + const type = shaderUtils.mapType(gl, attribData.type); /*eslint-disable */ const data = { type: type, name: attribData.name, - size: glCore.shader.mapSize(type), + size: shaderUtils.mapSize(type), location: 0, }; /*eslint-enable */ @@ -137,13 +140,14 @@ // TODO expose this as a prop? // const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); - const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); + //const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); for (let i = 0; i < totalUniforms; i++) { const uniformData = gl.getActiveUniform(program, i); const name = uniformData.name.replace(/\[.*?\]/, ''); - const type = glCore.shader.mapType(gl, uniformData.type); + const type = shaderUtils.mapType(gl, uniformData.type); + console.log('mapping ' + uniformData.type + ' to ' + type); // if (!name.match(maskRegex)) { @@ -151,7 +155,7 @@ uniforms[name] = { type: type, size: uniformData.size, - value: glCore.shader.defaultValue(type, uniformData.size), + value: shaderUtils.defaultValue(type, uniformData.size), }; /*eslint-enable */ } diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index e387e99..b57d095 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -147,12 +147,12 @@ // so if _width is 0 then width was not set.. if (this._width) { - this.scale.x = sign(this.scale.x) * this._width / this.texture.orig.width; + this.scale.x = sign(this.scale.x) * this._width / this._texture.orig.width; } if (this._height) { - this.scale.y = sign(this.scale.y) * this._height / this.texture.orig.height; + this.scale.y = sign(this.scale.y) * this._height / this._texture.orig.height; } } diff --git a/src/core/sprites/canvas/CanvasTinter.js b/src/core/sprites/canvas/CanvasTinter.js index 27e5e0c..fd9de5d 100644 --- a/src/core/sprites/canvas/CanvasTinter.js +++ b/src/core/sprites/canvas/CanvasTinter.js @@ -18,7 +18,7 @@ */ getTintedTexture: (sprite, color) => { - const texture = sprite.texture; + const texture = sprite._texture; color = CanvasTinter.roundColor(color); diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 5a4edda..8b9b797 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -344,6 +344,7 @@ const texture = this._texture; const style = this._style; + const padding = style.trim ? 0 : style.padding; texture.baseTexture.valid = true; texture.baseTexture.resolution = this.resolution; @@ -353,11 +354,11 @@ texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - texture.trim.x = -style.padding; - texture.trim.y = -style.padding; + texture.trim.x = -padding; + texture.trim.y = -padding; - texture.orig.width = texture._frame.width - (style.padding * 2); - texture.orig.height = texture._frame.height - (style.padding * 2); + texture.orig.width = texture._frame.width - (padding * 2); + texture.orig.height = texture._frame.height - (padding * 2); // call sprite onTextureUpdate to update scale if _width or _height were set this._onTextureUpdate(); diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index 853da3d..8a9c34b 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -114,6 +114,38 @@ this.cacheId = null; this.validate(); + + /** + * Fired when a not-immediately-available source finishes loading. + * + * @protected + * @event PIXI.BaseTexture#loaded + * @param {PIXI.BaseTexture} baseTexture - Resource loaded. + */ + + /** + * Fired when a not-immediately-available source fails to load. + * + * @protected + * @event PIXI.BaseTexture#error + * @param {PIXI.BaseTexture} baseTexture - Resource errored. + */ + + /** + * Fired when BaseTexture is updated. + * + * @protected + * @event PIXI.BaseTexture#update + * @param {PIXI.BaseTexture} baseTexture - Instance of texture being updated. + */ + + /** + * Fired when BaseTexture is destroyed. + * + * @protected + * @event PIXI.BaseTexture#dispose + * @param {PIXI.BaseTexture} baseTexture - Instance of texture being destroyed. + */ } setResource(resource) diff --git a/src/core/textures/Spritesheet.js b/src/core/textures/Spritesheet.js index d469c62..9b24f27 100644 --- a/src/core/textures/Spritesheet.js +++ b/src/core/textures/Spritesheet.js @@ -208,6 +208,7 @@ // lets also add the frame to pixi's global cache for fromFrame and fromImage functions TextureCache[i] = this.textures[i]; + this.textures[i].textureCacheId = i; } frameIndex++; diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 0a2c379..2213087 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -147,9 +147,9 @@ /** * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. * - * @event update - * @memberof PIXI.Texture# + * @event PIXI.Texture#update * @protected + * @param {PIXI.Texture} texture - Instance of texture being updated. */ this._updateID = 0; @@ -159,6 +159,15 @@ * @type {Object} */ this.transform = null; + + /** + * The id under which this Texture has been added to the texture cache. This is + * automatically set in certain cases, but may not always be accurate, particularly if + * the texture is in the cache under multiple ids. + * + * @member {string} + */ + this.textureCacheId = null; } /** @@ -225,11 +234,17 @@ this._uvs = null; this.trim = null; this.orig = null; + this.textureCacheId = null; this.valid = false; - this.off('dispose', this.dispose, this); - this.off('update', this.update, this); + for (const prop in TextureCache) + { + if (TextureCache[prop] === this) + { + delete TextureCache[prop]; + } + } } /** @@ -315,14 +330,15 @@ */ static fromLoader(source, imageUrl, name) { - // console.log('added from loader...') + // console.log('added from loader...') const resource = new ImageResource(source);//.from(imageUrl, crossorigin);// document.createElement('img'); - // console.log('base resource ' + resource.width); + // console.log('base resource ' + resource.width); const baseTexture = new BaseTexture(resource, settings.SCALE_MODE, getResolutionOfUrl(imageUrl)); + /// console.log('base width ' + baseTexture.width); const texture = new Texture(baseTexture); @@ -335,6 +351,7 @@ // lets also add the frame to pixi's global cache for fromFrame and fromImage fucntions BaseTextureCache[name] = baseTexture; TextureCache[name] = texture; + texture.textureCacheId = name; // also add references by url if they are different. if (name !== imageUrl) @@ -355,6 +372,10 @@ */ static addTextureToCache(texture, id) { + if (!texture.textureCacheId) + { + texture.textureCacheId = id; + } TextureCache[id] = texture; } @@ -495,6 +516,7 @@ */ Texture.EMPTY = new Texture(new BaseTexture()); removeAllHandlers(Texture.EMPTY); +removeAllHandlers(Texture.EMPTY.baseTexture); /** * A white texture of 10x10 size, used for graphics and other things @@ -505,3 +527,4 @@ */ Texture.WHITE = createWhiteTexture(); removeAllHandlers(Texture.WHITE); +removeAllHandlers(Texture.WHITE.baseTexture); diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index 59eb9fd..a9b9f51 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -1,6 +1,7 @@ import BaseTexture from './BaseTexture'; import { uid, BaseTextureCache } from '../utils'; -import * as ticker from '../ticker'; +import { shared } from '../ticker'; +import { UPDATE_PRIORITY } from '../const'; /** * A texture of a [playing] Video. @@ -125,7 +126,7 @@ if (!this._isAutoUpdating && this.autoUpdate) { - ticker.shared.add(this.update, this); + shared.add(this.update, this, UPDATE_PRIORITY.HIGH); this._isAutoUpdating = true; } } @@ -139,7 +140,7 @@ { if (this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); this._isAutoUpdating = false; } } @@ -187,7 +188,7 @@ { if (this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); } if (this.source && this.source._pixiId) @@ -281,12 +282,12 @@ if (!this._autoUpdate && this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); this._isAutoUpdating = false; } else if (this._autoUpdate && !this._isAutoUpdating) { - ticker.shared.add(this.update, this); + shared.add(this.update, this, UPDATE_PRIORITY.HIGH); this._isAutoUpdating = true; } } diff --git a/src/core/ticker/Ticker.js b/src/core/ticker/Ticker.js index 17a1517..40df138 100644 --- a/src/core/ticker/Ticker.js +++ b/src/core/ticker/Ticker.js @@ -1,12 +1,10 @@ import settings from '../settings'; -import EventEmitter from 'eventemitter3'; - -// Internal event used by composed emitter -const TICK = 'tick'; +import { UPDATE_PRIORITY } from '../const'; +import TickerListener from './TickerListener'; /** * A Ticker class that runs an update loop that other objects listen to. - * This class is composed around an EventEmitter object to add listeners + * This class is composed around listeners * meant for execution on the next requested animation frame. * Animation frames are requested only when necessary, * e.g. When the ticker is started and the emitter has listeners. @@ -22,10 +20,11 @@ constructor() { /** - * Internal emitter used to fire 'tick' event + * The first listener. All new listeners added are chained on this. * @private + * @type {TickerListener} */ - this._emitter = new EventEmitter(); + this._head = new TickerListener(null, null, Infinity); /** * Internal current frame request ID @@ -131,7 +130,7 @@ // Invoke listeners now this.update(time); // Listener side effects may have modified ticker state. - if (this.started && this._requestId === null && this._emitter.listeners(TICK, true)) + if (this.started && this._requestId === null && this._head.next) { this._requestId = requestAnimationFrame(this._tick); } @@ -148,7 +147,7 @@ */ _requestIfNeeded() { - if (this._requestId === null && this._emitter.listeners(TICK, true)) + if (this._requestId === null && this._head.next) { // ensure callbacks get correct delta this.lastTime = performance.now(); @@ -193,35 +192,72 @@ } /** - * Calls {@link module:eventemitter3.EventEmitter#on} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. + * Register a handler for tick events. Calls continuously unless + * it is removed or the ticker is stopped. * * @param {Function} fn - The listener function to be added for updates * @param {Function} [context] - The listener context + * @param {number} [priority=PIXI.UPDATE_PRIORITY.NORMAL] - The priority for emitting * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - add(fn, context) + add(fn, context, priority = UPDATE_PRIORITY.NORMAL) { - this._emitter.on(TICK, fn, context); - - this._startIfPossible(); - - return this; + return this._addListener(new TickerListener(fn, context, priority)); } /** - * Calls {@link module:eventemitter3.EventEmitter#once} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. + * Add a handler for the tick event which is only execute once. * * @param {Function} fn - The listener function to be added for one update * @param {Function} [context] - The listener context + * @param {number} [priority=PIXI.UPDATE_PRIORITY.NORMAL] - The priority for emitting * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - addOnce(fn, context) + addOnce(fn, context, priority = UPDATE_PRIORITY.NORMAL) { - this._emitter.once(TICK, fn, context); + return this._addListener(new TickerListener(fn, context, priority, true)); + } + + /** + * Internally adds the event handler so that it can be sorted by priority. + * Priority allows certain handler (user, AnimatedSprite, Interaction) to be run + * before the rendering. + * + * @private + * @param {TickerListener} listener - Current listener being added. + * @returns {PIXI.ticker.Ticker} This instance of a ticker + */ + _addListener(listener) + { + // For attaching to head + let current = this._head.next; + let previous = this._head; + + // Add the first item + if (!current) + { + listener.connect(previous); + } + else + { + // Go from highest to lowest priority + while (current) + { + if (listener.priority >= current.priority) + { + listener.connect(previous); + break; + } + previous = current; + current = current.next; + } + + // Not yet connected + if (!listener.previous) + { + listener.connect(previous); + } + } this._startIfPossible(); @@ -229,19 +265,33 @@ } /** - * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. - * It checks if the emitter has listeners for 'tick' event. - * If it does, then it cancels the animation frame. + * Removes any handlers matching the function and context parameters. + * If no handlers are left after removing, then it cancels the animation frame. * - * @param {Function} [fn] - The listener function to be removed + * @param {Function} fn - The listener function to be removed * @param {Function} [context] - The listener context to be removed * @returns {PIXI.ticker.Ticker} This instance of a ticker */ remove(fn, context) { - this._emitter.off(TICK, fn, context); + let listener = this._head.next; - if (!this._emitter.listeners(TICK, true)) + while (listener) + { + // We found a match, lets remove it + // no break to delete all possible matches + // incase a listener was added 2+ times + if (listener.match(fn, context)) + { + listener = listener.destroy(); + } + else + { + listener = listener.next; + } + } + + if (!this._head.next) { this._cancelIfNeeded(); } @@ -276,6 +326,25 @@ } /** + * Destroy the ticker and don't use after this. Calling + * this method removes all references to internal events. + */ + destroy() + { + this.stop(); + + let listener = this._head.next; + + while (listener) + { + listener = listener.destroy(true); + } + + this._head.destroy(); + this._head = null; + } + + /** * Triggers an update. An update entails setting the * current {@link PIXI.ticker.Ticker#elapsedMS}, * the current {@link PIXI.ticker.Ticker#deltaTime}, @@ -320,8 +389,22 @@ this.deltaTime = elapsedMS * settings.TARGET_FPMS * this.speed; + // Cache a local reference, in-case ticker is destroyed + // during the emit, we can still check for head.next + const head = this._head; + // Invoke listeners added to internal emitter - this._emitter.emit(TICK, this.deltaTime); + let listener = head.next; + + while (listener) + { + listener = listener.emit(this.deltaTime); + } + + if (!head.next) + { + this._cancelIfNeeded(); + } } else { diff --git a/src/core/ticker/TickerListener.js b/src/core/ticker/TickerListener.js new file mode 100644 index 0000000..2bedb34 --- /dev/null +++ b/src/core/ticker/TickerListener.js @@ -0,0 +1,158 @@ +/** + * Internal class for handling the priority sorting of ticker handlers. + * + * @private + * @class + * @memberof PIXI.ticker + */ +export default class TickerListener +{ + /** + * Constructor + * + * @param {Function} fn - The listener function to be added for one update + * @param {Function} [context=null] - The listener context + * @param {number} [priority=0] - The priority for emitting + * @param {boolean} [once=false] - If the handler should fire once + */ + constructor(fn, context = null, priority = 0, once = false) + { + /** + * The handler function to execute. + * @member {Function} + */ + this.fn = fn; + + /** + * The calling to execute. + * @member {Function} + */ + this.context = context; + + /** + * The current priority. + * @member {number} + */ + this.priority = priority; + + /** + * If this should only execute once. + * @member {boolean} + */ + this.once = once; + + /** + * The next item in chain. + * @member {TickerListener} + */ + this.next = null; + + /** + * The previous item in chain. + * @member {TickerListener} + */ + this.previous = null; + + /** + * `true` if this listener has been destroyed already. + * @member {boolean} + * @private + */ + this._destroyed = false; + } + + /** + * Simple compare function to figure out if a function and context match. + * + * @param {Function} fn - The listener function to be added for one update + * @param {Function} context - The listener context + * @return {boolean} `true` if the listener match the arguments + */ + match(fn, context) + { + context = context || null; + + return this.fn === fn && this.context === context; + } + + /** + * Emit by calling the current function. + * @param {number} deltaTime - time since the last emit. + * @return {TickerListener} Next ticker + */ + emit(deltaTime) + { + if (this.context) + { + this.fn.call(this.context, deltaTime); + } + else + { + this.fn(deltaTime); + } + + if (this.once) + { + this.destroy(); + } + + const redirect = this.next; + + // Soft-destroying should remove + // the next reference + if (this._destroyed) + { + this.next = null; + } + + return redirect; + } + + /** + * Connect to the list. + * @param {TickerListener} previous - Input node, previous listener + */ + connect(previous) + { + this.previous = previous; + if (previous.next) + { + previous.next.previous = this; + } + this.next = previous.next; + previous.next = this; + } + + /** + * Destroy and don't use after this. + * @param {boolean} [hard = false] `true` to remove the `next` reference, this + * is considered a hard destroy. Soft destroy maintains the next reference. + * @return {TickerListener} The listener to redirect while emitting or removing. + */ + destroy(hard = false) + { + this._destroyed = true; + this.fn = null; + this.context = null; + + // Disconnect, hook up next and previous + if (this.previous) + { + this.previous.next = this.next; + } + + if (this.next) + { + this.next.previous = this.previous; + } + + // Redirect to the next item + const redirect = this.previous; + + // Remove references + this.next = hard ? null : redirect; + this.previous = null; + + return redirect; + } +} diff --git a/src/core/ticker/index.js b/src/core/ticker/index.js index 4b3017c..c3020d4 100644 --- a/src/core/ticker/index.js +++ b/src/core/ticker/index.js @@ -45,8 +45,25 @@ const shared = new Ticker(); shared.autoStart = true; +shared.destroy = () => +{ + // protect destroying shared ticker + // this is used by other internal systems + // like AnimatedSprite and InteractionManager +}; /** + * This namespace contains an API for interacting with PIXI's internal global update loop. + * + * This ticker is used for rendering, {@link PIXI.extras.AnimatedSprite AnimatedSprite}, + * {@link PIXI.interaction.InteractionManager InteractionManager} and many other time-based PIXI systems. + * @example + * const ticker = new PIXI.ticker.Ticker(); + * ticker.stop(); + * ticker.add((deltaTime) => { + * // do something every frame + * }); + * ticker.start(); * @namespace PIXI.ticker */ export { shared, Ticker }; diff --git a/.travis.yml b/.travis.yml index 3df8b22..14de334 100644 --- a/.travis.yml +++ b/.travis.yml @@ -110,3 +110,43 @@ on: all_branches: true condition: $TRAVIS_TAG + # Deploy config for latest release + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: dist + upload-dir: "release" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: docs + upload-dir: "release/docs" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: coverage + upload-dir: "release/coverage" + on: + all_branches: true + condition: $TRAVIS_TAG \ No newline at end of file diff --git a/README.md b/README.md index d26fadd..3b85f85 100644 --- a/README.md +++ b/README.md @@ -36,22 +36,14 @@ - Wiki: Other misc tutorials and resources are [on the Wiki](https://github.com/pixijs/pixi.js/wiki/Resources). ### Community ### -- Forums: Check out the [forum] (http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow] (http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. +- Forums: Check out the [forum](http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow](http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. - Inspiration: Check out the [gallery](http://www.pixijs.com/gallery) to see some of the amazing things people have created! - Chat: You can join us on [Gitter](https://gitter.im/pixijs/pixi.js) To chat about Pixi. We also now have a Slack channel. If you would like to join it please Send me an email (mat@goodboydigital.com) and I will invite you in. ### Setup ### -It's easy to get started with Pixi.js! Simply grab the pre-built versions from here: - -Release Branch - Nice and stable Pixi.js -- Unminified: [http://pixijs.download/release/pixi.js] -- Minified: [http://pixijs.download/release/pixi.min.js] - -Develop Branch - The bleeding edge version of Pixi.js -- Unminified: [http://pixijs.download/dev/pixi.js] -- Minified: [http://pixijs.download/dev/pixi.min.js] +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. @@ -70,10 +62,10 @@ #### CDN Install (via cdnjs) ```html - + ``` -_Note: `4.2.2` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ +_Note: `4.4.3` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ ### Demos ### @@ -130,7 +122,7 @@ // and the root stage PIXI.Container. var app = new PIXI.Application(); -// The application will create a canvas element for you that you +// The application will create a canvas element for you that you // can then insert into the DOM. document.body.appendChild(app.view); @@ -150,7 +142,7 @@ // Add the bunny to the scene we are building. app.stage.addChild(bunny); - + // Listen for frame updates app.ticker.add(function() { // each frame we spin the bunny around a bit diff --git a/bower.json b/bower.json deleted file mode 100644 index 41b668c..0000000 --- a/bower.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "pixi.js", - "main": "dist/pixi.min.js", - "ignore": [ - "**/.*", - "docs", - "node_modules", - "bower_components", - "test", - "scripts", - "bower.json", - "inch.json" - ], - "dependencies": { - }, - "devDependencies": { - } -} diff --git a/package.json b/package.json index d35cbb2..308469f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.3.5", + "version": "4.4.4", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ @@ -40,11 +40,11 @@ "lib": "babel src --out-dir lib -s", "predocs": "rimraf docs/**", "docs": "jsdoc -c scripts/jsdoc.conf.json -R README.md", - "publish:patch": "npm version patch --no-git-tag-version && npm publish", - "publish:minor": "npm version minor --no-git-tag-version && npm publish", - "publish:major": "npm version major --no-git-tag-version && npm publish", + "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", - "postpublish": "node scripts/release.js" + "postpublish": "git push && git push --tags" }, "files": [ "dist/", @@ -72,7 +72,6 @@ "electron": "^1.4.15", "eslint": "^3.5.0", "floss": "^2.0.1", - "gh-pages": "^0.11.0", "jaguarjs-jsdoc": "^1.0.1", "js-md5": "^0.4.1", "jsdoc": "^3.4.2", diff --git a/scripts/jsdoc.conf.json b/scripts/jsdoc.conf.json index 553e61c..3f52180 100644 --- a/scripts/jsdoc.conf.json +++ b/scripts/jsdoc.conf.json @@ -43,7 +43,7 @@ }, "markdown" : { "parser" : "gfm", - "hardwrap" : true + "hardwrap" : false }, "opts": { "encoding" : "utf8", diff --git a/scripts/release.js b/scripts/release.js deleted file mode 100644 index 156620c..0000000 --- a/scripts/release.js +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env node -'use strict'; - -// Publish script to push releases of the bin files -// the normally are gitignored -const ghpages = require('gh-pages'); -const path = require('path'); -const packageInfo = require(path.join(__dirname, '..', 'package.json')); - -const options = { - src: [ - 'dist/**/*', - 'lib/**/*', - 'src/**/*', - 'scripts/*', - 'scripts/renders/*', - 'test/**/*', - '*.json', - '*.md', - 'LICENSE', - '.eslintrc', - '.editorconfig', - '.travis.yml', - '.babelrc', - ], - dotfiles: true, - branch: 'release', - message: packageInfo.version, - logger: console.log.bind(console), -}; - -ghpages.publish(process.cwd(), options, (err) => -{ - if (err) - { - console.log(err); - process.exit(1); - - return; - } - - process.exit(0); -}); diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index f16bcb1..220427a 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -21,7 +21,7 @@ const DIV_HOOK_ZINDEX = 2; /** - * The Accessibility manager reacreates the ability to tab and and have content read by screen + * The Accessibility manager recreates the ability to tab and and have content read by screen * readers. This is very important as it can possibly help people with disabilities access pixi * content. * diff --git a/src/accessibility/index.js b/src/accessibility/index.js index 568cfb1..0ae108f 100644 --- a/src/accessibility/index.js +++ b/src/accessibility/index.js @@ -1,4 +1,9 @@ /** + * This namespace contains a renderer plugin for interaction accessibility for end-users + * with physical impairments which require screen-renders, keyboard navigation, etc. + * + * Do not instantiate this plugin directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. * @namespace PIXI.accessibility */ export { default as accessibleTarget } from './accessibleTarget'; diff --git a/src/core/Application.js b/src/core/Application.js index 5340578..2fcc729 100644 --- a/src/core/Application.js +++ b/src/core/Application.js @@ -1,6 +1,8 @@ import { autoDetectRenderer } from './autoDetectRenderer'; import Container from './display/Container'; import { shared, Ticker } from './ticker'; +import settings from './settings'; +import { UPDATE_PRIORITY } from './const'; /** * Convenience class to create a new PIXI application. @@ -22,28 +24,52 @@ */ export default class Application { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experience unexplained flickering try setting this to true. - * @param {boolean} [useSharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedLoader=false] - `true` to use PIXI.loaders.shared, `false` to create new Loader. */ - constructor(width, height, options, noWebGL, useSharedTicker = false) + constructor(options, arg2, arg3, arg4, arg5) { + // Support for constructor(width, height, options, noWebGL, useSharedTicker) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + forceCanvas: !!arg4, + sharedTicker: !!arg5, + }, arg3); + } + + /** + * The default options, so we mixin functionality later. + * @member {object} + * @protected + */ + this._options = options = Object.assign({ + sharedTicker: false, + forceCanvas: false, + sharedLoader: false, + }, options); + /** * WebGL renderer if available, otherwise CanvasRenderer * @member {PIXI.WebGLRenderer|PIXI.CanvasRenderer} */ - this.renderer = autoDetectRenderer(width, height, options, noWebGL); + this.renderer = autoDetectRenderer(options); /** * The root display container that's rendered. @@ -63,7 +89,7 @@ * @member {PIXI.ticker.Ticker} * @default PIXI.ticker.shared */ - this.ticker = useSharedTicker ? shared : new Ticker(); + this.ticker = options.sharedTicker ? shared : new Ticker(); // Start the rendering this.start(); @@ -78,7 +104,7 @@ this._ticker = ticker; if (ticker) { - ticker.add(this.render, this); + ticker.add(this.render, this, UPDATE_PRIORITY.LOW); } } get ticker() // eslint-disable-line require-jsdoc @@ -136,13 +162,18 @@ */ destroy(removeView) { - this.stop(); + const oldTicker = this._ticker; + this.ticker = null; + oldTicker.destroy(); + this.stage.destroy(); this.stage = null; this.renderer.destroy(removeView); this.renderer = null; + + this._options = null; } } diff --git a/src/core/autoDetectRenderer.js b/src/core/autoDetectRenderer.js index 8960425..9ec0364 100644 --- a/src/core/autoDetectRenderer.js +++ b/src/core/autoDetectRenderer.js @@ -2,6 +2,7 @@ import CanvasRenderer from './renderers/canvas/CanvasRenderer'; import WebGLRenderer from './renderers/webgl/WebGLRenderer'; +// eslint-disable-next-line valid-jsdoc /** * This helper function will automatically detect which renderer you should be using. * WebGL is the preferred renderer as it is a lot faster. If webGL is not supported by @@ -9,24 +10,32 @@ * * @memberof PIXI * @function autoDetectRenderer - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @return {PIXI.WebGLRenderer|PIXI.CanvasRenderer} Returns WebGL renderer if available, otherwise CanvasRenderer */ -export function autoDetectRenderer(width = 800, height = 600, options, noWebGL) +export function autoDetectRenderer(options, arg1, arg2, arg3) { - if (!noWebGL && utils.isWebGLSupported()) + // Backward-compatible support for noWebGL option + let forceCanvas = options && options.forceCanvas; + + if (arg3 !== undefined) { - return new WebGLRenderer(width, height, options); + forceCanvas = arg3; } - return new CanvasRenderer(width, height, options); + if (!forceCanvas && utils.isWebGLSupported()) + { + return new WebGLRenderer(options, arg1, arg2); + } + + return new CanvasRenderer(options, arg1, arg2); } diff --git a/src/core/const.js b/src/core/const.js index b9e99ae..2c68dee 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -341,3 +341,27 @@ LINEAR_VERTICAL: 0, LINEAR_HORIZONTAL: 1, }; + +/** + * Represents the update priorities used by internal PIXI classes when registered with + * the {@link PIXI.ticker.Ticker} object. Higher priority items are updated first and lower + * priority items, such as render, should go later. + * + * @static + * @constant + * @name UPDATE_PRIORITY + * @memberof PIXI + * @type {object} + * @property {number} INTERACTION=50 Highest priority, used for {@link PIXI.interaction.InteractionManager} + * @property {number} HIGH=25 High priority updating, {@link PIXI.VideoBaseTexture} and {@link PIXI.extras.AnimatedSprite} + * @property {number} NORMAL=0 Default priority for ticker events, see {@link PIXI.ticker.Ticker#add}. + * @property {number} LOW=-25 Low priority used for {@link PIXI.Application} rendering. + * @property {number} UTILITY=-50 Lowest priority used for {@link PIXI.prepare.BasePrepare} utility. + */ +export const UPDATE_PRIORITY = { + INTERACTION: 50, + HIGH: 25, + NORMAL: 0, + LOW: -25, + UTILITY: -50, +}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 4e6c77d..decb42a 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -13,7 +13,6 @@ * * @class * @extends EventEmitter - * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ export default class DisplayObject extends EventEmitter @@ -113,6 +112,29 @@ * @private */ this._mask = null; + + /** + * If the object has been destroyed via destroy(). If true, it should not be used. + * + * @member {boolean} + * @private + * @readonly + */ + this._destroyed = false; + + /** + * Fired when this DisplayObject is added to a Container. + * + * @event PIXI.DisplayObject#added + * @param {PIXI.Container} container - The container added to. + */ + + /** + * Fired when this DisplayObject is removed from a Container. + * + * @event PIXI.DisplayObject#removed + * @param {PIXI.Container} container - The container removed from. + */ } /** @@ -403,6 +425,8 @@ this.interactive = false; this.interactiveChildren = false; + + this._destroyed = true; } /** diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index aafdfe2..f787c4a 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -792,8 +792,8 @@ sprite.worldAlpha = this.worldAlpha * sprite.alpha; sprite.blendMode = this.blendMode; - sprite.texture._frame.width = rect.width; - sprite.texture._frame.height = rect.height; + sprite._texture._frame.width = rect.width; + sprite._texture._frame.height = rect.height; sprite.transform.worldTransform = this.transform.worldTransform; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index a24c775..b9f192d 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -19,11 +19,12 @@ */ export default class SystemRenderer extends EventEmitter { + // eslint-disable-next-line valid-jsdoc /** * @param {string} system - The name of the system this renderer is for. - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -37,27 +38,31 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(system, screenWidth, screenHeight, options) + constructor(system, options, arg2, arg3) { super(); sayHello(system); - // prepare options - if (options) + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') { - for (const i in settings.RENDER_OPTIONS) - { - if (typeof options[i] === 'undefined') - { - options[i] = settings.RENDER_OPTIONS[i]; - } - } + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); } - else - { - options = settings.RENDER_OPTIONS; - } + + // Add the default render options + options = Object.assign({}, settings.RENDER_OPTIONS, options); + + /** + * The supplied constructor options. + * + * @member {Object} + * @readOnly + */ + this.options = options; /** * The type of the renderer. @@ -75,7 +80,7 @@ * * @member {PIXI.Rectangle} */ - this.screen = new Rectangle(0, 0, screenWidth || 800, screenHeight || 600); + this.screen = new Rectangle(0, 0, options.width, options.height); /** * The canvas element that everything is drawn to @@ -279,6 +284,8 @@ this.blendModes = null; + this.options = null; + this.preserveDrawingBuffer = false; this.clearBeforeRender = false; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index e84e49c..691caf0 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -17,10 +17,11 @@ */ export default class CanvasRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -34,9 +35,9 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('Canvas', screenWidth, screenHeight, options); + super('Canvas', options, arg2, arg3); this.type = RENDERER_TYPE.CANVAS; @@ -96,7 +97,19 @@ this.context = null; this.renderingToScreen = false; - this.resize(screenWidth, screenHeight); + this.resize(this.options.width, this.options.height); + + /** + * Fired after rendering finishes. + * + * @event PIXI.CanvasRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.CanvasRenderer#prerender + */ } /** @@ -299,4 +312,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.CanvasRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.CanvasExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.CanvasPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.CanvasRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index e1cf7cd..f6fe123 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -31,11 +31,12 @@ */ export default class WebGLRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** * - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -55,9 +56,9 @@ * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('WebGL', screenWidth, screenHeight, options); + super('WebGL', options, arg2, arg3); /** * The type of this renderer as a standardised const @@ -89,12 +90,6 @@ }; - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ this._backgroundColorRgba[3] = this.transparent ? 0 : 1; this.globalUniforms = new UniformGroup({ @@ -117,6 +112,13 @@ this.initPlugins(); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ if(options.context) { this.context.initFromContext(options.context); @@ -169,6 +171,25 @@ } return this; + + /** + * Fired after rendering finishes. + * + * @event PIXI.WebGLRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.WebGLRenderer#prerender + */ + + /** + * Fired when the WebGL context is set. + * + * @event PIXI.WebGLRenderer#context + * @param {WebGLRenderingContext} gl - WebGL context. + */ } /** @@ -295,4 +316,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.WebGLRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.WebGLRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(WebGLRenderer); diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index 4eb6128..fa3d883 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -47,6 +47,14 @@ * @member {boolean} */ this.enabled = true; + + /** + * If enabled, pixi will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; } /** diff --git a/src/core/renderers/webgl/systems/ContextSystem.js b/src/core/renderers/webgl/systems/ContextSystem.js index ff958c7..8df85ea 100644 --- a/src/core/renderers/webgl/systems/ContextSystem.js +++ b/src/core/renderers/webgl/systems/ContextSystem.js @@ -75,7 +75,8 @@ */ createContext(canvas, options) { - var gl = canvas.getContext('webgl', options) || + var gl = //canvas.getContext('webgl2', options) || + canvas.getContext('webgl', options) || canvas.getContext('experimental-webgl', options); if (!gl) diff --git a/src/core/renderers/webgl/systems/FilterSystem.js b/src/core/renderers/webgl/systems/FilterSystem.js index f47335d..37e28d2 100644 --- a/src/core/renderers/webgl/systems/FilterSystem.js +++ b/src/core/renderers/webgl/systems/FilterSystem.js @@ -111,7 +111,7 @@ // TODO we should fit the rect around the transform.. } - else + else if (filters[0].autoFit) { sourceFrame.fit(filterData.stack[0].destinationFrame); } diff --git a/src/core/renderers/webgl/systems/shader/shader/mapType.js b/src/core/renderers/webgl/systems/shader/shader/mapType.js index 581dace..917538a 100644 --- a/src/core/renderers/webgl/systems/shader/shader/mapType.js +++ b/src/core/renderers/webgl/systems/shader/shader/mapType.js @@ -15,7 +15,7 @@ } } - return GL_TABLE[type]; + return GL_TABLE[type]; }; var GL_TABLE = null; diff --git a/src/core/settings.js b/src/core/settings.js index 18c5877..38ef45c 100644 --- a/src/core/settings.js +++ b/src/core/settings.js @@ -2,6 +2,15 @@ import canUploadSameBuffer from './utils/canUploadSameBuffer'; /** + * User's customizable globals for overriding the default PIXI settings, such + * as a renderer's default resolution, framerate, float percision, etc. + * @example + * // Use the native window resolution as the default resolution + * // will support high-density displays when rendering + * PIXI.settings.RESOLUTION = window.devicePixelRatio. + * + * // Disable interpolation when scaling, will make texture be pixelated + * PIXI.settings.SCALE_MODE = PIXI.SCALE_MODES.NEAREST; * @namespace PIXI.settings */ export default { @@ -101,6 +110,9 @@ * @property {boolean} clearBeforeRender=true * @property {boolean} preserveDrawingBuffer=false * @property {boolean} roundPixels=false + * @property {number} width=800 + * @property {number} height=600 + * @property {boolean} legacy=false */ RENDER_OPTIONS: { view: null, @@ -112,6 +124,9 @@ clearBeforeRender: true, preserveDrawingBuffer: false, roundPixels: false, + width: 800, + height: 600, + legacy: false, }, /** diff --git a/src/core/shader/Program.js b/src/core/shader/Program.js index c0b538b..07b4398 100644 --- a/src/core/shader/Program.js +++ b/src/core/shader/Program.js @@ -1,6 +1,6 @@ import extractUniformsFromSrc from './extractUniformsFromSrc'; import generateUniformsSync from './generateUniformsSync'; -import glCore from 'pixi-gl-core'; +import shaderUtils from '../renderers/webgl/systems/shader/shader'; import { ProgramCache } from '../utils'; import getTestContext from '../utils/getTestContext'; @@ -64,14 +64,17 @@ } else { - vertexSrc = glCore.shader.setPrecision(vertexSrc, 'mediump'); - fragmentSrc = glCore.shader.setPrecision(fragmentSrc, 'mediump'); + vertexSrc = shaderUtils.setPrecision(vertexSrc, 'mediump'); + fragmentSrc = shaderUtils.setPrecision(fragmentSrc, 'mediump'); - const program = glCore.shader.compileProgram(gl, vertexSrc, fragmentSrc); + const program = shaderUtils.compileProgram(gl, vertexSrc, fragmentSrc); + console.log("<<<<<<>>><<"); this.attributeData = this.getAttributeData(program, gl); this.uniformData = this.getUniformData(program, gl); + console.log(this.uniformData); + //gl.deleteProgram(program); } } @@ -95,13 +98,13 @@ for (let i = 0; i < totalAttributes; i++) { const attribData = gl.getActiveAttrib(program, i); - const type = glCore.shader.mapType(gl, attribData.type); + const type = shaderUtils.mapType(gl, attribData.type); /*eslint-disable */ const data = { type: type, name: attribData.name, - size: glCore.shader.mapSize(type), + size: shaderUtils.mapSize(type), location: 0, }; /*eslint-enable */ @@ -137,13 +140,14 @@ // TODO expose this as a prop? // const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); - const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); + //const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); for (let i = 0; i < totalUniforms; i++) { const uniformData = gl.getActiveUniform(program, i); const name = uniformData.name.replace(/\[.*?\]/, ''); - const type = glCore.shader.mapType(gl, uniformData.type); + const type = shaderUtils.mapType(gl, uniformData.type); + console.log('mapping ' + uniformData.type + ' to ' + type); // if (!name.match(maskRegex)) { @@ -151,7 +155,7 @@ uniforms[name] = { type: type, size: uniformData.size, - value: glCore.shader.defaultValue(type, uniformData.size), + value: shaderUtils.defaultValue(type, uniformData.size), }; /*eslint-enable */ } diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index e387e99..b57d095 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -147,12 +147,12 @@ // so if _width is 0 then width was not set.. if (this._width) { - this.scale.x = sign(this.scale.x) * this._width / this.texture.orig.width; + this.scale.x = sign(this.scale.x) * this._width / this._texture.orig.width; } if (this._height) { - this.scale.y = sign(this.scale.y) * this._height / this.texture.orig.height; + this.scale.y = sign(this.scale.y) * this._height / this._texture.orig.height; } } diff --git a/src/core/sprites/canvas/CanvasTinter.js b/src/core/sprites/canvas/CanvasTinter.js index 27e5e0c..fd9de5d 100644 --- a/src/core/sprites/canvas/CanvasTinter.js +++ b/src/core/sprites/canvas/CanvasTinter.js @@ -18,7 +18,7 @@ */ getTintedTexture: (sprite, color) => { - const texture = sprite.texture; + const texture = sprite._texture; color = CanvasTinter.roundColor(color); diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 5a4edda..8b9b797 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -344,6 +344,7 @@ const texture = this._texture; const style = this._style; + const padding = style.trim ? 0 : style.padding; texture.baseTexture.valid = true; texture.baseTexture.resolution = this.resolution; @@ -353,11 +354,11 @@ texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - texture.trim.x = -style.padding; - texture.trim.y = -style.padding; + texture.trim.x = -padding; + texture.trim.y = -padding; - texture.orig.width = texture._frame.width - (style.padding * 2); - texture.orig.height = texture._frame.height - (style.padding * 2); + texture.orig.width = texture._frame.width - (padding * 2); + texture.orig.height = texture._frame.height - (padding * 2); // call sprite onTextureUpdate to update scale if _width or _height were set this._onTextureUpdate(); diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index 853da3d..8a9c34b 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -114,6 +114,38 @@ this.cacheId = null; this.validate(); + + /** + * Fired when a not-immediately-available source finishes loading. + * + * @protected + * @event PIXI.BaseTexture#loaded + * @param {PIXI.BaseTexture} baseTexture - Resource loaded. + */ + + /** + * Fired when a not-immediately-available source fails to load. + * + * @protected + * @event PIXI.BaseTexture#error + * @param {PIXI.BaseTexture} baseTexture - Resource errored. + */ + + /** + * Fired when BaseTexture is updated. + * + * @protected + * @event PIXI.BaseTexture#update + * @param {PIXI.BaseTexture} baseTexture - Instance of texture being updated. + */ + + /** + * Fired when BaseTexture is destroyed. + * + * @protected + * @event PIXI.BaseTexture#dispose + * @param {PIXI.BaseTexture} baseTexture - Instance of texture being destroyed. + */ } setResource(resource) diff --git a/src/core/textures/Spritesheet.js b/src/core/textures/Spritesheet.js index d469c62..9b24f27 100644 --- a/src/core/textures/Spritesheet.js +++ b/src/core/textures/Spritesheet.js @@ -208,6 +208,7 @@ // lets also add the frame to pixi's global cache for fromFrame and fromImage functions TextureCache[i] = this.textures[i]; + this.textures[i].textureCacheId = i; } frameIndex++; diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 0a2c379..2213087 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -147,9 +147,9 @@ /** * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. * - * @event update - * @memberof PIXI.Texture# + * @event PIXI.Texture#update * @protected + * @param {PIXI.Texture} texture - Instance of texture being updated. */ this._updateID = 0; @@ -159,6 +159,15 @@ * @type {Object} */ this.transform = null; + + /** + * The id under which this Texture has been added to the texture cache. This is + * automatically set in certain cases, but may not always be accurate, particularly if + * the texture is in the cache under multiple ids. + * + * @member {string} + */ + this.textureCacheId = null; } /** @@ -225,11 +234,17 @@ this._uvs = null; this.trim = null; this.orig = null; + this.textureCacheId = null; this.valid = false; - this.off('dispose', this.dispose, this); - this.off('update', this.update, this); + for (const prop in TextureCache) + { + if (TextureCache[prop] === this) + { + delete TextureCache[prop]; + } + } } /** @@ -315,14 +330,15 @@ */ static fromLoader(source, imageUrl, name) { - // console.log('added from loader...') + // console.log('added from loader...') const resource = new ImageResource(source);//.from(imageUrl, crossorigin);// document.createElement('img'); - // console.log('base resource ' + resource.width); + // console.log('base resource ' + resource.width); const baseTexture = new BaseTexture(resource, settings.SCALE_MODE, getResolutionOfUrl(imageUrl)); + /// console.log('base width ' + baseTexture.width); const texture = new Texture(baseTexture); @@ -335,6 +351,7 @@ // lets also add the frame to pixi's global cache for fromFrame and fromImage fucntions BaseTextureCache[name] = baseTexture; TextureCache[name] = texture; + texture.textureCacheId = name; // also add references by url if they are different. if (name !== imageUrl) @@ -355,6 +372,10 @@ */ static addTextureToCache(texture, id) { + if (!texture.textureCacheId) + { + texture.textureCacheId = id; + } TextureCache[id] = texture; } @@ -495,6 +516,7 @@ */ Texture.EMPTY = new Texture(new BaseTexture()); removeAllHandlers(Texture.EMPTY); +removeAllHandlers(Texture.EMPTY.baseTexture); /** * A white texture of 10x10 size, used for graphics and other things @@ -505,3 +527,4 @@ */ Texture.WHITE = createWhiteTexture(); removeAllHandlers(Texture.WHITE); +removeAllHandlers(Texture.WHITE.baseTexture); diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index 59eb9fd..a9b9f51 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -1,6 +1,7 @@ import BaseTexture from './BaseTexture'; import { uid, BaseTextureCache } from '../utils'; -import * as ticker from '../ticker'; +import { shared } from '../ticker'; +import { UPDATE_PRIORITY } from '../const'; /** * A texture of a [playing] Video. @@ -125,7 +126,7 @@ if (!this._isAutoUpdating && this.autoUpdate) { - ticker.shared.add(this.update, this); + shared.add(this.update, this, UPDATE_PRIORITY.HIGH); this._isAutoUpdating = true; } } @@ -139,7 +140,7 @@ { if (this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); this._isAutoUpdating = false; } } @@ -187,7 +188,7 @@ { if (this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); } if (this.source && this.source._pixiId) @@ -281,12 +282,12 @@ if (!this._autoUpdate && this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); this._isAutoUpdating = false; } else if (this._autoUpdate && !this._isAutoUpdating) { - ticker.shared.add(this.update, this); + shared.add(this.update, this, UPDATE_PRIORITY.HIGH); this._isAutoUpdating = true; } } diff --git a/src/core/ticker/Ticker.js b/src/core/ticker/Ticker.js index 17a1517..40df138 100644 --- a/src/core/ticker/Ticker.js +++ b/src/core/ticker/Ticker.js @@ -1,12 +1,10 @@ import settings from '../settings'; -import EventEmitter from 'eventemitter3'; - -// Internal event used by composed emitter -const TICK = 'tick'; +import { UPDATE_PRIORITY } from '../const'; +import TickerListener from './TickerListener'; /** * A Ticker class that runs an update loop that other objects listen to. - * This class is composed around an EventEmitter object to add listeners + * This class is composed around listeners * meant for execution on the next requested animation frame. * Animation frames are requested only when necessary, * e.g. When the ticker is started and the emitter has listeners. @@ -22,10 +20,11 @@ constructor() { /** - * Internal emitter used to fire 'tick' event + * The first listener. All new listeners added are chained on this. * @private + * @type {TickerListener} */ - this._emitter = new EventEmitter(); + this._head = new TickerListener(null, null, Infinity); /** * Internal current frame request ID @@ -131,7 +130,7 @@ // Invoke listeners now this.update(time); // Listener side effects may have modified ticker state. - if (this.started && this._requestId === null && this._emitter.listeners(TICK, true)) + if (this.started && this._requestId === null && this._head.next) { this._requestId = requestAnimationFrame(this._tick); } @@ -148,7 +147,7 @@ */ _requestIfNeeded() { - if (this._requestId === null && this._emitter.listeners(TICK, true)) + if (this._requestId === null && this._head.next) { // ensure callbacks get correct delta this.lastTime = performance.now(); @@ -193,35 +192,72 @@ } /** - * Calls {@link module:eventemitter3.EventEmitter#on} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. + * Register a handler for tick events. Calls continuously unless + * it is removed or the ticker is stopped. * * @param {Function} fn - The listener function to be added for updates * @param {Function} [context] - The listener context + * @param {number} [priority=PIXI.UPDATE_PRIORITY.NORMAL] - The priority for emitting * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - add(fn, context) + add(fn, context, priority = UPDATE_PRIORITY.NORMAL) { - this._emitter.on(TICK, fn, context); - - this._startIfPossible(); - - return this; + return this._addListener(new TickerListener(fn, context, priority)); } /** - * Calls {@link module:eventemitter3.EventEmitter#once} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. + * Add a handler for the tick event which is only execute once. * * @param {Function} fn - The listener function to be added for one update * @param {Function} [context] - The listener context + * @param {number} [priority=PIXI.UPDATE_PRIORITY.NORMAL] - The priority for emitting * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - addOnce(fn, context) + addOnce(fn, context, priority = UPDATE_PRIORITY.NORMAL) { - this._emitter.once(TICK, fn, context); + return this._addListener(new TickerListener(fn, context, priority, true)); + } + + /** + * Internally adds the event handler so that it can be sorted by priority. + * Priority allows certain handler (user, AnimatedSprite, Interaction) to be run + * before the rendering. + * + * @private + * @param {TickerListener} listener - Current listener being added. + * @returns {PIXI.ticker.Ticker} This instance of a ticker + */ + _addListener(listener) + { + // For attaching to head + let current = this._head.next; + let previous = this._head; + + // Add the first item + if (!current) + { + listener.connect(previous); + } + else + { + // Go from highest to lowest priority + while (current) + { + if (listener.priority >= current.priority) + { + listener.connect(previous); + break; + } + previous = current; + current = current.next; + } + + // Not yet connected + if (!listener.previous) + { + listener.connect(previous); + } + } this._startIfPossible(); @@ -229,19 +265,33 @@ } /** - * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. - * It checks if the emitter has listeners for 'tick' event. - * If it does, then it cancels the animation frame. + * Removes any handlers matching the function and context parameters. + * If no handlers are left after removing, then it cancels the animation frame. * - * @param {Function} [fn] - The listener function to be removed + * @param {Function} fn - The listener function to be removed * @param {Function} [context] - The listener context to be removed * @returns {PIXI.ticker.Ticker} This instance of a ticker */ remove(fn, context) { - this._emitter.off(TICK, fn, context); + let listener = this._head.next; - if (!this._emitter.listeners(TICK, true)) + while (listener) + { + // We found a match, lets remove it + // no break to delete all possible matches + // incase a listener was added 2+ times + if (listener.match(fn, context)) + { + listener = listener.destroy(); + } + else + { + listener = listener.next; + } + } + + if (!this._head.next) { this._cancelIfNeeded(); } @@ -276,6 +326,25 @@ } /** + * Destroy the ticker and don't use after this. Calling + * this method removes all references to internal events. + */ + destroy() + { + this.stop(); + + let listener = this._head.next; + + while (listener) + { + listener = listener.destroy(true); + } + + this._head.destroy(); + this._head = null; + } + + /** * Triggers an update. An update entails setting the * current {@link PIXI.ticker.Ticker#elapsedMS}, * the current {@link PIXI.ticker.Ticker#deltaTime}, @@ -320,8 +389,22 @@ this.deltaTime = elapsedMS * settings.TARGET_FPMS * this.speed; + // Cache a local reference, in-case ticker is destroyed + // during the emit, we can still check for head.next + const head = this._head; + // Invoke listeners added to internal emitter - this._emitter.emit(TICK, this.deltaTime); + let listener = head.next; + + while (listener) + { + listener = listener.emit(this.deltaTime); + } + + if (!head.next) + { + this._cancelIfNeeded(); + } } else { diff --git a/src/core/ticker/TickerListener.js b/src/core/ticker/TickerListener.js new file mode 100644 index 0000000..2bedb34 --- /dev/null +++ b/src/core/ticker/TickerListener.js @@ -0,0 +1,158 @@ +/** + * Internal class for handling the priority sorting of ticker handlers. + * + * @private + * @class + * @memberof PIXI.ticker + */ +export default class TickerListener +{ + /** + * Constructor + * + * @param {Function} fn - The listener function to be added for one update + * @param {Function} [context=null] - The listener context + * @param {number} [priority=0] - The priority for emitting + * @param {boolean} [once=false] - If the handler should fire once + */ + constructor(fn, context = null, priority = 0, once = false) + { + /** + * The handler function to execute. + * @member {Function} + */ + this.fn = fn; + + /** + * The calling to execute. + * @member {Function} + */ + this.context = context; + + /** + * The current priority. + * @member {number} + */ + this.priority = priority; + + /** + * If this should only execute once. + * @member {boolean} + */ + this.once = once; + + /** + * The next item in chain. + * @member {TickerListener} + */ + this.next = null; + + /** + * The previous item in chain. + * @member {TickerListener} + */ + this.previous = null; + + /** + * `true` if this listener has been destroyed already. + * @member {boolean} + * @private + */ + this._destroyed = false; + } + + /** + * Simple compare function to figure out if a function and context match. + * + * @param {Function} fn - The listener function to be added for one update + * @param {Function} context - The listener context + * @return {boolean} `true` if the listener match the arguments + */ + match(fn, context) + { + context = context || null; + + return this.fn === fn && this.context === context; + } + + /** + * Emit by calling the current function. + * @param {number} deltaTime - time since the last emit. + * @return {TickerListener} Next ticker + */ + emit(deltaTime) + { + if (this.context) + { + this.fn.call(this.context, deltaTime); + } + else + { + this.fn(deltaTime); + } + + if (this.once) + { + this.destroy(); + } + + const redirect = this.next; + + // Soft-destroying should remove + // the next reference + if (this._destroyed) + { + this.next = null; + } + + return redirect; + } + + /** + * Connect to the list. + * @param {TickerListener} previous - Input node, previous listener + */ + connect(previous) + { + this.previous = previous; + if (previous.next) + { + previous.next.previous = this; + } + this.next = previous.next; + previous.next = this; + } + + /** + * Destroy and don't use after this. + * @param {boolean} [hard = false] `true` to remove the `next` reference, this + * is considered a hard destroy. Soft destroy maintains the next reference. + * @return {TickerListener} The listener to redirect while emitting or removing. + */ + destroy(hard = false) + { + this._destroyed = true; + this.fn = null; + this.context = null; + + // Disconnect, hook up next and previous + if (this.previous) + { + this.previous.next = this.next; + } + + if (this.next) + { + this.next.previous = this.previous; + } + + // Redirect to the next item + const redirect = this.previous; + + // Remove references + this.next = hard ? null : redirect; + this.previous = null; + + return redirect; + } +} diff --git a/src/core/ticker/index.js b/src/core/ticker/index.js index 4b3017c..c3020d4 100644 --- a/src/core/ticker/index.js +++ b/src/core/ticker/index.js @@ -45,8 +45,25 @@ const shared = new Ticker(); shared.autoStart = true; +shared.destroy = () => +{ + // protect destroying shared ticker + // this is used by other internal systems + // like AnimatedSprite and InteractionManager +}; /** + * This namespace contains an API for interacting with PIXI's internal global update loop. + * + * This ticker is used for rendering, {@link PIXI.extras.AnimatedSprite AnimatedSprite}, + * {@link PIXI.interaction.InteractionManager InteractionManager} and many other time-based PIXI systems. + * @example + * const ticker = new PIXI.ticker.Ticker(); + * ticker.stop(); + * ticker.add((deltaTime) => { + * // do something every frame + * }); + * ticker.start(); * @namespace PIXI.ticker */ export { shared, Ticker }; diff --git a/src/core/utils/index.js b/src/core/utils/index.js index be44750..7441f7b 100644 --- a/src/core/utils/index.js +++ b/src/core/utils/index.js @@ -9,6 +9,21 @@ let saidHello = false; /** + * Generalized convenience utilities for PIXI. + * @example + * // Extend PIXI's internal Event Emitter. + * class MyEmitter extends PIXI.utils.EventEmitter { + * constructor() { + * super(); + * console.log("Emitter created!"); + * } + * } + * + * // Get info on current device + * console.log(PIXI.utils.isMobile); + * + * // Convert hex color to string + * console.log(PIXI.utils.hex2string(0xff00ff)); // returns: "#ff00ff" * @namespace PIXI.utils */ export { diff --git a/.travis.yml b/.travis.yml index 3df8b22..14de334 100644 --- a/.travis.yml +++ b/.travis.yml @@ -110,3 +110,43 @@ on: all_branches: true condition: $TRAVIS_TAG + # Deploy config for latest release + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: dist + upload-dir: "release" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: docs + upload-dir: "release/docs" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: coverage + upload-dir: "release/coverage" + on: + all_branches: true + condition: $TRAVIS_TAG \ No newline at end of file diff --git a/README.md b/README.md index d26fadd..3b85f85 100644 --- a/README.md +++ b/README.md @@ -36,22 +36,14 @@ - Wiki: Other misc tutorials and resources are [on the Wiki](https://github.com/pixijs/pixi.js/wiki/Resources). ### Community ### -- Forums: Check out the [forum] (http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow] (http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. +- Forums: Check out the [forum](http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow](http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. - Inspiration: Check out the [gallery](http://www.pixijs.com/gallery) to see some of the amazing things people have created! - Chat: You can join us on [Gitter](https://gitter.im/pixijs/pixi.js) To chat about Pixi. We also now have a Slack channel. If you would like to join it please Send me an email (mat@goodboydigital.com) and I will invite you in. ### Setup ### -It's easy to get started with Pixi.js! Simply grab the pre-built versions from here: - -Release Branch - Nice and stable Pixi.js -- Unminified: [http://pixijs.download/release/pixi.js] -- Minified: [http://pixijs.download/release/pixi.min.js] - -Develop Branch - The bleeding edge version of Pixi.js -- Unminified: [http://pixijs.download/dev/pixi.js] -- Minified: [http://pixijs.download/dev/pixi.min.js] +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. @@ -70,10 +62,10 @@ #### CDN Install (via cdnjs) ```html - + ``` -_Note: `4.2.2` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ +_Note: `4.4.3` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ ### Demos ### @@ -130,7 +122,7 @@ // and the root stage PIXI.Container. var app = new PIXI.Application(); -// The application will create a canvas element for you that you +// The application will create a canvas element for you that you // can then insert into the DOM. document.body.appendChild(app.view); @@ -150,7 +142,7 @@ // Add the bunny to the scene we are building. app.stage.addChild(bunny); - + // Listen for frame updates app.ticker.add(function() { // each frame we spin the bunny around a bit diff --git a/bower.json b/bower.json deleted file mode 100644 index 41b668c..0000000 --- a/bower.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "pixi.js", - "main": "dist/pixi.min.js", - "ignore": [ - "**/.*", - "docs", - "node_modules", - "bower_components", - "test", - "scripts", - "bower.json", - "inch.json" - ], - "dependencies": { - }, - "devDependencies": { - } -} diff --git a/package.json b/package.json index d35cbb2..308469f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.3.5", + "version": "4.4.4", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ @@ -40,11 +40,11 @@ "lib": "babel src --out-dir lib -s", "predocs": "rimraf docs/**", "docs": "jsdoc -c scripts/jsdoc.conf.json -R README.md", - "publish:patch": "npm version patch --no-git-tag-version && npm publish", - "publish:minor": "npm version minor --no-git-tag-version && npm publish", - "publish:major": "npm version major --no-git-tag-version && npm publish", + "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", - "postpublish": "node scripts/release.js" + "postpublish": "git push && git push --tags" }, "files": [ "dist/", @@ -72,7 +72,6 @@ "electron": "^1.4.15", "eslint": "^3.5.0", "floss": "^2.0.1", - "gh-pages": "^0.11.0", "jaguarjs-jsdoc": "^1.0.1", "js-md5": "^0.4.1", "jsdoc": "^3.4.2", diff --git a/scripts/jsdoc.conf.json b/scripts/jsdoc.conf.json index 553e61c..3f52180 100644 --- a/scripts/jsdoc.conf.json +++ b/scripts/jsdoc.conf.json @@ -43,7 +43,7 @@ }, "markdown" : { "parser" : "gfm", - "hardwrap" : true + "hardwrap" : false }, "opts": { "encoding" : "utf8", diff --git a/scripts/release.js b/scripts/release.js deleted file mode 100644 index 156620c..0000000 --- a/scripts/release.js +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env node -'use strict'; - -// Publish script to push releases of the bin files -// the normally are gitignored -const ghpages = require('gh-pages'); -const path = require('path'); -const packageInfo = require(path.join(__dirname, '..', 'package.json')); - -const options = { - src: [ - 'dist/**/*', - 'lib/**/*', - 'src/**/*', - 'scripts/*', - 'scripts/renders/*', - 'test/**/*', - '*.json', - '*.md', - 'LICENSE', - '.eslintrc', - '.editorconfig', - '.travis.yml', - '.babelrc', - ], - dotfiles: true, - branch: 'release', - message: packageInfo.version, - logger: console.log.bind(console), -}; - -ghpages.publish(process.cwd(), options, (err) => -{ - if (err) - { - console.log(err); - process.exit(1); - - return; - } - - process.exit(0); -}); diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index f16bcb1..220427a 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -21,7 +21,7 @@ const DIV_HOOK_ZINDEX = 2; /** - * The Accessibility manager reacreates the ability to tab and and have content read by screen + * The Accessibility manager recreates the ability to tab and and have content read by screen * readers. This is very important as it can possibly help people with disabilities access pixi * content. * diff --git a/src/accessibility/index.js b/src/accessibility/index.js index 568cfb1..0ae108f 100644 --- a/src/accessibility/index.js +++ b/src/accessibility/index.js @@ -1,4 +1,9 @@ /** + * This namespace contains a renderer plugin for interaction accessibility for end-users + * with physical impairments which require screen-renders, keyboard navigation, etc. + * + * Do not instantiate this plugin directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. * @namespace PIXI.accessibility */ export { default as accessibleTarget } from './accessibleTarget'; diff --git a/src/core/Application.js b/src/core/Application.js index 5340578..2fcc729 100644 --- a/src/core/Application.js +++ b/src/core/Application.js @@ -1,6 +1,8 @@ import { autoDetectRenderer } from './autoDetectRenderer'; import Container from './display/Container'; import { shared, Ticker } from './ticker'; +import settings from './settings'; +import { UPDATE_PRIORITY } from './const'; /** * Convenience class to create a new PIXI application. @@ -22,28 +24,52 @@ */ export default class Application { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experience unexplained flickering try setting this to true. - * @param {boolean} [useSharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedLoader=false] - `true` to use PIXI.loaders.shared, `false` to create new Loader. */ - constructor(width, height, options, noWebGL, useSharedTicker = false) + constructor(options, arg2, arg3, arg4, arg5) { + // Support for constructor(width, height, options, noWebGL, useSharedTicker) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + forceCanvas: !!arg4, + sharedTicker: !!arg5, + }, arg3); + } + + /** + * The default options, so we mixin functionality later. + * @member {object} + * @protected + */ + this._options = options = Object.assign({ + sharedTicker: false, + forceCanvas: false, + sharedLoader: false, + }, options); + /** * WebGL renderer if available, otherwise CanvasRenderer * @member {PIXI.WebGLRenderer|PIXI.CanvasRenderer} */ - this.renderer = autoDetectRenderer(width, height, options, noWebGL); + this.renderer = autoDetectRenderer(options); /** * The root display container that's rendered. @@ -63,7 +89,7 @@ * @member {PIXI.ticker.Ticker} * @default PIXI.ticker.shared */ - this.ticker = useSharedTicker ? shared : new Ticker(); + this.ticker = options.sharedTicker ? shared : new Ticker(); // Start the rendering this.start(); @@ -78,7 +104,7 @@ this._ticker = ticker; if (ticker) { - ticker.add(this.render, this); + ticker.add(this.render, this, UPDATE_PRIORITY.LOW); } } get ticker() // eslint-disable-line require-jsdoc @@ -136,13 +162,18 @@ */ destroy(removeView) { - this.stop(); + const oldTicker = this._ticker; + this.ticker = null; + oldTicker.destroy(); + this.stage.destroy(); this.stage = null; this.renderer.destroy(removeView); this.renderer = null; + + this._options = null; } } diff --git a/src/core/autoDetectRenderer.js b/src/core/autoDetectRenderer.js index 8960425..9ec0364 100644 --- a/src/core/autoDetectRenderer.js +++ b/src/core/autoDetectRenderer.js @@ -2,6 +2,7 @@ import CanvasRenderer from './renderers/canvas/CanvasRenderer'; import WebGLRenderer from './renderers/webgl/WebGLRenderer'; +// eslint-disable-next-line valid-jsdoc /** * This helper function will automatically detect which renderer you should be using. * WebGL is the preferred renderer as it is a lot faster. If webGL is not supported by @@ -9,24 +10,32 @@ * * @memberof PIXI * @function autoDetectRenderer - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @return {PIXI.WebGLRenderer|PIXI.CanvasRenderer} Returns WebGL renderer if available, otherwise CanvasRenderer */ -export function autoDetectRenderer(width = 800, height = 600, options, noWebGL) +export function autoDetectRenderer(options, arg1, arg2, arg3) { - if (!noWebGL && utils.isWebGLSupported()) + // Backward-compatible support for noWebGL option + let forceCanvas = options && options.forceCanvas; + + if (arg3 !== undefined) { - return new WebGLRenderer(width, height, options); + forceCanvas = arg3; } - return new CanvasRenderer(width, height, options); + if (!forceCanvas && utils.isWebGLSupported()) + { + return new WebGLRenderer(options, arg1, arg2); + } + + return new CanvasRenderer(options, arg1, arg2); } diff --git a/src/core/const.js b/src/core/const.js index b9e99ae..2c68dee 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -341,3 +341,27 @@ LINEAR_VERTICAL: 0, LINEAR_HORIZONTAL: 1, }; + +/** + * Represents the update priorities used by internal PIXI classes when registered with + * the {@link PIXI.ticker.Ticker} object. Higher priority items are updated first and lower + * priority items, such as render, should go later. + * + * @static + * @constant + * @name UPDATE_PRIORITY + * @memberof PIXI + * @type {object} + * @property {number} INTERACTION=50 Highest priority, used for {@link PIXI.interaction.InteractionManager} + * @property {number} HIGH=25 High priority updating, {@link PIXI.VideoBaseTexture} and {@link PIXI.extras.AnimatedSprite} + * @property {number} NORMAL=0 Default priority for ticker events, see {@link PIXI.ticker.Ticker#add}. + * @property {number} LOW=-25 Low priority used for {@link PIXI.Application} rendering. + * @property {number} UTILITY=-50 Lowest priority used for {@link PIXI.prepare.BasePrepare} utility. + */ +export const UPDATE_PRIORITY = { + INTERACTION: 50, + HIGH: 25, + NORMAL: 0, + LOW: -25, + UTILITY: -50, +}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 4e6c77d..decb42a 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -13,7 +13,6 @@ * * @class * @extends EventEmitter - * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ export default class DisplayObject extends EventEmitter @@ -113,6 +112,29 @@ * @private */ this._mask = null; + + /** + * If the object has been destroyed via destroy(). If true, it should not be used. + * + * @member {boolean} + * @private + * @readonly + */ + this._destroyed = false; + + /** + * Fired when this DisplayObject is added to a Container. + * + * @event PIXI.DisplayObject#added + * @param {PIXI.Container} container - The container added to. + */ + + /** + * Fired when this DisplayObject is removed from a Container. + * + * @event PIXI.DisplayObject#removed + * @param {PIXI.Container} container - The container removed from. + */ } /** @@ -403,6 +425,8 @@ this.interactive = false; this.interactiveChildren = false; + + this._destroyed = true; } /** diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index aafdfe2..f787c4a 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -792,8 +792,8 @@ sprite.worldAlpha = this.worldAlpha * sprite.alpha; sprite.blendMode = this.blendMode; - sprite.texture._frame.width = rect.width; - sprite.texture._frame.height = rect.height; + sprite._texture._frame.width = rect.width; + sprite._texture._frame.height = rect.height; sprite.transform.worldTransform = this.transform.worldTransform; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index a24c775..b9f192d 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -19,11 +19,12 @@ */ export default class SystemRenderer extends EventEmitter { + // eslint-disable-next-line valid-jsdoc /** * @param {string} system - The name of the system this renderer is for. - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -37,27 +38,31 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(system, screenWidth, screenHeight, options) + constructor(system, options, arg2, arg3) { super(); sayHello(system); - // prepare options - if (options) + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') { - for (const i in settings.RENDER_OPTIONS) - { - if (typeof options[i] === 'undefined') - { - options[i] = settings.RENDER_OPTIONS[i]; - } - } + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); } - else - { - options = settings.RENDER_OPTIONS; - } + + // Add the default render options + options = Object.assign({}, settings.RENDER_OPTIONS, options); + + /** + * The supplied constructor options. + * + * @member {Object} + * @readOnly + */ + this.options = options; /** * The type of the renderer. @@ -75,7 +80,7 @@ * * @member {PIXI.Rectangle} */ - this.screen = new Rectangle(0, 0, screenWidth || 800, screenHeight || 600); + this.screen = new Rectangle(0, 0, options.width, options.height); /** * The canvas element that everything is drawn to @@ -279,6 +284,8 @@ this.blendModes = null; + this.options = null; + this.preserveDrawingBuffer = false; this.clearBeforeRender = false; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index e84e49c..691caf0 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -17,10 +17,11 @@ */ export default class CanvasRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -34,9 +35,9 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('Canvas', screenWidth, screenHeight, options); + super('Canvas', options, arg2, arg3); this.type = RENDERER_TYPE.CANVAS; @@ -96,7 +97,19 @@ this.context = null; this.renderingToScreen = false; - this.resize(screenWidth, screenHeight); + this.resize(this.options.width, this.options.height); + + /** + * Fired after rendering finishes. + * + * @event PIXI.CanvasRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.CanvasRenderer#prerender + */ } /** @@ -299,4 +312,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.CanvasRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.CanvasExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.CanvasPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.CanvasRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index e1cf7cd..f6fe123 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -31,11 +31,12 @@ */ export default class WebGLRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** * - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -55,9 +56,9 @@ * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('WebGL', screenWidth, screenHeight, options); + super('WebGL', options, arg2, arg3); /** * The type of this renderer as a standardised const @@ -89,12 +90,6 @@ }; - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ this._backgroundColorRgba[3] = this.transparent ? 0 : 1; this.globalUniforms = new UniformGroup({ @@ -117,6 +112,13 @@ this.initPlugins(); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ if(options.context) { this.context.initFromContext(options.context); @@ -169,6 +171,25 @@ } return this; + + /** + * Fired after rendering finishes. + * + * @event PIXI.WebGLRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.WebGLRenderer#prerender + */ + + /** + * Fired when the WebGL context is set. + * + * @event PIXI.WebGLRenderer#context + * @param {WebGLRenderingContext} gl - WebGL context. + */ } /** @@ -295,4 +316,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.WebGLRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.WebGLRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(WebGLRenderer); diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index 4eb6128..fa3d883 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -47,6 +47,14 @@ * @member {boolean} */ this.enabled = true; + + /** + * If enabled, pixi will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; } /** diff --git a/src/core/renderers/webgl/systems/ContextSystem.js b/src/core/renderers/webgl/systems/ContextSystem.js index ff958c7..8df85ea 100644 --- a/src/core/renderers/webgl/systems/ContextSystem.js +++ b/src/core/renderers/webgl/systems/ContextSystem.js @@ -75,7 +75,8 @@ */ createContext(canvas, options) { - var gl = canvas.getContext('webgl', options) || + var gl = //canvas.getContext('webgl2', options) || + canvas.getContext('webgl', options) || canvas.getContext('experimental-webgl', options); if (!gl) diff --git a/src/core/renderers/webgl/systems/FilterSystem.js b/src/core/renderers/webgl/systems/FilterSystem.js index f47335d..37e28d2 100644 --- a/src/core/renderers/webgl/systems/FilterSystem.js +++ b/src/core/renderers/webgl/systems/FilterSystem.js @@ -111,7 +111,7 @@ // TODO we should fit the rect around the transform.. } - else + else if (filters[0].autoFit) { sourceFrame.fit(filterData.stack[0].destinationFrame); } diff --git a/src/core/renderers/webgl/systems/shader/shader/mapType.js b/src/core/renderers/webgl/systems/shader/shader/mapType.js index 581dace..917538a 100644 --- a/src/core/renderers/webgl/systems/shader/shader/mapType.js +++ b/src/core/renderers/webgl/systems/shader/shader/mapType.js @@ -15,7 +15,7 @@ } } - return GL_TABLE[type]; + return GL_TABLE[type]; }; var GL_TABLE = null; diff --git a/src/core/settings.js b/src/core/settings.js index 18c5877..38ef45c 100644 --- a/src/core/settings.js +++ b/src/core/settings.js @@ -2,6 +2,15 @@ import canUploadSameBuffer from './utils/canUploadSameBuffer'; /** + * User's customizable globals for overriding the default PIXI settings, such + * as a renderer's default resolution, framerate, float percision, etc. + * @example + * // Use the native window resolution as the default resolution + * // will support high-density displays when rendering + * PIXI.settings.RESOLUTION = window.devicePixelRatio. + * + * // Disable interpolation when scaling, will make texture be pixelated + * PIXI.settings.SCALE_MODE = PIXI.SCALE_MODES.NEAREST; * @namespace PIXI.settings */ export default { @@ -101,6 +110,9 @@ * @property {boolean} clearBeforeRender=true * @property {boolean} preserveDrawingBuffer=false * @property {boolean} roundPixels=false + * @property {number} width=800 + * @property {number} height=600 + * @property {boolean} legacy=false */ RENDER_OPTIONS: { view: null, @@ -112,6 +124,9 @@ clearBeforeRender: true, preserveDrawingBuffer: false, roundPixels: false, + width: 800, + height: 600, + legacy: false, }, /** diff --git a/src/core/shader/Program.js b/src/core/shader/Program.js index c0b538b..07b4398 100644 --- a/src/core/shader/Program.js +++ b/src/core/shader/Program.js @@ -1,6 +1,6 @@ import extractUniformsFromSrc from './extractUniformsFromSrc'; import generateUniformsSync from './generateUniformsSync'; -import glCore from 'pixi-gl-core'; +import shaderUtils from '../renderers/webgl/systems/shader/shader'; import { ProgramCache } from '../utils'; import getTestContext from '../utils/getTestContext'; @@ -64,14 +64,17 @@ } else { - vertexSrc = glCore.shader.setPrecision(vertexSrc, 'mediump'); - fragmentSrc = glCore.shader.setPrecision(fragmentSrc, 'mediump'); + vertexSrc = shaderUtils.setPrecision(vertexSrc, 'mediump'); + fragmentSrc = shaderUtils.setPrecision(fragmentSrc, 'mediump'); - const program = glCore.shader.compileProgram(gl, vertexSrc, fragmentSrc); + const program = shaderUtils.compileProgram(gl, vertexSrc, fragmentSrc); + console.log("<<<<<<>>><<"); this.attributeData = this.getAttributeData(program, gl); this.uniformData = this.getUniformData(program, gl); + console.log(this.uniformData); + //gl.deleteProgram(program); } } @@ -95,13 +98,13 @@ for (let i = 0; i < totalAttributes; i++) { const attribData = gl.getActiveAttrib(program, i); - const type = glCore.shader.mapType(gl, attribData.type); + const type = shaderUtils.mapType(gl, attribData.type); /*eslint-disable */ const data = { type: type, name: attribData.name, - size: glCore.shader.mapSize(type), + size: shaderUtils.mapSize(type), location: 0, }; /*eslint-enable */ @@ -137,13 +140,14 @@ // TODO expose this as a prop? // const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); - const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); + //const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); for (let i = 0; i < totalUniforms; i++) { const uniformData = gl.getActiveUniform(program, i); const name = uniformData.name.replace(/\[.*?\]/, ''); - const type = glCore.shader.mapType(gl, uniformData.type); + const type = shaderUtils.mapType(gl, uniformData.type); + console.log('mapping ' + uniformData.type + ' to ' + type); // if (!name.match(maskRegex)) { @@ -151,7 +155,7 @@ uniforms[name] = { type: type, size: uniformData.size, - value: glCore.shader.defaultValue(type, uniformData.size), + value: shaderUtils.defaultValue(type, uniformData.size), }; /*eslint-enable */ } diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index e387e99..b57d095 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -147,12 +147,12 @@ // so if _width is 0 then width was not set.. if (this._width) { - this.scale.x = sign(this.scale.x) * this._width / this.texture.orig.width; + this.scale.x = sign(this.scale.x) * this._width / this._texture.orig.width; } if (this._height) { - this.scale.y = sign(this.scale.y) * this._height / this.texture.orig.height; + this.scale.y = sign(this.scale.y) * this._height / this._texture.orig.height; } } diff --git a/src/core/sprites/canvas/CanvasTinter.js b/src/core/sprites/canvas/CanvasTinter.js index 27e5e0c..fd9de5d 100644 --- a/src/core/sprites/canvas/CanvasTinter.js +++ b/src/core/sprites/canvas/CanvasTinter.js @@ -18,7 +18,7 @@ */ getTintedTexture: (sprite, color) => { - const texture = sprite.texture; + const texture = sprite._texture; color = CanvasTinter.roundColor(color); diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 5a4edda..8b9b797 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -344,6 +344,7 @@ const texture = this._texture; const style = this._style; + const padding = style.trim ? 0 : style.padding; texture.baseTexture.valid = true; texture.baseTexture.resolution = this.resolution; @@ -353,11 +354,11 @@ texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - texture.trim.x = -style.padding; - texture.trim.y = -style.padding; + texture.trim.x = -padding; + texture.trim.y = -padding; - texture.orig.width = texture._frame.width - (style.padding * 2); - texture.orig.height = texture._frame.height - (style.padding * 2); + texture.orig.width = texture._frame.width - (padding * 2); + texture.orig.height = texture._frame.height - (padding * 2); // call sprite onTextureUpdate to update scale if _width or _height were set this._onTextureUpdate(); diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index 853da3d..8a9c34b 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -114,6 +114,38 @@ this.cacheId = null; this.validate(); + + /** + * Fired when a not-immediately-available source finishes loading. + * + * @protected + * @event PIXI.BaseTexture#loaded + * @param {PIXI.BaseTexture} baseTexture - Resource loaded. + */ + + /** + * Fired when a not-immediately-available source fails to load. + * + * @protected + * @event PIXI.BaseTexture#error + * @param {PIXI.BaseTexture} baseTexture - Resource errored. + */ + + /** + * Fired when BaseTexture is updated. + * + * @protected + * @event PIXI.BaseTexture#update + * @param {PIXI.BaseTexture} baseTexture - Instance of texture being updated. + */ + + /** + * Fired when BaseTexture is destroyed. + * + * @protected + * @event PIXI.BaseTexture#dispose + * @param {PIXI.BaseTexture} baseTexture - Instance of texture being destroyed. + */ } setResource(resource) diff --git a/src/core/textures/Spritesheet.js b/src/core/textures/Spritesheet.js index d469c62..9b24f27 100644 --- a/src/core/textures/Spritesheet.js +++ b/src/core/textures/Spritesheet.js @@ -208,6 +208,7 @@ // lets also add the frame to pixi's global cache for fromFrame and fromImage functions TextureCache[i] = this.textures[i]; + this.textures[i].textureCacheId = i; } frameIndex++; diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 0a2c379..2213087 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -147,9 +147,9 @@ /** * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. * - * @event update - * @memberof PIXI.Texture# + * @event PIXI.Texture#update * @protected + * @param {PIXI.Texture} texture - Instance of texture being updated. */ this._updateID = 0; @@ -159,6 +159,15 @@ * @type {Object} */ this.transform = null; + + /** + * The id under which this Texture has been added to the texture cache. This is + * automatically set in certain cases, but may not always be accurate, particularly if + * the texture is in the cache under multiple ids. + * + * @member {string} + */ + this.textureCacheId = null; } /** @@ -225,11 +234,17 @@ this._uvs = null; this.trim = null; this.orig = null; + this.textureCacheId = null; this.valid = false; - this.off('dispose', this.dispose, this); - this.off('update', this.update, this); + for (const prop in TextureCache) + { + if (TextureCache[prop] === this) + { + delete TextureCache[prop]; + } + } } /** @@ -315,14 +330,15 @@ */ static fromLoader(source, imageUrl, name) { - // console.log('added from loader...') + // console.log('added from loader...') const resource = new ImageResource(source);//.from(imageUrl, crossorigin);// document.createElement('img'); - // console.log('base resource ' + resource.width); + // console.log('base resource ' + resource.width); const baseTexture = new BaseTexture(resource, settings.SCALE_MODE, getResolutionOfUrl(imageUrl)); + /// console.log('base width ' + baseTexture.width); const texture = new Texture(baseTexture); @@ -335,6 +351,7 @@ // lets also add the frame to pixi's global cache for fromFrame and fromImage fucntions BaseTextureCache[name] = baseTexture; TextureCache[name] = texture; + texture.textureCacheId = name; // also add references by url if they are different. if (name !== imageUrl) @@ -355,6 +372,10 @@ */ static addTextureToCache(texture, id) { + if (!texture.textureCacheId) + { + texture.textureCacheId = id; + } TextureCache[id] = texture; } @@ -495,6 +516,7 @@ */ Texture.EMPTY = new Texture(new BaseTexture()); removeAllHandlers(Texture.EMPTY); +removeAllHandlers(Texture.EMPTY.baseTexture); /** * A white texture of 10x10 size, used for graphics and other things @@ -505,3 +527,4 @@ */ Texture.WHITE = createWhiteTexture(); removeAllHandlers(Texture.WHITE); +removeAllHandlers(Texture.WHITE.baseTexture); diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index 59eb9fd..a9b9f51 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -1,6 +1,7 @@ import BaseTexture from './BaseTexture'; import { uid, BaseTextureCache } from '../utils'; -import * as ticker from '../ticker'; +import { shared } from '../ticker'; +import { UPDATE_PRIORITY } from '../const'; /** * A texture of a [playing] Video. @@ -125,7 +126,7 @@ if (!this._isAutoUpdating && this.autoUpdate) { - ticker.shared.add(this.update, this); + shared.add(this.update, this, UPDATE_PRIORITY.HIGH); this._isAutoUpdating = true; } } @@ -139,7 +140,7 @@ { if (this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); this._isAutoUpdating = false; } } @@ -187,7 +188,7 @@ { if (this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); } if (this.source && this.source._pixiId) @@ -281,12 +282,12 @@ if (!this._autoUpdate && this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); this._isAutoUpdating = false; } else if (this._autoUpdate && !this._isAutoUpdating) { - ticker.shared.add(this.update, this); + shared.add(this.update, this, UPDATE_PRIORITY.HIGH); this._isAutoUpdating = true; } } diff --git a/src/core/ticker/Ticker.js b/src/core/ticker/Ticker.js index 17a1517..40df138 100644 --- a/src/core/ticker/Ticker.js +++ b/src/core/ticker/Ticker.js @@ -1,12 +1,10 @@ import settings from '../settings'; -import EventEmitter from 'eventemitter3'; - -// Internal event used by composed emitter -const TICK = 'tick'; +import { UPDATE_PRIORITY } from '../const'; +import TickerListener from './TickerListener'; /** * A Ticker class that runs an update loop that other objects listen to. - * This class is composed around an EventEmitter object to add listeners + * This class is composed around listeners * meant for execution on the next requested animation frame. * Animation frames are requested only when necessary, * e.g. When the ticker is started and the emitter has listeners. @@ -22,10 +20,11 @@ constructor() { /** - * Internal emitter used to fire 'tick' event + * The first listener. All new listeners added are chained on this. * @private + * @type {TickerListener} */ - this._emitter = new EventEmitter(); + this._head = new TickerListener(null, null, Infinity); /** * Internal current frame request ID @@ -131,7 +130,7 @@ // Invoke listeners now this.update(time); // Listener side effects may have modified ticker state. - if (this.started && this._requestId === null && this._emitter.listeners(TICK, true)) + if (this.started && this._requestId === null && this._head.next) { this._requestId = requestAnimationFrame(this._tick); } @@ -148,7 +147,7 @@ */ _requestIfNeeded() { - if (this._requestId === null && this._emitter.listeners(TICK, true)) + if (this._requestId === null && this._head.next) { // ensure callbacks get correct delta this.lastTime = performance.now(); @@ -193,35 +192,72 @@ } /** - * Calls {@link module:eventemitter3.EventEmitter#on} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. + * Register a handler for tick events. Calls continuously unless + * it is removed or the ticker is stopped. * * @param {Function} fn - The listener function to be added for updates * @param {Function} [context] - The listener context + * @param {number} [priority=PIXI.UPDATE_PRIORITY.NORMAL] - The priority for emitting * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - add(fn, context) + add(fn, context, priority = UPDATE_PRIORITY.NORMAL) { - this._emitter.on(TICK, fn, context); - - this._startIfPossible(); - - return this; + return this._addListener(new TickerListener(fn, context, priority)); } /** - * Calls {@link module:eventemitter3.EventEmitter#once} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. + * Add a handler for the tick event which is only execute once. * * @param {Function} fn - The listener function to be added for one update * @param {Function} [context] - The listener context + * @param {number} [priority=PIXI.UPDATE_PRIORITY.NORMAL] - The priority for emitting * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - addOnce(fn, context) + addOnce(fn, context, priority = UPDATE_PRIORITY.NORMAL) { - this._emitter.once(TICK, fn, context); + return this._addListener(new TickerListener(fn, context, priority, true)); + } + + /** + * Internally adds the event handler so that it can be sorted by priority. + * Priority allows certain handler (user, AnimatedSprite, Interaction) to be run + * before the rendering. + * + * @private + * @param {TickerListener} listener - Current listener being added. + * @returns {PIXI.ticker.Ticker} This instance of a ticker + */ + _addListener(listener) + { + // For attaching to head + let current = this._head.next; + let previous = this._head; + + // Add the first item + if (!current) + { + listener.connect(previous); + } + else + { + // Go from highest to lowest priority + while (current) + { + if (listener.priority >= current.priority) + { + listener.connect(previous); + break; + } + previous = current; + current = current.next; + } + + // Not yet connected + if (!listener.previous) + { + listener.connect(previous); + } + } this._startIfPossible(); @@ -229,19 +265,33 @@ } /** - * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. - * It checks if the emitter has listeners for 'tick' event. - * If it does, then it cancels the animation frame. + * Removes any handlers matching the function and context parameters. + * If no handlers are left after removing, then it cancels the animation frame. * - * @param {Function} [fn] - The listener function to be removed + * @param {Function} fn - The listener function to be removed * @param {Function} [context] - The listener context to be removed * @returns {PIXI.ticker.Ticker} This instance of a ticker */ remove(fn, context) { - this._emitter.off(TICK, fn, context); + let listener = this._head.next; - if (!this._emitter.listeners(TICK, true)) + while (listener) + { + // We found a match, lets remove it + // no break to delete all possible matches + // incase a listener was added 2+ times + if (listener.match(fn, context)) + { + listener = listener.destroy(); + } + else + { + listener = listener.next; + } + } + + if (!this._head.next) { this._cancelIfNeeded(); } @@ -276,6 +326,25 @@ } /** + * Destroy the ticker and don't use after this. Calling + * this method removes all references to internal events. + */ + destroy() + { + this.stop(); + + let listener = this._head.next; + + while (listener) + { + listener = listener.destroy(true); + } + + this._head.destroy(); + this._head = null; + } + + /** * Triggers an update. An update entails setting the * current {@link PIXI.ticker.Ticker#elapsedMS}, * the current {@link PIXI.ticker.Ticker#deltaTime}, @@ -320,8 +389,22 @@ this.deltaTime = elapsedMS * settings.TARGET_FPMS * this.speed; + // Cache a local reference, in-case ticker is destroyed + // during the emit, we can still check for head.next + const head = this._head; + // Invoke listeners added to internal emitter - this._emitter.emit(TICK, this.deltaTime); + let listener = head.next; + + while (listener) + { + listener = listener.emit(this.deltaTime); + } + + if (!head.next) + { + this._cancelIfNeeded(); + } } else { diff --git a/src/core/ticker/TickerListener.js b/src/core/ticker/TickerListener.js new file mode 100644 index 0000000..2bedb34 --- /dev/null +++ b/src/core/ticker/TickerListener.js @@ -0,0 +1,158 @@ +/** + * Internal class for handling the priority sorting of ticker handlers. + * + * @private + * @class + * @memberof PIXI.ticker + */ +export default class TickerListener +{ + /** + * Constructor + * + * @param {Function} fn - The listener function to be added for one update + * @param {Function} [context=null] - The listener context + * @param {number} [priority=0] - The priority for emitting + * @param {boolean} [once=false] - If the handler should fire once + */ + constructor(fn, context = null, priority = 0, once = false) + { + /** + * The handler function to execute. + * @member {Function} + */ + this.fn = fn; + + /** + * The calling to execute. + * @member {Function} + */ + this.context = context; + + /** + * The current priority. + * @member {number} + */ + this.priority = priority; + + /** + * If this should only execute once. + * @member {boolean} + */ + this.once = once; + + /** + * The next item in chain. + * @member {TickerListener} + */ + this.next = null; + + /** + * The previous item in chain. + * @member {TickerListener} + */ + this.previous = null; + + /** + * `true` if this listener has been destroyed already. + * @member {boolean} + * @private + */ + this._destroyed = false; + } + + /** + * Simple compare function to figure out if a function and context match. + * + * @param {Function} fn - The listener function to be added for one update + * @param {Function} context - The listener context + * @return {boolean} `true` if the listener match the arguments + */ + match(fn, context) + { + context = context || null; + + return this.fn === fn && this.context === context; + } + + /** + * Emit by calling the current function. + * @param {number} deltaTime - time since the last emit. + * @return {TickerListener} Next ticker + */ + emit(deltaTime) + { + if (this.context) + { + this.fn.call(this.context, deltaTime); + } + else + { + this.fn(deltaTime); + } + + if (this.once) + { + this.destroy(); + } + + const redirect = this.next; + + // Soft-destroying should remove + // the next reference + if (this._destroyed) + { + this.next = null; + } + + return redirect; + } + + /** + * Connect to the list. + * @param {TickerListener} previous - Input node, previous listener + */ + connect(previous) + { + this.previous = previous; + if (previous.next) + { + previous.next.previous = this; + } + this.next = previous.next; + previous.next = this; + } + + /** + * Destroy and don't use after this. + * @param {boolean} [hard = false] `true` to remove the `next` reference, this + * is considered a hard destroy. Soft destroy maintains the next reference. + * @return {TickerListener} The listener to redirect while emitting or removing. + */ + destroy(hard = false) + { + this._destroyed = true; + this.fn = null; + this.context = null; + + // Disconnect, hook up next and previous + if (this.previous) + { + this.previous.next = this.next; + } + + if (this.next) + { + this.next.previous = this.previous; + } + + // Redirect to the next item + const redirect = this.previous; + + // Remove references + this.next = hard ? null : redirect; + this.previous = null; + + return redirect; + } +} diff --git a/src/core/ticker/index.js b/src/core/ticker/index.js index 4b3017c..c3020d4 100644 --- a/src/core/ticker/index.js +++ b/src/core/ticker/index.js @@ -45,8 +45,25 @@ const shared = new Ticker(); shared.autoStart = true; +shared.destroy = () => +{ + // protect destroying shared ticker + // this is used by other internal systems + // like AnimatedSprite and InteractionManager +}; /** + * This namespace contains an API for interacting with PIXI's internal global update loop. + * + * This ticker is used for rendering, {@link PIXI.extras.AnimatedSprite AnimatedSprite}, + * {@link PIXI.interaction.InteractionManager InteractionManager} and many other time-based PIXI systems. + * @example + * const ticker = new PIXI.ticker.Ticker(); + * ticker.stop(); + * ticker.add((deltaTime) => { + * // do something every frame + * }); + * ticker.start(); * @namespace PIXI.ticker */ export { shared, Ticker }; diff --git a/src/core/utils/index.js b/src/core/utils/index.js index be44750..7441f7b 100644 --- a/src/core/utils/index.js +++ b/src/core/utils/index.js @@ -9,6 +9,21 @@ let saidHello = false; /** + * Generalized convenience utilities for PIXI. + * @example + * // Extend PIXI's internal Event Emitter. + * class MyEmitter extends PIXI.utils.EventEmitter { + * constructor() { + * super(); + * console.log("Emitter created!"); + * } + * } + * + * // Get info on current device + * console.log(PIXI.utils.isMobile); + * + * // Convert hex color to string + * console.log(PIXI.utils.hex2string(0xff00ff)); // returns: "#ff00ff" * @namespace PIXI.utils */ export { diff --git a/src/deprecation.js b/src/deprecation.js index b9c73b3..33daeec 100644 --- a/src/deprecation.js +++ b/src/deprecation.js @@ -918,6 +918,35 @@ }); /** + * @method + * @name PIXI.prepare.BasePrepare#register + * @see PIXI.prepare.BasePrepare#registerFindHook + * @deprecated since version 4.4.2 + * @param {Function} [addHook] - Function call that takes two parameters: `item:*, queue:Array` + * function must return `true` if it was able to add item to the queue. + * @param {Function} [uploadHook] - Function call that takes two parameters: `prepare:CanvasPrepare, item:*` and + * function must return `true` if it was able to handle upload of item. + * @return {PIXI.BasePrepare} Instance of plugin for chaining. + */ +prepare.BasePrepare.prototype.register = function register(addHook, uploadHook) +{ + warn('renderer.plugins.prepare.register is now deprecated, ' + + 'please use renderer.plugins.prepare.registerFindHook & renderer.plugins.prepare.registerUploadHook'); + + if (addHook) + { + this.registerFindHook(addHook); + } + + if (uploadHook) + { + this.registerUploadHook(uploadHook); + } + + return this; +}; + +/** * The number of graphics or textures to upload to the GPU. * * @name PIXI.prepare.canvas.UPLOADS_PER_FRAME @@ -969,67 +998,73 @@ }, }); -Object.defineProperties(loaders.Resource.prototype, { - isJson: { - get() - { - warn('The isJson property is deprecated, please use `resource.type === Resource.TYPE.JSON`.'); +if (loaders.Loader) +{ + const Resource = loaders.Resource; + const Loader = loaders.Loader; - return this.type === loaders.Loader.Resource.TYPE.JSON; + Object.defineProperties(Resource.prototype, { + isJson: { + get() + { + warn('The isJson property is deprecated, please use `resource.type === Resource.TYPE.JSON`.'); + + return this.type === Resource.TYPE.JSON; + }, }, - }, - isXml: { - get() - { - warn('The isXml property is deprecated, please use `resource.type === Resource.TYPE.XML`.'); + isXml: { + get() + { + warn('The isXml property is deprecated, please use `resource.type === Resource.TYPE.XML`.'); - return this.type === loaders.Loader.Resource.TYPE.XML; + return this.type === Resource.TYPE.XML; + }, }, - }, - isImage: { - get() - { - warn('The isImage property is deprecated, please use `resource.type === Resource.TYPE.IMAGE`.'); + isImage: { + get() + { + warn('The isImage property is deprecated, please use `resource.type === Resource.TYPE.IMAGE`.'); - return this.type === loaders.Loader.Resource.TYPE.IMAGE; + return this.type === Resource.TYPE.IMAGE; + }, }, - }, - isAudio: { - get() - { - warn('The isAudio property is deprecated, please use `resource.type === Resource.TYPE.AUDIO`.'); + isAudio: { + get() + { + warn('The isAudio property is deprecated, please use `resource.type === Resource.TYPE.AUDIO`.'); - return this.type === loaders.Loader.Resource.TYPE.AUDIO; + return this.type === Resource.TYPE.AUDIO; + }, }, - }, - isVideo: { - get() - { - warn('The isVideo property is deprecated, please use `resource.type === Resource.TYPE.VIDEO`.'); + isVideo: { + get() + { + warn('The isVideo property is deprecated, please use `resource.type === Resource.TYPE.VIDEO`.'); - return this.type === loaders.Loader.Resource.TYPE.VIDEO; + return this.type === Resource.TYPE.VIDEO; + }, }, - }, -}); + }); -Object.defineProperties(loaders.Loader.prototype, { - before: { - get() - { - warn('The before() method is deprecated, please use pre().'); + Object.defineProperties(Loader.prototype, { + before: { + get() + { + warn('The before() method is deprecated, please use pre().'); - return this.pre; + return this.pre; + }, }, - }, - after: { - get() - { - warn('The after() method is deprecated, please use use().'); + after: { + get() + { + warn('The after() method is deprecated, please use use().'); - return this.use; + return this.use; + }, }, - }, -}); + }); +} /** * @name PIXI.interaction.interactiveTarget#defaultCursor diff --git a/.travis.yml b/.travis.yml index 3df8b22..14de334 100644 --- a/.travis.yml +++ b/.travis.yml @@ -110,3 +110,43 @@ on: all_branches: true condition: $TRAVIS_TAG + # Deploy config for latest release + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: dist + upload-dir: "release" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: docs + upload-dir: "release/docs" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: coverage + upload-dir: "release/coverage" + on: + all_branches: true + condition: $TRAVIS_TAG \ No newline at end of file diff --git a/README.md b/README.md index d26fadd..3b85f85 100644 --- a/README.md +++ b/README.md @@ -36,22 +36,14 @@ - Wiki: Other misc tutorials and resources are [on the Wiki](https://github.com/pixijs/pixi.js/wiki/Resources). ### Community ### -- Forums: Check out the [forum] (http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow] (http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. +- Forums: Check out the [forum](http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow](http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. - Inspiration: Check out the [gallery](http://www.pixijs.com/gallery) to see some of the amazing things people have created! - Chat: You can join us on [Gitter](https://gitter.im/pixijs/pixi.js) To chat about Pixi. We also now have a Slack channel. If you would like to join it please Send me an email (mat@goodboydigital.com) and I will invite you in. ### Setup ### -It's easy to get started with Pixi.js! Simply grab the pre-built versions from here: - -Release Branch - Nice and stable Pixi.js -- Unminified: [http://pixijs.download/release/pixi.js] -- Minified: [http://pixijs.download/release/pixi.min.js] - -Develop Branch - The bleeding edge version of Pixi.js -- Unminified: [http://pixijs.download/dev/pixi.js] -- Minified: [http://pixijs.download/dev/pixi.min.js] +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. @@ -70,10 +62,10 @@ #### CDN Install (via cdnjs) ```html - + ``` -_Note: `4.2.2` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ +_Note: `4.4.3` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ ### Demos ### @@ -130,7 +122,7 @@ // and the root stage PIXI.Container. var app = new PIXI.Application(); -// The application will create a canvas element for you that you +// The application will create a canvas element for you that you // can then insert into the DOM. document.body.appendChild(app.view); @@ -150,7 +142,7 @@ // Add the bunny to the scene we are building. app.stage.addChild(bunny); - + // Listen for frame updates app.ticker.add(function() { // each frame we spin the bunny around a bit diff --git a/bower.json b/bower.json deleted file mode 100644 index 41b668c..0000000 --- a/bower.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "pixi.js", - "main": "dist/pixi.min.js", - "ignore": [ - "**/.*", - "docs", - "node_modules", - "bower_components", - "test", - "scripts", - "bower.json", - "inch.json" - ], - "dependencies": { - }, - "devDependencies": { - } -} diff --git a/package.json b/package.json index d35cbb2..308469f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.3.5", + "version": "4.4.4", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ @@ -40,11 +40,11 @@ "lib": "babel src --out-dir lib -s", "predocs": "rimraf docs/**", "docs": "jsdoc -c scripts/jsdoc.conf.json -R README.md", - "publish:patch": "npm version patch --no-git-tag-version && npm publish", - "publish:minor": "npm version minor --no-git-tag-version && npm publish", - "publish:major": "npm version major --no-git-tag-version && npm publish", + "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", - "postpublish": "node scripts/release.js" + "postpublish": "git push && git push --tags" }, "files": [ "dist/", @@ -72,7 +72,6 @@ "electron": "^1.4.15", "eslint": "^3.5.0", "floss": "^2.0.1", - "gh-pages": "^0.11.0", "jaguarjs-jsdoc": "^1.0.1", "js-md5": "^0.4.1", "jsdoc": "^3.4.2", diff --git a/scripts/jsdoc.conf.json b/scripts/jsdoc.conf.json index 553e61c..3f52180 100644 --- a/scripts/jsdoc.conf.json +++ b/scripts/jsdoc.conf.json @@ -43,7 +43,7 @@ }, "markdown" : { "parser" : "gfm", - "hardwrap" : true + "hardwrap" : false }, "opts": { "encoding" : "utf8", diff --git a/scripts/release.js b/scripts/release.js deleted file mode 100644 index 156620c..0000000 --- a/scripts/release.js +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env node -'use strict'; - -// Publish script to push releases of the bin files -// the normally are gitignored -const ghpages = require('gh-pages'); -const path = require('path'); -const packageInfo = require(path.join(__dirname, '..', 'package.json')); - -const options = { - src: [ - 'dist/**/*', - 'lib/**/*', - 'src/**/*', - 'scripts/*', - 'scripts/renders/*', - 'test/**/*', - '*.json', - '*.md', - 'LICENSE', - '.eslintrc', - '.editorconfig', - '.travis.yml', - '.babelrc', - ], - dotfiles: true, - branch: 'release', - message: packageInfo.version, - logger: console.log.bind(console), -}; - -ghpages.publish(process.cwd(), options, (err) => -{ - if (err) - { - console.log(err); - process.exit(1); - - return; - } - - process.exit(0); -}); diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index f16bcb1..220427a 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -21,7 +21,7 @@ const DIV_HOOK_ZINDEX = 2; /** - * The Accessibility manager reacreates the ability to tab and and have content read by screen + * The Accessibility manager recreates the ability to tab and and have content read by screen * readers. This is very important as it can possibly help people with disabilities access pixi * content. * diff --git a/src/accessibility/index.js b/src/accessibility/index.js index 568cfb1..0ae108f 100644 --- a/src/accessibility/index.js +++ b/src/accessibility/index.js @@ -1,4 +1,9 @@ /** + * This namespace contains a renderer plugin for interaction accessibility for end-users + * with physical impairments which require screen-renders, keyboard navigation, etc. + * + * Do not instantiate this plugin directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. * @namespace PIXI.accessibility */ export { default as accessibleTarget } from './accessibleTarget'; diff --git a/src/core/Application.js b/src/core/Application.js index 5340578..2fcc729 100644 --- a/src/core/Application.js +++ b/src/core/Application.js @@ -1,6 +1,8 @@ import { autoDetectRenderer } from './autoDetectRenderer'; import Container from './display/Container'; import { shared, Ticker } from './ticker'; +import settings from './settings'; +import { UPDATE_PRIORITY } from './const'; /** * Convenience class to create a new PIXI application. @@ -22,28 +24,52 @@ */ export default class Application { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experience unexplained flickering try setting this to true. - * @param {boolean} [useSharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedLoader=false] - `true` to use PIXI.loaders.shared, `false` to create new Loader. */ - constructor(width, height, options, noWebGL, useSharedTicker = false) + constructor(options, arg2, arg3, arg4, arg5) { + // Support for constructor(width, height, options, noWebGL, useSharedTicker) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + forceCanvas: !!arg4, + sharedTicker: !!arg5, + }, arg3); + } + + /** + * The default options, so we mixin functionality later. + * @member {object} + * @protected + */ + this._options = options = Object.assign({ + sharedTicker: false, + forceCanvas: false, + sharedLoader: false, + }, options); + /** * WebGL renderer if available, otherwise CanvasRenderer * @member {PIXI.WebGLRenderer|PIXI.CanvasRenderer} */ - this.renderer = autoDetectRenderer(width, height, options, noWebGL); + this.renderer = autoDetectRenderer(options); /** * The root display container that's rendered. @@ -63,7 +89,7 @@ * @member {PIXI.ticker.Ticker} * @default PIXI.ticker.shared */ - this.ticker = useSharedTicker ? shared : new Ticker(); + this.ticker = options.sharedTicker ? shared : new Ticker(); // Start the rendering this.start(); @@ -78,7 +104,7 @@ this._ticker = ticker; if (ticker) { - ticker.add(this.render, this); + ticker.add(this.render, this, UPDATE_PRIORITY.LOW); } } get ticker() // eslint-disable-line require-jsdoc @@ -136,13 +162,18 @@ */ destroy(removeView) { - this.stop(); + const oldTicker = this._ticker; + this.ticker = null; + oldTicker.destroy(); + this.stage.destroy(); this.stage = null; this.renderer.destroy(removeView); this.renderer = null; + + this._options = null; } } diff --git a/src/core/autoDetectRenderer.js b/src/core/autoDetectRenderer.js index 8960425..9ec0364 100644 --- a/src/core/autoDetectRenderer.js +++ b/src/core/autoDetectRenderer.js @@ -2,6 +2,7 @@ import CanvasRenderer from './renderers/canvas/CanvasRenderer'; import WebGLRenderer from './renderers/webgl/WebGLRenderer'; +// eslint-disable-next-line valid-jsdoc /** * This helper function will automatically detect which renderer you should be using. * WebGL is the preferred renderer as it is a lot faster. If webGL is not supported by @@ -9,24 +10,32 @@ * * @memberof PIXI * @function autoDetectRenderer - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @return {PIXI.WebGLRenderer|PIXI.CanvasRenderer} Returns WebGL renderer if available, otherwise CanvasRenderer */ -export function autoDetectRenderer(width = 800, height = 600, options, noWebGL) +export function autoDetectRenderer(options, arg1, arg2, arg3) { - if (!noWebGL && utils.isWebGLSupported()) + // Backward-compatible support for noWebGL option + let forceCanvas = options && options.forceCanvas; + + if (arg3 !== undefined) { - return new WebGLRenderer(width, height, options); + forceCanvas = arg3; } - return new CanvasRenderer(width, height, options); + if (!forceCanvas && utils.isWebGLSupported()) + { + return new WebGLRenderer(options, arg1, arg2); + } + + return new CanvasRenderer(options, arg1, arg2); } diff --git a/src/core/const.js b/src/core/const.js index b9e99ae..2c68dee 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -341,3 +341,27 @@ LINEAR_VERTICAL: 0, LINEAR_HORIZONTAL: 1, }; + +/** + * Represents the update priorities used by internal PIXI classes when registered with + * the {@link PIXI.ticker.Ticker} object. Higher priority items are updated first and lower + * priority items, such as render, should go later. + * + * @static + * @constant + * @name UPDATE_PRIORITY + * @memberof PIXI + * @type {object} + * @property {number} INTERACTION=50 Highest priority, used for {@link PIXI.interaction.InteractionManager} + * @property {number} HIGH=25 High priority updating, {@link PIXI.VideoBaseTexture} and {@link PIXI.extras.AnimatedSprite} + * @property {number} NORMAL=0 Default priority for ticker events, see {@link PIXI.ticker.Ticker#add}. + * @property {number} LOW=-25 Low priority used for {@link PIXI.Application} rendering. + * @property {number} UTILITY=-50 Lowest priority used for {@link PIXI.prepare.BasePrepare} utility. + */ +export const UPDATE_PRIORITY = { + INTERACTION: 50, + HIGH: 25, + NORMAL: 0, + LOW: -25, + UTILITY: -50, +}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 4e6c77d..decb42a 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -13,7 +13,6 @@ * * @class * @extends EventEmitter - * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ export default class DisplayObject extends EventEmitter @@ -113,6 +112,29 @@ * @private */ this._mask = null; + + /** + * If the object has been destroyed via destroy(). If true, it should not be used. + * + * @member {boolean} + * @private + * @readonly + */ + this._destroyed = false; + + /** + * Fired when this DisplayObject is added to a Container. + * + * @event PIXI.DisplayObject#added + * @param {PIXI.Container} container - The container added to. + */ + + /** + * Fired when this DisplayObject is removed from a Container. + * + * @event PIXI.DisplayObject#removed + * @param {PIXI.Container} container - The container removed from. + */ } /** @@ -403,6 +425,8 @@ this.interactive = false; this.interactiveChildren = false; + + this._destroyed = true; } /** diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index aafdfe2..f787c4a 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -792,8 +792,8 @@ sprite.worldAlpha = this.worldAlpha * sprite.alpha; sprite.blendMode = this.blendMode; - sprite.texture._frame.width = rect.width; - sprite.texture._frame.height = rect.height; + sprite._texture._frame.width = rect.width; + sprite._texture._frame.height = rect.height; sprite.transform.worldTransform = this.transform.worldTransform; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index a24c775..b9f192d 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -19,11 +19,12 @@ */ export default class SystemRenderer extends EventEmitter { + // eslint-disable-next-line valid-jsdoc /** * @param {string} system - The name of the system this renderer is for. - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -37,27 +38,31 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(system, screenWidth, screenHeight, options) + constructor(system, options, arg2, arg3) { super(); sayHello(system); - // prepare options - if (options) + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') { - for (const i in settings.RENDER_OPTIONS) - { - if (typeof options[i] === 'undefined') - { - options[i] = settings.RENDER_OPTIONS[i]; - } - } + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); } - else - { - options = settings.RENDER_OPTIONS; - } + + // Add the default render options + options = Object.assign({}, settings.RENDER_OPTIONS, options); + + /** + * The supplied constructor options. + * + * @member {Object} + * @readOnly + */ + this.options = options; /** * The type of the renderer. @@ -75,7 +80,7 @@ * * @member {PIXI.Rectangle} */ - this.screen = new Rectangle(0, 0, screenWidth || 800, screenHeight || 600); + this.screen = new Rectangle(0, 0, options.width, options.height); /** * The canvas element that everything is drawn to @@ -279,6 +284,8 @@ this.blendModes = null; + this.options = null; + this.preserveDrawingBuffer = false; this.clearBeforeRender = false; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index e84e49c..691caf0 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -17,10 +17,11 @@ */ export default class CanvasRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -34,9 +35,9 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('Canvas', screenWidth, screenHeight, options); + super('Canvas', options, arg2, arg3); this.type = RENDERER_TYPE.CANVAS; @@ -96,7 +97,19 @@ this.context = null; this.renderingToScreen = false; - this.resize(screenWidth, screenHeight); + this.resize(this.options.width, this.options.height); + + /** + * Fired after rendering finishes. + * + * @event PIXI.CanvasRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.CanvasRenderer#prerender + */ } /** @@ -299,4 +312,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.CanvasRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.CanvasExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.CanvasPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.CanvasRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index e1cf7cd..f6fe123 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -31,11 +31,12 @@ */ export default class WebGLRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** * - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -55,9 +56,9 @@ * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('WebGL', screenWidth, screenHeight, options); + super('WebGL', options, arg2, arg3); /** * The type of this renderer as a standardised const @@ -89,12 +90,6 @@ }; - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ this._backgroundColorRgba[3] = this.transparent ? 0 : 1; this.globalUniforms = new UniformGroup({ @@ -117,6 +112,13 @@ this.initPlugins(); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ if(options.context) { this.context.initFromContext(options.context); @@ -169,6 +171,25 @@ } return this; + + /** + * Fired after rendering finishes. + * + * @event PIXI.WebGLRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.WebGLRenderer#prerender + */ + + /** + * Fired when the WebGL context is set. + * + * @event PIXI.WebGLRenderer#context + * @param {WebGLRenderingContext} gl - WebGL context. + */ } /** @@ -295,4 +316,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.WebGLRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.WebGLRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(WebGLRenderer); diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index 4eb6128..fa3d883 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -47,6 +47,14 @@ * @member {boolean} */ this.enabled = true; + + /** + * If enabled, pixi will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; } /** diff --git a/src/core/renderers/webgl/systems/ContextSystem.js b/src/core/renderers/webgl/systems/ContextSystem.js index ff958c7..8df85ea 100644 --- a/src/core/renderers/webgl/systems/ContextSystem.js +++ b/src/core/renderers/webgl/systems/ContextSystem.js @@ -75,7 +75,8 @@ */ createContext(canvas, options) { - var gl = canvas.getContext('webgl', options) || + var gl = //canvas.getContext('webgl2', options) || + canvas.getContext('webgl', options) || canvas.getContext('experimental-webgl', options); if (!gl) diff --git a/src/core/renderers/webgl/systems/FilterSystem.js b/src/core/renderers/webgl/systems/FilterSystem.js index f47335d..37e28d2 100644 --- a/src/core/renderers/webgl/systems/FilterSystem.js +++ b/src/core/renderers/webgl/systems/FilterSystem.js @@ -111,7 +111,7 @@ // TODO we should fit the rect around the transform.. } - else + else if (filters[0].autoFit) { sourceFrame.fit(filterData.stack[0].destinationFrame); } diff --git a/src/core/renderers/webgl/systems/shader/shader/mapType.js b/src/core/renderers/webgl/systems/shader/shader/mapType.js index 581dace..917538a 100644 --- a/src/core/renderers/webgl/systems/shader/shader/mapType.js +++ b/src/core/renderers/webgl/systems/shader/shader/mapType.js @@ -15,7 +15,7 @@ } } - return GL_TABLE[type]; + return GL_TABLE[type]; }; var GL_TABLE = null; diff --git a/src/core/settings.js b/src/core/settings.js index 18c5877..38ef45c 100644 --- a/src/core/settings.js +++ b/src/core/settings.js @@ -2,6 +2,15 @@ import canUploadSameBuffer from './utils/canUploadSameBuffer'; /** + * User's customizable globals for overriding the default PIXI settings, such + * as a renderer's default resolution, framerate, float percision, etc. + * @example + * // Use the native window resolution as the default resolution + * // will support high-density displays when rendering + * PIXI.settings.RESOLUTION = window.devicePixelRatio. + * + * // Disable interpolation when scaling, will make texture be pixelated + * PIXI.settings.SCALE_MODE = PIXI.SCALE_MODES.NEAREST; * @namespace PIXI.settings */ export default { @@ -101,6 +110,9 @@ * @property {boolean} clearBeforeRender=true * @property {boolean} preserveDrawingBuffer=false * @property {boolean} roundPixels=false + * @property {number} width=800 + * @property {number} height=600 + * @property {boolean} legacy=false */ RENDER_OPTIONS: { view: null, @@ -112,6 +124,9 @@ clearBeforeRender: true, preserveDrawingBuffer: false, roundPixels: false, + width: 800, + height: 600, + legacy: false, }, /** diff --git a/src/core/shader/Program.js b/src/core/shader/Program.js index c0b538b..07b4398 100644 --- a/src/core/shader/Program.js +++ b/src/core/shader/Program.js @@ -1,6 +1,6 @@ import extractUniformsFromSrc from './extractUniformsFromSrc'; import generateUniformsSync from './generateUniformsSync'; -import glCore from 'pixi-gl-core'; +import shaderUtils from '../renderers/webgl/systems/shader/shader'; import { ProgramCache } from '../utils'; import getTestContext from '../utils/getTestContext'; @@ -64,14 +64,17 @@ } else { - vertexSrc = glCore.shader.setPrecision(vertexSrc, 'mediump'); - fragmentSrc = glCore.shader.setPrecision(fragmentSrc, 'mediump'); + vertexSrc = shaderUtils.setPrecision(vertexSrc, 'mediump'); + fragmentSrc = shaderUtils.setPrecision(fragmentSrc, 'mediump'); - const program = glCore.shader.compileProgram(gl, vertexSrc, fragmentSrc); + const program = shaderUtils.compileProgram(gl, vertexSrc, fragmentSrc); + console.log("<<<<<<>>><<"); this.attributeData = this.getAttributeData(program, gl); this.uniformData = this.getUniformData(program, gl); + console.log(this.uniformData); + //gl.deleteProgram(program); } } @@ -95,13 +98,13 @@ for (let i = 0; i < totalAttributes; i++) { const attribData = gl.getActiveAttrib(program, i); - const type = glCore.shader.mapType(gl, attribData.type); + const type = shaderUtils.mapType(gl, attribData.type); /*eslint-disable */ const data = { type: type, name: attribData.name, - size: glCore.shader.mapSize(type), + size: shaderUtils.mapSize(type), location: 0, }; /*eslint-enable */ @@ -137,13 +140,14 @@ // TODO expose this as a prop? // const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); - const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); + //const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); for (let i = 0; i < totalUniforms; i++) { const uniformData = gl.getActiveUniform(program, i); const name = uniformData.name.replace(/\[.*?\]/, ''); - const type = glCore.shader.mapType(gl, uniformData.type); + const type = shaderUtils.mapType(gl, uniformData.type); + console.log('mapping ' + uniformData.type + ' to ' + type); // if (!name.match(maskRegex)) { @@ -151,7 +155,7 @@ uniforms[name] = { type: type, size: uniformData.size, - value: glCore.shader.defaultValue(type, uniformData.size), + value: shaderUtils.defaultValue(type, uniformData.size), }; /*eslint-enable */ } diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index e387e99..b57d095 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -147,12 +147,12 @@ // so if _width is 0 then width was not set.. if (this._width) { - this.scale.x = sign(this.scale.x) * this._width / this.texture.orig.width; + this.scale.x = sign(this.scale.x) * this._width / this._texture.orig.width; } if (this._height) { - this.scale.y = sign(this.scale.y) * this._height / this.texture.orig.height; + this.scale.y = sign(this.scale.y) * this._height / this._texture.orig.height; } } diff --git a/src/core/sprites/canvas/CanvasTinter.js b/src/core/sprites/canvas/CanvasTinter.js index 27e5e0c..fd9de5d 100644 --- a/src/core/sprites/canvas/CanvasTinter.js +++ b/src/core/sprites/canvas/CanvasTinter.js @@ -18,7 +18,7 @@ */ getTintedTexture: (sprite, color) => { - const texture = sprite.texture; + const texture = sprite._texture; color = CanvasTinter.roundColor(color); diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 5a4edda..8b9b797 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -344,6 +344,7 @@ const texture = this._texture; const style = this._style; + const padding = style.trim ? 0 : style.padding; texture.baseTexture.valid = true; texture.baseTexture.resolution = this.resolution; @@ -353,11 +354,11 @@ texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - texture.trim.x = -style.padding; - texture.trim.y = -style.padding; + texture.trim.x = -padding; + texture.trim.y = -padding; - texture.orig.width = texture._frame.width - (style.padding * 2); - texture.orig.height = texture._frame.height - (style.padding * 2); + texture.orig.width = texture._frame.width - (padding * 2); + texture.orig.height = texture._frame.height - (padding * 2); // call sprite onTextureUpdate to update scale if _width or _height were set this._onTextureUpdate(); diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index 853da3d..8a9c34b 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -114,6 +114,38 @@ this.cacheId = null; this.validate(); + + /** + * Fired when a not-immediately-available source finishes loading. + * + * @protected + * @event PIXI.BaseTexture#loaded + * @param {PIXI.BaseTexture} baseTexture - Resource loaded. + */ + + /** + * Fired when a not-immediately-available source fails to load. + * + * @protected + * @event PIXI.BaseTexture#error + * @param {PIXI.BaseTexture} baseTexture - Resource errored. + */ + + /** + * Fired when BaseTexture is updated. + * + * @protected + * @event PIXI.BaseTexture#update + * @param {PIXI.BaseTexture} baseTexture - Instance of texture being updated. + */ + + /** + * Fired when BaseTexture is destroyed. + * + * @protected + * @event PIXI.BaseTexture#dispose + * @param {PIXI.BaseTexture} baseTexture - Instance of texture being destroyed. + */ } setResource(resource) diff --git a/src/core/textures/Spritesheet.js b/src/core/textures/Spritesheet.js index d469c62..9b24f27 100644 --- a/src/core/textures/Spritesheet.js +++ b/src/core/textures/Spritesheet.js @@ -208,6 +208,7 @@ // lets also add the frame to pixi's global cache for fromFrame and fromImage functions TextureCache[i] = this.textures[i]; + this.textures[i].textureCacheId = i; } frameIndex++; diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 0a2c379..2213087 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -147,9 +147,9 @@ /** * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. * - * @event update - * @memberof PIXI.Texture# + * @event PIXI.Texture#update * @protected + * @param {PIXI.Texture} texture - Instance of texture being updated. */ this._updateID = 0; @@ -159,6 +159,15 @@ * @type {Object} */ this.transform = null; + + /** + * The id under which this Texture has been added to the texture cache. This is + * automatically set in certain cases, but may not always be accurate, particularly if + * the texture is in the cache under multiple ids. + * + * @member {string} + */ + this.textureCacheId = null; } /** @@ -225,11 +234,17 @@ this._uvs = null; this.trim = null; this.orig = null; + this.textureCacheId = null; this.valid = false; - this.off('dispose', this.dispose, this); - this.off('update', this.update, this); + for (const prop in TextureCache) + { + if (TextureCache[prop] === this) + { + delete TextureCache[prop]; + } + } } /** @@ -315,14 +330,15 @@ */ static fromLoader(source, imageUrl, name) { - // console.log('added from loader...') + // console.log('added from loader...') const resource = new ImageResource(source);//.from(imageUrl, crossorigin);// document.createElement('img'); - // console.log('base resource ' + resource.width); + // console.log('base resource ' + resource.width); const baseTexture = new BaseTexture(resource, settings.SCALE_MODE, getResolutionOfUrl(imageUrl)); + /// console.log('base width ' + baseTexture.width); const texture = new Texture(baseTexture); @@ -335,6 +351,7 @@ // lets also add the frame to pixi's global cache for fromFrame and fromImage fucntions BaseTextureCache[name] = baseTexture; TextureCache[name] = texture; + texture.textureCacheId = name; // also add references by url if they are different. if (name !== imageUrl) @@ -355,6 +372,10 @@ */ static addTextureToCache(texture, id) { + if (!texture.textureCacheId) + { + texture.textureCacheId = id; + } TextureCache[id] = texture; } @@ -495,6 +516,7 @@ */ Texture.EMPTY = new Texture(new BaseTexture()); removeAllHandlers(Texture.EMPTY); +removeAllHandlers(Texture.EMPTY.baseTexture); /** * A white texture of 10x10 size, used for graphics and other things @@ -505,3 +527,4 @@ */ Texture.WHITE = createWhiteTexture(); removeAllHandlers(Texture.WHITE); +removeAllHandlers(Texture.WHITE.baseTexture); diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index 59eb9fd..a9b9f51 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -1,6 +1,7 @@ import BaseTexture from './BaseTexture'; import { uid, BaseTextureCache } from '../utils'; -import * as ticker from '../ticker'; +import { shared } from '../ticker'; +import { UPDATE_PRIORITY } from '../const'; /** * A texture of a [playing] Video. @@ -125,7 +126,7 @@ if (!this._isAutoUpdating && this.autoUpdate) { - ticker.shared.add(this.update, this); + shared.add(this.update, this, UPDATE_PRIORITY.HIGH); this._isAutoUpdating = true; } } @@ -139,7 +140,7 @@ { if (this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); this._isAutoUpdating = false; } } @@ -187,7 +188,7 @@ { if (this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); } if (this.source && this.source._pixiId) @@ -281,12 +282,12 @@ if (!this._autoUpdate && this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); this._isAutoUpdating = false; } else if (this._autoUpdate && !this._isAutoUpdating) { - ticker.shared.add(this.update, this); + shared.add(this.update, this, UPDATE_PRIORITY.HIGH); this._isAutoUpdating = true; } } diff --git a/src/core/ticker/Ticker.js b/src/core/ticker/Ticker.js index 17a1517..40df138 100644 --- a/src/core/ticker/Ticker.js +++ b/src/core/ticker/Ticker.js @@ -1,12 +1,10 @@ import settings from '../settings'; -import EventEmitter from 'eventemitter3'; - -// Internal event used by composed emitter -const TICK = 'tick'; +import { UPDATE_PRIORITY } from '../const'; +import TickerListener from './TickerListener'; /** * A Ticker class that runs an update loop that other objects listen to. - * This class is composed around an EventEmitter object to add listeners + * This class is composed around listeners * meant for execution on the next requested animation frame. * Animation frames are requested only when necessary, * e.g. When the ticker is started and the emitter has listeners. @@ -22,10 +20,11 @@ constructor() { /** - * Internal emitter used to fire 'tick' event + * The first listener. All new listeners added are chained on this. * @private + * @type {TickerListener} */ - this._emitter = new EventEmitter(); + this._head = new TickerListener(null, null, Infinity); /** * Internal current frame request ID @@ -131,7 +130,7 @@ // Invoke listeners now this.update(time); // Listener side effects may have modified ticker state. - if (this.started && this._requestId === null && this._emitter.listeners(TICK, true)) + if (this.started && this._requestId === null && this._head.next) { this._requestId = requestAnimationFrame(this._tick); } @@ -148,7 +147,7 @@ */ _requestIfNeeded() { - if (this._requestId === null && this._emitter.listeners(TICK, true)) + if (this._requestId === null && this._head.next) { // ensure callbacks get correct delta this.lastTime = performance.now(); @@ -193,35 +192,72 @@ } /** - * Calls {@link module:eventemitter3.EventEmitter#on} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. + * Register a handler for tick events. Calls continuously unless + * it is removed or the ticker is stopped. * * @param {Function} fn - The listener function to be added for updates * @param {Function} [context] - The listener context + * @param {number} [priority=PIXI.UPDATE_PRIORITY.NORMAL] - The priority for emitting * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - add(fn, context) + add(fn, context, priority = UPDATE_PRIORITY.NORMAL) { - this._emitter.on(TICK, fn, context); - - this._startIfPossible(); - - return this; + return this._addListener(new TickerListener(fn, context, priority)); } /** - * Calls {@link module:eventemitter3.EventEmitter#once} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. + * Add a handler for the tick event which is only execute once. * * @param {Function} fn - The listener function to be added for one update * @param {Function} [context] - The listener context + * @param {number} [priority=PIXI.UPDATE_PRIORITY.NORMAL] - The priority for emitting * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - addOnce(fn, context) + addOnce(fn, context, priority = UPDATE_PRIORITY.NORMAL) { - this._emitter.once(TICK, fn, context); + return this._addListener(new TickerListener(fn, context, priority, true)); + } + + /** + * Internally adds the event handler so that it can be sorted by priority. + * Priority allows certain handler (user, AnimatedSprite, Interaction) to be run + * before the rendering. + * + * @private + * @param {TickerListener} listener - Current listener being added. + * @returns {PIXI.ticker.Ticker} This instance of a ticker + */ + _addListener(listener) + { + // For attaching to head + let current = this._head.next; + let previous = this._head; + + // Add the first item + if (!current) + { + listener.connect(previous); + } + else + { + // Go from highest to lowest priority + while (current) + { + if (listener.priority >= current.priority) + { + listener.connect(previous); + break; + } + previous = current; + current = current.next; + } + + // Not yet connected + if (!listener.previous) + { + listener.connect(previous); + } + } this._startIfPossible(); @@ -229,19 +265,33 @@ } /** - * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. - * It checks if the emitter has listeners for 'tick' event. - * If it does, then it cancels the animation frame. + * Removes any handlers matching the function and context parameters. + * If no handlers are left after removing, then it cancels the animation frame. * - * @param {Function} [fn] - The listener function to be removed + * @param {Function} fn - The listener function to be removed * @param {Function} [context] - The listener context to be removed * @returns {PIXI.ticker.Ticker} This instance of a ticker */ remove(fn, context) { - this._emitter.off(TICK, fn, context); + let listener = this._head.next; - if (!this._emitter.listeners(TICK, true)) + while (listener) + { + // We found a match, lets remove it + // no break to delete all possible matches + // incase a listener was added 2+ times + if (listener.match(fn, context)) + { + listener = listener.destroy(); + } + else + { + listener = listener.next; + } + } + + if (!this._head.next) { this._cancelIfNeeded(); } @@ -276,6 +326,25 @@ } /** + * Destroy the ticker and don't use after this. Calling + * this method removes all references to internal events. + */ + destroy() + { + this.stop(); + + let listener = this._head.next; + + while (listener) + { + listener = listener.destroy(true); + } + + this._head.destroy(); + this._head = null; + } + + /** * Triggers an update. An update entails setting the * current {@link PIXI.ticker.Ticker#elapsedMS}, * the current {@link PIXI.ticker.Ticker#deltaTime}, @@ -320,8 +389,22 @@ this.deltaTime = elapsedMS * settings.TARGET_FPMS * this.speed; + // Cache a local reference, in-case ticker is destroyed + // during the emit, we can still check for head.next + const head = this._head; + // Invoke listeners added to internal emitter - this._emitter.emit(TICK, this.deltaTime); + let listener = head.next; + + while (listener) + { + listener = listener.emit(this.deltaTime); + } + + if (!head.next) + { + this._cancelIfNeeded(); + } } else { diff --git a/src/core/ticker/TickerListener.js b/src/core/ticker/TickerListener.js new file mode 100644 index 0000000..2bedb34 --- /dev/null +++ b/src/core/ticker/TickerListener.js @@ -0,0 +1,158 @@ +/** + * Internal class for handling the priority sorting of ticker handlers. + * + * @private + * @class + * @memberof PIXI.ticker + */ +export default class TickerListener +{ + /** + * Constructor + * + * @param {Function} fn - The listener function to be added for one update + * @param {Function} [context=null] - The listener context + * @param {number} [priority=0] - The priority for emitting + * @param {boolean} [once=false] - If the handler should fire once + */ + constructor(fn, context = null, priority = 0, once = false) + { + /** + * The handler function to execute. + * @member {Function} + */ + this.fn = fn; + + /** + * The calling to execute. + * @member {Function} + */ + this.context = context; + + /** + * The current priority. + * @member {number} + */ + this.priority = priority; + + /** + * If this should only execute once. + * @member {boolean} + */ + this.once = once; + + /** + * The next item in chain. + * @member {TickerListener} + */ + this.next = null; + + /** + * The previous item in chain. + * @member {TickerListener} + */ + this.previous = null; + + /** + * `true` if this listener has been destroyed already. + * @member {boolean} + * @private + */ + this._destroyed = false; + } + + /** + * Simple compare function to figure out if a function and context match. + * + * @param {Function} fn - The listener function to be added for one update + * @param {Function} context - The listener context + * @return {boolean} `true` if the listener match the arguments + */ + match(fn, context) + { + context = context || null; + + return this.fn === fn && this.context === context; + } + + /** + * Emit by calling the current function. + * @param {number} deltaTime - time since the last emit. + * @return {TickerListener} Next ticker + */ + emit(deltaTime) + { + if (this.context) + { + this.fn.call(this.context, deltaTime); + } + else + { + this.fn(deltaTime); + } + + if (this.once) + { + this.destroy(); + } + + const redirect = this.next; + + // Soft-destroying should remove + // the next reference + if (this._destroyed) + { + this.next = null; + } + + return redirect; + } + + /** + * Connect to the list. + * @param {TickerListener} previous - Input node, previous listener + */ + connect(previous) + { + this.previous = previous; + if (previous.next) + { + previous.next.previous = this; + } + this.next = previous.next; + previous.next = this; + } + + /** + * Destroy and don't use after this. + * @param {boolean} [hard = false] `true` to remove the `next` reference, this + * is considered a hard destroy. Soft destroy maintains the next reference. + * @return {TickerListener} The listener to redirect while emitting or removing. + */ + destroy(hard = false) + { + this._destroyed = true; + this.fn = null; + this.context = null; + + // Disconnect, hook up next and previous + if (this.previous) + { + this.previous.next = this.next; + } + + if (this.next) + { + this.next.previous = this.previous; + } + + // Redirect to the next item + const redirect = this.previous; + + // Remove references + this.next = hard ? null : redirect; + this.previous = null; + + return redirect; + } +} diff --git a/src/core/ticker/index.js b/src/core/ticker/index.js index 4b3017c..c3020d4 100644 --- a/src/core/ticker/index.js +++ b/src/core/ticker/index.js @@ -45,8 +45,25 @@ const shared = new Ticker(); shared.autoStart = true; +shared.destroy = () => +{ + // protect destroying shared ticker + // this is used by other internal systems + // like AnimatedSprite and InteractionManager +}; /** + * This namespace contains an API for interacting with PIXI's internal global update loop. + * + * This ticker is used for rendering, {@link PIXI.extras.AnimatedSprite AnimatedSprite}, + * {@link PIXI.interaction.InteractionManager InteractionManager} and many other time-based PIXI systems. + * @example + * const ticker = new PIXI.ticker.Ticker(); + * ticker.stop(); + * ticker.add((deltaTime) => { + * // do something every frame + * }); + * ticker.start(); * @namespace PIXI.ticker */ export { shared, Ticker }; diff --git a/src/core/utils/index.js b/src/core/utils/index.js index be44750..7441f7b 100644 --- a/src/core/utils/index.js +++ b/src/core/utils/index.js @@ -9,6 +9,21 @@ let saidHello = false; /** + * Generalized convenience utilities for PIXI. + * @example + * // Extend PIXI's internal Event Emitter. + * class MyEmitter extends PIXI.utils.EventEmitter { + * constructor() { + * super(); + * console.log("Emitter created!"); + * } + * } + * + * // Get info on current device + * console.log(PIXI.utils.isMobile); + * + * // Convert hex color to string + * console.log(PIXI.utils.hex2string(0xff00ff)); // returns: "#ff00ff" * @namespace PIXI.utils */ export { diff --git a/src/deprecation.js b/src/deprecation.js index b9c73b3..33daeec 100644 --- a/src/deprecation.js +++ b/src/deprecation.js @@ -918,6 +918,35 @@ }); /** + * @method + * @name PIXI.prepare.BasePrepare#register + * @see PIXI.prepare.BasePrepare#registerFindHook + * @deprecated since version 4.4.2 + * @param {Function} [addHook] - Function call that takes two parameters: `item:*, queue:Array` + * function must return `true` if it was able to add item to the queue. + * @param {Function} [uploadHook] - Function call that takes two parameters: `prepare:CanvasPrepare, item:*` and + * function must return `true` if it was able to handle upload of item. + * @return {PIXI.BasePrepare} Instance of plugin for chaining. + */ +prepare.BasePrepare.prototype.register = function register(addHook, uploadHook) +{ + warn('renderer.plugins.prepare.register is now deprecated, ' + + 'please use renderer.plugins.prepare.registerFindHook & renderer.plugins.prepare.registerUploadHook'); + + if (addHook) + { + this.registerFindHook(addHook); + } + + if (uploadHook) + { + this.registerUploadHook(uploadHook); + } + + return this; +}; + +/** * The number of graphics or textures to upload to the GPU. * * @name PIXI.prepare.canvas.UPLOADS_PER_FRAME @@ -969,67 +998,73 @@ }, }); -Object.defineProperties(loaders.Resource.prototype, { - isJson: { - get() - { - warn('The isJson property is deprecated, please use `resource.type === Resource.TYPE.JSON`.'); +if (loaders.Loader) +{ + const Resource = loaders.Resource; + const Loader = loaders.Loader; - return this.type === loaders.Loader.Resource.TYPE.JSON; + Object.defineProperties(Resource.prototype, { + isJson: { + get() + { + warn('The isJson property is deprecated, please use `resource.type === Resource.TYPE.JSON`.'); + + return this.type === Resource.TYPE.JSON; + }, }, - }, - isXml: { - get() - { - warn('The isXml property is deprecated, please use `resource.type === Resource.TYPE.XML`.'); + isXml: { + get() + { + warn('The isXml property is deprecated, please use `resource.type === Resource.TYPE.XML`.'); - return this.type === loaders.Loader.Resource.TYPE.XML; + return this.type === Resource.TYPE.XML; + }, }, - }, - isImage: { - get() - { - warn('The isImage property is deprecated, please use `resource.type === Resource.TYPE.IMAGE`.'); + isImage: { + get() + { + warn('The isImage property is deprecated, please use `resource.type === Resource.TYPE.IMAGE`.'); - return this.type === loaders.Loader.Resource.TYPE.IMAGE; + return this.type === Resource.TYPE.IMAGE; + }, }, - }, - isAudio: { - get() - { - warn('The isAudio property is deprecated, please use `resource.type === Resource.TYPE.AUDIO`.'); + isAudio: { + get() + { + warn('The isAudio property is deprecated, please use `resource.type === Resource.TYPE.AUDIO`.'); - return this.type === loaders.Loader.Resource.TYPE.AUDIO; + return this.type === Resource.TYPE.AUDIO; + }, }, - }, - isVideo: { - get() - { - warn('The isVideo property is deprecated, please use `resource.type === Resource.TYPE.VIDEO`.'); + isVideo: { + get() + { + warn('The isVideo property is deprecated, please use `resource.type === Resource.TYPE.VIDEO`.'); - return this.type === loaders.Loader.Resource.TYPE.VIDEO; + return this.type === Resource.TYPE.VIDEO; + }, }, - }, -}); + }); -Object.defineProperties(loaders.Loader.prototype, { - before: { - get() - { - warn('The before() method is deprecated, please use pre().'); + Object.defineProperties(Loader.prototype, { + before: { + get() + { + warn('The before() method is deprecated, please use pre().'); - return this.pre; + return this.pre; + }, }, - }, - after: { - get() - { - warn('The after() method is deprecated, please use use().'); + after: { + get() + { + warn('The after() method is deprecated, please use use().'); - return this.use; + return this.use; + }, }, - }, -}); + }); +} /** * @name PIXI.interaction.interactiveTarget#defaultCursor diff --git a/src/extract/canvas/CanvasExtract.js b/src/extract/canvas/CanvasExtract.js index 941ca05..3ac5aee 100644 --- a/src/extract/canvas/CanvasExtract.js +++ b/src/extract/canvas/CanvasExtract.js @@ -8,7 +8,7 @@ * An instance of this class is automatically created by default, and can be found at renderer.plugins.extract * * @class - * @memberof PIXI + * @memberof PIXI.extract */ export default class CanvasExtract { @@ -21,9 +21,9 @@ /** * Collection of methods for extracting data (image, pixels, etc.) from a display object or render texture * - * @member {PIXI.CanvasExtract} extract + * @member {PIXI.extract.CanvasExtract} extract * @memberof PIXI.CanvasRenderer# - * @see PIXI.CanvasExtract + * @see PIXI.extract.CanvasExtract */ renderer.extract = this; } diff --git a/.travis.yml b/.travis.yml index 3df8b22..14de334 100644 --- a/.travis.yml +++ b/.travis.yml @@ -110,3 +110,43 @@ on: all_branches: true condition: $TRAVIS_TAG + # Deploy config for latest release + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: dist + upload-dir: "release" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: docs + upload-dir: "release/docs" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: coverage + upload-dir: "release/coverage" + on: + all_branches: true + condition: $TRAVIS_TAG \ No newline at end of file diff --git a/README.md b/README.md index d26fadd..3b85f85 100644 --- a/README.md +++ b/README.md @@ -36,22 +36,14 @@ - Wiki: Other misc tutorials and resources are [on the Wiki](https://github.com/pixijs/pixi.js/wiki/Resources). ### Community ### -- Forums: Check out the [forum] (http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow] (http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. +- Forums: Check out the [forum](http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow](http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. - Inspiration: Check out the [gallery](http://www.pixijs.com/gallery) to see some of the amazing things people have created! - Chat: You can join us on [Gitter](https://gitter.im/pixijs/pixi.js) To chat about Pixi. We also now have a Slack channel. If you would like to join it please Send me an email (mat@goodboydigital.com) and I will invite you in. ### Setup ### -It's easy to get started with Pixi.js! Simply grab the pre-built versions from here: - -Release Branch - Nice and stable Pixi.js -- Unminified: [http://pixijs.download/release/pixi.js] -- Minified: [http://pixijs.download/release/pixi.min.js] - -Develop Branch - The bleeding edge version of Pixi.js -- Unminified: [http://pixijs.download/dev/pixi.js] -- Minified: [http://pixijs.download/dev/pixi.min.js] +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. @@ -70,10 +62,10 @@ #### CDN Install (via cdnjs) ```html - + ``` -_Note: `4.2.2` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ +_Note: `4.4.3` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ ### Demos ### @@ -130,7 +122,7 @@ // and the root stage PIXI.Container. var app = new PIXI.Application(); -// The application will create a canvas element for you that you +// The application will create a canvas element for you that you // can then insert into the DOM. document.body.appendChild(app.view); @@ -150,7 +142,7 @@ // Add the bunny to the scene we are building. app.stage.addChild(bunny); - + // Listen for frame updates app.ticker.add(function() { // each frame we spin the bunny around a bit diff --git a/bower.json b/bower.json deleted file mode 100644 index 41b668c..0000000 --- a/bower.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "pixi.js", - "main": "dist/pixi.min.js", - "ignore": [ - "**/.*", - "docs", - "node_modules", - "bower_components", - "test", - "scripts", - "bower.json", - "inch.json" - ], - "dependencies": { - }, - "devDependencies": { - } -} diff --git a/package.json b/package.json index d35cbb2..308469f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.3.5", + "version": "4.4.4", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ @@ -40,11 +40,11 @@ "lib": "babel src --out-dir lib -s", "predocs": "rimraf docs/**", "docs": "jsdoc -c scripts/jsdoc.conf.json -R README.md", - "publish:patch": "npm version patch --no-git-tag-version && npm publish", - "publish:minor": "npm version minor --no-git-tag-version && npm publish", - "publish:major": "npm version major --no-git-tag-version && npm publish", + "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", - "postpublish": "node scripts/release.js" + "postpublish": "git push && git push --tags" }, "files": [ "dist/", @@ -72,7 +72,6 @@ "electron": "^1.4.15", "eslint": "^3.5.0", "floss": "^2.0.1", - "gh-pages": "^0.11.0", "jaguarjs-jsdoc": "^1.0.1", "js-md5": "^0.4.1", "jsdoc": "^3.4.2", diff --git a/scripts/jsdoc.conf.json b/scripts/jsdoc.conf.json index 553e61c..3f52180 100644 --- a/scripts/jsdoc.conf.json +++ b/scripts/jsdoc.conf.json @@ -43,7 +43,7 @@ }, "markdown" : { "parser" : "gfm", - "hardwrap" : true + "hardwrap" : false }, "opts": { "encoding" : "utf8", diff --git a/scripts/release.js b/scripts/release.js deleted file mode 100644 index 156620c..0000000 --- a/scripts/release.js +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env node -'use strict'; - -// Publish script to push releases of the bin files -// the normally are gitignored -const ghpages = require('gh-pages'); -const path = require('path'); -const packageInfo = require(path.join(__dirname, '..', 'package.json')); - -const options = { - src: [ - 'dist/**/*', - 'lib/**/*', - 'src/**/*', - 'scripts/*', - 'scripts/renders/*', - 'test/**/*', - '*.json', - '*.md', - 'LICENSE', - '.eslintrc', - '.editorconfig', - '.travis.yml', - '.babelrc', - ], - dotfiles: true, - branch: 'release', - message: packageInfo.version, - logger: console.log.bind(console), -}; - -ghpages.publish(process.cwd(), options, (err) => -{ - if (err) - { - console.log(err); - process.exit(1); - - return; - } - - process.exit(0); -}); diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index f16bcb1..220427a 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -21,7 +21,7 @@ const DIV_HOOK_ZINDEX = 2; /** - * The Accessibility manager reacreates the ability to tab and and have content read by screen + * The Accessibility manager recreates the ability to tab and and have content read by screen * readers. This is very important as it can possibly help people with disabilities access pixi * content. * diff --git a/src/accessibility/index.js b/src/accessibility/index.js index 568cfb1..0ae108f 100644 --- a/src/accessibility/index.js +++ b/src/accessibility/index.js @@ -1,4 +1,9 @@ /** + * This namespace contains a renderer plugin for interaction accessibility for end-users + * with physical impairments which require screen-renders, keyboard navigation, etc. + * + * Do not instantiate this plugin directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. * @namespace PIXI.accessibility */ export { default as accessibleTarget } from './accessibleTarget'; diff --git a/src/core/Application.js b/src/core/Application.js index 5340578..2fcc729 100644 --- a/src/core/Application.js +++ b/src/core/Application.js @@ -1,6 +1,8 @@ import { autoDetectRenderer } from './autoDetectRenderer'; import Container from './display/Container'; import { shared, Ticker } from './ticker'; +import settings from './settings'; +import { UPDATE_PRIORITY } from './const'; /** * Convenience class to create a new PIXI application. @@ -22,28 +24,52 @@ */ export default class Application { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experience unexplained flickering try setting this to true. - * @param {boolean} [useSharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedLoader=false] - `true` to use PIXI.loaders.shared, `false` to create new Loader. */ - constructor(width, height, options, noWebGL, useSharedTicker = false) + constructor(options, arg2, arg3, arg4, arg5) { + // Support for constructor(width, height, options, noWebGL, useSharedTicker) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + forceCanvas: !!arg4, + sharedTicker: !!arg5, + }, arg3); + } + + /** + * The default options, so we mixin functionality later. + * @member {object} + * @protected + */ + this._options = options = Object.assign({ + sharedTicker: false, + forceCanvas: false, + sharedLoader: false, + }, options); + /** * WebGL renderer if available, otherwise CanvasRenderer * @member {PIXI.WebGLRenderer|PIXI.CanvasRenderer} */ - this.renderer = autoDetectRenderer(width, height, options, noWebGL); + this.renderer = autoDetectRenderer(options); /** * The root display container that's rendered. @@ -63,7 +89,7 @@ * @member {PIXI.ticker.Ticker} * @default PIXI.ticker.shared */ - this.ticker = useSharedTicker ? shared : new Ticker(); + this.ticker = options.sharedTicker ? shared : new Ticker(); // Start the rendering this.start(); @@ -78,7 +104,7 @@ this._ticker = ticker; if (ticker) { - ticker.add(this.render, this); + ticker.add(this.render, this, UPDATE_PRIORITY.LOW); } } get ticker() // eslint-disable-line require-jsdoc @@ -136,13 +162,18 @@ */ destroy(removeView) { - this.stop(); + const oldTicker = this._ticker; + this.ticker = null; + oldTicker.destroy(); + this.stage.destroy(); this.stage = null; this.renderer.destroy(removeView); this.renderer = null; + + this._options = null; } } diff --git a/src/core/autoDetectRenderer.js b/src/core/autoDetectRenderer.js index 8960425..9ec0364 100644 --- a/src/core/autoDetectRenderer.js +++ b/src/core/autoDetectRenderer.js @@ -2,6 +2,7 @@ import CanvasRenderer from './renderers/canvas/CanvasRenderer'; import WebGLRenderer from './renderers/webgl/WebGLRenderer'; +// eslint-disable-next-line valid-jsdoc /** * This helper function will automatically detect which renderer you should be using. * WebGL is the preferred renderer as it is a lot faster. If webGL is not supported by @@ -9,24 +10,32 @@ * * @memberof PIXI * @function autoDetectRenderer - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @return {PIXI.WebGLRenderer|PIXI.CanvasRenderer} Returns WebGL renderer if available, otherwise CanvasRenderer */ -export function autoDetectRenderer(width = 800, height = 600, options, noWebGL) +export function autoDetectRenderer(options, arg1, arg2, arg3) { - if (!noWebGL && utils.isWebGLSupported()) + // Backward-compatible support for noWebGL option + let forceCanvas = options && options.forceCanvas; + + if (arg3 !== undefined) { - return new WebGLRenderer(width, height, options); + forceCanvas = arg3; } - return new CanvasRenderer(width, height, options); + if (!forceCanvas && utils.isWebGLSupported()) + { + return new WebGLRenderer(options, arg1, arg2); + } + + return new CanvasRenderer(options, arg1, arg2); } diff --git a/src/core/const.js b/src/core/const.js index b9e99ae..2c68dee 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -341,3 +341,27 @@ LINEAR_VERTICAL: 0, LINEAR_HORIZONTAL: 1, }; + +/** + * Represents the update priorities used by internal PIXI classes when registered with + * the {@link PIXI.ticker.Ticker} object. Higher priority items are updated first and lower + * priority items, such as render, should go later. + * + * @static + * @constant + * @name UPDATE_PRIORITY + * @memberof PIXI + * @type {object} + * @property {number} INTERACTION=50 Highest priority, used for {@link PIXI.interaction.InteractionManager} + * @property {number} HIGH=25 High priority updating, {@link PIXI.VideoBaseTexture} and {@link PIXI.extras.AnimatedSprite} + * @property {number} NORMAL=0 Default priority for ticker events, see {@link PIXI.ticker.Ticker#add}. + * @property {number} LOW=-25 Low priority used for {@link PIXI.Application} rendering. + * @property {number} UTILITY=-50 Lowest priority used for {@link PIXI.prepare.BasePrepare} utility. + */ +export const UPDATE_PRIORITY = { + INTERACTION: 50, + HIGH: 25, + NORMAL: 0, + LOW: -25, + UTILITY: -50, +}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 4e6c77d..decb42a 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -13,7 +13,6 @@ * * @class * @extends EventEmitter - * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ export default class DisplayObject extends EventEmitter @@ -113,6 +112,29 @@ * @private */ this._mask = null; + + /** + * If the object has been destroyed via destroy(). If true, it should not be used. + * + * @member {boolean} + * @private + * @readonly + */ + this._destroyed = false; + + /** + * Fired when this DisplayObject is added to a Container. + * + * @event PIXI.DisplayObject#added + * @param {PIXI.Container} container - The container added to. + */ + + /** + * Fired when this DisplayObject is removed from a Container. + * + * @event PIXI.DisplayObject#removed + * @param {PIXI.Container} container - The container removed from. + */ } /** @@ -403,6 +425,8 @@ this.interactive = false; this.interactiveChildren = false; + + this._destroyed = true; } /** diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index aafdfe2..f787c4a 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -792,8 +792,8 @@ sprite.worldAlpha = this.worldAlpha * sprite.alpha; sprite.blendMode = this.blendMode; - sprite.texture._frame.width = rect.width; - sprite.texture._frame.height = rect.height; + sprite._texture._frame.width = rect.width; + sprite._texture._frame.height = rect.height; sprite.transform.worldTransform = this.transform.worldTransform; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index a24c775..b9f192d 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -19,11 +19,12 @@ */ export default class SystemRenderer extends EventEmitter { + // eslint-disable-next-line valid-jsdoc /** * @param {string} system - The name of the system this renderer is for. - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -37,27 +38,31 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(system, screenWidth, screenHeight, options) + constructor(system, options, arg2, arg3) { super(); sayHello(system); - // prepare options - if (options) + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') { - for (const i in settings.RENDER_OPTIONS) - { - if (typeof options[i] === 'undefined') - { - options[i] = settings.RENDER_OPTIONS[i]; - } - } + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); } - else - { - options = settings.RENDER_OPTIONS; - } + + // Add the default render options + options = Object.assign({}, settings.RENDER_OPTIONS, options); + + /** + * The supplied constructor options. + * + * @member {Object} + * @readOnly + */ + this.options = options; /** * The type of the renderer. @@ -75,7 +80,7 @@ * * @member {PIXI.Rectangle} */ - this.screen = new Rectangle(0, 0, screenWidth || 800, screenHeight || 600); + this.screen = new Rectangle(0, 0, options.width, options.height); /** * The canvas element that everything is drawn to @@ -279,6 +284,8 @@ this.blendModes = null; + this.options = null; + this.preserveDrawingBuffer = false; this.clearBeforeRender = false; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index e84e49c..691caf0 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -17,10 +17,11 @@ */ export default class CanvasRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -34,9 +35,9 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('Canvas', screenWidth, screenHeight, options); + super('Canvas', options, arg2, arg3); this.type = RENDERER_TYPE.CANVAS; @@ -96,7 +97,19 @@ this.context = null; this.renderingToScreen = false; - this.resize(screenWidth, screenHeight); + this.resize(this.options.width, this.options.height); + + /** + * Fired after rendering finishes. + * + * @event PIXI.CanvasRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.CanvasRenderer#prerender + */ } /** @@ -299,4 +312,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.CanvasRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.CanvasExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.CanvasPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.CanvasRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index e1cf7cd..f6fe123 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -31,11 +31,12 @@ */ export default class WebGLRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** * - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -55,9 +56,9 @@ * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('WebGL', screenWidth, screenHeight, options); + super('WebGL', options, arg2, arg3); /** * The type of this renderer as a standardised const @@ -89,12 +90,6 @@ }; - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ this._backgroundColorRgba[3] = this.transparent ? 0 : 1; this.globalUniforms = new UniformGroup({ @@ -117,6 +112,13 @@ this.initPlugins(); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ if(options.context) { this.context.initFromContext(options.context); @@ -169,6 +171,25 @@ } return this; + + /** + * Fired after rendering finishes. + * + * @event PIXI.WebGLRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.WebGLRenderer#prerender + */ + + /** + * Fired when the WebGL context is set. + * + * @event PIXI.WebGLRenderer#context + * @param {WebGLRenderingContext} gl - WebGL context. + */ } /** @@ -295,4 +316,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.WebGLRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.WebGLRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(WebGLRenderer); diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index 4eb6128..fa3d883 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -47,6 +47,14 @@ * @member {boolean} */ this.enabled = true; + + /** + * If enabled, pixi will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; } /** diff --git a/src/core/renderers/webgl/systems/ContextSystem.js b/src/core/renderers/webgl/systems/ContextSystem.js index ff958c7..8df85ea 100644 --- a/src/core/renderers/webgl/systems/ContextSystem.js +++ b/src/core/renderers/webgl/systems/ContextSystem.js @@ -75,7 +75,8 @@ */ createContext(canvas, options) { - var gl = canvas.getContext('webgl', options) || + var gl = //canvas.getContext('webgl2', options) || + canvas.getContext('webgl', options) || canvas.getContext('experimental-webgl', options); if (!gl) diff --git a/src/core/renderers/webgl/systems/FilterSystem.js b/src/core/renderers/webgl/systems/FilterSystem.js index f47335d..37e28d2 100644 --- a/src/core/renderers/webgl/systems/FilterSystem.js +++ b/src/core/renderers/webgl/systems/FilterSystem.js @@ -111,7 +111,7 @@ // TODO we should fit the rect around the transform.. } - else + else if (filters[0].autoFit) { sourceFrame.fit(filterData.stack[0].destinationFrame); } diff --git a/src/core/renderers/webgl/systems/shader/shader/mapType.js b/src/core/renderers/webgl/systems/shader/shader/mapType.js index 581dace..917538a 100644 --- a/src/core/renderers/webgl/systems/shader/shader/mapType.js +++ b/src/core/renderers/webgl/systems/shader/shader/mapType.js @@ -15,7 +15,7 @@ } } - return GL_TABLE[type]; + return GL_TABLE[type]; }; var GL_TABLE = null; diff --git a/src/core/settings.js b/src/core/settings.js index 18c5877..38ef45c 100644 --- a/src/core/settings.js +++ b/src/core/settings.js @@ -2,6 +2,15 @@ import canUploadSameBuffer from './utils/canUploadSameBuffer'; /** + * User's customizable globals for overriding the default PIXI settings, such + * as a renderer's default resolution, framerate, float percision, etc. + * @example + * // Use the native window resolution as the default resolution + * // will support high-density displays when rendering + * PIXI.settings.RESOLUTION = window.devicePixelRatio. + * + * // Disable interpolation when scaling, will make texture be pixelated + * PIXI.settings.SCALE_MODE = PIXI.SCALE_MODES.NEAREST; * @namespace PIXI.settings */ export default { @@ -101,6 +110,9 @@ * @property {boolean} clearBeforeRender=true * @property {boolean} preserveDrawingBuffer=false * @property {boolean} roundPixels=false + * @property {number} width=800 + * @property {number} height=600 + * @property {boolean} legacy=false */ RENDER_OPTIONS: { view: null, @@ -112,6 +124,9 @@ clearBeforeRender: true, preserveDrawingBuffer: false, roundPixels: false, + width: 800, + height: 600, + legacy: false, }, /** diff --git a/src/core/shader/Program.js b/src/core/shader/Program.js index c0b538b..07b4398 100644 --- a/src/core/shader/Program.js +++ b/src/core/shader/Program.js @@ -1,6 +1,6 @@ import extractUniformsFromSrc from './extractUniformsFromSrc'; import generateUniformsSync from './generateUniformsSync'; -import glCore from 'pixi-gl-core'; +import shaderUtils from '../renderers/webgl/systems/shader/shader'; import { ProgramCache } from '../utils'; import getTestContext from '../utils/getTestContext'; @@ -64,14 +64,17 @@ } else { - vertexSrc = glCore.shader.setPrecision(vertexSrc, 'mediump'); - fragmentSrc = glCore.shader.setPrecision(fragmentSrc, 'mediump'); + vertexSrc = shaderUtils.setPrecision(vertexSrc, 'mediump'); + fragmentSrc = shaderUtils.setPrecision(fragmentSrc, 'mediump'); - const program = glCore.shader.compileProgram(gl, vertexSrc, fragmentSrc); + const program = shaderUtils.compileProgram(gl, vertexSrc, fragmentSrc); + console.log("<<<<<<>>><<"); this.attributeData = this.getAttributeData(program, gl); this.uniformData = this.getUniformData(program, gl); + console.log(this.uniformData); + //gl.deleteProgram(program); } } @@ -95,13 +98,13 @@ for (let i = 0; i < totalAttributes; i++) { const attribData = gl.getActiveAttrib(program, i); - const type = glCore.shader.mapType(gl, attribData.type); + const type = shaderUtils.mapType(gl, attribData.type); /*eslint-disable */ const data = { type: type, name: attribData.name, - size: glCore.shader.mapSize(type), + size: shaderUtils.mapSize(type), location: 0, }; /*eslint-enable */ @@ -137,13 +140,14 @@ // TODO expose this as a prop? // const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); - const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); + //const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); for (let i = 0; i < totalUniforms; i++) { const uniformData = gl.getActiveUniform(program, i); const name = uniformData.name.replace(/\[.*?\]/, ''); - const type = glCore.shader.mapType(gl, uniformData.type); + const type = shaderUtils.mapType(gl, uniformData.type); + console.log('mapping ' + uniformData.type + ' to ' + type); // if (!name.match(maskRegex)) { @@ -151,7 +155,7 @@ uniforms[name] = { type: type, size: uniformData.size, - value: glCore.shader.defaultValue(type, uniformData.size), + value: shaderUtils.defaultValue(type, uniformData.size), }; /*eslint-enable */ } diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index e387e99..b57d095 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -147,12 +147,12 @@ // so if _width is 0 then width was not set.. if (this._width) { - this.scale.x = sign(this.scale.x) * this._width / this.texture.orig.width; + this.scale.x = sign(this.scale.x) * this._width / this._texture.orig.width; } if (this._height) { - this.scale.y = sign(this.scale.y) * this._height / this.texture.orig.height; + this.scale.y = sign(this.scale.y) * this._height / this._texture.orig.height; } } diff --git a/src/core/sprites/canvas/CanvasTinter.js b/src/core/sprites/canvas/CanvasTinter.js index 27e5e0c..fd9de5d 100644 --- a/src/core/sprites/canvas/CanvasTinter.js +++ b/src/core/sprites/canvas/CanvasTinter.js @@ -18,7 +18,7 @@ */ getTintedTexture: (sprite, color) => { - const texture = sprite.texture; + const texture = sprite._texture; color = CanvasTinter.roundColor(color); diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 5a4edda..8b9b797 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -344,6 +344,7 @@ const texture = this._texture; const style = this._style; + const padding = style.trim ? 0 : style.padding; texture.baseTexture.valid = true; texture.baseTexture.resolution = this.resolution; @@ -353,11 +354,11 @@ texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - texture.trim.x = -style.padding; - texture.trim.y = -style.padding; + texture.trim.x = -padding; + texture.trim.y = -padding; - texture.orig.width = texture._frame.width - (style.padding * 2); - texture.orig.height = texture._frame.height - (style.padding * 2); + texture.orig.width = texture._frame.width - (padding * 2); + texture.orig.height = texture._frame.height - (padding * 2); // call sprite onTextureUpdate to update scale if _width or _height were set this._onTextureUpdate(); diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index 853da3d..8a9c34b 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -114,6 +114,38 @@ this.cacheId = null; this.validate(); + + /** + * Fired when a not-immediately-available source finishes loading. + * + * @protected + * @event PIXI.BaseTexture#loaded + * @param {PIXI.BaseTexture} baseTexture - Resource loaded. + */ + + /** + * Fired when a not-immediately-available source fails to load. + * + * @protected + * @event PIXI.BaseTexture#error + * @param {PIXI.BaseTexture} baseTexture - Resource errored. + */ + + /** + * Fired when BaseTexture is updated. + * + * @protected + * @event PIXI.BaseTexture#update + * @param {PIXI.BaseTexture} baseTexture - Instance of texture being updated. + */ + + /** + * Fired when BaseTexture is destroyed. + * + * @protected + * @event PIXI.BaseTexture#dispose + * @param {PIXI.BaseTexture} baseTexture - Instance of texture being destroyed. + */ } setResource(resource) diff --git a/src/core/textures/Spritesheet.js b/src/core/textures/Spritesheet.js index d469c62..9b24f27 100644 --- a/src/core/textures/Spritesheet.js +++ b/src/core/textures/Spritesheet.js @@ -208,6 +208,7 @@ // lets also add the frame to pixi's global cache for fromFrame and fromImage functions TextureCache[i] = this.textures[i]; + this.textures[i].textureCacheId = i; } frameIndex++; diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 0a2c379..2213087 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -147,9 +147,9 @@ /** * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. * - * @event update - * @memberof PIXI.Texture# + * @event PIXI.Texture#update * @protected + * @param {PIXI.Texture} texture - Instance of texture being updated. */ this._updateID = 0; @@ -159,6 +159,15 @@ * @type {Object} */ this.transform = null; + + /** + * The id under which this Texture has been added to the texture cache. This is + * automatically set in certain cases, but may not always be accurate, particularly if + * the texture is in the cache under multiple ids. + * + * @member {string} + */ + this.textureCacheId = null; } /** @@ -225,11 +234,17 @@ this._uvs = null; this.trim = null; this.orig = null; + this.textureCacheId = null; this.valid = false; - this.off('dispose', this.dispose, this); - this.off('update', this.update, this); + for (const prop in TextureCache) + { + if (TextureCache[prop] === this) + { + delete TextureCache[prop]; + } + } } /** @@ -315,14 +330,15 @@ */ static fromLoader(source, imageUrl, name) { - // console.log('added from loader...') + // console.log('added from loader...') const resource = new ImageResource(source);//.from(imageUrl, crossorigin);// document.createElement('img'); - // console.log('base resource ' + resource.width); + // console.log('base resource ' + resource.width); const baseTexture = new BaseTexture(resource, settings.SCALE_MODE, getResolutionOfUrl(imageUrl)); + /// console.log('base width ' + baseTexture.width); const texture = new Texture(baseTexture); @@ -335,6 +351,7 @@ // lets also add the frame to pixi's global cache for fromFrame and fromImage fucntions BaseTextureCache[name] = baseTexture; TextureCache[name] = texture; + texture.textureCacheId = name; // also add references by url if they are different. if (name !== imageUrl) @@ -355,6 +372,10 @@ */ static addTextureToCache(texture, id) { + if (!texture.textureCacheId) + { + texture.textureCacheId = id; + } TextureCache[id] = texture; } @@ -495,6 +516,7 @@ */ Texture.EMPTY = new Texture(new BaseTexture()); removeAllHandlers(Texture.EMPTY); +removeAllHandlers(Texture.EMPTY.baseTexture); /** * A white texture of 10x10 size, used for graphics and other things @@ -505,3 +527,4 @@ */ Texture.WHITE = createWhiteTexture(); removeAllHandlers(Texture.WHITE); +removeAllHandlers(Texture.WHITE.baseTexture); diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index 59eb9fd..a9b9f51 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -1,6 +1,7 @@ import BaseTexture from './BaseTexture'; import { uid, BaseTextureCache } from '../utils'; -import * as ticker from '../ticker'; +import { shared } from '../ticker'; +import { UPDATE_PRIORITY } from '../const'; /** * A texture of a [playing] Video. @@ -125,7 +126,7 @@ if (!this._isAutoUpdating && this.autoUpdate) { - ticker.shared.add(this.update, this); + shared.add(this.update, this, UPDATE_PRIORITY.HIGH); this._isAutoUpdating = true; } } @@ -139,7 +140,7 @@ { if (this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); this._isAutoUpdating = false; } } @@ -187,7 +188,7 @@ { if (this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); } if (this.source && this.source._pixiId) @@ -281,12 +282,12 @@ if (!this._autoUpdate && this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); this._isAutoUpdating = false; } else if (this._autoUpdate && !this._isAutoUpdating) { - ticker.shared.add(this.update, this); + shared.add(this.update, this, UPDATE_PRIORITY.HIGH); this._isAutoUpdating = true; } } diff --git a/src/core/ticker/Ticker.js b/src/core/ticker/Ticker.js index 17a1517..40df138 100644 --- a/src/core/ticker/Ticker.js +++ b/src/core/ticker/Ticker.js @@ -1,12 +1,10 @@ import settings from '../settings'; -import EventEmitter from 'eventemitter3'; - -// Internal event used by composed emitter -const TICK = 'tick'; +import { UPDATE_PRIORITY } from '../const'; +import TickerListener from './TickerListener'; /** * A Ticker class that runs an update loop that other objects listen to. - * This class is composed around an EventEmitter object to add listeners + * This class is composed around listeners * meant for execution on the next requested animation frame. * Animation frames are requested only when necessary, * e.g. When the ticker is started and the emitter has listeners. @@ -22,10 +20,11 @@ constructor() { /** - * Internal emitter used to fire 'tick' event + * The first listener. All new listeners added are chained on this. * @private + * @type {TickerListener} */ - this._emitter = new EventEmitter(); + this._head = new TickerListener(null, null, Infinity); /** * Internal current frame request ID @@ -131,7 +130,7 @@ // Invoke listeners now this.update(time); // Listener side effects may have modified ticker state. - if (this.started && this._requestId === null && this._emitter.listeners(TICK, true)) + if (this.started && this._requestId === null && this._head.next) { this._requestId = requestAnimationFrame(this._tick); } @@ -148,7 +147,7 @@ */ _requestIfNeeded() { - if (this._requestId === null && this._emitter.listeners(TICK, true)) + if (this._requestId === null && this._head.next) { // ensure callbacks get correct delta this.lastTime = performance.now(); @@ -193,35 +192,72 @@ } /** - * Calls {@link module:eventemitter3.EventEmitter#on} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. + * Register a handler for tick events. Calls continuously unless + * it is removed or the ticker is stopped. * * @param {Function} fn - The listener function to be added for updates * @param {Function} [context] - The listener context + * @param {number} [priority=PIXI.UPDATE_PRIORITY.NORMAL] - The priority for emitting * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - add(fn, context) + add(fn, context, priority = UPDATE_PRIORITY.NORMAL) { - this._emitter.on(TICK, fn, context); - - this._startIfPossible(); - - return this; + return this._addListener(new TickerListener(fn, context, priority)); } /** - * Calls {@link module:eventemitter3.EventEmitter#once} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. + * Add a handler for the tick event which is only execute once. * * @param {Function} fn - The listener function to be added for one update * @param {Function} [context] - The listener context + * @param {number} [priority=PIXI.UPDATE_PRIORITY.NORMAL] - The priority for emitting * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - addOnce(fn, context) + addOnce(fn, context, priority = UPDATE_PRIORITY.NORMAL) { - this._emitter.once(TICK, fn, context); + return this._addListener(new TickerListener(fn, context, priority, true)); + } + + /** + * Internally adds the event handler so that it can be sorted by priority. + * Priority allows certain handler (user, AnimatedSprite, Interaction) to be run + * before the rendering. + * + * @private + * @param {TickerListener} listener - Current listener being added. + * @returns {PIXI.ticker.Ticker} This instance of a ticker + */ + _addListener(listener) + { + // For attaching to head + let current = this._head.next; + let previous = this._head; + + // Add the first item + if (!current) + { + listener.connect(previous); + } + else + { + // Go from highest to lowest priority + while (current) + { + if (listener.priority >= current.priority) + { + listener.connect(previous); + break; + } + previous = current; + current = current.next; + } + + // Not yet connected + if (!listener.previous) + { + listener.connect(previous); + } + } this._startIfPossible(); @@ -229,19 +265,33 @@ } /** - * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. - * It checks if the emitter has listeners for 'tick' event. - * If it does, then it cancels the animation frame. + * Removes any handlers matching the function and context parameters. + * If no handlers are left after removing, then it cancels the animation frame. * - * @param {Function} [fn] - The listener function to be removed + * @param {Function} fn - The listener function to be removed * @param {Function} [context] - The listener context to be removed * @returns {PIXI.ticker.Ticker} This instance of a ticker */ remove(fn, context) { - this._emitter.off(TICK, fn, context); + let listener = this._head.next; - if (!this._emitter.listeners(TICK, true)) + while (listener) + { + // We found a match, lets remove it + // no break to delete all possible matches + // incase a listener was added 2+ times + if (listener.match(fn, context)) + { + listener = listener.destroy(); + } + else + { + listener = listener.next; + } + } + + if (!this._head.next) { this._cancelIfNeeded(); } @@ -276,6 +326,25 @@ } /** + * Destroy the ticker and don't use after this. Calling + * this method removes all references to internal events. + */ + destroy() + { + this.stop(); + + let listener = this._head.next; + + while (listener) + { + listener = listener.destroy(true); + } + + this._head.destroy(); + this._head = null; + } + + /** * Triggers an update. An update entails setting the * current {@link PIXI.ticker.Ticker#elapsedMS}, * the current {@link PIXI.ticker.Ticker#deltaTime}, @@ -320,8 +389,22 @@ this.deltaTime = elapsedMS * settings.TARGET_FPMS * this.speed; + // Cache a local reference, in-case ticker is destroyed + // during the emit, we can still check for head.next + const head = this._head; + // Invoke listeners added to internal emitter - this._emitter.emit(TICK, this.deltaTime); + let listener = head.next; + + while (listener) + { + listener = listener.emit(this.deltaTime); + } + + if (!head.next) + { + this._cancelIfNeeded(); + } } else { diff --git a/src/core/ticker/TickerListener.js b/src/core/ticker/TickerListener.js new file mode 100644 index 0000000..2bedb34 --- /dev/null +++ b/src/core/ticker/TickerListener.js @@ -0,0 +1,158 @@ +/** + * Internal class for handling the priority sorting of ticker handlers. + * + * @private + * @class + * @memberof PIXI.ticker + */ +export default class TickerListener +{ + /** + * Constructor + * + * @param {Function} fn - The listener function to be added for one update + * @param {Function} [context=null] - The listener context + * @param {number} [priority=0] - The priority for emitting + * @param {boolean} [once=false] - If the handler should fire once + */ + constructor(fn, context = null, priority = 0, once = false) + { + /** + * The handler function to execute. + * @member {Function} + */ + this.fn = fn; + + /** + * The calling to execute. + * @member {Function} + */ + this.context = context; + + /** + * The current priority. + * @member {number} + */ + this.priority = priority; + + /** + * If this should only execute once. + * @member {boolean} + */ + this.once = once; + + /** + * The next item in chain. + * @member {TickerListener} + */ + this.next = null; + + /** + * The previous item in chain. + * @member {TickerListener} + */ + this.previous = null; + + /** + * `true` if this listener has been destroyed already. + * @member {boolean} + * @private + */ + this._destroyed = false; + } + + /** + * Simple compare function to figure out if a function and context match. + * + * @param {Function} fn - The listener function to be added for one update + * @param {Function} context - The listener context + * @return {boolean} `true` if the listener match the arguments + */ + match(fn, context) + { + context = context || null; + + return this.fn === fn && this.context === context; + } + + /** + * Emit by calling the current function. + * @param {number} deltaTime - time since the last emit. + * @return {TickerListener} Next ticker + */ + emit(deltaTime) + { + if (this.context) + { + this.fn.call(this.context, deltaTime); + } + else + { + this.fn(deltaTime); + } + + if (this.once) + { + this.destroy(); + } + + const redirect = this.next; + + // Soft-destroying should remove + // the next reference + if (this._destroyed) + { + this.next = null; + } + + return redirect; + } + + /** + * Connect to the list. + * @param {TickerListener} previous - Input node, previous listener + */ + connect(previous) + { + this.previous = previous; + if (previous.next) + { + previous.next.previous = this; + } + this.next = previous.next; + previous.next = this; + } + + /** + * Destroy and don't use after this. + * @param {boolean} [hard = false] `true` to remove the `next` reference, this + * is considered a hard destroy. Soft destroy maintains the next reference. + * @return {TickerListener} The listener to redirect while emitting or removing. + */ + destroy(hard = false) + { + this._destroyed = true; + this.fn = null; + this.context = null; + + // Disconnect, hook up next and previous + if (this.previous) + { + this.previous.next = this.next; + } + + if (this.next) + { + this.next.previous = this.previous; + } + + // Redirect to the next item + const redirect = this.previous; + + // Remove references + this.next = hard ? null : redirect; + this.previous = null; + + return redirect; + } +} diff --git a/src/core/ticker/index.js b/src/core/ticker/index.js index 4b3017c..c3020d4 100644 --- a/src/core/ticker/index.js +++ b/src/core/ticker/index.js @@ -45,8 +45,25 @@ const shared = new Ticker(); shared.autoStart = true; +shared.destroy = () => +{ + // protect destroying shared ticker + // this is used by other internal systems + // like AnimatedSprite and InteractionManager +}; /** + * This namespace contains an API for interacting with PIXI's internal global update loop. + * + * This ticker is used for rendering, {@link PIXI.extras.AnimatedSprite AnimatedSprite}, + * {@link PIXI.interaction.InteractionManager InteractionManager} and many other time-based PIXI systems. + * @example + * const ticker = new PIXI.ticker.Ticker(); + * ticker.stop(); + * ticker.add((deltaTime) => { + * // do something every frame + * }); + * ticker.start(); * @namespace PIXI.ticker */ export { shared, Ticker }; diff --git a/src/core/utils/index.js b/src/core/utils/index.js index be44750..7441f7b 100644 --- a/src/core/utils/index.js +++ b/src/core/utils/index.js @@ -9,6 +9,21 @@ let saidHello = false; /** + * Generalized convenience utilities for PIXI. + * @example + * // Extend PIXI's internal Event Emitter. + * class MyEmitter extends PIXI.utils.EventEmitter { + * constructor() { + * super(); + * console.log("Emitter created!"); + * } + * } + * + * // Get info on current device + * console.log(PIXI.utils.isMobile); + * + * // Convert hex color to string + * console.log(PIXI.utils.hex2string(0xff00ff)); // returns: "#ff00ff" * @namespace PIXI.utils */ export { diff --git a/src/deprecation.js b/src/deprecation.js index b9c73b3..33daeec 100644 --- a/src/deprecation.js +++ b/src/deprecation.js @@ -918,6 +918,35 @@ }); /** + * @method + * @name PIXI.prepare.BasePrepare#register + * @see PIXI.prepare.BasePrepare#registerFindHook + * @deprecated since version 4.4.2 + * @param {Function} [addHook] - Function call that takes two parameters: `item:*, queue:Array` + * function must return `true` if it was able to add item to the queue. + * @param {Function} [uploadHook] - Function call that takes two parameters: `prepare:CanvasPrepare, item:*` and + * function must return `true` if it was able to handle upload of item. + * @return {PIXI.BasePrepare} Instance of plugin for chaining. + */ +prepare.BasePrepare.prototype.register = function register(addHook, uploadHook) +{ + warn('renderer.plugins.prepare.register is now deprecated, ' + + 'please use renderer.plugins.prepare.registerFindHook & renderer.plugins.prepare.registerUploadHook'); + + if (addHook) + { + this.registerFindHook(addHook); + } + + if (uploadHook) + { + this.registerUploadHook(uploadHook); + } + + return this; +}; + +/** * The number of graphics or textures to upload to the GPU. * * @name PIXI.prepare.canvas.UPLOADS_PER_FRAME @@ -969,67 +998,73 @@ }, }); -Object.defineProperties(loaders.Resource.prototype, { - isJson: { - get() - { - warn('The isJson property is deprecated, please use `resource.type === Resource.TYPE.JSON`.'); +if (loaders.Loader) +{ + const Resource = loaders.Resource; + const Loader = loaders.Loader; - return this.type === loaders.Loader.Resource.TYPE.JSON; + Object.defineProperties(Resource.prototype, { + isJson: { + get() + { + warn('The isJson property is deprecated, please use `resource.type === Resource.TYPE.JSON`.'); + + return this.type === Resource.TYPE.JSON; + }, }, - }, - isXml: { - get() - { - warn('The isXml property is deprecated, please use `resource.type === Resource.TYPE.XML`.'); + isXml: { + get() + { + warn('The isXml property is deprecated, please use `resource.type === Resource.TYPE.XML`.'); - return this.type === loaders.Loader.Resource.TYPE.XML; + return this.type === Resource.TYPE.XML; + }, }, - }, - isImage: { - get() - { - warn('The isImage property is deprecated, please use `resource.type === Resource.TYPE.IMAGE`.'); + isImage: { + get() + { + warn('The isImage property is deprecated, please use `resource.type === Resource.TYPE.IMAGE`.'); - return this.type === loaders.Loader.Resource.TYPE.IMAGE; + return this.type === Resource.TYPE.IMAGE; + }, }, - }, - isAudio: { - get() - { - warn('The isAudio property is deprecated, please use `resource.type === Resource.TYPE.AUDIO`.'); + isAudio: { + get() + { + warn('The isAudio property is deprecated, please use `resource.type === Resource.TYPE.AUDIO`.'); - return this.type === loaders.Loader.Resource.TYPE.AUDIO; + return this.type === Resource.TYPE.AUDIO; + }, }, - }, - isVideo: { - get() - { - warn('The isVideo property is deprecated, please use `resource.type === Resource.TYPE.VIDEO`.'); + isVideo: { + get() + { + warn('The isVideo property is deprecated, please use `resource.type === Resource.TYPE.VIDEO`.'); - return this.type === loaders.Loader.Resource.TYPE.VIDEO; + return this.type === Resource.TYPE.VIDEO; + }, }, - }, -}); + }); -Object.defineProperties(loaders.Loader.prototype, { - before: { - get() - { - warn('The before() method is deprecated, please use pre().'); + Object.defineProperties(Loader.prototype, { + before: { + get() + { + warn('The before() method is deprecated, please use pre().'); - return this.pre; + return this.pre; + }, }, - }, - after: { - get() - { - warn('The after() method is deprecated, please use use().'); + after: { + get() + { + warn('The after() method is deprecated, please use use().'); - return this.use; + return this.use; + }, }, - }, -}); + }); +} /** * @name PIXI.interaction.interactiveTarget#defaultCursor diff --git a/src/extract/canvas/CanvasExtract.js b/src/extract/canvas/CanvasExtract.js index 941ca05..3ac5aee 100644 --- a/src/extract/canvas/CanvasExtract.js +++ b/src/extract/canvas/CanvasExtract.js @@ -8,7 +8,7 @@ * An instance of this class is automatically created by default, and can be found at renderer.plugins.extract * * @class - * @memberof PIXI + * @memberof PIXI.extract */ export default class CanvasExtract { @@ -21,9 +21,9 @@ /** * Collection of methods for extracting data (image, pixels, etc.) from a display object or render texture * - * @member {PIXI.CanvasExtract} extract + * @member {PIXI.extract.CanvasExtract} extract * @memberof PIXI.CanvasRenderer# - * @see PIXI.CanvasExtract + * @see PIXI.extract.CanvasExtract */ renderer.extract = this; } diff --git a/src/extract/index.js b/src/extract/index.js index 0f2170d..e9478c1 100644 --- a/src/extract/index.js +++ b/src/extract/index.js @@ -1,2 +1,22 @@ +/** + * This namespace provides renderer-specific plugins for exporting content from a renderer. + * For instance, these plugins can be used for saving an Image, Canvas element or for exporting the raw image data (pixels). + * + * Do not instantiate these plugins directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. + * @example + * // Create a new app (will auto-add extract plugin to renderer) + * const app = new PIXI.Application(); + * + * // Draw a red circle + * const graphics = new PIXI.Graphics() + * .beginFill(0xFF0000) + * .drawCircle(0, 0, 50); + * + * // Render the graphics as an HTMLImageElement + * const image = app.renderer.plugins.extract.image(graphics); + * document.body.appendChild(image); + * @namespace PIXI.extract + */ export { default as webgl } from './webgl/WebGLExtract'; export { default as canvas } from './canvas/CanvasExtract'; diff --git a/.travis.yml b/.travis.yml index 3df8b22..14de334 100644 --- a/.travis.yml +++ b/.travis.yml @@ -110,3 +110,43 @@ on: all_branches: true condition: $TRAVIS_TAG + # Deploy config for latest release + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: dist + upload-dir: "release" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: docs + upload-dir: "release/docs" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: coverage + upload-dir: "release/coverage" + on: + all_branches: true + condition: $TRAVIS_TAG \ No newline at end of file diff --git a/README.md b/README.md index d26fadd..3b85f85 100644 --- a/README.md +++ b/README.md @@ -36,22 +36,14 @@ - Wiki: Other misc tutorials and resources are [on the Wiki](https://github.com/pixijs/pixi.js/wiki/Resources). ### Community ### -- Forums: Check out the [forum] (http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow] (http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. +- Forums: Check out the [forum](http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow](http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. - Inspiration: Check out the [gallery](http://www.pixijs.com/gallery) to see some of the amazing things people have created! - Chat: You can join us on [Gitter](https://gitter.im/pixijs/pixi.js) To chat about Pixi. We also now have a Slack channel. If you would like to join it please Send me an email (mat@goodboydigital.com) and I will invite you in. ### Setup ### -It's easy to get started with Pixi.js! Simply grab the pre-built versions from here: - -Release Branch - Nice and stable Pixi.js -- Unminified: [http://pixijs.download/release/pixi.js] -- Minified: [http://pixijs.download/release/pixi.min.js] - -Develop Branch - The bleeding edge version of Pixi.js -- Unminified: [http://pixijs.download/dev/pixi.js] -- Minified: [http://pixijs.download/dev/pixi.min.js] +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. @@ -70,10 +62,10 @@ #### CDN Install (via cdnjs) ```html - + ``` -_Note: `4.2.2` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ +_Note: `4.4.3` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ ### Demos ### @@ -130,7 +122,7 @@ // and the root stage PIXI.Container. var app = new PIXI.Application(); -// The application will create a canvas element for you that you +// The application will create a canvas element for you that you // can then insert into the DOM. document.body.appendChild(app.view); @@ -150,7 +142,7 @@ // Add the bunny to the scene we are building. app.stage.addChild(bunny); - + // Listen for frame updates app.ticker.add(function() { // each frame we spin the bunny around a bit diff --git a/bower.json b/bower.json deleted file mode 100644 index 41b668c..0000000 --- a/bower.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "pixi.js", - "main": "dist/pixi.min.js", - "ignore": [ - "**/.*", - "docs", - "node_modules", - "bower_components", - "test", - "scripts", - "bower.json", - "inch.json" - ], - "dependencies": { - }, - "devDependencies": { - } -} diff --git a/package.json b/package.json index d35cbb2..308469f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.3.5", + "version": "4.4.4", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ @@ -40,11 +40,11 @@ "lib": "babel src --out-dir lib -s", "predocs": "rimraf docs/**", "docs": "jsdoc -c scripts/jsdoc.conf.json -R README.md", - "publish:patch": "npm version patch --no-git-tag-version && npm publish", - "publish:minor": "npm version minor --no-git-tag-version && npm publish", - "publish:major": "npm version major --no-git-tag-version && npm publish", + "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", - "postpublish": "node scripts/release.js" + "postpublish": "git push && git push --tags" }, "files": [ "dist/", @@ -72,7 +72,6 @@ "electron": "^1.4.15", "eslint": "^3.5.0", "floss": "^2.0.1", - "gh-pages": "^0.11.0", "jaguarjs-jsdoc": "^1.0.1", "js-md5": "^0.4.1", "jsdoc": "^3.4.2", diff --git a/scripts/jsdoc.conf.json b/scripts/jsdoc.conf.json index 553e61c..3f52180 100644 --- a/scripts/jsdoc.conf.json +++ b/scripts/jsdoc.conf.json @@ -43,7 +43,7 @@ }, "markdown" : { "parser" : "gfm", - "hardwrap" : true + "hardwrap" : false }, "opts": { "encoding" : "utf8", diff --git a/scripts/release.js b/scripts/release.js deleted file mode 100644 index 156620c..0000000 --- a/scripts/release.js +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env node -'use strict'; - -// Publish script to push releases of the bin files -// the normally are gitignored -const ghpages = require('gh-pages'); -const path = require('path'); -const packageInfo = require(path.join(__dirname, '..', 'package.json')); - -const options = { - src: [ - 'dist/**/*', - 'lib/**/*', - 'src/**/*', - 'scripts/*', - 'scripts/renders/*', - 'test/**/*', - '*.json', - '*.md', - 'LICENSE', - '.eslintrc', - '.editorconfig', - '.travis.yml', - '.babelrc', - ], - dotfiles: true, - branch: 'release', - message: packageInfo.version, - logger: console.log.bind(console), -}; - -ghpages.publish(process.cwd(), options, (err) => -{ - if (err) - { - console.log(err); - process.exit(1); - - return; - } - - process.exit(0); -}); diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index f16bcb1..220427a 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -21,7 +21,7 @@ const DIV_HOOK_ZINDEX = 2; /** - * The Accessibility manager reacreates the ability to tab and and have content read by screen + * The Accessibility manager recreates the ability to tab and and have content read by screen * readers. This is very important as it can possibly help people with disabilities access pixi * content. * diff --git a/src/accessibility/index.js b/src/accessibility/index.js index 568cfb1..0ae108f 100644 --- a/src/accessibility/index.js +++ b/src/accessibility/index.js @@ -1,4 +1,9 @@ /** + * This namespace contains a renderer plugin for interaction accessibility for end-users + * with physical impairments which require screen-renders, keyboard navigation, etc. + * + * Do not instantiate this plugin directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. * @namespace PIXI.accessibility */ export { default as accessibleTarget } from './accessibleTarget'; diff --git a/src/core/Application.js b/src/core/Application.js index 5340578..2fcc729 100644 --- a/src/core/Application.js +++ b/src/core/Application.js @@ -1,6 +1,8 @@ import { autoDetectRenderer } from './autoDetectRenderer'; import Container from './display/Container'; import { shared, Ticker } from './ticker'; +import settings from './settings'; +import { UPDATE_PRIORITY } from './const'; /** * Convenience class to create a new PIXI application. @@ -22,28 +24,52 @@ */ export default class Application { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experience unexplained flickering try setting this to true. - * @param {boolean} [useSharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedLoader=false] - `true` to use PIXI.loaders.shared, `false` to create new Loader. */ - constructor(width, height, options, noWebGL, useSharedTicker = false) + constructor(options, arg2, arg3, arg4, arg5) { + // Support for constructor(width, height, options, noWebGL, useSharedTicker) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + forceCanvas: !!arg4, + sharedTicker: !!arg5, + }, arg3); + } + + /** + * The default options, so we mixin functionality later. + * @member {object} + * @protected + */ + this._options = options = Object.assign({ + sharedTicker: false, + forceCanvas: false, + sharedLoader: false, + }, options); + /** * WebGL renderer if available, otherwise CanvasRenderer * @member {PIXI.WebGLRenderer|PIXI.CanvasRenderer} */ - this.renderer = autoDetectRenderer(width, height, options, noWebGL); + this.renderer = autoDetectRenderer(options); /** * The root display container that's rendered. @@ -63,7 +89,7 @@ * @member {PIXI.ticker.Ticker} * @default PIXI.ticker.shared */ - this.ticker = useSharedTicker ? shared : new Ticker(); + this.ticker = options.sharedTicker ? shared : new Ticker(); // Start the rendering this.start(); @@ -78,7 +104,7 @@ this._ticker = ticker; if (ticker) { - ticker.add(this.render, this); + ticker.add(this.render, this, UPDATE_PRIORITY.LOW); } } get ticker() // eslint-disable-line require-jsdoc @@ -136,13 +162,18 @@ */ destroy(removeView) { - this.stop(); + const oldTicker = this._ticker; + this.ticker = null; + oldTicker.destroy(); + this.stage.destroy(); this.stage = null; this.renderer.destroy(removeView); this.renderer = null; + + this._options = null; } } diff --git a/src/core/autoDetectRenderer.js b/src/core/autoDetectRenderer.js index 8960425..9ec0364 100644 --- a/src/core/autoDetectRenderer.js +++ b/src/core/autoDetectRenderer.js @@ -2,6 +2,7 @@ import CanvasRenderer from './renderers/canvas/CanvasRenderer'; import WebGLRenderer from './renderers/webgl/WebGLRenderer'; +// eslint-disable-next-line valid-jsdoc /** * This helper function will automatically detect which renderer you should be using. * WebGL is the preferred renderer as it is a lot faster. If webGL is not supported by @@ -9,24 +10,32 @@ * * @memberof PIXI * @function autoDetectRenderer - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @return {PIXI.WebGLRenderer|PIXI.CanvasRenderer} Returns WebGL renderer if available, otherwise CanvasRenderer */ -export function autoDetectRenderer(width = 800, height = 600, options, noWebGL) +export function autoDetectRenderer(options, arg1, arg2, arg3) { - if (!noWebGL && utils.isWebGLSupported()) + // Backward-compatible support for noWebGL option + let forceCanvas = options && options.forceCanvas; + + if (arg3 !== undefined) { - return new WebGLRenderer(width, height, options); + forceCanvas = arg3; } - return new CanvasRenderer(width, height, options); + if (!forceCanvas && utils.isWebGLSupported()) + { + return new WebGLRenderer(options, arg1, arg2); + } + + return new CanvasRenderer(options, arg1, arg2); } diff --git a/src/core/const.js b/src/core/const.js index b9e99ae..2c68dee 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -341,3 +341,27 @@ LINEAR_VERTICAL: 0, LINEAR_HORIZONTAL: 1, }; + +/** + * Represents the update priorities used by internal PIXI classes when registered with + * the {@link PIXI.ticker.Ticker} object. Higher priority items are updated first and lower + * priority items, such as render, should go later. + * + * @static + * @constant + * @name UPDATE_PRIORITY + * @memberof PIXI + * @type {object} + * @property {number} INTERACTION=50 Highest priority, used for {@link PIXI.interaction.InteractionManager} + * @property {number} HIGH=25 High priority updating, {@link PIXI.VideoBaseTexture} and {@link PIXI.extras.AnimatedSprite} + * @property {number} NORMAL=0 Default priority for ticker events, see {@link PIXI.ticker.Ticker#add}. + * @property {number} LOW=-25 Low priority used for {@link PIXI.Application} rendering. + * @property {number} UTILITY=-50 Lowest priority used for {@link PIXI.prepare.BasePrepare} utility. + */ +export const UPDATE_PRIORITY = { + INTERACTION: 50, + HIGH: 25, + NORMAL: 0, + LOW: -25, + UTILITY: -50, +}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 4e6c77d..decb42a 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -13,7 +13,6 @@ * * @class * @extends EventEmitter - * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ export default class DisplayObject extends EventEmitter @@ -113,6 +112,29 @@ * @private */ this._mask = null; + + /** + * If the object has been destroyed via destroy(). If true, it should not be used. + * + * @member {boolean} + * @private + * @readonly + */ + this._destroyed = false; + + /** + * Fired when this DisplayObject is added to a Container. + * + * @event PIXI.DisplayObject#added + * @param {PIXI.Container} container - The container added to. + */ + + /** + * Fired when this DisplayObject is removed from a Container. + * + * @event PIXI.DisplayObject#removed + * @param {PIXI.Container} container - The container removed from. + */ } /** @@ -403,6 +425,8 @@ this.interactive = false; this.interactiveChildren = false; + + this._destroyed = true; } /** diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index aafdfe2..f787c4a 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -792,8 +792,8 @@ sprite.worldAlpha = this.worldAlpha * sprite.alpha; sprite.blendMode = this.blendMode; - sprite.texture._frame.width = rect.width; - sprite.texture._frame.height = rect.height; + sprite._texture._frame.width = rect.width; + sprite._texture._frame.height = rect.height; sprite.transform.worldTransform = this.transform.worldTransform; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index a24c775..b9f192d 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -19,11 +19,12 @@ */ export default class SystemRenderer extends EventEmitter { + // eslint-disable-next-line valid-jsdoc /** * @param {string} system - The name of the system this renderer is for. - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -37,27 +38,31 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(system, screenWidth, screenHeight, options) + constructor(system, options, arg2, arg3) { super(); sayHello(system); - // prepare options - if (options) + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') { - for (const i in settings.RENDER_OPTIONS) - { - if (typeof options[i] === 'undefined') - { - options[i] = settings.RENDER_OPTIONS[i]; - } - } + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); } - else - { - options = settings.RENDER_OPTIONS; - } + + // Add the default render options + options = Object.assign({}, settings.RENDER_OPTIONS, options); + + /** + * The supplied constructor options. + * + * @member {Object} + * @readOnly + */ + this.options = options; /** * The type of the renderer. @@ -75,7 +80,7 @@ * * @member {PIXI.Rectangle} */ - this.screen = new Rectangle(0, 0, screenWidth || 800, screenHeight || 600); + this.screen = new Rectangle(0, 0, options.width, options.height); /** * The canvas element that everything is drawn to @@ -279,6 +284,8 @@ this.blendModes = null; + this.options = null; + this.preserveDrawingBuffer = false; this.clearBeforeRender = false; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index e84e49c..691caf0 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -17,10 +17,11 @@ */ export default class CanvasRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -34,9 +35,9 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('Canvas', screenWidth, screenHeight, options); + super('Canvas', options, arg2, arg3); this.type = RENDERER_TYPE.CANVAS; @@ -96,7 +97,19 @@ this.context = null; this.renderingToScreen = false; - this.resize(screenWidth, screenHeight); + this.resize(this.options.width, this.options.height); + + /** + * Fired after rendering finishes. + * + * @event PIXI.CanvasRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.CanvasRenderer#prerender + */ } /** @@ -299,4 +312,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.CanvasRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.CanvasExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.CanvasPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.CanvasRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index e1cf7cd..f6fe123 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -31,11 +31,12 @@ */ export default class WebGLRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** * - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -55,9 +56,9 @@ * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('WebGL', screenWidth, screenHeight, options); + super('WebGL', options, arg2, arg3); /** * The type of this renderer as a standardised const @@ -89,12 +90,6 @@ }; - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ this._backgroundColorRgba[3] = this.transparent ? 0 : 1; this.globalUniforms = new UniformGroup({ @@ -117,6 +112,13 @@ this.initPlugins(); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ if(options.context) { this.context.initFromContext(options.context); @@ -169,6 +171,25 @@ } return this; + + /** + * Fired after rendering finishes. + * + * @event PIXI.WebGLRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.WebGLRenderer#prerender + */ + + /** + * Fired when the WebGL context is set. + * + * @event PIXI.WebGLRenderer#context + * @param {WebGLRenderingContext} gl - WebGL context. + */ } /** @@ -295,4 +316,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.WebGLRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.WebGLRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(WebGLRenderer); diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index 4eb6128..fa3d883 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -47,6 +47,14 @@ * @member {boolean} */ this.enabled = true; + + /** + * If enabled, pixi will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; } /** diff --git a/src/core/renderers/webgl/systems/ContextSystem.js b/src/core/renderers/webgl/systems/ContextSystem.js index ff958c7..8df85ea 100644 --- a/src/core/renderers/webgl/systems/ContextSystem.js +++ b/src/core/renderers/webgl/systems/ContextSystem.js @@ -75,7 +75,8 @@ */ createContext(canvas, options) { - var gl = canvas.getContext('webgl', options) || + var gl = //canvas.getContext('webgl2', options) || + canvas.getContext('webgl', options) || canvas.getContext('experimental-webgl', options); if (!gl) diff --git a/src/core/renderers/webgl/systems/FilterSystem.js b/src/core/renderers/webgl/systems/FilterSystem.js index f47335d..37e28d2 100644 --- a/src/core/renderers/webgl/systems/FilterSystem.js +++ b/src/core/renderers/webgl/systems/FilterSystem.js @@ -111,7 +111,7 @@ // TODO we should fit the rect around the transform.. } - else + else if (filters[0].autoFit) { sourceFrame.fit(filterData.stack[0].destinationFrame); } diff --git a/src/core/renderers/webgl/systems/shader/shader/mapType.js b/src/core/renderers/webgl/systems/shader/shader/mapType.js index 581dace..917538a 100644 --- a/src/core/renderers/webgl/systems/shader/shader/mapType.js +++ b/src/core/renderers/webgl/systems/shader/shader/mapType.js @@ -15,7 +15,7 @@ } } - return GL_TABLE[type]; + return GL_TABLE[type]; }; var GL_TABLE = null; diff --git a/src/core/settings.js b/src/core/settings.js index 18c5877..38ef45c 100644 --- a/src/core/settings.js +++ b/src/core/settings.js @@ -2,6 +2,15 @@ import canUploadSameBuffer from './utils/canUploadSameBuffer'; /** + * User's customizable globals for overriding the default PIXI settings, such + * as a renderer's default resolution, framerate, float percision, etc. + * @example + * // Use the native window resolution as the default resolution + * // will support high-density displays when rendering + * PIXI.settings.RESOLUTION = window.devicePixelRatio. + * + * // Disable interpolation when scaling, will make texture be pixelated + * PIXI.settings.SCALE_MODE = PIXI.SCALE_MODES.NEAREST; * @namespace PIXI.settings */ export default { @@ -101,6 +110,9 @@ * @property {boolean} clearBeforeRender=true * @property {boolean} preserveDrawingBuffer=false * @property {boolean} roundPixels=false + * @property {number} width=800 + * @property {number} height=600 + * @property {boolean} legacy=false */ RENDER_OPTIONS: { view: null, @@ -112,6 +124,9 @@ clearBeforeRender: true, preserveDrawingBuffer: false, roundPixels: false, + width: 800, + height: 600, + legacy: false, }, /** diff --git a/src/core/shader/Program.js b/src/core/shader/Program.js index c0b538b..07b4398 100644 --- a/src/core/shader/Program.js +++ b/src/core/shader/Program.js @@ -1,6 +1,6 @@ import extractUniformsFromSrc from './extractUniformsFromSrc'; import generateUniformsSync from './generateUniformsSync'; -import glCore from 'pixi-gl-core'; +import shaderUtils from '../renderers/webgl/systems/shader/shader'; import { ProgramCache } from '../utils'; import getTestContext from '../utils/getTestContext'; @@ -64,14 +64,17 @@ } else { - vertexSrc = glCore.shader.setPrecision(vertexSrc, 'mediump'); - fragmentSrc = glCore.shader.setPrecision(fragmentSrc, 'mediump'); + vertexSrc = shaderUtils.setPrecision(vertexSrc, 'mediump'); + fragmentSrc = shaderUtils.setPrecision(fragmentSrc, 'mediump'); - const program = glCore.shader.compileProgram(gl, vertexSrc, fragmentSrc); + const program = shaderUtils.compileProgram(gl, vertexSrc, fragmentSrc); + console.log("<<<<<<>>><<"); this.attributeData = this.getAttributeData(program, gl); this.uniformData = this.getUniformData(program, gl); + console.log(this.uniformData); + //gl.deleteProgram(program); } } @@ -95,13 +98,13 @@ for (let i = 0; i < totalAttributes; i++) { const attribData = gl.getActiveAttrib(program, i); - const type = glCore.shader.mapType(gl, attribData.type); + const type = shaderUtils.mapType(gl, attribData.type); /*eslint-disable */ const data = { type: type, name: attribData.name, - size: glCore.shader.mapSize(type), + size: shaderUtils.mapSize(type), location: 0, }; /*eslint-enable */ @@ -137,13 +140,14 @@ // TODO expose this as a prop? // const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); - const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); + //const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); for (let i = 0; i < totalUniforms; i++) { const uniformData = gl.getActiveUniform(program, i); const name = uniformData.name.replace(/\[.*?\]/, ''); - const type = glCore.shader.mapType(gl, uniformData.type); + const type = shaderUtils.mapType(gl, uniformData.type); + console.log('mapping ' + uniformData.type + ' to ' + type); // if (!name.match(maskRegex)) { @@ -151,7 +155,7 @@ uniforms[name] = { type: type, size: uniformData.size, - value: glCore.shader.defaultValue(type, uniformData.size), + value: shaderUtils.defaultValue(type, uniformData.size), }; /*eslint-enable */ } diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index e387e99..b57d095 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -147,12 +147,12 @@ // so if _width is 0 then width was not set.. if (this._width) { - this.scale.x = sign(this.scale.x) * this._width / this.texture.orig.width; + this.scale.x = sign(this.scale.x) * this._width / this._texture.orig.width; } if (this._height) { - this.scale.y = sign(this.scale.y) * this._height / this.texture.orig.height; + this.scale.y = sign(this.scale.y) * this._height / this._texture.orig.height; } } diff --git a/src/core/sprites/canvas/CanvasTinter.js b/src/core/sprites/canvas/CanvasTinter.js index 27e5e0c..fd9de5d 100644 --- a/src/core/sprites/canvas/CanvasTinter.js +++ b/src/core/sprites/canvas/CanvasTinter.js @@ -18,7 +18,7 @@ */ getTintedTexture: (sprite, color) => { - const texture = sprite.texture; + const texture = sprite._texture; color = CanvasTinter.roundColor(color); diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 5a4edda..8b9b797 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -344,6 +344,7 @@ const texture = this._texture; const style = this._style; + const padding = style.trim ? 0 : style.padding; texture.baseTexture.valid = true; texture.baseTexture.resolution = this.resolution; @@ -353,11 +354,11 @@ texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - texture.trim.x = -style.padding; - texture.trim.y = -style.padding; + texture.trim.x = -padding; + texture.trim.y = -padding; - texture.orig.width = texture._frame.width - (style.padding * 2); - texture.orig.height = texture._frame.height - (style.padding * 2); + texture.orig.width = texture._frame.width - (padding * 2); + texture.orig.height = texture._frame.height - (padding * 2); // call sprite onTextureUpdate to update scale if _width or _height were set this._onTextureUpdate(); diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index 853da3d..8a9c34b 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -114,6 +114,38 @@ this.cacheId = null; this.validate(); + + /** + * Fired when a not-immediately-available source finishes loading. + * + * @protected + * @event PIXI.BaseTexture#loaded + * @param {PIXI.BaseTexture} baseTexture - Resource loaded. + */ + + /** + * Fired when a not-immediately-available source fails to load. + * + * @protected + * @event PIXI.BaseTexture#error + * @param {PIXI.BaseTexture} baseTexture - Resource errored. + */ + + /** + * Fired when BaseTexture is updated. + * + * @protected + * @event PIXI.BaseTexture#update + * @param {PIXI.BaseTexture} baseTexture - Instance of texture being updated. + */ + + /** + * Fired when BaseTexture is destroyed. + * + * @protected + * @event PIXI.BaseTexture#dispose + * @param {PIXI.BaseTexture} baseTexture - Instance of texture being destroyed. + */ } setResource(resource) diff --git a/src/core/textures/Spritesheet.js b/src/core/textures/Spritesheet.js index d469c62..9b24f27 100644 --- a/src/core/textures/Spritesheet.js +++ b/src/core/textures/Spritesheet.js @@ -208,6 +208,7 @@ // lets also add the frame to pixi's global cache for fromFrame and fromImage functions TextureCache[i] = this.textures[i]; + this.textures[i].textureCacheId = i; } frameIndex++; diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 0a2c379..2213087 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -147,9 +147,9 @@ /** * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. * - * @event update - * @memberof PIXI.Texture# + * @event PIXI.Texture#update * @protected + * @param {PIXI.Texture} texture - Instance of texture being updated. */ this._updateID = 0; @@ -159,6 +159,15 @@ * @type {Object} */ this.transform = null; + + /** + * The id under which this Texture has been added to the texture cache. This is + * automatically set in certain cases, but may not always be accurate, particularly if + * the texture is in the cache under multiple ids. + * + * @member {string} + */ + this.textureCacheId = null; } /** @@ -225,11 +234,17 @@ this._uvs = null; this.trim = null; this.orig = null; + this.textureCacheId = null; this.valid = false; - this.off('dispose', this.dispose, this); - this.off('update', this.update, this); + for (const prop in TextureCache) + { + if (TextureCache[prop] === this) + { + delete TextureCache[prop]; + } + } } /** @@ -315,14 +330,15 @@ */ static fromLoader(source, imageUrl, name) { - // console.log('added from loader...') + // console.log('added from loader...') const resource = new ImageResource(source);//.from(imageUrl, crossorigin);// document.createElement('img'); - // console.log('base resource ' + resource.width); + // console.log('base resource ' + resource.width); const baseTexture = new BaseTexture(resource, settings.SCALE_MODE, getResolutionOfUrl(imageUrl)); + /// console.log('base width ' + baseTexture.width); const texture = new Texture(baseTexture); @@ -335,6 +351,7 @@ // lets also add the frame to pixi's global cache for fromFrame and fromImage fucntions BaseTextureCache[name] = baseTexture; TextureCache[name] = texture; + texture.textureCacheId = name; // also add references by url if they are different. if (name !== imageUrl) @@ -355,6 +372,10 @@ */ static addTextureToCache(texture, id) { + if (!texture.textureCacheId) + { + texture.textureCacheId = id; + } TextureCache[id] = texture; } @@ -495,6 +516,7 @@ */ Texture.EMPTY = new Texture(new BaseTexture()); removeAllHandlers(Texture.EMPTY); +removeAllHandlers(Texture.EMPTY.baseTexture); /** * A white texture of 10x10 size, used for graphics and other things @@ -505,3 +527,4 @@ */ Texture.WHITE = createWhiteTexture(); removeAllHandlers(Texture.WHITE); +removeAllHandlers(Texture.WHITE.baseTexture); diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index 59eb9fd..a9b9f51 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -1,6 +1,7 @@ import BaseTexture from './BaseTexture'; import { uid, BaseTextureCache } from '../utils'; -import * as ticker from '../ticker'; +import { shared } from '../ticker'; +import { UPDATE_PRIORITY } from '../const'; /** * A texture of a [playing] Video. @@ -125,7 +126,7 @@ if (!this._isAutoUpdating && this.autoUpdate) { - ticker.shared.add(this.update, this); + shared.add(this.update, this, UPDATE_PRIORITY.HIGH); this._isAutoUpdating = true; } } @@ -139,7 +140,7 @@ { if (this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); this._isAutoUpdating = false; } } @@ -187,7 +188,7 @@ { if (this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); } if (this.source && this.source._pixiId) @@ -281,12 +282,12 @@ if (!this._autoUpdate && this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); this._isAutoUpdating = false; } else if (this._autoUpdate && !this._isAutoUpdating) { - ticker.shared.add(this.update, this); + shared.add(this.update, this, UPDATE_PRIORITY.HIGH); this._isAutoUpdating = true; } } diff --git a/src/core/ticker/Ticker.js b/src/core/ticker/Ticker.js index 17a1517..40df138 100644 --- a/src/core/ticker/Ticker.js +++ b/src/core/ticker/Ticker.js @@ -1,12 +1,10 @@ import settings from '../settings'; -import EventEmitter from 'eventemitter3'; - -// Internal event used by composed emitter -const TICK = 'tick'; +import { UPDATE_PRIORITY } from '../const'; +import TickerListener from './TickerListener'; /** * A Ticker class that runs an update loop that other objects listen to. - * This class is composed around an EventEmitter object to add listeners + * This class is composed around listeners * meant for execution on the next requested animation frame. * Animation frames are requested only when necessary, * e.g. When the ticker is started and the emitter has listeners. @@ -22,10 +20,11 @@ constructor() { /** - * Internal emitter used to fire 'tick' event + * The first listener. All new listeners added are chained on this. * @private + * @type {TickerListener} */ - this._emitter = new EventEmitter(); + this._head = new TickerListener(null, null, Infinity); /** * Internal current frame request ID @@ -131,7 +130,7 @@ // Invoke listeners now this.update(time); // Listener side effects may have modified ticker state. - if (this.started && this._requestId === null && this._emitter.listeners(TICK, true)) + if (this.started && this._requestId === null && this._head.next) { this._requestId = requestAnimationFrame(this._tick); } @@ -148,7 +147,7 @@ */ _requestIfNeeded() { - if (this._requestId === null && this._emitter.listeners(TICK, true)) + if (this._requestId === null && this._head.next) { // ensure callbacks get correct delta this.lastTime = performance.now(); @@ -193,35 +192,72 @@ } /** - * Calls {@link module:eventemitter3.EventEmitter#on} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. + * Register a handler for tick events. Calls continuously unless + * it is removed or the ticker is stopped. * * @param {Function} fn - The listener function to be added for updates * @param {Function} [context] - The listener context + * @param {number} [priority=PIXI.UPDATE_PRIORITY.NORMAL] - The priority for emitting * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - add(fn, context) + add(fn, context, priority = UPDATE_PRIORITY.NORMAL) { - this._emitter.on(TICK, fn, context); - - this._startIfPossible(); - - return this; + return this._addListener(new TickerListener(fn, context, priority)); } /** - * Calls {@link module:eventemitter3.EventEmitter#once} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. + * Add a handler for the tick event which is only execute once. * * @param {Function} fn - The listener function to be added for one update * @param {Function} [context] - The listener context + * @param {number} [priority=PIXI.UPDATE_PRIORITY.NORMAL] - The priority for emitting * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - addOnce(fn, context) + addOnce(fn, context, priority = UPDATE_PRIORITY.NORMAL) { - this._emitter.once(TICK, fn, context); + return this._addListener(new TickerListener(fn, context, priority, true)); + } + + /** + * Internally adds the event handler so that it can be sorted by priority. + * Priority allows certain handler (user, AnimatedSprite, Interaction) to be run + * before the rendering. + * + * @private + * @param {TickerListener} listener - Current listener being added. + * @returns {PIXI.ticker.Ticker} This instance of a ticker + */ + _addListener(listener) + { + // For attaching to head + let current = this._head.next; + let previous = this._head; + + // Add the first item + if (!current) + { + listener.connect(previous); + } + else + { + // Go from highest to lowest priority + while (current) + { + if (listener.priority >= current.priority) + { + listener.connect(previous); + break; + } + previous = current; + current = current.next; + } + + // Not yet connected + if (!listener.previous) + { + listener.connect(previous); + } + } this._startIfPossible(); @@ -229,19 +265,33 @@ } /** - * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. - * It checks if the emitter has listeners for 'tick' event. - * If it does, then it cancels the animation frame. + * Removes any handlers matching the function and context parameters. + * If no handlers are left after removing, then it cancels the animation frame. * - * @param {Function} [fn] - The listener function to be removed + * @param {Function} fn - The listener function to be removed * @param {Function} [context] - The listener context to be removed * @returns {PIXI.ticker.Ticker} This instance of a ticker */ remove(fn, context) { - this._emitter.off(TICK, fn, context); + let listener = this._head.next; - if (!this._emitter.listeners(TICK, true)) + while (listener) + { + // We found a match, lets remove it + // no break to delete all possible matches + // incase a listener was added 2+ times + if (listener.match(fn, context)) + { + listener = listener.destroy(); + } + else + { + listener = listener.next; + } + } + + if (!this._head.next) { this._cancelIfNeeded(); } @@ -276,6 +326,25 @@ } /** + * Destroy the ticker and don't use after this. Calling + * this method removes all references to internal events. + */ + destroy() + { + this.stop(); + + let listener = this._head.next; + + while (listener) + { + listener = listener.destroy(true); + } + + this._head.destroy(); + this._head = null; + } + + /** * Triggers an update. An update entails setting the * current {@link PIXI.ticker.Ticker#elapsedMS}, * the current {@link PIXI.ticker.Ticker#deltaTime}, @@ -320,8 +389,22 @@ this.deltaTime = elapsedMS * settings.TARGET_FPMS * this.speed; + // Cache a local reference, in-case ticker is destroyed + // during the emit, we can still check for head.next + const head = this._head; + // Invoke listeners added to internal emitter - this._emitter.emit(TICK, this.deltaTime); + let listener = head.next; + + while (listener) + { + listener = listener.emit(this.deltaTime); + } + + if (!head.next) + { + this._cancelIfNeeded(); + } } else { diff --git a/src/core/ticker/TickerListener.js b/src/core/ticker/TickerListener.js new file mode 100644 index 0000000..2bedb34 --- /dev/null +++ b/src/core/ticker/TickerListener.js @@ -0,0 +1,158 @@ +/** + * Internal class for handling the priority sorting of ticker handlers. + * + * @private + * @class + * @memberof PIXI.ticker + */ +export default class TickerListener +{ + /** + * Constructor + * + * @param {Function} fn - The listener function to be added for one update + * @param {Function} [context=null] - The listener context + * @param {number} [priority=0] - The priority for emitting + * @param {boolean} [once=false] - If the handler should fire once + */ + constructor(fn, context = null, priority = 0, once = false) + { + /** + * The handler function to execute. + * @member {Function} + */ + this.fn = fn; + + /** + * The calling to execute. + * @member {Function} + */ + this.context = context; + + /** + * The current priority. + * @member {number} + */ + this.priority = priority; + + /** + * If this should only execute once. + * @member {boolean} + */ + this.once = once; + + /** + * The next item in chain. + * @member {TickerListener} + */ + this.next = null; + + /** + * The previous item in chain. + * @member {TickerListener} + */ + this.previous = null; + + /** + * `true` if this listener has been destroyed already. + * @member {boolean} + * @private + */ + this._destroyed = false; + } + + /** + * Simple compare function to figure out if a function and context match. + * + * @param {Function} fn - The listener function to be added for one update + * @param {Function} context - The listener context + * @return {boolean} `true` if the listener match the arguments + */ + match(fn, context) + { + context = context || null; + + return this.fn === fn && this.context === context; + } + + /** + * Emit by calling the current function. + * @param {number} deltaTime - time since the last emit. + * @return {TickerListener} Next ticker + */ + emit(deltaTime) + { + if (this.context) + { + this.fn.call(this.context, deltaTime); + } + else + { + this.fn(deltaTime); + } + + if (this.once) + { + this.destroy(); + } + + const redirect = this.next; + + // Soft-destroying should remove + // the next reference + if (this._destroyed) + { + this.next = null; + } + + return redirect; + } + + /** + * Connect to the list. + * @param {TickerListener} previous - Input node, previous listener + */ + connect(previous) + { + this.previous = previous; + if (previous.next) + { + previous.next.previous = this; + } + this.next = previous.next; + previous.next = this; + } + + /** + * Destroy and don't use after this. + * @param {boolean} [hard = false] `true` to remove the `next` reference, this + * is considered a hard destroy. Soft destroy maintains the next reference. + * @return {TickerListener} The listener to redirect while emitting or removing. + */ + destroy(hard = false) + { + this._destroyed = true; + this.fn = null; + this.context = null; + + // Disconnect, hook up next and previous + if (this.previous) + { + this.previous.next = this.next; + } + + if (this.next) + { + this.next.previous = this.previous; + } + + // Redirect to the next item + const redirect = this.previous; + + // Remove references + this.next = hard ? null : redirect; + this.previous = null; + + return redirect; + } +} diff --git a/src/core/ticker/index.js b/src/core/ticker/index.js index 4b3017c..c3020d4 100644 --- a/src/core/ticker/index.js +++ b/src/core/ticker/index.js @@ -45,8 +45,25 @@ const shared = new Ticker(); shared.autoStart = true; +shared.destroy = () => +{ + // protect destroying shared ticker + // this is used by other internal systems + // like AnimatedSprite and InteractionManager +}; /** + * This namespace contains an API for interacting with PIXI's internal global update loop. + * + * This ticker is used for rendering, {@link PIXI.extras.AnimatedSprite AnimatedSprite}, + * {@link PIXI.interaction.InteractionManager InteractionManager} and many other time-based PIXI systems. + * @example + * const ticker = new PIXI.ticker.Ticker(); + * ticker.stop(); + * ticker.add((deltaTime) => { + * // do something every frame + * }); + * ticker.start(); * @namespace PIXI.ticker */ export { shared, Ticker }; diff --git a/src/core/utils/index.js b/src/core/utils/index.js index be44750..7441f7b 100644 --- a/src/core/utils/index.js +++ b/src/core/utils/index.js @@ -9,6 +9,21 @@ let saidHello = false; /** + * Generalized convenience utilities for PIXI. + * @example + * // Extend PIXI's internal Event Emitter. + * class MyEmitter extends PIXI.utils.EventEmitter { + * constructor() { + * super(); + * console.log("Emitter created!"); + * } + * } + * + * // Get info on current device + * console.log(PIXI.utils.isMobile); + * + * // Convert hex color to string + * console.log(PIXI.utils.hex2string(0xff00ff)); // returns: "#ff00ff" * @namespace PIXI.utils */ export { diff --git a/src/deprecation.js b/src/deprecation.js index b9c73b3..33daeec 100644 --- a/src/deprecation.js +++ b/src/deprecation.js @@ -918,6 +918,35 @@ }); /** + * @method + * @name PIXI.prepare.BasePrepare#register + * @see PIXI.prepare.BasePrepare#registerFindHook + * @deprecated since version 4.4.2 + * @param {Function} [addHook] - Function call that takes two parameters: `item:*, queue:Array` + * function must return `true` if it was able to add item to the queue. + * @param {Function} [uploadHook] - Function call that takes two parameters: `prepare:CanvasPrepare, item:*` and + * function must return `true` if it was able to handle upload of item. + * @return {PIXI.BasePrepare} Instance of plugin for chaining. + */ +prepare.BasePrepare.prototype.register = function register(addHook, uploadHook) +{ + warn('renderer.plugins.prepare.register is now deprecated, ' + + 'please use renderer.plugins.prepare.registerFindHook & renderer.plugins.prepare.registerUploadHook'); + + if (addHook) + { + this.registerFindHook(addHook); + } + + if (uploadHook) + { + this.registerUploadHook(uploadHook); + } + + return this; +}; + +/** * The number of graphics or textures to upload to the GPU. * * @name PIXI.prepare.canvas.UPLOADS_PER_FRAME @@ -969,67 +998,73 @@ }, }); -Object.defineProperties(loaders.Resource.prototype, { - isJson: { - get() - { - warn('The isJson property is deprecated, please use `resource.type === Resource.TYPE.JSON`.'); +if (loaders.Loader) +{ + const Resource = loaders.Resource; + const Loader = loaders.Loader; - return this.type === loaders.Loader.Resource.TYPE.JSON; + Object.defineProperties(Resource.prototype, { + isJson: { + get() + { + warn('The isJson property is deprecated, please use `resource.type === Resource.TYPE.JSON`.'); + + return this.type === Resource.TYPE.JSON; + }, }, - }, - isXml: { - get() - { - warn('The isXml property is deprecated, please use `resource.type === Resource.TYPE.XML`.'); + isXml: { + get() + { + warn('The isXml property is deprecated, please use `resource.type === Resource.TYPE.XML`.'); - return this.type === loaders.Loader.Resource.TYPE.XML; + return this.type === Resource.TYPE.XML; + }, }, - }, - isImage: { - get() - { - warn('The isImage property is deprecated, please use `resource.type === Resource.TYPE.IMAGE`.'); + isImage: { + get() + { + warn('The isImage property is deprecated, please use `resource.type === Resource.TYPE.IMAGE`.'); - return this.type === loaders.Loader.Resource.TYPE.IMAGE; + return this.type === Resource.TYPE.IMAGE; + }, }, - }, - isAudio: { - get() - { - warn('The isAudio property is deprecated, please use `resource.type === Resource.TYPE.AUDIO`.'); + isAudio: { + get() + { + warn('The isAudio property is deprecated, please use `resource.type === Resource.TYPE.AUDIO`.'); - return this.type === loaders.Loader.Resource.TYPE.AUDIO; + return this.type === Resource.TYPE.AUDIO; + }, }, - }, - isVideo: { - get() - { - warn('The isVideo property is deprecated, please use `resource.type === Resource.TYPE.VIDEO`.'); + isVideo: { + get() + { + warn('The isVideo property is deprecated, please use `resource.type === Resource.TYPE.VIDEO`.'); - return this.type === loaders.Loader.Resource.TYPE.VIDEO; + return this.type === Resource.TYPE.VIDEO; + }, }, - }, -}); + }); -Object.defineProperties(loaders.Loader.prototype, { - before: { - get() - { - warn('The before() method is deprecated, please use pre().'); + Object.defineProperties(Loader.prototype, { + before: { + get() + { + warn('The before() method is deprecated, please use pre().'); - return this.pre; + return this.pre; + }, }, - }, - after: { - get() - { - warn('The after() method is deprecated, please use use().'); + after: { + get() + { + warn('The after() method is deprecated, please use use().'); - return this.use; + return this.use; + }, }, - }, -}); + }); +} /** * @name PIXI.interaction.interactiveTarget#defaultCursor diff --git a/src/extract/canvas/CanvasExtract.js b/src/extract/canvas/CanvasExtract.js index 941ca05..3ac5aee 100644 --- a/src/extract/canvas/CanvasExtract.js +++ b/src/extract/canvas/CanvasExtract.js @@ -8,7 +8,7 @@ * An instance of this class is automatically created by default, and can be found at renderer.plugins.extract * * @class - * @memberof PIXI + * @memberof PIXI.extract */ export default class CanvasExtract { @@ -21,9 +21,9 @@ /** * Collection of methods for extracting data (image, pixels, etc.) from a display object or render texture * - * @member {PIXI.CanvasExtract} extract + * @member {PIXI.extract.CanvasExtract} extract * @memberof PIXI.CanvasRenderer# - * @see PIXI.CanvasExtract + * @see PIXI.extract.CanvasExtract */ renderer.extract = this; } diff --git a/src/extract/index.js b/src/extract/index.js index 0f2170d..e9478c1 100644 --- a/src/extract/index.js +++ b/src/extract/index.js @@ -1,2 +1,22 @@ +/** + * This namespace provides renderer-specific plugins for exporting content from a renderer. + * For instance, these plugins can be used for saving an Image, Canvas element or for exporting the raw image data (pixels). + * + * Do not instantiate these plugins directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. + * @example + * // Create a new app (will auto-add extract plugin to renderer) + * const app = new PIXI.Application(); + * + * // Draw a red circle + * const graphics = new PIXI.Graphics() + * .beginFill(0xFF0000) + * .drawCircle(0, 0, 50); + * + * // Render the graphics as an HTMLImageElement + * const image = app.renderer.plugins.extract.image(graphics); + * document.body.appendChild(image); + * @namespace PIXI.extract + */ export { default as webgl } from './webgl/WebGLExtract'; export { default as canvas } from './canvas/CanvasExtract'; diff --git a/src/extract/webgl/WebGLExtract.js b/src/extract/webgl/WebGLExtract.js index 60228bb..1c3dd30 100644 --- a/src/extract/webgl/WebGLExtract.js +++ b/src/extract/webgl/WebGLExtract.js @@ -9,7 +9,7 @@ * An instance of this class is automatically created by default, and can be found at renderer.plugins.extract * * @class - * @memberof PIXI + * @memberof PIXI.extract */ export default class WebGLExtract { @@ -22,9 +22,9 @@ /** * Collection of methods for extracting data (image, pixels, etc.) from a display object or render texture * - * @member {PIXI.WebGLExtract} extract + * @member {PIXI.extract.WebGLExtract} extract * @memberof PIXI.WebGLRenderer# - * @see PIXI.WebGLExtract + * @see PIXI.extract.WebGLExtract */ renderer.extract = this; } diff --git a/.travis.yml b/.travis.yml index 3df8b22..14de334 100644 --- a/.travis.yml +++ b/.travis.yml @@ -110,3 +110,43 @@ on: all_branches: true condition: $TRAVIS_TAG + # Deploy config for latest release + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: dist + upload-dir: "release" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: docs + upload-dir: "release/docs" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: coverage + upload-dir: "release/coverage" + on: + all_branches: true + condition: $TRAVIS_TAG \ No newline at end of file diff --git a/README.md b/README.md index d26fadd..3b85f85 100644 --- a/README.md +++ b/README.md @@ -36,22 +36,14 @@ - Wiki: Other misc tutorials and resources are [on the Wiki](https://github.com/pixijs/pixi.js/wiki/Resources). ### Community ### -- Forums: Check out the [forum] (http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow] (http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. +- Forums: Check out the [forum](http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow](http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. - Inspiration: Check out the [gallery](http://www.pixijs.com/gallery) to see some of the amazing things people have created! - Chat: You can join us on [Gitter](https://gitter.im/pixijs/pixi.js) To chat about Pixi. We also now have a Slack channel. If you would like to join it please Send me an email (mat@goodboydigital.com) and I will invite you in. ### Setup ### -It's easy to get started with Pixi.js! Simply grab the pre-built versions from here: - -Release Branch - Nice and stable Pixi.js -- Unminified: [http://pixijs.download/release/pixi.js] -- Minified: [http://pixijs.download/release/pixi.min.js] - -Develop Branch - The bleeding edge version of Pixi.js -- Unminified: [http://pixijs.download/dev/pixi.js] -- Minified: [http://pixijs.download/dev/pixi.min.js] +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. @@ -70,10 +62,10 @@ #### CDN Install (via cdnjs) ```html - + ``` -_Note: `4.2.2` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ +_Note: `4.4.3` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ ### Demos ### @@ -130,7 +122,7 @@ // and the root stage PIXI.Container. var app = new PIXI.Application(); -// The application will create a canvas element for you that you +// The application will create a canvas element for you that you // can then insert into the DOM. document.body.appendChild(app.view); @@ -150,7 +142,7 @@ // Add the bunny to the scene we are building. app.stage.addChild(bunny); - + // Listen for frame updates app.ticker.add(function() { // each frame we spin the bunny around a bit diff --git a/bower.json b/bower.json deleted file mode 100644 index 41b668c..0000000 --- a/bower.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "pixi.js", - "main": "dist/pixi.min.js", - "ignore": [ - "**/.*", - "docs", - "node_modules", - "bower_components", - "test", - "scripts", - "bower.json", - "inch.json" - ], - "dependencies": { - }, - "devDependencies": { - } -} diff --git a/package.json b/package.json index d35cbb2..308469f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.3.5", + "version": "4.4.4", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ @@ -40,11 +40,11 @@ "lib": "babel src --out-dir lib -s", "predocs": "rimraf docs/**", "docs": "jsdoc -c scripts/jsdoc.conf.json -R README.md", - "publish:patch": "npm version patch --no-git-tag-version && npm publish", - "publish:minor": "npm version minor --no-git-tag-version && npm publish", - "publish:major": "npm version major --no-git-tag-version && npm publish", + "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", - "postpublish": "node scripts/release.js" + "postpublish": "git push && git push --tags" }, "files": [ "dist/", @@ -72,7 +72,6 @@ "electron": "^1.4.15", "eslint": "^3.5.0", "floss": "^2.0.1", - "gh-pages": "^0.11.0", "jaguarjs-jsdoc": "^1.0.1", "js-md5": "^0.4.1", "jsdoc": "^3.4.2", diff --git a/scripts/jsdoc.conf.json b/scripts/jsdoc.conf.json index 553e61c..3f52180 100644 --- a/scripts/jsdoc.conf.json +++ b/scripts/jsdoc.conf.json @@ -43,7 +43,7 @@ }, "markdown" : { "parser" : "gfm", - "hardwrap" : true + "hardwrap" : false }, "opts": { "encoding" : "utf8", diff --git a/scripts/release.js b/scripts/release.js deleted file mode 100644 index 156620c..0000000 --- a/scripts/release.js +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env node -'use strict'; - -// Publish script to push releases of the bin files -// the normally are gitignored -const ghpages = require('gh-pages'); -const path = require('path'); -const packageInfo = require(path.join(__dirname, '..', 'package.json')); - -const options = { - src: [ - 'dist/**/*', - 'lib/**/*', - 'src/**/*', - 'scripts/*', - 'scripts/renders/*', - 'test/**/*', - '*.json', - '*.md', - 'LICENSE', - '.eslintrc', - '.editorconfig', - '.travis.yml', - '.babelrc', - ], - dotfiles: true, - branch: 'release', - message: packageInfo.version, - logger: console.log.bind(console), -}; - -ghpages.publish(process.cwd(), options, (err) => -{ - if (err) - { - console.log(err); - process.exit(1); - - return; - } - - process.exit(0); -}); diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index f16bcb1..220427a 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -21,7 +21,7 @@ const DIV_HOOK_ZINDEX = 2; /** - * The Accessibility manager reacreates the ability to tab and and have content read by screen + * The Accessibility manager recreates the ability to tab and and have content read by screen * readers. This is very important as it can possibly help people with disabilities access pixi * content. * diff --git a/src/accessibility/index.js b/src/accessibility/index.js index 568cfb1..0ae108f 100644 --- a/src/accessibility/index.js +++ b/src/accessibility/index.js @@ -1,4 +1,9 @@ /** + * This namespace contains a renderer plugin for interaction accessibility for end-users + * with physical impairments which require screen-renders, keyboard navigation, etc. + * + * Do not instantiate this plugin directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. * @namespace PIXI.accessibility */ export { default as accessibleTarget } from './accessibleTarget'; diff --git a/src/core/Application.js b/src/core/Application.js index 5340578..2fcc729 100644 --- a/src/core/Application.js +++ b/src/core/Application.js @@ -1,6 +1,8 @@ import { autoDetectRenderer } from './autoDetectRenderer'; import Container from './display/Container'; import { shared, Ticker } from './ticker'; +import settings from './settings'; +import { UPDATE_PRIORITY } from './const'; /** * Convenience class to create a new PIXI application. @@ -22,28 +24,52 @@ */ export default class Application { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experience unexplained flickering try setting this to true. - * @param {boolean} [useSharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedLoader=false] - `true` to use PIXI.loaders.shared, `false` to create new Loader. */ - constructor(width, height, options, noWebGL, useSharedTicker = false) + constructor(options, arg2, arg3, arg4, arg5) { + // Support for constructor(width, height, options, noWebGL, useSharedTicker) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + forceCanvas: !!arg4, + sharedTicker: !!arg5, + }, arg3); + } + + /** + * The default options, so we mixin functionality later. + * @member {object} + * @protected + */ + this._options = options = Object.assign({ + sharedTicker: false, + forceCanvas: false, + sharedLoader: false, + }, options); + /** * WebGL renderer if available, otherwise CanvasRenderer * @member {PIXI.WebGLRenderer|PIXI.CanvasRenderer} */ - this.renderer = autoDetectRenderer(width, height, options, noWebGL); + this.renderer = autoDetectRenderer(options); /** * The root display container that's rendered. @@ -63,7 +89,7 @@ * @member {PIXI.ticker.Ticker} * @default PIXI.ticker.shared */ - this.ticker = useSharedTicker ? shared : new Ticker(); + this.ticker = options.sharedTicker ? shared : new Ticker(); // Start the rendering this.start(); @@ -78,7 +104,7 @@ this._ticker = ticker; if (ticker) { - ticker.add(this.render, this); + ticker.add(this.render, this, UPDATE_PRIORITY.LOW); } } get ticker() // eslint-disable-line require-jsdoc @@ -136,13 +162,18 @@ */ destroy(removeView) { - this.stop(); + const oldTicker = this._ticker; + this.ticker = null; + oldTicker.destroy(); + this.stage.destroy(); this.stage = null; this.renderer.destroy(removeView); this.renderer = null; + + this._options = null; } } diff --git a/src/core/autoDetectRenderer.js b/src/core/autoDetectRenderer.js index 8960425..9ec0364 100644 --- a/src/core/autoDetectRenderer.js +++ b/src/core/autoDetectRenderer.js @@ -2,6 +2,7 @@ import CanvasRenderer from './renderers/canvas/CanvasRenderer'; import WebGLRenderer from './renderers/webgl/WebGLRenderer'; +// eslint-disable-next-line valid-jsdoc /** * This helper function will automatically detect which renderer you should be using. * WebGL is the preferred renderer as it is a lot faster. If webGL is not supported by @@ -9,24 +10,32 @@ * * @memberof PIXI * @function autoDetectRenderer - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @return {PIXI.WebGLRenderer|PIXI.CanvasRenderer} Returns WebGL renderer if available, otherwise CanvasRenderer */ -export function autoDetectRenderer(width = 800, height = 600, options, noWebGL) +export function autoDetectRenderer(options, arg1, arg2, arg3) { - if (!noWebGL && utils.isWebGLSupported()) + // Backward-compatible support for noWebGL option + let forceCanvas = options && options.forceCanvas; + + if (arg3 !== undefined) { - return new WebGLRenderer(width, height, options); + forceCanvas = arg3; } - return new CanvasRenderer(width, height, options); + if (!forceCanvas && utils.isWebGLSupported()) + { + return new WebGLRenderer(options, arg1, arg2); + } + + return new CanvasRenderer(options, arg1, arg2); } diff --git a/src/core/const.js b/src/core/const.js index b9e99ae..2c68dee 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -341,3 +341,27 @@ LINEAR_VERTICAL: 0, LINEAR_HORIZONTAL: 1, }; + +/** + * Represents the update priorities used by internal PIXI classes when registered with + * the {@link PIXI.ticker.Ticker} object. Higher priority items are updated first and lower + * priority items, such as render, should go later. + * + * @static + * @constant + * @name UPDATE_PRIORITY + * @memberof PIXI + * @type {object} + * @property {number} INTERACTION=50 Highest priority, used for {@link PIXI.interaction.InteractionManager} + * @property {number} HIGH=25 High priority updating, {@link PIXI.VideoBaseTexture} and {@link PIXI.extras.AnimatedSprite} + * @property {number} NORMAL=0 Default priority for ticker events, see {@link PIXI.ticker.Ticker#add}. + * @property {number} LOW=-25 Low priority used for {@link PIXI.Application} rendering. + * @property {number} UTILITY=-50 Lowest priority used for {@link PIXI.prepare.BasePrepare} utility. + */ +export const UPDATE_PRIORITY = { + INTERACTION: 50, + HIGH: 25, + NORMAL: 0, + LOW: -25, + UTILITY: -50, +}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 4e6c77d..decb42a 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -13,7 +13,6 @@ * * @class * @extends EventEmitter - * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ export default class DisplayObject extends EventEmitter @@ -113,6 +112,29 @@ * @private */ this._mask = null; + + /** + * If the object has been destroyed via destroy(). If true, it should not be used. + * + * @member {boolean} + * @private + * @readonly + */ + this._destroyed = false; + + /** + * Fired when this DisplayObject is added to a Container. + * + * @event PIXI.DisplayObject#added + * @param {PIXI.Container} container - The container added to. + */ + + /** + * Fired when this DisplayObject is removed from a Container. + * + * @event PIXI.DisplayObject#removed + * @param {PIXI.Container} container - The container removed from. + */ } /** @@ -403,6 +425,8 @@ this.interactive = false; this.interactiveChildren = false; + + this._destroyed = true; } /** diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index aafdfe2..f787c4a 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -792,8 +792,8 @@ sprite.worldAlpha = this.worldAlpha * sprite.alpha; sprite.blendMode = this.blendMode; - sprite.texture._frame.width = rect.width; - sprite.texture._frame.height = rect.height; + sprite._texture._frame.width = rect.width; + sprite._texture._frame.height = rect.height; sprite.transform.worldTransform = this.transform.worldTransform; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index a24c775..b9f192d 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -19,11 +19,12 @@ */ export default class SystemRenderer extends EventEmitter { + // eslint-disable-next-line valid-jsdoc /** * @param {string} system - The name of the system this renderer is for. - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -37,27 +38,31 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(system, screenWidth, screenHeight, options) + constructor(system, options, arg2, arg3) { super(); sayHello(system); - // prepare options - if (options) + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') { - for (const i in settings.RENDER_OPTIONS) - { - if (typeof options[i] === 'undefined') - { - options[i] = settings.RENDER_OPTIONS[i]; - } - } + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); } - else - { - options = settings.RENDER_OPTIONS; - } + + // Add the default render options + options = Object.assign({}, settings.RENDER_OPTIONS, options); + + /** + * The supplied constructor options. + * + * @member {Object} + * @readOnly + */ + this.options = options; /** * The type of the renderer. @@ -75,7 +80,7 @@ * * @member {PIXI.Rectangle} */ - this.screen = new Rectangle(0, 0, screenWidth || 800, screenHeight || 600); + this.screen = new Rectangle(0, 0, options.width, options.height); /** * The canvas element that everything is drawn to @@ -279,6 +284,8 @@ this.blendModes = null; + this.options = null; + this.preserveDrawingBuffer = false; this.clearBeforeRender = false; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index e84e49c..691caf0 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -17,10 +17,11 @@ */ export default class CanvasRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -34,9 +35,9 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('Canvas', screenWidth, screenHeight, options); + super('Canvas', options, arg2, arg3); this.type = RENDERER_TYPE.CANVAS; @@ -96,7 +97,19 @@ this.context = null; this.renderingToScreen = false; - this.resize(screenWidth, screenHeight); + this.resize(this.options.width, this.options.height); + + /** + * Fired after rendering finishes. + * + * @event PIXI.CanvasRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.CanvasRenderer#prerender + */ } /** @@ -299,4 +312,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.CanvasRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.CanvasExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.CanvasPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.CanvasRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index e1cf7cd..f6fe123 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -31,11 +31,12 @@ */ export default class WebGLRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** * - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -55,9 +56,9 @@ * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('WebGL', screenWidth, screenHeight, options); + super('WebGL', options, arg2, arg3); /** * The type of this renderer as a standardised const @@ -89,12 +90,6 @@ }; - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ this._backgroundColorRgba[3] = this.transparent ? 0 : 1; this.globalUniforms = new UniformGroup({ @@ -117,6 +112,13 @@ this.initPlugins(); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ if(options.context) { this.context.initFromContext(options.context); @@ -169,6 +171,25 @@ } return this; + + /** + * Fired after rendering finishes. + * + * @event PIXI.WebGLRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.WebGLRenderer#prerender + */ + + /** + * Fired when the WebGL context is set. + * + * @event PIXI.WebGLRenderer#context + * @param {WebGLRenderingContext} gl - WebGL context. + */ } /** @@ -295,4 +316,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.WebGLRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.WebGLRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(WebGLRenderer); diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index 4eb6128..fa3d883 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -47,6 +47,14 @@ * @member {boolean} */ this.enabled = true; + + /** + * If enabled, pixi will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; } /** diff --git a/src/core/renderers/webgl/systems/ContextSystem.js b/src/core/renderers/webgl/systems/ContextSystem.js index ff958c7..8df85ea 100644 --- a/src/core/renderers/webgl/systems/ContextSystem.js +++ b/src/core/renderers/webgl/systems/ContextSystem.js @@ -75,7 +75,8 @@ */ createContext(canvas, options) { - var gl = canvas.getContext('webgl', options) || + var gl = //canvas.getContext('webgl2', options) || + canvas.getContext('webgl', options) || canvas.getContext('experimental-webgl', options); if (!gl) diff --git a/src/core/renderers/webgl/systems/FilterSystem.js b/src/core/renderers/webgl/systems/FilterSystem.js index f47335d..37e28d2 100644 --- a/src/core/renderers/webgl/systems/FilterSystem.js +++ b/src/core/renderers/webgl/systems/FilterSystem.js @@ -111,7 +111,7 @@ // TODO we should fit the rect around the transform.. } - else + else if (filters[0].autoFit) { sourceFrame.fit(filterData.stack[0].destinationFrame); } diff --git a/src/core/renderers/webgl/systems/shader/shader/mapType.js b/src/core/renderers/webgl/systems/shader/shader/mapType.js index 581dace..917538a 100644 --- a/src/core/renderers/webgl/systems/shader/shader/mapType.js +++ b/src/core/renderers/webgl/systems/shader/shader/mapType.js @@ -15,7 +15,7 @@ } } - return GL_TABLE[type]; + return GL_TABLE[type]; }; var GL_TABLE = null; diff --git a/src/core/settings.js b/src/core/settings.js index 18c5877..38ef45c 100644 --- a/src/core/settings.js +++ b/src/core/settings.js @@ -2,6 +2,15 @@ import canUploadSameBuffer from './utils/canUploadSameBuffer'; /** + * User's customizable globals for overriding the default PIXI settings, such + * as a renderer's default resolution, framerate, float percision, etc. + * @example + * // Use the native window resolution as the default resolution + * // will support high-density displays when rendering + * PIXI.settings.RESOLUTION = window.devicePixelRatio. + * + * // Disable interpolation when scaling, will make texture be pixelated + * PIXI.settings.SCALE_MODE = PIXI.SCALE_MODES.NEAREST; * @namespace PIXI.settings */ export default { @@ -101,6 +110,9 @@ * @property {boolean} clearBeforeRender=true * @property {boolean} preserveDrawingBuffer=false * @property {boolean} roundPixels=false + * @property {number} width=800 + * @property {number} height=600 + * @property {boolean} legacy=false */ RENDER_OPTIONS: { view: null, @@ -112,6 +124,9 @@ clearBeforeRender: true, preserveDrawingBuffer: false, roundPixels: false, + width: 800, + height: 600, + legacy: false, }, /** diff --git a/src/core/shader/Program.js b/src/core/shader/Program.js index c0b538b..07b4398 100644 --- a/src/core/shader/Program.js +++ b/src/core/shader/Program.js @@ -1,6 +1,6 @@ import extractUniformsFromSrc from './extractUniformsFromSrc'; import generateUniformsSync from './generateUniformsSync'; -import glCore from 'pixi-gl-core'; +import shaderUtils from '../renderers/webgl/systems/shader/shader'; import { ProgramCache } from '../utils'; import getTestContext from '../utils/getTestContext'; @@ -64,14 +64,17 @@ } else { - vertexSrc = glCore.shader.setPrecision(vertexSrc, 'mediump'); - fragmentSrc = glCore.shader.setPrecision(fragmentSrc, 'mediump'); + vertexSrc = shaderUtils.setPrecision(vertexSrc, 'mediump'); + fragmentSrc = shaderUtils.setPrecision(fragmentSrc, 'mediump'); - const program = glCore.shader.compileProgram(gl, vertexSrc, fragmentSrc); + const program = shaderUtils.compileProgram(gl, vertexSrc, fragmentSrc); + console.log("<<<<<<>>><<"); this.attributeData = this.getAttributeData(program, gl); this.uniformData = this.getUniformData(program, gl); + console.log(this.uniformData); + //gl.deleteProgram(program); } } @@ -95,13 +98,13 @@ for (let i = 0; i < totalAttributes; i++) { const attribData = gl.getActiveAttrib(program, i); - const type = glCore.shader.mapType(gl, attribData.type); + const type = shaderUtils.mapType(gl, attribData.type); /*eslint-disable */ const data = { type: type, name: attribData.name, - size: glCore.shader.mapSize(type), + size: shaderUtils.mapSize(type), location: 0, }; /*eslint-enable */ @@ -137,13 +140,14 @@ // TODO expose this as a prop? // const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); - const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); + //const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); for (let i = 0; i < totalUniforms; i++) { const uniformData = gl.getActiveUniform(program, i); const name = uniformData.name.replace(/\[.*?\]/, ''); - const type = glCore.shader.mapType(gl, uniformData.type); + const type = shaderUtils.mapType(gl, uniformData.type); + console.log('mapping ' + uniformData.type + ' to ' + type); // if (!name.match(maskRegex)) { @@ -151,7 +155,7 @@ uniforms[name] = { type: type, size: uniformData.size, - value: glCore.shader.defaultValue(type, uniformData.size), + value: shaderUtils.defaultValue(type, uniformData.size), }; /*eslint-enable */ } diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index e387e99..b57d095 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -147,12 +147,12 @@ // so if _width is 0 then width was not set.. if (this._width) { - this.scale.x = sign(this.scale.x) * this._width / this.texture.orig.width; + this.scale.x = sign(this.scale.x) * this._width / this._texture.orig.width; } if (this._height) { - this.scale.y = sign(this.scale.y) * this._height / this.texture.orig.height; + this.scale.y = sign(this.scale.y) * this._height / this._texture.orig.height; } } diff --git a/src/core/sprites/canvas/CanvasTinter.js b/src/core/sprites/canvas/CanvasTinter.js index 27e5e0c..fd9de5d 100644 --- a/src/core/sprites/canvas/CanvasTinter.js +++ b/src/core/sprites/canvas/CanvasTinter.js @@ -18,7 +18,7 @@ */ getTintedTexture: (sprite, color) => { - const texture = sprite.texture; + const texture = sprite._texture; color = CanvasTinter.roundColor(color); diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 5a4edda..8b9b797 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -344,6 +344,7 @@ const texture = this._texture; const style = this._style; + const padding = style.trim ? 0 : style.padding; texture.baseTexture.valid = true; texture.baseTexture.resolution = this.resolution; @@ -353,11 +354,11 @@ texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - texture.trim.x = -style.padding; - texture.trim.y = -style.padding; + texture.trim.x = -padding; + texture.trim.y = -padding; - texture.orig.width = texture._frame.width - (style.padding * 2); - texture.orig.height = texture._frame.height - (style.padding * 2); + texture.orig.width = texture._frame.width - (padding * 2); + texture.orig.height = texture._frame.height - (padding * 2); // call sprite onTextureUpdate to update scale if _width or _height were set this._onTextureUpdate(); diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index 853da3d..8a9c34b 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -114,6 +114,38 @@ this.cacheId = null; this.validate(); + + /** + * Fired when a not-immediately-available source finishes loading. + * + * @protected + * @event PIXI.BaseTexture#loaded + * @param {PIXI.BaseTexture} baseTexture - Resource loaded. + */ + + /** + * Fired when a not-immediately-available source fails to load. + * + * @protected + * @event PIXI.BaseTexture#error + * @param {PIXI.BaseTexture} baseTexture - Resource errored. + */ + + /** + * Fired when BaseTexture is updated. + * + * @protected + * @event PIXI.BaseTexture#update + * @param {PIXI.BaseTexture} baseTexture - Instance of texture being updated. + */ + + /** + * Fired when BaseTexture is destroyed. + * + * @protected + * @event PIXI.BaseTexture#dispose + * @param {PIXI.BaseTexture} baseTexture - Instance of texture being destroyed. + */ } setResource(resource) diff --git a/src/core/textures/Spritesheet.js b/src/core/textures/Spritesheet.js index d469c62..9b24f27 100644 --- a/src/core/textures/Spritesheet.js +++ b/src/core/textures/Spritesheet.js @@ -208,6 +208,7 @@ // lets also add the frame to pixi's global cache for fromFrame and fromImage functions TextureCache[i] = this.textures[i]; + this.textures[i].textureCacheId = i; } frameIndex++; diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 0a2c379..2213087 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -147,9 +147,9 @@ /** * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. * - * @event update - * @memberof PIXI.Texture# + * @event PIXI.Texture#update * @protected + * @param {PIXI.Texture} texture - Instance of texture being updated. */ this._updateID = 0; @@ -159,6 +159,15 @@ * @type {Object} */ this.transform = null; + + /** + * The id under which this Texture has been added to the texture cache. This is + * automatically set in certain cases, but may not always be accurate, particularly if + * the texture is in the cache under multiple ids. + * + * @member {string} + */ + this.textureCacheId = null; } /** @@ -225,11 +234,17 @@ this._uvs = null; this.trim = null; this.orig = null; + this.textureCacheId = null; this.valid = false; - this.off('dispose', this.dispose, this); - this.off('update', this.update, this); + for (const prop in TextureCache) + { + if (TextureCache[prop] === this) + { + delete TextureCache[prop]; + } + } } /** @@ -315,14 +330,15 @@ */ static fromLoader(source, imageUrl, name) { - // console.log('added from loader...') + // console.log('added from loader...') const resource = new ImageResource(source);//.from(imageUrl, crossorigin);// document.createElement('img'); - // console.log('base resource ' + resource.width); + // console.log('base resource ' + resource.width); const baseTexture = new BaseTexture(resource, settings.SCALE_MODE, getResolutionOfUrl(imageUrl)); + /// console.log('base width ' + baseTexture.width); const texture = new Texture(baseTexture); @@ -335,6 +351,7 @@ // lets also add the frame to pixi's global cache for fromFrame and fromImage fucntions BaseTextureCache[name] = baseTexture; TextureCache[name] = texture; + texture.textureCacheId = name; // also add references by url if they are different. if (name !== imageUrl) @@ -355,6 +372,10 @@ */ static addTextureToCache(texture, id) { + if (!texture.textureCacheId) + { + texture.textureCacheId = id; + } TextureCache[id] = texture; } @@ -495,6 +516,7 @@ */ Texture.EMPTY = new Texture(new BaseTexture()); removeAllHandlers(Texture.EMPTY); +removeAllHandlers(Texture.EMPTY.baseTexture); /** * A white texture of 10x10 size, used for graphics and other things @@ -505,3 +527,4 @@ */ Texture.WHITE = createWhiteTexture(); removeAllHandlers(Texture.WHITE); +removeAllHandlers(Texture.WHITE.baseTexture); diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index 59eb9fd..a9b9f51 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -1,6 +1,7 @@ import BaseTexture from './BaseTexture'; import { uid, BaseTextureCache } from '../utils'; -import * as ticker from '../ticker'; +import { shared } from '../ticker'; +import { UPDATE_PRIORITY } from '../const'; /** * A texture of a [playing] Video. @@ -125,7 +126,7 @@ if (!this._isAutoUpdating && this.autoUpdate) { - ticker.shared.add(this.update, this); + shared.add(this.update, this, UPDATE_PRIORITY.HIGH); this._isAutoUpdating = true; } } @@ -139,7 +140,7 @@ { if (this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); this._isAutoUpdating = false; } } @@ -187,7 +188,7 @@ { if (this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); } if (this.source && this.source._pixiId) @@ -281,12 +282,12 @@ if (!this._autoUpdate && this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); this._isAutoUpdating = false; } else if (this._autoUpdate && !this._isAutoUpdating) { - ticker.shared.add(this.update, this); + shared.add(this.update, this, UPDATE_PRIORITY.HIGH); this._isAutoUpdating = true; } } diff --git a/src/core/ticker/Ticker.js b/src/core/ticker/Ticker.js index 17a1517..40df138 100644 --- a/src/core/ticker/Ticker.js +++ b/src/core/ticker/Ticker.js @@ -1,12 +1,10 @@ import settings from '../settings'; -import EventEmitter from 'eventemitter3'; - -// Internal event used by composed emitter -const TICK = 'tick'; +import { UPDATE_PRIORITY } from '../const'; +import TickerListener from './TickerListener'; /** * A Ticker class that runs an update loop that other objects listen to. - * This class is composed around an EventEmitter object to add listeners + * This class is composed around listeners * meant for execution on the next requested animation frame. * Animation frames are requested only when necessary, * e.g. When the ticker is started and the emitter has listeners. @@ -22,10 +20,11 @@ constructor() { /** - * Internal emitter used to fire 'tick' event + * The first listener. All new listeners added are chained on this. * @private + * @type {TickerListener} */ - this._emitter = new EventEmitter(); + this._head = new TickerListener(null, null, Infinity); /** * Internal current frame request ID @@ -131,7 +130,7 @@ // Invoke listeners now this.update(time); // Listener side effects may have modified ticker state. - if (this.started && this._requestId === null && this._emitter.listeners(TICK, true)) + if (this.started && this._requestId === null && this._head.next) { this._requestId = requestAnimationFrame(this._tick); } @@ -148,7 +147,7 @@ */ _requestIfNeeded() { - if (this._requestId === null && this._emitter.listeners(TICK, true)) + if (this._requestId === null && this._head.next) { // ensure callbacks get correct delta this.lastTime = performance.now(); @@ -193,35 +192,72 @@ } /** - * Calls {@link module:eventemitter3.EventEmitter#on} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. + * Register a handler for tick events. Calls continuously unless + * it is removed or the ticker is stopped. * * @param {Function} fn - The listener function to be added for updates * @param {Function} [context] - The listener context + * @param {number} [priority=PIXI.UPDATE_PRIORITY.NORMAL] - The priority for emitting * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - add(fn, context) + add(fn, context, priority = UPDATE_PRIORITY.NORMAL) { - this._emitter.on(TICK, fn, context); - - this._startIfPossible(); - - return this; + return this._addListener(new TickerListener(fn, context, priority)); } /** - * Calls {@link module:eventemitter3.EventEmitter#once} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. + * Add a handler for the tick event which is only execute once. * * @param {Function} fn - The listener function to be added for one update * @param {Function} [context] - The listener context + * @param {number} [priority=PIXI.UPDATE_PRIORITY.NORMAL] - The priority for emitting * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - addOnce(fn, context) + addOnce(fn, context, priority = UPDATE_PRIORITY.NORMAL) { - this._emitter.once(TICK, fn, context); + return this._addListener(new TickerListener(fn, context, priority, true)); + } + + /** + * Internally adds the event handler so that it can be sorted by priority. + * Priority allows certain handler (user, AnimatedSprite, Interaction) to be run + * before the rendering. + * + * @private + * @param {TickerListener} listener - Current listener being added. + * @returns {PIXI.ticker.Ticker} This instance of a ticker + */ + _addListener(listener) + { + // For attaching to head + let current = this._head.next; + let previous = this._head; + + // Add the first item + if (!current) + { + listener.connect(previous); + } + else + { + // Go from highest to lowest priority + while (current) + { + if (listener.priority >= current.priority) + { + listener.connect(previous); + break; + } + previous = current; + current = current.next; + } + + // Not yet connected + if (!listener.previous) + { + listener.connect(previous); + } + } this._startIfPossible(); @@ -229,19 +265,33 @@ } /** - * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. - * It checks if the emitter has listeners for 'tick' event. - * If it does, then it cancels the animation frame. + * Removes any handlers matching the function and context parameters. + * If no handlers are left after removing, then it cancels the animation frame. * - * @param {Function} [fn] - The listener function to be removed + * @param {Function} fn - The listener function to be removed * @param {Function} [context] - The listener context to be removed * @returns {PIXI.ticker.Ticker} This instance of a ticker */ remove(fn, context) { - this._emitter.off(TICK, fn, context); + let listener = this._head.next; - if (!this._emitter.listeners(TICK, true)) + while (listener) + { + // We found a match, lets remove it + // no break to delete all possible matches + // incase a listener was added 2+ times + if (listener.match(fn, context)) + { + listener = listener.destroy(); + } + else + { + listener = listener.next; + } + } + + if (!this._head.next) { this._cancelIfNeeded(); } @@ -276,6 +326,25 @@ } /** + * Destroy the ticker and don't use after this. Calling + * this method removes all references to internal events. + */ + destroy() + { + this.stop(); + + let listener = this._head.next; + + while (listener) + { + listener = listener.destroy(true); + } + + this._head.destroy(); + this._head = null; + } + + /** * Triggers an update. An update entails setting the * current {@link PIXI.ticker.Ticker#elapsedMS}, * the current {@link PIXI.ticker.Ticker#deltaTime}, @@ -320,8 +389,22 @@ this.deltaTime = elapsedMS * settings.TARGET_FPMS * this.speed; + // Cache a local reference, in-case ticker is destroyed + // during the emit, we can still check for head.next + const head = this._head; + // Invoke listeners added to internal emitter - this._emitter.emit(TICK, this.deltaTime); + let listener = head.next; + + while (listener) + { + listener = listener.emit(this.deltaTime); + } + + if (!head.next) + { + this._cancelIfNeeded(); + } } else { diff --git a/src/core/ticker/TickerListener.js b/src/core/ticker/TickerListener.js new file mode 100644 index 0000000..2bedb34 --- /dev/null +++ b/src/core/ticker/TickerListener.js @@ -0,0 +1,158 @@ +/** + * Internal class for handling the priority sorting of ticker handlers. + * + * @private + * @class + * @memberof PIXI.ticker + */ +export default class TickerListener +{ + /** + * Constructor + * + * @param {Function} fn - The listener function to be added for one update + * @param {Function} [context=null] - The listener context + * @param {number} [priority=0] - The priority for emitting + * @param {boolean} [once=false] - If the handler should fire once + */ + constructor(fn, context = null, priority = 0, once = false) + { + /** + * The handler function to execute. + * @member {Function} + */ + this.fn = fn; + + /** + * The calling to execute. + * @member {Function} + */ + this.context = context; + + /** + * The current priority. + * @member {number} + */ + this.priority = priority; + + /** + * If this should only execute once. + * @member {boolean} + */ + this.once = once; + + /** + * The next item in chain. + * @member {TickerListener} + */ + this.next = null; + + /** + * The previous item in chain. + * @member {TickerListener} + */ + this.previous = null; + + /** + * `true` if this listener has been destroyed already. + * @member {boolean} + * @private + */ + this._destroyed = false; + } + + /** + * Simple compare function to figure out if a function and context match. + * + * @param {Function} fn - The listener function to be added for one update + * @param {Function} context - The listener context + * @return {boolean} `true` if the listener match the arguments + */ + match(fn, context) + { + context = context || null; + + return this.fn === fn && this.context === context; + } + + /** + * Emit by calling the current function. + * @param {number} deltaTime - time since the last emit. + * @return {TickerListener} Next ticker + */ + emit(deltaTime) + { + if (this.context) + { + this.fn.call(this.context, deltaTime); + } + else + { + this.fn(deltaTime); + } + + if (this.once) + { + this.destroy(); + } + + const redirect = this.next; + + // Soft-destroying should remove + // the next reference + if (this._destroyed) + { + this.next = null; + } + + return redirect; + } + + /** + * Connect to the list. + * @param {TickerListener} previous - Input node, previous listener + */ + connect(previous) + { + this.previous = previous; + if (previous.next) + { + previous.next.previous = this; + } + this.next = previous.next; + previous.next = this; + } + + /** + * Destroy and don't use after this. + * @param {boolean} [hard = false] `true` to remove the `next` reference, this + * is considered a hard destroy. Soft destroy maintains the next reference. + * @return {TickerListener} The listener to redirect while emitting or removing. + */ + destroy(hard = false) + { + this._destroyed = true; + this.fn = null; + this.context = null; + + // Disconnect, hook up next and previous + if (this.previous) + { + this.previous.next = this.next; + } + + if (this.next) + { + this.next.previous = this.previous; + } + + // Redirect to the next item + const redirect = this.previous; + + // Remove references + this.next = hard ? null : redirect; + this.previous = null; + + return redirect; + } +} diff --git a/src/core/ticker/index.js b/src/core/ticker/index.js index 4b3017c..c3020d4 100644 --- a/src/core/ticker/index.js +++ b/src/core/ticker/index.js @@ -45,8 +45,25 @@ const shared = new Ticker(); shared.autoStart = true; +shared.destroy = () => +{ + // protect destroying shared ticker + // this is used by other internal systems + // like AnimatedSprite and InteractionManager +}; /** + * This namespace contains an API for interacting with PIXI's internal global update loop. + * + * This ticker is used for rendering, {@link PIXI.extras.AnimatedSprite AnimatedSprite}, + * {@link PIXI.interaction.InteractionManager InteractionManager} and many other time-based PIXI systems. + * @example + * const ticker = new PIXI.ticker.Ticker(); + * ticker.stop(); + * ticker.add((deltaTime) => { + * // do something every frame + * }); + * ticker.start(); * @namespace PIXI.ticker */ export { shared, Ticker }; diff --git a/src/core/utils/index.js b/src/core/utils/index.js index be44750..7441f7b 100644 --- a/src/core/utils/index.js +++ b/src/core/utils/index.js @@ -9,6 +9,21 @@ let saidHello = false; /** + * Generalized convenience utilities for PIXI. + * @example + * // Extend PIXI's internal Event Emitter. + * class MyEmitter extends PIXI.utils.EventEmitter { + * constructor() { + * super(); + * console.log("Emitter created!"); + * } + * } + * + * // Get info on current device + * console.log(PIXI.utils.isMobile); + * + * // Convert hex color to string + * console.log(PIXI.utils.hex2string(0xff00ff)); // returns: "#ff00ff" * @namespace PIXI.utils */ export { diff --git a/src/deprecation.js b/src/deprecation.js index b9c73b3..33daeec 100644 --- a/src/deprecation.js +++ b/src/deprecation.js @@ -918,6 +918,35 @@ }); /** + * @method + * @name PIXI.prepare.BasePrepare#register + * @see PIXI.prepare.BasePrepare#registerFindHook + * @deprecated since version 4.4.2 + * @param {Function} [addHook] - Function call that takes two parameters: `item:*, queue:Array` + * function must return `true` if it was able to add item to the queue. + * @param {Function} [uploadHook] - Function call that takes two parameters: `prepare:CanvasPrepare, item:*` and + * function must return `true` if it was able to handle upload of item. + * @return {PIXI.BasePrepare} Instance of plugin for chaining. + */ +prepare.BasePrepare.prototype.register = function register(addHook, uploadHook) +{ + warn('renderer.plugins.prepare.register is now deprecated, ' + + 'please use renderer.plugins.prepare.registerFindHook & renderer.plugins.prepare.registerUploadHook'); + + if (addHook) + { + this.registerFindHook(addHook); + } + + if (uploadHook) + { + this.registerUploadHook(uploadHook); + } + + return this; +}; + +/** * The number of graphics or textures to upload to the GPU. * * @name PIXI.prepare.canvas.UPLOADS_PER_FRAME @@ -969,67 +998,73 @@ }, }); -Object.defineProperties(loaders.Resource.prototype, { - isJson: { - get() - { - warn('The isJson property is deprecated, please use `resource.type === Resource.TYPE.JSON`.'); +if (loaders.Loader) +{ + const Resource = loaders.Resource; + const Loader = loaders.Loader; - return this.type === loaders.Loader.Resource.TYPE.JSON; + Object.defineProperties(Resource.prototype, { + isJson: { + get() + { + warn('The isJson property is deprecated, please use `resource.type === Resource.TYPE.JSON`.'); + + return this.type === Resource.TYPE.JSON; + }, }, - }, - isXml: { - get() - { - warn('The isXml property is deprecated, please use `resource.type === Resource.TYPE.XML`.'); + isXml: { + get() + { + warn('The isXml property is deprecated, please use `resource.type === Resource.TYPE.XML`.'); - return this.type === loaders.Loader.Resource.TYPE.XML; + return this.type === Resource.TYPE.XML; + }, }, - }, - isImage: { - get() - { - warn('The isImage property is deprecated, please use `resource.type === Resource.TYPE.IMAGE`.'); + isImage: { + get() + { + warn('The isImage property is deprecated, please use `resource.type === Resource.TYPE.IMAGE`.'); - return this.type === loaders.Loader.Resource.TYPE.IMAGE; + return this.type === Resource.TYPE.IMAGE; + }, }, - }, - isAudio: { - get() - { - warn('The isAudio property is deprecated, please use `resource.type === Resource.TYPE.AUDIO`.'); + isAudio: { + get() + { + warn('The isAudio property is deprecated, please use `resource.type === Resource.TYPE.AUDIO`.'); - return this.type === loaders.Loader.Resource.TYPE.AUDIO; + return this.type === Resource.TYPE.AUDIO; + }, }, - }, - isVideo: { - get() - { - warn('The isVideo property is deprecated, please use `resource.type === Resource.TYPE.VIDEO`.'); + isVideo: { + get() + { + warn('The isVideo property is deprecated, please use `resource.type === Resource.TYPE.VIDEO`.'); - return this.type === loaders.Loader.Resource.TYPE.VIDEO; + return this.type === Resource.TYPE.VIDEO; + }, }, - }, -}); + }); -Object.defineProperties(loaders.Loader.prototype, { - before: { - get() - { - warn('The before() method is deprecated, please use pre().'); + Object.defineProperties(Loader.prototype, { + before: { + get() + { + warn('The before() method is deprecated, please use pre().'); - return this.pre; + return this.pre; + }, }, - }, - after: { - get() - { - warn('The after() method is deprecated, please use use().'); + after: { + get() + { + warn('The after() method is deprecated, please use use().'); - return this.use; + return this.use; + }, }, - }, -}); + }); +} /** * @name PIXI.interaction.interactiveTarget#defaultCursor diff --git a/src/extract/canvas/CanvasExtract.js b/src/extract/canvas/CanvasExtract.js index 941ca05..3ac5aee 100644 --- a/src/extract/canvas/CanvasExtract.js +++ b/src/extract/canvas/CanvasExtract.js @@ -8,7 +8,7 @@ * An instance of this class is automatically created by default, and can be found at renderer.plugins.extract * * @class - * @memberof PIXI + * @memberof PIXI.extract */ export default class CanvasExtract { @@ -21,9 +21,9 @@ /** * Collection of methods for extracting data (image, pixels, etc.) from a display object or render texture * - * @member {PIXI.CanvasExtract} extract + * @member {PIXI.extract.CanvasExtract} extract * @memberof PIXI.CanvasRenderer# - * @see PIXI.CanvasExtract + * @see PIXI.extract.CanvasExtract */ renderer.extract = this; } diff --git a/src/extract/index.js b/src/extract/index.js index 0f2170d..e9478c1 100644 --- a/src/extract/index.js +++ b/src/extract/index.js @@ -1,2 +1,22 @@ +/** + * This namespace provides renderer-specific plugins for exporting content from a renderer. + * For instance, these plugins can be used for saving an Image, Canvas element or for exporting the raw image data (pixels). + * + * Do not instantiate these plugins directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. + * @example + * // Create a new app (will auto-add extract plugin to renderer) + * const app = new PIXI.Application(); + * + * // Draw a red circle + * const graphics = new PIXI.Graphics() + * .beginFill(0xFF0000) + * .drawCircle(0, 0, 50); + * + * // Render the graphics as an HTMLImageElement + * const image = app.renderer.plugins.extract.image(graphics); + * document.body.appendChild(image); + * @namespace PIXI.extract + */ export { default as webgl } from './webgl/WebGLExtract'; export { default as canvas } from './canvas/CanvasExtract'; diff --git a/src/extract/webgl/WebGLExtract.js b/src/extract/webgl/WebGLExtract.js index 60228bb..1c3dd30 100644 --- a/src/extract/webgl/WebGLExtract.js +++ b/src/extract/webgl/WebGLExtract.js @@ -9,7 +9,7 @@ * An instance of this class is automatically created by default, and can be found at renderer.plugins.extract * * @class - * @memberof PIXI + * @memberof PIXI.extract */ export default class WebGLExtract { @@ -22,9 +22,9 @@ /** * Collection of methods for extracting data (image, pixels, etc.) from a display object or render texture * - * @member {PIXI.WebGLExtract} extract + * @member {PIXI.extract.WebGLExtract} extract * @memberof PIXI.WebGLRenderer# - * @see PIXI.WebGLExtract + * @see PIXI.extract.WebGLExtract */ renderer.extract = this; } diff --git a/src/extras/AnimatedSprite.js b/src/extras/AnimatedSprite.js index 13b804f..5297778 100644 --- a/src/extras/AnimatedSprite.js +++ b/src/extras/AnimatedSprite.js @@ -137,7 +137,7 @@ this.playing = true; if (this._autoUpdate) { - core.ticker.shared.add(this.update, this); + core.ticker.shared.add(this.update, this, core.UPDATE_PRIORITY.HIGH); } } diff --git a/.travis.yml b/.travis.yml index 3df8b22..14de334 100644 --- a/.travis.yml +++ b/.travis.yml @@ -110,3 +110,43 @@ on: all_branches: true condition: $TRAVIS_TAG + # Deploy config for latest release + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: dist + upload-dir: "release" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: docs + upload-dir: "release/docs" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: coverage + upload-dir: "release/coverage" + on: + all_branches: true + condition: $TRAVIS_TAG \ No newline at end of file diff --git a/README.md b/README.md index d26fadd..3b85f85 100644 --- a/README.md +++ b/README.md @@ -36,22 +36,14 @@ - Wiki: Other misc tutorials and resources are [on the Wiki](https://github.com/pixijs/pixi.js/wiki/Resources). ### Community ### -- Forums: Check out the [forum] (http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow] (http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. +- Forums: Check out the [forum](http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow](http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. - Inspiration: Check out the [gallery](http://www.pixijs.com/gallery) to see some of the amazing things people have created! - Chat: You can join us on [Gitter](https://gitter.im/pixijs/pixi.js) To chat about Pixi. We also now have a Slack channel. If you would like to join it please Send me an email (mat@goodboydigital.com) and I will invite you in. ### Setup ### -It's easy to get started with Pixi.js! Simply grab the pre-built versions from here: - -Release Branch - Nice and stable Pixi.js -- Unminified: [http://pixijs.download/release/pixi.js] -- Minified: [http://pixijs.download/release/pixi.min.js] - -Develop Branch - The bleeding edge version of Pixi.js -- Unminified: [http://pixijs.download/dev/pixi.js] -- Minified: [http://pixijs.download/dev/pixi.min.js] +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. @@ -70,10 +62,10 @@ #### CDN Install (via cdnjs) ```html - + ``` -_Note: `4.2.2` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ +_Note: `4.4.3` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ ### Demos ### @@ -130,7 +122,7 @@ // and the root stage PIXI.Container. var app = new PIXI.Application(); -// The application will create a canvas element for you that you +// The application will create a canvas element for you that you // can then insert into the DOM. document.body.appendChild(app.view); @@ -150,7 +142,7 @@ // Add the bunny to the scene we are building. app.stage.addChild(bunny); - + // Listen for frame updates app.ticker.add(function() { // each frame we spin the bunny around a bit diff --git a/bower.json b/bower.json deleted file mode 100644 index 41b668c..0000000 --- a/bower.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "pixi.js", - "main": "dist/pixi.min.js", - "ignore": [ - "**/.*", - "docs", - "node_modules", - "bower_components", - "test", - "scripts", - "bower.json", - "inch.json" - ], - "dependencies": { - }, - "devDependencies": { - } -} diff --git a/package.json b/package.json index d35cbb2..308469f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.3.5", + "version": "4.4.4", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ @@ -40,11 +40,11 @@ "lib": "babel src --out-dir lib -s", "predocs": "rimraf docs/**", "docs": "jsdoc -c scripts/jsdoc.conf.json -R README.md", - "publish:patch": "npm version patch --no-git-tag-version && npm publish", - "publish:minor": "npm version minor --no-git-tag-version && npm publish", - "publish:major": "npm version major --no-git-tag-version && npm publish", + "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", - "postpublish": "node scripts/release.js" + "postpublish": "git push && git push --tags" }, "files": [ "dist/", @@ -72,7 +72,6 @@ "electron": "^1.4.15", "eslint": "^3.5.0", "floss": "^2.0.1", - "gh-pages": "^0.11.0", "jaguarjs-jsdoc": "^1.0.1", "js-md5": "^0.4.1", "jsdoc": "^3.4.2", diff --git a/scripts/jsdoc.conf.json b/scripts/jsdoc.conf.json index 553e61c..3f52180 100644 --- a/scripts/jsdoc.conf.json +++ b/scripts/jsdoc.conf.json @@ -43,7 +43,7 @@ }, "markdown" : { "parser" : "gfm", - "hardwrap" : true + "hardwrap" : false }, "opts": { "encoding" : "utf8", diff --git a/scripts/release.js b/scripts/release.js deleted file mode 100644 index 156620c..0000000 --- a/scripts/release.js +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env node -'use strict'; - -// Publish script to push releases of the bin files -// the normally are gitignored -const ghpages = require('gh-pages'); -const path = require('path'); -const packageInfo = require(path.join(__dirname, '..', 'package.json')); - -const options = { - src: [ - 'dist/**/*', - 'lib/**/*', - 'src/**/*', - 'scripts/*', - 'scripts/renders/*', - 'test/**/*', - '*.json', - '*.md', - 'LICENSE', - '.eslintrc', - '.editorconfig', - '.travis.yml', - '.babelrc', - ], - dotfiles: true, - branch: 'release', - message: packageInfo.version, - logger: console.log.bind(console), -}; - -ghpages.publish(process.cwd(), options, (err) => -{ - if (err) - { - console.log(err); - process.exit(1); - - return; - } - - process.exit(0); -}); diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index f16bcb1..220427a 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -21,7 +21,7 @@ const DIV_HOOK_ZINDEX = 2; /** - * The Accessibility manager reacreates the ability to tab and and have content read by screen + * The Accessibility manager recreates the ability to tab and and have content read by screen * readers. This is very important as it can possibly help people with disabilities access pixi * content. * diff --git a/src/accessibility/index.js b/src/accessibility/index.js index 568cfb1..0ae108f 100644 --- a/src/accessibility/index.js +++ b/src/accessibility/index.js @@ -1,4 +1,9 @@ /** + * This namespace contains a renderer plugin for interaction accessibility for end-users + * with physical impairments which require screen-renders, keyboard navigation, etc. + * + * Do not instantiate this plugin directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. * @namespace PIXI.accessibility */ export { default as accessibleTarget } from './accessibleTarget'; diff --git a/src/core/Application.js b/src/core/Application.js index 5340578..2fcc729 100644 --- a/src/core/Application.js +++ b/src/core/Application.js @@ -1,6 +1,8 @@ import { autoDetectRenderer } from './autoDetectRenderer'; import Container from './display/Container'; import { shared, Ticker } from './ticker'; +import settings from './settings'; +import { UPDATE_PRIORITY } from './const'; /** * Convenience class to create a new PIXI application. @@ -22,28 +24,52 @@ */ export default class Application { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experience unexplained flickering try setting this to true. - * @param {boolean} [useSharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedLoader=false] - `true` to use PIXI.loaders.shared, `false` to create new Loader. */ - constructor(width, height, options, noWebGL, useSharedTicker = false) + constructor(options, arg2, arg3, arg4, arg5) { + // Support for constructor(width, height, options, noWebGL, useSharedTicker) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + forceCanvas: !!arg4, + sharedTicker: !!arg5, + }, arg3); + } + + /** + * The default options, so we mixin functionality later. + * @member {object} + * @protected + */ + this._options = options = Object.assign({ + sharedTicker: false, + forceCanvas: false, + sharedLoader: false, + }, options); + /** * WebGL renderer if available, otherwise CanvasRenderer * @member {PIXI.WebGLRenderer|PIXI.CanvasRenderer} */ - this.renderer = autoDetectRenderer(width, height, options, noWebGL); + this.renderer = autoDetectRenderer(options); /** * The root display container that's rendered. @@ -63,7 +89,7 @@ * @member {PIXI.ticker.Ticker} * @default PIXI.ticker.shared */ - this.ticker = useSharedTicker ? shared : new Ticker(); + this.ticker = options.sharedTicker ? shared : new Ticker(); // Start the rendering this.start(); @@ -78,7 +104,7 @@ this._ticker = ticker; if (ticker) { - ticker.add(this.render, this); + ticker.add(this.render, this, UPDATE_PRIORITY.LOW); } } get ticker() // eslint-disable-line require-jsdoc @@ -136,13 +162,18 @@ */ destroy(removeView) { - this.stop(); + const oldTicker = this._ticker; + this.ticker = null; + oldTicker.destroy(); + this.stage.destroy(); this.stage = null; this.renderer.destroy(removeView); this.renderer = null; + + this._options = null; } } diff --git a/src/core/autoDetectRenderer.js b/src/core/autoDetectRenderer.js index 8960425..9ec0364 100644 --- a/src/core/autoDetectRenderer.js +++ b/src/core/autoDetectRenderer.js @@ -2,6 +2,7 @@ import CanvasRenderer from './renderers/canvas/CanvasRenderer'; import WebGLRenderer from './renderers/webgl/WebGLRenderer'; +// eslint-disable-next-line valid-jsdoc /** * This helper function will automatically detect which renderer you should be using. * WebGL is the preferred renderer as it is a lot faster. If webGL is not supported by @@ -9,24 +10,32 @@ * * @memberof PIXI * @function autoDetectRenderer - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @return {PIXI.WebGLRenderer|PIXI.CanvasRenderer} Returns WebGL renderer if available, otherwise CanvasRenderer */ -export function autoDetectRenderer(width = 800, height = 600, options, noWebGL) +export function autoDetectRenderer(options, arg1, arg2, arg3) { - if (!noWebGL && utils.isWebGLSupported()) + // Backward-compatible support for noWebGL option + let forceCanvas = options && options.forceCanvas; + + if (arg3 !== undefined) { - return new WebGLRenderer(width, height, options); + forceCanvas = arg3; } - return new CanvasRenderer(width, height, options); + if (!forceCanvas && utils.isWebGLSupported()) + { + return new WebGLRenderer(options, arg1, arg2); + } + + return new CanvasRenderer(options, arg1, arg2); } diff --git a/src/core/const.js b/src/core/const.js index b9e99ae..2c68dee 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -341,3 +341,27 @@ LINEAR_VERTICAL: 0, LINEAR_HORIZONTAL: 1, }; + +/** + * Represents the update priorities used by internal PIXI classes when registered with + * the {@link PIXI.ticker.Ticker} object. Higher priority items are updated first and lower + * priority items, such as render, should go later. + * + * @static + * @constant + * @name UPDATE_PRIORITY + * @memberof PIXI + * @type {object} + * @property {number} INTERACTION=50 Highest priority, used for {@link PIXI.interaction.InteractionManager} + * @property {number} HIGH=25 High priority updating, {@link PIXI.VideoBaseTexture} and {@link PIXI.extras.AnimatedSprite} + * @property {number} NORMAL=0 Default priority for ticker events, see {@link PIXI.ticker.Ticker#add}. + * @property {number} LOW=-25 Low priority used for {@link PIXI.Application} rendering. + * @property {number} UTILITY=-50 Lowest priority used for {@link PIXI.prepare.BasePrepare} utility. + */ +export const UPDATE_PRIORITY = { + INTERACTION: 50, + HIGH: 25, + NORMAL: 0, + LOW: -25, + UTILITY: -50, +}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 4e6c77d..decb42a 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -13,7 +13,6 @@ * * @class * @extends EventEmitter - * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ export default class DisplayObject extends EventEmitter @@ -113,6 +112,29 @@ * @private */ this._mask = null; + + /** + * If the object has been destroyed via destroy(). If true, it should not be used. + * + * @member {boolean} + * @private + * @readonly + */ + this._destroyed = false; + + /** + * Fired when this DisplayObject is added to a Container. + * + * @event PIXI.DisplayObject#added + * @param {PIXI.Container} container - The container added to. + */ + + /** + * Fired when this DisplayObject is removed from a Container. + * + * @event PIXI.DisplayObject#removed + * @param {PIXI.Container} container - The container removed from. + */ } /** @@ -403,6 +425,8 @@ this.interactive = false; this.interactiveChildren = false; + + this._destroyed = true; } /** diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index aafdfe2..f787c4a 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -792,8 +792,8 @@ sprite.worldAlpha = this.worldAlpha * sprite.alpha; sprite.blendMode = this.blendMode; - sprite.texture._frame.width = rect.width; - sprite.texture._frame.height = rect.height; + sprite._texture._frame.width = rect.width; + sprite._texture._frame.height = rect.height; sprite.transform.worldTransform = this.transform.worldTransform; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index a24c775..b9f192d 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -19,11 +19,12 @@ */ export default class SystemRenderer extends EventEmitter { + // eslint-disable-next-line valid-jsdoc /** * @param {string} system - The name of the system this renderer is for. - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -37,27 +38,31 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(system, screenWidth, screenHeight, options) + constructor(system, options, arg2, arg3) { super(); sayHello(system); - // prepare options - if (options) + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') { - for (const i in settings.RENDER_OPTIONS) - { - if (typeof options[i] === 'undefined') - { - options[i] = settings.RENDER_OPTIONS[i]; - } - } + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); } - else - { - options = settings.RENDER_OPTIONS; - } + + // Add the default render options + options = Object.assign({}, settings.RENDER_OPTIONS, options); + + /** + * The supplied constructor options. + * + * @member {Object} + * @readOnly + */ + this.options = options; /** * The type of the renderer. @@ -75,7 +80,7 @@ * * @member {PIXI.Rectangle} */ - this.screen = new Rectangle(0, 0, screenWidth || 800, screenHeight || 600); + this.screen = new Rectangle(0, 0, options.width, options.height); /** * The canvas element that everything is drawn to @@ -279,6 +284,8 @@ this.blendModes = null; + this.options = null; + this.preserveDrawingBuffer = false; this.clearBeforeRender = false; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index e84e49c..691caf0 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -17,10 +17,11 @@ */ export default class CanvasRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -34,9 +35,9 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('Canvas', screenWidth, screenHeight, options); + super('Canvas', options, arg2, arg3); this.type = RENDERER_TYPE.CANVAS; @@ -96,7 +97,19 @@ this.context = null; this.renderingToScreen = false; - this.resize(screenWidth, screenHeight); + this.resize(this.options.width, this.options.height); + + /** + * Fired after rendering finishes. + * + * @event PIXI.CanvasRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.CanvasRenderer#prerender + */ } /** @@ -299,4 +312,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.CanvasRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.CanvasExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.CanvasPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.CanvasRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index e1cf7cd..f6fe123 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -31,11 +31,12 @@ */ export default class WebGLRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** * - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -55,9 +56,9 @@ * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('WebGL', screenWidth, screenHeight, options); + super('WebGL', options, arg2, arg3); /** * The type of this renderer as a standardised const @@ -89,12 +90,6 @@ }; - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ this._backgroundColorRgba[3] = this.transparent ? 0 : 1; this.globalUniforms = new UniformGroup({ @@ -117,6 +112,13 @@ this.initPlugins(); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ if(options.context) { this.context.initFromContext(options.context); @@ -169,6 +171,25 @@ } return this; + + /** + * Fired after rendering finishes. + * + * @event PIXI.WebGLRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.WebGLRenderer#prerender + */ + + /** + * Fired when the WebGL context is set. + * + * @event PIXI.WebGLRenderer#context + * @param {WebGLRenderingContext} gl - WebGL context. + */ } /** @@ -295,4 +316,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.WebGLRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.WebGLRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(WebGLRenderer); diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index 4eb6128..fa3d883 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -47,6 +47,14 @@ * @member {boolean} */ this.enabled = true; + + /** + * If enabled, pixi will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; } /** diff --git a/src/core/renderers/webgl/systems/ContextSystem.js b/src/core/renderers/webgl/systems/ContextSystem.js index ff958c7..8df85ea 100644 --- a/src/core/renderers/webgl/systems/ContextSystem.js +++ b/src/core/renderers/webgl/systems/ContextSystem.js @@ -75,7 +75,8 @@ */ createContext(canvas, options) { - var gl = canvas.getContext('webgl', options) || + var gl = //canvas.getContext('webgl2', options) || + canvas.getContext('webgl', options) || canvas.getContext('experimental-webgl', options); if (!gl) diff --git a/src/core/renderers/webgl/systems/FilterSystem.js b/src/core/renderers/webgl/systems/FilterSystem.js index f47335d..37e28d2 100644 --- a/src/core/renderers/webgl/systems/FilterSystem.js +++ b/src/core/renderers/webgl/systems/FilterSystem.js @@ -111,7 +111,7 @@ // TODO we should fit the rect around the transform.. } - else + else if (filters[0].autoFit) { sourceFrame.fit(filterData.stack[0].destinationFrame); } diff --git a/src/core/renderers/webgl/systems/shader/shader/mapType.js b/src/core/renderers/webgl/systems/shader/shader/mapType.js index 581dace..917538a 100644 --- a/src/core/renderers/webgl/systems/shader/shader/mapType.js +++ b/src/core/renderers/webgl/systems/shader/shader/mapType.js @@ -15,7 +15,7 @@ } } - return GL_TABLE[type]; + return GL_TABLE[type]; }; var GL_TABLE = null; diff --git a/src/core/settings.js b/src/core/settings.js index 18c5877..38ef45c 100644 --- a/src/core/settings.js +++ b/src/core/settings.js @@ -2,6 +2,15 @@ import canUploadSameBuffer from './utils/canUploadSameBuffer'; /** + * User's customizable globals for overriding the default PIXI settings, such + * as a renderer's default resolution, framerate, float percision, etc. + * @example + * // Use the native window resolution as the default resolution + * // will support high-density displays when rendering + * PIXI.settings.RESOLUTION = window.devicePixelRatio. + * + * // Disable interpolation when scaling, will make texture be pixelated + * PIXI.settings.SCALE_MODE = PIXI.SCALE_MODES.NEAREST; * @namespace PIXI.settings */ export default { @@ -101,6 +110,9 @@ * @property {boolean} clearBeforeRender=true * @property {boolean} preserveDrawingBuffer=false * @property {boolean} roundPixels=false + * @property {number} width=800 + * @property {number} height=600 + * @property {boolean} legacy=false */ RENDER_OPTIONS: { view: null, @@ -112,6 +124,9 @@ clearBeforeRender: true, preserveDrawingBuffer: false, roundPixels: false, + width: 800, + height: 600, + legacy: false, }, /** diff --git a/src/core/shader/Program.js b/src/core/shader/Program.js index c0b538b..07b4398 100644 --- a/src/core/shader/Program.js +++ b/src/core/shader/Program.js @@ -1,6 +1,6 @@ import extractUniformsFromSrc from './extractUniformsFromSrc'; import generateUniformsSync from './generateUniformsSync'; -import glCore from 'pixi-gl-core'; +import shaderUtils from '../renderers/webgl/systems/shader/shader'; import { ProgramCache } from '../utils'; import getTestContext from '../utils/getTestContext'; @@ -64,14 +64,17 @@ } else { - vertexSrc = glCore.shader.setPrecision(vertexSrc, 'mediump'); - fragmentSrc = glCore.shader.setPrecision(fragmentSrc, 'mediump'); + vertexSrc = shaderUtils.setPrecision(vertexSrc, 'mediump'); + fragmentSrc = shaderUtils.setPrecision(fragmentSrc, 'mediump'); - const program = glCore.shader.compileProgram(gl, vertexSrc, fragmentSrc); + const program = shaderUtils.compileProgram(gl, vertexSrc, fragmentSrc); + console.log("<<<<<<>>><<"); this.attributeData = this.getAttributeData(program, gl); this.uniformData = this.getUniformData(program, gl); + console.log(this.uniformData); + //gl.deleteProgram(program); } } @@ -95,13 +98,13 @@ for (let i = 0; i < totalAttributes; i++) { const attribData = gl.getActiveAttrib(program, i); - const type = glCore.shader.mapType(gl, attribData.type); + const type = shaderUtils.mapType(gl, attribData.type); /*eslint-disable */ const data = { type: type, name: attribData.name, - size: glCore.shader.mapSize(type), + size: shaderUtils.mapSize(type), location: 0, }; /*eslint-enable */ @@ -137,13 +140,14 @@ // TODO expose this as a prop? // const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); - const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); + //const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); for (let i = 0; i < totalUniforms; i++) { const uniformData = gl.getActiveUniform(program, i); const name = uniformData.name.replace(/\[.*?\]/, ''); - const type = glCore.shader.mapType(gl, uniformData.type); + const type = shaderUtils.mapType(gl, uniformData.type); + console.log('mapping ' + uniformData.type + ' to ' + type); // if (!name.match(maskRegex)) { @@ -151,7 +155,7 @@ uniforms[name] = { type: type, size: uniformData.size, - value: glCore.shader.defaultValue(type, uniformData.size), + value: shaderUtils.defaultValue(type, uniformData.size), }; /*eslint-enable */ } diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index e387e99..b57d095 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -147,12 +147,12 @@ // so if _width is 0 then width was not set.. if (this._width) { - this.scale.x = sign(this.scale.x) * this._width / this.texture.orig.width; + this.scale.x = sign(this.scale.x) * this._width / this._texture.orig.width; } if (this._height) { - this.scale.y = sign(this.scale.y) * this._height / this.texture.orig.height; + this.scale.y = sign(this.scale.y) * this._height / this._texture.orig.height; } } diff --git a/src/core/sprites/canvas/CanvasTinter.js b/src/core/sprites/canvas/CanvasTinter.js index 27e5e0c..fd9de5d 100644 --- a/src/core/sprites/canvas/CanvasTinter.js +++ b/src/core/sprites/canvas/CanvasTinter.js @@ -18,7 +18,7 @@ */ getTintedTexture: (sprite, color) => { - const texture = sprite.texture; + const texture = sprite._texture; color = CanvasTinter.roundColor(color); diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 5a4edda..8b9b797 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -344,6 +344,7 @@ const texture = this._texture; const style = this._style; + const padding = style.trim ? 0 : style.padding; texture.baseTexture.valid = true; texture.baseTexture.resolution = this.resolution; @@ -353,11 +354,11 @@ texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - texture.trim.x = -style.padding; - texture.trim.y = -style.padding; + texture.trim.x = -padding; + texture.trim.y = -padding; - texture.orig.width = texture._frame.width - (style.padding * 2); - texture.orig.height = texture._frame.height - (style.padding * 2); + texture.orig.width = texture._frame.width - (padding * 2); + texture.orig.height = texture._frame.height - (padding * 2); // call sprite onTextureUpdate to update scale if _width or _height were set this._onTextureUpdate(); diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index 853da3d..8a9c34b 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -114,6 +114,38 @@ this.cacheId = null; this.validate(); + + /** + * Fired when a not-immediately-available source finishes loading. + * + * @protected + * @event PIXI.BaseTexture#loaded + * @param {PIXI.BaseTexture} baseTexture - Resource loaded. + */ + + /** + * Fired when a not-immediately-available source fails to load. + * + * @protected + * @event PIXI.BaseTexture#error + * @param {PIXI.BaseTexture} baseTexture - Resource errored. + */ + + /** + * Fired when BaseTexture is updated. + * + * @protected + * @event PIXI.BaseTexture#update + * @param {PIXI.BaseTexture} baseTexture - Instance of texture being updated. + */ + + /** + * Fired when BaseTexture is destroyed. + * + * @protected + * @event PIXI.BaseTexture#dispose + * @param {PIXI.BaseTexture} baseTexture - Instance of texture being destroyed. + */ } setResource(resource) diff --git a/src/core/textures/Spritesheet.js b/src/core/textures/Spritesheet.js index d469c62..9b24f27 100644 --- a/src/core/textures/Spritesheet.js +++ b/src/core/textures/Spritesheet.js @@ -208,6 +208,7 @@ // lets also add the frame to pixi's global cache for fromFrame and fromImage functions TextureCache[i] = this.textures[i]; + this.textures[i].textureCacheId = i; } frameIndex++; diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 0a2c379..2213087 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -147,9 +147,9 @@ /** * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. * - * @event update - * @memberof PIXI.Texture# + * @event PIXI.Texture#update * @protected + * @param {PIXI.Texture} texture - Instance of texture being updated. */ this._updateID = 0; @@ -159,6 +159,15 @@ * @type {Object} */ this.transform = null; + + /** + * The id under which this Texture has been added to the texture cache. This is + * automatically set in certain cases, but may not always be accurate, particularly if + * the texture is in the cache under multiple ids. + * + * @member {string} + */ + this.textureCacheId = null; } /** @@ -225,11 +234,17 @@ this._uvs = null; this.trim = null; this.orig = null; + this.textureCacheId = null; this.valid = false; - this.off('dispose', this.dispose, this); - this.off('update', this.update, this); + for (const prop in TextureCache) + { + if (TextureCache[prop] === this) + { + delete TextureCache[prop]; + } + } } /** @@ -315,14 +330,15 @@ */ static fromLoader(source, imageUrl, name) { - // console.log('added from loader...') + // console.log('added from loader...') const resource = new ImageResource(source);//.from(imageUrl, crossorigin);// document.createElement('img'); - // console.log('base resource ' + resource.width); + // console.log('base resource ' + resource.width); const baseTexture = new BaseTexture(resource, settings.SCALE_MODE, getResolutionOfUrl(imageUrl)); + /// console.log('base width ' + baseTexture.width); const texture = new Texture(baseTexture); @@ -335,6 +351,7 @@ // lets also add the frame to pixi's global cache for fromFrame and fromImage fucntions BaseTextureCache[name] = baseTexture; TextureCache[name] = texture; + texture.textureCacheId = name; // also add references by url if they are different. if (name !== imageUrl) @@ -355,6 +372,10 @@ */ static addTextureToCache(texture, id) { + if (!texture.textureCacheId) + { + texture.textureCacheId = id; + } TextureCache[id] = texture; } @@ -495,6 +516,7 @@ */ Texture.EMPTY = new Texture(new BaseTexture()); removeAllHandlers(Texture.EMPTY); +removeAllHandlers(Texture.EMPTY.baseTexture); /** * A white texture of 10x10 size, used for graphics and other things @@ -505,3 +527,4 @@ */ Texture.WHITE = createWhiteTexture(); removeAllHandlers(Texture.WHITE); +removeAllHandlers(Texture.WHITE.baseTexture); diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index 59eb9fd..a9b9f51 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -1,6 +1,7 @@ import BaseTexture from './BaseTexture'; import { uid, BaseTextureCache } from '../utils'; -import * as ticker from '../ticker'; +import { shared } from '../ticker'; +import { UPDATE_PRIORITY } from '../const'; /** * A texture of a [playing] Video. @@ -125,7 +126,7 @@ if (!this._isAutoUpdating && this.autoUpdate) { - ticker.shared.add(this.update, this); + shared.add(this.update, this, UPDATE_PRIORITY.HIGH); this._isAutoUpdating = true; } } @@ -139,7 +140,7 @@ { if (this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); this._isAutoUpdating = false; } } @@ -187,7 +188,7 @@ { if (this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); } if (this.source && this.source._pixiId) @@ -281,12 +282,12 @@ if (!this._autoUpdate && this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); this._isAutoUpdating = false; } else if (this._autoUpdate && !this._isAutoUpdating) { - ticker.shared.add(this.update, this); + shared.add(this.update, this, UPDATE_PRIORITY.HIGH); this._isAutoUpdating = true; } } diff --git a/src/core/ticker/Ticker.js b/src/core/ticker/Ticker.js index 17a1517..40df138 100644 --- a/src/core/ticker/Ticker.js +++ b/src/core/ticker/Ticker.js @@ -1,12 +1,10 @@ import settings from '../settings'; -import EventEmitter from 'eventemitter3'; - -// Internal event used by composed emitter -const TICK = 'tick'; +import { UPDATE_PRIORITY } from '../const'; +import TickerListener from './TickerListener'; /** * A Ticker class that runs an update loop that other objects listen to. - * This class is composed around an EventEmitter object to add listeners + * This class is composed around listeners * meant for execution on the next requested animation frame. * Animation frames are requested only when necessary, * e.g. When the ticker is started and the emitter has listeners. @@ -22,10 +20,11 @@ constructor() { /** - * Internal emitter used to fire 'tick' event + * The first listener. All new listeners added are chained on this. * @private + * @type {TickerListener} */ - this._emitter = new EventEmitter(); + this._head = new TickerListener(null, null, Infinity); /** * Internal current frame request ID @@ -131,7 +130,7 @@ // Invoke listeners now this.update(time); // Listener side effects may have modified ticker state. - if (this.started && this._requestId === null && this._emitter.listeners(TICK, true)) + if (this.started && this._requestId === null && this._head.next) { this._requestId = requestAnimationFrame(this._tick); } @@ -148,7 +147,7 @@ */ _requestIfNeeded() { - if (this._requestId === null && this._emitter.listeners(TICK, true)) + if (this._requestId === null && this._head.next) { // ensure callbacks get correct delta this.lastTime = performance.now(); @@ -193,35 +192,72 @@ } /** - * Calls {@link module:eventemitter3.EventEmitter#on} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. + * Register a handler for tick events. Calls continuously unless + * it is removed or the ticker is stopped. * * @param {Function} fn - The listener function to be added for updates * @param {Function} [context] - The listener context + * @param {number} [priority=PIXI.UPDATE_PRIORITY.NORMAL] - The priority for emitting * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - add(fn, context) + add(fn, context, priority = UPDATE_PRIORITY.NORMAL) { - this._emitter.on(TICK, fn, context); - - this._startIfPossible(); - - return this; + return this._addListener(new TickerListener(fn, context, priority)); } /** - * Calls {@link module:eventemitter3.EventEmitter#once} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. + * Add a handler for the tick event which is only execute once. * * @param {Function} fn - The listener function to be added for one update * @param {Function} [context] - The listener context + * @param {number} [priority=PIXI.UPDATE_PRIORITY.NORMAL] - The priority for emitting * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - addOnce(fn, context) + addOnce(fn, context, priority = UPDATE_PRIORITY.NORMAL) { - this._emitter.once(TICK, fn, context); + return this._addListener(new TickerListener(fn, context, priority, true)); + } + + /** + * Internally adds the event handler so that it can be sorted by priority. + * Priority allows certain handler (user, AnimatedSprite, Interaction) to be run + * before the rendering. + * + * @private + * @param {TickerListener} listener - Current listener being added. + * @returns {PIXI.ticker.Ticker} This instance of a ticker + */ + _addListener(listener) + { + // For attaching to head + let current = this._head.next; + let previous = this._head; + + // Add the first item + if (!current) + { + listener.connect(previous); + } + else + { + // Go from highest to lowest priority + while (current) + { + if (listener.priority >= current.priority) + { + listener.connect(previous); + break; + } + previous = current; + current = current.next; + } + + // Not yet connected + if (!listener.previous) + { + listener.connect(previous); + } + } this._startIfPossible(); @@ -229,19 +265,33 @@ } /** - * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. - * It checks if the emitter has listeners for 'tick' event. - * If it does, then it cancels the animation frame. + * Removes any handlers matching the function and context parameters. + * If no handlers are left after removing, then it cancels the animation frame. * - * @param {Function} [fn] - The listener function to be removed + * @param {Function} fn - The listener function to be removed * @param {Function} [context] - The listener context to be removed * @returns {PIXI.ticker.Ticker} This instance of a ticker */ remove(fn, context) { - this._emitter.off(TICK, fn, context); + let listener = this._head.next; - if (!this._emitter.listeners(TICK, true)) + while (listener) + { + // We found a match, lets remove it + // no break to delete all possible matches + // incase a listener was added 2+ times + if (listener.match(fn, context)) + { + listener = listener.destroy(); + } + else + { + listener = listener.next; + } + } + + if (!this._head.next) { this._cancelIfNeeded(); } @@ -276,6 +326,25 @@ } /** + * Destroy the ticker and don't use after this. Calling + * this method removes all references to internal events. + */ + destroy() + { + this.stop(); + + let listener = this._head.next; + + while (listener) + { + listener = listener.destroy(true); + } + + this._head.destroy(); + this._head = null; + } + + /** * Triggers an update. An update entails setting the * current {@link PIXI.ticker.Ticker#elapsedMS}, * the current {@link PIXI.ticker.Ticker#deltaTime}, @@ -320,8 +389,22 @@ this.deltaTime = elapsedMS * settings.TARGET_FPMS * this.speed; + // Cache a local reference, in-case ticker is destroyed + // during the emit, we can still check for head.next + const head = this._head; + // Invoke listeners added to internal emitter - this._emitter.emit(TICK, this.deltaTime); + let listener = head.next; + + while (listener) + { + listener = listener.emit(this.deltaTime); + } + + if (!head.next) + { + this._cancelIfNeeded(); + } } else { diff --git a/src/core/ticker/TickerListener.js b/src/core/ticker/TickerListener.js new file mode 100644 index 0000000..2bedb34 --- /dev/null +++ b/src/core/ticker/TickerListener.js @@ -0,0 +1,158 @@ +/** + * Internal class for handling the priority sorting of ticker handlers. + * + * @private + * @class + * @memberof PIXI.ticker + */ +export default class TickerListener +{ + /** + * Constructor + * + * @param {Function} fn - The listener function to be added for one update + * @param {Function} [context=null] - The listener context + * @param {number} [priority=0] - The priority for emitting + * @param {boolean} [once=false] - If the handler should fire once + */ + constructor(fn, context = null, priority = 0, once = false) + { + /** + * The handler function to execute. + * @member {Function} + */ + this.fn = fn; + + /** + * The calling to execute. + * @member {Function} + */ + this.context = context; + + /** + * The current priority. + * @member {number} + */ + this.priority = priority; + + /** + * If this should only execute once. + * @member {boolean} + */ + this.once = once; + + /** + * The next item in chain. + * @member {TickerListener} + */ + this.next = null; + + /** + * The previous item in chain. + * @member {TickerListener} + */ + this.previous = null; + + /** + * `true` if this listener has been destroyed already. + * @member {boolean} + * @private + */ + this._destroyed = false; + } + + /** + * Simple compare function to figure out if a function and context match. + * + * @param {Function} fn - The listener function to be added for one update + * @param {Function} context - The listener context + * @return {boolean} `true` if the listener match the arguments + */ + match(fn, context) + { + context = context || null; + + return this.fn === fn && this.context === context; + } + + /** + * Emit by calling the current function. + * @param {number} deltaTime - time since the last emit. + * @return {TickerListener} Next ticker + */ + emit(deltaTime) + { + if (this.context) + { + this.fn.call(this.context, deltaTime); + } + else + { + this.fn(deltaTime); + } + + if (this.once) + { + this.destroy(); + } + + const redirect = this.next; + + // Soft-destroying should remove + // the next reference + if (this._destroyed) + { + this.next = null; + } + + return redirect; + } + + /** + * Connect to the list. + * @param {TickerListener} previous - Input node, previous listener + */ + connect(previous) + { + this.previous = previous; + if (previous.next) + { + previous.next.previous = this; + } + this.next = previous.next; + previous.next = this; + } + + /** + * Destroy and don't use after this. + * @param {boolean} [hard = false] `true` to remove the `next` reference, this + * is considered a hard destroy. Soft destroy maintains the next reference. + * @return {TickerListener} The listener to redirect while emitting or removing. + */ + destroy(hard = false) + { + this._destroyed = true; + this.fn = null; + this.context = null; + + // Disconnect, hook up next and previous + if (this.previous) + { + this.previous.next = this.next; + } + + if (this.next) + { + this.next.previous = this.previous; + } + + // Redirect to the next item + const redirect = this.previous; + + // Remove references + this.next = hard ? null : redirect; + this.previous = null; + + return redirect; + } +} diff --git a/src/core/ticker/index.js b/src/core/ticker/index.js index 4b3017c..c3020d4 100644 --- a/src/core/ticker/index.js +++ b/src/core/ticker/index.js @@ -45,8 +45,25 @@ const shared = new Ticker(); shared.autoStart = true; +shared.destroy = () => +{ + // protect destroying shared ticker + // this is used by other internal systems + // like AnimatedSprite and InteractionManager +}; /** + * This namespace contains an API for interacting with PIXI's internal global update loop. + * + * This ticker is used for rendering, {@link PIXI.extras.AnimatedSprite AnimatedSprite}, + * {@link PIXI.interaction.InteractionManager InteractionManager} and many other time-based PIXI systems. + * @example + * const ticker = new PIXI.ticker.Ticker(); + * ticker.stop(); + * ticker.add((deltaTime) => { + * // do something every frame + * }); + * ticker.start(); * @namespace PIXI.ticker */ export { shared, Ticker }; diff --git a/src/core/utils/index.js b/src/core/utils/index.js index be44750..7441f7b 100644 --- a/src/core/utils/index.js +++ b/src/core/utils/index.js @@ -9,6 +9,21 @@ let saidHello = false; /** + * Generalized convenience utilities for PIXI. + * @example + * // Extend PIXI's internal Event Emitter. + * class MyEmitter extends PIXI.utils.EventEmitter { + * constructor() { + * super(); + * console.log("Emitter created!"); + * } + * } + * + * // Get info on current device + * console.log(PIXI.utils.isMobile); + * + * // Convert hex color to string + * console.log(PIXI.utils.hex2string(0xff00ff)); // returns: "#ff00ff" * @namespace PIXI.utils */ export { diff --git a/src/deprecation.js b/src/deprecation.js index b9c73b3..33daeec 100644 --- a/src/deprecation.js +++ b/src/deprecation.js @@ -918,6 +918,35 @@ }); /** + * @method + * @name PIXI.prepare.BasePrepare#register + * @see PIXI.prepare.BasePrepare#registerFindHook + * @deprecated since version 4.4.2 + * @param {Function} [addHook] - Function call that takes two parameters: `item:*, queue:Array` + * function must return `true` if it was able to add item to the queue. + * @param {Function} [uploadHook] - Function call that takes two parameters: `prepare:CanvasPrepare, item:*` and + * function must return `true` if it was able to handle upload of item. + * @return {PIXI.BasePrepare} Instance of plugin for chaining. + */ +prepare.BasePrepare.prototype.register = function register(addHook, uploadHook) +{ + warn('renderer.plugins.prepare.register is now deprecated, ' + + 'please use renderer.plugins.prepare.registerFindHook & renderer.plugins.prepare.registerUploadHook'); + + if (addHook) + { + this.registerFindHook(addHook); + } + + if (uploadHook) + { + this.registerUploadHook(uploadHook); + } + + return this; +}; + +/** * The number of graphics or textures to upload to the GPU. * * @name PIXI.prepare.canvas.UPLOADS_PER_FRAME @@ -969,67 +998,73 @@ }, }); -Object.defineProperties(loaders.Resource.prototype, { - isJson: { - get() - { - warn('The isJson property is deprecated, please use `resource.type === Resource.TYPE.JSON`.'); +if (loaders.Loader) +{ + const Resource = loaders.Resource; + const Loader = loaders.Loader; - return this.type === loaders.Loader.Resource.TYPE.JSON; + Object.defineProperties(Resource.prototype, { + isJson: { + get() + { + warn('The isJson property is deprecated, please use `resource.type === Resource.TYPE.JSON`.'); + + return this.type === Resource.TYPE.JSON; + }, }, - }, - isXml: { - get() - { - warn('The isXml property is deprecated, please use `resource.type === Resource.TYPE.XML`.'); + isXml: { + get() + { + warn('The isXml property is deprecated, please use `resource.type === Resource.TYPE.XML`.'); - return this.type === loaders.Loader.Resource.TYPE.XML; + return this.type === Resource.TYPE.XML; + }, }, - }, - isImage: { - get() - { - warn('The isImage property is deprecated, please use `resource.type === Resource.TYPE.IMAGE`.'); + isImage: { + get() + { + warn('The isImage property is deprecated, please use `resource.type === Resource.TYPE.IMAGE`.'); - return this.type === loaders.Loader.Resource.TYPE.IMAGE; + return this.type === Resource.TYPE.IMAGE; + }, }, - }, - isAudio: { - get() - { - warn('The isAudio property is deprecated, please use `resource.type === Resource.TYPE.AUDIO`.'); + isAudio: { + get() + { + warn('The isAudio property is deprecated, please use `resource.type === Resource.TYPE.AUDIO`.'); - return this.type === loaders.Loader.Resource.TYPE.AUDIO; + return this.type === Resource.TYPE.AUDIO; + }, }, - }, - isVideo: { - get() - { - warn('The isVideo property is deprecated, please use `resource.type === Resource.TYPE.VIDEO`.'); + isVideo: { + get() + { + warn('The isVideo property is deprecated, please use `resource.type === Resource.TYPE.VIDEO`.'); - return this.type === loaders.Loader.Resource.TYPE.VIDEO; + return this.type === Resource.TYPE.VIDEO; + }, }, - }, -}); + }); -Object.defineProperties(loaders.Loader.prototype, { - before: { - get() - { - warn('The before() method is deprecated, please use pre().'); + Object.defineProperties(Loader.prototype, { + before: { + get() + { + warn('The before() method is deprecated, please use pre().'); - return this.pre; + return this.pre; + }, }, - }, - after: { - get() - { - warn('The after() method is deprecated, please use use().'); + after: { + get() + { + warn('The after() method is deprecated, please use use().'); - return this.use; + return this.use; + }, }, - }, -}); + }); +} /** * @name PIXI.interaction.interactiveTarget#defaultCursor diff --git a/src/extract/canvas/CanvasExtract.js b/src/extract/canvas/CanvasExtract.js index 941ca05..3ac5aee 100644 --- a/src/extract/canvas/CanvasExtract.js +++ b/src/extract/canvas/CanvasExtract.js @@ -8,7 +8,7 @@ * An instance of this class is automatically created by default, and can be found at renderer.plugins.extract * * @class - * @memberof PIXI + * @memberof PIXI.extract */ export default class CanvasExtract { @@ -21,9 +21,9 @@ /** * Collection of methods for extracting data (image, pixels, etc.) from a display object or render texture * - * @member {PIXI.CanvasExtract} extract + * @member {PIXI.extract.CanvasExtract} extract * @memberof PIXI.CanvasRenderer# - * @see PIXI.CanvasExtract + * @see PIXI.extract.CanvasExtract */ renderer.extract = this; } diff --git a/src/extract/index.js b/src/extract/index.js index 0f2170d..e9478c1 100644 --- a/src/extract/index.js +++ b/src/extract/index.js @@ -1,2 +1,22 @@ +/** + * This namespace provides renderer-specific plugins for exporting content from a renderer. + * For instance, these plugins can be used for saving an Image, Canvas element or for exporting the raw image data (pixels). + * + * Do not instantiate these plugins directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. + * @example + * // Create a new app (will auto-add extract plugin to renderer) + * const app = new PIXI.Application(); + * + * // Draw a red circle + * const graphics = new PIXI.Graphics() + * .beginFill(0xFF0000) + * .drawCircle(0, 0, 50); + * + * // Render the graphics as an HTMLImageElement + * const image = app.renderer.plugins.extract.image(graphics); + * document.body.appendChild(image); + * @namespace PIXI.extract + */ export { default as webgl } from './webgl/WebGLExtract'; export { default as canvas } from './canvas/CanvasExtract'; diff --git a/src/extract/webgl/WebGLExtract.js b/src/extract/webgl/WebGLExtract.js index 60228bb..1c3dd30 100644 --- a/src/extract/webgl/WebGLExtract.js +++ b/src/extract/webgl/WebGLExtract.js @@ -9,7 +9,7 @@ * An instance of this class is automatically created by default, and can be found at renderer.plugins.extract * * @class - * @memberof PIXI + * @memberof PIXI.extract */ export default class WebGLExtract { @@ -22,9 +22,9 @@ /** * Collection of methods for extracting data (image, pixels, etc.) from a display object or render texture * - * @member {PIXI.WebGLExtract} extract + * @member {PIXI.extract.WebGLExtract} extract * @memberof PIXI.WebGLRenderer# - * @see PIXI.WebGLExtract + * @see PIXI.extract.WebGLExtract */ renderer.extract = this; } diff --git a/src/extras/AnimatedSprite.js b/src/extras/AnimatedSprite.js index 13b804f..5297778 100644 --- a/src/extras/AnimatedSprite.js +++ b/src/extras/AnimatedSprite.js @@ -137,7 +137,7 @@ this.playing = true; if (this._autoUpdate) { - core.ticker.shared.add(this.update, this); + core.ticker.shared.add(this.update, this, core.UPDATE_PRIORITY.HIGH); } } diff --git a/src/extras/TextureTransform.js b/src/extras/TextureTransform.js index 78e6e40..16536d2 100644 --- a/src/extras/TextureTransform.js +++ b/src/extras/TextureTransform.js @@ -65,8 +65,36 @@ } /** + * Multiplies uvs array to transform + * @param {Float32Array} uvs mesh uvs + * @param {Float32Array} [out=uvs] output + * @returns {Float32Array} output + */ + multiplyUvs(uvs, out) + { + if (out === undefined) + { + out = uvs; + } + + const mat = this.mapCoord; + + for (let i = 0; i < uvs.length; i += 2) + { + const x = uvs[i]; + const y = uvs[i + 1]; + + out[i] = (x * mat.a) + (y * mat.c) + mat.tx; + out[i + 1] = (x * mat.b) + (y * mat.d) + mat.ty; + } + + return out; + } + + /** * updates matrices if texture was changed * @param {boolean} forceUpdate if true, matrices will be updated any case + * @returns {boolean} whether or not it was updated */ update(forceUpdate) { @@ -74,13 +102,13 @@ if (!tex || !tex.valid) { - return; + return false; } if (!forceUpdate && this._lastTextureID === tex._updateID) { - return; + return false; } this._lastTextureID = tex._updateID; @@ -110,5 +138,7 @@ frame[3] = (tex._frame.y + tex._frame.height - margin + offset) / texBase.height; this.uClampOffset[0] = offset / texBase.realWidth; this.uClampOffset[1] = offset / texBase.realHeight; + + return true; } } diff --git a/.travis.yml b/.travis.yml index 3df8b22..14de334 100644 --- a/.travis.yml +++ b/.travis.yml @@ -110,3 +110,43 @@ on: all_branches: true condition: $TRAVIS_TAG + # Deploy config for latest release + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: dist + upload-dir: "release" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: docs + upload-dir: "release/docs" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: coverage + upload-dir: "release/coverage" + on: + all_branches: true + condition: $TRAVIS_TAG \ No newline at end of file diff --git a/README.md b/README.md index d26fadd..3b85f85 100644 --- a/README.md +++ b/README.md @@ -36,22 +36,14 @@ - Wiki: Other misc tutorials and resources are [on the Wiki](https://github.com/pixijs/pixi.js/wiki/Resources). ### Community ### -- Forums: Check out the [forum] (http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow] (http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. +- Forums: Check out the [forum](http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow](http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. - Inspiration: Check out the [gallery](http://www.pixijs.com/gallery) to see some of the amazing things people have created! - Chat: You can join us on [Gitter](https://gitter.im/pixijs/pixi.js) To chat about Pixi. We also now have a Slack channel. If you would like to join it please Send me an email (mat@goodboydigital.com) and I will invite you in. ### Setup ### -It's easy to get started with Pixi.js! Simply grab the pre-built versions from here: - -Release Branch - Nice and stable Pixi.js -- Unminified: [http://pixijs.download/release/pixi.js] -- Minified: [http://pixijs.download/release/pixi.min.js] - -Develop Branch - The bleeding edge version of Pixi.js -- Unminified: [http://pixijs.download/dev/pixi.js] -- Minified: [http://pixijs.download/dev/pixi.min.js] +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. @@ -70,10 +62,10 @@ #### CDN Install (via cdnjs) ```html - + ``` -_Note: `4.2.2` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ +_Note: `4.4.3` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ ### Demos ### @@ -130,7 +122,7 @@ // and the root stage PIXI.Container. var app = new PIXI.Application(); -// The application will create a canvas element for you that you +// The application will create a canvas element for you that you // can then insert into the DOM. document.body.appendChild(app.view); @@ -150,7 +142,7 @@ // Add the bunny to the scene we are building. app.stage.addChild(bunny); - + // Listen for frame updates app.ticker.add(function() { // each frame we spin the bunny around a bit diff --git a/bower.json b/bower.json deleted file mode 100644 index 41b668c..0000000 --- a/bower.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "pixi.js", - "main": "dist/pixi.min.js", - "ignore": [ - "**/.*", - "docs", - "node_modules", - "bower_components", - "test", - "scripts", - "bower.json", - "inch.json" - ], - "dependencies": { - }, - "devDependencies": { - } -} diff --git a/package.json b/package.json index d35cbb2..308469f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.3.5", + "version": "4.4.4", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ @@ -40,11 +40,11 @@ "lib": "babel src --out-dir lib -s", "predocs": "rimraf docs/**", "docs": "jsdoc -c scripts/jsdoc.conf.json -R README.md", - "publish:patch": "npm version patch --no-git-tag-version && npm publish", - "publish:minor": "npm version minor --no-git-tag-version && npm publish", - "publish:major": "npm version major --no-git-tag-version && npm publish", + "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", - "postpublish": "node scripts/release.js" + "postpublish": "git push && git push --tags" }, "files": [ "dist/", @@ -72,7 +72,6 @@ "electron": "^1.4.15", "eslint": "^3.5.0", "floss": "^2.0.1", - "gh-pages": "^0.11.0", "jaguarjs-jsdoc": "^1.0.1", "js-md5": "^0.4.1", "jsdoc": "^3.4.2", diff --git a/scripts/jsdoc.conf.json b/scripts/jsdoc.conf.json index 553e61c..3f52180 100644 --- a/scripts/jsdoc.conf.json +++ b/scripts/jsdoc.conf.json @@ -43,7 +43,7 @@ }, "markdown" : { "parser" : "gfm", - "hardwrap" : true + "hardwrap" : false }, "opts": { "encoding" : "utf8", diff --git a/scripts/release.js b/scripts/release.js deleted file mode 100644 index 156620c..0000000 --- a/scripts/release.js +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env node -'use strict'; - -// Publish script to push releases of the bin files -// the normally are gitignored -const ghpages = require('gh-pages'); -const path = require('path'); -const packageInfo = require(path.join(__dirname, '..', 'package.json')); - -const options = { - src: [ - 'dist/**/*', - 'lib/**/*', - 'src/**/*', - 'scripts/*', - 'scripts/renders/*', - 'test/**/*', - '*.json', - '*.md', - 'LICENSE', - '.eslintrc', - '.editorconfig', - '.travis.yml', - '.babelrc', - ], - dotfiles: true, - branch: 'release', - message: packageInfo.version, - logger: console.log.bind(console), -}; - -ghpages.publish(process.cwd(), options, (err) => -{ - if (err) - { - console.log(err); - process.exit(1); - - return; - } - - process.exit(0); -}); diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index f16bcb1..220427a 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -21,7 +21,7 @@ const DIV_HOOK_ZINDEX = 2; /** - * The Accessibility manager reacreates the ability to tab and and have content read by screen + * The Accessibility manager recreates the ability to tab and and have content read by screen * readers. This is very important as it can possibly help people with disabilities access pixi * content. * diff --git a/src/accessibility/index.js b/src/accessibility/index.js index 568cfb1..0ae108f 100644 --- a/src/accessibility/index.js +++ b/src/accessibility/index.js @@ -1,4 +1,9 @@ /** + * This namespace contains a renderer plugin for interaction accessibility for end-users + * with physical impairments which require screen-renders, keyboard navigation, etc. + * + * Do not instantiate this plugin directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. * @namespace PIXI.accessibility */ export { default as accessibleTarget } from './accessibleTarget'; diff --git a/src/core/Application.js b/src/core/Application.js index 5340578..2fcc729 100644 --- a/src/core/Application.js +++ b/src/core/Application.js @@ -1,6 +1,8 @@ import { autoDetectRenderer } from './autoDetectRenderer'; import Container from './display/Container'; import { shared, Ticker } from './ticker'; +import settings from './settings'; +import { UPDATE_PRIORITY } from './const'; /** * Convenience class to create a new PIXI application. @@ -22,28 +24,52 @@ */ export default class Application { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experience unexplained flickering try setting this to true. - * @param {boolean} [useSharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedLoader=false] - `true` to use PIXI.loaders.shared, `false` to create new Loader. */ - constructor(width, height, options, noWebGL, useSharedTicker = false) + constructor(options, arg2, arg3, arg4, arg5) { + // Support for constructor(width, height, options, noWebGL, useSharedTicker) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + forceCanvas: !!arg4, + sharedTicker: !!arg5, + }, arg3); + } + + /** + * The default options, so we mixin functionality later. + * @member {object} + * @protected + */ + this._options = options = Object.assign({ + sharedTicker: false, + forceCanvas: false, + sharedLoader: false, + }, options); + /** * WebGL renderer if available, otherwise CanvasRenderer * @member {PIXI.WebGLRenderer|PIXI.CanvasRenderer} */ - this.renderer = autoDetectRenderer(width, height, options, noWebGL); + this.renderer = autoDetectRenderer(options); /** * The root display container that's rendered. @@ -63,7 +89,7 @@ * @member {PIXI.ticker.Ticker} * @default PIXI.ticker.shared */ - this.ticker = useSharedTicker ? shared : new Ticker(); + this.ticker = options.sharedTicker ? shared : new Ticker(); // Start the rendering this.start(); @@ -78,7 +104,7 @@ this._ticker = ticker; if (ticker) { - ticker.add(this.render, this); + ticker.add(this.render, this, UPDATE_PRIORITY.LOW); } } get ticker() // eslint-disable-line require-jsdoc @@ -136,13 +162,18 @@ */ destroy(removeView) { - this.stop(); + const oldTicker = this._ticker; + this.ticker = null; + oldTicker.destroy(); + this.stage.destroy(); this.stage = null; this.renderer.destroy(removeView); this.renderer = null; + + this._options = null; } } diff --git a/src/core/autoDetectRenderer.js b/src/core/autoDetectRenderer.js index 8960425..9ec0364 100644 --- a/src/core/autoDetectRenderer.js +++ b/src/core/autoDetectRenderer.js @@ -2,6 +2,7 @@ import CanvasRenderer from './renderers/canvas/CanvasRenderer'; import WebGLRenderer from './renderers/webgl/WebGLRenderer'; +// eslint-disable-next-line valid-jsdoc /** * This helper function will automatically detect which renderer you should be using. * WebGL is the preferred renderer as it is a lot faster. If webGL is not supported by @@ -9,24 +10,32 @@ * * @memberof PIXI * @function autoDetectRenderer - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @return {PIXI.WebGLRenderer|PIXI.CanvasRenderer} Returns WebGL renderer if available, otherwise CanvasRenderer */ -export function autoDetectRenderer(width = 800, height = 600, options, noWebGL) +export function autoDetectRenderer(options, arg1, arg2, arg3) { - if (!noWebGL && utils.isWebGLSupported()) + // Backward-compatible support for noWebGL option + let forceCanvas = options && options.forceCanvas; + + if (arg3 !== undefined) { - return new WebGLRenderer(width, height, options); + forceCanvas = arg3; } - return new CanvasRenderer(width, height, options); + if (!forceCanvas && utils.isWebGLSupported()) + { + return new WebGLRenderer(options, arg1, arg2); + } + + return new CanvasRenderer(options, arg1, arg2); } diff --git a/src/core/const.js b/src/core/const.js index b9e99ae..2c68dee 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -341,3 +341,27 @@ LINEAR_VERTICAL: 0, LINEAR_HORIZONTAL: 1, }; + +/** + * Represents the update priorities used by internal PIXI classes when registered with + * the {@link PIXI.ticker.Ticker} object. Higher priority items are updated first and lower + * priority items, such as render, should go later. + * + * @static + * @constant + * @name UPDATE_PRIORITY + * @memberof PIXI + * @type {object} + * @property {number} INTERACTION=50 Highest priority, used for {@link PIXI.interaction.InteractionManager} + * @property {number} HIGH=25 High priority updating, {@link PIXI.VideoBaseTexture} and {@link PIXI.extras.AnimatedSprite} + * @property {number} NORMAL=0 Default priority for ticker events, see {@link PIXI.ticker.Ticker#add}. + * @property {number} LOW=-25 Low priority used for {@link PIXI.Application} rendering. + * @property {number} UTILITY=-50 Lowest priority used for {@link PIXI.prepare.BasePrepare} utility. + */ +export const UPDATE_PRIORITY = { + INTERACTION: 50, + HIGH: 25, + NORMAL: 0, + LOW: -25, + UTILITY: -50, +}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 4e6c77d..decb42a 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -13,7 +13,6 @@ * * @class * @extends EventEmitter - * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ export default class DisplayObject extends EventEmitter @@ -113,6 +112,29 @@ * @private */ this._mask = null; + + /** + * If the object has been destroyed via destroy(). If true, it should not be used. + * + * @member {boolean} + * @private + * @readonly + */ + this._destroyed = false; + + /** + * Fired when this DisplayObject is added to a Container. + * + * @event PIXI.DisplayObject#added + * @param {PIXI.Container} container - The container added to. + */ + + /** + * Fired when this DisplayObject is removed from a Container. + * + * @event PIXI.DisplayObject#removed + * @param {PIXI.Container} container - The container removed from. + */ } /** @@ -403,6 +425,8 @@ this.interactive = false; this.interactiveChildren = false; + + this._destroyed = true; } /** diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index aafdfe2..f787c4a 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -792,8 +792,8 @@ sprite.worldAlpha = this.worldAlpha * sprite.alpha; sprite.blendMode = this.blendMode; - sprite.texture._frame.width = rect.width; - sprite.texture._frame.height = rect.height; + sprite._texture._frame.width = rect.width; + sprite._texture._frame.height = rect.height; sprite.transform.worldTransform = this.transform.worldTransform; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index a24c775..b9f192d 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -19,11 +19,12 @@ */ export default class SystemRenderer extends EventEmitter { + // eslint-disable-next-line valid-jsdoc /** * @param {string} system - The name of the system this renderer is for. - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -37,27 +38,31 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(system, screenWidth, screenHeight, options) + constructor(system, options, arg2, arg3) { super(); sayHello(system); - // prepare options - if (options) + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') { - for (const i in settings.RENDER_OPTIONS) - { - if (typeof options[i] === 'undefined') - { - options[i] = settings.RENDER_OPTIONS[i]; - } - } + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); } - else - { - options = settings.RENDER_OPTIONS; - } + + // Add the default render options + options = Object.assign({}, settings.RENDER_OPTIONS, options); + + /** + * The supplied constructor options. + * + * @member {Object} + * @readOnly + */ + this.options = options; /** * The type of the renderer. @@ -75,7 +80,7 @@ * * @member {PIXI.Rectangle} */ - this.screen = new Rectangle(0, 0, screenWidth || 800, screenHeight || 600); + this.screen = new Rectangle(0, 0, options.width, options.height); /** * The canvas element that everything is drawn to @@ -279,6 +284,8 @@ this.blendModes = null; + this.options = null; + this.preserveDrawingBuffer = false; this.clearBeforeRender = false; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index e84e49c..691caf0 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -17,10 +17,11 @@ */ export default class CanvasRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -34,9 +35,9 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('Canvas', screenWidth, screenHeight, options); + super('Canvas', options, arg2, arg3); this.type = RENDERER_TYPE.CANVAS; @@ -96,7 +97,19 @@ this.context = null; this.renderingToScreen = false; - this.resize(screenWidth, screenHeight); + this.resize(this.options.width, this.options.height); + + /** + * Fired after rendering finishes. + * + * @event PIXI.CanvasRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.CanvasRenderer#prerender + */ } /** @@ -299,4 +312,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.CanvasRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.CanvasExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.CanvasPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.CanvasRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index e1cf7cd..f6fe123 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -31,11 +31,12 @@ */ export default class WebGLRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** * - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -55,9 +56,9 @@ * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('WebGL', screenWidth, screenHeight, options); + super('WebGL', options, arg2, arg3); /** * The type of this renderer as a standardised const @@ -89,12 +90,6 @@ }; - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ this._backgroundColorRgba[3] = this.transparent ? 0 : 1; this.globalUniforms = new UniformGroup({ @@ -117,6 +112,13 @@ this.initPlugins(); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ if(options.context) { this.context.initFromContext(options.context); @@ -169,6 +171,25 @@ } return this; + + /** + * Fired after rendering finishes. + * + * @event PIXI.WebGLRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.WebGLRenderer#prerender + */ + + /** + * Fired when the WebGL context is set. + * + * @event PIXI.WebGLRenderer#context + * @param {WebGLRenderingContext} gl - WebGL context. + */ } /** @@ -295,4 +316,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.WebGLRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.WebGLRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(WebGLRenderer); diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index 4eb6128..fa3d883 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -47,6 +47,14 @@ * @member {boolean} */ this.enabled = true; + + /** + * If enabled, pixi will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; } /** diff --git a/src/core/renderers/webgl/systems/ContextSystem.js b/src/core/renderers/webgl/systems/ContextSystem.js index ff958c7..8df85ea 100644 --- a/src/core/renderers/webgl/systems/ContextSystem.js +++ b/src/core/renderers/webgl/systems/ContextSystem.js @@ -75,7 +75,8 @@ */ createContext(canvas, options) { - var gl = canvas.getContext('webgl', options) || + var gl = //canvas.getContext('webgl2', options) || + canvas.getContext('webgl', options) || canvas.getContext('experimental-webgl', options); if (!gl) diff --git a/src/core/renderers/webgl/systems/FilterSystem.js b/src/core/renderers/webgl/systems/FilterSystem.js index f47335d..37e28d2 100644 --- a/src/core/renderers/webgl/systems/FilterSystem.js +++ b/src/core/renderers/webgl/systems/FilterSystem.js @@ -111,7 +111,7 @@ // TODO we should fit the rect around the transform.. } - else + else if (filters[0].autoFit) { sourceFrame.fit(filterData.stack[0].destinationFrame); } diff --git a/src/core/renderers/webgl/systems/shader/shader/mapType.js b/src/core/renderers/webgl/systems/shader/shader/mapType.js index 581dace..917538a 100644 --- a/src/core/renderers/webgl/systems/shader/shader/mapType.js +++ b/src/core/renderers/webgl/systems/shader/shader/mapType.js @@ -15,7 +15,7 @@ } } - return GL_TABLE[type]; + return GL_TABLE[type]; }; var GL_TABLE = null; diff --git a/src/core/settings.js b/src/core/settings.js index 18c5877..38ef45c 100644 --- a/src/core/settings.js +++ b/src/core/settings.js @@ -2,6 +2,15 @@ import canUploadSameBuffer from './utils/canUploadSameBuffer'; /** + * User's customizable globals for overriding the default PIXI settings, such + * as a renderer's default resolution, framerate, float percision, etc. + * @example + * // Use the native window resolution as the default resolution + * // will support high-density displays when rendering + * PIXI.settings.RESOLUTION = window.devicePixelRatio. + * + * // Disable interpolation when scaling, will make texture be pixelated + * PIXI.settings.SCALE_MODE = PIXI.SCALE_MODES.NEAREST; * @namespace PIXI.settings */ export default { @@ -101,6 +110,9 @@ * @property {boolean} clearBeforeRender=true * @property {boolean} preserveDrawingBuffer=false * @property {boolean} roundPixels=false + * @property {number} width=800 + * @property {number} height=600 + * @property {boolean} legacy=false */ RENDER_OPTIONS: { view: null, @@ -112,6 +124,9 @@ clearBeforeRender: true, preserveDrawingBuffer: false, roundPixels: false, + width: 800, + height: 600, + legacy: false, }, /** diff --git a/src/core/shader/Program.js b/src/core/shader/Program.js index c0b538b..07b4398 100644 --- a/src/core/shader/Program.js +++ b/src/core/shader/Program.js @@ -1,6 +1,6 @@ import extractUniformsFromSrc from './extractUniformsFromSrc'; import generateUniformsSync from './generateUniformsSync'; -import glCore from 'pixi-gl-core'; +import shaderUtils from '../renderers/webgl/systems/shader/shader'; import { ProgramCache } from '../utils'; import getTestContext from '../utils/getTestContext'; @@ -64,14 +64,17 @@ } else { - vertexSrc = glCore.shader.setPrecision(vertexSrc, 'mediump'); - fragmentSrc = glCore.shader.setPrecision(fragmentSrc, 'mediump'); + vertexSrc = shaderUtils.setPrecision(vertexSrc, 'mediump'); + fragmentSrc = shaderUtils.setPrecision(fragmentSrc, 'mediump'); - const program = glCore.shader.compileProgram(gl, vertexSrc, fragmentSrc); + const program = shaderUtils.compileProgram(gl, vertexSrc, fragmentSrc); + console.log("<<<<<<>>><<"); this.attributeData = this.getAttributeData(program, gl); this.uniformData = this.getUniformData(program, gl); + console.log(this.uniformData); + //gl.deleteProgram(program); } } @@ -95,13 +98,13 @@ for (let i = 0; i < totalAttributes; i++) { const attribData = gl.getActiveAttrib(program, i); - const type = glCore.shader.mapType(gl, attribData.type); + const type = shaderUtils.mapType(gl, attribData.type); /*eslint-disable */ const data = { type: type, name: attribData.name, - size: glCore.shader.mapSize(type), + size: shaderUtils.mapSize(type), location: 0, }; /*eslint-enable */ @@ -137,13 +140,14 @@ // TODO expose this as a prop? // const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); - const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); + //const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); for (let i = 0; i < totalUniforms; i++) { const uniformData = gl.getActiveUniform(program, i); const name = uniformData.name.replace(/\[.*?\]/, ''); - const type = glCore.shader.mapType(gl, uniformData.type); + const type = shaderUtils.mapType(gl, uniformData.type); + console.log('mapping ' + uniformData.type + ' to ' + type); // if (!name.match(maskRegex)) { @@ -151,7 +155,7 @@ uniforms[name] = { type: type, size: uniformData.size, - value: glCore.shader.defaultValue(type, uniformData.size), + value: shaderUtils.defaultValue(type, uniformData.size), }; /*eslint-enable */ } diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index e387e99..b57d095 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -147,12 +147,12 @@ // so if _width is 0 then width was not set.. if (this._width) { - this.scale.x = sign(this.scale.x) * this._width / this.texture.orig.width; + this.scale.x = sign(this.scale.x) * this._width / this._texture.orig.width; } if (this._height) { - this.scale.y = sign(this.scale.y) * this._height / this.texture.orig.height; + this.scale.y = sign(this.scale.y) * this._height / this._texture.orig.height; } } diff --git a/src/core/sprites/canvas/CanvasTinter.js b/src/core/sprites/canvas/CanvasTinter.js index 27e5e0c..fd9de5d 100644 --- a/src/core/sprites/canvas/CanvasTinter.js +++ b/src/core/sprites/canvas/CanvasTinter.js @@ -18,7 +18,7 @@ */ getTintedTexture: (sprite, color) => { - const texture = sprite.texture; + const texture = sprite._texture; color = CanvasTinter.roundColor(color); diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 5a4edda..8b9b797 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -344,6 +344,7 @@ const texture = this._texture; const style = this._style; + const padding = style.trim ? 0 : style.padding; texture.baseTexture.valid = true; texture.baseTexture.resolution = this.resolution; @@ -353,11 +354,11 @@ texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - texture.trim.x = -style.padding; - texture.trim.y = -style.padding; + texture.trim.x = -padding; + texture.trim.y = -padding; - texture.orig.width = texture._frame.width - (style.padding * 2); - texture.orig.height = texture._frame.height - (style.padding * 2); + texture.orig.width = texture._frame.width - (padding * 2); + texture.orig.height = texture._frame.height - (padding * 2); // call sprite onTextureUpdate to update scale if _width or _height were set this._onTextureUpdate(); diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index 853da3d..8a9c34b 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -114,6 +114,38 @@ this.cacheId = null; this.validate(); + + /** + * Fired when a not-immediately-available source finishes loading. + * + * @protected + * @event PIXI.BaseTexture#loaded + * @param {PIXI.BaseTexture} baseTexture - Resource loaded. + */ + + /** + * Fired when a not-immediately-available source fails to load. + * + * @protected + * @event PIXI.BaseTexture#error + * @param {PIXI.BaseTexture} baseTexture - Resource errored. + */ + + /** + * Fired when BaseTexture is updated. + * + * @protected + * @event PIXI.BaseTexture#update + * @param {PIXI.BaseTexture} baseTexture - Instance of texture being updated. + */ + + /** + * Fired when BaseTexture is destroyed. + * + * @protected + * @event PIXI.BaseTexture#dispose + * @param {PIXI.BaseTexture} baseTexture - Instance of texture being destroyed. + */ } setResource(resource) diff --git a/src/core/textures/Spritesheet.js b/src/core/textures/Spritesheet.js index d469c62..9b24f27 100644 --- a/src/core/textures/Spritesheet.js +++ b/src/core/textures/Spritesheet.js @@ -208,6 +208,7 @@ // lets also add the frame to pixi's global cache for fromFrame and fromImage functions TextureCache[i] = this.textures[i]; + this.textures[i].textureCacheId = i; } frameIndex++; diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 0a2c379..2213087 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -147,9 +147,9 @@ /** * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. * - * @event update - * @memberof PIXI.Texture# + * @event PIXI.Texture#update * @protected + * @param {PIXI.Texture} texture - Instance of texture being updated. */ this._updateID = 0; @@ -159,6 +159,15 @@ * @type {Object} */ this.transform = null; + + /** + * The id under which this Texture has been added to the texture cache. This is + * automatically set in certain cases, but may not always be accurate, particularly if + * the texture is in the cache under multiple ids. + * + * @member {string} + */ + this.textureCacheId = null; } /** @@ -225,11 +234,17 @@ this._uvs = null; this.trim = null; this.orig = null; + this.textureCacheId = null; this.valid = false; - this.off('dispose', this.dispose, this); - this.off('update', this.update, this); + for (const prop in TextureCache) + { + if (TextureCache[prop] === this) + { + delete TextureCache[prop]; + } + } } /** @@ -315,14 +330,15 @@ */ static fromLoader(source, imageUrl, name) { - // console.log('added from loader...') + // console.log('added from loader...') const resource = new ImageResource(source);//.from(imageUrl, crossorigin);// document.createElement('img'); - // console.log('base resource ' + resource.width); + // console.log('base resource ' + resource.width); const baseTexture = new BaseTexture(resource, settings.SCALE_MODE, getResolutionOfUrl(imageUrl)); + /// console.log('base width ' + baseTexture.width); const texture = new Texture(baseTexture); @@ -335,6 +351,7 @@ // lets also add the frame to pixi's global cache for fromFrame and fromImage fucntions BaseTextureCache[name] = baseTexture; TextureCache[name] = texture; + texture.textureCacheId = name; // also add references by url if they are different. if (name !== imageUrl) @@ -355,6 +372,10 @@ */ static addTextureToCache(texture, id) { + if (!texture.textureCacheId) + { + texture.textureCacheId = id; + } TextureCache[id] = texture; } @@ -495,6 +516,7 @@ */ Texture.EMPTY = new Texture(new BaseTexture()); removeAllHandlers(Texture.EMPTY); +removeAllHandlers(Texture.EMPTY.baseTexture); /** * A white texture of 10x10 size, used for graphics and other things @@ -505,3 +527,4 @@ */ Texture.WHITE = createWhiteTexture(); removeAllHandlers(Texture.WHITE); +removeAllHandlers(Texture.WHITE.baseTexture); diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index 59eb9fd..a9b9f51 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -1,6 +1,7 @@ import BaseTexture from './BaseTexture'; import { uid, BaseTextureCache } from '../utils'; -import * as ticker from '../ticker'; +import { shared } from '../ticker'; +import { UPDATE_PRIORITY } from '../const'; /** * A texture of a [playing] Video. @@ -125,7 +126,7 @@ if (!this._isAutoUpdating && this.autoUpdate) { - ticker.shared.add(this.update, this); + shared.add(this.update, this, UPDATE_PRIORITY.HIGH); this._isAutoUpdating = true; } } @@ -139,7 +140,7 @@ { if (this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); this._isAutoUpdating = false; } } @@ -187,7 +188,7 @@ { if (this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); } if (this.source && this.source._pixiId) @@ -281,12 +282,12 @@ if (!this._autoUpdate && this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); this._isAutoUpdating = false; } else if (this._autoUpdate && !this._isAutoUpdating) { - ticker.shared.add(this.update, this); + shared.add(this.update, this, UPDATE_PRIORITY.HIGH); this._isAutoUpdating = true; } } diff --git a/src/core/ticker/Ticker.js b/src/core/ticker/Ticker.js index 17a1517..40df138 100644 --- a/src/core/ticker/Ticker.js +++ b/src/core/ticker/Ticker.js @@ -1,12 +1,10 @@ import settings from '../settings'; -import EventEmitter from 'eventemitter3'; - -// Internal event used by composed emitter -const TICK = 'tick'; +import { UPDATE_PRIORITY } from '../const'; +import TickerListener from './TickerListener'; /** * A Ticker class that runs an update loop that other objects listen to. - * This class is composed around an EventEmitter object to add listeners + * This class is composed around listeners * meant for execution on the next requested animation frame. * Animation frames are requested only when necessary, * e.g. When the ticker is started and the emitter has listeners. @@ -22,10 +20,11 @@ constructor() { /** - * Internal emitter used to fire 'tick' event + * The first listener. All new listeners added are chained on this. * @private + * @type {TickerListener} */ - this._emitter = new EventEmitter(); + this._head = new TickerListener(null, null, Infinity); /** * Internal current frame request ID @@ -131,7 +130,7 @@ // Invoke listeners now this.update(time); // Listener side effects may have modified ticker state. - if (this.started && this._requestId === null && this._emitter.listeners(TICK, true)) + if (this.started && this._requestId === null && this._head.next) { this._requestId = requestAnimationFrame(this._tick); } @@ -148,7 +147,7 @@ */ _requestIfNeeded() { - if (this._requestId === null && this._emitter.listeners(TICK, true)) + if (this._requestId === null && this._head.next) { // ensure callbacks get correct delta this.lastTime = performance.now(); @@ -193,35 +192,72 @@ } /** - * Calls {@link module:eventemitter3.EventEmitter#on} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. + * Register a handler for tick events. Calls continuously unless + * it is removed or the ticker is stopped. * * @param {Function} fn - The listener function to be added for updates * @param {Function} [context] - The listener context + * @param {number} [priority=PIXI.UPDATE_PRIORITY.NORMAL] - The priority for emitting * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - add(fn, context) + add(fn, context, priority = UPDATE_PRIORITY.NORMAL) { - this._emitter.on(TICK, fn, context); - - this._startIfPossible(); - - return this; + return this._addListener(new TickerListener(fn, context, priority)); } /** - * Calls {@link module:eventemitter3.EventEmitter#once} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. + * Add a handler for the tick event which is only execute once. * * @param {Function} fn - The listener function to be added for one update * @param {Function} [context] - The listener context + * @param {number} [priority=PIXI.UPDATE_PRIORITY.NORMAL] - The priority for emitting * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - addOnce(fn, context) + addOnce(fn, context, priority = UPDATE_PRIORITY.NORMAL) { - this._emitter.once(TICK, fn, context); + return this._addListener(new TickerListener(fn, context, priority, true)); + } + + /** + * Internally adds the event handler so that it can be sorted by priority. + * Priority allows certain handler (user, AnimatedSprite, Interaction) to be run + * before the rendering. + * + * @private + * @param {TickerListener} listener - Current listener being added. + * @returns {PIXI.ticker.Ticker} This instance of a ticker + */ + _addListener(listener) + { + // For attaching to head + let current = this._head.next; + let previous = this._head; + + // Add the first item + if (!current) + { + listener.connect(previous); + } + else + { + // Go from highest to lowest priority + while (current) + { + if (listener.priority >= current.priority) + { + listener.connect(previous); + break; + } + previous = current; + current = current.next; + } + + // Not yet connected + if (!listener.previous) + { + listener.connect(previous); + } + } this._startIfPossible(); @@ -229,19 +265,33 @@ } /** - * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. - * It checks if the emitter has listeners for 'tick' event. - * If it does, then it cancels the animation frame. + * Removes any handlers matching the function and context parameters. + * If no handlers are left after removing, then it cancels the animation frame. * - * @param {Function} [fn] - The listener function to be removed + * @param {Function} fn - The listener function to be removed * @param {Function} [context] - The listener context to be removed * @returns {PIXI.ticker.Ticker} This instance of a ticker */ remove(fn, context) { - this._emitter.off(TICK, fn, context); + let listener = this._head.next; - if (!this._emitter.listeners(TICK, true)) + while (listener) + { + // We found a match, lets remove it + // no break to delete all possible matches + // incase a listener was added 2+ times + if (listener.match(fn, context)) + { + listener = listener.destroy(); + } + else + { + listener = listener.next; + } + } + + if (!this._head.next) { this._cancelIfNeeded(); } @@ -276,6 +326,25 @@ } /** + * Destroy the ticker and don't use after this. Calling + * this method removes all references to internal events. + */ + destroy() + { + this.stop(); + + let listener = this._head.next; + + while (listener) + { + listener = listener.destroy(true); + } + + this._head.destroy(); + this._head = null; + } + + /** * Triggers an update. An update entails setting the * current {@link PIXI.ticker.Ticker#elapsedMS}, * the current {@link PIXI.ticker.Ticker#deltaTime}, @@ -320,8 +389,22 @@ this.deltaTime = elapsedMS * settings.TARGET_FPMS * this.speed; + // Cache a local reference, in-case ticker is destroyed + // during the emit, we can still check for head.next + const head = this._head; + // Invoke listeners added to internal emitter - this._emitter.emit(TICK, this.deltaTime); + let listener = head.next; + + while (listener) + { + listener = listener.emit(this.deltaTime); + } + + if (!head.next) + { + this._cancelIfNeeded(); + } } else { diff --git a/src/core/ticker/TickerListener.js b/src/core/ticker/TickerListener.js new file mode 100644 index 0000000..2bedb34 --- /dev/null +++ b/src/core/ticker/TickerListener.js @@ -0,0 +1,158 @@ +/** + * Internal class for handling the priority sorting of ticker handlers. + * + * @private + * @class + * @memberof PIXI.ticker + */ +export default class TickerListener +{ + /** + * Constructor + * + * @param {Function} fn - The listener function to be added for one update + * @param {Function} [context=null] - The listener context + * @param {number} [priority=0] - The priority for emitting + * @param {boolean} [once=false] - If the handler should fire once + */ + constructor(fn, context = null, priority = 0, once = false) + { + /** + * The handler function to execute. + * @member {Function} + */ + this.fn = fn; + + /** + * The calling to execute. + * @member {Function} + */ + this.context = context; + + /** + * The current priority. + * @member {number} + */ + this.priority = priority; + + /** + * If this should only execute once. + * @member {boolean} + */ + this.once = once; + + /** + * The next item in chain. + * @member {TickerListener} + */ + this.next = null; + + /** + * The previous item in chain. + * @member {TickerListener} + */ + this.previous = null; + + /** + * `true` if this listener has been destroyed already. + * @member {boolean} + * @private + */ + this._destroyed = false; + } + + /** + * Simple compare function to figure out if a function and context match. + * + * @param {Function} fn - The listener function to be added for one update + * @param {Function} context - The listener context + * @return {boolean} `true` if the listener match the arguments + */ + match(fn, context) + { + context = context || null; + + return this.fn === fn && this.context === context; + } + + /** + * Emit by calling the current function. + * @param {number} deltaTime - time since the last emit. + * @return {TickerListener} Next ticker + */ + emit(deltaTime) + { + if (this.context) + { + this.fn.call(this.context, deltaTime); + } + else + { + this.fn(deltaTime); + } + + if (this.once) + { + this.destroy(); + } + + const redirect = this.next; + + // Soft-destroying should remove + // the next reference + if (this._destroyed) + { + this.next = null; + } + + return redirect; + } + + /** + * Connect to the list. + * @param {TickerListener} previous - Input node, previous listener + */ + connect(previous) + { + this.previous = previous; + if (previous.next) + { + previous.next.previous = this; + } + this.next = previous.next; + previous.next = this; + } + + /** + * Destroy and don't use after this. + * @param {boolean} [hard = false] `true` to remove the `next` reference, this + * is considered a hard destroy. Soft destroy maintains the next reference. + * @return {TickerListener} The listener to redirect while emitting or removing. + */ + destroy(hard = false) + { + this._destroyed = true; + this.fn = null; + this.context = null; + + // Disconnect, hook up next and previous + if (this.previous) + { + this.previous.next = this.next; + } + + if (this.next) + { + this.next.previous = this.previous; + } + + // Redirect to the next item + const redirect = this.previous; + + // Remove references + this.next = hard ? null : redirect; + this.previous = null; + + return redirect; + } +} diff --git a/src/core/ticker/index.js b/src/core/ticker/index.js index 4b3017c..c3020d4 100644 --- a/src/core/ticker/index.js +++ b/src/core/ticker/index.js @@ -45,8 +45,25 @@ const shared = new Ticker(); shared.autoStart = true; +shared.destroy = () => +{ + // protect destroying shared ticker + // this is used by other internal systems + // like AnimatedSprite and InteractionManager +}; /** + * This namespace contains an API for interacting with PIXI's internal global update loop. + * + * This ticker is used for rendering, {@link PIXI.extras.AnimatedSprite AnimatedSprite}, + * {@link PIXI.interaction.InteractionManager InteractionManager} and many other time-based PIXI systems. + * @example + * const ticker = new PIXI.ticker.Ticker(); + * ticker.stop(); + * ticker.add((deltaTime) => { + * // do something every frame + * }); + * ticker.start(); * @namespace PIXI.ticker */ export { shared, Ticker }; diff --git a/src/core/utils/index.js b/src/core/utils/index.js index be44750..7441f7b 100644 --- a/src/core/utils/index.js +++ b/src/core/utils/index.js @@ -9,6 +9,21 @@ let saidHello = false; /** + * Generalized convenience utilities for PIXI. + * @example + * // Extend PIXI's internal Event Emitter. + * class MyEmitter extends PIXI.utils.EventEmitter { + * constructor() { + * super(); + * console.log("Emitter created!"); + * } + * } + * + * // Get info on current device + * console.log(PIXI.utils.isMobile); + * + * // Convert hex color to string + * console.log(PIXI.utils.hex2string(0xff00ff)); // returns: "#ff00ff" * @namespace PIXI.utils */ export { diff --git a/src/deprecation.js b/src/deprecation.js index b9c73b3..33daeec 100644 --- a/src/deprecation.js +++ b/src/deprecation.js @@ -918,6 +918,35 @@ }); /** + * @method + * @name PIXI.prepare.BasePrepare#register + * @see PIXI.prepare.BasePrepare#registerFindHook + * @deprecated since version 4.4.2 + * @param {Function} [addHook] - Function call that takes two parameters: `item:*, queue:Array` + * function must return `true` if it was able to add item to the queue. + * @param {Function} [uploadHook] - Function call that takes two parameters: `prepare:CanvasPrepare, item:*` and + * function must return `true` if it was able to handle upload of item. + * @return {PIXI.BasePrepare} Instance of plugin for chaining. + */ +prepare.BasePrepare.prototype.register = function register(addHook, uploadHook) +{ + warn('renderer.plugins.prepare.register is now deprecated, ' + + 'please use renderer.plugins.prepare.registerFindHook & renderer.plugins.prepare.registerUploadHook'); + + if (addHook) + { + this.registerFindHook(addHook); + } + + if (uploadHook) + { + this.registerUploadHook(uploadHook); + } + + return this; +}; + +/** * The number of graphics or textures to upload to the GPU. * * @name PIXI.prepare.canvas.UPLOADS_PER_FRAME @@ -969,67 +998,73 @@ }, }); -Object.defineProperties(loaders.Resource.prototype, { - isJson: { - get() - { - warn('The isJson property is deprecated, please use `resource.type === Resource.TYPE.JSON`.'); +if (loaders.Loader) +{ + const Resource = loaders.Resource; + const Loader = loaders.Loader; - return this.type === loaders.Loader.Resource.TYPE.JSON; + Object.defineProperties(Resource.prototype, { + isJson: { + get() + { + warn('The isJson property is deprecated, please use `resource.type === Resource.TYPE.JSON`.'); + + return this.type === Resource.TYPE.JSON; + }, }, - }, - isXml: { - get() - { - warn('The isXml property is deprecated, please use `resource.type === Resource.TYPE.XML`.'); + isXml: { + get() + { + warn('The isXml property is deprecated, please use `resource.type === Resource.TYPE.XML`.'); - return this.type === loaders.Loader.Resource.TYPE.XML; + return this.type === Resource.TYPE.XML; + }, }, - }, - isImage: { - get() - { - warn('The isImage property is deprecated, please use `resource.type === Resource.TYPE.IMAGE`.'); + isImage: { + get() + { + warn('The isImage property is deprecated, please use `resource.type === Resource.TYPE.IMAGE`.'); - return this.type === loaders.Loader.Resource.TYPE.IMAGE; + return this.type === Resource.TYPE.IMAGE; + }, }, - }, - isAudio: { - get() - { - warn('The isAudio property is deprecated, please use `resource.type === Resource.TYPE.AUDIO`.'); + isAudio: { + get() + { + warn('The isAudio property is deprecated, please use `resource.type === Resource.TYPE.AUDIO`.'); - return this.type === loaders.Loader.Resource.TYPE.AUDIO; + return this.type === Resource.TYPE.AUDIO; + }, }, - }, - isVideo: { - get() - { - warn('The isVideo property is deprecated, please use `resource.type === Resource.TYPE.VIDEO`.'); + isVideo: { + get() + { + warn('The isVideo property is deprecated, please use `resource.type === Resource.TYPE.VIDEO`.'); - return this.type === loaders.Loader.Resource.TYPE.VIDEO; + return this.type === Resource.TYPE.VIDEO; + }, }, - }, -}); + }); -Object.defineProperties(loaders.Loader.prototype, { - before: { - get() - { - warn('The before() method is deprecated, please use pre().'); + Object.defineProperties(Loader.prototype, { + before: { + get() + { + warn('The before() method is deprecated, please use pre().'); - return this.pre; + return this.pre; + }, }, - }, - after: { - get() - { - warn('The after() method is deprecated, please use use().'); + after: { + get() + { + warn('The after() method is deprecated, please use use().'); - return this.use; + return this.use; + }, }, - }, -}); + }); +} /** * @name PIXI.interaction.interactiveTarget#defaultCursor diff --git a/src/extract/canvas/CanvasExtract.js b/src/extract/canvas/CanvasExtract.js index 941ca05..3ac5aee 100644 --- a/src/extract/canvas/CanvasExtract.js +++ b/src/extract/canvas/CanvasExtract.js @@ -8,7 +8,7 @@ * An instance of this class is automatically created by default, and can be found at renderer.plugins.extract * * @class - * @memberof PIXI + * @memberof PIXI.extract */ export default class CanvasExtract { @@ -21,9 +21,9 @@ /** * Collection of methods for extracting data (image, pixels, etc.) from a display object or render texture * - * @member {PIXI.CanvasExtract} extract + * @member {PIXI.extract.CanvasExtract} extract * @memberof PIXI.CanvasRenderer# - * @see PIXI.CanvasExtract + * @see PIXI.extract.CanvasExtract */ renderer.extract = this; } diff --git a/src/extract/index.js b/src/extract/index.js index 0f2170d..e9478c1 100644 --- a/src/extract/index.js +++ b/src/extract/index.js @@ -1,2 +1,22 @@ +/** + * This namespace provides renderer-specific plugins for exporting content from a renderer. + * For instance, these plugins can be used for saving an Image, Canvas element or for exporting the raw image data (pixels). + * + * Do not instantiate these plugins directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. + * @example + * // Create a new app (will auto-add extract plugin to renderer) + * const app = new PIXI.Application(); + * + * // Draw a red circle + * const graphics = new PIXI.Graphics() + * .beginFill(0xFF0000) + * .drawCircle(0, 0, 50); + * + * // Render the graphics as an HTMLImageElement + * const image = app.renderer.plugins.extract.image(graphics); + * document.body.appendChild(image); + * @namespace PIXI.extract + */ export { default as webgl } from './webgl/WebGLExtract'; export { default as canvas } from './canvas/CanvasExtract'; diff --git a/src/extract/webgl/WebGLExtract.js b/src/extract/webgl/WebGLExtract.js index 60228bb..1c3dd30 100644 --- a/src/extract/webgl/WebGLExtract.js +++ b/src/extract/webgl/WebGLExtract.js @@ -9,7 +9,7 @@ * An instance of this class is automatically created by default, and can be found at renderer.plugins.extract * * @class - * @memberof PIXI + * @memberof PIXI.extract */ export default class WebGLExtract { @@ -22,9 +22,9 @@ /** * Collection of methods for extracting data (image, pixels, etc.) from a display object or render texture * - * @member {PIXI.WebGLExtract} extract + * @member {PIXI.extract.WebGLExtract} extract * @memberof PIXI.WebGLRenderer# - * @see PIXI.WebGLExtract + * @see PIXI.extract.WebGLExtract */ renderer.extract = this; } diff --git a/src/extras/AnimatedSprite.js b/src/extras/AnimatedSprite.js index 13b804f..5297778 100644 --- a/src/extras/AnimatedSprite.js +++ b/src/extras/AnimatedSprite.js @@ -137,7 +137,7 @@ this.playing = true; if (this._autoUpdate) { - core.ticker.shared.add(this.update, this); + core.ticker.shared.add(this.update, this, core.UPDATE_PRIORITY.HIGH); } } diff --git a/src/extras/TextureTransform.js b/src/extras/TextureTransform.js index 78e6e40..16536d2 100644 --- a/src/extras/TextureTransform.js +++ b/src/extras/TextureTransform.js @@ -65,8 +65,36 @@ } /** + * Multiplies uvs array to transform + * @param {Float32Array} uvs mesh uvs + * @param {Float32Array} [out=uvs] output + * @returns {Float32Array} output + */ + multiplyUvs(uvs, out) + { + if (out === undefined) + { + out = uvs; + } + + const mat = this.mapCoord; + + for (let i = 0; i < uvs.length; i += 2) + { + const x = uvs[i]; + const y = uvs[i + 1]; + + out[i] = (x * mat.a) + (y * mat.c) + mat.tx; + out[i + 1] = (x * mat.b) + (y * mat.d) + mat.ty; + } + + return out; + } + + /** * updates matrices if texture was changed * @param {boolean} forceUpdate if true, matrices will be updated any case + * @returns {boolean} whether or not it was updated */ update(forceUpdate) { @@ -74,13 +102,13 @@ if (!tex || !tex.valid) { - return; + return false; } if (!forceUpdate && this._lastTextureID === tex._updateID) { - return; + return false; } this._lastTextureID = tex._updateID; @@ -110,5 +138,7 @@ frame[3] = (tex._frame.y + tex._frame.height - margin + offset) / texBase.height; this.uClampOffset[0] = offset / texBase.realWidth; this.uClampOffset[1] = offset / texBase.realHeight; + + return true; } } diff --git a/src/extras/TilingSprite.js b/src/extras/TilingSprite.js index 1f6014f..a3f027f 100644 --- a/src/extras/TilingSprite.js +++ b/src/extras/TilingSprite.js @@ -180,9 +180,9 @@ const transform = this.worldTransform; const resolution = renderer.resolution; const baseTexture = texture.baseTexture; - const baseTextureResolution = texture.baseTexture.resolution; - const modX = (this.tilePosition.x / this.tileScale.x) % texture._frame.width; - const modY = (this.tilePosition.y / this.tileScale.y) % texture._frame.height; + const baseTextureResolution = baseTexture.resolution; + const modX = ((this.tilePosition.x / this.tileScale.x) % texture._frame.width) * baseTextureResolution; + const modY = ((this.tilePosition.y / this.tileScale.y) % texture._frame.height) * baseTextureResolution; // create a nice shiny pattern! // TODO this needs to be refreshed if texture changes.. diff --git a/.travis.yml b/.travis.yml index 3df8b22..14de334 100644 --- a/.travis.yml +++ b/.travis.yml @@ -110,3 +110,43 @@ on: all_branches: true condition: $TRAVIS_TAG + # Deploy config for latest release + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: dist + upload-dir: "release" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: docs + upload-dir: "release/docs" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: coverage + upload-dir: "release/coverage" + on: + all_branches: true + condition: $TRAVIS_TAG \ No newline at end of file diff --git a/README.md b/README.md index d26fadd..3b85f85 100644 --- a/README.md +++ b/README.md @@ -36,22 +36,14 @@ - Wiki: Other misc tutorials and resources are [on the Wiki](https://github.com/pixijs/pixi.js/wiki/Resources). ### Community ### -- Forums: Check out the [forum] (http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow] (http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. +- Forums: Check out the [forum](http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow](http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. - Inspiration: Check out the [gallery](http://www.pixijs.com/gallery) to see some of the amazing things people have created! - Chat: You can join us on [Gitter](https://gitter.im/pixijs/pixi.js) To chat about Pixi. We also now have a Slack channel. If you would like to join it please Send me an email (mat@goodboydigital.com) and I will invite you in. ### Setup ### -It's easy to get started with Pixi.js! Simply grab the pre-built versions from here: - -Release Branch - Nice and stable Pixi.js -- Unminified: [http://pixijs.download/release/pixi.js] -- Minified: [http://pixijs.download/release/pixi.min.js] - -Develop Branch - The bleeding edge version of Pixi.js -- Unminified: [http://pixijs.download/dev/pixi.js] -- Minified: [http://pixijs.download/dev/pixi.min.js] +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. @@ -70,10 +62,10 @@ #### CDN Install (via cdnjs) ```html - + ``` -_Note: `4.2.2` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ +_Note: `4.4.3` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ ### Demos ### @@ -130,7 +122,7 @@ // and the root stage PIXI.Container. var app = new PIXI.Application(); -// The application will create a canvas element for you that you +// The application will create a canvas element for you that you // can then insert into the DOM. document.body.appendChild(app.view); @@ -150,7 +142,7 @@ // Add the bunny to the scene we are building. app.stage.addChild(bunny); - + // Listen for frame updates app.ticker.add(function() { // each frame we spin the bunny around a bit diff --git a/bower.json b/bower.json deleted file mode 100644 index 41b668c..0000000 --- a/bower.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "pixi.js", - "main": "dist/pixi.min.js", - "ignore": [ - "**/.*", - "docs", - "node_modules", - "bower_components", - "test", - "scripts", - "bower.json", - "inch.json" - ], - "dependencies": { - }, - "devDependencies": { - } -} diff --git a/package.json b/package.json index d35cbb2..308469f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.3.5", + "version": "4.4.4", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ @@ -40,11 +40,11 @@ "lib": "babel src --out-dir lib -s", "predocs": "rimraf docs/**", "docs": "jsdoc -c scripts/jsdoc.conf.json -R README.md", - "publish:patch": "npm version patch --no-git-tag-version && npm publish", - "publish:minor": "npm version minor --no-git-tag-version && npm publish", - "publish:major": "npm version major --no-git-tag-version && npm publish", + "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", - "postpublish": "node scripts/release.js" + "postpublish": "git push && git push --tags" }, "files": [ "dist/", @@ -72,7 +72,6 @@ "electron": "^1.4.15", "eslint": "^3.5.0", "floss": "^2.0.1", - "gh-pages": "^0.11.0", "jaguarjs-jsdoc": "^1.0.1", "js-md5": "^0.4.1", "jsdoc": "^3.4.2", diff --git a/scripts/jsdoc.conf.json b/scripts/jsdoc.conf.json index 553e61c..3f52180 100644 --- a/scripts/jsdoc.conf.json +++ b/scripts/jsdoc.conf.json @@ -43,7 +43,7 @@ }, "markdown" : { "parser" : "gfm", - "hardwrap" : true + "hardwrap" : false }, "opts": { "encoding" : "utf8", diff --git a/scripts/release.js b/scripts/release.js deleted file mode 100644 index 156620c..0000000 --- a/scripts/release.js +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env node -'use strict'; - -// Publish script to push releases of the bin files -// the normally are gitignored -const ghpages = require('gh-pages'); -const path = require('path'); -const packageInfo = require(path.join(__dirname, '..', 'package.json')); - -const options = { - src: [ - 'dist/**/*', - 'lib/**/*', - 'src/**/*', - 'scripts/*', - 'scripts/renders/*', - 'test/**/*', - '*.json', - '*.md', - 'LICENSE', - '.eslintrc', - '.editorconfig', - '.travis.yml', - '.babelrc', - ], - dotfiles: true, - branch: 'release', - message: packageInfo.version, - logger: console.log.bind(console), -}; - -ghpages.publish(process.cwd(), options, (err) => -{ - if (err) - { - console.log(err); - process.exit(1); - - return; - } - - process.exit(0); -}); diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index f16bcb1..220427a 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -21,7 +21,7 @@ const DIV_HOOK_ZINDEX = 2; /** - * The Accessibility manager reacreates the ability to tab and and have content read by screen + * The Accessibility manager recreates the ability to tab and and have content read by screen * readers. This is very important as it can possibly help people with disabilities access pixi * content. * diff --git a/src/accessibility/index.js b/src/accessibility/index.js index 568cfb1..0ae108f 100644 --- a/src/accessibility/index.js +++ b/src/accessibility/index.js @@ -1,4 +1,9 @@ /** + * This namespace contains a renderer plugin for interaction accessibility for end-users + * with physical impairments which require screen-renders, keyboard navigation, etc. + * + * Do not instantiate this plugin directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. * @namespace PIXI.accessibility */ export { default as accessibleTarget } from './accessibleTarget'; diff --git a/src/core/Application.js b/src/core/Application.js index 5340578..2fcc729 100644 --- a/src/core/Application.js +++ b/src/core/Application.js @@ -1,6 +1,8 @@ import { autoDetectRenderer } from './autoDetectRenderer'; import Container from './display/Container'; import { shared, Ticker } from './ticker'; +import settings from './settings'; +import { UPDATE_PRIORITY } from './const'; /** * Convenience class to create a new PIXI application. @@ -22,28 +24,52 @@ */ export default class Application { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experience unexplained flickering try setting this to true. - * @param {boolean} [useSharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedLoader=false] - `true` to use PIXI.loaders.shared, `false` to create new Loader. */ - constructor(width, height, options, noWebGL, useSharedTicker = false) + constructor(options, arg2, arg3, arg4, arg5) { + // Support for constructor(width, height, options, noWebGL, useSharedTicker) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + forceCanvas: !!arg4, + sharedTicker: !!arg5, + }, arg3); + } + + /** + * The default options, so we mixin functionality later. + * @member {object} + * @protected + */ + this._options = options = Object.assign({ + sharedTicker: false, + forceCanvas: false, + sharedLoader: false, + }, options); + /** * WebGL renderer if available, otherwise CanvasRenderer * @member {PIXI.WebGLRenderer|PIXI.CanvasRenderer} */ - this.renderer = autoDetectRenderer(width, height, options, noWebGL); + this.renderer = autoDetectRenderer(options); /** * The root display container that's rendered. @@ -63,7 +89,7 @@ * @member {PIXI.ticker.Ticker} * @default PIXI.ticker.shared */ - this.ticker = useSharedTicker ? shared : new Ticker(); + this.ticker = options.sharedTicker ? shared : new Ticker(); // Start the rendering this.start(); @@ -78,7 +104,7 @@ this._ticker = ticker; if (ticker) { - ticker.add(this.render, this); + ticker.add(this.render, this, UPDATE_PRIORITY.LOW); } } get ticker() // eslint-disable-line require-jsdoc @@ -136,13 +162,18 @@ */ destroy(removeView) { - this.stop(); + const oldTicker = this._ticker; + this.ticker = null; + oldTicker.destroy(); + this.stage.destroy(); this.stage = null; this.renderer.destroy(removeView); this.renderer = null; + + this._options = null; } } diff --git a/src/core/autoDetectRenderer.js b/src/core/autoDetectRenderer.js index 8960425..9ec0364 100644 --- a/src/core/autoDetectRenderer.js +++ b/src/core/autoDetectRenderer.js @@ -2,6 +2,7 @@ import CanvasRenderer from './renderers/canvas/CanvasRenderer'; import WebGLRenderer from './renderers/webgl/WebGLRenderer'; +// eslint-disable-next-line valid-jsdoc /** * This helper function will automatically detect which renderer you should be using. * WebGL is the preferred renderer as it is a lot faster. If webGL is not supported by @@ -9,24 +10,32 @@ * * @memberof PIXI * @function autoDetectRenderer - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @return {PIXI.WebGLRenderer|PIXI.CanvasRenderer} Returns WebGL renderer if available, otherwise CanvasRenderer */ -export function autoDetectRenderer(width = 800, height = 600, options, noWebGL) +export function autoDetectRenderer(options, arg1, arg2, arg3) { - if (!noWebGL && utils.isWebGLSupported()) + // Backward-compatible support for noWebGL option + let forceCanvas = options && options.forceCanvas; + + if (arg3 !== undefined) { - return new WebGLRenderer(width, height, options); + forceCanvas = arg3; } - return new CanvasRenderer(width, height, options); + if (!forceCanvas && utils.isWebGLSupported()) + { + return new WebGLRenderer(options, arg1, arg2); + } + + return new CanvasRenderer(options, arg1, arg2); } diff --git a/src/core/const.js b/src/core/const.js index b9e99ae..2c68dee 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -341,3 +341,27 @@ LINEAR_VERTICAL: 0, LINEAR_HORIZONTAL: 1, }; + +/** + * Represents the update priorities used by internal PIXI classes when registered with + * the {@link PIXI.ticker.Ticker} object. Higher priority items are updated first and lower + * priority items, such as render, should go later. + * + * @static + * @constant + * @name UPDATE_PRIORITY + * @memberof PIXI + * @type {object} + * @property {number} INTERACTION=50 Highest priority, used for {@link PIXI.interaction.InteractionManager} + * @property {number} HIGH=25 High priority updating, {@link PIXI.VideoBaseTexture} and {@link PIXI.extras.AnimatedSprite} + * @property {number} NORMAL=0 Default priority for ticker events, see {@link PIXI.ticker.Ticker#add}. + * @property {number} LOW=-25 Low priority used for {@link PIXI.Application} rendering. + * @property {number} UTILITY=-50 Lowest priority used for {@link PIXI.prepare.BasePrepare} utility. + */ +export const UPDATE_PRIORITY = { + INTERACTION: 50, + HIGH: 25, + NORMAL: 0, + LOW: -25, + UTILITY: -50, +}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 4e6c77d..decb42a 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -13,7 +13,6 @@ * * @class * @extends EventEmitter - * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ export default class DisplayObject extends EventEmitter @@ -113,6 +112,29 @@ * @private */ this._mask = null; + + /** + * If the object has been destroyed via destroy(). If true, it should not be used. + * + * @member {boolean} + * @private + * @readonly + */ + this._destroyed = false; + + /** + * Fired when this DisplayObject is added to a Container. + * + * @event PIXI.DisplayObject#added + * @param {PIXI.Container} container - The container added to. + */ + + /** + * Fired when this DisplayObject is removed from a Container. + * + * @event PIXI.DisplayObject#removed + * @param {PIXI.Container} container - The container removed from. + */ } /** @@ -403,6 +425,8 @@ this.interactive = false; this.interactiveChildren = false; + + this._destroyed = true; } /** diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index aafdfe2..f787c4a 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -792,8 +792,8 @@ sprite.worldAlpha = this.worldAlpha * sprite.alpha; sprite.blendMode = this.blendMode; - sprite.texture._frame.width = rect.width; - sprite.texture._frame.height = rect.height; + sprite._texture._frame.width = rect.width; + sprite._texture._frame.height = rect.height; sprite.transform.worldTransform = this.transform.worldTransform; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index a24c775..b9f192d 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -19,11 +19,12 @@ */ export default class SystemRenderer extends EventEmitter { + // eslint-disable-next-line valid-jsdoc /** * @param {string} system - The name of the system this renderer is for. - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -37,27 +38,31 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(system, screenWidth, screenHeight, options) + constructor(system, options, arg2, arg3) { super(); sayHello(system); - // prepare options - if (options) + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') { - for (const i in settings.RENDER_OPTIONS) - { - if (typeof options[i] === 'undefined') - { - options[i] = settings.RENDER_OPTIONS[i]; - } - } + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); } - else - { - options = settings.RENDER_OPTIONS; - } + + // Add the default render options + options = Object.assign({}, settings.RENDER_OPTIONS, options); + + /** + * The supplied constructor options. + * + * @member {Object} + * @readOnly + */ + this.options = options; /** * The type of the renderer. @@ -75,7 +80,7 @@ * * @member {PIXI.Rectangle} */ - this.screen = new Rectangle(0, 0, screenWidth || 800, screenHeight || 600); + this.screen = new Rectangle(0, 0, options.width, options.height); /** * The canvas element that everything is drawn to @@ -279,6 +284,8 @@ this.blendModes = null; + this.options = null; + this.preserveDrawingBuffer = false; this.clearBeforeRender = false; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index e84e49c..691caf0 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -17,10 +17,11 @@ */ export default class CanvasRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -34,9 +35,9 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('Canvas', screenWidth, screenHeight, options); + super('Canvas', options, arg2, arg3); this.type = RENDERER_TYPE.CANVAS; @@ -96,7 +97,19 @@ this.context = null; this.renderingToScreen = false; - this.resize(screenWidth, screenHeight); + this.resize(this.options.width, this.options.height); + + /** + * Fired after rendering finishes. + * + * @event PIXI.CanvasRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.CanvasRenderer#prerender + */ } /** @@ -299,4 +312,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.CanvasRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.CanvasExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.CanvasPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.CanvasRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index e1cf7cd..f6fe123 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -31,11 +31,12 @@ */ export default class WebGLRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** * - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -55,9 +56,9 @@ * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('WebGL', screenWidth, screenHeight, options); + super('WebGL', options, arg2, arg3); /** * The type of this renderer as a standardised const @@ -89,12 +90,6 @@ }; - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ this._backgroundColorRgba[3] = this.transparent ? 0 : 1; this.globalUniforms = new UniformGroup({ @@ -117,6 +112,13 @@ this.initPlugins(); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ if(options.context) { this.context.initFromContext(options.context); @@ -169,6 +171,25 @@ } return this; + + /** + * Fired after rendering finishes. + * + * @event PIXI.WebGLRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.WebGLRenderer#prerender + */ + + /** + * Fired when the WebGL context is set. + * + * @event PIXI.WebGLRenderer#context + * @param {WebGLRenderingContext} gl - WebGL context. + */ } /** @@ -295,4 +316,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.WebGLRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.WebGLRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(WebGLRenderer); diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index 4eb6128..fa3d883 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -47,6 +47,14 @@ * @member {boolean} */ this.enabled = true; + + /** + * If enabled, pixi will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; } /** diff --git a/src/core/renderers/webgl/systems/ContextSystem.js b/src/core/renderers/webgl/systems/ContextSystem.js index ff958c7..8df85ea 100644 --- a/src/core/renderers/webgl/systems/ContextSystem.js +++ b/src/core/renderers/webgl/systems/ContextSystem.js @@ -75,7 +75,8 @@ */ createContext(canvas, options) { - var gl = canvas.getContext('webgl', options) || + var gl = //canvas.getContext('webgl2', options) || + canvas.getContext('webgl', options) || canvas.getContext('experimental-webgl', options); if (!gl) diff --git a/src/core/renderers/webgl/systems/FilterSystem.js b/src/core/renderers/webgl/systems/FilterSystem.js index f47335d..37e28d2 100644 --- a/src/core/renderers/webgl/systems/FilterSystem.js +++ b/src/core/renderers/webgl/systems/FilterSystem.js @@ -111,7 +111,7 @@ // TODO we should fit the rect around the transform.. } - else + else if (filters[0].autoFit) { sourceFrame.fit(filterData.stack[0].destinationFrame); } diff --git a/src/core/renderers/webgl/systems/shader/shader/mapType.js b/src/core/renderers/webgl/systems/shader/shader/mapType.js index 581dace..917538a 100644 --- a/src/core/renderers/webgl/systems/shader/shader/mapType.js +++ b/src/core/renderers/webgl/systems/shader/shader/mapType.js @@ -15,7 +15,7 @@ } } - return GL_TABLE[type]; + return GL_TABLE[type]; }; var GL_TABLE = null; diff --git a/src/core/settings.js b/src/core/settings.js index 18c5877..38ef45c 100644 --- a/src/core/settings.js +++ b/src/core/settings.js @@ -2,6 +2,15 @@ import canUploadSameBuffer from './utils/canUploadSameBuffer'; /** + * User's customizable globals for overriding the default PIXI settings, such + * as a renderer's default resolution, framerate, float percision, etc. + * @example + * // Use the native window resolution as the default resolution + * // will support high-density displays when rendering + * PIXI.settings.RESOLUTION = window.devicePixelRatio. + * + * // Disable interpolation when scaling, will make texture be pixelated + * PIXI.settings.SCALE_MODE = PIXI.SCALE_MODES.NEAREST; * @namespace PIXI.settings */ export default { @@ -101,6 +110,9 @@ * @property {boolean} clearBeforeRender=true * @property {boolean} preserveDrawingBuffer=false * @property {boolean} roundPixels=false + * @property {number} width=800 + * @property {number} height=600 + * @property {boolean} legacy=false */ RENDER_OPTIONS: { view: null, @@ -112,6 +124,9 @@ clearBeforeRender: true, preserveDrawingBuffer: false, roundPixels: false, + width: 800, + height: 600, + legacy: false, }, /** diff --git a/src/core/shader/Program.js b/src/core/shader/Program.js index c0b538b..07b4398 100644 --- a/src/core/shader/Program.js +++ b/src/core/shader/Program.js @@ -1,6 +1,6 @@ import extractUniformsFromSrc from './extractUniformsFromSrc'; import generateUniformsSync from './generateUniformsSync'; -import glCore from 'pixi-gl-core'; +import shaderUtils from '../renderers/webgl/systems/shader/shader'; import { ProgramCache } from '../utils'; import getTestContext from '../utils/getTestContext'; @@ -64,14 +64,17 @@ } else { - vertexSrc = glCore.shader.setPrecision(vertexSrc, 'mediump'); - fragmentSrc = glCore.shader.setPrecision(fragmentSrc, 'mediump'); + vertexSrc = shaderUtils.setPrecision(vertexSrc, 'mediump'); + fragmentSrc = shaderUtils.setPrecision(fragmentSrc, 'mediump'); - const program = glCore.shader.compileProgram(gl, vertexSrc, fragmentSrc); + const program = shaderUtils.compileProgram(gl, vertexSrc, fragmentSrc); + console.log("<<<<<<>>><<"); this.attributeData = this.getAttributeData(program, gl); this.uniformData = this.getUniformData(program, gl); + console.log(this.uniformData); + //gl.deleteProgram(program); } } @@ -95,13 +98,13 @@ for (let i = 0; i < totalAttributes; i++) { const attribData = gl.getActiveAttrib(program, i); - const type = glCore.shader.mapType(gl, attribData.type); + const type = shaderUtils.mapType(gl, attribData.type); /*eslint-disable */ const data = { type: type, name: attribData.name, - size: glCore.shader.mapSize(type), + size: shaderUtils.mapSize(type), location: 0, }; /*eslint-enable */ @@ -137,13 +140,14 @@ // TODO expose this as a prop? // const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); - const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); + //const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); for (let i = 0; i < totalUniforms; i++) { const uniformData = gl.getActiveUniform(program, i); const name = uniformData.name.replace(/\[.*?\]/, ''); - const type = glCore.shader.mapType(gl, uniformData.type); + const type = shaderUtils.mapType(gl, uniformData.type); + console.log('mapping ' + uniformData.type + ' to ' + type); // if (!name.match(maskRegex)) { @@ -151,7 +155,7 @@ uniforms[name] = { type: type, size: uniformData.size, - value: glCore.shader.defaultValue(type, uniformData.size), + value: shaderUtils.defaultValue(type, uniformData.size), }; /*eslint-enable */ } diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index e387e99..b57d095 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -147,12 +147,12 @@ // so if _width is 0 then width was not set.. if (this._width) { - this.scale.x = sign(this.scale.x) * this._width / this.texture.orig.width; + this.scale.x = sign(this.scale.x) * this._width / this._texture.orig.width; } if (this._height) { - this.scale.y = sign(this.scale.y) * this._height / this.texture.orig.height; + this.scale.y = sign(this.scale.y) * this._height / this._texture.orig.height; } } diff --git a/src/core/sprites/canvas/CanvasTinter.js b/src/core/sprites/canvas/CanvasTinter.js index 27e5e0c..fd9de5d 100644 --- a/src/core/sprites/canvas/CanvasTinter.js +++ b/src/core/sprites/canvas/CanvasTinter.js @@ -18,7 +18,7 @@ */ getTintedTexture: (sprite, color) => { - const texture = sprite.texture; + const texture = sprite._texture; color = CanvasTinter.roundColor(color); diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 5a4edda..8b9b797 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -344,6 +344,7 @@ const texture = this._texture; const style = this._style; + const padding = style.trim ? 0 : style.padding; texture.baseTexture.valid = true; texture.baseTexture.resolution = this.resolution; @@ -353,11 +354,11 @@ texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - texture.trim.x = -style.padding; - texture.trim.y = -style.padding; + texture.trim.x = -padding; + texture.trim.y = -padding; - texture.orig.width = texture._frame.width - (style.padding * 2); - texture.orig.height = texture._frame.height - (style.padding * 2); + texture.orig.width = texture._frame.width - (padding * 2); + texture.orig.height = texture._frame.height - (padding * 2); // call sprite onTextureUpdate to update scale if _width or _height were set this._onTextureUpdate(); diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index 853da3d..8a9c34b 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -114,6 +114,38 @@ this.cacheId = null; this.validate(); + + /** + * Fired when a not-immediately-available source finishes loading. + * + * @protected + * @event PIXI.BaseTexture#loaded + * @param {PIXI.BaseTexture} baseTexture - Resource loaded. + */ + + /** + * Fired when a not-immediately-available source fails to load. + * + * @protected + * @event PIXI.BaseTexture#error + * @param {PIXI.BaseTexture} baseTexture - Resource errored. + */ + + /** + * Fired when BaseTexture is updated. + * + * @protected + * @event PIXI.BaseTexture#update + * @param {PIXI.BaseTexture} baseTexture - Instance of texture being updated. + */ + + /** + * Fired when BaseTexture is destroyed. + * + * @protected + * @event PIXI.BaseTexture#dispose + * @param {PIXI.BaseTexture} baseTexture - Instance of texture being destroyed. + */ } setResource(resource) diff --git a/src/core/textures/Spritesheet.js b/src/core/textures/Spritesheet.js index d469c62..9b24f27 100644 --- a/src/core/textures/Spritesheet.js +++ b/src/core/textures/Spritesheet.js @@ -208,6 +208,7 @@ // lets also add the frame to pixi's global cache for fromFrame and fromImage functions TextureCache[i] = this.textures[i]; + this.textures[i].textureCacheId = i; } frameIndex++; diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 0a2c379..2213087 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -147,9 +147,9 @@ /** * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. * - * @event update - * @memberof PIXI.Texture# + * @event PIXI.Texture#update * @protected + * @param {PIXI.Texture} texture - Instance of texture being updated. */ this._updateID = 0; @@ -159,6 +159,15 @@ * @type {Object} */ this.transform = null; + + /** + * The id under which this Texture has been added to the texture cache. This is + * automatically set in certain cases, but may not always be accurate, particularly if + * the texture is in the cache under multiple ids. + * + * @member {string} + */ + this.textureCacheId = null; } /** @@ -225,11 +234,17 @@ this._uvs = null; this.trim = null; this.orig = null; + this.textureCacheId = null; this.valid = false; - this.off('dispose', this.dispose, this); - this.off('update', this.update, this); + for (const prop in TextureCache) + { + if (TextureCache[prop] === this) + { + delete TextureCache[prop]; + } + } } /** @@ -315,14 +330,15 @@ */ static fromLoader(source, imageUrl, name) { - // console.log('added from loader...') + // console.log('added from loader...') const resource = new ImageResource(source);//.from(imageUrl, crossorigin);// document.createElement('img'); - // console.log('base resource ' + resource.width); + // console.log('base resource ' + resource.width); const baseTexture = new BaseTexture(resource, settings.SCALE_MODE, getResolutionOfUrl(imageUrl)); + /// console.log('base width ' + baseTexture.width); const texture = new Texture(baseTexture); @@ -335,6 +351,7 @@ // lets also add the frame to pixi's global cache for fromFrame and fromImage fucntions BaseTextureCache[name] = baseTexture; TextureCache[name] = texture; + texture.textureCacheId = name; // also add references by url if they are different. if (name !== imageUrl) @@ -355,6 +372,10 @@ */ static addTextureToCache(texture, id) { + if (!texture.textureCacheId) + { + texture.textureCacheId = id; + } TextureCache[id] = texture; } @@ -495,6 +516,7 @@ */ Texture.EMPTY = new Texture(new BaseTexture()); removeAllHandlers(Texture.EMPTY); +removeAllHandlers(Texture.EMPTY.baseTexture); /** * A white texture of 10x10 size, used for graphics and other things @@ -505,3 +527,4 @@ */ Texture.WHITE = createWhiteTexture(); removeAllHandlers(Texture.WHITE); +removeAllHandlers(Texture.WHITE.baseTexture); diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index 59eb9fd..a9b9f51 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -1,6 +1,7 @@ import BaseTexture from './BaseTexture'; import { uid, BaseTextureCache } from '../utils'; -import * as ticker from '../ticker'; +import { shared } from '../ticker'; +import { UPDATE_PRIORITY } from '../const'; /** * A texture of a [playing] Video. @@ -125,7 +126,7 @@ if (!this._isAutoUpdating && this.autoUpdate) { - ticker.shared.add(this.update, this); + shared.add(this.update, this, UPDATE_PRIORITY.HIGH); this._isAutoUpdating = true; } } @@ -139,7 +140,7 @@ { if (this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); this._isAutoUpdating = false; } } @@ -187,7 +188,7 @@ { if (this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); } if (this.source && this.source._pixiId) @@ -281,12 +282,12 @@ if (!this._autoUpdate && this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); this._isAutoUpdating = false; } else if (this._autoUpdate && !this._isAutoUpdating) { - ticker.shared.add(this.update, this); + shared.add(this.update, this, UPDATE_PRIORITY.HIGH); this._isAutoUpdating = true; } } diff --git a/src/core/ticker/Ticker.js b/src/core/ticker/Ticker.js index 17a1517..40df138 100644 --- a/src/core/ticker/Ticker.js +++ b/src/core/ticker/Ticker.js @@ -1,12 +1,10 @@ import settings from '../settings'; -import EventEmitter from 'eventemitter3'; - -// Internal event used by composed emitter -const TICK = 'tick'; +import { UPDATE_PRIORITY } from '../const'; +import TickerListener from './TickerListener'; /** * A Ticker class that runs an update loop that other objects listen to. - * This class is composed around an EventEmitter object to add listeners + * This class is composed around listeners * meant for execution on the next requested animation frame. * Animation frames are requested only when necessary, * e.g. When the ticker is started and the emitter has listeners. @@ -22,10 +20,11 @@ constructor() { /** - * Internal emitter used to fire 'tick' event + * The first listener. All new listeners added are chained on this. * @private + * @type {TickerListener} */ - this._emitter = new EventEmitter(); + this._head = new TickerListener(null, null, Infinity); /** * Internal current frame request ID @@ -131,7 +130,7 @@ // Invoke listeners now this.update(time); // Listener side effects may have modified ticker state. - if (this.started && this._requestId === null && this._emitter.listeners(TICK, true)) + if (this.started && this._requestId === null && this._head.next) { this._requestId = requestAnimationFrame(this._tick); } @@ -148,7 +147,7 @@ */ _requestIfNeeded() { - if (this._requestId === null && this._emitter.listeners(TICK, true)) + if (this._requestId === null && this._head.next) { // ensure callbacks get correct delta this.lastTime = performance.now(); @@ -193,35 +192,72 @@ } /** - * Calls {@link module:eventemitter3.EventEmitter#on} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. + * Register a handler for tick events. Calls continuously unless + * it is removed or the ticker is stopped. * * @param {Function} fn - The listener function to be added for updates * @param {Function} [context] - The listener context + * @param {number} [priority=PIXI.UPDATE_PRIORITY.NORMAL] - The priority for emitting * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - add(fn, context) + add(fn, context, priority = UPDATE_PRIORITY.NORMAL) { - this._emitter.on(TICK, fn, context); - - this._startIfPossible(); - - return this; + return this._addListener(new TickerListener(fn, context, priority)); } /** - * Calls {@link module:eventemitter3.EventEmitter#once} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. + * Add a handler for the tick event which is only execute once. * * @param {Function} fn - The listener function to be added for one update * @param {Function} [context] - The listener context + * @param {number} [priority=PIXI.UPDATE_PRIORITY.NORMAL] - The priority for emitting * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - addOnce(fn, context) + addOnce(fn, context, priority = UPDATE_PRIORITY.NORMAL) { - this._emitter.once(TICK, fn, context); + return this._addListener(new TickerListener(fn, context, priority, true)); + } + + /** + * Internally adds the event handler so that it can be sorted by priority. + * Priority allows certain handler (user, AnimatedSprite, Interaction) to be run + * before the rendering. + * + * @private + * @param {TickerListener} listener - Current listener being added. + * @returns {PIXI.ticker.Ticker} This instance of a ticker + */ + _addListener(listener) + { + // For attaching to head + let current = this._head.next; + let previous = this._head; + + // Add the first item + if (!current) + { + listener.connect(previous); + } + else + { + // Go from highest to lowest priority + while (current) + { + if (listener.priority >= current.priority) + { + listener.connect(previous); + break; + } + previous = current; + current = current.next; + } + + // Not yet connected + if (!listener.previous) + { + listener.connect(previous); + } + } this._startIfPossible(); @@ -229,19 +265,33 @@ } /** - * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. - * It checks if the emitter has listeners for 'tick' event. - * If it does, then it cancels the animation frame. + * Removes any handlers matching the function and context parameters. + * If no handlers are left after removing, then it cancels the animation frame. * - * @param {Function} [fn] - The listener function to be removed + * @param {Function} fn - The listener function to be removed * @param {Function} [context] - The listener context to be removed * @returns {PIXI.ticker.Ticker} This instance of a ticker */ remove(fn, context) { - this._emitter.off(TICK, fn, context); + let listener = this._head.next; - if (!this._emitter.listeners(TICK, true)) + while (listener) + { + // We found a match, lets remove it + // no break to delete all possible matches + // incase a listener was added 2+ times + if (listener.match(fn, context)) + { + listener = listener.destroy(); + } + else + { + listener = listener.next; + } + } + + if (!this._head.next) { this._cancelIfNeeded(); } @@ -276,6 +326,25 @@ } /** + * Destroy the ticker and don't use after this. Calling + * this method removes all references to internal events. + */ + destroy() + { + this.stop(); + + let listener = this._head.next; + + while (listener) + { + listener = listener.destroy(true); + } + + this._head.destroy(); + this._head = null; + } + + /** * Triggers an update. An update entails setting the * current {@link PIXI.ticker.Ticker#elapsedMS}, * the current {@link PIXI.ticker.Ticker#deltaTime}, @@ -320,8 +389,22 @@ this.deltaTime = elapsedMS * settings.TARGET_FPMS * this.speed; + // Cache a local reference, in-case ticker is destroyed + // during the emit, we can still check for head.next + const head = this._head; + // Invoke listeners added to internal emitter - this._emitter.emit(TICK, this.deltaTime); + let listener = head.next; + + while (listener) + { + listener = listener.emit(this.deltaTime); + } + + if (!head.next) + { + this._cancelIfNeeded(); + } } else { diff --git a/src/core/ticker/TickerListener.js b/src/core/ticker/TickerListener.js new file mode 100644 index 0000000..2bedb34 --- /dev/null +++ b/src/core/ticker/TickerListener.js @@ -0,0 +1,158 @@ +/** + * Internal class for handling the priority sorting of ticker handlers. + * + * @private + * @class + * @memberof PIXI.ticker + */ +export default class TickerListener +{ + /** + * Constructor + * + * @param {Function} fn - The listener function to be added for one update + * @param {Function} [context=null] - The listener context + * @param {number} [priority=0] - The priority for emitting + * @param {boolean} [once=false] - If the handler should fire once + */ + constructor(fn, context = null, priority = 0, once = false) + { + /** + * The handler function to execute. + * @member {Function} + */ + this.fn = fn; + + /** + * The calling to execute. + * @member {Function} + */ + this.context = context; + + /** + * The current priority. + * @member {number} + */ + this.priority = priority; + + /** + * If this should only execute once. + * @member {boolean} + */ + this.once = once; + + /** + * The next item in chain. + * @member {TickerListener} + */ + this.next = null; + + /** + * The previous item in chain. + * @member {TickerListener} + */ + this.previous = null; + + /** + * `true` if this listener has been destroyed already. + * @member {boolean} + * @private + */ + this._destroyed = false; + } + + /** + * Simple compare function to figure out if a function and context match. + * + * @param {Function} fn - The listener function to be added for one update + * @param {Function} context - The listener context + * @return {boolean} `true` if the listener match the arguments + */ + match(fn, context) + { + context = context || null; + + return this.fn === fn && this.context === context; + } + + /** + * Emit by calling the current function. + * @param {number} deltaTime - time since the last emit. + * @return {TickerListener} Next ticker + */ + emit(deltaTime) + { + if (this.context) + { + this.fn.call(this.context, deltaTime); + } + else + { + this.fn(deltaTime); + } + + if (this.once) + { + this.destroy(); + } + + const redirect = this.next; + + // Soft-destroying should remove + // the next reference + if (this._destroyed) + { + this.next = null; + } + + return redirect; + } + + /** + * Connect to the list. + * @param {TickerListener} previous - Input node, previous listener + */ + connect(previous) + { + this.previous = previous; + if (previous.next) + { + previous.next.previous = this; + } + this.next = previous.next; + previous.next = this; + } + + /** + * Destroy and don't use after this. + * @param {boolean} [hard = false] `true` to remove the `next` reference, this + * is considered a hard destroy. Soft destroy maintains the next reference. + * @return {TickerListener} The listener to redirect while emitting or removing. + */ + destroy(hard = false) + { + this._destroyed = true; + this.fn = null; + this.context = null; + + // Disconnect, hook up next and previous + if (this.previous) + { + this.previous.next = this.next; + } + + if (this.next) + { + this.next.previous = this.previous; + } + + // Redirect to the next item + const redirect = this.previous; + + // Remove references + this.next = hard ? null : redirect; + this.previous = null; + + return redirect; + } +} diff --git a/src/core/ticker/index.js b/src/core/ticker/index.js index 4b3017c..c3020d4 100644 --- a/src/core/ticker/index.js +++ b/src/core/ticker/index.js @@ -45,8 +45,25 @@ const shared = new Ticker(); shared.autoStart = true; +shared.destroy = () => +{ + // protect destroying shared ticker + // this is used by other internal systems + // like AnimatedSprite and InteractionManager +}; /** + * This namespace contains an API for interacting with PIXI's internal global update loop. + * + * This ticker is used for rendering, {@link PIXI.extras.AnimatedSprite AnimatedSprite}, + * {@link PIXI.interaction.InteractionManager InteractionManager} and many other time-based PIXI systems. + * @example + * const ticker = new PIXI.ticker.Ticker(); + * ticker.stop(); + * ticker.add((deltaTime) => { + * // do something every frame + * }); + * ticker.start(); * @namespace PIXI.ticker */ export { shared, Ticker }; diff --git a/src/core/utils/index.js b/src/core/utils/index.js index be44750..7441f7b 100644 --- a/src/core/utils/index.js +++ b/src/core/utils/index.js @@ -9,6 +9,21 @@ let saidHello = false; /** + * Generalized convenience utilities for PIXI. + * @example + * // Extend PIXI's internal Event Emitter. + * class MyEmitter extends PIXI.utils.EventEmitter { + * constructor() { + * super(); + * console.log("Emitter created!"); + * } + * } + * + * // Get info on current device + * console.log(PIXI.utils.isMobile); + * + * // Convert hex color to string + * console.log(PIXI.utils.hex2string(0xff00ff)); // returns: "#ff00ff" * @namespace PIXI.utils */ export { diff --git a/src/deprecation.js b/src/deprecation.js index b9c73b3..33daeec 100644 --- a/src/deprecation.js +++ b/src/deprecation.js @@ -918,6 +918,35 @@ }); /** + * @method + * @name PIXI.prepare.BasePrepare#register + * @see PIXI.prepare.BasePrepare#registerFindHook + * @deprecated since version 4.4.2 + * @param {Function} [addHook] - Function call that takes two parameters: `item:*, queue:Array` + * function must return `true` if it was able to add item to the queue. + * @param {Function} [uploadHook] - Function call that takes two parameters: `prepare:CanvasPrepare, item:*` and + * function must return `true` if it was able to handle upload of item. + * @return {PIXI.BasePrepare} Instance of plugin for chaining. + */ +prepare.BasePrepare.prototype.register = function register(addHook, uploadHook) +{ + warn('renderer.plugins.prepare.register is now deprecated, ' + + 'please use renderer.plugins.prepare.registerFindHook & renderer.plugins.prepare.registerUploadHook'); + + if (addHook) + { + this.registerFindHook(addHook); + } + + if (uploadHook) + { + this.registerUploadHook(uploadHook); + } + + return this; +}; + +/** * The number of graphics or textures to upload to the GPU. * * @name PIXI.prepare.canvas.UPLOADS_PER_FRAME @@ -969,67 +998,73 @@ }, }); -Object.defineProperties(loaders.Resource.prototype, { - isJson: { - get() - { - warn('The isJson property is deprecated, please use `resource.type === Resource.TYPE.JSON`.'); +if (loaders.Loader) +{ + const Resource = loaders.Resource; + const Loader = loaders.Loader; - return this.type === loaders.Loader.Resource.TYPE.JSON; + Object.defineProperties(Resource.prototype, { + isJson: { + get() + { + warn('The isJson property is deprecated, please use `resource.type === Resource.TYPE.JSON`.'); + + return this.type === Resource.TYPE.JSON; + }, }, - }, - isXml: { - get() - { - warn('The isXml property is deprecated, please use `resource.type === Resource.TYPE.XML`.'); + isXml: { + get() + { + warn('The isXml property is deprecated, please use `resource.type === Resource.TYPE.XML`.'); - return this.type === loaders.Loader.Resource.TYPE.XML; + return this.type === Resource.TYPE.XML; + }, }, - }, - isImage: { - get() - { - warn('The isImage property is deprecated, please use `resource.type === Resource.TYPE.IMAGE`.'); + isImage: { + get() + { + warn('The isImage property is deprecated, please use `resource.type === Resource.TYPE.IMAGE`.'); - return this.type === loaders.Loader.Resource.TYPE.IMAGE; + return this.type === Resource.TYPE.IMAGE; + }, }, - }, - isAudio: { - get() - { - warn('The isAudio property is deprecated, please use `resource.type === Resource.TYPE.AUDIO`.'); + isAudio: { + get() + { + warn('The isAudio property is deprecated, please use `resource.type === Resource.TYPE.AUDIO`.'); - return this.type === loaders.Loader.Resource.TYPE.AUDIO; + return this.type === Resource.TYPE.AUDIO; + }, }, - }, - isVideo: { - get() - { - warn('The isVideo property is deprecated, please use `resource.type === Resource.TYPE.VIDEO`.'); + isVideo: { + get() + { + warn('The isVideo property is deprecated, please use `resource.type === Resource.TYPE.VIDEO`.'); - return this.type === loaders.Loader.Resource.TYPE.VIDEO; + return this.type === Resource.TYPE.VIDEO; + }, }, - }, -}); + }); -Object.defineProperties(loaders.Loader.prototype, { - before: { - get() - { - warn('The before() method is deprecated, please use pre().'); + Object.defineProperties(Loader.prototype, { + before: { + get() + { + warn('The before() method is deprecated, please use pre().'); - return this.pre; + return this.pre; + }, }, - }, - after: { - get() - { - warn('The after() method is deprecated, please use use().'); + after: { + get() + { + warn('The after() method is deprecated, please use use().'); - return this.use; + return this.use; + }, }, - }, -}); + }); +} /** * @name PIXI.interaction.interactiveTarget#defaultCursor diff --git a/src/extract/canvas/CanvasExtract.js b/src/extract/canvas/CanvasExtract.js index 941ca05..3ac5aee 100644 --- a/src/extract/canvas/CanvasExtract.js +++ b/src/extract/canvas/CanvasExtract.js @@ -8,7 +8,7 @@ * An instance of this class is automatically created by default, and can be found at renderer.plugins.extract * * @class - * @memberof PIXI + * @memberof PIXI.extract */ export default class CanvasExtract { @@ -21,9 +21,9 @@ /** * Collection of methods for extracting data (image, pixels, etc.) from a display object or render texture * - * @member {PIXI.CanvasExtract} extract + * @member {PIXI.extract.CanvasExtract} extract * @memberof PIXI.CanvasRenderer# - * @see PIXI.CanvasExtract + * @see PIXI.extract.CanvasExtract */ renderer.extract = this; } diff --git a/src/extract/index.js b/src/extract/index.js index 0f2170d..e9478c1 100644 --- a/src/extract/index.js +++ b/src/extract/index.js @@ -1,2 +1,22 @@ +/** + * This namespace provides renderer-specific plugins for exporting content from a renderer. + * For instance, these plugins can be used for saving an Image, Canvas element or for exporting the raw image data (pixels). + * + * Do not instantiate these plugins directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. + * @example + * // Create a new app (will auto-add extract plugin to renderer) + * const app = new PIXI.Application(); + * + * // Draw a red circle + * const graphics = new PIXI.Graphics() + * .beginFill(0xFF0000) + * .drawCircle(0, 0, 50); + * + * // Render the graphics as an HTMLImageElement + * const image = app.renderer.plugins.extract.image(graphics); + * document.body.appendChild(image); + * @namespace PIXI.extract + */ export { default as webgl } from './webgl/WebGLExtract'; export { default as canvas } from './canvas/CanvasExtract'; diff --git a/src/extract/webgl/WebGLExtract.js b/src/extract/webgl/WebGLExtract.js index 60228bb..1c3dd30 100644 --- a/src/extract/webgl/WebGLExtract.js +++ b/src/extract/webgl/WebGLExtract.js @@ -9,7 +9,7 @@ * An instance of this class is automatically created by default, and can be found at renderer.plugins.extract * * @class - * @memberof PIXI + * @memberof PIXI.extract */ export default class WebGLExtract { @@ -22,9 +22,9 @@ /** * Collection of methods for extracting data (image, pixels, etc.) from a display object or render texture * - * @member {PIXI.WebGLExtract} extract + * @member {PIXI.extract.WebGLExtract} extract * @memberof PIXI.WebGLRenderer# - * @see PIXI.WebGLExtract + * @see PIXI.extract.WebGLExtract */ renderer.extract = this; } diff --git a/src/extras/AnimatedSprite.js b/src/extras/AnimatedSprite.js index 13b804f..5297778 100644 --- a/src/extras/AnimatedSprite.js +++ b/src/extras/AnimatedSprite.js @@ -137,7 +137,7 @@ this.playing = true; if (this._autoUpdate) { - core.ticker.shared.add(this.update, this); + core.ticker.shared.add(this.update, this, core.UPDATE_PRIORITY.HIGH); } } diff --git a/src/extras/TextureTransform.js b/src/extras/TextureTransform.js index 78e6e40..16536d2 100644 --- a/src/extras/TextureTransform.js +++ b/src/extras/TextureTransform.js @@ -65,8 +65,36 @@ } /** + * Multiplies uvs array to transform + * @param {Float32Array} uvs mesh uvs + * @param {Float32Array} [out=uvs] output + * @returns {Float32Array} output + */ + multiplyUvs(uvs, out) + { + if (out === undefined) + { + out = uvs; + } + + const mat = this.mapCoord; + + for (let i = 0; i < uvs.length; i += 2) + { + const x = uvs[i]; + const y = uvs[i + 1]; + + out[i] = (x * mat.a) + (y * mat.c) + mat.tx; + out[i + 1] = (x * mat.b) + (y * mat.d) + mat.ty; + } + + return out; + } + + /** * updates matrices if texture was changed * @param {boolean} forceUpdate if true, matrices will be updated any case + * @returns {boolean} whether or not it was updated */ update(forceUpdate) { @@ -74,13 +102,13 @@ if (!tex || !tex.valid) { - return; + return false; } if (!forceUpdate && this._lastTextureID === tex._updateID) { - return; + return false; } this._lastTextureID = tex._updateID; @@ -110,5 +138,7 @@ frame[3] = (tex._frame.y + tex._frame.height - margin + offset) / texBase.height; this.uClampOffset[0] = offset / texBase.realWidth; this.uClampOffset[1] = offset / texBase.realHeight; + + return true; } } diff --git a/src/extras/TilingSprite.js b/src/extras/TilingSprite.js index 1f6014f..a3f027f 100644 --- a/src/extras/TilingSprite.js +++ b/src/extras/TilingSprite.js @@ -180,9 +180,9 @@ const transform = this.worldTransform; const resolution = renderer.resolution; const baseTexture = texture.baseTexture; - const baseTextureResolution = texture.baseTexture.resolution; - const modX = (this.tilePosition.x / this.tileScale.x) % texture._frame.width; - const modY = (this.tilePosition.y / this.tileScale.y) % texture._frame.height; + const baseTextureResolution = baseTexture.resolution; + const modX = ((this.tilePosition.x / this.tileScale.x) % texture._frame.width) * baseTextureResolution; + const modY = ((this.tilePosition.y / this.tileScale.y) % texture._frame.height) * baseTextureResolution; // create a nice shiny pattern! // TODO this needs to be refreshed if texture changes.. diff --git a/src/extras/cacheAsBitmap.js b/src/extras/cacheAsBitmap.js index 5dd5640..aea06a8 100644 --- a/src/extras/cacheAsBitmap.js +++ b/src/extras/cacheAsBitmap.js @@ -227,7 +227,16 @@ this.transform._parentID = -1; // restore the transform of the cached sprite to avoid the nasty flicker.. - this.updateTransform(); + if (!this.parent) + { + this.parent = renderer._tempDisplayObjectParent; + this.updateTransform(); + this.parent = null; + } + else + { + this.updateTransform(); + } // map the hit test.. this.containsPoint = cachedSprite.containsPoint.bind(cachedSprite); @@ -314,7 +323,17 @@ cachedSprite._bounds = this._bounds; cachedSprite.alpha = cacheAlpha; - this.updateTransform(); + if (!this.parent) + { + this.parent = renderer._tempDisplayObjectParent; + this.updateTransform(); + this.parent = null; + } + else + { + this.updateTransform(); + } + this.updateTransform = this.displayObjectUpdateTransform; this._cacheData.sprite = cachedSprite; @@ -358,9 +377,12 @@ * Destroys the cached object. * * @private + * @param {object|boolean} [options] - Options parameter. A boolean will act as if all options + * have been set to that value. + * Used when destroying containers, see the Container.destroy method. */ -DisplayObject.prototype._cacheAsBitmapDestroy = function _cacheAsBitmapDestroy() +DisplayObject.prototype._cacheAsBitmapDestroy = function _cacheAsBitmapDestroy(options) { this.cacheAsBitmap = false; - this.destroy(); + this.destroy(options); }; diff --git a/.travis.yml b/.travis.yml index 3df8b22..14de334 100644 --- a/.travis.yml +++ b/.travis.yml @@ -110,3 +110,43 @@ on: all_branches: true condition: $TRAVIS_TAG + # Deploy config for latest release + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: dist + upload-dir: "release" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: docs + upload-dir: "release/docs" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: coverage + upload-dir: "release/coverage" + on: + all_branches: true + condition: $TRAVIS_TAG \ No newline at end of file diff --git a/README.md b/README.md index d26fadd..3b85f85 100644 --- a/README.md +++ b/README.md @@ -36,22 +36,14 @@ - Wiki: Other misc tutorials and resources are [on the Wiki](https://github.com/pixijs/pixi.js/wiki/Resources). ### Community ### -- Forums: Check out the [forum] (http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow] (http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. +- Forums: Check out the [forum](http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow](http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. - Inspiration: Check out the [gallery](http://www.pixijs.com/gallery) to see some of the amazing things people have created! - Chat: You can join us on [Gitter](https://gitter.im/pixijs/pixi.js) To chat about Pixi. We also now have a Slack channel. If you would like to join it please Send me an email (mat@goodboydigital.com) and I will invite you in. ### Setup ### -It's easy to get started with Pixi.js! Simply grab the pre-built versions from here: - -Release Branch - Nice and stable Pixi.js -- Unminified: [http://pixijs.download/release/pixi.js] -- Minified: [http://pixijs.download/release/pixi.min.js] - -Develop Branch - The bleeding edge version of Pixi.js -- Unminified: [http://pixijs.download/dev/pixi.js] -- Minified: [http://pixijs.download/dev/pixi.min.js] +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. @@ -70,10 +62,10 @@ #### CDN Install (via cdnjs) ```html - + ``` -_Note: `4.2.2` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ +_Note: `4.4.3` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ ### Demos ### @@ -130,7 +122,7 @@ // and the root stage PIXI.Container. var app = new PIXI.Application(); -// The application will create a canvas element for you that you +// The application will create a canvas element for you that you // can then insert into the DOM. document.body.appendChild(app.view); @@ -150,7 +142,7 @@ // Add the bunny to the scene we are building. app.stage.addChild(bunny); - + // Listen for frame updates app.ticker.add(function() { // each frame we spin the bunny around a bit diff --git a/bower.json b/bower.json deleted file mode 100644 index 41b668c..0000000 --- a/bower.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "pixi.js", - "main": "dist/pixi.min.js", - "ignore": [ - "**/.*", - "docs", - "node_modules", - "bower_components", - "test", - "scripts", - "bower.json", - "inch.json" - ], - "dependencies": { - }, - "devDependencies": { - } -} diff --git a/package.json b/package.json index d35cbb2..308469f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.3.5", + "version": "4.4.4", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ @@ -40,11 +40,11 @@ "lib": "babel src --out-dir lib -s", "predocs": "rimraf docs/**", "docs": "jsdoc -c scripts/jsdoc.conf.json -R README.md", - "publish:patch": "npm version patch --no-git-tag-version && npm publish", - "publish:minor": "npm version minor --no-git-tag-version && npm publish", - "publish:major": "npm version major --no-git-tag-version && npm publish", + "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", - "postpublish": "node scripts/release.js" + "postpublish": "git push && git push --tags" }, "files": [ "dist/", @@ -72,7 +72,6 @@ "electron": "^1.4.15", "eslint": "^3.5.0", "floss": "^2.0.1", - "gh-pages": "^0.11.0", "jaguarjs-jsdoc": "^1.0.1", "js-md5": "^0.4.1", "jsdoc": "^3.4.2", diff --git a/scripts/jsdoc.conf.json b/scripts/jsdoc.conf.json index 553e61c..3f52180 100644 --- a/scripts/jsdoc.conf.json +++ b/scripts/jsdoc.conf.json @@ -43,7 +43,7 @@ }, "markdown" : { "parser" : "gfm", - "hardwrap" : true + "hardwrap" : false }, "opts": { "encoding" : "utf8", diff --git a/scripts/release.js b/scripts/release.js deleted file mode 100644 index 156620c..0000000 --- a/scripts/release.js +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env node -'use strict'; - -// Publish script to push releases of the bin files -// the normally are gitignored -const ghpages = require('gh-pages'); -const path = require('path'); -const packageInfo = require(path.join(__dirname, '..', 'package.json')); - -const options = { - src: [ - 'dist/**/*', - 'lib/**/*', - 'src/**/*', - 'scripts/*', - 'scripts/renders/*', - 'test/**/*', - '*.json', - '*.md', - 'LICENSE', - '.eslintrc', - '.editorconfig', - '.travis.yml', - '.babelrc', - ], - dotfiles: true, - branch: 'release', - message: packageInfo.version, - logger: console.log.bind(console), -}; - -ghpages.publish(process.cwd(), options, (err) => -{ - if (err) - { - console.log(err); - process.exit(1); - - return; - } - - process.exit(0); -}); diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index f16bcb1..220427a 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -21,7 +21,7 @@ const DIV_HOOK_ZINDEX = 2; /** - * The Accessibility manager reacreates the ability to tab and and have content read by screen + * The Accessibility manager recreates the ability to tab and and have content read by screen * readers. This is very important as it can possibly help people with disabilities access pixi * content. * diff --git a/src/accessibility/index.js b/src/accessibility/index.js index 568cfb1..0ae108f 100644 --- a/src/accessibility/index.js +++ b/src/accessibility/index.js @@ -1,4 +1,9 @@ /** + * This namespace contains a renderer plugin for interaction accessibility for end-users + * with physical impairments which require screen-renders, keyboard navigation, etc. + * + * Do not instantiate this plugin directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. * @namespace PIXI.accessibility */ export { default as accessibleTarget } from './accessibleTarget'; diff --git a/src/core/Application.js b/src/core/Application.js index 5340578..2fcc729 100644 --- a/src/core/Application.js +++ b/src/core/Application.js @@ -1,6 +1,8 @@ import { autoDetectRenderer } from './autoDetectRenderer'; import Container from './display/Container'; import { shared, Ticker } from './ticker'; +import settings from './settings'; +import { UPDATE_PRIORITY } from './const'; /** * Convenience class to create a new PIXI application. @@ -22,28 +24,52 @@ */ export default class Application { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experience unexplained flickering try setting this to true. - * @param {boolean} [useSharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedLoader=false] - `true` to use PIXI.loaders.shared, `false` to create new Loader. */ - constructor(width, height, options, noWebGL, useSharedTicker = false) + constructor(options, arg2, arg3, arg4, arg5) { + // Support for constructor(width, height, options, noWebGL, useSharedTicker) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + forceCanvas: !!arg4, + sharedTicker: !!arg5, + }, arg3); + } + + /** + * The default options, so we mixin functionality later. + * @member {object} + * @protected + */ + this._options = options = Object.assign({ + sharedTicker: false, + forceCanvas: false, + sharedLoader: false, + }, options); + /** * WebGL renderer if available, otherwise CanvasRenderer * @member {PIXI.WebGLRenderer|PIXI.CanvasRenderer} */ - this.renderer = autoDetectRenderer(width, height, options, noWebGL); + this.renderer = autoDetectRenderer(options); /** * The root display container that's rendered. @@ -63,7 +89,7 @@ * @member {PIXI.ticker.Ticker} * @default PIXI.ticker.shared */ - this.ticker = useSharedTicker ? shared : new Ticker(); + this.ticker = options.sharedTicker ? shared : new Ticker(); // Start the rendering this.start(); @@ -78,7 +104,7 @@ this._ticker = ticker; if (ticker) { - ticker.add(this.render, this); + ticker.add(this.render, this, UPDATE_PRIORITY.LOW); } } get ticker() // eslint-disable-line require-jsdoc @@ -136,13 +162,18 @@ */ destroy(removeView) { - this.stop(); + const oldTicker = this._ticker; + this.ticker = null; + oldTicker.destroy(); + this.stage.destroy(); this.stage = null; this.renderer.destroy(removeView); this.renderer = null; + + this._options = null; } } diff --git a/src/core/autoDetectRenderer.js b/src/core/autoDetectRenderer.js index 8960425..9ec0364 100644 --- a/src/core/autoDetectRenderer.js +++ b/src/core/autoDetectRenderer.js @@ -2,6 +2,7 @@ import CanvasRenderer from './renderers/canvas/CanvasRenderer'; import WebGLRenderer from './renderers/webgl/WebGLRenderer'; +// eslint-disable-next-line valid-jsdoc /** * This helper function will automatically detect which renderer you should be using. * WebGL is the preferred renderer as it is a lot faster. If webGL is not supported by @@ -9,24 +10,32 @@ * * @memberof PIXI * @function autoDetectRenderer - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @return {PIXI.WebGLRenderer|PIXI.CanvasRenderer} Returns WebGL renderer if available, otherwise CanvasRenderer */ -export function autoDetectRenderer(width = 800, height = 600, options, noWebGL) +export function autoDetectRenderer(options, arg1, arg2, arg3) { - if (!noWebGL && utils.isWebGLSupported()) + // Backward-compatible support for noWebGL option + let forceCanvas = options && options.forceCanvas; + + if (arg3 !== undefined) { - return new WebGLRenderer(width, height, options); + forceCanvas = arg3; } - return new CanvasRenderer(width, height, options); + if (!forceCanvas && utils.isWebGLSupported()) + { + return new WebGLRenderer(options, arg1, arg2); + } + + return new CanvasRenderer(options, arg1, arg2); } diff --git a/src/core/const.js b/src/core/const.js index b9e99ae..2c68dee 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -341,3 +341,27 @@ LINEAR_VERTICAL: 0, LINEAR_HORIZONTAL: 1, }; + +/** + * Represents the update priorities used by internal PIXI classes when registered with + * the {@link PIXI.ticker.Ticker} object. Higher priority items are updated first and lower + * priority items, such as render, should go later. + * + * @static + * @constant + * @name UPDATE_PRIORITY + * @memberof PIXI + * @type {object} + * @property {number} INTERACTION=50 Highest priority, used for {@link PIXI.interaction.InteractionManager} + * @property {number} HIGH=25 High priority updating, {@link PIXI.VideoBaseTexture} and {@link PIXI.extras.AnimatedSprite} + * @property {number} NORMAL=0 Default priority for ticker events, see {@link PIXI.ticker.Ticker#add}. + * @property {number} LOW=-25 Low priority used for {@link PIXI.Application} rendering. + * @property {number} UTILITY=-50 Lowest priority used for {@link PIXI.prepare.BasePrepare} utility. + */ +export const UPDATE_PRIORITY = { + INTERACTION: 50, + HIGH: 25, + NORMAL: 0, + LOW: -25, + UTILITY: -50, +}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 4e6c77d..decb42a 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -13,7 +13,6 @@ * * @class * @extends EventEmitter - * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ export default class DisplayObject extends EventEmitter @@ -113,6 +112,29 @@ * @private */ this._mask = null; + + /** + * If the object has been destroyed via destroy(). If true, it should not be used. + * + * @member {boolean} + * @private + * @readonly + */ + this._destroyed = false; + + /** + * Fired when this DisplayObject is added to a Container. + * + * @event PIXI.DisplayObject#added + * @param {PIXI.Container} container - The container added to. + */ + + /** + * Fired when this DisplayObject is removed from a Container. + * + * @event PIXI.DisplayObject#removed + * @param {PIXI.Container} container - The container removed from. + */ } /** @@ -403,6 +425,8 @@ this.interactive = false; this.interactiveChildren = false; + + this._destroyed = true; } /** diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index aafdfe2..f787c4a 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -792,8 +792,8 @@ sprite.worldAlpha = this.worldAlpha * sprite.alpha; sprite.blendMode = this.blendMode; - sprite.texture._frame.width = rect.width; - sprite.texture._frame.height = rect.height; + sprite._texture._frame.width = rect.width; + sprite._texture._frame.height = rect.height; sprite.transform.worldTransform = this.transform.worldTransform; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index a24c775..b9f192d 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -19,11 +19,12 @@ */ export default class SystemRenderer extends EventEmitter { + // eslint-disable-next-line valid-jsdoc /** * @param {string} system - The name of the system this renderer is for. - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -37,27 +38,31 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(system, screenWidth, screenHeight, options) + constructor(system, options, arg2, arg3) { super(); sayHello(system); - // prepare options - if (options) + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') { - for (const i in settings.RENDER_OPTIONS) - { - if (typeof options[i] === 'undefined') - { - options[i] = settings.RENDER_OPTIONS[i]; - } - } + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); } - else - { - options = settings.RENDER_OPTIONS; - } + + // Add the default render options + options = Object.assign({}, settings.RENDER_OPTIONS, options); + + /** + * The supplied constructor options. + * + * @member {Object} + * @readOnly + */ + this.options = options; /** * The type of the renderer. @@ -75,7 +80,7 @@ * * @member {PIXI.Rectangle} */ - this.screen = new Rectangle(0, 0, screenWidth || 800, screenHeight || 600); + this.screen = new Rectangle(0, 0, options.width, options.height); /** * The canvas element that everything is drawn to @@ -279,6 +284,8 @@ this.blendModes = null; + this.options = null; + this.preserveDrawingBuffer = false; this.clearBeforeRender = false; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index e84e49c..691caf0 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -17,10 +17,11 @@ */ export default class CanvasRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -34,9 +35,9 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('Canvas', screenWidth, screenHeight, options); + super('Canvas', options, arg2, arg3); this.type = RENDERER_TYPE.CANVAS; @@ -96,7 +97,19 @@ this.context = null; this.renderingToScreen = false; - this.resize(screenWidth, screenHeight); + this.resize(this.options.width, this.options.height); + + /** + * Fired after rendering finishes. + * + * @event PIXI.CanvasRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.CanvasRenderer#prerender + */ } /** @@ -299,4 +312,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.CanvasRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.CanvasExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.CanvasPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.CanvasRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index e1cf7cd..f6fe123 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -31,11 +31,12 @@ */ export default class WebGLRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** * - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -55,9 +56,9 @@ * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('WebGL', screenWidth, screenHeight, options); + super('WebGL', options, arg2, arg3); /** * The type of this renderer as a standardised const @@ -89,12 +90,6 @@ }; - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ this._backgroundColorRgba[3] = this.transparent ? 0 : 1; this.globalUniforms = new UniformGroup({ @@ -117,6 +112,13 @@ this.initPlugins(); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ if(options.context) { this.context.initFromContext(options.context); @@ -169,6 +171,25 @@ } return this; + + /** + * Fired after rendering finishes. + * + * @event PIXI.WebGLRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.WebGLRenderer#prerender + */ + + /** + * Fired when the WebGL context is set. + * + * @event PIXI.WebGLRenderer#context + * @param {WebGLRenderingContext} gl - WebGL context. + */ } /** @@ -295,4 +316,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.WebGLRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.WebGLRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(WebGLRenderer); diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index 4eb6128..fa3d883 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -47,6 +47,14 @@ * @member {boolean} */ this.enabled = true; + + /** + * If enabled, pixi will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; } /** diff --git a/src/core/renderers/webgl/systems/ContextSystem.js b/src/core/renderers/webgl/systems/ContextSystem.js index ff958c7..8df85ea 100644 --- a/src/core/renderers/webgl/systems/ContextSystem.js +++ b/src/core/renderers/webgl/systems/ContextSystem.js @@ -75,7 +75,8 @@ */ createContext(canvas, options) { - var gl = canvas.getContext('webgl', options) || + var gl = //canvas.getContext('webgl2', options) || + canvas.getContext('webgl', options) || canvas.getContext('experimental-webgl', options); if (!gl) diff --git a/src/core/renderers/webgl/systems/FilterSystem.js b/src/core/renderers/webgl/systems/FilterSystem.js index f47335d..37e28d2 100644 --- a/src/core/renderers/webgl/systems/FilterSystem.js +++ b/src/core/renderers/webgl/systems/FilterSystem.js @@ -111,7 +111,7 @@ // TODO we should fit the rect around the transform.. } - else + else if (filters[0].autoFit) { sourceFrame.fit(filterData.stack[0].destinationFrame); } diff --git a/src/core/renderers/webgl/systems/shader/shader/mapType.js b/src/core/renderers/webgl/systems/shader/shader/mapType.js index 581dace..917538a 100644 --- a/src/core/renderers/webgl/systems/shader/shader/mapType.js +++ b/src/core/renderers/webgl/systems/shader/shader/mapType.js @@ -15,7 +15,7 @@ } } - return GL_TABLE[type]; + return GL_TABLE[type]; }; var GL_TABLE = null; diff --git a/src/core/settings.js b/src/core/settings.js index 18c5877..38ef45c 100644 --- a/src/core/settings.js +++ b/src/core/settings.js @@ -2,6 +2,15 @@ import canUploadSameBuffer from './utils/canUploadSameBuffer'; /** + * User's customizable globals for overriding the default PIXI settings, such + * as a renderer's default resolution, framerate, float percision, etc. + * @example + * // Use the native window resolution as the default resolution + * // will support high-density displays when rendering + * PIXI.settings.RESOLUTION = window.devicePixelRatio. + * + * // Disable interpolation when scaling, will make texture be pixelated + * PIXI.settings.SCALE_MODE = PIXI.SCALE_MODES.NEAREST; * @namespace PIXI.settings */ export default { @@ -101,6 +110,9 @@ * @property {boolean} clearBeforeRender=true * @property {boolean} preserveDrawingBuffer=false * @property {boolean} roundPixels=false + * @property {number} width=800 + * @property {number} height=600 + * @property {boolean} legacy=false */ RENDER_OPTIONS: { view: null, @@ -112,6 +124,9 @@ clearBeforeRender: true, preserveDrawingBuffer: false, roundPixels: false, + width: 800, + height: 600, + legacy: false, }, /** diff --git a/src/core/shader/Program.js b/src/core/shader/Program.js index c0b538b..07b4398 100644 --- a/src/core/shader/Program.js +++ b/src/core/shader/Program.js @@ -1,6 +1,6 @@ import extractUniformsFromSrc from './extractUniformsFromSrc'; import generateUniformsSync from './generateUniformsSync'; -import glCore from 'pixi-gl-core'; +import shaderUtils from '../renderers/webgl/systems/shader/shader'; import { ProgramCache } from '../utils'; import getTestContext from '../utils/getTestContext'; @@ -64,14 +64,17 @@ } else { - vertexSrc = glCore.shader.setPrecision(vertexSrc, 'mediump'); - fragmentSrc = glCore.shader.setPrecision(fragmentSrc, 'mediump'); + vertexSrc = shaderUtils.setPrecision(vertexSrc, 'mediump'); + fragmentSrc = shaderUtils.setPrecision(fragmentSrc, 'mediump'); - const program = glCore.shader.compileProgram(gl, vertexSrc, fragmentSrc); + const program = shaderUtils.compileProgram(gl, vertexSrc, fragmentSrc); + console.log("<<<<<<>>><<"); this.attributeData = this.getAttributeData(program, gl); this.uniformData = this.getUniformData(program, gl); + console.log(this.uniformData); + //gl.deleteProgram(program); } } @@ -95,13 +98,13 @@ for (let i = 0; i < totalAttributes; i++) { const attribData = gl.getActiveAttrib(program, i); - const type = glCore.shader.mapType(gl, attribData.type); + const type = shaderUtils.mapType(gl, attribData.type); /*eslint-disable */ const data = { type: type, name: attribData.name, - size: glCore.shader.mapSize(type), + size: shaderUtils.mapSize(type), location: 0, }; /*eslint-enable */ @@ -137,13 +140,14 @@ // TODO expose this as a prop? // const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); - const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); + //const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); for (let i = 0; i < totalUniforms; i++) { const uniformData = gl.getActiveUniform(program, i); const name = uniformData.name.replace(/\[.*?\]/, ''); - const type = glCore.shader.mapType(gl, uniformData.type); + const type = shaderUtils.mapType(gl, uniformData.type); + console.log('mapping ' + uniformData.type + ' to ' + type); // if (!name.match(maskRegex)) { @@ -151,7 +155,7 @@ uniforms[name] = { type: type, size: uniformData.size, - value: glCore.shader.defaultValue(type, uniformData.size), + value: shaderUtils.defaultValue(type, uniformData.size), }; /*eslint-enable */ } diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index e387e99..b57d095 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -147,12 +147,12 @@ // so if _width is 0 then width was not set.. if (this._width) { - this.scale.x = sign(this.scale.x) * this._width / this.texture.orig.width; + this.scale.x = sign(this.scale.x) * this._width / this._texture.orig.width; } if (this._height) { - this.scale.y = sign(this.scale.y) * this._height / this.texture.orig.height; + this.scale.y = sign(this.scale.y) * this._height / this._texture.orig.height; } } diff --git a/src/core/sprites/canvas/CanvasTinter.js b/src/core/sprites/canvas/CanvasTinter.js index 27e5e0c..fd9de5d 100644 --- a/src/core/sprites/canvas/CanvasTinter.js +++ b/src/core/sprites/canvas/CanvasTinter.js @@ -18,7 +18,7 @@ */ getTintedTexture: (sprite, color) => { - const texture = sprite.texture; + const texture = sprite._texture; color = CanvasTinter.roundColor(color); diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 5a4edda..8b9b797 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -344,6 +344,7 @@ const texture = this._texture; const style = this._style; + const padding = style.trim ? 0 : style.padding; texture.baseTexture.valid = true; texture.baseTexture.resolution = this.resolution; @@ -353,11 +354,11 @@ texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - texture.trim.x = -style.padding; - texture.trim.y = -style.padding; + texture.trim.x = -padding; + texture.trim.y = -padding; - texture.orig.width = texture._frame.width - (style.padding * 2); - texture.orig.height = texture._frame.height - (style.padding * 2); + texture.orig.width = texture._frame.width - (padding * 2); + texture.orig.height = texture._frame.height - (padding * 2); // call sprite onTextureUpdate to update scale if _width or _height were set this._onTextureUpdate(); diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index 853da3d..8a9c34b 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -114,6 +114,38 @@ this.cacheId = null; this.validate(); + + /** + * Fired when a not-immediately-available source finishes loading. + * + * @protected + * @event PIXI.BaseTexture#loaded + * @param {PIXI.BaseTexture} baseTexture - Resource loaded. + */ + + /** + * Fired when a not-immediately-available source fails to load. + * + * @protected + * @event PIXI.BaseTexture#error + * @param {PIXI.BaseTexture} baseTexture - Resource errored. + */ + + /** + * Fired when BaseTexture is updated. + * + * @protected + * @event PIXI.BaseTexture#update + * @param {PIXI.BaseTexture} baseTexture - Instance of texture being updated. + */ + + /** + * Fired when BaseTexture is destroyed. + * + * @protected + * @event PIXI.BaseTexture#dispose + * @param {PIXI.BaseTexture} baseTexture - Instance of texture being destroyed. + */ } setResource(resource) diff --git a/src/core/textures/Spritesheet.js b/src/core/textures/Spritesheet.js index d469c62..9b24f27 100644 --- a/src/core/textures/Spritesheet.js +++ b/src/core/textures/Spritesheet.js @@ -208,6 +208,7 @@ // lets also add the frame to pixi's global cache for fromFrame and fromImage functions TextureCache[i] = this.textures[i]; + this.textures[i].textureCacheId = i; } frameIndex++; diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 0a2c379..2213087 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -147,9 +147,9 @@ /** * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. * - * @event update - * @memberof PIXI.Texture# + * @event PIXI.Texture#update * @protected + * @param {PIXI.Texture} texture - Instance of texture being updated. */ this._updateID = 0; @@ -159,6 +159,15 @@ * @type {Object} */ this.transform = null; + + /** + * The id under which this Texture has been added to the texture cache. This is + * automatically set in certain cases, but may not always be accurate, particularly if + * the texture is in the cache under multiple ids. + * + * @member {string} + */ + this.textureCacheId = null; } /** @@ -225,11 +234,17 @@ this._uvs = null; this.trim = null; this.orig = null; + this.textureCacheId = null; this.valid = false; - this.off('dispose', this.dispose, this); - this.off('update', this.update, this); + for (const prop in TextureCache) + { + if (TextureCache[prop] === this) + { + delete TextureCache[prop]; + } + } } /** @@ -315,14 +330,15 @@ */ static fromLoader(source, imageUrl, name) { - // console.log('added from loader...') + // console.log('added from loader...') const resource = new ImageResource(source);//.from(imageUrl, crossorigin);// document.createElement('img'); - // console.log('base resource ' + resource.width); + // console.log('base resource ' + resource.width); const baseTexture = new BaseTexture(resource, settings.SCALE_MODE, getResolutionOfUrl(imageUrl)); + /// console.log('base width ' + baseTexture.width); const texture = new Texture(baseTexture); @@ -335,6 +351,7 @@ // lets also add the frame to pixi's global cache for fromFrame and fromImage fucntions BaseTextureCache[name] = baseTexture; TextureCache[name] = texture; + texture.textureCacheId = name; // also add references by url if they are different. if (name !== imageUrl) @@ -355,6 +372,10 @@ */ static addTextureToCache(texture, id) { + if (!texture.textureCacheId) + { + texture.textureCacheId = id; + } TextureCache[id] = texture; } @@ -495,6 +516,7 @@ */ Texture.EMPTY = new Texture(new BaseTexture()); removeAllHandlers(Texture.EMPTY); +removeAllHandlers(Texture.EMPTY.baseTexture); /** * A white texture of 10x10 size, used for graphics and other things @@ -505,3 +527,4 @@ */ Texture.WHITE = createWhiteTexture(); removeAllHandlers(Texture.WHITE); +removeAllHandlers(Texture.WHITE.baseTexture); diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index 59eb9fd..a9b9f51 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -1,6 +1,7 @@ import BaseTexture from './BaseTexture'; import { uid, BaseTextureCache } from '../utils'; -import * as ticker from '../ticker'; +import { shared } from '../ticker'; +import { UPDATE_PRIORITY } from '../const'; /** * A texture of a [playing] Video. @@ -125,7 +126,7 @@ if (!this._isAutoUpdating && this.autoUpdate) { - ticker.shared.add(this.update, this); + shared.add(this.update, this, UPDATE_PRIORITY.HIGH); this._isAutoUpdating = true; } } @@ -139,7 +140,7 @@ { if (this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); this._isAutoUpdating = false; } } @@ -187,7 +188,7 @@ { if (this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); } if (this.source && this.source._pixiId) @@ -281,12 +282,12 @@ if (!this._autoUpdate && this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); this._isAutoUpdating = false; } else if (this._autoUpdate && !this._isAutoUpdating) { - ticker.shared.add(this.update, this); + shared.add(this.update, this, UPDATE_PRIORITY.HIGH); this._isAutoUpdating = true; } } diff --git a/src/core/ticker/Ticker.js b/src/core/ticker/Ticker.js index 17a1517..40df138 100644 --- a/src/core/ticker/Ticker.js +++ b/src/core/ticker/Ticker.js @@ -1,12 +1,10 @@ import settings from '../settings'; -import EventEmitter from 'eventemitter3'; - -// Internal event used by composed emitter -const TICK = 'tick'; +import { UPDATE_PRIORITY } from '../const'; +import TickerListener from './TickerListener'; /** * A Ticker class that runs an update loop that other objects listen to. - * This class is composed around an EventEmitter object to add listeners + * This class is composed around listeners * meant for execution on the next requested animation frame. * Animation frames are requested only when necessary, * e.g. When the ticker is started and the emitter has listeners. @@ -22,10 +20,11 @@ constructor() { /** - * Internal emitter used to fire 'tick' event + * The first listener. All new listeners added are chained on this. * @private + * @type {TickerListener} */ - this._emitter = new EventEmitter(); + this._head = new TickerListener(null, null, Infinity); /** * Internal current frame request ID @@ -131,7 +130,7 @@ // Invoke listeners now this.update(time); // Listener side effects may have modified ticker state. - if (this.started && this._requestId === null && this._emitter.listeners(TICK, true)) + if (this.started && this._requestId === null && this._head.next) { this._requestId = requestAnimationFrame(this._tick); } @@ -148,7 +147,7 @@ */ _requestIfNeeded() { - if (this._requestId === null && this._emitter.listeners(TICK, true)) + if (this._requestId === null && this._head.next) { // ensure callbacks get correct delta this.lastTime = performance.now(); @@ -193,35 +192,72 @@ } /** - * Calls {@link module:eventemitter3.EventEmitter#on} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. + * Register a handler for tick events. Calls continuously unless + * it is removed or the ticker is stopped. * * @param {Function} fn - The listener function to be added for updates * @param {Function} [context] - The listener context + * @param {number} [priority=PIXI.UPDATE_PRIORITY.NORMAL] - The priority for emitting * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - add(fn, context) + add(fn, context, priority = UPDATE_PRIORITY.NORMAL) { - this._emitter.on(TICK, fn, context); - - this._startIfPossible(); - - return this; + return this._addListener(new TickerListener(fn, context, priority)); } /** - * Calls {@link module:eventemitter3.EventEmitter#once} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. + * Add a handler for the tick event which is only execute once. * * @param {Function} fn - The listener function to be added for one update * @param {Function} [context] - The listener context + * @param {number} [priority=PIXI.UPDATE_PRIORITY.NORMAL] - The priority for emitting * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - addOnce(fn, context) + addOnce(fn, context, priority = UPDATE_PRIORITY.NORMAL) { - this._emitter.once(TICK, fn, context); + return this._addListener(new TickerListener(fn, context, priority, true)); + } + + /** + * Internally adds the event handler so that it can be sorted by priority. + * Priority allows certain handler (user, AnimatedSprite, Interaction) to be run + * before the rendering. + * + * @private + * @param {TickerListener} listener - Current listener being added. + * @returns {PIXI.ticker.Ticker} This instance of a ticker + */ + _addListener(listener) + { + // For attaching to head + let current = this._head.next; + let previous = this._head; + + // Add the first item + if (!current) + { + listener.connect(previous); + } + else + { + // Go from highest to lowest priority + while (current) + { + if (listener.priority >= current.priority) + { + listener.connect(previous); + break; + } + previous = current; + current = current.next; + } + + // Not yet connected + if (!listener.previous) + { + listener.connect(previous); + } + } this._startIfPossible(); @@ -229,19 +265,33 @@ } /** - * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. - * It checks if the emitter has listeners for 'tick' event. - * If it does, then it cancels the animation frame. + * Removes any handlers matching the function and context parameters. + * If no handlers are left after removing, then it cancels the animation frame. * - * @param {Function} [fn] - The listener function to be removed + * @param {Function} fn - The listener function to be removed * @param {Function} [context] - The listener context to be removed * @returns {PIXI.ticker.Ticker} This instance of a ticker */ remove(fn, context) { - this._emitter.off(TICK, fn, context); + let listener = this._head.next; - if (!this._emitter.listeners(TICK, true)) + while (listener) + { + // We found a match, lets remove it + // no break to delete all possible matches + // incase a listener was added 2+ times + if (listener.match(fn, context)) + { + listener = listener.destroy(); + } + else + { + listener = listener.next; + } + } + + if (!this._head.next) { this._cancelIfNeeded(); } @@ -276,6 +326,25 @@ } /** + * Destroy the ticker and don't use after this. Calling + * this method removes all references to internal events. + */ + destroy() + { + this.stop(); + + let listener = this._head.next; + + while (listener) + { + listener = listener.destroy(true); + } + + this._head.destroy(); + this._head = null; + } + + /** * Triggers an update. An update entails setting the * current {@link PIXI.ticker.Ticker#elapsedMS}, * the current {@link PIXI.ticker.Ticker#deltaTime}, @@ -320,8 +389,22 @@ this.deltaTime = elapsedMS * settings.TARGET_FPMS * this.speed; + // Cache a local reference, in-case ticker is destroyed + // during the emit, we can still check for head.next + const head = this._head; + // Invoke listeners added to internal emitter - this._emitter.emit(TICK, this.deltaTime); + let listener = head.next; + + while (listener) + { + listener = listener.emit(this.deltaTime); + } + + if (!head.next) + { + this._cancelIfNeeded(); + } } else { diff --git a/src/core/ticker/TickerListener.js b/src/core/ticker/TickerListener.js new file mode 100644 index 0000000..2bedb34 --- /dev/null +++ b/src/core/ticker/TickerListener.js @@ -0,0 +1,158 @@ +/** + * Internal class for handling the priority sorting of ticker handlers. + * + * @private + * @class + * @memberof PIXI.ticker + */ +export default class TickerListener +{ + /** + * Constructor + * + * @param {Function} fn - The listener function to be added for one update + * @param {Function} [context=null] - The listener context + * @param {number} [priority=0] - The priority for emitting + * @param {boolean} [once=false] - If the handler should fire once + */ + constructor(fn, context = null, priority = 0, once = false) + { + /** + * The handler function to execute. + * @member {Function} + */ + this.fn = fn; + + /** + * The calling to execute. + * @member {Function} + */ + this.context = context; + + /** + * The current priority. + * @member {number} + */ + this.priority = priority; + + /** + * If this should only execute once. + * @member {boolean} + */ + this.once = once; + + /** + * The next item in chain. + * @member {TickerListener} + */ + this.next = null; + + /** + * The previous item in chain. + * @member {TickerListener} + */ + this.previous = null; + + /** + * `true` if this listener has been destroyed already. + * @member {boolean} + * @private + */ + this._destroyed = false; + } + + /** + * Simple compare function to figure out if a function and context match. + * + * @param {Function} fn - The listener function to be added for one update + * @param {Function} context - The listener context + * @return {boolean} `true` if the listener match the arguments + */ + match(fn, context) + { + context = context || null; + + return this.fn === fn && this.context === context; + } + + /** + * Emit by calling the current function. + * @param {number} deltaTime - time since the last emit. + * @return {TickerListener} Next ticker + */ + emit(deltaTime) + { + if (this.context) + { + this.fn.call(this.context, deltaTime); + } + else + { + this.fn(deltaTime); + } + + if (this.once) + { + this.destroy(); + } + + const redirect = this.next; + + // Soft-destroying should remove + // the next reference + if (this._destroyed) + { + this.next = null; + } + + return redirect; + } + + /** + * Connect to the list. + * @param {TickerListener} previous - Input node, previous listener + */ + connect(previous) + { + this.previous = previous; + if (previous.next) + { + previous.next.previous = this; + } + this.next = previous.next; + previous.next = this; + } + + /** + * Destroy and don't use after this. + * @param {boolean} [hard = false] `true` to remove the `next` reference, this + * is considered a hard destroy. Soft destroy maintains the next reference. + * @return {TickerListener} The listener to redirect while emitting or removing. + */ + destroy(hard = false) + { + this._destroyed = true; + this.fn = null; + this.context = null; + + // Disconnect, hook up next and previous + if (this.previous) + { + this.previous.next = this.next; + } + + if (this.next) + { + this.next.previous = this.previous; + } + + // Redirect to the next item + const redirect = this.previous; + + // Remove references + this.next = hard ? null : redirect; + this.previous = null; + + return redirect; + } +} diff --git a/src/core/ticker/index.js b/src/core/ticker/index.js index 4b3017c..c3020d4 100644 --- a/src/core/ticker/index.js +++ b/src/core/ticker/index.js @@ -45,8 +45,25 @@ const shared = new Ticker(); shared.autoStart = true; +shared.destroy = () => +{ + // protect destroying shared ticker + // this is used by other internal systems + // like AnimatedSprite and InteractionManager +}; /** + * This namespace contains an API for interacting with PIXI's internal global update loop. + * + * This ticker is used for rendering, {@link PIXI.extras.AnimatedSprite AnimatedSprite}, + * {@link PIXI.interaction.InteractionManager InteractionManager} and many other time-based PIXI systems. + * @example + * const ticker = new PIXI.ticker.Ticker(); + * ticker.stop(); + * ticker.add((deltaTime) => { + * // do something every frame + * }); + * ticker.start(); * @namespace PIXI.ticker */ export { shared, Ticker }; diff --git a/src/core/utils/index.js b/src/core/utils/index.js index be44750..7441f7b 100644 --- a/src/core/utils/index.js +++ b/src/core/utils/index.js @@ -9,6 +9,21 @@ let saidHello = false; /** + * Generalized convenience utilities for PIXI. + * @example + * // Extend PIXI's internal Event Emitter. + * class MyEmitter extends PIXI.utils.EventEmitter { + * constructor() { + * super(); + * console.log("Emitter created!"); + * } + * } + * + * // Get info on current device + * console.log(PIXI.utils.isMobile); + * + * // Convert hex color to string + * console.log(PIXI.utils.hex2string(0xff00ff)); // returns: "#ff00ff" * @namespace PIXI.utils */ export { diff --git a/src/deprecation.js b/src/deprecation.js index b9c73b3..33daeec 100644 --- a/src/deprecation.js +++ b/src/deprecation.js @@ -918,6 +918,35 @@ }); /** + * @method + * @name PIXI.prepare.BasePrepare#register + * @see PIXI.prepare.BasePrepare#registerFindHook + * @deprecated since version 4.4.2 + * @param {Function} [addHook] - Function call that takes two parameters: `item:*, queue:Array` + * function must return `true` if it was able to add item to the queue. + * @param {Function} [uploadHook] - Function call that takes two parameters: `prepare:CanvasPrepare, item:*` and + * function must return `true` if it was able to handle upload of item. + * @return {PIXI.BasePrepare} Instance of plugin for chaining. + */ +prepare.BasePrepare.prototype.register = function register(addHook, uploadHook) +{ + warn('renderer.plugins.prepare.register is now deprecated, ' + + 'please use renderer.plugins.prepare.registerFindHook & renderer.plugins.prepare.registerUploadHook'); + + if (addHook) + { + this.registerFindHook(addHook); + } + + if (uploadHook) + { + this.registerUploadHook(uploadHook); + } + + return this; +}; + +/** * The number of graphics or textures to upload to the GPU. * * @name PIXI.prepare.canvas.UPLOADS_PER_FRAME @@ -969,67 +998,73 @@ }, }); -Object.defineProperties(loaders.Resource.prototype, { - isJson: { - get() - { - warn('The isJson property is deprecated, please use `resource.type === Resource.TYPE.JSON`.'); +if (loaders.Loader) +{ + const Resource = loaders.Resource; + const Loader = loaders.Loader; - return this.type === loaders.Loader.Resource.TYPE.JSON; + Object.defineProperties(Resource.prototype, { + isJson: { + get() + { + warn('The isJson property is deprecated, please use `resource.type === Resource.TYPE.JSON`.'); + + return this.type === Resource.TYPE.JSON; + }, }, - }, - isXml: { - get() - { - warn('The isXml property is deprecated, please use `resource.type === Resource.TYPE.XML`.'); + isXml: { + get() + { + warn('The isXml property is deprecated, please use `resource.type === Resource.TYPE.XML`.'); - return this.type === loaders.Loader.Resource.TYPE.XML; + return this.type === Resource.TYPE.XML; + }, }, - }, - isImage: { - get() - { - warn('The isImage property is deprecated, please use `resource.type === Resource.TYPE.IMAGE`.'); + isImage: { + get() + { + warn('The isImage property is deprecated, please use `resource.type === Resource.TYPE.IMAGE`.'); - return this.type === loaders.Loader.Resource.TYPE.IMAGE; + return this.type === Resource.TYPE.IMAGE; + }, }, - }, - isAudio: { - get() - { - warn('The isAudio property is deprecated, please use `resource.type === Resource.TYPE.AUDIO`.'); + isAudio: { + get() + { + warn('The isAudio property is deprecated, please use `resource.type === Resource.TYPE.AUDIO`.'); - return this.type === loaders.Loader.Resource.TYPE.AUDIO; + return this.type === Resource.TYPE.AUDIO; + }, }, - }, - isVideo: { - get() - { - warn('The isVideo property is deprecated, please use `resource.type === Resource.TYPE.VIDEO`.'); + isVideo: { + get() + { + warn('The isVideo property is deprecated, please use `resource.type === Resource.TYPE.VIDEO`.'); - return this.type === loaders.Loader.Resource.TYPE.VIDEO; + return this.type === Resource.TYPE.VIDEO; + }, }, - }, -}); + }); -Object.defineProperties(loaders.Loader.prototype, { - before: { - get() - { - warn('The before() method is deprecated, please use pre().'); + Object.defineProperties(Loader.prototype, { + before: { + get() + { + warn('The before() method is deprecated, please use pre().'); - return this.pre; + return this.pre; + }, }, - }, - after: { - get() - { - warn('The after() method is deprecated, please use use().'); + after: { + get() + { + warn('The after() method is deprecated, please use use().'); - return this.use; + return this.use; + }, }, - }, -}); + }); +} /** * @name PIXI.interaction.interactiveTarget#defaultCursor diff --git a/src/extract/canvas/CanvasExtract.js b/src/extract/canvas/CanvasExtract.js index 941ca05..3ac5aee 100644 --- a/src/extract/canvas/CanvasExtract.js +++ b/src/extract/canvas/CanvasExtract.js @@ -8,7 +8,7 @@ * An instance of this class is automatically created by default, and can be found at renderer.plugins.extract * * @class - * @memberof PIXI + * @memberof PIXI.extract */ export default class CanvasExtract { @@ -21,9 +21,9 @@ /** * Collection of methods for extracting data (image, pixels, etc.) from a display object or render texture * - * @member {PIXI.CanvasExtract} extract + * @member {PIXI.extract.CanvasExtract} extract * @memberof PIXI.CanvasRenderer# - * @see PIXI.CanvasExtract + * @see PIXI.extract.CanvasExtract */ renderer.extract = this; } diff --git a/src/extract/index.js b/src/extract/index.js index 0f2170d..e9478c1 100644 --- a/src/extract/index.js +++ b/src/extract/index.js @@ -1,2 +1,22 @@ +/** + * This namespace provides renderer-specific plugins for exporting content from a renderer. + * For instance, these plugins can be used for saving an Image, Canvas element or for exporting the raw image data (pixels). + * + * Do not instantiate these plugins directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. + * @example + * // Create a new app (will auto-add extract plugin to renderer) + * const app = new PIXI.Application(); + * + * // Draw a red circle + * const graphics = new PIXI.Graphics() + * .beginFill(0xFF0000) + * .drawCircle(0, 0, 50); + * + * // Render the graphics as an HTMLImageElement + * const image = app.renderer.plugins.extract.image(graphics); + * document.body.appendChild(image); + * @namespace PIXI.extract + */ export { default as webgl } from './webgl/WebGLExtract'; export { default as canvas } from './canvas/CanvasExtract'; diff --git a/src/extract/webgl/WebGLExtract.js b/src/extract/webgl/WebGLExtract.js index 60228bb..1c3dd30 100644 --- a/src/extract/webgl/WebGLExtract.js +++ b/src/extract/webgl/WebGLExtract.js @@ -9,7 +9,7 @@ * An instance of this class is automatically created by default, and can be found at renderer.plugins.extract * * @class - * @memberof PIXI + * @memberof PIXI.extract */ export default class WebGLExtract { @@ -22,9 +22,9 @@ /** * Collection of methods for extracting data (image, pixels, etc.) from a display object or render texture * - * @member {PIXI.WebGLExtract} extract + * @member {PIXI.extract.WebGLExtract} extract * @memberof PIXI.WebGLRenderer# - * @see PIXI.WebGLExtract + * @see PIXI.extract.WebGLExtract */ renderer.extract = this; } diff --git a/src/extras/AnimatedSprite.js b/src/extras/AnimatedSprite.js index 13b804f..5297778 100644 --- a/src/extras/AnimatedSprite.js +++ b/src/extras/AnimatedSprite.js @@ -137,7 +137,7 @@ this.playing = true; if (this._autoUpdate) { - core.ticker.shared.add(this.update, this); + core.ticker.shared.add(this.update, this, core.UPDATE_PRIORITY.HIGH); } } diff --git a/src/extras/TextureTransform.js b/src/extras/TextureTransform.js index 78e6e40..16536d2 100644 --- a/src/extras/TextureTransform.js +++ b/src/extras/TextureTransform.js @@ -65,8 +65,36 @@ } /** + * Multiplies uvs array to transform + * @param {Float32Array} uvs mesh uvs + * @param {Float32Array} [out=uvs] output + * @returns {Float32Array} output + */ + multiplyUvs(uvs, out) + { + if (out === undefined) + { + out = uvs; + } + + const mat = this.mapCoord; + + for (let i = 0; i < uvs.length; i += 2) + { + const x = uvs[i]; + const y = uvs[i + 1]; + + out[i] = (x * mat.a) + (y * mat.c) + mat.tx; + out[i + 1] = (x * mat.b) + (y * mat.d) + mat.ty; + } + + return out; + } + + /** * updates matrices if texture was changed * @param {boolean} forceUpdate if true, matrices will be updated any case + * @returns {boolean} whether or not it was updated */ update(forceUpdate) { @@ -74,13 +102,13 @@ if (!tex || !tex.valid) { - return; + return false; } if (!forceUpdate && this._lastTextureID === tex._updateID) { - return; + return false; } this._lastTextureID = tex._updateID; @@ -110,5 +138,7 @@ frame[3] = (tex._frame.y + tex._frame.height - margin + offset) / texBase.height; this.uClampOffset[0] = offset / texBase.realWidth; this.uClampOffset[1] = offset / texBase.realHeight; + + return true; } } diff --git a/src/extras/TilingSprite.js b/src/extras/TilingSprite.js index 1f6014f..a3f027f 100644 --- a/src/extras/TilingSprite.js +++ b/src/extras/TilingSprite.js @@ -180,9 +180,9 @@ const transform = this.worldTransform; const resolution = renderer.resolution; const baseTexture = texture.baseTexture; - const baseTextureResolution = texture.baseTexture.resolution; - const modX = (this.tilePosition.x / this.tileScale.x) % texture._frame.width; - const modY = (this.tilePosition.y / this.tileScale.y) % texture._frame.height; + const baseTextureResolution = baseTexture.resolution; + const modX = ((this.tilePosition.x / this.tileScale.x) % texture._frame.width) * baseTextureResolution; + const modY = ((this.tilePosition.y / this.tileScale.y) % texture._frame.height) * baseTextureResolution; // create a nice shiny pattern! // TODO this needs to be refreshed if texture changes.. diff --git a/src/extras/cacheAsBitmap.js b/src/extras/cacheAsBitmap.js index 5dd5640..aea06a8 100644 --- a/src/extras/cacheAsBitmap.js +++ b/src/extras/cacheAsBitmap.js @@ -227,7 +227,16 @@ this.transform._parentID = -1; // restore the transform of the cached sprite to avoid the nasty flicker.. - this.updateTransform(); + if (!this.parent) + { + this.parent = renderer._tempDisplayObjectParent; + this.updateTransform(); + this.parent = null; + } + else + { + this.updateTransform(); + } // map the hit test.. this.containsPoint = cachedSprite.containsPoint.bind(cachedSprite); @@ -314,7 +323,17 @@ cachedSprite._bounds = this._bounds; cachedSprite.alpha = cacheAlpha; - this.updateTransform(); + if (!this.parent) + { + this.parent = renderer._tempDisplayObjectParent; + this.updateTransform(); + this.parent = null; + } + else + { + this.updateTransform(); + } + this.updateTransform = this.displayObjectUpdateTransform; this._cacheData.sprite = cachedSprite; @@ -358,9 +377,12 @@ * Destroys the cached object. * * @private + * @param {object|boolean} [options] - Options parameter. A boolean will act as if all options + * have been set to that value. + * Used when destroying containers, see the Container.destroy method. */ -DisplayObject.prototype._cacheAsBitmapDestroy = function _cacheAsBitmapDestroy() +DisplayObject.prototype._cacheAsBitmapDestroy = function _cacheAsBitmapDestroy(options) { this.cacheAsBitmap = false; - this.destroy(); + this.destroy(options); }; diff --git a/src/extras/index.js b/src/extras/index.js index bbd8bbf..a34d7ec 100644 --- a/src/extras/index.js +++ b/src/extras/index.js @@ -1,7 +1,7 @@ /** + * Additional PIXI DisplayObjects for animation, tiling and bitmap text. * @namespace PIXI.extras */ -export { default as TextureTransform } from './TextureTransform'; export { default as AnimatedSprite } from './AnimatedSprite'; export { default as TilingSprite } from './TilingSprite'; export { default as TilingSpriteRenderer } from './webgl/TilingSpriteRenderer'; diff --git a/.travis.yml b/.travis.yml index 3df8b22..14de334 100644 --- a/.travis.yml +++ b/.travis.yml @@ -110,3 +110,43 @@ on: all_branches: true condition: $TRAVIS_TAG + # Deploy config for latest release + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: dist + upload-dir: "release" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: docs + upload-dir: "release/docs" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: coverage + upload-dir: "release/coverage" + on: + all_branches: true + condition: $TRAVIS_TAG \ No newline at end of file diff --git a/README.md b/README.md index d26fadd..3b85f85 100644 --- a/README.md +++ b/README.md @@ -36,22 +36,14 @@ - Wiki: Other misc tutorials and resources are [on the Wiki](https://github.com/pixijs/pixi.js/wiki/Resources). ### Community ### -- Forums: Check out the [forum] (http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow] (http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. +- Forums: Check out the [forum](http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow](http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. - Inspiration: Check out the [gallery](http://www.pixijs.com/gallery) to see some of the amazing things people have created! - Chat: You can join us on [Gitter](https://gitter.im/pixijs/pixi.js) To chat about Pixi. We also now have a Slack channel. If you would like to join it please Send me an email (mat@goodboydigital.com) and I will invite you in. ### Setup ### -It's easy to get started with Pixi.js! Simply grab the pre-built versions from here: - -Release Branch - Nice and stable Pixi.js -- Unminified: [http://pixijs.download/release/pixi.js] -- Minified: [http://pixijs.download/release/pixi.min.js] - -Develop Branch - The bleeding edge version of Pixi.js -- Unminified: [http://pixijs.download/dev/pixi.js] -- Minified: [http://pixijs.download/dev/pixi.min.js] +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. @@ -70,10 +62,10 @@ #### CDN Install (via cdnjs) ```html - + ``` -_Note: `4.2.2` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ +_Note: `4.4.3` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ ### Demos ### @@ -130,7 +122,7 @@ // and the root stage PIXI.Container. var app = new PIXI.Application(); -// The application will create a canvas element for you that you +// The application will create a canvas element for you that you // can then insert into the DOM. document.body.appendChild(app.view); @@ -150,7 +142,7 @@ // Add the bunny to the scene we are building. app.stage.addChild(bunny); - + // Listen for frame updates app.ticker.add(function() { // each frame we spin the bunny around a bit diff --git a/bower.json b/bower.json deleted file mode 100644 index 41b668c..0000000 --- a/bower.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "pixi.js", - "main": "dist/pixi.min.js", - "ignore": [ - "**/.*", - "docs", - "node_modules", - "bower_components", - "test", - "scripts", - "bower.json", - "inch.json" - ], - "dependencies": { - }, - "devDependencies": { - } -} diff --git a/package.json b/package.json index d35cbb2..308469f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.3.5", + "version": "4.4.4", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ @@ -40,11 +40,11 @@ "lib": "babel src --out-dir lib -s", "predocs": "rimraf docs/**", "docs": "jsdoc -c scripts/jsdoc.conf.json -R README.md", - "publish:patch": "npm version patch --no-git-tag-version && npm publish", - "publish:minor": "npm version minor --no-git-tag-version && npm publish", - "publish:major": "npm version major --no-git-tag-version && npm publish", + "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", - "postpublish": "node scripts/release.js" + "postpublish": "git push && git push --tags" }, "files": [ "dist/", @@ -72,7 +72,6 @@ "electron": "^1.4.15", "eslint": "^3.5.0", "floss": "^2.0.1", - "gh-pages": "^0.11.0", "jaguarjs-jsdoc": "^1.0.1", "js-md5": "^0.4.1", "jsdoc": "^3.4.2", diff --git a/scripts/jsdoc.conf.json b/scripts/jsdoc.conf.json index 553e61c..3f52180 100644 --- a/scripts/jsdoc.conf.json +++ b/scripts/jsdoc.conf.json @@ -43,7 +43,7 @@ }, "markdown" : { "parser" : "gfm", - "hardwrap" : true + "hardwrap" : false }, "opts": { "encoding" : "utf8", diff --git a/scripts/release.js b/scripts/release.js deleted file mode 100644 index 156620c..0000000 --- a/scripts/release.js +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env node -'use strict'; - -// Publish script to push releases of the bin files -// the normally are gitignored -const ghpages = require('gh-pages'); -const path = require('path'); -const packageInfo = require(path.join(__dirname, '..', 'package.json')); - -const options = { - src: [ - 'dist/**/*', - 'lib/**/*', - 'src/**/*', - 'scripts/*', - 'scripts/renders/*', - 'test/**/*', - '*.json', - '*.md', - 'LICENSE', - '.eslintrc', - '.editorconfig', - '.travis.yml', - '.babelrc', - ], - dotfiles: true, - branch: 'release', - message: packageInfo.version, - logger: console.log.bind(console), -}; - -ghpages.publish(process.cwd(), options, (err) => -{ - if (err) - { - console.log(err); - process.exit(1); - - return; - } - - process.exit(0); -}); diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index f16bcb1..220427a 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -21,7 +21,7 @@ const DIV_HOOK_ZINDEX = 2; /** - * The Accessibility manager reacreates the ability to tab and and have content read by screen + * The Accessibility manager recreates the ability to tab and and have content read by screen * readers. This is very important as it can possibly help people with disabilities access pixi * content. * diff --git a/src/accessibility/index.js b/src/accessibility/index.js index 568cfb1..0ae108f 100644 --- a/src/accessibility/index.js +++ b/src/accessibility/index.js @@ -1,4 +1,9 @@ /** + * This namespace contains a renderer plugin for interaction accessibility for end-users + * with physical impairments which require screen-renders, keyboard navigation, etc. + * + * Do not instantiate this plugin directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. * @namespace PIXI.accessibility */ export { default as accessibleTarget } from './accessibleTarget'; diff --git a/src/core/Application.js b/src/core/Application.js index 5340578..2fcc729 100644 --- a/src/core/Application.js +++ b/src/core/Application.js @@ -1,6 +1,8 @@ import { autoDetectRenderer } from './autoDetectRenderer'; import Container from './display/Container'; import { shared, Ticker } from './ticker'; +import settings from './settings'; +import { UPDATE_PRIORITY } from './const'; /** * Convenience class to create a new PIXI application. @@ -22,28 +24,52 @@ */ export default class Application { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experience unexplained flickering try setting this to true. - * @param {boolean} [useSharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedLoader=false] - `true` to use PIXI.loaders.shared, `false` to create new Loader. */ - constructor(width, height, options, noWebGL, useSharedTicker = false) + constructor(options, arg2, arg3, arg4, arg5) { + // Support for constructor(width, height, options, noWebGL, useSharedTicker) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + forceCanvas: !!arg4, + sharedTicker: !!arg5, + }, arg3); + } + + /** + * The default options, so we mixin functionality later. + * @member {object} + * @protected + */ + this._options = options = Object.assign({ + sharedTicker: false, + forceCanvas: false, + sharedLoader: false, + }, options); + /** * WebGL renderer if available, otherwise CanvasRenderer * @member {PIXI.WebGLRenderer|PIXI.CanvasRenderer} */ - this.renderer = autoDetectRenderer(width, height, options, noWebGL); + this.renderer = autoDetectRenderer(options); /** * The root display container that's rendered. @@ -63,7 +89,7 @@ * @member {PIXI.ticker.Ticker} * @default PIXI.ticker.shared */ - this.ticker = useSharedTicker ? shared : new Ticker(); + this.ticker = options.sharedTicker ? shared : new Ticker(); // Start the rendering this.start(); @@ -78,7 +104,7 @@ this._ticker = ticker; if (ticker) { - ticker.add(this.render, this); + ticker.add(this.render, this, UPDATE_PRIORITY.LOW); } } get ticker() // eslint-disable-line require-jsdoc @@ -136,13 +162,18 @@ */ destroy(removeView) { - this.stop(); + const oldTicker = this._ticker; + this.ticker = null; + oldTicker.destroy(); + this.stage.destroy(); this.stage = null; this.renderer.destroy(removeView); this.renderer = null; + + this._options = null; } } diff --git a/src/core/autoDetectRenderer.js b/src/core/autoDetectRenderer.js index 8960425..9ec0364 100644 --- a/src/core/autoDetectRenderer.js +++ b/src/core/autoDetectRenderer.js @@ -2,6 +2,7 @@ import CanvasRenderer from './renderers/canvas/CanvasRenderer'; import WebGLRenderer from './renderers/webgl/WebGLRenderer'; +// eslint-disable-next-line valid-jsdoc /** * This helper function will automatically detect which renderer you should be using. * WebGL is the preferred renderer as it is a lot faster. If webGL is not supported by @@ -9,24 +10,32 @@ * * @memberof PIXI * @function autoDetectRenderer - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @return {PIXI.WebGLRenderer|PIXI.CanvasRenderer} Returns WebGL renderer if available, otherwise CanvasRenderer */ -export function autoDetectRenderer(width = 800, height = 600, options, noWebGL) +export function autoDetectRenderer(options, arg1, arg2, arg3) { - if (!noWebGL && utils.isWebGLSupported()) + // Backward-compatible support for noWebGL option + let forceCanvas = options && options.forceCanvas; + + if (arg3 !== undefined) { - return new WebGLRenderer(width, height, options); + forceCanvas = arg3; } - return new CanvasRenderer(width, height, options); + if (!forceCanvas && utils.isWebGLSupported()) + { + return new WebGLRenderer(options, arg1, arg2); + } + + return new CanvasRenderer(options, arg1, arg2); } diff --git a/src/core/const.js b/src/core/const.js index b9e99ae..2c68dee 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -341,3 +341,27 @@ LINEAR_VERTICAL: 0, LINEAR_HORIZONTAL: 1, }; + +/** + * Represents the update priorities used by internal PIXI classes when registered with + * the {@link PIXI.ticker.Ticker} object. Higher priority items are updated first and lower + * priority items, such as render, should go later. + * + * @static + * @constant + * @name UPDATE_PRIORITY + * @memberof PIXI + * @type {object} + * @property {number} INTERACTION=50 Highest priority, used for {@link PIXI.interaction.InteractionManager} + * @property {number} HIGH=25 High priority updating, {@link PIXI.VideoBaseTexture} and {@link PIXI.extras.AnimatedSprite} + * @property {number} NORMAL=0 Default priority for ticker events, see {@link PIXI.ticker.Ticker#add}. + * @property {number} LOW=-25 Low priority used for {@link PIXI.Application} rendering. + * @property {number} UTILITY=-50 Lowest priority used for {@link PIXI.prepare.BasePrepare} utility. + */ +export const UPDATE_PRIORITY = { + INTERACTION: 50, + HIGH: 25, + NORMAL: 0, + LOW: -25, + UTILITY: -50, +}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 4e6c77d..decb42a 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -13,7 +13,6 @@ * * @class * @extends EventEmitter - * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ export default class DisplayObject extends EventEmitter @@ -113,6 +112,29 @@ * @private */ this._mask = null; + + /** + * If the object has been destroyed via destroy(). If true, it should not be used. + * + * @member {boolean} + * @private + * @readonly + */ + this._destroyed = false; + + /** + * Fired when this DisplayObject is added to a Container. + * + * @event PIXI.DisplayObject#added + * @param {PIXI.Container} container - The container added to. + */ + + /** + * Fired when this DisplayObject is removed from a Container. + * + * @event PIXI.DisplayObject#removed + * @param {PIXI.Container} container - The container removed from. + */ } /** @@ -403,6 +425,8 @@ this.interactive = false; this.interactiveChildren = false; + + this._destroyed = true; } /** diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index aafdfe2..f787c4a 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -792,8 +792,8 @@ sprite.worldAlpha = this.worldAlpha * sprite.alpha; sprite.blendMode = this.blendMode; - sprite.texture._frame.width = rect.width; - sprite.texture._frame.height = rect.height; + sprite._texture._frame.width = rect.width; + sprite._texture._frame.height = rect.height; sprite.transform.worldTransform = this.transform.worldTransform; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index a24c775..b9f192d 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -19,11 +19,12 @@ */ export default class SystemRenderer extends EventEmitter { + // eslint-disable-next-line valid-jsdoc /** * @param {string} system - The name of the system this renderer is for. - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -37,27 +38,31 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(system, screenWidth, screenHeight, options) + constructor(system, options, arg2, arg3) { super(); sayHello(system); - // prepare options - if (options) + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') { - for (const i in settings.RENDER_OPTIONS) - { - if (typeof options[i] === 'undefined') - { - options[i] = settings.RENDER_OPTIONS[i]; - } - } + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); } - else - { - options = settings.RENDER_OPTIONS; - } + + // Add the default render options + options = Object.assign({}, settings.RENDER_OPTIONS, options); + + /** + * The supplied constructor options. + * + * @member {Object} + * @readOnly + */ + this.options = options; /** * The type of the renderer. @@ -75,7 +80,7 @@ * * @member {PIXI.Rectangle} */ - this.screen = new Rectangle(0, 0, screenWidth || 800, screenHeight || 600); + this.screen = new Rectangle(0, 0, options.width, options.height); /** * The canvas element that everything is drawn to @@ -279,6 +284,8 @@ this.blendModes = null; + this.options = null; + this.preserveDrawingBuffer = false; this.clearBeforeRender = false; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index e84e49c..691caf0 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -17,10 +17,11 @@ */ export default class CanvasRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -34,9 +35,9 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('Canvas', screenWidth, screenHeight, options); + super('Canvas', options, arg2, arg3); this.type = RENDERER_TYPE.CANVAS; @@ -96,7 +97,19 @@ this.context = null; this.renderingToScreen = false; - this.resize(screenWidth, screenHeight); + this.resize(this.options.width, this.options.height); + + /** + * Fired after rendering finishes. + * + * @event PIXI.CanvasRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.CanvasRenderer#prerender + */ } /** @@ -299,4 +312,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.CanvasRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.CanvasExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.CanvasPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.CanvasRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index e1cf7cd..f6fe123 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -31,11 +31,12 @@ */ export default class WebGLRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** * - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -55,9 +56,9 @@ * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('WebGL', screenWidth, screenHeight, options); + super('WebGL', options, arg2, arg3); /** * The type of this renderer as a standardised const @@ -89,12 +90,6 @@ }; - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ this._backgroundColorRgba[3] = this.transparent ? 0 : 1; this.globalUniforms = new UniformGroup({ @@ -117,6 +112,13 @@ this.initPlugins(); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ if(options.context) { this.context.initFromContext(options.context); @@ -169,6 +171,25 @@ } return this; + + /** + * Fired after rendering finishes. + * + * @event PIXI.WebGLRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.WebGLRenderer#prerender + */ + + /** + * Fired when the WebGL context is set. + * + * @event PIXI.WebGLRenderer#context + * @param {WebGLRenderingContext} gl - WebGL context. + */ } /** @@ -295,4 +316,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.WebGLRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.WebGLRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(WebGLRenderer); diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index 4eb6128..fa3d883 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -47,6 +47,14 @@ * @member {boolean} */ this.enabled = true; + + /** + * If enabled, pixi will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; } /** diff --git a/src/core/renderers/webgl/systems/ContextSystem.js b/src/core/renderers/webgl/systems/ContextSystem.js index ff958c7..8df85ea 100644 --- a/src/core/renderers/webgl/systems/ContextSystem.js +++ b/src/core/renderers/webgl/systems/ContextSystem.js @@ -75,7 +75,8 @@ */ createContext(canvas, options) { - var gl = canvas.getContext('webgl', options) || + var gl = //canvas.getContext('webgl2', options) || + canvas.getContext('webgl', options) || canvas.getContext('experimental-webgl', options); if (!gl) diff --git a/src/core/renderers/webgl/systems/FilterSystem.js b/src/core/renderers/webgl/systems/FilterSystem.js index f47335d..37e28d2 100644 --- a/src/core/renderers/webgl/systems/FilterSystem.js +++ b/src/core/renderers/webgl/systems/FilterSystem.js @@ -111,7 +111,7 @@ // TODO we should fit the rect around the transform.. } - else + else if (filters[0].autoFit) { sourceFrame.fit(filterData.stack[0].destinationFrame); } diff --git a/src/core/renderers/webgl/systems/shader/shader/mapType.js b/src/core/renderers/webgl/systems/shader/shader/mapType.js index 581dace..917538a 100644 --- a/src/core/renderers/webgl/systems/shader/shader/mapType.js +++ b/src/core/renderers/webgl/systems/shader/shader/mapType.js @@ -15,7 +15,7 @@ } } - return GL_TABLE[type]; + return GL_TABLE[type]; }; var GL_TABLE = null; diff --git a/src/core/settings.js b/src/core/settings.js index 18c5877..38ef45c 100644 --- a/src/core/settings.js +++ b/src/core/settings.js @@ -2,6 +2,15 @@ import canUploadSameBuffer from './utils/canUploadSameBuffer'; /** + * User's customizable globals for overriding the default PIXI settings, such + * as a renderer's default resolution, framerate, float percision, etc. + * @example + * // Use the native window resolution as the default resolution + * // will support high-density displays when rendering + * PIXI.settings.RESOLUTION = window.devicePixelRatio. + * + * // Disable interpolation when scaling, will make texture be pixelated + * PIXI.settings.SCALE_MODE = PIXI.SCALE_MODES.NEAREST; * @namespace PIXI.settings */ export default { @@ -101,6 +110,9 @@ * @property {boolean} clearBeforeRender=true * @property {boolean} preserveDrawingBuffer=false * @property {boolean} roundPixels=false + * @property {number} width=800 + * @property {number} height=600 + * @property {boolean} legacy=false */ RENDER_OPTIONS: { view: null, @@ -112,6 +124,9 @@ clearBeforeRender: true, preserveDrawingBuffer: false, roundPixels: false, + width: 800, + height: 600, + legacy: false, }, /** diff --git a/src/core/shader/Program.js b/src/core/shader/Program.js index c0b538b..07b4398 100644 --- a/src/core/shader/Program.js +++ b/src/core/shader/Program.js @@ -1,6 +1,6 @@ import extractUniformsFromSrc from './extractUniformsFromSrc'; import generateUniformsSync from './generateUniformsSync'; -import glCore from 'pixi-gl-core'; +import shaderUtils from '../renderers/webgl/systems/shader/shader'; import { ProgramCache } from '../utils'; import getTestContext from '../utils/getTestContext'; @@ -64,14 +64,17 @@ } else { - vertexSrc = glCore.shader.setPrecision(vertexSrc, 'mediump'); - fragmentSrc = glCore.shader.setPrecision(fragmentSrc, 'mediump'); + vertexSrc = shaderUtils.setPrecision(vertexSrc, 'mediump'); + fragmentSrc = shaderUtils.setPrecision(fragmentSrc, 'mediump'); - const program = glCore.shader.compileProgram(gl, vertexSrc, fragmentSrc); + const program = shaderUtils.compileProgram(gl, vertexSrc, fragmentSrc); + console.log("<<<<<<>>><<"); this.attributeData = this.getAttributeData(program, gl); this.uniformData = this.getUniformData(program, gl); + console.log(this.uniformData); + //gl.deleteProgram(program); } } @@ -95,13 +98,13 @@ for (let i = 0; i < totalAttributes; i++) { const attribData = gl.getActiveAttrib(program, i); - const type = glCore.shader.mapType(gl, attribData.type); + const type = shaderUtils.mapType(gl, attribData.type); /*eslint-disable */ const data = { type: type, name: attribData.name, - size: glCore.shader.mapSize(type), + size: shaderUtils.mapSize(type), location: 0, }; /*eslint-enable */ @@ -137,13 +140,14 @@ // TODO expose this as a prop? // const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); - const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); + //const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); for (let i = 0; i < totalUniforms; i++) { const uniformData = gl.getActiveUniform(program, i); const name = uniformData.name.replace(/\[.*?\]/, ''); - const type = glCore.shader.mapType(gl, uniformData.type); + const type = shaderUtils.mapType(gl, uniformData.type); + console.log('mapping ' + uniformData.type + ' to ' + type); // if (!name.match(maskRegex)) { @@ -151,7 +155,7 @@ uniforms[name] = { type: type, size: uniformData.size, - value: glCore.shader.defaultValue(type, uniformData.size), + value: shaderUtils.defaultValue(type, uniformData.size), }; /*eslint-enable */ } diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index e387e99..b57d095 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -147,12 +147,12 @@ // so if _width is 0 then width was not set.. if (this._width) { - this.scale.x = sign(this.scale.x) * this._width / this.texture.orig.width; + this.scale.x = sign(this.scale.x) * this._width / this._texture.orig.width; } if (this._height) { - this.scale.y = sign(this.scale.y) * this._height / this.texture.orig.height; + this.scale.y = sign(this.scale.y) * this._height / this._texture.orig.height; } } diff --git a/src/core/sprites/canvas/CanvasTinter.js b/src/core/sprites/canvas/CanvasTinter.js index 27e5e0c..fd9de5d 100644 --- a/src/core/sprites/canvas/CanvasTinter.js +++ b/src/core/sprites/canvas/CanvasTinter.js @@ -18,7 +18,7 @@ */ getTintedTexture: (sprite, color) => { - const texture = sprite.texture; + const texture = sprite._texture; color = CanvasTinter.roundColor(color); diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 5a4edda..8b9b797 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -344,6 +344,7 @@ const texture = this._texture; const style = this._style; + const padding = style.trim ? 0 : style.padding; texture.baseTexture.valid = true; texture.baseTexture.resolution = this.resolution; @@ -353,11 +354,11 @@ texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - texture.trim.x = -style.padding; - texture.trim.y = -style.padding; + texture.trim.x = -padding; + texture.trim.y = -padding; - texture.orig.width = texture._frame.width - (style.padding * 2); - texture.orig.height = texture._frame.height - (style.padding * 2); + texture.orig.width = texture._frame.width - (padding * 2); + texture.orig.height = texture._frame.height - (padding * 2); // call sprite onTextureUpdate to update scale if _width or _height were set this._onTextureUpdate(); diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index 853da3d..8a9c34b 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -114,6 +114,38 @@ this.cacheId = null; this.validate(); + + /** + * Fired when a not-immediately-available source finishes loading. + * + * @protected + * @event PIXI.BaseTexture#loaded + * @param {PIXI.BaseTexture} baseTexture - Resource loaded. + */ + + /** + * Fired when a not-immediately-available source fails to load. + * + * @protected + * @event PIXI.BaseTexture#error + * @param {PIXI.BaseTexture} baseTexture - Resource errored. + */ + + /** + * Fired when BaseTexture is updated. + * + * @protected + * @event PIXI.BaseTexture#update + * @param {PIXI.BaseTexture} baseTexture - Instance of texture being updated. + */ + + /** + * Fired when BaseTexture is destroyed. + * + * @protected + * @event PIXI.BaseTexture#dispose + * @param {PIXI.BaseTexture} baseTexture - Instance of texture being destroyed. + */ } setResource(resource) diff --git a/src/core/textures/Spritesheet.js b/src/core/textures/Spritesheet.js index d469c62..9b24f27 100644 --- a/src/core/textures/Spritesheet.js +++ b/src/core/textures/Spritesheet.js @@ -208,6 +208,7 @@ // lets also add the frame to pixi's global cache for fromFrame and fromImage functions TextureCache[i] = this.textures[i]; + this.textures[i].textureCacheId = i; } frameIndex++; diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 0a2c379..2213087 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -147,9 +147,9 @@ /** * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. * - * @event update - * @memberof PIXI.Texture# + * @event PIXI.Texture#update * @protected + * @param {PIXI.Texture} texture - Instance of texture being updated. */ this._updateID = 0; @@ -159,6 +159,15 @@ * @type {Object} */ this.transform = null; + + /** + * The id under which this Texture has been added to the texture cache. This is + * automatically set in certain cases, but may not always be accurate, particularly if + * the texture is in the cache under multiple ids. + * + * @member {string} + */ + this.textureCacheId = null; } /** @@ -225,11 +234,17 @@ this._uvs = null; this.trim = null; this.orig = null; + this.textureCacheId = null; this.valid = false; - this.off('dispose', this.dispose, this); - this.off('update', this.update, this); + for (const prop in TextureCache) + { + if (TextureCache[prop] === this) + { + delete TextureCache[prop]; + } + } } /** @@ -315,14 +330,15 @@ */ static fromLoader(source, imageUrl, name) { - // console.log('added from loader...') + // console.log('added from loader...') const resource = new ImageResource(source);//.from(imageUrl, crossorigin);// document.createElement('img'); - // console.log('base resource ' + resource.width); + // console.log('base resource ' + resource.width); const baseTexture = new BaseTexture(resource, settings.SCALE_MODE, getResolutionOfUrl(imageUrl)); + /// console.log('base width ' + baseTexture.width); const texture = new Texture(baseTexture); @@ -335,6 +351,7 @@ // lets also add the frame to pixi's global cache for fromFrame and fromImage fucntions BaseTextureCache[name] = baseTexture; TextureCache[name] = texture; + texture.textureCacheId = name; // also add references by url if they are different. if (name !== imageUrl) @@ -355,6 +372,10 @@ */ static addTextureToCache(texture, id) { + if (!texture.textureCacheId) + { + texture.textureCacheId = id; + } TextureCache[id] = texture; } @@ -495,6 +516,7 @@ */ Texture.EMPTY = new Texture(new BaseTexture()); removeAllHandlers(Texture.EMPTY); +removeAllHandlers(Texture.EMPTY.baseTexture); /** * A white texture of 10x10 size, used for graphics and other things @@ -505,3 +527,4 @@ */ Texture.WHITE = createWhiteTexture(); removeAllHandlers(Texture.WHITE); +removeAllHandlers(Texture.WHITE.baseTexture); diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index 59eb9fd..a9b9f51 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -1,6 +1,7 @@ import BaseTexture from './BaseTexture'; import { uid, BaseTextureCache } from '../utils'; -import * as ticker from '../ticker'; +import { shared } from '../ticker'; +import { UPDATE_PRIORITY } from '../const'; /** * A texture of a [playing] Video. @@ -125,7 +126,7 @@ if (!this._isAutoUpdating && this.autoUpdate) { - ticker.shared.add(this.update, this); + shared.add(this.update, this, UPDATE_PRIORITY.HIGH); this._isAutoUpdating = true; } } @@ -139,7 +140,7 @@ { if (this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); this._isAutoUpdating = false; } } @@ -187,7 +188,7 @@ { if (this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); } if (this.source && this.source._pixiId) @@ -281,12 +282,12 @@ if (!this._autoUpdate && this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); this._isAutoUpdating = false; } else if (this._autoUpdate && !this._isAutoUpdating) { - ticker.shared.add(this.update, this); + shared.add(this.update, this, UPDATE_PRIORITY.HIGH); this._isAutoUpdating = true; } } diff --git a/src/core/ticker/Ticker.js b/src/core/ticker/Ticker.js index 17a1517..40df138 100644 --- a/src/core/ticker/Ticker.js +++ b/src/core/ticker/Ticker.js @@ -1,12 +1,10 @@ import settings from '../settings'; -import EventEmitter from 'eventemitter3'; - -// Internal event used by composed emitter -const TICK = 'tick'; +import { UPDATE_PRIORITY } from '../const'; +import TickerListener from './TickerListener'; /** * A Ticker class that runs an update loop that other objects listen to. - * This class is composed around an EventEmitter object to add listeners + * This class is composed around listeners * meant for execution on the next requested animation frame. * Animation frames are requested only when necessary, * e.g. When the ticker is started and the emitter has listeners. @@ -22,10 +20,11 @@ constructor() { /** - * Internal emitter used to fire 'tick' event + * The first listener. All new listeners added are chained on this. * @private + * @type {TickerListener} */ - this._emitter = new EventEmitter(); + this._head = new TickerListener(null, null, Infinity); /** * Internal current frame request ID @@ -131,7 +130,7 @@ // Invoke listeners now this.update(time); // Listener side effects may have modified ticker state. - if (this.started && this._requestId === null && this._emitter.listeners(TICK, true)) + if (this.started && this._requestId === null && this._head.next) { this._requestId = requestAnimationFrame(this._tick); } @@ -148,7 +147,7 @@ */ _requestIfNeeded() { - if (this._requestId === null && this._emitter.listeners(TICK, true)) + if (this._requestId === null && this._head.next) { // ensure callbacks get correct delta this.lastTime = performance.now(); @@ -193,35 +192,72 @@ } /** - * Calls {@link module:eventemitter3.EventEmitter#on} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. + * Register a handler for tick events. Calls continuously unless + * it is removed or the ticker is stopped. * * @param {Function} fn - The listener function to be added for updates * @param {Function} [context] - The listener context + * @param {number} [priority=PIXI.UPDATE_PRIORITY.NORMAL] - The priority for emitting * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - add(fn, context) + add(fn, context, priority = UPDATE_PRIORITY.NORMAL) { - this._emitter.on(TICK, fn, context); - - this._startIfPossible(); - - return this; + return this._addListener(new TickerListener(fn, context, priority)); } /** - * Calls {@link module:eventemitter3.EventEmitter#once} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. + * Add a handler for the tick event which is only execute once. * * @param {Function} fn - The listener function to be added for one update * @param {Function} [context] - The listener context + * @param {number} [priority=PIXI.UPDATE_PRIORITY.NORMAL] - The priority for emitting * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - addOnce(fn, context) + addOnce(fn, context, priority = UPDATE_PRIORITY.NORMAL) { - this._emitter.once(TICK, fn, context); + return this._addListener(new TickerListener(fn, context, priority, true)); + } + + /** + * Internally adds the event handler so that it can be sorted by priority. + * Priority allows certain handler (user, AnimatedSprite, Interaction) to be run + * before the rendering. + * + * @private + * @param {TickerListener} listener - Current listener being added. + * @returns {PIXI.ticker.Ticker} This instance of a ticker + */ + _addListener(listener) + { + // For attaching to head + let current = this._head.next; + let previous = this._head; + + // Add the first item + if (!current) + { + listener.connect(previous); + } + else + { + // Go from highest to lowest priority + while (current) + { + if (listener.priority >= current.priority) + { + listener.connect(previous); + break; + } + previous = current; + current = current.next; + } + + // Not yet connected + if (!listener.previous) + { + listener.connect(previous); + } + } this._startIfPossible(); @@ -229,19 +265,33 @@ } /** - * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. - * It checks if the emitter has listeners for 'tick' event. - * If it does, then it cancels the animation frame. + * Removes any handlers matching the function and context parameters. + * If no handlers are left after removing, then it cancels the animation frame. * - * @param {Function} [fn] - The listener function to be removed + * @param {Function} fn - The listener function to be removed * @param {Function} [context] - The listener context to be removed * @returns {PIXI.ticker.Ticker} This instance of a ticker */ remove(fn, context) { - this._emitter.off(TICK, fn, context); + let listener = this._head.next; - if (!this._emitter.listeners(TICK, true)) + while (listener) + { + // We found a match, lets remove it + // no break to delete all possible matches + // incase a listener was added 2+ times + if (listener.match(fn, context)) + { + listener = listener.destroy(); + } + else + { + listener = listener.next; + } + } + + if (!this._head.next) { this._cancelIfNeeded(); } @@ -276,6 +326,25 @@ } /** + * Destroy the ticker and don't use after this. Calling + * this method removes all references to internal events. + */ + destroy() + { + this.stop(); + + let listener = this._head.next; + + while (listener) + { + listener = listener.destroy(true); + } + + this._head.destroy(); + this._head = null; + } + + /** * Triggers an update. An update entails setting the * current {@link PIXI.ticker.Ticker#elapsedMS}, * the current {@link PIXI.ticker.Ticker#deltaTime}, @@ -320,8 +389,22 @@ this.deltaTime = elapsedMS * settings.TARGET_FPMS * this.speed; + // Cache a local reference, in-case ticker is destroyed + // during the emit, we can still check for head.next + const head = this._head; + // Invoke listeners added to internal emitter - this._emitter.emit(TICK, this.deltaTime); + let listener = head.next; + + while (listener) + { + listener = listener.emit(this.deltaTime); + } + + if (!head.next) + { + this._cancelIfNeeded(); + } } else { diff --git a/src/core/ticker/TickerListener.js b/src/core/ticker/TickerListener.js new file mode 100644 index 0000000..2bedb34 --- /dev/null +++ b/src/core/ticker/TickerListener.js @@ -0,0 +1,158 @@ +/** + * Internal class for handling the priority sorting of ticker handlers. + * + * @private + * @class + * @memberof PIXI.ticker + */ +export default class TickerListener +{ + /** + * Constructor + * + * @param {Function} fn - The listener function to be added for one update + * @param {Function} [context=null] - The listener context + * @param {number} [priority=0] - The priority for emitting + * @param {boolean} [once=false] - If the handler should fire once + */ + constructor(fn, context = null, priority = 0, once = false) + { + /** + * The handler function to execute. + * @member {Function} + */ + this.fn = fn; + + /** + * The calling to execute. + * @member {Function} + */ + this.context = context; + + /** + * The current priority. + * @member {number} + */ + this.priority = priority; + + /** + * If this should only execute once. + * @member {boolean} + */ + this.once = once; + + /** + * The next item in chain. + * @member {TickerListener} + */ + this.next = null; + + /** + * The previous item in chain. + * @member {TickerListener} + */ + this.previous = null; + + /** + * `true` if this listener has been destroyed already. + * @member {boolean} + * @private + */ + this._destroyed = false; + } + + /** + * Simple compare function to figure out if a function and context match. + * + * @param {Function} fn - The listener function to be added for one update + * @param {Function} context - The listener context + * @return {boolean} `true` if the listener match the arguments + */ + match(fn, context) + { + context = context || null; + + return this.fn === fn && this.context === context; + } + + /** + * Emit by calling the current function. + * @param {number} deltaTime - time since the last emit. + * @return {TickerListener} Next ticker + */ + emit(deltaTime) + { + if (this.context) + { + this.fn.call(this.context, deltaTime); + } + else + { + this.fn(deltaTime); + } + + if (this.once) + { + this.destroy(); + } + + const redirect = this.next; + + // Soft-destroying should remove + // the next reference + if (this._destroyed) + { + this.next = null; + } + + return redirect; + } + + /** + * Connect to the list. + * @param {TickerListener} previous - Input node, previous listener + */ + connect(previous) + { + this.previous = previous; + if (previous.next) + { + previous.next.previous = this; + } + this.next = previous.next; + previous.next = this; + } + + /** + * Destroy and don't use after this. + * @param {boolean} [hard = false] `true` to remove the `next` reference, this + * is considered a hard destroy. Soft destroy maintains the next reference. + * @return {TickerListener} The listener to redirect while emitting or removing. + */ + destroy(hard = false) + { + this._destroyed = true; + this.fn = null; + this.context = null; + + // Disconnect, hook up next and previous + if (this.previous) + { + this.previous.next = this.next; + } + + if (this.next) + { + this.next.previous = this.previous; + } + + // Redirect to the next item + const redirect = this.previous; + + // Remove references + this.next = hard ? null : redirect; + this.previous = null; + + return redirect; + } +} diff --git a/src/core/ticker/index.js b/src/core/ticker/index.js index 4b3017c..c3020d4 100644 --- a/src/core/ticker/index.js +++ b/src/core/ticker/index.js @@ -45,8 +45,25 @@ const shared = new Ticker(); shared.autoStart = true; +shared.destroy = () => +{ + // protect destroying shared ticker + // this is used by other internal systems + // like AnimatedSprite and InteractionManager +}; /** + * This namespace contains an API for interacting with PIXI's internal global update loop. + * + * This ticker is used for rendering, {@link PIXI.extras.AnimatedSprite AnimatedSprite}, + * {@link PIXI.interaction.InteractionManager InteractionManager} and many other time-based PIXI systems. + * @example + * const ticker = new PIXI.ticker.Ticker(); + * ticker.stop(); + * ticker.add((deltaTime) => { + * // do something every frame + * }); + * ticker.start(); * @namespace PIXI.ticker */ export { shared, Ticker }; diff --git a/src/core/utils/index.js b/src/core/utils/index.js index be44750..7441f7b 100644 --- a/src/core/utils/index.js +++ b/src/core/utils/index.js @@ -9,6 +9,21 @@ let saidHello = false; /** + * Generalized convenience utilities for PIXI. + * @example + * // Extend PIXI's internal Event Emitter. + * class MyEmitter extends PIXI.utils.EventEmitter { + * constructor() { + * super(); + * console.log("Emitter created!"); + * } + * } + * + * // Get info on current device + * console.log(PIXI.utils.isMobile); + * + * // Convert hex color to string + * console.log(PIXI.utils.hex2string(0xff00ff)); // returns: "#ff00ff" * @namespace PIXI.utils */ export { diff --git a/src/deprecation.js b/src/deprecation.js index b9c73b3..33daeec 100644 --- a/src/deprecation.js +++ b/src/deprecation.js @@ -918,6 +918,35 @@ }); /** + * @method + * @name PIXI.prepare.BasePrepare#register + * @see PIXI.prepare.BasePrepare#registerFindHook + * @deprecated since version 4.4.2 + * @param {Function} [addHook] - Function call that takes two parameters: `item:*, queue:Array` + * function must return `true` if it was able to add item to the queue. + * @param {Function} [uploadHook] - Function call that takes two parameters: `prepare:CanvasPrepare, item:*` and + * function must return `true` if it was able to handle upload of item. + * @return {PIXI.BasePrepare} Instance of plugin for chaining. + */ +prepare.BasePrepare.prototype.register = function register(addHook, uploadHook) +{ + warn('renderer.plugins.prepare.register is now deprecated, ' + + 'please use renderer.plugins.prepare.registerFindHook & renderer.plugins.prepare.registerUploadHook'); + + if (addHook) + { + this.registerFindHook(addHook); + } + + if (uploadHook) + { + this.registerUploadHook(uploadHook); + } + + return this; +}; + +/** * The number of graphics or textures to upload to the GPU. * * @name PIXI.prepare.canvas.UPLOADS_PER_FRAME @@ -969,67 +998,73 @@ }, }); -Object.defineProperties(loaders.Resource.prototype, { - isJson: { - get() - { - warn('The isJson property is deprecated, please use `resource.type === Resource.TYPE.JSON`.'); +if (loaders.Loader) +{ + const Resource = loaders.Resource; + const Loader = loaders.Loader; - return this.type === loaders.Loader.Resource.TYPE.JSON; + Object.defineProperties(Resource.prototype, { + isJson: { + get() + { + warn('The isJson property is deprecated, please use `resource.type === Resource.TYPE.JSON`.'); + + return this.type === Resource.TYPE.JSON; + }, }, - }, - isXml: { - get() - { - warn('The isXml property is deprecated, please use `resource.type === Resource.TYPE.XML`.'); + isXml: { + get() + { + warn('The isXml property is deprecated, please use `resource.type === Resource.TYPE.XML`.'); - return this.type === loaders.Loader.Resource.TYPE.XML; + return this.type === Resource.TYPE.XML; + }, }, - }, - isImage: { - get() - { - warn('The isImage property is deprecated, please use `resource.type === Resource.TYPE.IMAGE`.'); + isImage: { + get() + { + warn('The isImage property is deprecated, please use `resource.type === Resource.TYPE.IMAGE`.'); - return this.type === loaders.Loader.Resource.TYPE.IMAGE; + return this.type === Resource.TYPE.IMAGE; + }, }, - }, - isAudio: { - get() - { - warn('The isAudio property is deprecated, please use `resource.type === Resource.TYPE.AUDIO`.'); + isAudio: { + get() + { + warn('The isAudio property is deprecated, please use `resource.type === Resource.TYPE.AUDIO`.'); - return this.type === loaders.Loader.Resource.TYPE.AUDIO; + return this.type === Resource.TYPE.AUDIO; + }, }, - }, - isVideo: { - get() - { - warn('The isVideo property is deprecated, please use `resource.type === Resource.TYPE.VIDEO`.'); + isVideo: { + get() + { + warn('The isVideo property is deprecated, please use `resource.type === Resource.TYPE.VIDEO`.'); - return this.type === loaders.Loader.Resource.TYPE.VIDEO; + return this.type === Resource.TYPE.VIDEO; + }, }, - }, -}); + }); -Object.defineProperties(loaders.Loader.prototype, { - before: { - get() - { - warn('The before() method is deprecated, please use pre().'); + Object.defineProperties(Loader.prototype, { + before: { + get() + { + warn('The before() method is deprecated, please use pre().'); - return this.pre; + return this.pre; + }, }, - }, - after: { - get() - { - warn('The after() method is deprecated, please use use().'); + after: { + get() + { + warn('The after() method is deprecated, please use use().'); - return this.use; + return this.use; + }, }, - }, -}); + }); +} /** * @name PIXI.interaction.interactiveTarget#defaultCursor diff --git a/src/extract/canvas/CanvasExtract.js b/src/extract/canvas/CanvasExtract.js index 941ca05..3ac5aee 100644 --- a/src/extract/canvas/CanvasExtract.js +++ b/src/extract/canvas/CanvasExtract.js @@ -8,7 +8,7 @@ * An instance of this class is automatically created by default, and can be found at renderer.plugins.extract * * @class - * @memberof PIXI + * @memberof PIXI.extract */ export default class CanvasExtract { @@ -21,9 +21,9 @@ /** * Collection of methods for extracting data (image, pixels, etc.) from a display object or render texture * - * @member {PIXI.CanvasExtract} extract + * @member {PIXI.extract.CanvasExtract} extract * @memberof PIXI.CanvasRenderer# - * @see PIXI.CanvasExtract + * @see PIXI.extract.CanvasExtract */ renderer.extract = this; } diff --git a/src/extract/index.js b/src/extract/index.js index 0f2170d..e9478c1 100644 --- a/src/extract/index.js +++ b/src/extract/index.js @@ -1,2 +1,22 @@ +/** + * This namespace provides renderer-specific plugins for exporting content from a renderer. + * For instance, these plugins can be used for saving an Image, Canvas element or for exporting the raw image data (pixels). + * + * Do not instantiate these plugins directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. + * @example + * // Create a new app (will auto-add extract plugin to renderer) + * const app = new PIXI.Application(); + * + * // Draw a red circle + * const graphics = new PIXI.Graphics() + * .beginFill(0xFF0000) + * .drawCircle(0, 0, 50); + * + * // Render the graphics as an HTMLImageElement + * const image = app.renderer.plugins.extract.image(graphics); + * document.body.appendChild(image); + * @namespace PIXI.extract + */ export { default as webgl } from './webgl/WebGLExtract'; export { default as canvas } from './canvas/CanvasExtract'; diff --git a/src/extract/webgl/WebGLExtract.js b/src/extract/webgl/WebGLExtract.js index 60228bb..1c3dd30 100644 --- a/src/extract/webgl/WebGLExtract.js +++ b/src/extract/webgl/WebGLExtract.js @@ -9,7 +9,7 @@ * An instance of this class is automatically created by default, and can be found at renderer.plugins.extract * * @class - * @memberof PIXI + * @memberof PIXI.extract */ export default class WebGLExtract { @@ -22,9 +22,9 @@ /** * Collection of methods for extracting data (image, pixels, etc.) from a display object or render texture * - * @member {PIXI.WebGLExtract} extract + * @member {PIXI.extract.WebGLExtract} extract * @memberof PIXI.WebGLRenderer# - * @see PIXI.WebGLExtract + * @see PIXI.extract.WebGLExtract */ renderer.extract = this; } diff --git a/src/extras/AnimatedSprite.js b/src/extras/AnimatedSprite.js index 13b804f..5297778 100644 --- a/src/extras/AnimatedSprite.js +++ b/src/extras/AnimatedSprite.js @@ -137,7 +137,7 @@ this.playing = true; if (this._autoUpdate) { - core.ticker.shared.add(this.update, this); + core.ticker.shared.add(this.update, this, core.UPDATE_PRIORITY.HIGH); } } diff --git a/src/extras/TextureTransform.js b/src/extras/TextureTransform.js index 78e6e40..16536d2 100644 --- a/src/extras/TextureTransform.js +++ b/src/extras/TextureTransform.js @@ -65,8 +65,36 @@ } /** + * Multiplies uvs array to transform + * @param {Float32Array} uvs mesh uvs + * @param {Float32Array} [out=uvs] output + * @returns {Float32Array} output + */ + multiplyUvs(uvs, out) + { + if (out === undefined) + { + out = uvs; + } + + const mat = this.mapCoord; + + for (let i = 0; i < uvs.length; i += 2) + { + const x = uvs[i]; + const y = uvs[i + 1]; + + out[i] = (x * mat.a) + (y * mat.c) + mat.tx; + out[i + 1] = (x * mat.b) + (y * mat.d) + mat.ty; + } + + return out; + } + + /** * updates matrices if texture was changed * @param {boolean} forceUpdate if true, matrices will be updated any case + * @returns {boolean} whether or not it was updated */ update(forceUpdate) { @@ -74,13 +102,13 @@ if (!tex || !tex.valid) { - return; + return false; } if (!forceUpdate && this._lastTextureID === tex._updateID) { - return; + return false; } this._lastTextureID = tex._updateID; @@ -110,5 +138,7 @@ frame[3] = (tex._frame.y + tex._frame.height - margin + offset) / texBase.height; this.uClampOffset[0] = offset / texBase.realWidth; this.uClampOffset[1] = offset / texBase.realHeight; + + return true; } } diff --git a/src/extras/TilingSprite.js b/src/extras/TilingSprite.js index 1f6014f..a3f027f 100644 --- a/src/extras/TilingSprite.js +++ b/src/extras/TilingSprite.js @@ -180,9 +180,9 @@ const transform = this.worldTransform; const resolution = renderer.resolution; const baseTexture = texture.baseTexture; - const baseTextureResolution = texture.baseTexture.resolution; - const modX = (this.tilePosition.x / this.tileScale.x) % texture._frame.width; - const modY = (this.tilePosition.y / this.tileScale.y) % texture._frame.height; + const baseTextureResolution = baseTexture.resolution; + const modX = ((this.tilePosition.x / this.tileScale.x) % texture._frame.width) * baseTextureResolution; + const modY = ((this.tilePosition.y / this.tileScale.y) % texture._frame.height) * baseTextureResolution; // create a nice shiny pattern! // TODO this needs to be refreshed if texture changes.. diff --git a/src/extras/cacheAsBitmap.js b/src/extras/cacheAsBitmap.js index 5dd5640..aea06a8 100644 --- a/src/extras/cacheAsBitmap.js +++ b/src/extras/cacheAsBitmap.js @@ -227,7 +227,16 @@ this.transform._parentID = -1; // restore the transform of the cached sprite to avoid the nasty flicker.. - this.updateTransform(); + if (!this.parent) + { + this.parent = renderer._tempDisplayObjectParent; + this.updateTransform(); + this.parent = null; + } + else + { + this.updateTransform(); + } // map the hit test.. this.containsPoint = cachedSprite.containsPoint.bind(cachedSprite); @@ -314,7 +323,17 @@ cachedSprite._bounds = this._bounds; cachedSprite.alpha = cacheAlpha; - this.updateTransform(); + if (!this.parent) + { + this.parent = renderer._tempDisplayObjectParent; + this.updateTransform(); + this.parent = null; + } + else + { + this.updateTransform(); + } + this.updateTransform = this.displayObjectUpdateTransform; this._cacheData.sprite = cachedSprite; @@ -358,9 +377,12 @@ * Destroys the cached object. * * @private + * @param {object|boolean} [options] - Options parameter. A boolean will act as if all options + * have been set to that value. + * Used when destroying containers, see the Container.destroy method. */ -DisplayObject.prototype._cacheAsBitmapDestroy = function _cacheAsBitmapDestroy() +DisplayObject.prototype._cacheAsBitmapDestroy = function _cacheAsBitmapDestroy(options) { this.cacheAsBitmap = false; - this.destroy(); + this.destroy(options); }; diff --git a/src/extras/index.js b/src/extras/index.js index bbd8bbf..a34d7ec 100644 --- a/src/extras/index.js +++ b/src/extras/index.js @@ -1,7 +1,7 @@ /** + * Additional PIXI DisplayObjects for animation, tiling and bitmap text. * @namespace PIXI.extras */ -export { default as TextureTransform } from './TextureTransform'; export { default as AnimatedSprite } from './AnimatedSprite'; export { default as TilingSprite } from './TilingSprite'; export { default as TilingSpriteRenderer } from './webgl/TilingSpriteRenderer'; diff --git a/src/extras/webgl/TilingSpriteRenderer.js b/src/extras/webgl/TilingSpriteRenderer.js index aeec172..eee3b5c 100644 --- a/src/extras/webgl/TilingSpriteRenderer.js +++ b/src/extras/webgl/TilingSpriteRenderer.js @@ -11,7 +11,7 @@ * WebGL renderer plugin for tiling sprites * * @class - * @memberof PIXI + * @memberof PIXI.extras * @extends PIXI.ObjectRenderer */ export default class TilingSpriteRenderer extends core.ObjectRenderer @@ -118,7 +118,7 @@ tempMat.invert(); if (isSimple) { - tempMat.append(uv.mapCoord); + tempMat.prepend(uv.mapCoord); } else { diff --git a/.travis.yml b/.travis.yml index 3df8b22..14de334 100644 --- a/.travis.yml +++ b/.travis.yml @@ -110,3 +110,43 @@ on: all_branches: true condition: $TRAVIS_TAG + # Deploy config for latest release + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: dist + upload-dir: "release" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: docs + upload-dir: "release/docs" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: coverage + upload-dir: "release/coverage" + on: + all_branches: true + condition: $TRAVIS_TAG \ No newline at end of file diff --git a/README.md b/README.md index d26fadd..3b85f85 100644 --- a/README.md +++ b/README.md @@ -36,22 +36,14 @@ - Wiki: Other misc tutorials and resources are [on the Wiki](https://github.com/pixijs/pixi.js/wiki/Resources). ### Community ### -- Forums: Check out the [forum] (http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow] (http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. +- Forums: Check out the [forum](http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow](http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. - Inspiration: Check out the [gallery](http://www.pixijs.com/gallery) to see some of the amazing things people have created! - Chat: You can join us on [Gitter](https://gitter.im/pixijs/pixi.js) To chat about Pixi. We also now have a Slack channel. If you would like to join it please Send me an email (mat@goodboydigital.com) and I will invite you in. ### Setup ### -It's easy to get started with Pixi.js! Simply grab the pre-built versions from here: - -Release Branch - Nice and stable Pixi.js -- Unminified: [http://pixijs.download/release/pixi.js] -- Minified: [http://pixijs.download/release/pixi.min.js] - -Develop Branch - The bleeding edge version of Pixi.js -- Unminified: [http://pixijs.download/dev/pixi.js] -- Minified: [http://pixijs.download/dev/pixi.min.js] +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. @@ -70,10 +62,10 @@ #### CDN Install (via cdnjs) ```html - + ``` -_Note: `4.2.2` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ +_Note: `4.4.3` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ ### Demos ### @@ -130,7 +122,7 @@ // and the root stage PIXI.Container. var app = new PIXI.Application(); -// The application will create a canvas element for you that you +// The application will create a canvas element for you that you // can then insert into the DOM. document.body.appendChild(app.view); @@ -150,7 +142,7 @@ // Add the bunny to the scene we are building. app.stage.addChild(bunny); - + // Listen for frame updates app.ticker.add(function() { // each frame we spin the bunny around a bit diff --git a/bower.json b/bower.json deleted file mode 100644 index 41b668c..0000000 --- a/bower.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "pixi.js", - "main": "dist/pixi.min.js", - "ignore": [ - "**/.*", - "docs", - "node_modules", - "bower_components", - "test", - "scripts", - "bower.json", - "inch.json" - ], - "dependencies": { - }, - "devDependencies": { - } -} diff --git a/package.json b/package.json index d35cbb2..308469f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.3.5", + "version": "4.4.4", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ @@ -40,11 +40,11 @@ "lib": "babel src --out-dir lib -s", "predocs": "rimraf docs/**", "docs": "jsdoc -c scripts/jsdoc.conf.json -R README.md", - "publish:patch": "npm version patch --no-git-tag-version && npm publish", - "publish:minor": "npm version minor --no-git-tag-version && npm publish", - "publish:major": "npm version major --no-git-tag-version && npm publish", + "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", - "postpublish": "node scripts/release.js" + "postpublish": "git push && git push --tags" }, "files": [ "dist/", @@ -72,7 +72,6 @@ "electron": "^1.4.15", "eslint": "^3.5.0", "floss": "^2.0.1", - "gh-pages": "^0.11.0", "jaguarjs-jsdoc": "^1.0.1", "js-md5": "^0.4.1", "jsdoc": "^3.4.2", diff --git a/scripts/jsdoc.conf.json b/scripts/jsdoc.conf.json index 553e61c..3f52180 100644 --- a/scripts/jsdoc.conf.json +++ b/scripts/jsdoc.conf.json @@ -43,7 +43,7 @@ }, "markdown" : { "parser" : "gfm", - "hardwrap" : true + "hardwrap" : false }, "opts": { "encoding" : "utf8", diff --git a/scripts/release.js b/scripts/release.js deleted file mode 100644 index 156620c..0000000 --- a/scripts/release.js +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env node -'use strict'; - -// Publish script to push releases of the bin files -// the normally are gitignored -const ghpages = require('gh-pages'); -const path = require('path'); -const packageInfo = require(path.join(__dirname, '..', 'package.json')); - -const options = { - src: [ - 'dist/**/*', - 'lib/**/*', - 'src/**/*', - 'scripts/*', - 'scripts/renders/*', - 'test/**/*', - '*.json', - '*.md', - 'LICENSE', - '.eslintrc', - '.editorconfig', - '.travis.yml', - '.babelrc', - ], - dotfiles: true, - branch: 'release', - message: packageInfo.version, - logger: console.log.bind(console), -}; - -ghpages.publish(process.cwd(), options, (err) => -{ - if (err) - { - console.log(err); - process.exit(1); - - return; - } - - process.exit(0); -}); diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index f16bcb1..220427a 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -21,7 +21,7 @@ const DIV_HOOK_ZINDEX = 2; /** - * The Accessibility manager reacreates the ability to tab and and have content read by screen + * The Accessibility manager recreates the ability to tab and and have content read by screen * readers. This is very important as it can possibly help people with disabilities access pixi * content. * diff --git a/src/accessibility/index.js b/src/accessibility/index.js index 568cfb1..0ae108f 100644 --- a/src/accessibility/index.js +++ b/src/accessibility/index.js @@ -1,4 +1,9 @@ /** + * This namespace contains a renderer plugin for interaction accessibility for end-users + * with physical impairments which require screen-renders, keyboard navigation, etc. + * + * Do not instantiate this plugin directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. * @namespace PIXI.accessibility */ export { default as accessibleTarget } from './accessibleTarget'; diff --git a/src/core/Application.js b/src/core/Application.js index 5340578..2fcc729 100644 --- a/src/core/Application.js +++ b/src/core/Application.js @@ -1,6 +1,8 @@ import { autoDetectRenderer } from './autoDetectRenderer'; import Container from './display/Container'; import { shared, Ticker } from './ticker'; +import settings from './settings'; +import { UPDATE_PRIORITY } from './const'; /** * Convenience class to create a new PIXI application. @@ -22,28 +24,52 @@ */ export default class Application { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experience unexplained flickering try setting this to true. - * @param {boolean} [useSharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedLoader=false] - `true` to use PIXI.loaders.shared, `false` to create new Loader. */ - constructor(width, height, options, noWebGL, useSharedTicker = false) + constructor(options, arg2, arg3, arg4, arg5) { + // Support for constructor(width, height, options, noWebGL, useSharedTicker) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + forceCanvas: !!arg4, + sharedTicker: !!arg5, + }, arg3); + } + + /** + * The default options, so we mixin functionality later. + * @member {object} + * @protected + */ + this._options = options = Object.assign({ + sharedTicker: false, + forceCanvas: false, + sharedLoader: false, + }, options); + /** * WebGL renderer if available, otherwise CanvasRenderer * @member {PIXI.WebGLRenderer|PIXI.CanvasRenderer} */ - this.renderer = autoDetectRenderer(width, height, options, noWebGL); + this.renderer = autoDetectRenderer(options); /** * The root display container that's rendered. @@ -63,7 +89,7 @@ * @member {PIXI.ticker.Ticker} * @default PIXI.ticker.shared */ - this.ticker = useSharedTicker ? shared : new Ticker(); + this.ticker = options.sharedTicker ? shared : new Ticker(); // Start the rendering this.start(); @@ -78,7 +104,7 @@ this._ticker = ticker; if (ticker) { - ticker.add(this.render, this); + ticker.add(this.render, this, UPDATE_PRIORITY.LOW); } } get ticker() // eslint-disable-line require-jsdoc @@ -136,13 +162,18 @@ */ destroy(removeView) { - this.stop(); + const oldTicker = this._ticker; + this.ticker = null; + oldTicker.destroy(); + this.stage.destroy(); this.stage = null; this.renderer.destroy(removeView); this.renderer = null; + + this._options = null; } } diff --git a/src/core/autoDetectRenderer.js b/src/core/autoDetectRenderer.js index 8960425..9ec0364 100644 --- a/src/core/autoDetectRenderer.js +++ b/src/core/autoDetectRenderer.js @@ -2,6 +2,7 @@ import CanvasRenderer from './renderers/canvas/CanvasRenderer'; import WebGLRenderer from './renderers/webgl/WebGLRenderer'; +// eslint-disable-next-line valid-jsdoc /** * This helper function will automatically detect which renderer you should be using. * WebGL is the preferred renderer as it is a lot faster. If webGL is not supported by @@ -9,24 +10,32 @@ * * @memberof PIXI * @function autoDetectRenderer - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @return {PIXI.WebGLRenderer|PIXI.CanvasRenderer} Returns WebGL renderer if available, otherwise CanvasRenderer */ -export function autoDetectRenderer(width = 800, height = 600, options, noWebGL) +export function autoDetectRenderer(options, arg1, arg2, arg3) { - if (!noWebGL && utils.isWebGLSupported()) + // Backward-compatible support for noWebGL option + let forceCanvas = options && options.forceCanvas; + + if (arg3 !== undefined) { - return new WebGLRenderer(width, height, options); + forceCanvas = arg3; } - return new CanvasRenderer(width, height, options); + if (!forceCanvas && utils.isWebGLSupported()) + { + return new WebGLRenderer(options, arg1, arg2); + } + + return new CanvasRenderer(options, arg1, arg2); } diff --git a/src/core/const.js b/src/core/const.js index b9e99ae..2c68dee 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -341,3 +341,27 @@ LINEAR_VERTICAL: 0, LINEAR_HORIZONTAL: 1, }; + +/** + * Represents the update priorities used by internal PIXI classes when registered with + * the {@link PIXI.ticker.Ticker} object. Higher priority items are updated first and lower + * priority items, such as render, should go later. + * + * @static + * @constant + * @name UPDATE_PRIORITY + * @memberof PIXI + * @type {object} + * @property {number} INTERACTION=50 Highest priority, used for {@link PIXI.interaction.InteractionManager} + * @property {number} HIGH=25 High priority updating, {@link PIXI.VideoBaseTexture} and {@link PIXI.extras.AnimatedSprite} + * @property {number} NORMAL=0 Default priority for ticker events, see {@link PIXI.ticker.Ticker#add}. + * @property {number} LOW=-25 Low priority used for {@link PIXI.Application} rendering. + * @property {number} UTILITY=-50 Lowest priority used for {@link PIXI.prepare.BasePrepare} utility. + */ +export const UPDATE_PRIORITY = { + INTERACTION: 50, + HIGH: 25, + NORMAL: 0, + LOW: -25, + UTILITY: -50, +}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 4e6c77d..decb42a 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -13,7 +13,6 @@ * * @class * @extends EventEmitter - * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ export default class DisplayObject extends EventEmitter @@ -113,6 +112,29 @@ * @private */ this._mask = null; + + /** + * If the object has been destroyed via destroy(). If true, it should not be used. + * + * @member {boolean} + * @private + * @readonly + */ + this._destroyed = false; + + /** + * Fired when this DisplayObject is added to a Container. + * + * @event PIXI.DisplayObject#added + * @param {PIXI.Container} container - The container added to. + */ + + /** + * Fired when this DisplayObject is removed from a Container. + * + * @event PIXI.DisplayObject#removed + * @param {PIXI.Container} container - The container removed from. + */ } /** @@ -403,6 +425,8 @@ this.interactive = false; this.interactiveChildren = false; + + this._destroyed = true; } /** diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index aafdfe2..f787c4a 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -792,8 +792,8 @@ sprite.worldAlpha = this.worldAlpha * sprite.alpha; sprite.blendMode = this.blendMode; - sprite.texture._frame.width = rect.width; - sprite.texture._frame.height = rect.height; + sprite._texture._frame.width = rect.width; + sprite._texture._frame.height = rect.height; sprite.transform.worldTransform = this.transform.worldTransform; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index a24c775..b9f192d 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -19,11 +19,12 @@ */ export default class SystemRenderer extends EventEmitter { + // eslint-disable-next-line valid-jsdoc /** * @param {string} system - The name of the system this renderer is for. - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -37,27 +38,31 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(system, screenWidth, screenHeight, options) + constructor(system, options, arg2, arg3) { super(); sayHello(system); - // prepare options - if (options) + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') { - for (const i in settings.RENDER_OPTIONS) - { - if (typeof options[i] === 'undefined') - { - options[i] = settings.RENDER_OPTIONS[i]; - } - } + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); } - else - { - options = settings.RENDER_OPTIONS; - } + + // Add the default render options + options = Object.assign({}, settings.RENDER_OPTIONS, options); + + /** + * The supplied constructor options. + * + * @member {Object} + * @readOnly + */ + this.options = options; /** * The type of the renderer. @@ -75,7 +80,7 @@ * * @member {PIXI.Rectangle} */ - this.screen = new Rectangle(0, 0, screenWidth || 800, screenHeight || 600); + this.screen = new Rectangle(0, 0, options.width, options.height); /** * The canvas element that everything is drawn to @@ -279,6 +284,8 @@ this.blendModes = null; + this.options = null; + this.preserveDrawingBuffer = false; this.clearBeforeRender = false; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index e84e49c..691caf0 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -17,10 +17,11 @@ */ export default class CanvasRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -34,9 +35,9 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('Canvas', screenWidth, screenHeight, options); + super('Canvas', options, arg2, arg3); this.type = RENDERER_TYPE.CANVAS; @@ -96,7 +97,19 @@ this.context = null; this.renderingToScreen = false; - this.resize(screenWidth, screenHeight); + this.resize(this.options.width, this.options.height); + + /** + * Fired after rendering finishes. + * + * @event PIXI.CanvasRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.CanvasRenderer#prerender + */ } /** @@ -299,4 +312,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.CanvasRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.CanvasExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.CanvasPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.CanvasRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index e1cf7cd..f6fe123 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -31,11 +31,12 @@ */ export default class WebGLRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** * - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -55,9 +56,9 @@ * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('WebGL', screenWidth, screenHeight, options); + super('WebGL', options, arg2, arg3); /** * The type of this renderer as a standardised const @@ -89,12 +90,6 @@ }; - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ this._backgroundColorRgba[3] = this.transparent ? 0 : 1; this.globalUniforms = new UniformGroup({ @@ -117,6 +112,13 @@ this.initPlugins(); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ if(options.context) { this.context.initFromContext(options.context); @@ -169,6 +171,25 @@ } return this; + + /** + * Fired after rendering finishes. + * + * @event PIXI.WebGLRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.WebGLRenderer#prerender + */ + + /** + * Fired when the WebGL context is set. + * + * @event PIXI.WebGLRenderer#context + * @param {WebGLRenderingContext} gl - WebGL context. + */ } /** @@ -295,4 +316,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.WebGLRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.WebGLRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(WebGLRenderer); diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index 4eb6128..fa3d883 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -47,6 +47,14 @@ * @member {boolean} */ this.enabled = true; + + /** + * If enabled, pixi will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; } /** diff --git a/src/core/renderers/webgl/systems/ContextSystem.js b/src/core/renderers/webgl/systems/ContextSystem.js index ff958c7..8df85ea 100644 --- a/src/core/renderers/webgl/systems/ContextSystem.js +++ b/src/core/renderers/webgl/systems/ContextSystem.js @@ -75,7 +75,8 @@ */ createContext(canvas, options) { - var gl = canvas.getContext('webgl', options) || + var gl = //canvas.getContext('webgl2', options) || + canvas.getContext('webgl', options) || canvas.getContext('experimental-webgl', options); if (!gl) diff --git a/src/core/renderers/webgl/systems/FilterSystem.js b/src/core/renderers/webgl/systems/FilterSystem.js index f47335d..37e28d2 100644 --- a/src/core/renderers/webgl/systems/FilterSystem.js +++ b/src/core/renderers/webgl/systems/FilterSystem.js @@ -111,7 +111,7 @@ // TODO we should fit the rect around the transform.. } - else + else if (filters[0].autoFit) { sourceFrame.fit(filterData.stack[0].destinationFrame); } diff --git a/src/core/renderers/webgl/systems/shader/shader/mapType.js b/src/core/renderers/webgl/systems/shader/shader/mapType.js index 581dace..917538a 100644 --- a/src/core/renderers/webgl/systems/shader/shader/mapType.js +++ b/src/core/renderers/webgl/systems/shader/shader/mapType.js @@ -15,7 +15,7 @@ } } - return GL_TABLE[type]; + return GL_TABLE[type]; }; var GL_TABLE = null; diff --git a/src/core/settings.js b/src/core/settings.js index 18c5877..38ef45c 100644 --- a/src/core/settings.js +++ b/src/core/settings.js @@ -2,6 +2,15 @@ import canUploadSameBuffer from './utils/canUploadSameBuffer'; /** + * User's customizable globals for overriding the default PIXI settings, such + * as a renderer's default resolution, framerate, float percision, etc. + * @example + * // Use the native window resolution as the default resolution + * // will support high-density displays when rendering + * PIXI.settings.RESOLUTION = window.devicePixelRatio. + * + * // Disable interpolation when scaling, will make texture be pixelated + * PIXI.settings.SCALE_MODE = PIXI.SCALE_MODES.NEAREST; * @namespace PIXI.settings */ export default { @@ -101,6 +110,9 @@ * @property {boolean} clearBeforeRender=true * @property {boolean} preserveDrawingBuffer=false * @property {boolean} roundPixels=false + * @property {number} width=800 + * @property {number} height=600 + * @property {boolean} legacy=false */ RENDER_OPTIONS: { view: null, @@ -112,6 +124,9 @@ clearBeforeRender: true, preserveDrawingBuffer: false, roundPixels: false, + width: 800, + height: 600, + legacy: false, }, /** diff --git a/src/core/shader/Program.js b/src/core/shader/Program.js index c0b538b..07b4398 100644 --- a/src/core/shader/Program.js +++ b/src/core/shader/Program.js @@ -1,6 +1,6 @@ import extractUniformsFromSrc from './extractUniformsFromSrc'; import generateUniformsSync from './generateUniformsSync'; -import glCore from 'pixi-gl-core'; +import shaderUtils from '../renderers/webgl/systems/shader/shader'; import { ProgramCache } from '../utils'; import getTestContext from '../utils/getTestContext'; @@ -64,14 +64,17 @@ } else { - vertexSrc = glCore.shader.setPrecision(vertexSrc, 'mediump'); - fragmentSrc = glCore.shader.setPrecision(fragmentSrc, 'mediump'); + vertexSrc = shaderUtils.setPrecision(vertexSrc, 'mediump'); + fragmentSrc = shaderUtils.setPrecision(fragmentSrc, 'mediump'); - const program = glCore.shader.compileProgram(gl, vertexSrc, fragmentSrc); + const program = shaderUtils.compileProgram(gl, vertexSrc, fragmentSrc); + console.log("<<<<<<>>><<"); this.attributeData = this.getAttributeData(program, gl); this.uniformData = this.getUniformData(program, gl); + console.log(this.uniformData); + //gl.deleteProgram(program); } } @@ -95,13 +98,13 @@ for (let i = 0; i < totalAttributes; i++) { const attribData = gl.getActiveAttrib(program, i); - const type = glCore.shader.mapType(gl, attribData.type); + const type = shaderUtils.mapType(gl, attribData.type); /*eslint-disable */ const data = { type: type, name: attribData.name, - size: glCore.shader.mapSize(type), + size: shaderUtils.mapSize(type), location: 0, }; /*eslint-enable */ @@ -137,13 +140,14 @@ // TODO expose this as a prop? // const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); - const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); + //const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); for (let i = 0; i < totalUniforms; i++) { const uniformData = gl.getActiveUniform(program, i); const name = uniformData.name.replace(/\[.*?\]/, ''); - const type = glCore.shader.mapType(gl, uniformData.type); + const type = shaderUtils.mapType(gl, uniformData.type); + console.log('mapping ' + uniformData.type + ' to ' + type); // if (!name.match(maskRegex)) { @@ -151,7 +155,7 @@ uniforms[name] = { type: type, size: uniformData.size, - value: glCore.shader.defaultValue(type, uniformData.size), + value: shaderUtils.defaultValue(type, uniformData.size), }; /*eslint-enable */ } diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index e387e99..b57d095 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -147,12 +147,12 @@ // so if _width is 0 then width was not set.. if (this._width) { - this.scale.x = sign(this.scale.x) * this._width / this.texture.orig.width; + this.scale.x = sign(this.scale.x) * this._width / this._texture.orig.width; } if (this._height) { - this.scale.y = sign(this.scale.y) * this._height / this.texture.orig.height; + this.scale.y = sign(this.scale.y) * this._height / this._texture.orig.height; } } diff --git a/src/core/sprites/canvas/CanvasTinter.js b/src/core/sprites/canvas/CanvasTinter.js index 27e5e0c..fd9de5d 100644 --- a/src/core/sprites/canvas/CanvasTinter.js +++ b/src/core/sprites/canvas/CanvasTinter.js @@ -18,7 +18,7 @@ */ getTintedTexture: (sprite, color) => { - const texture = sprite.texture; + const texture = sprite._texture; color = CanvasTinter.roundColor(color); diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 5a4edda..8b9b797 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -344,6 +344,7 @@ const texture = this._texture; const style = this._style; + const padding = style.trim ? 0 : style.padding; texture.baseTexture.valid = true; texture.baseTexture.resolution = this.resolution; @@ -353,11 +354,11 @@ texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - texture.trim.x = -style.padding; - texture.trim.y = -style.padding; + texture.trim.x = -padding; + texture.trim.y = -padding; - texture.orig.width = texture._frame.width - (style.padding * 2); - texture.orig.height = texture._frame.height - (style.padding * 2); + texture.orig.width = texture._frame.width - (padding * 2); + texture.orig.height = texture._frame.height - (padding * 2); // call sprite onTextureUpdate to update scale if _width or _height were set this._onTextureUpdate(); diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index 853da3d..8a9c34b 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -114,6 +114,38 @@ this.cacheId = null; this.validate(); + + /** + * Fired when a not-immediately-available source finishes loading. + * + * @protected + * @event PIXI.BaseTexture#loaded + * @param {PIXI.BaseTexture} baseTexture - Resource loaded. + */ + + /** + * Fired when a not-immediately-available source fails to load. + * + * @protected + * @event PIXI.BaseTexture#error + * @param {PIXI.BaseTexture} baseTexture - Resource errored. + */ + + /** + * Fired when BaseTexture is updated. + * + * @protected + * @event PIXI.BaseTexture#update + * @param {PIXI.BaseTexture} baseTexture - Instance of texture being updated. + */ + + /** + * Fired when BaseTexture is destroyed. + * + * @protected + * @event PIXI.BaseTexture#dispose + * @param {PIXI.BaseTexture} baseTexture - Instance of texture being destroyed. + */ } setResource(resource) diff --git a/src/core/textures/Spritesheet.js b/src/core/textures/Spritesheet.js index d469c62..9b24f27 100644 --- a/src/core/textures/Spritesheet.js +++ b/src/core/textures/Spritesheet.js @@ -208,6 +208,7 @@ // lets also add the frame to pixi's global cache for fromFrame and fromImage functions TextureCache[i] = this.textures[i]; + this.textures[i].textureCacheId = i; } frameIndex++; diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 0a2c379..2213087 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -147,9 +147,9 @@ /** * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. * - * @event update - * @memberof PIXI.Texture# + * @event PIXI.Texture#update * @protected + * @param {PIXI.Texture} texture - Instance of texture being updated. */ this._updateID = 0; @@ -159,6 +159,15 @@ * @type {Object} */ this.transform = null; + + /** + * The id under which this Texture has been added to the texture cache. This is + * automatically set in certain cases, but may not always be accurate, particularly if + * the texture is in the cache under multiple ids. + * + * @member {string} + */ + this.textureCacheId = null; } /** @@ -225,11 +234,17 @@ this._uvs = null; this.trim = null; this.orig = null; + this.textureCacheId = null; this.valid = false; - this.off('dispose', this.dispose, this); - this.off('update', this.update, this); + for (const prop in TextureCache) + { + if (TextureCache[prop] === this) + { + delete TextureCache[prop]; + } + } } /** @@ -315,14 +330,15 @@ */ static fromLoader(source, imageUrl, name) { - // console.log('added from loader...') + // console.log('added from loader...') const resource = new ImageResource(source);//.from(imageUrl, crossorigin);// document.createElement('img'); - // console.log('base resource ' + resource.width); + // console.log('base resource ' + resource.width); const baseTexture = new BaseTexture(resource, settings.SCALE_MODE, getResolutionOfUrl(imageUrl)); + /// console.log('base width ' + baseTexture.width); const texture = new Texture(baseTexture); @@ -335,6 +351,7 @@ // lets also add the frame to pixi's global cache for fromFrame and fromImage fucntions BaseTextureCache[name] = baseTexture; TextureCache[name] = texture; + texture.textureCacheId = name; // also add references by url if they are different. if (name !== imageUrl) @@ -355,6 +372,10 @@ */ static addTextureToCache(texture, id) { + if (!texture.textureCacheId) + { + texture.textureCacheId = id; + } TextureCache[id] = texture; } @@ -495,6 +516,7 @@ */ Texture.EMPTY = new Texture(new BaseTexture()); removeAllHandlers(Texture.EMPTY); +removeAllHandlers(Texture.EMPTY.baseTexture); /** * A white texture of 10x10 size, used for graphics and other things @@ -505,3 +527,4 @@ */ Texture.WHITE = createWhiteTexture(); removeAllHandlers(Texture.WHITE); +removeAllHandlers(Texture.WHITE.baseTexture); diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index 59eb9fd..a9b9f51 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -1,6 +1,7 @@ import BaseTexture from './BaseTexture'; import { uid, BaseTextureCache } from '../utils'; -import * as ticker from '../ticker'; +import { shared } from '../ticker'; +import { UPDATE_PRIORITY } from '../const'; /** * A texture of a [playing] Video. @@ -125,7 +126,7 @@ if (!this._isAutoUpdating && this.autoUpdate) { - ticker.shared.add(this.update, this); + shared.add(this.update, this, UPDATE_PRIORITY.HIGH); this._isAutoUpdating = true; } } @@ -139,7 +140,7 @@ { if (this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); this._isAutoUpdating = false; } } @@ -187,7 +188,7 @@ { if (this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); } if (this.source && this.source._pixiId) @@ -281,12 +282,12 @@ if (!this._autoUpdate && this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); this._isAutoUpdating = false; } else if (this._autoUpdate && !this._isAutoUpdating) { - ticker.shared.add(this.update, this); + shared.add(this.update, this, UPDATE_PRIORITY.HIGH); this._isAutoUpdating = true; } } diff --git a/src/core/ticker/Ticker.js b/src/core/ticker/Ticker.js index 17a1517..40df138 100644 --- a/src/core/ticker/Ticker.js +++ b/src/core/ticker/Ticker.js @@ -1,12 +1,10 @@ import settings from '../settings'; -import EventEmitter from 'eventemitter3'; - -// Internal event used by composed emitter -const TICK = 'tick'; +import { UPDATE_PRIORITY } from '../const'; +import TickerListener from './TickerListener'; /** * A Ticker class that runs an update loop that other objects listen to. - * This class is composed around an EventEmitter object to add listeners + * This class is composed around listeners * meant for execution on the next requested animation frame. * Animation frames are requested only when necessary, * e.g. When the ticker is started and the emitter has listeners. @@ -22,10 +20,11 @@ constructor() { /** - * Internal emitter used to fire 'tick' event + * The first listener. All new listeners added are chained on this. * @private + * @type {TickerListener} */ - this._emitter = new EventEmitter(); + this._head = new TickerListener(null, null, Infinity); /** * Internal current frame request ID @@ -131,7 +130,7 @@ // Invoke listeners now this.update(time); // Listener side effects may have modified ticker state. - if (this.started && this._requestId === null && this._emitter.listeners(TICK, true)) + if (this.started && this._requestId === null && this._head.next) { this._requestId = requestAnimationFrame(this._tick); } @@ -148,7 +147,7 @@ */ _requestIfNeeded() { - if (this._requestId === null && this._emitter.listeners(TICK, true)) + if (this._requestId === null && this._head.next) { // ensure callbacks get correct delta this.lastTime = performance.now(); @@ -193,35 +192,72 @@ } /** - * Calls {@link module:eventemitter3.EventEmitter#on} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. + * Register a handler for tick events. Calls continuously unless + * it is removed or the ticker is stopped. * * @param {Function} fn - The listener function to be added for updates * @param {Function} [context] - The listener context + * @param {number} [priority=PIXI.UPDATE_PRIORITY.NORMAL] - The priority for emitting * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - add(fn, context) + add(fn, context, priority = UPDATE_PRIORITY.NORMAL) { - this._emitter.on(TICK, fn, context); - - this._startIfPossible(); - - return this; + return this._addListener(new TickerListener(fn, context, priority)); } /** - * Calls {@link module:eventemitter3.EventEmitter#once} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. + * Add a handler for the tick event which is only execute once. * * @param {Function} fn - The listener function to be added for one update * @param {Function} [context] - The listener context + * @param {number} [priority=PIXI.UPDATE_PRIORITY.NORMAL] - The priority for emitting * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - addOnce(fn, context) + addOnce(fn, context, priority = UPDATE_PRIORITY.NORMAL) { - this._emitter.once(TICK, fn, context); + return this._addListener(new TickerListener(fn, context, priority, true)); + } + + /** + * Internally adds the event handler so that it can be sorted by priority. + * Priority allows certain handler (user, AnimatedSprite, Interaction) to be run + * before the rendering. + * + * @private + * @param {TickerListener} listener - Current listener being added. + * @returns {PIXI.ticker.Ticker} This instance of a ticker + */ + _addListener(listener) + { + // For attaching to head + let current = this._head.next; + let previous = this._head; + + // Add the first item + if (!current) + { + listener.connect(previous); + } + else + { + // Go from highest to lowest priority + while (current) + { + if (listener.priority >= current.priority) + { + listener.connect(previous); + break; + } + previous = current; + current = current.next; + } + + // Not yet connected + if (!listener.previous) + { + listener.connect(previous); + } + } this._startIfPossible(); @@ -229,19 +265,33 @@ } /** - * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. - * It checks if the emitter has listeners for 'tick' event. - * If it does, then it cancels the animation frame. + * Removes any handlers matching the function and context parameters. + * If no handlers are left after removing, then it cancels the animation frame. * - * @param {Function} [fn] - The listener function to be removed + * @param {Function} fn - The listener function to be removed * @param {Function} [context] - The listener context to be removed * @returns {PIXI.ticker.Ticker} This instance of a ticker */ remove(fn, context) { - this._emitter.off(TICK, fn, context); + let listener = this._head.next; - if (!this._emitter.listeners(TICK, true)) + while (listener) + { + // We found a match, lets remove it + // no break to delete all possible matches + // incase a listener was added 2+ times + if (listener.match(fn, context)) + { + listener = listener.destroy(); + } + else + { + listener = listener.next; + } + } + + if (!this._head.next) { this._cancelIfNeeded(); } @@ -276,6 +326,25 @@ } /** + * Destroy the ticker and don't use after this. Calling + * this method removes all references to internal events. + */ + destroy() + { + this.stop(); + + let listener = this._head.next; + + while (listener) + { + listener = listener.destroy(true); + } + + this._head.destroy(); + this._head = null; + } + + /** * Triggers an update. An update entails setting the * current {@link PIXI.ticker.Ticker#elapsedMS}, * the current {@link PIXI.ticker.Ticker#deltaTime}, @@ -320,8 +389,22 @@ this.deltaTime = elapsedMS * settings.TARGET_FPMS * this.speed; + // Cache a local reference, in-case ticker is destroyed + // during the emit, we can still check for head.next + const head = this._head; + // Invoke listeners added to internal emitter - this._emitter.emit(TICK, this.deltaTime); + let listener = head.next; + + while (listener) + { + listener = listener.emit(this.deltaTime); + } + + if (!head.next) + { + this._cancelIfNeeded(); + } } else { diff --git a/src/core/ticker/TickerListener.js b/src/core/ticker/TickerListener.js new file mode 100644 index 0000000..2bedb34 --- /dev/null +++ b/src/core/ticker/TickerListener.js @@ -0,0 +1,158 @@ +/** + * Internal class for handling the priority sorting of ticker handlers. + * + * @private + * @class + * @memberof PIXI.ticker + */ +export default class TickerListener +{ + /** + * Constructor + * + * @param {Function} fn - The listener function to be added for one update + * @param {Function} [context=null] - The listener context + * @param {number} [priority=0] - The priority for emitting + * @param {boolean} [once=false] - If the handler should fire once + */ + constructor(fn, context = null, priority = 0, once = false) + { + /** + * The handler function to execute. + * @member {Function} + */ + this.fn = fn; + + /** + * The calling to execute. + * @member {Function} + */ + this.context = context; + + /** + * The current priority. + * @member {number} + */ + this.priority = priority; + + /** + * If this should only execute once. + * @member {boolean} + */ + this.once = once; + + /** + * The next item in chain. + * @member {TickerListener} + */ + this.next = null; + + /** + * The previous item in chain. + * @member {TickerListener} + */ + this.previous = null; + + /** + * `true` if this listener has been destroyed already. + * @member {boolean} + * @private + */ + this._destroyed = false; + } + + /** + * Simple compare function to figure out if a function and context match. + * + * @param {Function} fn - The listener function to be added for one update + * @param {Function} context - The listener context + * @return {boolean} `true` if the listener match the arguments + */ + match(fn, context) + { + context = context || null; + + return this.fn === fn && this.context === context; + } + + /** + * Emit by calling the current function. + * @param {number} deltaTime - time since the last emit. + * @return {TickerListener} Next ticker + */ + emit(deltaTime) + { + if (this.context) + { + this.fn.call(this.context, deltaTime); + } + else + { + this.fn(deltaTime); + } + + if (this.once) + { + this.destroy(); + } + + const redirect = this.next; + + // Soft-destroying should remove + // the next reference + if (this._destroyed) + { + this.next = null; + } + + return redirect; + } + + /** + * Connect to the list. + * @param {TickerListener} previous - Input node, previous listener + */ + connect(previous) + { + this.previous = previous; + if (previous.next) + { + previous.next.previous = this; + } + this.next = previous.next; + previous.next = this; + } + + /** + * Destroy and don't use after this. + * @param {boolean} [hard = false] `true` to remove the `next` reference, this + * is considered a hard destroy. Soft destroy maintains the next reference. + * @return {TickerListener} The listener to redirect while emitting or removing. + */ + destroy(hard = false) + { + this._destroyed = true; + this.fn = null; + this.context = null; + + // Disconnect, hook up next and previous + if (this.previous) + { + this.previous.next = this.next; + } + + if (this.next) + { + this.next.previous = this.previous; + } + + // Redirect to the next item + const redirect = this.previous; + + // Remove references + this.next = hard ? null : redirect; + this.previous = null; + + return redirect; + } +} diff --git a/src/core/ticker/index.js b/src/core/ticker/index.js index 4b3017c..c3020d4 100644 --- a/src/core/ticker/index.js +++ b/src/core/ticker/index.js @@ -45,8 +45,25 @@ const shared = new Ticker(); shared.autoStart = true; +shared.destroy = () => +{ + // protect destroying shared ticker + // this is used by other internal systems + // like AnimatedSprite and InteractionManager +}; /** + * This namespace contains an API for interacting with PIXI's internal global update loop. + * + * This ticker is used for rendering, {@link PIXI.extras.AnimatedSprite AnimatedSprite}, + * {@link PIXI.interaction.InteractionManager InteractionManager} and many other time-based PIXI systems. + * @example + * const ticker = new PIXI.ticker.Ticker(); + * ticker.stop(); + * ticker.add((deltaTime) => { + * // do something every frame + * }); + * ticker.start(); * @namespace PIXI.ticker */ export { shared, Ticker }; diff --git a/src/core/utils/index.js b/src/core/utils/index.js index be44750..7441f7b 100644 --- a/src/core/utils/index.js +++ b/src/core/utils/index.js @@ -9,6 +9,21 @@ let saidHello = false; /** + * Generalized convenience utilities for PIXI. + * @example + * // Extend PIXI's internal Event Emitter. + * class MyEmitter extends PIXI.utils.EventEmitter { + * constructor() { + * super(); + * console.log("Emitter created!"); + * } + * } + * + * // Get info on current device + * console.log(PIXI.utils.isMobile); + * + * // Convert hex color to string + * console.log(PIXI.utils.hex2string(0xff00ff)); // returns: "#ff00ff" * @namespace PIXI.utils */ export { diff --git a/src/deprecation.js b/src/deprecation.js index b9c73b3..33daeec 100644 --- a/src/deprecation.js +++ b/src/deprecation.js @@ -918,6 +918,35 @@ }); /** + * @method + * @name PIXI.prepare.BasePrepare#register + * @see PIXI.prepare.BasePrepare#registerFindHook + * @deprecated since version 4.4.2 + * @param {Function} [addHook] - Function call that takes two parameters: `item:*, queue:Array` + * function must return `true` if it was able to add item to the queue. + * @param {Function} [uploadHook] - Function call that takes two parameters: `prepare:CanvasPrepare, item:*` and + * function must return `true` if it was able to handle upload of item. + * @return {PIXI.BasePrepare} Instance of plugin for chaining. + */ +prepare.BasePrepare.prototype.register = function register(addHook, uploadHook) +{ + warn('renderer.plugins.prepare.register is now deprecated, ' + + 'please use renderer.plugins.prepare.registerFindHook & renderer.plugins.prepare.registerUploadHook'); + + if (addHook) + { + this.registerFindHook(addHook); + } + + if (uploadHook) + { + this.registerUploadHook(uploadHook); + } + + return this; +}; + +/** * The number of graphics or textures to upload to the GPU. * * @name PIXI.prepare.canvas.UPLOADS_PER_FRAME @@ -969,67 +998,73 @@ }, }); -Object.defineProperties(loaders.Resource.prototype, { - isJson: { - get() - { - warn('The isJson property is deprecated, please use `resource.type === Resource.TYPE.JSON`.'); +if (loaders.Loader) +{ + const Resource = loaders.Resource; + const Loader = loaders.Loader; - return this.type === loaders.Loader.Resource.TYPE.JSON; + Object.defineProperties(Resource.prototype, { + isJson: { + get() + { + warn('The isJson property is deprecated, please use `resource.type === Resource.TYPE.JSON`.'); + + return this.type === Resource.TYPE.JSON; + }, }, - }, - isXml: { - get() - { - warn('The isXml property is deprecated, please use `resource.type === Resource.TYPE.XML`.'); + isXml: { + get() + { + warn('The isXml property is deprecated, please use `resource.type === Resource.TYPE.XML`.'); - return this.type === loaders.Loader.Resource.TYPE.XML; + return this.type === Resource.TYPE.XML; + }, }, - }, - isImage: { - get() - { - warn('The isImage property is deprecated, please use `resource.type === Resource.TYPE.IMAGE`.'); + isImage: { + get() + { + warn('The isImage property is deprecated, please use `resource.type === Resource.TYPE.IMAGE`.'); - return this.type === loaders.Loader.Resource.TYPE.IMAGE; + return this.type === Resource.TYPE.IMAGE; + }, }, - }, - isAudio: { - get() - { - warn('The isAudio property is deprecated, please use `resource.type === Resource.TYPE.AUDIO`.'); + isAudio: { + get() + { + warn('The isAudio property is deprecated, please use `resource.type === Resource.TYPE.AUDIO`.'); - return this.type === loaders.Loader.Resource.TYPE.AUDIO; + return this.type === Resource.TYPE.AUDIO; + }, }, - }, - isVideo: { - get() - { - warn('The isVideo property is deprecated, please use `resource.type === Resource.TYPE.VIDEO`.'); + isVideo: { + get() + { + warn('The isVideo property is deprecated, please use `resource.type === Resource.TYPE.VIDEO`.'); - return this.type === loaders.Loader.Resource.TYPE.VIDEO; + return this.type === Resource.TYPE.VIDEO; + }, }, - }, -}); + }); -Object.defineProperties(loaders.Loader.prototype, { - before: { - get() - { - warn('The before() method is deprecated, please use pre().'); + Object.defineProperties(Loader.prototype, { + before: { + get() + { + warn('The before() method is deprecated, please use pre().'); - return this.pre; + return this.pre; + }, }, - }, - after: { - get() - { - warn('The after() method is deprecated, please use use().'); + after: { + get() + { + warn('The after() method is deprecated, please use use().'); - return this.use; + return this.use; + }, }, - }, -}); + }); +} /** * @name PIXI.interaction.interactiveTarget#defaultCursor diff --git a/src/extract/canvas/CanvasExtract.js b/src/extract/canvas/CanvasExtract.js index 941ca05..3ac5aee 100644 --- a/src/extract/canvas/CanvasExtract.js +++ b/src/extract/canvas/CanvasExtract.js @@ -8,7 +8,7 @@ * An instance of this class is automatically created by default, and can be found at renderer.plugins.extract * * @class - * @memberof PIXI + * @memberof PIXI.extract */ export default class CanvasExtract { @@ -21,9 +21,9 @@ /** * Collection of methods for extracting data (image, pixels, etc.) from a display object or render texture * - * @member {PIXI.CanvasExtract} extract + * @member {PIXI.extract.CanvasExtract} extract * @memberof PIXI.CanvasRenderer# - * @see PIXI.CanvasExtract + * @see PIXI.extract.CanvasExtract */ renderer.extract = this; } diff --git a/src/extract/index.js b/src/extract/index.js index 0f2170d..e9478c1 100644 --- a/src/extract/index.js +++ b/src/extract/index.js @@ -1,2 +1,22 @@ +/** + * This namespace provides renderer-specific plugins for exporting content from a renderer. + * For instance, these plugins can be used for saving an Image, Canvas element or for exporting the raw image data (pixels). + * + * Do not instantiate these plugins directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. + * @example + * // Create a new app (will auto-add extract plugin to renderer) + * const app = new PIXI.Application(); + * + * // Draw a red circle + * const graphics = new PIXI.Graphics() + * .beginFill(0xFF0000) + * .drawCircle(0, 0, 50); + * + * // Render the graphics as an HTMLImageElement + * const image = app.renderer.plugins.extract.image(graphics); + * document.body.appendChild(image); + * @namespace PIXI.extract + */ export { default as webgl } from './webgl/WebGLExtract'; export { default as canvas } from './canvas/CanvasExtract'; diff --git a/src/extract/webgl/WebGLExtract.js b/src/extract/webgl/WebGLExtract.js index 60228bb..1c3dd30 100644 --- a/src/extract/webgl/WebGLExtract.js +++ b/src/extract/webgl/WebGLExtract.js @@ -9,7 +9,7 @@ * An instance of this class is automatically created by default, and can be found at renderer.plugins.extract * * @class - * @memberof PIXI + * @memberof PIXI.extract */ export default class WebGLExtract { @@ -22,9 +22,9 @@ /** * Collection of methods for extracting data (image, pixels, etc.) from a display object or render texture * - * @member {PIXI.WebGLExtract} extract + * @member {PIXI.extract.WebGLExtract} extract * @memberof PIXI.WebGLRenderer# - * @see PIXI.WebGLExtract + * @see PIXI.extract.WebGLExtract */ renderer.extract = this; } diff --git a/src/extras/AnimatedSprite.js b/src/extras/AnimatedSprite.js index 13b804f..5297778 100644 --- a/src/extras/AnimatedSprite.js +++ b/src/extras/AnimatedSprite.js @@ -137,7 +137,7 @@ this.playing = true; if (this._autoUpdate) { - core.ticker.shared.add(this.update, this); + core.ticker.shared.add(this.update, this, core.UPDATE_PRIORITY.HIGH); } } diff --git a/src/extras/TextureTransform.js b/src/extras/TextureTransform.js index 78e6e40..16536d2 100644 --- a/src/extras/TextureTransform.js +++ b/src/extras/TextureTransform.js @@ -65,8 +65,36 @@ } /** + * Multiplies uvs array to transform + * @param {Float32Array} uvs mesh uvs + * @param {Float32Array} [out=uvs] output + * @returns {Float32Array} output + */ + multiplyUvs(uvs, out) + { + if (out === undefined) + { + out = uvs; + } + + const mat = this.mapCoord; + + for (let i = 0; i < uvs.length; i += 2) + { + const x = uvs[i]; + const y = uvs[i + 1]; + + out[i] = (x * mat.a) + (y * mat.c) + mat.tx; + out[i + 1] = (x * mat.b) + (y * mat.d) + mat.ty; + } + + return out; + } + + /** * updates matrices if texture was changed * @param {boolean} forceUpdate if true, matrices will be updated any case + * @returns {boolean} whether or not it was updated */ update(forceUpdate) { @@ -74,13 +102,13 @@ if (!tex || !tex.valid) { - return; + return false; } if (!forceUpdate && this._lastTextureID === tex._updateID) { - return; + return false; } this._lastTextureID = tex._updateID; @@ -110,5 +138,7 @@ frame[3] = (tex._frame.y + tex._frame.height - margin + offset) / texBase.height; this.uClampOffset[0] = offset / texBase.realWidth; this.uClampOffset[1] = offset / texBase.realHeight; + + return true; } } diff --git a/src/extras/TilingSprite.js b/src/extras/TilingSprite.js index 1f6014f..a3f027f 100644 --- a/src/extras/TilingSprite.js +++ b/src/extras/TilingSprite.js @@ -180,9 +180,9 @@ const transform = this.worldTransform; const resolution = renderer.resolution; const baseTexture = texture.baseTexture; - const baseTextureResolution = texture.baseTexture.resolution; - const modX = (this.tilePosition.x / this.tileScale.x) % texture._frame.width; - const modY = (this.tilePosition.y / this.tileScale.y) % texture._frame.height; + const baseTextureResolution = baseTexture.resolution; + const modX = ((this.tilePosition.x / this.tileScale.x) % texture._frame.width) * baseTextureResolution; + const modY = ((this.tilePosition.y / this.tileScale.y) % texture._frame.height) * baseTextureResolution; // create a nice shiny pattern! // TODO this needs to be refreshed if texture changes.. diff --git a/src/extras/cacheAsBitmap.js b/src/extras/cacheAsBitmap.js index 5dd5640..aea06a8 100644 --- a/src/extras/cacheAsBitmap.js +++ b/src/extras/cacheAsBitmap.js @@ -227,7 +227,16 @@ this.transform._parentID = -1; // restore the transform of the cached sprite to avoid the nasty flicker.. - this.updateTransform(); + if (!this.parent) + { + this.parent = renderer._tempDisplayObjectParent; + this.updateTransform(); + this.parent = null; + } + else + { + this.updateTransform(); + } // map the hit test.. this.containsPoint = cachedSprite.containsPoint.bind(cachedSprite); @@ -314,7 +323,17 @@ cachedSprite._bounds = this._bounds; cachedSprite.alpha = cacheAlpha; - this.updateTransform(); + if (!this.parent) + { + this.parent = renderer._tempDisplayObjectParent; + this.updateTransform(); + this.parent = null; + } + else + { + this.updateTransform(); + } + this.updateTransform = this.displayObjectUpdateTransform; this._cacheData.sprite = cachedSprite; @@ -358,9 +377,12 @@ * Destroys the cached object. * * @private + * @param {object|boolean} [options] - Options parameter. A boolean will act as if all options + * have been set to that value. + * Used when destroying containers, see the Container.destroy method. */ -DisplayObject.prototype._cacheAsBitmapDestroy = function _cacheAsBitmapDestroy() +DisplayObject.prototype._cacheAsBitmapDestroy = function _cacheAsBitmapDestroy(options) { this.cacheAsBitmap = false; - this.destroy(); + this.destroy(options); }; diff --git a/src/extras/index.js b/src/extras/index.js index bbd8bbf..a34d7ec 100644 --- a/src/extras/index.js +++ b/src/extras/index.js @@ -1,7 +1,7 @@ /** + * Additional PIXI DisplayObjects for animation, tiling and bitmap text. * @namespace PIXI.extras */ -export { default as TextureTransform } from './TextureTransform'; export { default as AnimatedSprite } from './AnimatedSprite'; export { default as TilingSprite } from './TilingSprite'; export { default as TilingSpriteRenderer } from './webgl/TilingSpriteRenderer'; diff --git a/src/extras/webgl/TilingSpriteRenderer.js b/src/extras/webgl/TilingSpriteRenderer.js index aeec172..eee3b5c 100644 --- a/src/extras/webgl/TilingSpriteRenderer.js +++ b/src/extras/webgl/TilingSpriteRenderer.js @@ -11,7 +11,7 @@ * WebGL renderer plugin for tiling sprites * * @class - * @memberof PIXI + * @memberof PIXI.extras * @extends PIXI.ObjectRenderer */ export default class TilingSpriteRenderer extends core.ObjectRenderer @@ -118,7 +118,7 @@ tempMat.invert(); if (isSimple) { - tempMat.append(uv.mapCoord); + tempMat.prepend(uv.mapCoord); } else { diff --git a/src/filters/displacement/DisplacementFilter.js b/src/filters/displacement/DisplacementFilter.js index d5355c3..b5d030c 100644 --- a/src/filters/displacement/DisplacementFilter.js +++ b/src/filters/displacement/DisplacementFilter.js @@ -35,7 +35,7 @@ this.maskSprite = sprite; this.maskMatrix = maskMatrix; - this.uniforms.mapSampler = sprite.texture; + this.uniforms.mapSampler = sprite._texture; this.uniforms.filterMatrix = maskMatrix; this.uniforms.scale = { x: 1, y: 1 }; diff --git a/.travis.yml b/.travis.yml index 3df8b22..14de334 100644 --- a/.travis.yml +++ b/.travis.yml @@ -110,3 +110,43 @@ on: all_branches: true condition: $TRAVIS_TAG + # Deploy config for latest release + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: dist + upload-dir: "release" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: docs + upload-dir: "release/docs" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: coverage + upload-dir: "release/coverage" + on: + all_branches: true + condition: $TRAVIS_TAG \ No newline at end of file diff --git a/README.md b/README.md index d26fadd..3b85f85 100644 --- a/README.md +++ b/README.md @@ -36,22 +36,14 @@ - Wiki: Other misc tutorials and resources are [on the Wiki](https://github.com/pixijs/pixi.js/wiki/Resources). ### Community ### -- Forums: Check out the [forum] (http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow] (http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. +- Forums: Check out the [forum](http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow](http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. - Inspiration: Check out the [gallery](http://www.pixijs.com/gallery) to see some of the amazing things people have created! - Chat: You can join us on [Gitter](https://gitter.im/pixijs/pixi.js) To chat about Pixi. We also now have a Slack channel. If you would like to join it please Send me an email (mat@goodboydigital.com) and I will invite you in. ### Setup ### -It's easy to get started with Pixi.js! Simply grab the pre-built versions from here: - -Release Branch - Nice and stable Pixi.js -- Unminified: [http://pixijs.download/release/pixi.js] -- Minified: [http://pixijs.download/release/pixi.min.js] - -Develop Branch - The bleeding edge version of Pixi.js -- Unminified: [http://pixijs.download/dev/pixi.js] -- Minified: [http://pixijs.download/dev/pixi.min.js] +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. @@ -70,10 +62,10 @@ #### CDN Install (via cdnjs) ```html - + ``` -_Note: `4.2.2` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ +_Note: `4.4.3` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ ### Demos ### @@ -130,7 +122,7 @@ // and the root stage PIXI.Container. var app = new PIXI.Application(); -// The application will create a canvas element for you that you +// The application will create a canvas element for you that you // can then insert into the DOM. document.body.appendChild(app.view); @@ -150,7 +142,7 @@ // Add the bunny to the scene we are building. app.stage.addChild(bunny); - + // Listen for frame updates app.ticker.add(function() { // each frame we spin the bunny around a bit diff --git a/bower.json b/bower.json deleted file mode 100644 index 41b668c..0000000 --- a/bower.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "pixi.js", - "main": "dist/pixi.min.js", - "ignore": [ - "**/.*", - "docs", - "node_modules", - "bower_components", - "test", - "scripts", - "bower.json", - "inch.json" - ], - "dependencies": { - }, - "devDependencies": { - } -} diff --git a/package.json b/package.json index d35cbb2..308469f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.3.5", + "version": "4.4.4", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ @@ -40,11 +40,11 @@ "lib": "babel src --out-dir lib -s", "predocs": "rimraf docs/**", "docs": "jsdoc -c scripts/jsdoc.conf.json -R README.md", - "publish:patch": "npm version patch --no-git-tag-version && npm publish", - "publish:minor": "npm version minor --no-git-tag-version && npm publish", - "publish:major": "npm version major --no-git-tag-version && npm publish", + "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", - "postpublish": "node scripts/release.js" + "postpublish": "git push && git push --tags" }, "files": [ "dist/", @@ -72,7 +72,6 @@ "electron": "^1.4.15", "eslint": "^3.5.0", "floss": "^2.0.1", - "gh-pages": "^0.11.0", "jaguarjs-jsdoc": "^1.0.1", "js-md5": "^0.4.1", "jsdoc": "^3.4.2", diff --git a/scripts/jsdoc.conf.json b/scripts/jsdoc.conf.json index 553e61c..3f52180 100644 --- a/scripts/jsdoc.conf.json +++ b/scripts/jsdoc.conf.json @@ -43,7 +43,7 @@ }, "markdown" : { "parser" : "gfm", - "hardwrap" : true + "hardwrap" : false }, "opts": { "encoding" : "utf8", diff --git a/scripts/release.js b/scripts/release.js deleted file mode 100644 index 156620c..0000000 --- a/scripts/release.js +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env node -'use strict'; - -// Publish script to push releases of the bin files -// the normally are gitignored -const ghpages = require('gh-pages'); -const path = require('path'); -const packageInfo = require(path.join(__dirname, '..', 'package.json')); - -const options = { - src: [ - 'dist/**/*', - 'lib/**/*', - 'src/**/*', - 'scripts/*', - 'scripts/renders/*', - 'test/**/*', - '*.json', - '*.md', - 'LICENSE', - '.eslintrc', - '.editorconfig', - '.travis.yml', - '.babelrc', - ], - dotfiles: true, - branch: 'release', - message: packageInfo.version, - logger: console.log.bind(console), -}; - -ghpages.publish(process.cwd(), options, (err) => -{ - if (err) - { - console.log(err); - process.exit(1); - - return; - } - - process.exit(0); -}); diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index f16bcb1..220427a 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -21,7 +21,7 @@ const DIV_HOOK_ZINDEX = 2; /** - * The Accessibility manager reacreates the ability to tab and and have content read by screen + * The Accessibility manager recreates the ability to tab and and have content read by screen * readers. This is very important as it can possibly help people with disabilities access pixi * content. * diff --git a/src/accessibility/index.js b/src/accessibility/index.js index 568cfb1..0ae108f 100644 --- a/src/accessibility/index.js +++ b/src/accessibility/index.js @@ -1,4 +1,9 @@ /** + * This namespace contains a renderer plugin for interaction accessibility for end-users + * with physical impairments which require screen-renders, keyboard navigation, etc. + * + * Do not instantiate this plugin directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. * @namespace PIXI.accessibility */ export { default as accessibleTarget } from './accessibleTarget'; diff --git a/src/core/Application.js b/src/core/Application.js index 5340578..2fcc729 100644 --- a/src/core/Application.js +++ b/src/core/Application.js @@ -1,6 +1,8 @@ import { autoDetectRenderer } from './autoDetectRenderer'; import Container from './display/Container'; import { shared, Ticker } from './ticker'; +import settings from './settings'; +import { UPDATE_PRIORITY } from './const'; /** * Convenience class to create a new PIXI application. @@ -22,28 +24,52 @@ */ export default class Application { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experience unexplained flickering try setting this to true. - * @param {boolean} [useSharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedLoader=false] - `true` to use PIXI.loaders.shared, `false` to create new Loader. */ - constructor(width, height, options, noWebGL, useSharedTicker = false) + constructor(options, arg2, arg3, arg4, arg5) { + // Support for constructor(width, height, options, noWebGL, useSharedTicker) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + forceCanvas: !!arg4, + sharedTicker: !!arg5, + }, arg3); + } + + /** + * The default options, so we mixin functionality later. + * @member {object} + * @protected + */ + this._options = options = Object.assign({ + sharedTicker: false, + forceCanvas: false, + sharedLoader: false, + }, options); + /** * WebGL renderer if available, otherwise CanvasRenderer * @member {PIXI.WebGLRenderer|PIXI.CanvasRenderer} */ - this.renderer = autoDetectRenderer(width, height, options, noWebGL); + this.renderer = autoDetectRenderer(options); /** * The root display container that's rendered. @@ -63,7 +89,7 @@ * @member {PIXI.ticker.Ticker} * @default PIXI.ticker.shared */ - this.ticker = useSharedTicker ? shared : new Ticker(); + this.ticker = options.sharedTicker ? shared : new Ticker(); // Start the rendering this.start(); @@ -78,7 +104,7 @@ this._ticker = ticker; if (ticker) { - ticker.add(this.render, this); + ticker.add(this.render, this, UPDATE_PRIORITY.LOW); } } get ticker() // eslint-disable-line require-jsdoc @@ -136,13 +162,18 @@ */ destroy(removeView) { - this.stop(); + const oldTicker = this._ticker; + this.ticker = null; + oldTicker.destroy(); + this.stage.destroy(); this.stage = null; this.renderer.destroy(removeView); this.renderer = null; + + this._options = null; } } diff --git a/src/core/autoDetectRenderer.js b/src/core/autoDetectRenderer.js index 8960425..9ec0364 100644 --- a/src/core/autoDetectRenderer.js +++ b/src/core/autoDetectRenderer.js @@ -2,6 +2,7 @@ import CanvasRenderer from './renderers/canvas/CanvasRenderer'; import WebGLRenderer from './renderers/webgl/WebGLRenderer'; +// eslint-disable-next-line valid-jsdoc /** * This helper function will automatically detect which renderer you should be using. * WebGL is the preferred renderer as it is a lot faster. If webGL is not supported by @@ -9,24 +10,32 @@ * * @memberof PIXI * @function autoDetectRenderer - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @return {PIXI.WebGLRenderer|PIXI.CanvasRenderer} Returns WebGL renderer if available, otherwise CanvasRenderer */ -export function autoDetectRenderer(width = 800, height = 600, options, noWebGL) +export function autoDetectRenderer(options, arg1, arg2, arg3) { - if (!noWebGL && utils.isWebGLSupported()) + // Backward-compatible support for noWebGL option + let forceCanvas = options && options.forceCanvas; + + if (arg3 !== undefined) { - return new WebGLRenderer(width, height, options); + forceCanvas = arg3; } - return new CanvasRenderer(width, height, options); + if (!forceCanvas && utils.isWebGLSupported()) + { + return new WebGLRenderer(options, arg1, arg2); + } + + return new CanvasRenderer(options, arg1, arg2); } diff --git a/src/core/const.js b/src/core/const.js index b9e99ae..2c68dee 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -341,3 +341,27 @@ LINEAR_VERTICAL: 0, LINEAR_HORIZONTAL: 1, }; + +/** + * Represents the update priorities used by internal PIXI classes when registered with + * the {@link PIXI.ticker.Ticker} object. Higher priority items are updated first and lower + * priority items, such as render, should go later. + * + * @static + * @constant + * @name UPDATE_PRIORITY + * @memberof PIXI + * @type {object} + * @property {number} INTERACTION=50 Highest priority, used for {@link PIXI.interaction.InteractionManager} + * @property {number} HIGH=25 High priority updating, {@link PIXI.VideoBaseTexture} and {@link PIXI.extras.AnimatedSprite} + * @property {number} NORMAL=0 Default priority for ticker events, see {@link PIXI.ticker.Ticker#add}. + * @property {number} LOW=-25 Low priority used for {@link PIXI.Application} rendering. + * @property {number} UTILITY=-50 Lowest priority used for {@link PIXI.prepare.BasePrepare} utility. + */ +export const UPDATE_PRIORITY = { + INTERACTION: 50, + HIGH: 25, + NORMAL: 0, + LOW: -25, + UTILITY: -50, +}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 4e6c77d..decb42a 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -13,7 +13,6 @@ * * @class * @extends EventEmitter - * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ export default class DisplayObject extends EventEmitter @@ -113,6 +112,29 @@ * @private */ this._mask = null; + + /** + * If the object has been destroyed via destroy(). If true, it should not be used. + * + * @member {boolean} + * @private + * @readonly + */ + this._destroyed = false; + + /** + * Fired when this DisplayObject is added to a Container. + * + * @event PIXI.DisplayObject#added + * @param {PIXI.Container} container - The container added to. + */ + + /** + * Fired when this DisplayObject is removed from a Container. + * + * @event PIXI.DisplayObject#removed + * @param {PIXI.Container} container - The container removed from. + */ } /** @@ -403,6 +425,8 @@ this.interactive = false; this.interactiveChildren = false; + + this._destroyed = true; } /** diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index aafdfe2..f787c4a 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -792,8 +792,8 @@ sprite.worldAlpha = this.worldAlpha * sprite.alpha; sprite.blendMode = this.blendMode; - sprite.texture._frame.width = rect.width; - sprite.texture._frame.height = rect.height; + sprite._texture._frame.width = rect.width; + sprite._texture._frame.height = rect.height; sprite.transform.worldTransform = this.transform.worldTransform; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index a24c775..b9f192d 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -19,11 +19,12 @@ */ export default class SystemRenderer extends EventEmitter { + // eslint-disable-next-line valid-jsdoc /** * @param {string} system - The name of the system this renderer is for. - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -37,27 +38,31 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(system, screenWidth, screenHeight, options) + constructor(system, options, arg2, arg3) { super(); sayHello(system); - // prepare options - if (options) + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') { - for (const i in settings.RENDER_OPTIONS) - { - if (typeof options[i] === 'undefined') - { - options[i] = settings.RENDER_OPTIONS[i]; - } - } + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); } - else - { - options = settings.RENDER_OPTIONS; - } + + // Add the default render options + options = Object.assign({}, settings.RENDER_OPTIONS, options); + + /** + * The supplied constructor options. + * + * @member {Object} + * @readOnly + */ + this.options = options; /** * The type of the renderer. @@ -75,7 +80,7 @@ * * @member {PIXI.Rectangle} */ - this.screen = new Rectangle(0, 0, screenWidth || 800, screenHeight || 600); + this.screen = new Rectangle(0, 0, options.width, options.height); /** * The canvas element that everything is drawn to @@ -279,6 +284,8 @@ this.blendModes = null; + this.options = null; + this.preserveDrawingBuffer = false; this.clearBeforeRender = false; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index e84e49c..691caf0 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -17,10 +17,11 @@ */ export default class CanvasRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -34,9 +35,9 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('Canvas', screenWidth, screenHeight, options); + super('Canvas', options, arg2, arg3); this.type = RENDERER_TYPE.CANVAS; @@ -96,7 +97,19 @@ this.context = null; this.renderingToScreen = false; - this.resize(screenWidth, screenHeight); + this.resize(this.options.width, this.options.height); + + /** + * Fired after rendering finishes. + * + * @event PIXI.CanvasRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.CanvasRenderer#prerender + */ } /** @@ -299,4 +312,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.CanvasRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.CanvasExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.CanvasPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.CanvasRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index e1cf7cd..f6fe123 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -31,11 +31,12 @@ */ export default class WebGLRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** * - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -55,9 +56,9 @@ * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('WebGL', screenWidth, screenHeight, options); + super('WebGL', options, arg2, arg3); /** * The type of this renderer as a standardised const @@ -89,12 +90,6 @@ }; - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ this._backgroundColorRgba[3] = this.transparent ? 0 : 1; this.globalUniforms = new UniformGroup({ @@ -117,6 +112,13 @@ this.initPlugins(); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ if(options.context) { this.context.initFromContext(options.context); @@ -169,6 +171,25 @@ } return this; + + /** + * Fired after rendering finishes. + * + * @event PIXI.WebGLRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.WebGLRenderer#prerender + */ + + /** + * Fired when the WebGL context is set. + * + * @event PIXI.WebGLRenderer#context + * @param {WebGLRenderingContext} gl - WebGL context. + */ } /** @@ -295,4 +316,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.WebGLRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.WebGLRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(WebGLRenderer); diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index 4eb6128..fa3d883 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -47,6 +47,14 @@ * @member {boolean} */ this.enabled = true; + + /** + * If enabled, pixi will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; } /** diff --git a/src/core/renderers/webgl/systems/ContextSystem.js b/src/core/renderers/webgl/systems/ContextSystem.js index ff958c7..8df85ea 100644 --- a/src/core/renderers/webgl/systems/ContextSystem.js +++ b/src/core/renderers/webgl/systems/ContextSystem.js @@ -75,7 +75,8 @@ */ createContext(canvas, options) { - var gl = canvas.getContext('webgl', options) || + var gl = //canvas.getContext('webgl2', options) || + canvas.getContext('webgl', options) || canvas.getContext('experimental-webgl', options); if (!gl) diff --git a/src/core/renderers/webgl/systems/FilterSystem.js b/src/core/renderers/webgl/systems/FilterSystem.js index f47335d..37e28d2 100644 --- a/src/core/renderers/webgl/systems/FilterSystem.js +++ b/src/core/renderers/webgl/systems/FilterSystem.js @@ -111,7 +111,7 @@ // TODO we should fit the rect around the transform.. } - else + else if (filters[0].autoFit) { sourceFrame.fit(filterData.stack[0].destinationFrame); } diff --git a/src/core/renderers/webgl/systems/shader/shader/mapType.js b/src/core/renderers/webgl/systems/shader/shader/mapType.js index 581dace..917538a 100644 --- a/src/core/renderers/webgl/systems/shader/shader/mapType.js +++ b/src/core/renderers/webgl/systems/shader/shader/mapType.js @@ -15,7 +15,7 @@ } } - return GL_TABLE[type]; + return GL_TABLE[type]; }; var GL_TABLE = null; diff --git a/src/core/settings.js b/src/core/settings.js index 18c5877..38ef45c 100644 --- a/src/core/settings.js +++ b/src/core/settings.js @@ -2,6 +2,15 @@ import canUploadSameBuffer from './utils/canUploadSameBuffer'; /** + * User's customizable globals for overriding the default PIXI settings, such + * as a renderer's default resolution, framerate, float percision, etc. + * @example + * // Use the native window resolution as the default resolution + * // will support high-density displays when rendering + * PIXI.settings.RESOLUTION = window.devicePixelRatio. + * + * // Disable interpolation when scaling, will make texture be pixelated + * PIXI.settings.SCALE_MODE = PIXI.SCALE_MODES.NEAREST; * @namespace PIXI.settings */ export default { @@ -101,6 +110,9 @@ * @property {boolean} clearBeforeRender=true * @property {boolean} preserveDrawingBuffer=false * @property {boolean} roundPixels=false + * @property {number} width=800 + * @property {number} height=600 + * @property {boolean} legacy=false */ RENDER_OPTIONS: { view: null, @@ -112,6 +124,9 @@ clearBeforeRender: true, preserveDrawingBuffer: false, roundPixels: false, + width: 800, + height: 600, + legacy: false, }, /** diff --git a/src/core/shader/Program.js b/src/core/shader/Program.js index c0b538b..07b4398 100644 --- a/src/core/shader/Program.js +++ b/src/core/shader/Program.js @@ -1,6 +1,6 @@ import extractUniformsFromSrc from './extractUniformsFromSrc'; import generateUniformsSync from './generateUniformsSync'; -import glCore from 'pixi-gl-core'; +import shaderUtils from '../renderers/webgl/systems/shader/shader'; import { ProgramCache } from '../utils'; import getTestContext from '../utils/getTestContext'; @@ -64,14 +64,17 @@ } else { - vertexSrc = glCore.shader.setPrecision(vertexSrc, 'mediump'); - fragmentSrc = glCore.shader.setPrecision(fragmentSrc, 'mediump'); + vertexSrc = shaderUtils.setPrecision(vertexSrc, 'mediump'); + fragmentSrc = shaderUtils.setPrecision(fragmentSrc, 'mediump'); - const program = glCore.shader.compileProgram(gl, vertexSrc, fragmentSrc); + const program = shaderUtils.compileProgram(gl, vertexSrc, fragmentSrc); + console.log("<<<<<<>>><<"); this.attributeData = this.getAttributeData(program, gl); this.uniformData = this.getUniformData(program, gl); + console.log(this.uniformData); + //gl.deleteProgram(program); } } @@ -95,13 +98,13 @@ for (let i = 0; i < totalAttributes; i++) { const attribData = gl.getActiveAttrib(program, i); - const type = glCore.shader.mapType(gl, attribData.type); + const type = shaderUtils.mapType(gl, attribData.type); /*eslint-disable */ const data = { type: type, name: attribData.name, - size: glCore.shader.mapSize(type), + size: shaderUtils.mapSize(type), location: 0, }; /*eslint-enable */ @@ -137,13 +140,14 @@ // TODO expose this as a prop? // const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); - const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); + //const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); for (let i = 0; i < totalUniforms; i++) { const uniformData = gl.getActiveUniform(program, i); const name = uniformData.name.replace(/\[.*?\]/, ''); - const type = glCore.shader.mapType(gl, uniformData.type); + const type = shaderUtils.mapType(gl, uniformData.type); + console.log('mapping ' + uniformData.type + ' to ' + type); // if (!name.match(maskRegex)) { @@ -151,7 +155,7 @@ uniforms[name] = { type: type, size: uniformData.size, - value: glCore.shader.defaultValue(type, uniformData.size), + value: shaderUtils.defaultValue(type, uniformData.size), }; /*eslint-enable */ } diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index e387e99..b57d095 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -147,12 +147,12 @@ // so if _width is 0 then width was not set.. if (this._width) { - this.scale.x = sign(this.scale.x) * this._width / this.texture.orig.width; + this.scale.x = sign(this.scale.x) * this._width / this._texture.orig.width; } if (this._height) { - this.scale.y = sign(this.scale.y) * this._height / this.texture.orig.height; + this.scale.y = sign(this.scale.y) * this._height / this._texture.orig.height; } } diff --git a/src/core/sprites/canvas/CanvasTinter.js b/src/core/sprites/canvas/CanvasTinter.js index 27e5e0c..fd9de5d 100644 --- a/src/core/sprites/canvas/CanvasTinter.js +++ b/src/core/sprites/canvas/CanvasTinter.js @@ -18,7 +18,7 @@ */ getTintedTexture: (sprite, color) => { - const texture = sprite.texture; + const texture = sprite._texture; color = CanvasTinter.roundColor(color); diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 5a4edda..8b9b797 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -344,6 +344,7 @@ const texture = this._texture; const style = this._style; + const padding = style.trim ? 0 : style.padding; texture.baseTexture.valid = true; texture.baseTexture.resolution = this.resolution; @@ -353,11 +354,11 @@ texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - texture.trim.x = -style.padding; - texture.trim.y = -style.padding; + texture.trim.x = -padding; + texture.trim.y = -padding; - texture.orig.width = texture._frame.width - (style.padding * 2); - texture.orig.height = texture._frame.height - (style.padding * 2); + texture.orig.width = texture._frame.width - (padding * 2); + texture.orig.height = texture._frame.height - (padding * 2); // call sprite onTextureUpdate to update scale if _width or _height were set this._onTextureUpdate(); diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index 853da3d..8a9c34b 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -114,6 +114,38 @@ this.cacheId = null; this.validate(); + + /** + * Fired when a not-immediately-available source finishes loading. + * + * @protected + * @event PIXI.BaseTexture#loaded + * @param {PIXI.BaseTexture} baseTexture - Resource loaded. + */ + + /** + * Fired when a not-immediately-available source fails to load. + * + * @protected + * @event PIXI.BaseTexture#error + * @param {PIXI.BaseTexture} baseTexture - Resource errored. + */ + + /** + * Fired when BaseTexture is updated. + * + * @protected + * @event PIXI.BaseTexture#update + * @param {PIXI.BaseTexture} baseTexture - Instance of texture being updated. + */ + + /** + * Fired when BaseTexture is destroyed. + * + * @protected + * @event PIXI.BaseTexture#dispose + * @param {PIXI.BaseTexture} baseTexture - Instance of texture being destroyed. + */ } setResource(resource) diff --git a/src/core/textures/Spritesheet.js b/src/core/textures/Spritesheet.js index d469c62..9b24f27 100644 --- a/src/core/textures/Spritesheet.js +++ b/src/core/textures/Spritesheet.js @@ -208,6 +208,7 @@ // lets also add the frame to pixi's global cache for fromFrame and fromImage functions TextureCache[i] = this.textures[i]; + this.textures[i].textureCacheId = i; } frameIndex++; diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 0a2c379..2213087 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -147,9 +147,9 @@ /** * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. * - * @event update - * @memberof PIXI.Texture# + * @event PIXI.Texture#update * @protected + * @param {PIXI.Texture} texture - Instance of texture being updated. */ this._updateID = 0; @@ -159,6 +159,15 @@ * @type {Object} */ this.transform = null; + + /** + * The id under which this Texture has been added to the texture cache. This is + * automatically set in certain cases, but may not always be accurate, particularly if + * the texture is in the cache under multiple ids. + * + * @member {string} + */ + this.textureCacheId = null; } /** @@ -225,11 +234,17 @@ this._uvs = null; this.trim = null; this.orig = null; + this.textureCacheId = null; this.valid = false; - this.off('dispose', this.dispose, this); - this.off('update', this.update, this); + for (const prop in TextureCache) + { + if (TextureCache[prop] === this) + { + delete TextureCache[prop]; + } + } } /** @@ -315,14 +330,15 @@ */ static fromLoader(source, imageUrl, name) { - // console.log('added from loader...') + // console.log('added from loader...') const resource = new ImageResource(source);//.from(imageUrl, crossorigin);// document.createElement('img'); - // console.log('base resource ' + resource.width); + // console.log('base resource ' + resource.width); const baseTexture = new BaseTexture(resource, settings.SCALE_MODE, getResolutionOfUrl(imageUrl)); + /// console.log('base width ' + baseTexture.width); const texture = new Texture(baseTexture); @@ -335,6 +351,7 @@ // lets also add the frame to pixi's global cache for fromFrame and fromImage fucntions BaseTextureCache[name] = baseTexture; TextureCache[name] = texture; + texture.textureCacheId = name; // also add references by url if they are different. if (name !== imageUrl) @@ -355,6 +372,10 @@ */ static addTextureToCache(texture, id) { + if (!texture.textureCacheId) + { + texture.textureCacheId = id; + } TextureCache[id] = texture; } @@ -495,6 +516,7 @@ */ Texture.EMPTY = new Texture(new BaseTexture()); removeAllHandlers(Texture.EMPTY); +removeAllHandlers(Texture.EMPTY.baseTexture); /** * A white texture of 10x10 size, used for graphics and other things @@ -505,3 +527,4 @@ */ Texture.WHITE = createWhiteTexture(); removeAllHandlers(Texture.WHITE); +removeAllHandlers(Texture.WHITE.baseTexture); diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index 59eb9fd..a9b9f51 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -1,6 +1,7 @@ import BaseTexture from './BaseTexture'; import { uid, BaseTextureCache } from '../utils'; -import * as ticker from '../ticker'; +import { shared } from '../ticker'; +import { UPDATE_PRIORITY } from '../const'; /** * A texture of a [playing] Video. @@ -125,7 +126,7 @@ if (!this._isAutoUpdating && this.autoUpdate) { - ticker.shared.add(this.update, this); + shared.add(this.update, this, UPDATE_PRIORITY.HIGH); this._isAutoUpdating = true; } } @@ -139,7 +140,7 @@ { if (this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); this._isAutoUpdating = false; } } @@ -187,7 +188,7 @@ { if (this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); } if (this.source && this.source._pixiId) @@ -281,12 +282,12 @@ if (!this._autoUpdate && this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); this._isAutoUpdating = false; } else if (this._autoUpdate && !this._isAutoUpdating) { - ticker.shared.add(this.update, this); + shared.add(this.update, this, UPDATE_PRIORITY.HIGH); this._isAutoUpdating = true; } } diff --git a/src/core/ticker/Ticker.js b/src/core/ticker/Ticker.js index 17a1517..40df138 100644 --- a/src/core/ticker/Ticker.js +++ b/src/core/ticker/Ticker.js @@ -1,12 +1,10 @@ import settings from '../settings'; -import EventEmitter from 'eventemitter3'; - -// Internal event used by composed emitter -const TICK = 'tick'; +import { UPDATE_PRIORITY } from '../const'; +import TickerListener from './TickerListener'; /** * A Ticker class that runs an update loop that other objects listen to. - * This class is composed around an EventEmitter object to add listeners + * This class is composed around listeners * meant for execution on the next requested animation frame. * Animation frames are requested only when necessary, * e.g. When the ticker is started and the emitter has listeners. @@ -22,10 +20,11 @@ constructor() { /** - * Internal emitter used to fire 'tick' event + * The first listener. All new listeners added are chained on this. * @private + * @type {TickerListener} */ - this._emitter = new EventEmitter(); + this._head = new TickerListener(null, null, Infinity); /** * Internal current frame request ID @@ -131,7 +130,7 @@ // Invoke listeners now this.update(time); // Listener side effects may have modified ticker state. - if (this.started && this._requestId === null && this._emitter.listeners(TICK, true)) + if (this.started && this._requestId === null && this._head.next) { this._requestId = requestAnimationFrame(this._tick); } @@ -148,7 +147,7 @@ */ _requestIfNeeded() { - if (this._requestId === null && this._emitter.listeners(TICK, true)) + if (this._requestId === null && this._head.next) { // ensure callbacks get correct delta this.lastTime = performance.now(); @@ -193,35 +192,72 @@ } /** - * Calls {@link module:eventemitter3.EventEmitter#on} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. + * Register a handler for tick events. Calls continuously unless + * it is removed or the ticker is stopped. * * @param {Function} fn - The listener function to be added for updates * @param {Function} [context] - The listener context + * @param {number} [priority=PIXI.UPDATE_PRIORITY.NORMAL] - The priority for emitting * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - add(fn, context) + add(fn, context, priority = UPDATE_PRIORITY.NORMAL) { - this._emitter.on(TICK, fn, context); - - this._startIfPossible(); - - return this; + return this._addListener(new TickerListener(fn, context, priority)); } /** - * Calls {@link module:eventemitter3.EventEmitter#once} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. + * Add a handler for the tick event which is only execute once. * * @param {Function} fn - The listener function to be added for one update * @param {Function} [context] - The listener context + * @param {number} [priority=PIXI.UPDATE_PRIORITY.NORMAL] - The priority for emitting * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - addOnce(fn, context) + addOnce(fn, context, priority = UPDATE_PRIORITY.NORMAL) { - this._emitter.once(TICK, fn, context); + return this._addListener(new TickerListener(fn, context, priority, true)); + } + + /** + * Internally adds the event handler so that it can be sorted by priority. + * Priority allows certain handler (user, AnimatedSprite, Interaction) to be run + * before the rendering. + * + * @private + * @param {TickerListener} listener - Current listener being added. + * @returns {PIXI.ticker.Ticker} This instance of a ticker + */ + _addListener(listener) + { + // For attaching to head + let current = this._head.next; + let previous = this._head; + + // Add the first item + if (!current) + { + listener.connect(previous); + } + else + { + // Go from highest to lowest priority + while (current) + { + if (listener.priority >= current.priority) + { + listener.connect(previous); + break; + } + previous = current; + current = current.next; + } + + // Not yet connected + if (!listener.previous) + { + listener.connect(previous); + } + } this._startIfPossible(); @@ -229,19 +265,33 @@ } /** - * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. - * It checks if the emitter has listeners for 'tick' event. - * If it does, then it cancels the animation frame. + * Removes any handlers matching the function and context parameters. + * If no handlers are left after removing, then it cancels the animation frame. * - * @param {Function} [fn] - The listener function to be removed + * @param {Function} fn - The listener function to be removed * @param {Function} [context] - The listener context to be removed * @returns {PIXI.ticker.Ticker} This instance of a ticker */ remove(fn, context) { - this._emitter.off(TICK, fn, context); + let listener = this._head.next; - if (!this._emitter.listeners(TICK, true)) + while (listener) + { + // We found a match, lets remove it + // no break to delete all possible matches + // incase a listener was added 2+ times + if (listener.match(fn, context)) + { + listener = listener.destroy(); + } + else + { + listener = listener.next; + } + } + + if (!this._head.next) { this._cancelIfNeeded(); } @@ -276,6 +326,25 @@ } /** + * Destroy the ticker and don't use after this. Calling + * this method removes all references to internal events. + */ + destroy() + { + this.stop(); + + let listener = this._head.next; + + while (listener) + { + listener = listener.destroy(true); + } + + this._head.destroy(); + this._head = null; + } + + /** * Triggers an update. An update entails setting the * current {@link PIXI.ticker.Ticker#elapsedMS}, * the current {@link PIXI.ticker.Ticker#deltaTime}, @@ -320,8 +389,22 @@ this.deltaTime = elapsedMS * settings.TARGET_FPMS * this.speed; + // Cache a local reference, in-case ticker is destroyed + // during the emit, we can still check for head.next + const head = this._head; + // Invoke listeners added to internal emitter - this._emitter.emit(TICK, this.deltaTime); + let listener = head.next; + + while (listener) + { + listener = listener.emit(this.deltaTime); + } + + if (!head.next) + { + this._cancelIfNeeded(); + } } else { diff --git a/src/core/ticker/TickerListener.js b/src/core/ticker/TickerListener.js new file mode 100644 index 0000000..2bedb34 --- /dev/null +++ b/src/core/ticker/TickerListener.js @@ -0,0 +1,158 @@ +/** + * Internal class for handling the priority sorting of ticker handlers. + * + * @private + * @class + * @memberof PIXI.ticker + */ +export default class TickerListener +{ + /** + * Constructor + * + * @param {Function} fn - The listener function to be added for one update + * @param {Function} [context=null] - The listener context + * @param {number} [priority=0] - The priority for emitting + * @param {boolean} [once=false] - If the handler should fire once + */ + constructor(fn, context = null, priority = 0, once = false) + { + /** + * The handler function to execute. + * @member {Function} + */ + this.fn = fn; + + /** + * The calling to execute. + * @member {Function} + */ + this.context = context; + + /** + * The current priority. + * @member {number} + */ + this.priority = priority; + + /** + * If this should only execute once. + * @member {boolean} + */ + this.once = once; + + /** + * The next item in chain. + * @member {TickerListener} + */ + this.next = null; + + /** + * The previous item in chain. + * @member {TickerListener} + */ + this.previous = null; + + /** + * `true` if this listener has been destroyed already. + * @member {boolean} + * @private + */ + this._destroyed = false; + } + + /** + * Simple compare function to figure out if a function and context match. + * + * @param {Function} fn - The listener function to be added for one update + * @param {Function} context - The listener context + * @return {boolean} `true` if the listener match the arguments + */ + match(fn, context) + { + context = context || null; + + return this.fn === fn && this.context === context; + } + + /** + * Emit by calling the current function. + * @param {number} deltaTime - time since the last emit. + * @return {TickerListener} Next ticker + */ + emit(deltaTime) + { + if (this.context) + { + this.fn.call(this.context, deltaTime); + } + else + { + this.fn(deltaTime); + } + + if (this.once) + { + this.destroy(); + } + + const redirect = this.next; + + // Soft-destroying should remove + // the next reference + if (this._destroyed) + { + this.next = null; + } + + return redirect; + } + + /** + * Connect to the list. + * @param {TickerListener} previous - Input node, previous listener + */ + connect(previous) + { + this.previous = previous; + if (previous.next) + { + previous.next.previous = this; + } + this.next = previous.next; + previous.next = this; + } + + /** + * Destroy and don't use after this. + * @param {boolean} [hard = false] `true` to remove the `next` reference, this + * is considered a hard destroy. Soft destroy maintains the next reference. + * @return {TickerListener} The listener to redirect while emitting or removing. + */ + destroy(hard = false) + { + this._destroyed = true; + this.fn = null; + this.context = null; + + // Disconnect, hook up next and previous + if (this.previous) + { + this.previous.next = this.next; + } + + if (this.next) + { + this.next.previous = this.previous; + } + + // Redirect to the next item + const redirect = this.previous; + + // Remove references + this.next = hard ? null : redirect; + this.previous = null; + + return redirect; + } +} diff --git a/src/core/ticker/index.js b/src/core/ticker/index.js index 4b3017c..c3020d4 100644 --- a/src/core/ticker/index.js +++ b/src/core/ticker/index.js @@ -45,8 +45,25 @@ const shared = new Ticker(); shared.autoStart = true; +shared.destroy = () => +{ + // protect destroying shared ticker + // this is used by other internal systems + // like AnimatedSprite and InteractionManager +}; /** + * This namespace contains an API for interacting with PIXI's internal global update loop. + * + * This ticker is used for rendering, {@link PIXI.extras.AnimatedSprite AnimatedSprite}, + * {@link PIXI.interaction.InteractionManager InteractionManager} and many other time-based PIXI systems. + * @example + * const ticker = new PIXI.ticker.Ticker(); + * ticker.stop(); + * ticker.add((deltaTime) => { + * // do something every frame + * }); + * ticker.start(); * @namespace PIXI.ticker */ export { shared, Ticker }; diff --git a/src/core/utils/index.js b/src/core/utils/index.js index be44750..7441f7b 100644 --- a/src/core/utils/index.js +++ b/src/core/utils/index.js @@ -9,6 +9,21 @@ let saidHello = false; /** + * Generalized convenience utilities for PIXI. + * @example + * // Extend PIXI's internal Event Emitter. + * class MyEmitter extends PIXI.utils.EventEmitter { + * constructor() { + * super(); + * console.log("Emitter created!"); + * } + * } + * + * // Get info on current device + * console.log(PIXI.utils.isMobile); + * + * // Convert hex color to string + * console.log(PIXI.utils.hex2string(0xff00ff)); // returns: "#ff00ff" * @namespace PIXI.utils */ export { diff --git a/src/deprecation.js b/src/deprecation.js index b9c73b3..33daeec 100644 --- a/src/deprecation.js +++ b/src/deprecation.js @@ -918,6 +918,35 @@ }); /** + * @method + * @name PIXI.prepare.BasePrepare#register + * @see PIXI.prepare.BasePrepare#registerFindHook + * @deprecated since version 4.4.2 + * @param {Function} [addHook] - Function call that takes two parameters: `item:*, queue:Array` + * function must return `true` if it was able to add item to the queue. + * @param {Function} [uploadHook] - Function call that takes two parameters: `prepare:CanvasPrepare, item:*` and + * function must return `true` if it was able to handle upload of item. + * @return {PIXI.BasePrepare} Instance of plugin for chaining. + */ +prepare.BasePrepare.prototype.register = function register(addHook, uploadHook) +{ + warn('renderer.plugins.prepare.register is now deprecated, ' + + 'please use renderer.plugins.prepare.registerFindHook & renderer.plugins.prepare.registerUploadHook'); + + if (addHook) + { + this.registerFindHook(addHook); + } + + if (uploadHook) + { + this.registerUploadHook(uploadHook); + } + + return this; +}; + +/** * The number of graphics or textures to upload to the GPU. * * @name PIXI.prepare.canvas.UPLOADS_PER_FRAME @@ -969,67 +998,73 @@ }, }); -Object.defineProperties(loaders.Resource.prototype, { - isJson: { - get() - { - warn('The isJson property is deprecated, please use `resource.type === Resource.TYPE.JSON`.'); +if (loaders.Loader) +{ + const Resource = loaders.Resource; + const Loader = loaders.Loader; - return this.type === loaders.Loader.Resource.TYPE.JSON; + Object.defineProperties(Resource.prototype, { + isJson: { + get() + { + warn('The isJson property is deprecated, please use `resource.type === Resource.TYPE.JSON`.'); + + return this.type === Resource.TYPE.JSON; + }, }, - }, - isXml: { - get() - { - warn('The isXml property is deprecated, please use `resource.type === Resource.TYPE.XML`.'); + isXml: { + get() + { + warn('The isXml property is deprecated, please use `resource.type === Resource.TYPE.XML`.'); - return this.type === loaders.Loader.Resource.TYPE.XML; + return this.type === Resource.TYPE.XML; + }, }, - }, - isImage: { - get() - { - warn('The isImage property is deprecated, please use `resource.type === Resource.TYPE.IMAGE`.'); + isImage: { + get() + { + warn('The isImage property is deprecated, please use `resource.type === Resource.TYPE.IMAGE`.'); - return this.type === loaders.Loader.Resource.TYPE.IMAGE; + return this.type === Resource.TYPE.IMAGE; + }, }, - }, - isAudio: { - get() - { - warn('The isAudio property is deprecated, please use `resource.type === Resource.TYPE.AUDIO`.'); + isAudio: { + get() + { + warn('The isAudio property is deprecated, please use `resource.type === Resource.TYPE.AUDIO`.'); - return this.type === loaders.Loader.Resource.TYPE.AUDIO; + return this.type === Resource.TYPE.AUDIO; + }, }, - }, - isVideo: { - get() - { - warn('The isVideo property is deprecated, please use `resource.type === Resource.TYPE.VIDEO`.'); + isVideo: { + get() + { + warn('The isVideo property is deprecated, please use `resource.type === Resource.TYPE.VIDEO`.'); - return this.type === loaders.Loader.Resource.TYPE.VIDEO; + return this.type === Resource.TYPE.VIDEO; + }, }, - }, -}); + }); -Object.defineProperties(loaders.Loader.prototype, { - before: { - get() - { - warn('The before() method is deprecated, please use pre().'); + Object.defineProperties(Loader.prototype, { + before: { + get() + { + warn('The before() method is deprecated, please use pre().'); - return this.pre; + return this.pre; + }, }, - }, - after: { - get() - { - warn('The after() method is deprecated, please use use().'); + after: { + get() + { + warn('The after() method is deprecated, please use use().'); - return this.use; + return this.use; + }, }, - }, -}); + }); +} /** * @name PIXI.interaction.interactiveTarget#defaultCursor diff --git a/src/extract/canvas/CanvasExtract.js b/src/extract/canvas/CanvasExtract.js index 941ca05..3ac5aee 100644 --- a/src/extract/canvas/CanvasExtract.js +++ b/src/extract/canvas/CanvasExtract.js @@ -8,7 +8,7 @@ * An instance of this class is automatically created by default, and can be found at renderer.plugins.extract * * @class - * @memberof PIXI + * @memberof PIXI.extract */ export default class CanvasExtract { @@ -21,9 +21,9 @@ /** * Collection of methods for extracting data (image, pixels, etc.) from a display object or render texture * - * @member {PIXI.CanvasExtract} extract + * @member {PIXI.extract.CanvasExtract} extract * @memberof PIXI.CanvasRenderer# - * @see PIXI.CanvasExtract + * @see PIXI.extract.CanvasExtract */ renderer.extract = this; } diff --git a/src/extract/index.js b/src/extract/index.js index 0f2170d..e9478c1 100644 --- a/src/extract/index.js +++ b/src/extract/index.js @@ -1,2 +1,22 @@ +/** + * This namespace provides renderer-specific plugins for exporting content from a renderer. + * For instance, these plugins can be used for saving an Image, Canvas element or for exporting the raw image data (pixels). + * + * Do not instantiate these plugins directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. + * @example + * // Create a new app (will auto-add extract plugin to renderer) + * const app = new PIXI.Application(); + * + * // Draw a red circle + * const graphics = new PIXI.Graphics() + * .beginFill(0xFF0000) + * .drawCircle(0, 0, 50); + * + * // Render the graphics as an HTMLImageElement + * const image = app.renderer.plugins.extract.image(graphics); + * document.body.appendChild(image); + * @namespace PIXI.extract + */ export { default as webgl } from './webgl/WebGLExtract'; export { default as canvas } from './canvas/CanvasExtract'; diff --git a/src/extract/webgl/WebGLExtract.js b/src/extract/webgl/WebGLExtract.js index 60228bb..1c3dd30 100644 --- a/src/extract/webgl/WebGLExtract.js +++ b/src/extract/webgl/WebGLExtract.js @@ -9,7 +9,7 @@ * An instance of this class is automatically created by default, and can be found at renderer.plugins.extract * * @class - * @memberof PIXI + * @memberof PIXI.extract */ export default class WebGLExtract { @@ -22,9 +22,9 @@ /** * Collection of methods for extracting data (image, pixels, etc.) from a display object or render texture * - * @member {PIXI.WebGLExtract} extract + * @member {PIXI.extract.WebGLExtract} extract * @memberof PIXI.WebGLRenderer# - * @see PIXI.WebGLExtract + * @see PIXI.extract.WebGLExtract */ renderer.extract = this; } diff --git a/src/extras/AnimatedSprite.js b/src/extras/AnimatedSprite.js index 13b804f..5297778 100644 --- a/src/extras/AnimatedSprite.js +++ b/src/extras/AnimatedSprite.js @@ -137,7 +137,7 @@ this.playing = true; if (this._autoUpdate) { - core.ticker.shared.add(this.update, this); + core.ticker.shared.add(this.update, this, core.UPDATE_PRIORITY.HIGH); } } diff --git a/src/extras/TextureTransform.js b/src/extras/TextureTransform.js index 78e6e40..16536d2 100644 --- a/src/extras/TextureTransform.js +++ b/src/extras/TextureTransform.js @@ -65,8 +65,36 @@ } /** + * Multiplies uvs array to transform + * @param {Float32Array} uvs mesh uvs + * @param {Float32Array} [out=uvs] output + * @returns {Float32Array} output + */ + multiplyUvs(uvs, out) + { + if (out === undefined) + { + out = uvs; + } + + const mat = this.mapCoord; + + for (let i = 0; i < uvs.length; i += 2) + { + const x = uvs[i]; + const y = uvs[i + 1]; + + out[i] = (x * mat.a) + (y * mat.c) + mat.tx; + out[i + 1] = (x * mat.b) + (y * mat.d) + mat.ty; + } + + return out; + } + + /** * updates matrices if texture was changed * @param {boolean} forceUpdate if true, matrices will be updated any case + * @returns {boolean} whether or not it was updated */ update(forceUpdate) { @@ -74,13 +102,13 @@ if (!tex || !tex.valid) { - return; + return false; } if (!forceUpdate && this._lastTextureID === tex._updateID) { - return; + return false; } this._lastTextureID = tex._updateID; @@ -110,5 +138,7 @@ frame[3] = (tex._frame.y + tex._frame.height - margin + offset) / texBase.height; this.uClampOffset[0] = offset / texBase.realWidth; this.uClampOffset[1] = offset / texBase.realHeight; + + return true; } } diff --git a/src/extras/TilingSprite.js b/src/extras/TilingSprite.js index 1f6014f..a3f027f 100644 --- a/src/extras/TilingSprite.js +++ b/src/extras/TilingSprite.js @@ -180,9 +180,9 @@ const transform = this.worldTransform; const resolution = renderer.resolution; const baseTexture = texture.baseTexture; - const baseTextureResolution = texture.baseTexture.resolution; - const modX = (this.tilePosition.x / this.tileScale.x) % texture._frame.width; - const modY = (this.tilePosition.y / this.tileScale.y) % texture._frame.height; + const baseTextureResolution = baseTexture.resolution; + const modX = ((this.tilePosition.x / this.tileScale.x) % texture._frame.width) * baseTextureResolution; + const modY = ((this.tilePosition.y / this.tileScale.y) % texture._frame.height) * baseTextureResolution; // create a nice shiny pattern! // TODO this needs to be refreshed if texture changes.. diff --git a/src/extras/cacheAsBitmap.js b/src/extras/cacheAsBitmap.js index 5dd5640..aea06a8 100644 --- a/src/extras/cacheAsBitmap.js +++ b/src/extras/cacheAsBitmap.js @@ -227,7 +227,16 @@ this.transform._parentID = -1; // restore the transform of the cached sprite to avoid the nasty flicker.. - this.updateTransform(); + if (!this.parent) + { + this.parent = renderer._tempDisplayObjectParent; + this.updateTransform(); + this.parent = null; + } + else + { + this.updateTransform(); + } // map the hit test.. this.containsPoint = cachedSprite.containsPoint.bind(cachedSprite); @@ -314,7 +323,17 @@ cachedSprite._bounds = this._bounds; cachedSprite.alpha = cacheAlpha; - this.updateTransform(); + if (!this.parent) + { + this.parent = renderer._tempDisplayObjectParent; + this.updateTransform(); + this.parent = null; + } + else + { + this.updateTransform(); + } + this.updateTransform = this.displayObjectUpdateTransform; this._cacheData.sprite = cachedSprite; @@ -358,9 +377,12 @@ * Destroys the cached object. * * @private + * @param {object|boolean} [options] - Options parameter. A boolean will act as if all options + * have been set to that value. + * Used when destroying containers, see the Container.destroy method. */ -DisplayObject.prototype._cacheAsBitmapDestroy = function _cacheAsBitmapDestroy() +DisplayObject.prototype._cacheAsBitmapDestroy = function _cacheAsBitmapDestroy(options) { this.cacheAsBitmap = false; - this.destroy(); + this.destroy(options); }; diff --git a/src/extras/index.js b/src/extras/index.js index bbd8bbf..a34d7ec 100644 --- a/src/extras/index.js +++ b/src/extras/index.js @@ -1,7 +1,7 @@ /** + * Additional PIXI DisplayObjects for animation, tiling and bitmap text. * @namespace PIXI.extras */ -export { default as TextureTransform } from './TextureTransform'; export { default as AnimatedSprite } from './AnimatedSprite'; export { default as TilingSprite } from './TilingSprite'; export { default as TilingSpriteRenderer } from './webgl/TilingSpriteRenderer'; diff --git a/src/extras/webgl/TilingSpriteRenderer.js b/src/extras/webgl/TilingSpriteRenderer.js index aeec172..eee3b5c 100644 --- a/src/extras/webgl/TilingSpriteRenderer.js +++ b/src/extras/webgl/TilingSpriteRenderer.js @@ -11,7 +11,7 @@ * WebGL renderer plugin for tiling sprites * * @class - * @memberof PIXI + * @memberof PIXI.extras * @extends PIXI.ObjectRenderer */ export default class TilingSpriteRenderer extends core.ObjectRenderer @@ -118,7 +118,7 @@ tempMat.invert(); if (isSimple) { - tempMat.append(uv.mapCoord); + tempMat.prepend(uv.mapCoord); } else { diff --git a/src/filters/displacement/DisplacementFilter.js b/src/filters/displacement/DisplacementFilter.js index d5355c3..b5d030c 100644 --- a/src/filters/displacement/DisplacementFilter.js +++ b/src/filters/displacement/DisplacementFilter.js @@ -35,7 +35,7 @@ this.maskSprite = sprite; this.maskMatrix = maskMatrix; - this.uniforms.mapSampler = sprite.texture; + this.uniforms.mapSampler = sprite._texture; this.uniforms.filterMatrix = maskMatrix; this.uniforms.scale = { x: 1, y: 1 }; diff --git a/src/filters/index.js b/src/filters/index.js index f3e8862..7ceb50a 100644 --- a/src/filters/index.js +++ b/src/filters/index.js @@ -1,4 +1,21 @@ /** + * This namespace contains WebGL-only display filters that can be applied + * to DisplayObjects using the {@link PIXI.DisplayObject#filters filters} property. + * @example + * // Create a new application + * const app = new PIXI.Application(); + * + * // Draw a green rectangle + * const rect = new PIXI.Graphics() + * .beginFill(0x00ff00) + * .drawRect(40, 40, 200, 200); + * + * // Add a blur filter + * rect.filters = [new PIXI.filters.BlurFilter()]; + * + * // Display rectangle + * app.stage.addChild(rect); + * document.body.appendChild(app.view); * @namespace PIXI.filters */ export { default as FXAAFilter } from './fxaa/FXAAFilter'; diff --git a/.travis.yml b/.travis.yml index 3df8b22..14de334 100644 --- a/.travis.yml +++ b/.travis.yml @@ -110,3 +110,43 @@ on: all_branches: true condition: $TRAVIS_TAG + # Deploy config for latest release + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: dist + upload-dir: "release" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: docs + upload-dir: "release/docs" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: coverage + upload-dir: "release/coverage" + on: + all_branches: true + condition: $TRAVIS_TAG \ No newline at end of file diff --git a/README.md b/README.md index d26fadd..3b85f85 100644 --- a/README.md +++ b/README.md @@ -36,22 +36,14 @@ - Wiki: Other misc tutorials and resources are [on the Wiki](https://github.com/pixijs/pixi.js/wiki/Resources). ### Community ### -- Forums: Check out the [forum] (http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow] (http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. +- Forums: Check out the [forum](http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow](http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. - Inspiration: Check out the [gallery](http://www.pixijs.com/gallery) to see some of the amazing things people have created! - Chat: You can join us on [Gitter](https://gitter.im/pixijs/pixi.js) To chat about Pixi. We also now have a Slack channel. If you would like to join it please Send me an email (mat@goodboydigital.com) and I will invite you in. ### Setup ### -It's easy to get started with Pixi.js! Simply grab the pre-built versions from here: - -Release Branch - Nice and stable Pixi.js -- Unminified: [http://pixijs.download/release/pixi.js] -- Minified: [http://pixijs.download/release/pixi.min.js] - -Develop Branch - The bleeding edge version of Pixi.js -- Unminified: [http://pixijs.download/dev/pixi.js] -- Minified: [http://pixijs.download/dev/pixi.min.js] +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. @@ -70,10 +62,10 @@ #### CDN Install (via cdnjs) ```html - + ``` -_Note: `4.2.2` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ +_Note: `4.4.3` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ ### Demos ### @@ -130,7 +122,7 @@ // and the root stage PIXI.Container. var app = new PIXI.Application(); -// The application will create a canvas element for you that you +// The application will create a canvas element for you that you // can then insert into the DOM. document.body.appendChild(app.view); @@ -150,7 +142,7 @@ // Add the bunny to the scene we are building. app.stage.addChild(bunny); - + // Listen for frame updates app.ticker.add(function() { // each frame we spin the bunny around a bit diff --git a/bower.json b/bower.json deleted file mode 100644 index 41b668c..0000000 --- a/bower.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "pixi.js", - "main": "dist/pixi.min.js", - "ignore": [ - "**/.*", - "docs", - "node_modules", - "bower_components", - "test", - "scripts", - "bower.json", - "inch.json" - ], - "dependencies": { - }, - "devDependencies": { - } -} diff --git a/package.json b/package.json index d35cbb2..308469f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.3.5", + "version": "4.4.4", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ @@ -40,11 +40,11 @@ "lib": "babel src --out-dir lib -s", "predocs": "rimraf docs/**", "docs": "jsdoc -c scripts/jsdoc.conf.json -R README.md", - "publish:patch": "npm version patch --no-git-tag-version && npm publish", - "publish:minor": "npm version minor --no-git-tag-version && npm publish", - "publish:major": "npm version major --no-git-tag-version && npm publish", + "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", - "postpublish": "node scripts/release.js" + "postpublish": "git push && git push --tags" }, "files": [ "dist/", @@ -72,7 +72,6 @@ "electron": "^1.4.15", "eslint": "^3.5.0", "floss": "^2.0.1", - "gh-pages": "^0.11.0", "jaguarjs-jsdoc": "^1.0.1", "js-md5": "^0.4.1", "jsdoc": "^3.4.2", diff --git a/scripts/jsdoc.conf.json b/scripts/jsdoc.conf.json index 553e61c..3f52180 100644 --- a/scripts/jsdoc.conf.json +++ b/scripts/jsdoc.conf.json @@ -43,7 +43,7 @@ }, "markdown" : { "parser" : "gfm", - "hardwrap" : true + "hardwrap" : false }, "opts": { "encoding" : "utf8", diff --git a/scripts/release.js b/scripts/release.js deleted file mode 100644 index 156620c..0000000 --- a/scripts/release.js +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env node -'use strict'; - -// Publish script to push releases of the bin files -// the normally are gitignored -const ghpages = require('gh-pages'); -const path = require('path'); -const packageInfo = require(path.join(__dirname, '..', 'package.json')); - -const options = { - src: [ - 'dist/**/*', - 'lib/**/*', - 'src/**/*', - 'scripts/*', - 'scripts/renders/*', - 'test/**/*', - '*.json', - '*.md', - 'LICENSE', - '.eslintrc', - '.editorconfig', - '.travis.yml', - '.babelrc', - ], - dotfiles: true, - branch: 'release', - message: packageInfo.version, - logger: console.log.bind(console), -}; - -ghpages.publish(process.cwd(), options, (err) => -{ - if (err) - { - console.log(err); - process.exit(1); - - return; - } - - process.exit(0); -}); diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index f16bcb1..220427a 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -21,7 +21,7 @@ const DIV_HOOK_ZINDEX = 2; /** - * The Accessibility manager reacreates the ability to tab and and have content read by screen + * The Accessibility manager recreates the ability to tab and and have content read by screen * readers. This is very important as it can possibly help people with disabilities access pixi * content. * diff --git a/src/accessibility/index.js b/src/accessibility/index.js index 568cfb1..0ae108f 100644 --- a/src/accessibility/index.js +++ b/src/accessibility/index.js @@ -1,4 +1,9 @@ /** + * This namespace contains a renderer plugin for interaction accessibility for end-users + * with physical impairments which require screen-renders, keyboard navigation, etc. + * + * Do not instantiate this plugin directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. * @namespace PIXI.accessibility */ export { default as accessibleTarget } from './accessibleTarget'; diff --git a/src/core/Application.js b/src/core/Application.js index 5340578..2fcc729 100644 --- a/src/core/Application.js +++ b/src/core/Application.js @@ -1,6 +1,8 @@ import { autoDetectRenderer } from './autoDetectRenderer'; import Container from './display/Container'; import { shared, Ticker } from './ticker'; +import settings from './settings'; +import { UPDATE_PRIORITY } from './const'; /** * Convenience class to create a new PIXI application. @@ -22,28 +24,52 @@ */ export default class Application { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experience unexplained flickering try setting this to true. - * @param {boolean} [useSharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedLoader=false] - `true` to use PIXI.loaders.shared, `false` to create new Loader. */ - constructor(width, height, options, noWebGL, useSharedTicker = false) + constructor(options, arg2, arg3, arg4, arg5) { + // Support for constructor(width, height, options, noWebGL, useSharedTicker) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + forceCanvas: !!arg4, + sharedTicker: !!arg5, + }, arg3); + } + + /** + * The default options, so we mixin functionality later. + * @member {object} + * @protected + */ + this._options = options = Object.assign({ + sharedTicker: false, + forceCanvas: false, + sharedLoader: false, + }, options); + /** * WebGL renderer if available, otherwise CanvasRenderer * @member {PIXI.WebGLRenderer|PIXI.CanvasRenderer} */ - this.renderer = autoDetectRenderer(width, height, options, noWebGL); + this.renderer = autoDetectRenderer(options); /** * The root display container that's rendered. @@ -63,7 +89,7 @@ * @member {PIXI.ticker.Ticker} * @default PIXI.ticker.shared */ - this.ticker = useSharedTicker ? shared : new Ticker(); + this.ticker = options.sharedTicker ? shared : new Ticker(); // Start the rendering this.start(); @@ -78,7 +104,7 @@ this._ticker = ticker; if (ticker) { - ticker.add(this.render, this); + ticker.add(this.render, this, UPDATE_PRIORITY.LOW); } } get ticker() // eslint-disable-line require-jsdoc @@ -136,13 +162,18 @@ */ destroy(removeView) { - this.stop(); + const oldTicker = this._ticker; + this.ticker = null; + oldTicker.destroy(); + this.stage.destroy(); this.stage = null; this.renderer.destroy(removeView); this.renderer = null; + + this._options = null; } } diff --git a/src/core/autoDetectRenderer.js b/src/core/autoDetectRenderer.js index 8960425..9ec0364 100644 --- a/src/core/autoDetectRenderer.js +++ b/src/core/autoDetectRenderer.js @@ -2,6 +2,7 @@ import CanvasRenderer from './renderers/canvas/CanvasRenderer'; import WebGLRenderer from './renderers/webgl/WebGLRenderer'; +// eslint-disable-next-line valid-jsdoc /** * This helper function will automatically detect which renderer you should be using. * WebGL is the preferred renderer as it is a lot faster. If webGL is not supported by @@ -9,24 +10,32 @@ * * @memberof PIXI * @function autoDetectRenderer - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @return {PIXI.WebGLRenderer|PIXI.CanvasRenderer} Returns WebGL renderer if available, otherwise CanvasRenderer */ -export function autoDetectRenderer(width = 800, height = 600, options, noWebGL) +export function autoDetectRenderer(options, arg1, arg2, arg3) { - if (!noWebGL && utils.isWebGLSupported()) + // Backward-compatible support for noWebGL option + let forceCanvas = options && options.forceCanvas; + + if (arg3 !== undefined) { - return new WebGLRenderer(width, height, options); + forceCanvas = arg3; } - return new CanvasRenderer(width, height, options); + if (!forceCanvas && utils.isWebGLSupported()) + { + return new WebGLRenderer(options, arg1, arg2); + } + + return new CanvasRenderer(options, arg1, arg2); } diff --git a/src/core/const.js b/src/core/const.js index b9e99ae..2c68dee 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -341,3 +341,27 @@ LINEAR_VERTICAL: 0, LINEAR_HORIZONTAL: 1, }; + +/** + * Represents the update priorities used by internal PIXI classes when registered with + * the {@link PIXI.ticker.Ticker} object. Higher priority items are updated first and lower + * priority items, such as render, should go later. + * + * @static + * @constant + * @name UPDATE_PRIORITY + * @memberof PIXI + * @type {object} + * @property {number} INTERACTION=50 Highest priority, used for {@link PIXI.interaction.InteractionManager} + * @property {number} HIGH=25 High priority updating, {@link PIXI.VideoBaseTexture} and {@link PIXI.extras.AnimatedSprite} + * @property {number} NORMAL=0 Default priority for ticker events, see {@link PIXI.ticker.Ticker#add}. + * @property {number} LOW=-25 Low priority used for {@link PIXI.Application} rendering. + * @property {number} UTILITY=-50 Lowest priority used for {@link PIXI.prepare.BasePrepare} utility. + */ +export const UPDATE_PRIORITY = { + INTERACTION: 50, + HIGH: 25, + NORMAL: 0, + LOW: -25, + UTILITY: -50, +}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 4e6c77d..decb42a 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -13,7 +13,6 @@ * * @class * @extends EventEmitter - * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ export default class DisplayObject extends EventEmitter @@ -113,6 +112,29 @@ * @private */ this._mask = null; + + /** + * If the object has been destroyed via destroy(). If true, it should not be used. + * + * @member {boolean} + * @private + * @readonly + */ + this._destroyed = false; + + /** + * Fired when this DisplayObject is added to a Container. + * + * @event PIXI.DisplayObject#added + * @param {PIXI.Container} container - The container added to. + */ + + /** + * Fired when this DisplayObject is removed from a Container. + * + * @event PIXI.DisplayObject#removed + * @param {PIXI.Container} container - The container removed from. + */ } /** @@ -403,6 +425,8 @@ this.interactive = false; this.interactiveChildren = false; + + this._destroyed = true; } /** diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index aafdfe2..f787c4a 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -792,8 +792,8 @@ sprite.worldAlpha = this.worldAlpha * sprite.alpha; sprite.blendMode = this.blendMode; - sprite.texture._frame.width = rect.width; - sprite.texture._frame.height = rect.height; + sprite._texture._frame.width = rect.width; + sprite._texture._frame.height = rect.height; sprite.transform.worldTransform = this.transform.worldTransform; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index a24c775..b9f192d 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -19,11 +19,12 @@ */ export default class SystemRenderer extends EventEmitter { + // eslint-disable-next-line valid-jsdoc /** * @param {string} system - The name of the system this renderer is for. - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -37,27 +38,31 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(system, screenWidth, screenHeight, options) + constructor(system, options, arg2, arg3) { super(); sayHello(system); - // prepare options - if (options) + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') { - for (const i in settings.RENDER_OPTIONS) - { - if (typeof options[i] === 'undefined') - { - options[i] = settings.RENDER_OPTIONS[i]; - } - } + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); } - else - { - options = settings.RENDER_OPTIONS; - } + + // Add the default render options + options = Object.assign({}, settings.RENDER_OPTIONS, options); + + /** + * The supplied constructor options. + * + * @member {Object} + * @readOnly + */ + this.options = options; /** * The type of the renderer. @@ -75,7 +80,7 @@ * * @member {PIXI.Rectangle} */ - this.screen = new Rectangle(0, 0, screenWidth || 800, screenHeight || 600); + this.screen = new Rectangle(0, 0, options.width, options.height); /** * The canvas element that everything is drawn to @@ -279,6 +284,8 @@ this.blendModes = null; + this.options = null; + this.preserveDrawingBuffer = false; this.clearBeforeRender = false; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index e84e49c..691caf0 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -17,10 +17,11 @@ */ export default class CanvasRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -34,9 +35,9 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('Canvas', screenWidth, screenHeight, options); + super('Canvas', options, arg2, arg3); this.type = RENDERER_TYPE.CANVAS; @@ -96,7 +97,19 @@ this.context = null; this.renderingToScreen = false; - this.resize(screenWidth, screenHeight); + this.resize(this.options.width, this.options.height); + + /** + * Fired after rendering finishes. + * + * @event PIXI.CanvasRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.CanvasRenderer#prerender + */ } /** @@ -299,4 +312,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.CanvasRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.CanvasExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.CanvasPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.CanvasRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index e1cf7cd..f6fe123 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -31,11 +31,12 @@ */ export default class WebGLRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** * - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -55,9 +56,9 @@ * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('WebGL', screenWidth, screenHeight, options); + super('WebGL', options, arg2, arg3); /** * The type of this renderer as a standardised const @@ -89,12 +90,6 @@ }; - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ this._backgroundColorRgba[3] = this.transparent ? 0 : 1; this.globalUniforms = new UniformGroup({ @@ -117,6 +112,13 @@ this.initPlugins(); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ if(options.context) { this.context.initFromContext(options.context); @@ -169,6 +171,25 @@ } return this; + + /** + * Fired after rendering finishes. + * + * @event PIXI.WebGLRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.WebGLRenderer#prerender + */ + + /** + * Fired when the WebGL context is set. + * + * @event PIXI.WebGLRenderer#context + * @param {WebGLRenderingContext} gl - WebGL context. + */ } /** @@ -295,4 +316,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.WebGLRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.WebGLRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(WebGLRenderer); diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index 4eb6128..fa3d883 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -47,6 +47,14 @@ * @member {boolean} */ this.enabled = true; + + /** + * If enabled, pixi will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; } /** diff --git a/src/core/renderers/webgl/systems/ContextSystem.js b/src/core/renderers/webgl/systems/ContextSystem.js index ff958c7..8df85ea 100644 --- a/src/core/renderers/webgl/systems/ContextSystem.js +++ b/src/core/renderers/webgl/systems/ContextSystem.js @@ -75,7 +75,8 @@ */ createContext(canvas, options) { - var gl = canvas.getContext('webgl', options) || + var gl = //canvas.getContext('webgl2', options) || + canvas.getContext('webgl', options) || canvas.getContext('experimental-webgl', options); if (!gl) diff --git a/src/core/renderers/webgl/systems/FilterSystem.js b/src/core/renderers/webgl/systems/FilterSystem.js index f47335d..37e28d2 100644 --- a/src/core/renderers/webgl/systems/FilterSystem.js +++ b/src/core/renderers/webgl/systems/FilterSystem.js @@ -111,7 +111,7 @@ // TODO we should fit the rect around the transform.. } - else + else if (filters[0].autoFit) { sourceFrame.fit(filterData.stack[0].destinationFrame); } diff --git a/src/core/renderers/webgl/systems/shader/shader/mapType.js b/src/core/renderers/webgl/systems/shader/shader/mapType.js index 581dace..917538a 100644 --- a/src/core/renderers/webgl/systems/shader/shader/mapType.js +++ b/src/core/renderers/webgl/systems/shader/shader/mapType.js @@ -15,7 +15,7 @@ } } - return GL_TABLE[type]; + return GL_TABLE[type]; }; var GL_TABLE = null; diff --git a/src/core/settings.js b/src/core/settings.js index 18c5877..38ef45c 100644 --- a/src/core/settings.js +++ b/src/core/settings.js @@ -2,6 +2,15 @@ import canUploadSameBuffer from './utils/canUploadSameBuffer'; /** + * User's customizable globals for overriding the default PIXI settings, such + * as a renderer's default resolution, framerate, float percision, etc. + * @example + * // Use the native window resolution as the default resolution + * // will support high-density displays when rendering + * PIXI.settings.RESOLUTION = window.devicePixelRatio. + * + * // Disable interpolation when scaling, will make texture be pixelated + * PIXI.settings.SCALE_MODE = PIXI.SCALE_MODES.NEAREST; * @namespace PIXI.settings */ export default { @@ -101,6 +110,9 @@ * @property {boolean} clearBeforeRender=true * @property {boolean} preserveDrawingBuffer=false * @property {boolean} roundPixels=false + * @property {number} width=800 + * @property {number} height=600 + * @property {boolean} legacy=false */ RENDER_OPTIONS: { view: null, @@ -112,6 +124,9 @@ clearBeforeRender: true, preserveDrawingBuffer: false, roundPixels: false, + width: 800, + height: 600, + legacy: false, }, /** diff --git a/src/core/shader/Program.js b/src/core/shader/Program.js index c0b538b..07b4398 100644 --- a/src/core/shader/Program.js +++ b/src/core/shader/Program.js @@ -1,6 +1,6 @@ import extractUniformsFromSrc from './extractUniformsFromSrc'; import generateUniformsSync from './generateUniformsSync'; -import glCore from 'pixi-gl-core'; +import shaderUtils from '../renderers/webgl/systems/shader/shader'; import { ProgramCache } from '../utils'; import getTestContext from '../utils/getTestContext'; @@ -64,14 +64,17 @@ } else { - vertexSrc = glCore.shader.setPrecision(vertexSrc, 'mediump'); - fragmentSrc = glCore.shader.setPrecision(fragmentSrc, 'mediump'); + vertexSrc = shaderUtils.setPrecision(vertexSrc, 'mediump'); + fragmentSrc = shaderUtils.setPrecision(fragmentSrc, 'mediump'); - const program = glCore.shader.compileProgram(gl, vertexSrc, fragmentSrc); + const program = shaderUtils.compileProgram(gl, vertexSrc, fragmentSrc); + console.log("<<<<<<>>><<"); this.attributeData = this.getAttributeData(program, gl); this.uniformData = this.getUniformData(program, gl); + console.log(this.uniformData); + //gl.deleteProgram(program); } } @@ -95,13 +98,13 @@ for (let i = 0; i < totalAttributes; i++) { const attribData = gl.getActiveAttrib(program, i); - const type = glCore.shader.mapType(gl, attribData.type); + const type = shaderUtils.mapType(gl, attribData.type); /*eslint-disable */ const data = { type: type, name: attribData.name, - size: glCore.shader.mapSize(type), + size: shaderUtils.mapSize(type), location: 0, }; /*eslint-enable */ @@ -137,13 +140,14 @@ // TODO expose this as a prop? // const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); - const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); + //const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); for (let i = 0; i < totalUniforms; i++) { const uniformData = gl.getActiveUniform(program, i); const name = uniformData.name.replace(/\[.*?\]/, ''); - const type = glCore.shader.mapType(gl, uniformData.type); + const type = shaderUtils.mapType(gl, uniformData.type); + console.log('mapping ' + uniformData.type + ' to ' + type); // if (!name.match(maskRegex)) { @@ -151,7 +155,7 @@ uniforms[name] = { type: type, size: uniformData.size, - value: glCore.shader.defaultValue(type, uniformData.size), + value: shaderUtils.defaultValue(type, uniformData.size), }; /*eslint-enable */ } diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index e387e99..b57d095 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -147,12 +147,12 @@ // so if _width is 0 then width was not set.. if (this._width) { - this.scale.x = sign(this.scale.x) * this._width / this.texture.orig.width; + this.scale.x = sign(this.scale.x) * this._width / this._texture.orig.width; } if (this._height) { - this.scale.y = sign(this.scale.y) * this._height / this.texture.orig.height; + this.scale.y = sign(this.scale.y) * this._height / this._texture.orig.height; } } diff --git a/src/core/sprites/canvas/CanvasTinter.js b/src/core/sprites/canvas/CanvasTinter.js index 27e5e0c..fd9de5d 100644 --- a/src/core/sprites/canvas/CanvasTinter.js +++ b/src/core/sprites/canvas/CanvasTinter.js @@ -18,7 +18,7 @@ */ getTintedTexture: (sprite, color) => { - const texture = sprite.texture; + const texture = sprite._texture; color = CanvasTinter.roundColor(color); diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 5a4edda..8b9b797 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -344,6 +344,7 @@ const texture = this._texture; const style = this._style; + const padding = style.trim ? 0 : style.padding; texture.baseTexture.valid = true; texture.baseTexture.resolution = this.resolution; @@ -353,11 +354,11 @@ texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - texture.trim.x = -style.padding; - texture.trim.y = -style.padding; + texture.trim.x = -padding; + texture.trim.y = -padding; - texture.orig.width = texture._frame.width - (style.padding * 2); - texture.orig.height = texture._frame.height - (style.padding * 2); + texture.orig.width = texture._frame.width - (padding * 2); + texture.orig.height = texture._frame.height - (padding * 2); // call sprite onTextureUpdate to update scale if _width or _height were set this._onTextureUpdate(); diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index 853da3d..8a9c34b 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -114,6 +114,38 @@ this.cacheId = null; this.validate(); + + /** + * Fired when a not-immediately-available source finishes loading. + * + * @protected + * @event PIXI.BaseTexture#loaded + * @param {PIXI.BaseTexture} baseTexture - Resource loaded. + */ + + /** + * Fired when a not-immediately-available source fails to load. + * + * @protected + * @event PIXI.BaseTexture#error + * @param {PIXI.BaseTexture} baseTexture - Resource errored. + */ + + /** + * Fired when BaseTexture is updated. + * + * @protected + * @event PIXI.BaseTexture#update + * @param {PIXI.BaseTexture} baseTexture - Instance of texture being updated. + */ + + /** + * Fired when BaseTexture is destroyed. + * + * @protected + * @event PIXI.BaseTexture#dispose + * @param {PIXI.BaseTexture} baseTexture - Instance of texture being destroyed. + */ } setResource(resource) diff --git a/src/core/textures/Spritesheet.js b/src/core/textures/Spritesheet.js index d469c62..9b24f27 100644 --- a/src/core/textures/Spritesheet.js +++ b/src/core/textures/Spritesheet.js @@ -208,6 +208,7 @@ // lets also add the frame to pixi's global cache for fromFrame and fromImage functions TextureCache[i] = this.textures[i]; + this.textures[i].textureCacheId = i; } frameIndex++; diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 0a2c379..2213087 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -147,9 +147,9 @@ /** * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. * - * @event update - * @memberof PIXI.Texture# + * @event PIXI.Texture#update * @protected + * @param {PIXI.Texture} texture - Instance of texture being updated. */ this._updateID = 0; @@ -159,6 +159,15 @@ * @type {Object} */ this.transform = null; + + /** + * The id under which this Texture has been added to the texture cache. This is + * automatically set in certain cases, but may not always be accurate, particularly if + * the texture is in the cache under multiple ids. + * + * @member {string} + */ + this.textureCacheId = null; } /** @@ -225,11 +234,17 @@ this._uvs = null; this.trim = null; this.orig = null; + this.textureCacheId = null; this.valid = false; - this.off('dispose', this.dispose, this); - this.off('update', this.update, this); + for (const prop in TextureCache) + { + if (TextureCache[prop] === this) + { + delete TextureCache[prop]; + } + } } /** @@ -315,14 +330,15 @@ */ static fromLoader(source, imageUrl, name) { - // console.log('added from loader...') + // console.log('added from loader...') const resource = new ImageResource(source);//.from(imageUrl, crossorigin);// document.createElement('img'); - // console.log('base resource ' + resource.width); + // console.log('base resource ' + resource.width); const baseTexture = new BaseTexture(resource, settings.SCALE_MODE, getResolutionOfUrl(imageUrl)); + /// console.log('base width ' + baseTexture.width); const texture = new Texture(baseTexture); @@ -335,6 +351,7 @@ // lets also add the frame to pixi's global cache for fromFrame and fromImage fucntions BaseTextureCache[name] = baseTexture; TextureCache[name] = texture; + texture.textureCacheId = name; // also add references by url if they are different. if (name !== imageUrl) @@ -355,6 +372,10 @@ */ static addTextureToCache(texture, id) { + if (!texture.textureCacheId) + { + texture.textureCacheId = id; + } TextureCache[id] = texture; } @@ -495,6 +516,7 @@ */ Texture.EMPTY = new Texture(new BaseTexture()); removeAllHandlers(Texture.EMPTY); +removeAllHandlers(Texture.EMPTY.baseTexture); /** * A white texture of 10x10 size, used for graphics and other things @@ -505,3 +527,4 @@ */ Texture.WHITE = createWhiteTexture(); removeAllHandlers(Texture.WHITE); +removeAllHandlers(Texture.WHITE.baseTexture); diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index 59eb9fd..a9b9f51 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -1,6 +1,7 @@ import BaseTexture from './BaseTexture'; import { uid, BaseTextureCache } from '../utils'; -import * as ticker from '../ticker'; +import { shared } from '../ticker'; +import { UPDATE_PRIORITY } from '../const'; /** * A texture of a [playing] Video. @@ -125,7 +126,7 @@ if (!this._isAutoUpdating && this.autoUpdate) { - ticker.shared.add(this.update, this); + shared.add(this.update, this, UPDATE_PRIORITY.HIGH); this._isAutoUpdating = true; } } @@ -139,7 +140,7 @@ { if (this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); this._isAutoUpdating = false; } } @@ -187,7 +188,7 @@ { if (this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); } if (this.source && this.source._pixiId) @@ -281,12 +282,12 @@ if (!this._autoUpdate && this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); this._isAutoUpdating = false; } else if (this._autoUpdate && !this._isAutoUpdating) { - ticker.shared.add(this.update, this); + shared.add(this.update, this, UPDATE_PRIORITY.HIGH); this._isAutoUpdating = true; } } diff --git a/src/core/ticker/Ticker.js b/src/core/ticker/Ticker.js index 17a1517..40df138 100644 --- a/src/core/ticker/Ticker.js +++ b/src/core/ticker/Ticker.js @@ -1,12 +1,10 @@ import settings from '../settings'; -import EventEmitter from 'eventemitter3'; - -// Internal event used by composed emitter -const TICK = 'tick'; +import { UPDATE_PRIORITY } from '../const'; +import TickerListener from './TickerListener'; /** * A Ticker class that runs an update loop that other objects listen to. - * This class is composed around an EventEmitter object to add listeners + * This class is composed around listeners * meant for execution on the next requested animation frame. * Animation frames are requested only when necessary, * e.g. When the ticker is started and the emitter has listeners. @@ -22,10 +20,11 @@ constructor() { /** - * Internal emitter used to fire 'tick' event + * The first listener. All new listeners added are chained on this. * @private + * @type {TickerListener} */ - this._emitter = new EventEmitter(); + this._head = new TickerListener(null, null, Infinity); /** * Internal current frame request ID @@ -131,7 +130,7 @@ // Invoke listeners now this.update(time); // Listener side effects may have modified ticker state. - if (this.started && this._requestId === null && this._emitter.listeners(TICK, true)) + if (this.started && this._requestId === null && this._head.next) { this._requestId = requestAnimationFrame(this._tick); } @@ -148,7 +147,7 @@ */ _requestIfNeeded() { - if (this._requestId === null && this._emitter.listeners(TICK, true)) + if (this._requestId === null && this._head.next) { // ensure callbacks get correct delta this.lastTime = performance.now(); @@ -193,35 +192,72 @@ } /** - * Calls {@link module:eventemitter3.EventEmitter#on} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. + * Register a handler for tick events. Calls continuously unless + * it is removed or the ticker is stopped. * * @param {Function} fn - The listener function to be added for updates * @param {Function} [context] - The listener context + * @param {number} [priority=PIXI.UPDATE_PRIORITY.NORMAL] - The priority for emitting * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - add(fn, context) + add(fn, context, priority = UPDATE_PRIORITY.NORMAL) { - this._emitter.on(TICK, fn, context); - - this._startIfPossible(); - - return this; + return this._addListener(new TickerListener(fn, context, priority)); } /** - * Calls {@link module:eventemitter3.EventEmitter#once} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. + * Add a handler for the tick event which is only execute once. * * @param {Function} fn - The listener function to be added for one update * @param {Function} [context] - The listener context + * @param {number} [priority=PIXI.UPDATE_PRIORITY.NORMAL] - The priority for emitting * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - addOnce(fn, context) + addOnce(fn, context, priority = UPDATE_PRIORITY.NORMAL) { - this._emitter.once(TICK, fn, context); + return this._addListener(new TickerListener(fn, context, priority, true)); + } + + /** + * Internally adds the event handler so that it can be sorted by priority. + * Priority allows certain handler (user, AnimatedSprite, Interaction) to be run + * before the rendering. + * + * @private + * @param {TickerListener} listener - Current listener being added. + * @returns {PIXI.ticker.Ticker} This instance of a ticker + */ + _addListener(listener) + { + // For attaching to head + let current = this._head.next; + let previous = this._head; + + // Add the first item + if (!current) + { + listener.connect(previous); + } + else + { + // Go from highest to lowest priority + while (current) + { + if (listener.priority >= current.priority) + { + listener.connect(previous); + break; + } + previous = current; + current = current.next; + } + + // Not yet connected + if (!listener.previous) + { + listener.connect(previous); + } + } this._startIfPossible(); @@ -229,19 +265,33 @@ } /** - * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. - * It checks if the emitter has listeners for 'tick' event. - * If it does, then it cancels the animation frame. + * Removes any handlers matching the function and context parameters. + * If no handlers are left after removing, then it cancels the animation frame. * - * @param {Function} [fn] - The listener function to be removed + * @param {Function} fn - The listener function to be removed * @param {Function} [context] - The listener context to be removed * @returns {PIXI.ticker.Ticker} This instance of a ticker */ remove(fn, context) { - this._emitter.off(TICK, fn, context); + let listener = this._head.next; - if (!this._emitter.listeners(TICK, true)) + while (listener) + { + // We found a match, lets remove it + // no break to delete all possible matches + // incase a listener was added 2+ times + if (listener.match(fn, context)) + { + listener = listener.destroy(); + } + else + { + listener = listener.next; + } + } + + if (!this._head.next) { this._cancelIfNeeded(); } @@ -276,6 +326,25 @@ } /** + * Destroy the ticker and don't use after this. Calling + * this method removes all references to internal events. + */ + destroy() + { + this.stop(); + + let listener = this._head.next; + + while (listener) + { + listener = listener.destroy(true); + } + + this._head.destroy(); + this._head = null; + } + + /** * Triggers an update. An update entails setting the * current {@link PIXI.ticker.Ticker#elapsedMS}, * the current {@link PIXI.ticker.Ticker#deltaTime}, @@ -320,8 +389,22 @@ this.deltaTime = elapsedMS * settings.TARGET_FPMS * this.speed; + // Cache a local reference, in-case ticker is destroyed + // during the emit, we can still check for head.next + const head = this._head; + // Invoke listeners added to internal emitter - this._emitter.emit(TICK, this.deltaTime); + let listener = head.next; + + while (listener) + { + listener = listener.emit(this.deltaTime); + } + + if (!head.next) + { + this._cancelIfNeeded(); + } } else { diff --git a/src/core/ticker/TickerListener.js b/src/core/ticker/TickerListener.js new file mode 100644 index 0000000..2bedb34 --- /dev/null +++ b/src/core/ticker/TickerListener.js @@ -0,0 +1,158 @@ +/** + * Internal class for handling the priority sorting of ticker handlers. + * + * @private + * @class + * @memberof PIXI.ticker + */ +export default class TickerListener +{ + /** + * Constructor + * + * @param {Function} fn - The listener function to be added for one update + * @param {Function} [context=null] - The listener context + * @param {number} [priority=0] - The priority for emitting + * @param {boolean} [once=false] - If the handler should fire once + */ + constructor(fn, context = null, priority = 0, once = false) + { + /** + * The handler function to execute. + * @member {Function} + */ + this.fn = fn; + + /** + * The calling to execute. + * @member {Function} + */ + this.context = context; + + /** + * The current priority. + * @member {number} + */ + this.priority = priority; + + /** + * If this should only execute once. + * @member {boolean} + */ + this.once = once; + + /** + * The next item in chain. + * @member {TickerListener} + */ + this.next = null; + + /** + * The previous item in chain. + * @member {TickerListener} + */ + this.previous = null; + + /** + * `true` if this listener has been destroyed already. + * @member {boolean} + * @private + */ + this._destroyed = false; + } + + /** + * Simple compare function to figure out if a function and context match. + * + * @param {Function} fn - The listener function to be added for one update + * @param {Function} context - The listener context + * @return {boolean} `true` if the listener match the arguments + */ + match(fn, context) + { + context = context || null; + + return this.fn === fn && this.context === context; + } + + /** + * Emit by calling the current function. + * @param {number} deltaTime - time since the last emit. + * @return {TickerListener} Next ticker + */ + emit(deltaTime) + { + if (this.context) + { + this.fn.call(this.context, deltaTime); + } + else + { + this.fn(deltaTime); + } + + if (this.once) + { + this.destroy(); + } + + const redirect = this.next; + + // Soft-destroying should remove + // the next reference + if (this._destroyed) + { + this.next = null; + } + + return redirect; + } + + /** + * Connect to the list. + * @param {TickerListener} previous - Input node, previous listener + */ + connect(previous) + { + this.previous = previous; + if (previous.next) + { + previous.next.previous = this; + } + this.next = previous.next; + previous.next = this; + } + + /** + * Destroy and don't use after this. + * @param {boolean} [hard = false] `true` to remove the `next` reference, this + * is considered a hard destroy. Soft destroy maintains the next reference. + * @return {TickerListener} The listener to redirect while emitting or removing. + */ + destroy(hard = false) + { + this._destroyed = true; + this.fn = null; + this.context = null; + + // Disconnect, hook up next and previous + if (this.previous) + { + this.previous.next = this.next; + } + + if (this.next) + { + this.next.previous = this.previous; + } + + // Redirect to the next item + const redirect = this.previous; + + // Remove references + this.next = hard ? null : redirect; + this.previous = null; + + return redirect; + } +} diff --git a/src/core/ticker/index.js b/src/core/ticker/index.js index 4b3017c..c3020d4 100644 --- a/src/core/ticker/index.js +++ b/src/core/ticker/index.js @@ -45,8 +45,25 @@ const shared = new Ticker(); shared.autoStart = true; +shared.destroy = () => +{ + // protect destroying shared ticker + // this is used by other internal systems + // like AnimatedSprite and InteractionManager +}; /** + * This namespace contains an API for interacting with PIXI's internal global update loop. + * + * This ticker is used for rendering, {@link PIXI.extras.AnimatedSprite AnimatedSprite}, + * {@link PIXI.interaction.InteractionManager InteractionManager} and many other time-based PIXI systems. + * @example + * const ticker = new PIXI.ticker.Ticker(); + * ticker.stop(); + * ticker.add((deltaTime) => { + * // do something every frame + * }); + * ticker.start(); * @namespace PIXI.ticker */ export { shared, Ticker }; diff --git a/src/core/utils/index.js b/src/core/utils/index.js index be44750..7441f7b 100644 --- a/src/core/utils/index.js +++ b/src/core/utils/index.js @@ -9,6 +9,21 @@ let saidHello = false; /** + * Generalized convenience utilities for PIXI. + * @example + * // Extend PIXI's internal Event Emitter. + * class MyEmitter extends PIXI.utils.EventEmitter { + * constructor() { + * super(); + * console.log("Emitter created!"); + * } + * } + * + * // Get info on current device + * console.log(PIXI.utils.isMobile); + * + * // Convert hex color to string + * console.log(PIXI.utils.hex2string(0xff00ff)); // returns: "#ff00ff" * @namespace PIXI.utils */ export { diff --git a/src/deprecation.js b/src/deprecation.js index b9c73b3..33daeec 100644 --- a/src/deprecation.js +++ b/src/deprecation.js @@ -918,6 +918,35 @@ }); /** + * @method + * @name PIXI.prepare.BasePrepare#register + * @see PIXI.prepare.BasePrepare#registerFindHook + * @deprecated since version 4.4.2 + * @param {Function} [addHook] - Function call that takes two parameters: `item:*, queue:Array` + * function must return `true` if it was able to add item to the queue. + * @param {Function} [uploadHook] - Function call that takes two parameters: `prepare:CanvasPrepare, item:*` and + * function must return `true` if it was able to handle upload of item. + * @return {PIXI.BasePrepare} Instance of plugin for chaining. + */ +prepare.BasePrepare.prototype.register = function register(addHook, uploadHook) +{ + warn('renderer.plugins.prepare.register is now deprecated, ' + + 'please use renderer.plugins.prepare.registerFindHook & renderer.plugins.prepare.registerUploadHook'); + + if (addHook) + { + this.registerFindHook(addHook); + } + + if (uploadHook) + { + this.registerUploadHook(uploadHook); + } + + return this; +}; + +/** * The number of graphics or textures to upload to the GPU. * * @name PIXI.prepare.canvas.UPLOADS_PER_FRAME @@ -969,67 +998,73 @@ }, }); -Object.defineProperties(loaders.Resource.prototype, { - isJson: { - get() - { - warn('The isJson property is deprecated, please use `resource.type === Resource.TYPE.JSON`.'); +if (loaders.Loader) +{ + const Resource = loaders.Resource; + const Loader = loaders.Loader; - return this.type === loaders.Loader.Resource.TYPE.JSON; + Object.defineProperties(Resource.prototype, { + isJson: { + get() + { + warn('The isJson property is deprecated, please use `resource.type === Resource.TYPE.JSON`.'); + + return this.type === Resource.TYPE.JSON; + }, }, - }, - isXml: { - get() - { - warn('The isXml property is deprecated, please use `resource.type === Resource.TYPE.XML`.'); + isXml: { + get() + { + warn('The isXml property is deprecated, please use `resource.type === Resource.TYPE.XML`.'); - return this.type === loaders.Loader.Resource.TYPE.XML; + return this.type === Resource.TYPE.XML; + }, }, - }, - isImage: { - get() - { - warn('The isImage property is deprecated, please use `resource.type === Resource.TYPE.IMAGE`.'); + isImage: { + get() + { + warn('The isImage property is deprecated, please use `resource.type === Resource.TYPE.IMAGE`.'); - return this.type === loaders.Loader.Resource.TYPE.IMAGE; + return this.type === Resource.TYPE.IMAGE; + }, }, - }, - isAudio: { - get() - { - warn('The isAudio property is deprecated, please use `resource.type === Resource.TYPE.AUDIO`.'); + isAudio: { + get() + { + warn('The isAudio property is deprecated, please use `resource.type === Resource.TYPE.AUDIO`.'); - return this.type === loaders.Loader.Resource.TYPE.AUDIO; + return this.type === Resource.TYPE.AUDIO; + }, }, - }, - isVideo: { - get() - { - warn('The isVideo property is deprecated, please use `resource.type === Resource.TYPE.VIDEO`.'); + isVideo: { + get() + { + warn('The isVideo property is deprecated, please use `resource.type === Resource.TYPE.VIDEO`.'); - return this.type === loaders.Loader.Resource.TYPE.VIDEO; + return this.type === Resource.TYPE.VIDEO; + }, }, - }, -}); + }); -Object.defineProperties(loaders.Loader.prototype, { - before: { - get() - { - warn('The before() method is deprecated, please use pre().'); + Object.defineProperties(Loader.prototype, { + before: { + get() + { + warn('The before() method is deprecated, please use pre().'); - return this.pre; + return this.pre; + }, }, - }, - after: { - get() - { - warn('The after() method is deprecated, please use use().'); + after: { + get() + { + warn('The after() method is deprecated, please use use().'); - return this.use; + return this.use; + }, }, - }, -}); + }); +} /** * @name PIXI.interaction.interactiveTarget#defaultCursor diff --git a/src/extract/canvas/CanvasExtract.js b/src/extract/canvas/CanvasExtract.js index 941ca05..3ac5aee 100644 --- a/src/extract/canvas/CanvasExtract.js +++ b/src/extract/canvas/CanvasExtract.js @@ -8,7 +8,7 @@ * An instance of this class is automatically created by default, and can be found at renderer.plugins.extract * * @class - * @memberof PIXI + * @memberof PIXI.extract */ export default class CanvasExtract { @@ -21,9 +21,9 @@ /** * Collection of methods for extracting data (image, pixels, etc.) from a display object or render texture * - * @member {PIXI.CanvasExtract} extract + * @member {PIXI.extract.CanvasExtract} extract * @memberof PIXI.CanvasRenderer# - * @see PIXI.CanvasExtract + * @see PIXI.extract.CanvasExtract */ renderer.extract = this; } diff --git a/src/extract/index.js b/src/extract/index.js index 0f2170d..e9478c1 100644 --- a/src/extract/index.js +++ b/src/extract/index.js @@ -1,2 +1,22 @@ +/** + * This namespace provides renderer-specific plugins for exporting content from a renderer. + * For instance, these plugins can be used for saving an Image, Canvas element or for exporting the raw image data (pixels). + * + * Do not instantiate these plugins directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. + * @example + * // Create a new app (will auto-add extract plugin to renderer) + * const app = new PIXI.Application(); + * + * // Draw a red circle + * const graphics = new PIXI.Graphics() + * .beginFill(0xFF0000) + * .drawCircle(0, 0, 50); + * + * // Render the graphics as an HTMLImageElement + * const image = app.renderer.plugins.extract.image(graphics); + * document.body.appendChild(image); + * @namespace PIXI.extract + */ export { default as webgl } from './webgl/WebGLExtract'; export { default as canvas } from './canvas/CanvasExtract'; diff --git a/src/extract/webgl/WebGLExtract.js b/src/extract/webgl/WebGLExtract.js index 60228bb..1c3dd30 100644 --- a/src/extract/webgl/WebGLExtract.js +++ b/src/extract/webgl/WebGLExtract.js @@ -9,7 +9,7 @@ * An instance of this class is automatically created by default, and can be found at renderer.plugins.extract * * @class - * @memberof PIXI + * @memberof PIXI.extract */ export default class WebGLExtract { @@ -22,9 +22,9 @@ /** * Collection of methods for extracting data (image, pixels, etc.) from a display object or render texture * - * @member {PIXI.WebGLExtract} extract + * @member {PIXI.extract.WebGLExtract} extract * @memberof PIXI.WebGLRenderer# - * @see PIXI.WebGLExtract + * @see PIXI.extract.WebGLExtract */ renderer.extract = this; } diff --git a/src/extras/AnimatedSprite.js b/src/extras/AnimatedSprite.js index 13b804f..5297778 100644 --- a/src/extras/AnimatedSprite.js +++ b/src/extras/AnimatedSprite.js @@ -137,7 +137,7 @@ this.playing = true; if (this._autoUpdate) { - core.ticker.shared.add(this.update, this); + core.ticker.shared.add(this.update, this, core.UPDATE_PRIORITY.HIGH); } } diff --git a/src/extras/TextureTransform.js b/src/extras/TextureTransform.js index 78e6e40..16536d2 100644 --- a/src/extras/TextureTransform.js +++ b/src/extras/TextureTransform.js @@ -65,8 +65,36 @@ } /** + * Multiplies uvs array to transform + * @param {Float32Array} uvs mesh uvs + * @param {Float32Array} [out=uvs] output + * @returns {Float32Array} output + */ + multiplyUvs(uvs, out) + { + if (out === undefined) + { + out = uvs; + } + + const mat = this.mapCoord; + + for (let i = 0; i < uvs.length; i += 2) + { + const x = uvs[i]; + const y = uvs[i + 1]; + + out[i] = (x * mat.a) + (y * mat.c) + mat.tx; + out[i + 1] = (x * mat.b) + (y * mat.d) + mat.ty; + } + + return out; + } + + /** * updates matrices if texture was changed * @param {boolean} forceUpdate if true, matrices will be updated any case + * @returns {boolean} whether or not it was updated */ update(forceUpdate) { @@ -74,13 +102,13 @@ if (!tex || !tex.valid) { - return; + return false; } if (!forceUpdate && this._lastTextureID === tex._updateID) { - return; + return false; } this._lastTextureID = tex._updateID; @@ -110,5 +138,7 @@ frame[3] = (tex._frame.y + tex._frame.height - margin + offset) / texBase.height; this.uClampOffset[0] = offset / texBase.realWidth; this.uClampOffset[1] = offset / texBase.realHeight; + + return true; } } diff --git a/src/extras/TilingSprite.js b/src/extras/TilingSprite.js index 1f6014f..a3f027f 100644 --- a/src/extras/TilingSprite.js +++ b/src/extras/TilingSprite.js @@ -180,9 +180,9 @@ const transform = this.worldTransform; const resolution = renderer.resolution; const baseTexture = texture.baseTexture; - const baseTextureResolution = texture.baseTexture.resolution; - const modX = (this.tilePosition.x / this.tileScale.x) % texture._frame.width; - const modY = (this.tilePosition.y / this.tileScale.y) % texture._frame.height; + const baseTextureResolution = baseTexture.resolution; + const modX = ((this.tilePosition.x / this.tileScale.x) % texture._frame.width) * baseTextureResolution; + const modY = ((this.tilePosition.y / this.tileScale.y) % texture._frame.height) * baseTextureResolution; // create a nice shiny pattern! // TODO this needs to be refreshed if texture changes.. diff --git a/src/extras/cacheAsBitmap.js b/src/extras/cacheAsBitmap.js index 5dd5640..aea06a8 100644 --- a/src/extras/cacheAsBitmap.js +++ b/src/extras/cacheAsBitmap.js @@ -227,7 +227,16 @@ this.transform._parentID = -1; // restore the transform of the cached sprite to avoid the nasty flicker.. - this.updateTransform(); + if (!this.parent) + { + this.parent = renderer._tempDisplayObjectParent; + this.updateTransform(); + this.parent = null; + } + else + { + this.updateTransform(); + } // map the hit test.. this.containsPoint = cachedSprite.containsPoint.bind(cachedSprite); @@ -314,7 +323,17 @@ cachedSprite._bounds = this._bounds; cachedSprite.alpha = cacheAlpha; - this.updateTransform(); + if (!this.parent) + { + this.parent = renderer._tempDisplayObjectParent; + this.updateTransform(); + this.parent = null; + } + else + { + this.updateTransform(); + } + this.updateTransform = this.displayObjectUpdateTransform; this._cacheData.sprite = cachedSprite; @@ -358,9 +377,12 @@ * Destroys the cached object. * * @private + * @param {object|boolean} [options] - Options parameter. A boolean will act as if all options + * have been set to that value. + * Used when destroying containers, see the Container.destroy method. */ -DisplayObject.prototype._cacheAsBitmapDestroy = function _cacheAsBitmapDestroy() +DisplayObject.prototype._cacheAsBitmapDestroy = function _cacheAsBitmapDestroy(options) { this.cacheAsBitmap = false; - this.destroy(); + this.destroy(options); }; diff --git a/src/extras/index.js b/src/extras/index.js index bbd8bbf..a34d7ec 100644 --- a/src/extras/index.js +++ b/src/extras/index.js @@ -1,7 +1,7 @@ /** + * Additional PIXI DisplayObjects for animation, tiling and bitmap text. * @namespace PIXI.extras */ -export { default as TextureTransform } from './TextureTransform'; export { default as AnimatedSprite } from './AnimatedSprite'; export { default as TilingSprite } from './TilingSprite'; export { default as TilingSpriteRenderer } from './webgl/TilingSpriteRenderer'; diff --git a/src/extras/webgl/TilingSpriteRenderer.js b/src/extras/webgl/TilingSpriteRenderer.js index aeec172..eee3b5c 100644 --- a/src/extras/webgl/TilingSpriteRenderer.js +++ b/src/extras/webgl/TilingSpriteRenderer.js @@ -11,7 +11,7 @@ * WebGL renderer plugin for tiling sprites * * @class - * @memberof PIXI + * @memberof PIXI.extras * @extends PIXI.ObjectRenderer */ export default class TilingSpriteRenderer extends core.ObjectRenderer @@ -118,7 +118,7 @@ tempMat.invert(); if (isSimple) { - tempMat.append(uv.mapCoord); + tempMat.prepend(uv.mapCoord); } else { diff --git a/src/filters/displacement/DisplacementFilter.js b/src/filters/displacement/DisplacementFilter.js index d5355c3..b5d030c 100644 --- a/src/filters/displacement/DisplacementFilter.js +++ b/src/filters/displacement/DisplacementFilter.js @@ -35,7 +35,7 @@ this.maskSprite = sprite; this.maskMatrix = maskMatrix; - this.uniforms.mapSampler = sprite.texture; + this.uniforms.mapSampler = sprite._texture; this.uniforms.filterMatrix = maskMatrix; this.uniforms.scale = { x: 1, y: 1 }; diff --git a/src/filters/index.js b/src/filters/index.js index f3e8862..7ceb50a 100644 --- a/src/filters/index.js +++ b/src/filters/index.js @@ -1,4 +1,21 @@ /** + * This namespace contains WebGL-only display filters that can be applied + * to DisplayObjects using the {@link PIXI.DisplayObject#filters filters} property. + * @example + * // Create a new application + * const app = new PIXI.Application(); + * + * // Draw a green rectangle + * const rect = new PIXI.Graphics() + * .beginFill(0x00ff00) + * .drawRect(40, 40, 200, 200); + * + * // Add a blur filter + * rect.filters = [new PIXI.filters.BlurFilter()]; + * + * // Display rectangle + * app.stage.addChild(rect); + * document.body.appendChild(app.view); * @namespace PIXI.filters */ export { default as FXAAFilter } from './fxaa/FXAAFilter'; diff --git a/src/index.js b/src/index.js index c553427..77da490 100644 --- a/src/index.js +++ b/src/index.js @@ -20,6 +20,14 @@ import { utils } from './core'; utils.mixins.performMixins(); +/** + * Alias for {@link PIXI.loaders.shared}. + * @name loader + * @memberof PIXI + * @type {PIXI.loader.Loader} + */ +const loader = loaders.shared || null; + export { accessibility, extract, @@ -30,18 +38,8 @@ mesh, particles, prepare, + loader, }; -/** - * A premade instance of the loader that can be used to load resources. - * - * @name loader - * @memberof PIXI - * @property {PIXI.loaders.Loader} - */ -const loader = loaders && loaders.Loader ? new loaders.Loader() : null; // check is there in case user excludes loader lib - -export { loader }; - // Always export pixi globally. global.PIXI = exports; // eslint-disable-line diff --git a/.travis.yml b/.travis.yml index 3df8b22..14de334 100644 --- a/.travis.yml +++ b/.travis.yml @@ -110,3 +110,43 @@ on: all_branches: true condition: $TRAVIS_TAG + # Deploy config for latest release + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: dist + upload-dir: "release" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: docs + upload-dir: "release/docs" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: coverage + upload-dir: "release/coverage" + on: + all_branches: true + condition: $TRAVIS_TAG \ No newline at end of file diff --git a/README.md b/README.md index d26fadd..3b85f85 100644 --- a/README.md +++ b/README.md @@ -36,22 +36,14 @@ - Wiki: Other misc tutorials and resources are [on the Wiki](https://github.com/pixijs/pixi.js/wiki/Resources). ### Community ### -- Forums: Check out the [forum] (http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow] (http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. +- Forums: Check out the [forum](http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow](http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. - Inspiration: Check out the [gallery](http://www.pixijs.com/gallery) to see some of the amazing things people have created! - Chat: You can join us on [Gitter](https://gitter.im/pixijs/pixi.js) To chat about Pixi. We also now have a Slack channel. If you would like to join it please Send me an email (mat@goodboydigital.com) and I will invite you in. ### Setup ### -It's easy to get started with Pixi.js! Simply grab the pre-built versions from here: - -Release Branch - Nice and stable Pixi.js -- Unminified: [http://pixijs.download/release/pixi.js] -- Minified: [http://pixijs.download/release/pixi.min.js] - -Develop Branch - The bleeding edge version of Pixi.js -- Unminified: [http://pixijs.download/dev/pixi.js] -- Minified: [http://pixijs.download/dev/pixi.min.js] +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. @@ -70,10 +62,10 @@ #### CDN Install (via cdnjs) ```html - + ``` -_Note: `4.2.2` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ +_Note: `4.4.3` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ ### Demos ### @@ -130,7 +122,7 @@ // and the root stage PIXI.Container. var app = new PIXI.Application(); -// The application will create a canvas element for you that you +// The application will create a canvas element for you that you // can then insert into the DOM. document.body.appendChild(app.view); @@ -150,7 +142,7 @@ // Add the bunny to the scene we are building. app.stage.addChild(bunny); - + // Listen for frame updates app.ticker.add(function() { // each frame we spin the bunny around a bit diff --git a/bower.json b/bower.json deleted file mode 100644 index 41b668c..0000000 --- a/bower.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "pixi.js", - "main": "dist/pixi.min.js", - "ignore": [ - "**/.*", - "docs", - "node_modules", - "bower_components", - "test", - "scripts", - "bower.json", - "inch.json" - ], - "dependencies": { - }, - "devDependencies": { - } -} diff --git a/package.json b/package.json index d35cbb2..308469f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.3.5", + "version": "4.4.4", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ @@ -40,11 +40,11 @@ "lib": "babel src --out-dir lib -s", "predocs": "rimraf docs/**", "docs": "jsdoc -c scripts/jsdoc.conf.json -R README.md", - "publish:patch": "npm version patch --no-git-tag-version && npm publish", - "publish:minor": "npm version minor --no-git-tag-version && npm publish", - "publish:major": "npm version major --no-git-tag-version && npm publish", + "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", - "postpublish": "node scripts/release.js" + "postpublish": "git push && git push --tags" }, "files": [ "dist/", @@ -72,7 +72,6 @@ "electron": "^1.4.15", "eslint": "^3.5.0", "floss": "^2.0.1", - "gh-pages": "^0.11.0", "jaguarjs-jsdoc": "^1.0.1", "js-md5": "^0.4.1", "jsdoc": "^3.4.2", diff --git a/scripts/jsdoc.conf.json b/scripts/jsdoc.conf.json index 553e61c..3f52180 100644 --- a/scripts/jsdoc.conf.json +++ b/scripts/jsdoc.conf.json @@ -43,7 +43,7 @@ }, "markdown" : { "parser" : "gfm", - "hardwrap" : true + "hardwrap" : false }, "opts": { "encoding" : "utf8", diff --git a/scripts/release.js b/scripts/release.js deleted file mode 100644 index 156620c..0000000 --- a/scripts/release.js +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env node -'use strict'; - -// Publish script to push releases of the bin files -// the normally are gitignored -const ghpages = require('gh-pages'); -const path = require('path'); -const packageInfo = require(path.join(__dirname, '..', 'package.json')); - -const options = { - src: [ - 'dist/**/*', - 'lib/**/*', - 'src/**/*', - 'scripts/*', - 'scripts/renders/*', - 'test/**/*', - '*.json', - '*.md', - 'LICENSE', - '.eslintrc', - '.editorconfig', - '.travis.yml', - '.babelrc', - ], - dotfiles: true, - branch: 'release', - message: packageInfo.version, - logger: console.log.bind(console), -}; - -ghpages.publish(process.cwd(), options, (err) => -{ - if (err) - { - console.log(err); - process.exit(1); - - return; - } - - process.exit(0); -}); diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index f16bcb1..220427a 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -21,7 +21,7 @@ const DIV_HOOK_ZINDEX = 2; /** - * The Accessibility manager reacreates the ability to tab and and have content read by screen + * The Accessibility manager recreates the ability to tab and and have content read by screen * readers. This is very important as it can possibly help people with disabilities access pixi * content. * diff --git a/src/accessibility/index.js b/src/accessibility/index.js index 568cfb1..0ae108f 100644 --- a/src/accessibility/index.js +++ b/src/accessibility/index.js @@ -1,4 +1,9 @@ /** + * This namespace contains a renderer plugin for interaction accessibility for end-users + * with physical impairments which require screen-renders, keyboard navigation, etc. + * + * Do not instantiate this plugin directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. * @namespace PIXI.accessibility */ export { default as accessibleTarget } from './accessibleTarget'; diff --git a/src/core/Application.js b/src/core/Application.js index 5340578..2fcc729 100644 --- a/src/core/Application.js +++ b/src/core/Application.js @@ -1,6 +1,8 @@ import { autoDetectRenderer } from './autoDetectRenderer'; import Container from './display/Container'; import { shared, Ticker } from './ticker'; +import settings from './settings'; +import { UPDATE_PRIORITY } from './const'; /** * Convenience class to create a new PIXI application. @@ -22,28 +24,52 @@ */ export default class Application { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experience unexplained flickering try setting this to true. - * @param {boolean} [useSharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedLoader=false] - `true` to use PIXI.loaders.shared, `false` to create new Loader. */ - constructor(width, height, options, noWebGL, useSharedTicker = false) + constructor(options, arg2, arg3, arg4, arg5) { + // Support for constructor(width, height, options, noWebGL, useSharedTicker) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + forceCanvas: !!arg4, + sharedTicker: !!arg5, + }, arg3); + } + + /** + * The default options, so we mixin functionality later. + * @member {object} + * @protected + */ + this._options = options = Object.assign({ + sharedTicker: false, + forceCanvas: false, + sharedLoader: false, + }, options); + /** * WebGL renderer if available, otherwise CanvasRenderer * @member {PIXI.WebGLRenderer|PIXI.CanvasRenderer} */ - this.renderer = autoDetectRenderer(width, height, options, noWebGL); + this.renderer = autoDetectRenderer(options); /** * The root display container that's rendered. @@ -63,7 +89,7 @@ * @member {PIXI.ticker.Ticker} * @default PIXI.ticker.shared */ - this.ticker = useSharedTicker ? shared : new Ticker(); + this.ticker = options.sharedTicker ? shared : new Ticker(); // Start the rendering this.start(); @@ -78,7 +104,7 @@ this._ticker = ticker; if (ticker) { - ticker.add(this.render, this); + ticker.add(this.render, this, UPDATE_PRIORITY.LOW); } } get ticker() // eslint-disable-line require-jsdoc @@ -136,13 +162,18 @@ */ destroy(removeView) { - this.stop(); + const oldTicker = this._ticker; + this.ticker = null; + oldTicker.destroy(); + this.stage.destroy(); this.stage = null; this.renderer.destroy(removeView); this.renderer = null; + + this._options = null; } } diff --git a/src/core/autoDetectRenderer.js b/src/core/autoDetectRenderer.js index 8960425..9ec0364 100644 --- a/src/core/autoDetectRenderer.js +++ b/src/core/autoDetectRenderer.js @@ -2,6 +2,7 @@ import CanvasRenderer from './renderers/canvas/CanvasRenderer'; import WebGLRenderer from './renderers/webgl/WebGLRenderer'; +// eslint-disable-next-line valid-jsdoc /** * This helper function will automatically detect which renderer you should be using. * WebGL is the preferred renderer as it is a lot faster. If webGL is not supported by @@ -9,24 +10,32 @@ * * @memberof PIXI * @function autoDetectRenderer - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @return {PIXI.WebGLRenderer|PIXI.CanvasRenderer} Returns WebGL renderer if available, otherwise CanvasRenderer */ -export function autoDetectRenderer(width = 800, height = 600, options, noWebGL) +export function autoDetectRenderer(options, arg1, arg2, arg3) { - if (!noWebGL && utils.isWebGLSupported()) + // Backward-compatible support for noWebGL option + let forceCanvas = options && options.forceCanvas; + + if (arg3 !== undefined) { - return new WebGLRenderer(width, height, options); + forceCanvas = arg3; } - return new CanvasRenderer(width, height, options); + if (!forceCanvas && utils.isWebGLSupported()) + { + return new WebGLRenderer(options, arg1, arg2); + } + + return new CanvasRenderer(options, arg1, arg2); } diff --git a/src/core/const.js b/src/core/const.js index b9e99ae..2c68dee 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -341,3 +341,27 @@ LINEAR_VERTICAL: 0, LINEAR_HORIZONTAL: 1, }; + +/** + * Represents the update priorities used by internal PIXI classes when registered with + * the {@link PIXI.ticker.Ticker} object. Higher priority items are updated first and lower + * priority items, such as render, should go later. + * + * @static + * @constant + * @name UPDATE_PRIORITY + * @memberof PIXI + * @type {object} + * @property {number} INTERACTION=50 Highest priority, used for {@link PIXI.interaction.InteractionManager} + * @property {number} HIGH=25 High priority updating, {@link PIXI.VideoBaseTexture} and {@link PIXI.extras.AnimatedSprite} + * @property {number} NORMAL=0 Default priority for ticker events, see {@link PIXI.ticker.Ticker#add}. + * @property {number} LOW=-25 Low priority used for {@link PIXI.Application} rendering. + * @property {number} UTILITY=-50 Lowest priority used for {@link PIXI.prepare.BasePrepare} utility. + */ +export const UPDATE_PRIORITY = { + INTERACTION: 50, + HIGH: 25, + NORMAL: 0, + LOW: -25, + UTILITY: -50, +}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 4e6c77d..decb42a 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -13,7 +13,6 @@ * * @class * @extends EventEmitter - * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ export default class DisplayObject extends EventEmitter @@ -113,6 +112,29 @@ * @private */ this._mask = null; + + /** + * If the object has been destroyed via destroy(). If true, it should not be used. + * + * @member {boolean} + * @private + * @readonly + */ + this._destroyed = false; + + /** + * Fired when this DisplayObject is added to a Container. + * + * @event PIXI.DisplayObject#added + * @param {PIXI.Container} container - The container added to. + */ + + /** + * Fired when this DisplayObject is removed from a Container. + * + * @event PIXI.DisplayObject#removed + * @param {PIXI.Container} container - The container removed from. + */ } /** @@ -403,6 +425,8 @@ this.interactive = false; this.interactiveChildren = false; + + this._destroyed = true; } /** diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index aafdfe2..f787c4a 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -792,8 +792,8 @@ sprite.worldAlpha = this.worldAlpha * sprite.alpha; sprite.blendMode = this.blendMode; - sprite.texture._frame.width = rect.width; - sprite.texture._frame.height = rect.height; + sprite._texture._frame.width = rect.width; + sprite._texture._frame.height = rect.height; sprite.transform.worldTransform = this.transform.worldTransform; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index a24c775..b9f192d 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -19,11 +19,12 @@ */ export default class SystemRenderer extends EventEmitter { + // eslint-disable-next-line valid-jsdoc /** * @param {string} system - The name of the system this renderer is for. - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -37,27 +38,31 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(system, screenWidth, screenHeight, options) + constructor(system, options, arg2, arg3) { super(); sayHello(system); - // prepare options - if (options) + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') { - for (const i in settings.RENDER_OPTIONS) - { - if (typeof options[i] === 'undefined') - { - options[i] = settings.RENDER_OPTIONS[i]; - } - } + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); } - else - { - options = settings.RENDER_OPTIONS; - } + + // Add the default render options + options = Object.assign({}, settings.RENDER_OPTIONS, options); + + /** + * The supplied constructor options. + * + * @member {Object} + * @readOnly + */ + this.options = options; /** * The type of the renderer. @@ -75,7 +80,7 @@ * * @member {PIXI.Rectangle} */ - this.screen = new Rectangle(0, 0, screenWidth || 800, screenHeight || 600); + this.screen = new Rectangle(0, 0, options.width, options.height); /** * The canvas element that everything is drawn to @@ -279,6 +284,8 @@ this.blendModes = null; + this.options = null; + this.preserveDrawingBuffer = false; this.clearBeforeRender = false; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index e84e49c..691caf0 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -17,10 +17,11 @@ */ export default class CanvasRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -34,9 +35,9 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('Canvas', screenWidth, screenHeight, options); + super('Canvas', options, arg2, arg3); this.type = RENDERER_TYPE.CANVAS; @@ -96,7 +97,19 @@ this.context = null; this.renderingToScreen = false; - this.resize(screenWidth, screenHeight); + this.resize(this.options.width, this.options.height); + + /** + * Fired after rendering finishes. + * + * @event PIXI.CanvasRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.CanvasRenderer#prerender + */ } /** @@ -299,4 +312,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.CanvasRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.CanvasExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.CanvasPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.CanvasRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index e1cf7cd..f6fe123 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -31,11 +31,12 @@ */ export default class WebGLRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** * - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -55,9 +56,9 @@ * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('WebGL', screenWidth, screenHeight, options); + super('WebGL', options, arg2, arg3); /** * The type of this renderer as a standardised const @@ -89,12 +90,6 @@ }; - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ this._backgroundColorRgba[3] = this.transparent ? 0 : 1; this.globalUniforms = new UniformGroup({ @@ -117,6 +112,13 @@ this.initPlugins(); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ if(options.context) { this.context.initFromContext(options.context); @@ -169,6 +171,25 @@ } return this; + + /** + * Fired after rendering finishes. + * + * @event PIXI.WebGLRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.WebGLRenderer#prerender + */ + + /** + * Fired when the WebGL context is set. + * + * @event PIXI.WebGLRenderer#context + * @param {WebGLRenderingContext} gl - WebGL context. + */ } /** @@ -295,4 +316,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.WebGLRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.WebGLRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(WebGLRenderer); diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index 4eb6128..fa3d883 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -47,6 +47,14 @@ * @member {boolean} */ this.enabled = true; + + /** + * If enabled, pixi will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; } /** diff --git a/src/core/renderers/webgl/systems/ContextSystem.js b/src/core/renderers/webgl/systems/ContextSystem.js index ff958c7..8df85ea 100644 --- a/src/core/renderers/webgl/systems/ContextSystem.js +++ b/src/core/renderers/webgl/systems/ContextSystem.js @@ -75,7 +75,8 @@ */ createContext(canvas, options) { - var gl = canvas.getContext('webgl', options) || + var gl = //canvas.getContext('webgl2', options) || + canvas.getContext('webgl', options) || canvas.getContext('experimental-webgl', options); if (!gl) diff --git a/src/core/renderers/webgl/systems/FilterSystem.js b/src/core/renderers/webgl/systems/FilterSystem.js index f47335d..37e28d2 100644 --- a/src/core/renderers/webgl/systems/FilterSystem.js +++ b/src/core/renderers/webgl/systems/FilterSystem.js @@ -111,7 +111,7 @@ // TODO we should fit the rect around the transform.. } - else + else if (filters[0].autoFit) { sourceFrame.fit(filterData.stack[0].destinationFrame); } diff --git a/src/core/renderers/webgl/systems/shader/shader/mapType.js b/src/core/renderers/webgl/systems/shader/shader/mapType.js index 581dace..917538a 100644 --- a/src/core/renderers/webgl/systems/shader/shader/mapType.js +++ b/src/core/renderers/webgl/systems/shader/shader/mapType.js @@ -15,7 +15,7 @@ } } - return GL_TABLE[type]; + return GL_TABLE[type]; }; var GL_TABLE = null; diff --git a/src/core/settings.js b/src/core/settings.js index 18c5877..38ef45c 100644 --- a/src/core/settings.js +++ b/src/core/settings.js @@ -2,6 +2,15 @@ import canUploadSameBuffer from './utils/canUploadSameBuffer'; /** + * User's customizable globals for overriding the default PIXI settings, such + * as a renderer's default resolution, framerate, float percision, etc. + * @example + * // Use the native window resolution as the default resolution + * // will support high-density displays when rendering + * PIXI.settings.RESOLUTION = window.devicePixelRatio. + * + * // Disable interpolation when scaling, will make texture be pixelated + * PIXI.settings.SCALE_MODE = PIXI.SCALE_MODES.NEAREST; * @namespace PIXI.settings */ export default { @@ -101,6 +110,9 @@ * @property {boolean} clearBeforeRender=true * @property {boolean} preserveDrawingBuffer=false * @property {boolean} roundPixels=false + * @property {number} width=800 + * @property {number} height=600 + * @property {boolean} legacy=false */ RENDER_OPTIONS: { view: null, @@ -112,6 +124,9 @@ clearBeforeRender: true, preserveDrawingBuffer: false, roundPixels: false, + width: 800, + height: 600, + legacy: false, }, /** diff --git a/src/core/shader/Program.js b/src/core/shader/Program.js index c0b538b..07b4398 100644 --- a/src/core/shader/Program.js +++ b/src/core/shader/Program.js @@ -1,6 +1,6 @@ import extractUniformsFromSrc from './extractUniformsFromSrc'; import generateUniformsSync from './generateUniformsSync'; -import glCore from 'pixi-gl-core'; +import shaderUtils from '../renderers/webgl/systems/shader/shader'; import { ProgramCache } from '../utils'; import getTestContext from '../utils/getTestContext'; @@ -64,14 +64,17 @@ } else { - vertexSrc = glCore.shader.setPrecision(vertexSrc, 'mediump'); - fragmentSrc = glCore.shader.setPrecision(fragmentSrc, 'mediump'); + vertexSrc = shaderUtils.setPrecision(vertexSrc, 'mediump'); + fragmentSrc = shaderUtils.setPrecision(fragmentSrc, 'mediump'); - const program = glCore.shader.compileProgram(gl, vertexSrc, fragmentSrc); + const program = shaderUtils.compileProgram(gl, vertexSrc, fragmentSrc); + console.log("<<<<<<>>><<"); this.attributeData = this.getAttributeData(program, gl); this.uniformData = this.getUniformData(program, gl); + console.log(this.uniformData); + //gl.deleteProgram(program); } } @@ -95,13 +98,13 @@ for (let i = 0; i < totalAttributes; i++) { const attribData = gl.getActiveAttrib(program, i); - const type = glCore.shader.mapType(gl, attribData.type); + const type = shaderUtils.mapType(gl, attribData.type); /*eslint-disable */ const data = { type: type, name: attribData.name, - size: glCore.shader.mapSize(type), + size: shaderUtils.mapSize(type), location: 0, }; /*eslint-enable */ @@ -137,13 +140,14 @@ // TODO expose this as a prop? // const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); - const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); + //const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); for (let i = 0; i < totalUniforms; i++) { const uniformData = gl.getActiveUniform(program, i); const name = uniformData.name.replace(/\[.*?\]/, ''); - const type = glCore.shader.mapType(gl, uniformData.type); + const type = shaderUtils.mapType(gl, uniformData.type); + console.log('mapping ' + uniformData.type + ' to ' + type); // if (!name.match(maskRegex)) { @@ -151,7 +155,7 @@ uniforms[name] = { type: type, size: uniformData.size, - value: glCore.shader.defaultValue(type, uniformData.size), + value: shaderUtils.defaultValue(type, uniformData.size), }; /*eslint-enable */ } diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index e387e99..b57d095 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -147,12 +147,12 @@ // so if _width is 0 then width was not set.. if (this._width) { - this.scale.x = sign(this.scale.x) * this._width / this.texture.orig.width; + this.scale.x = sign(this.scale.x) * this._width / this._texture.orig.width; } if (this._height) { - this.scale.y = sign(this.scale.y) * this._height / this.texture.orig.height; + this.scale.y = sign(this.scale.y) * this._height / this._texture.orig.height; } } diff --git a/src/core/sprites/canvas/CanvasTinter.js b/src/core/sprites/canvas/CanvasTinter.js index 27e5e0c..fd9de5d 100644 --- a/src/core/sprites/canvas/CanvasTinter.js +++ b/src/core/sprites/canvas/CanvasTinter.js @@ -18,7 +18,7 @@ */ getTintedTexture: (sprite, color) => { - const texture = sprite.texture; + const texture = sprite._texture; color = CanvasTinter.roundColor(color); diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 5a4edda..8b9b797 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -344,6 +344,7 @@ const texture = this._texture; const style = this._style; + const padding = style.trim ? 0 : style.padding; texture.baseTexture.valid = true; texture.baseTexture.resolution = this.resolution; @@ -353,11 +354,11 @@ texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - texture.trim.x = -style.padding; - texture.trim.y = -style.padding; + texture.trim.x = -padding; + texture.trim.y = -padding; - texture.orig.width = texture._frame.width - (style.padding * 2); - texture.orig.height = texture._frame.height - (style.padding * 2); + texture.orig.width = texture._frame.width - (padding * 2); + texture.orig.height = texture._frame.height - (padding * 2); // call sprite onTextureUpdate to update scale if _width or _height were set this._onTextureUpdate(); diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index 853da3d..8a9c34b 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -114,6 +114,38 @@ this.cacheId = null; this.validate(); + + /** + * Fired when a not-immediately-available source finishes loading. + * + * @protected + * @event PIXI.BaseTexture#loaded + * @param {PIXI.BaseTexture} baseTexture - Resource loaded. + */ + + /** + * Fired when a not-immediately-available source fails to load. + * + * @protected + * @event PIXI.BaseTexture#error + * @param {PIXI.BaseTexture} baseTexture - Resource errored. + */ + + /** + * Fired when BaseTexture is updated. + * + * @protected + * @event PIXI.BaseTexture#update + * @param {PIXI.BaseTexture} baseTexture - Instance of texture being updated. + */ + + /** + * Fired when BaseTexture is destroyed. + * + * @protected + * @event PIXI.BaseTexture#dispose + * @param {PIXI.BaseTexture} baseTexture - Instance of texture being destroyed. + */ } setResource(resource) diff --git a/src/core/textures/Spritesheet.js b/src/core/textures/Spritesheet.js index d469c62..9b24f27 100644 --- a/src/core/textures/Spritesheet.js +++ b/src/core/textures/Spritesheet.js @@ -208,6 +208,7 @@ // lets also add the frame to pixi's global cache for fromFrame and fromImage functions TextureCache[i] = this.textures[i]; + this.textures[i].textureCacheId = i; } frameIndex++; diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 0a2c379..2213087 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -147,9 +147,9 @@ /** * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. * - * @event update - * @memberof PIXI.Texture# + * @event PIXI.Texture#update * @protected + * @param {PIXI.Texture} texture - Instance of texture being updated. */ this._updateID = 0; @@ -159,6 +159,15 @@ * @type {Object} */ this.transform = null; + + /** + * The id under which this Texture has been added to the texture cache. This is + * automatically set in certain cases, but may not always be accurate, particularly if + * the texture is in the cache under multiple ids. + * + * @member {string} + */ + this.textureCacheId = null; } /** @@ -225,11 +234,17 @@ this._uvs = null; this.trim = null; this.orig = null; + this.textureCacheId = null; this.valid = false; - this.off('dispose', this.dispose, this); - this.off('update', this.update, this); + for (const prop in TextureCache) + { + if (TextureCache[prop] === this) + { + delete TextureCache[prop]; + } + } } /** @@ -315,14 +330,15 @@ */ static fromLoader(source, imageUrl, name) { - // console.log('added from loader...') + // console.log('added from loader...') const resource = new ImageResource(source);//.from(imageUrl, crossorigin);// document.createElement('img'); - // console.log('base resource ' + resource.width); + // console.log('base resource ' + resource.width); const baseTexture = new BaseTexture(resource, settings.SCALE_MODE, getResolutionOfUrl(imageUrl)); + /// console.log('base width ' + baseTexture.width); const texture = new Texture(baseTexture); @@ -335,6 +351,7 @@ // lets also add the frame to pixi's global cache for fromFrame and fromImage fucntions BaseTextureCache[name] = baseTexture; TextureCache[name] = texture; + texture.textureCacheId = name; // also add references by url if they are different. if (name !== imageUrl) @@ -355,6 +372,10 @@ */ static addTextureToCache(texture, id) { + if (!texture.textureCacheId) + { + texture.textureCacheId = id; + } TextureCache[id] = texture; } @@ -495,6 +516,7 @@ */ Texture.EMPTY = new Texture(new BaseTexture()); removeAllHandlers(Texture.EMPTY); +removeAllHandlers(Texture.EMPTY.baseTexture); /** * A white texture of 10x10 size, used for graphics and other things @@ -505,3 +527,4 @@ */ Texture.WHITE = createWhiteTexture(); removeAllHandlers(Texture.WHITE); +removeAllHandlers(Texture.WHITE.baseTexture); diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index 59eb9fd..a9b9f51 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -1,6 +1,7 @@ import BaseTexture from './BaseTexture'; import { uid, BaseTextureCache } from '../utils'; -import * as ticker from '../ticker'; +import { shared } from '../ticker'; +import { UPDATE_PRIORITY } from '../const'; /** * A texture of a [playing] Video. @@ -125,7 +126,7 @@ if (!this._isAutoUpdating && this.autoUpdate) { - ticker.shared.add(this.update, this); + shared.add(this.update, this, UPDATE_PRIORITY.HIGH); this._isAutoUpdating = true; } } @@ -139,7 +140,7 @@ { if (this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); this._isAutoUpdating = false; } } @@ -187,7 +188,7 @@ { if (this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); } if (this.source && this.source._pixiId) @@ -281,12 +282,12 @@ if (!this._autoUpdate && this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); this._isAutoUpdating = false; } else if (this._autoUpdate && !this._isAutoUpdating) { - ticker.shared.add(this.update, this); + shared.add(this.update, this, UPDATE_PRIORITY.HIGH); this._isAutoUpdating = true; } } diff --git a/src/core/ticker/Ticker.js b/src/core/ticker/Ticker.js index 17a1517..40df138 100644 --- a/src/core/ticker/Ticker.js +++ b/src/core/ticker/Ticker.js @@ -1,12 +1,10 @@ import settings from '../settings'; -import EventEmitter from 'eventemitter3'; - -// Internal event used by composed emitter -const TICK = 'tick'; +import { UPDATE_PRIORITY } from '../const'; +import TickerListener from './TickerListener'; /** * A Ticker class that runs an update loop that other objects listen to. - * This class is composed around an EventEmitter object to add listeners + * This class is composed around listeners * meant for execution on the next requested animation frame. * Animation frames are requested only when necessary, * e.g. When the ticker is started and the emitter has listeners. @@ -22,10 +20,11 @@ constructor() { /** - * Internal emitter used to fire 'tick' event + * The first listener. All new listeners added are chained on this. * @private + * @type {TickerListener} */ - this._emitter = new EventEmitter(); + this._head = new TickerListener(null, null, Infinity); /** * Internal current frame request ID @@ -131,7 +130,7 @@ // Invoke listeners now this.update(time); // Listener side effects may have modified ticker state. - if (this.started && this._requestId === null && this._emitter.listeners(TICK, true)) + if (this.started && this._requestId === null && this._head.next) { this._requestId = requestAnimationFrame(this._tick); } @@ -148,7 +147,7 @@ */ _requestIfNeeded() { - if (this._requestId === null && this._emitter.listeners(TICK, true)) + if (this._requestId === null && this._head.next) { // ensure callbacks get correct delta this.lastTime = performance.now(); @@ -193,35 +192,72 @@ } /** - * Calls {@link module:eventemitter3.EventEmitter#on} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. + * Register a handler for tick events. Calls continuously unless + * it is removed or the ticker is stopped. * * @param {Function} fn - The listener function to be added for updates * @param {Function} [context] - The listener context + * @param {number} [priority=PIXI.UPDATE_PRIORITY.NORMAL] - The priority for emitting * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - add(fn, context) + add(fn, context, priority = UPDATE_PRIORITY.NORMAL) { - this._emitter.on(TICK, fn, context); - - this._startIfPossible(); - - return this; + return this._addListener(new TickerListener(fn, context, priority)); } /** - * Calls {@link module:eventemitter3.EventEmitter#once} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. + * Add a handler for the tick event which is only execute once. * * @param {Function} fn - The listener function to be added for one update * @param {Function} [context] - The listener context + * @param {number} [priority=PIXI.UPDATE_PRIORITY.NORMAL] - The priority for emitting * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - addOnce(fn, context) + addOnce(fn, context, priority = UPDATE_PRIORITY.NORMAL) { - this._emitter.once(TICK, fn, context); + return this._addListener(new TickerListener(fn, context, priority, true)); + } + + /** + * Internally adds the event handler so that it can be sorted by priority. + * Priority allows certain handler (user, AnimatedSprite, Interaction) to be run + * before the rendering. + * + * @private + * @param {TickerListener} listener - Current listener being added. + * @returns {PIXI.ticker.Ticker} This instance of a ticker + */ + _addListener(listener) + { + // For attaching to head + let current = this._head.next; + let previous = this._head; + + // Add the first item + if (!current) + { + listener.connect(previous); + } + else + { + // Go from highest to lowest priority + while (current) + { + if (listener.priority >= current.priority) + { + listener.connect(previous); + break; + } + previous = current; + current = current.next; + } + + // Not yet connected + if (!listener.previous) + { + listener.connect(previous); + } + } this._startIfPossible(); @@ -229,19 +265,33 @@ } /** - * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. - * It checks if the emitter has listeners for 'tick' event. - * If it does, then it cancels the animation frame. + * Removes any handlers matching the function and context parameters. + * If no handlers are left after removing, then it cancels the animation frame. * - * @param {Function} [fn] - The listener function to be removed + * @param {Function} fn - The listener function to be removed * @param {Function} [context] - The listener context to be removed * @returns {PIXI.ticker.Ticker} This instance of a ticker */ remove(fn, context) { - this._emitter.off(TICK, fn, context); + let listener = this._head.next; - if (!this._emitter.listeners(TICK, true)) + while (listener) + { + // We found a match, lets remove it + // no break to delete all possible matches + // incase a listener was added 2+ times + if (listener.match(fn, context)) + { + listener = listener.destroy(); + } + else + { + listener = listener.next; + } + } + + if (!this._head.next) { this._cancelIfNeeded(); } @@ -276,6 +326,25 @@ } /** + * Destroy the ticker and don't use after this. Calling + * this method removes all references to internal events. + */ + destroy() + { + this.stop(); + + let listener = this._head.next; + + while (listener) + { + listener = listener.destroy(true); + } + + this._head.destroy(); + this._head = null; + } + + /** * Triggers an update. An update entails setting the * current {@link PIXI.ticker.Ticker#elapsedMS}, * the current {@link PIXI.ticker.Ticker#deltaTime}, @@ -320,8 +389,22 @@ this.deltaTime = elapsedMS * settings.TARGET_FPMS * this.speed; + // Cache a local reference, in-case ticker is destroyed + // during the emit, we can still check for head.next + const head = this._head; + // Invoke listeners added to internal emitter - this._emitter.emit(TICK, this.deltaTime); + let listener = head.next; + + while (listener) + { + listener = listener.emit(this.deltaTime); + } + + if (!head.next) + { + this._cancelIfNeeded(); + } } else { diff --git a/src/core/ticker/TickerListener.js b/src/core/ticker/TickerListener.js new file mode 100644 index 0000000..2bedb34 --- /dev/null +++ b/src/core/ticker/TickerListener.js @@ -0,0 +1,158 @@ +/** + * Internal class for handling the priority sorting of ticker handlers. + * + * @private + * @class + * @memberof PIXI.ticker + */ +export default class TickerListener +{ + /** + * Constructor + * + * @param {Function} fn - The listener function to be added for one update + * @param {Function} [context=null] - The listener context + * @param {number} [priority=0] - The priority for emitting + * @param {boolean} [once=false] - If the handler should fire once + */ + constructor(fn, context = null, priority = 0, once = false) + { + /** + * The handler function to execute. + * @member {Function} + */ + this.fn = fn; + + /** + * The calling to execute. + * @member {Function} + */ + this.context = context; + + /** + * The current priority. + * @member {number} + */ + this.priority = priority; + + /** + * If this should only execute once. + * @member {boolean} + */ + this.once = once; + + /** + * The next item in chain. + * @member {TickerListener} + */ + this.next = null; + + /** + * The previous item in chain. + * @member {TickerListener} + */ + this.previous = null; + + /** + * `true` if this listener has been destroyed already. + * @member {boolean} + * @private + */ + this._destroyed = false; + } + + /** + * Simple compare function to figure out if a function and context match. + * + * @param {Function} fn - The listener function to be added for one update + * @param {Function} context - The listener context + * @return {boolean} `true` if the listener match the arguments + */ + match(fn, context) + { + context = context || null; + + return this.fn === fn && this.context === context; + } + + /** + * Emit by calling the current function. + * @param {number} deltaTime - time since the last emit. + * @return {TickerListener} Next ticker + */ + emit(deltaTime) + { + if (this.context) + { + this.fn.call(this.context, deltaTime); + } + else + { + this.fn(deltaTime); + } + + if (this.once) + { + this.destroy(); + } + + const redirect = this.next; + + // Soft-destroying should remove + // the next reference + if (this._destroyed) + { + this.next = null; + } + + return redirect; + } + + /** + * Connect to the list. + * @param {TickerListener} previous - Input node, previous listener + */ + connect(previous) + { + this.previous = previous; + if (previous.next) + { + previous.next.previous = this; + } + this.next = previous.next; + previous.next = this; + } + + /** + * Destroy and don't use after this. + * @param {boolean} [hard = false] `true` to remove the `next` reference, this + * is considered a hard destroy. Soft destroy maintains the next reference. + * @return {TickerListener} The listener to redirect while emitting or removing. + */ + destroy(hard = false) + { + this._destroyed = true; + this.fn = null; + this.context = null; + + // Disconnect, hook up next and previous + if (this.previous) + { + this.previous.next = this.next; + } + + if (this.next) + { + this.next.previous = this.previous; + } + + // Redirect to the next item + const redirect = this.previous; + + // Remove references + this.next = hard ? null : redirect; + this.previous = null; + + return redirect; + } +} diff --git a/src/core/ticker/index.js b/src/core/ticker/index.js index 4b3017c..c3020d4 100644 --- a/src/core/ticker/index.js +++ b/src/core/ticker/index.js @@ -45,8 +45,25 @@ const shared = new Ticker(); shared.autoStart = true; +shared.destroy = () => +{ + // protect destroying shared ticker + // this is used by other internal systems + // like AnimatedSprite and InteractionManager +}; /** + * This namespace contains an API for interacting with PIXI's internal global update loop. + * + * This ticker is used for rendering, {@link PIXI.extras.AnimatedSprite AnimatedSprite}, + * {@link PIXI.interaction.InteractionManager InteractionManager} and many other time-based PIXI systems. + * @example + * const ticker = new PIXI.ticker.Ticker(); + * ticker.stop(); + * ticker.add((deltaTime) => { + * // do something every frame + * }); + * ticker.start(); * @namespace PIXI.ticker */ export { shared, Ticker }; diff --git a/src/core/utils/index.js b/src/core/utils/index.js index be44750..7441f7b 100644 --- a/src/core/utils/index.js +++ b/src/core/utils/index.js @@ -9,6 +9,21 @@ let saidHello = false; /** + * Generalized convenience utilities for PIXI. + * @example + * // Extend PIXI's internal Event Emitter. + * class MyEmitter extends PIXI.utils.EventEmitter { + * constructor() { + * super(); + * console.log("Emitter created!"); + * } + * } + * + * // Get info on current device + * console.log(PIXI.utils.isMobile); + * + * // Convert hex color to string + * console.log(PIXI.utils.hex2string(0xff00ff)); // returns: "#ff00ff" * @namespace PIXI.utils */ export { diff --git a/src/deprecation.js b/src/deprecation.js index b9c73b3..33daeec 100644 --- a/src/deprecation.js +++ b/src/deprecation.js @@ -918,6 +918,35 @@ }); /** + * @method + * @name PIXI.prepare.BasePrepare#register + * @see PIXI.prepare.BasePrepare#registerFindHook + * @deprecated since version 4.4.2 + * @param {Function} [addHook] - Function call that takes two parameters: `item:*, queue:Array` + * function must return `true` if it was able to add item to the queue. + * @param {Function} [uploadHook] - Function call that takes two parameters: `prepare:CanvasPrepare, item:*` and + * function must return `true` if it was able to handle upload of item. + * @return {PIXI.BasePrepare} Instance of plugin for chaining. + */ +prepare.BasePrepare.prototype.register = function register(addHook, uploadHook) +{ + warn('renderer.plugins.prepare.register is now deprecated, ' + + 'please use renderer.plugins.prepare.registerFindHook & renderer.plugins.prepare.registerUploadHook'); + + if (addHook) + { + this.registerFindHook(addHook); + } + + if (uploadHook) + { + this.registerUploadHook(uploadHook); + } + + return this; +}; + +/** * The number of graphics or textures to upload to the GPU. * * @name PIXI.prepare.canvas.UPLOADS_PER_FRAME @@ -969,67 +998,73 @@ }, }); -Object.defineProperties(loaders.Resource.prototype, { - isJson: { - get() - { - warn('The isJson property is deprecated, please use `resource.type === Resource.TYPE.JSON`.'); +if (loaders.Loader) +{ + const Resource = loaders.Resource; + const Loader = loaders.Loader; - return this.type === loaders.Loader.Resource.TYPE.JSON; + Object.defineProperties(Resource.prototype, { + isJson: { + get() + { + warn('The isJson property is deprecated, please use `resource.type === Resource.TYPE.JSON`.'); + + return this.type === Resource.TYPE.JSON; + }, }, - }, - isXml: { - get() - { - warn('The isXml property is deprecated, please use `resource.type === Resource.TYPE.XML`.'); + isXml: { + get() + { + warn('The isXml property is deprecated, please use `resource.type === Resource.TYPE.XML`.'); - return this.type === loaders.Loader.Resource.TYPE.XML; + return this.type === Resource.TYPE.XML; + }, }, - }, - isImage: { - get() - { - warn('The isImage property is deprecated, please use `resource.type === Resource.TYPE.IMAGE`.'); + isImage: { + get() + { + warn('The isImage property is deprecated, please use `resource.type === Resource.TYPE.IMAGE`.'); - return this.type === loaders.Loader.Resource.TYPE.IMAGE; + return this.type === Resource.TYPE.IMAGE; + }, }, - }, - isAudio: { - get() - { - warn('The isAudio property is deprecated, please use `resource.type === Resource.TYPE.AUDIO`.'); + isAudio: { + get() + { + warn('The isAudio property is deprecated, please use `resource.type === Resource.TYPE.AUDIO`.'); - return this.type === loaders.Loader.Resource.TYPE.AUDIO; + return this.type === Resource.TYPE.AUDIO; + }, }, - }, - isVideo: { - get() - { - warn('The isVideo property is deprecated, please use `resource.type === Resource.TYPE.VIDEO`.'); + isVideo: { + get() + { + warn('The isVideo property is deprecated, please use `resource.type === Resource.TYPE.VIDEO`.'); - return this.type === loaders.Loader.Resource.TYPE.VIDEO; + return this.type === Resource.TYPE.VIDEO; + }, }, - }, -}); + }); -Object.defineProperties(loaders.Loader.prototype, { - before: { - get() - { - warn('The before() method is deprecated, please use pre().'); + Object.defineProperties(Loader.prototype, { + before: { + get() + { + warn('The before() method is deprecated, please use pre().'); - return this.pre; + return this.pre; + }, }, - }, - after: { - get() - { - warn('The after() method is deprecated, please use use().'); + after: { + get() + { + warn('The after() method is deprecated, please use use().'); - return this.use; + return this.use; + }, }, - }, -}); + }); +} /** * @name PIXI.interaction.interactiveTarget#defaultCursor diff --git a/src/extract/canvas/CanvasExtract.js b/src/extract/canvas/CanvasExtract.js index 941ca05..3ac5aee 100644 --- a/src/extract/canvas/CanvasExtract.js +++ b/src/extract/canvas/CanvasExtract.js @@ -8,7 +8,7 @@ * An instance of this class is automatically created by default, and can be found at renderer.plugins.extract * * @class - * @memberof PIXI + * @memberof PIXI.extract */ export default class CanvasExtract { @@ -21,9 +21,9 @@ /** * Collection of methods for extracting data (image, pixels, etc.) from a display object or render texture * - * @member {PIXI.CanvasExtract} extract + * @member {PIXI.extract.CanvasExtract} extract * @memberof PIXI.CanvasRenderer# - * @see PIXI.CanvasExtract + * @see PIXI.extract.CanvasExtract */ renderer.extract = this; } diff --git a/src/extract/index.js b/src/extract/index.js index 0f2170d..e9478c1 100644 --- a/src/extract/index.js +++ b/src/extract/index.js @@ -1,2 +1,22 @@ +/** + * This namespace provides renderer-specific plugins for exporting content from a renderer. + * For instance, these plugins can be used for saving an Image, Canvas element or for exporting the raw image data (pixels). + * + * Do not instantiate these plugins directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. + * @example + * // Create a new app (will auto-add extract plugin to renderer) + * const app = new PIXI.Application(); + * + * // Draw a red circle + * const graphics = new PIXI.Graphics() + * .beginFill(0xFF0000) + * .drawCircle(0, 0, 50); + * + * // Render the graphics as an HTMLImageElement + * const image = app.renderer.plugins.extract.image(graphics); + * document.body.appendChild(image); + * @namespace PIXI.extract + */ export { default as webgl } from './webgl/WebGLExtract'; export { default as canvas } from './canvas/CanvasExtract'; diff --git a/src/extract/webgl/WebGLExtract.js b/src/extract/webgl/WebGLExtract.js index 60228bb..1c3dd30 100644 --- a/src/extract/webgl/WebGLExtract.js +++ b/src/extract/webgl/WebGLExtract.js @@ -9,7 +9,7 @@ * An instance of this class is automatically created by default, and can be found at renderer.plugins.extract * * @class - * @memberof PIXI + * @memberof PIXI.extract */ export default class WebGLExtract { @@ -22,9 +22,9 @@ /** * Collection of methods for extracting data (image, pixels, etc.) from a display object or render texture * - * @member {PIXI.WebGLExtract} extract + * @member {PIXI.extract.WebGLExtract} extract * @memberof PIXI.WebGLRenderer# - * @see PIXI.WebGLExtract + * @see PIXI.extract.WebGLExtract */ renderer.extract = this; } diff --git a/src/extras/AnimatedSprite.js b/src/extras/AnimatedSprite.js index 13b804f..5297778 100644 --- a/src/extras/AnimatedSprite.js +++ b/src/extras/AnimatedSprite.js @@ -137,7 +137,7 @@ this.playing = true; if (this._autoUpdate) { - core.ticker.shared.add(this.update, this); + core.ticker.shared.add(this.update, this, core.UPDATE_PRIORITY.HIGH); } } diff --git a/src/extras/TextureTransform.js b/src/extras/TextureTransform.js index 78e6e40..16536d2 100644 --- a/src/extras/TextureTransform.js +++ b/src/extras/TextureTransform.js @@ -65,8 +65,36 @@ } /** + * Multiplies uvs array to transform + * @param {Float32Array} uvs mesh uvs + * @param {Float32Array} [out=uvs] output + * @returns {Float32Array} output + */ + multiplyUvs(uvs, out) + { + if (out === undefined) + { + out = uvs; + } + + const mat = this.mapCoord; + + for (let i = 0; i < uvs.length; i += 2) + { + const x = uvs[i]; + const y = uvs[i + 1]; + + out[i] = (x * mat.a) + (y * mat.c) + mat.tx; + out[i + 1] = (x * mat.b) + (y * mat.d) + mat.ty; + } + + return out; + } + + /** * updates matrices if texture was changed * @param {boolean} forceUpdate if true, matrices will be updated any case + * @returns {boolean} whether or not it was updated */ update(forceUpdate) { @@ -74,13 +102,13 @@ if (!tex || !tex.valid) { - return; + return false; } if (!forceUpdate && this._lastTextureID === tex._updateID) { - return; + return false; } this._lastTextureID = tex._updateID; @@ -110,5 +138,7 @@ frame[3] = (tex._frame.y + tex._frame.height - margin + offset) / texBase.height; this.uClampOffset[0] = offset / texBase.realWidth; this.uClampOffset[1] = offset / texBase.realHeight; + + return true; } } diff --git a/src/extras/TilingSprite.js b/src/extras/TilingSprite.js index 1f6014f..a3f027f 100644 --- a/src/extras/TilingSprite.js +++ b/src/extras/TilingSprite.js @@ -180,9 +180,9 @@ const transform = this.worldTransform; const resolution = renderer.resolution; const baseTexture = texture.baseTexture; - const baseTextureResolution = texture.baseTexture.resolution; - const modX = (this.tilePosition.x / this.tileScale.x) % texture._frame.width; - const modY = (this.tilePosition.y / this.tileScale.y) % texture._frame.height; + const baseTextureResolution = baseTexture.resolution; + const modX = ((this.tilePosition.x / this.tileScale.x) % texture._frame.width) * baseTextureResolution; + const modY = ((this.tilePosition.y / this.tileScale.y) % texture._frame.height) * baseTextureResolution; // create a nice shiny pattern! // TODO this needs to be refreshed if texture changes.. diff --git a/src/extras/cacheAsBitmap.js b/src/extras/cacheAsBitmap.js index 5dd5640..aea06a8 100644 --- a/src/extras/cacheAsBitmap.js +++ b/src/extras/cacheAsBitmap.js @@ -227,7 +227,16 @@ this.transform._parentID = -1; // restore the transform of the cached sprite to avoid the nasty flicker.. - this.updateTransform(); + if (!this.parent) + { + this.parent = renderer._tempDisplayObjectParent; + this.updateTransform(); + this.parent = null; + } + else + { + this.updateTransform(); + } // map the hit test.. this.containsPoint = cachedSprite.containsPoint.bind(cachedSprite); @@ -314,7 +323,17 @@ cachedSprite._bounds = this._bounds; cachedSprite.alpha = cacheAlpha; - this.updateTransform(); + if (!this.parent) + { + this.parent = renderer._tempDisplayObjectParent; + this.updateTransform(); + this.parent = null; + } + else + { + this.updateTransform(); + } + this.updateTransform = this.displayObjectUpdateTransform; this._cacheData.sprite = cachedSprite; @@ -358,9 +377,12 @@ * Destroys the cached object. * * @private + * @param {object|boolean} [options] - Options parameter. A boolean will act as if all options + * have been set to that value. + * Used when destroying containers, see the Container.destroy method. */ -DisplayObject.prototype._cacheAsBitmapDestroy = function _cacheAsBitmapDestroy() +DisplayObject.prototype._cacheAsBitmapDestroy = function _cacheAsBitmapDestroy(options) { this.cacheAsBitmap = false; - this.destroy(); + this.destroy(options); }; diff --git a/src/extras/index.js b/src/extras/index.js index bbd8bbf..a34d7ec 100644 --- a/src/extras/index.js +++ b/src/extras/index.js @@ -1,7 +1,7 @@ /** + * Additional PIXI DisplayObjects for animation, tiling and bitmap text. * @namespace PIXI.extras */ -export { default as TextureTransform } from './TextureTransform'; export { default as AnimatedSprite } from './AnimatedSprite'; export { default as TilingSprite } from './TilingSprite'; export { default as TilingSpriteRenderer } from './webgl/TilingSpriteRenderer'; diff --git a/src/extras/webgl/TilingSpriteRenderer.js b/src/extras/webgl/TilingSpriteRenderer.js index aeec172..eee3b5c 100644 --- a/src/extras/webgl/TilingSpriteRenderer.js +++ b/src/extras/webgl/TilingSpriteRenderer.js @@ -11,7 +11,7 @@ * WebGL renderer plugin for tiling sprites * * @class - * @memberof PIXI + * @memberof PIXI.extras * @extends PIXI.ObjectRenderer */ export default class TilingSpriteRenderer extends core.ObjectRenderer @@ -118,7 +118,7 @@ tempMat.invert(); if (isSimple) { - tempMat.append(uv.mapCoord); + tempMat.prepend(uv.mapCoord); } else { diff --git a/src/filters/displacement/DisplacementFilter.js b/src/filters/displacement/DisplacementFilter.js index d5355c3..b5d030c 100644 --- a/src/filters/displacement/DisplacementFilter.js +++ b/src/filters/displacement/DisplacementFilter.js @@ -35,7 +35,7 @@ this.maskSprite = sprite; this.maskMatrix = maskMatrix; - this.uniforms.mapSampler = sprite.texture; + this.uniforms.mapSampler = sprite._texture; this.uniforms.filterMatrix = maskMatrix; this.uniforms.scale = { x: 1, y: 1 }; diff --git a/src/filters/index.js b/src/filters/index.js index f3e8862..7ceb50a 100644 --- a/src/filters/index.js +++ b/src/filters/index.js @@ -1,4 +1,21 @@ /** + * This namespace contains WebGL-only display filters that can be applied + * to DisplayObjects using the {@link PIXI.DisplayObject#filters filters} property. + * @example + * // Create a new application + * const app = new PIXI.Application(); + * + * // Draw a green rectangle + * const rect = new PIXI.Graphics() + * .beginFill(0x00ff00) + * .drawRect(40, 40, 200, 200); + * + * // Add a blur filter + * rect.filters = [new PIXI.filters.BlurFilter()]; + * + * // Display rectangle + * app.stage.addChild(rect); + * document.body.appendChild(app.view); * @namespace PIXI.filters */ export { default as FXAAFilter } from './fxaa/FXAAFilter'; diff --git a/src/index.js b/src/index.js index c553427..77da490 100644 --- a/src/index.js +++ b/src/index.js @@ -20,6 +20,14 @@ import { utils } from './core'; utils.mixins.performMixins(); +/** + * Alias for {@link PIXI.loaders.shared}. + * @name loader + * @memberof PIXI + * @type {PIXI.loader.Loader} + */ +const loader = loaders.shared || null; + export { accessibility, extract, @@ -30,18 +38,8 @@ mesh, particles, prepare, + loader, }; -/** - * A premade instance of the loader that can be used to load resources. - * - * @name loader - * @memberof PIXI - * @property {PIXI.loaders.Loader} - */ -const loader = loaders && loaders.Loader ? new loaders.Loader() : null; // check is there in case user excludes loader lib - -export { loader }; - // Always export pixi globally. global.PIXI = exports; // eslint-disable-line diff --git a/src/interaction/InteractionData.js b/src/interaction/InteractionData.js index 02d53a3..449f8cc 100644 --- a/src/interaction/InteractionData.js +++ b/src/interaction/InteractionData.js @@ -30,7 +30,10 @@ /** * When passed to an event handler, this will be the original DOM Event that was captured * - * @member {Event} + * @see https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent + * @see https://developer.mozilla.org/en-US/docs/Web/API/TouchEvent + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent + * @member {MouseEvent|TouchEvent|PointerEvent} */ this.originalEvent = null; } diff --git a/.travis.yml b/.travis.yml index 3df8b22..14de334 100644 --- a/.travis.yml +++ b/.travis.yml @@ -110,3 +110,43 @@ on: all_branches: true condition: $TRAVIS_TAG + # Deploy config for latest release + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: dist + upload-dir: "release" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: docs + upload-dir: "release/docs" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: coverage + upload-dir: "release/coverage" + on: + all_branches: true + condition: $TRAVIS_TAG \ No newline at end of file diff --git a/README.md b/README.md index d26fadd..3b85f85 100644 --- a/README.md +++ b/README.md @@ -36,22 +36,14 @@ - Wiki: Other misc tutorials and resources are [on the Wiki](https://github.com/pixijs/pixi.js/wiki/Resources). ### Community ### -- Forums: Check out the [forum] (http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow] (http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. +- Forums: Check out the [forum](http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow](http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. - Inspiration: Check out the [gallery](http://www.pixijs.com/gallery) to see some of the amazing things people have created! - Chat: You can join us on [Gitter](https://gitter.im/pixijs/pixi.js) To chat about Pixi. We also now have a Slack channel. If you would like to join it please Send me an email (mat@goodboydigital.com) and I will invite you in. ### Setup ### -It's easy to get started with Pixi.js! Simply grab the pre-built versions from here: - -Release Branch - Nice and stable Pixi.js -- Unminified: [http://pixijs.download/release/pixi.js] -- Minified: [http://pixijs.download/release/pixi.min.js] - -Develop Branch - The bleeding edge version of Pixi.js -- Unminified: [http://pixijs.download/dev/pixi.js] -- Minified: [http://pixijs.download/dev/pixi.min.js] +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. @@ -70,10 +62,10 @@ #### CDN Install (via cdnjs) ```html - + ``` -_Note: `4.2.2` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ +_Note: `4.4.3` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ ### Demos ### @@ -130,7 +122,7 @@ // and the root stage PIXI.Container. var app = new PIXI.Application(); -// The application will create a canvas element for you that you +// The application will create a canvas element for you that you // can then insert into the DOM. document.body.appendChild(app.view); @@ -150,7 +142,7 @@ // Add the bunny to the scene we are building. app.stage.addChild(bunny); - + // Listen for frame updates app.ticker.add(function() { // each frame we spin the bunny around a bit diff --git a/bower.json b/bower.json deleted file mode 100644 index 41b668c..0000000 --- a/bower.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "pixi.js", - "main": "dist/pixi.min.js", - "ignore": [ - "**/.*", - "docs", - "node_modules", - "bower_components", - "test", - "scripts", - "bower.json", - "inch.json" - ], - "dependencies": { - }, - "devDependencies": { - } -} diff --git a/package.json b/package.json index d35cbb2..308469f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.3.5", + "version": "4.4.4", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ @@ -40,11 +40,11 @@ "lib": "babel src --out-dir lib -s", "predocs": "rimraf docs/**", "docs": "jsdoc -c scripts/jsdoc.conf.json -R README.md", - "publish:patch": "npm version patch --no-git-tag-version && npm publish", - "publish:minor": "npm version minor --no-git-tag-version && npm publish", - "publish:major": "npm version major --no-git-tag-version && npm publish", + "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", - "postpublish": "node scripts/release.js" + "postpublish": "git push && git push --tags" }, "files": [ "dist/", @@ -72,7 +72,6 @@ "electron": "^1.4.15", "eslint": "^3.5.0", "floss": "^2.0.1", - "gh-pages": "^0.11.0", "jaguarjs-jsdoc": "^1.0.1", "js-md5": "^0.4.1", "jsdoc": "^3.4.2", diff --git a/scripts/jsdoc.conf.json b/scripts/jsdoc.conf.json index 553e61c..3f52180 100644 --- a/scripts/jsdoc.conf.json +++ b/scripts/jsdoc.conf.json @@ -43,7 +43,7 @@ }, "markdown" : { "parser" : "gfm", - "hardwrap" : true + "hardwrap" : false }, "opts": { "encoding" : "utf8", diff --git a/scripts/release.js b/scripts/release.js deleted file mode 100644 index 156620c..0000000 --- a/scripts/release.js +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env node -'use strict'; - -// Publish script to push releases of the bin files -// the normally are gitignored -const ghpages = require('gh-pages'); -const path = require('path'); -const packageInfo = require(path.join(__dirname, '..', 'package.json')); - -const options = { - src: [ - 'dist/**/*', - 'lib/**/*', - 'src/**/*', - 'scripts/*', - 'scripts/renders/*', - 'test/**/*', - '*.json', - '*.md', - 'LICENSE', - '.eslintrc', - '.editorconfig', - '.travis.yml', - '.babelrc', - ], - dotfiles: true, - branch: 'release', - message: packageInfo.version, - logger: console.log.bind(console), -}; - -ghpages.publish(process.cwd(), options, (err) => -{ - if (err) - { - console.log(err); - process.exit(1); - - return; - } - - process.exit(0); -}); diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index f16bcb1..220427a 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -21,7 +21,7 @@ const DIV_HOOK_ZINDEX = 2; /** - * The Accessibility manager reacreates the ability to tab and and have content read by screen + * The Accessibility manager recreates the ability to tab and and have content read by screen * readers. This is very important as it can possibly help people with disabilities access pixi * content. * diff --git a/src/accessibility/index.js b/src/accessibility/index.js index 568cfb1..0ae108f 100644 --- a/src/accessibility/index.js +++ b/src/accessibility/index.js @@ -1,4 +1,9 @@ /** + * This namespace contains a renderer plugin for interaction accessibility for end-users + * with physical impairments which require screen-renders, keyboard navigation, etc. + * + * Do not instantiate this plugin directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. * @namespace PIXI.accessibility */ export { default as accessibleTarget } from './accessibleTarget'; diff --git a/src/core/Application.js b/src/core/Application.js index 5340578..2fcc729 100644 --- a/src/core/Application.js +++ b/src/core/Application.js @@ -1,6 +1,8 @@ import { autoDetectRenderer } from './autoDetectRenderer'; import Container from './display/Container'; import { shared, Ticker } from './ticker'; +import settings from './settings'; +import { UPDATE_PRIORITY } from './const'; /** * Convenience class to create a new PIXI application. @@ -22,28 +24,52 @@ */ export default class Application { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experience unexplained flickering try setting this to true. - * @param {boolean} [useSharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedLoader=false] - `true` to use PIXI.loaders.shared, `false` to create new Loader. */ - constructor(width, height, options, noWebGL, useSharedTicker = false) + constructor(options, arg2, arg3, arg4, arg5) { + // Support for constructor(width, height, options, noWebGL, useSharedTicker) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + forceCanvas: !!arg4, + sharedTicker: !!arg5, + }, arg3); + } + + /** + * The default options, so we mixin functionality later. + * @member {object} + * @protected + */ + this._options = options = Object.assign({ + sharedTicker: false, + forceCanvas: false, + sharedLoader: false, + }, options); + /** * WebGL renderer if available, otherwise CanvasRenderer * @member {PIXI.WebGLRenderer|PIXI.CanvasRenderer} */ - this.renderer = autoDetectRenderer(width, height, options, noWebGL); + this.renderer = autoDetectRenderer(options); /** * The root display container that's rendered. @@ -63,7 +89,7 @@ * @member {PIXI.ticker.Ticker} * @default PIXI.ticker.shared */ - this.ticker = useSharedTicker ? shared : new Ticker(); + this.ticker = options.sharedTicker ? shared : new Ticker(); // Start the rendering this.start(); @@ -78,7 +104,7 @@ this._ticker = ticker; if (ticker) { - ticker.add(this.render, this); + ticker.add(this.render, this, UPDATE_PRIORITY.LOW); } } get ticker() // eslint-disable-line require-jsdoc @@ -136,13 +162,18 @@ */ destroy(removeView) { - this.stop(); + const oldTicker = this._ticker; + this.ticker = null; + oldTicker.destroy(); + this.stage.destroy(); this.stage = null; this.renderer.destroy(removeView); this.renderer = null; + + this._options = null; } } diff --git a/src/core/autoDetectRenderer.js b/src/core/autoDetectRenderer.js index 8960425..9ec0364 100644 --- a/src/core/autoDetectRenderer.js +++ b/src/core/autoDetectRenderer.js @@ -2,6 +2,7 @@ import CanvasRenderer from './renderers/canvas/CanvasRenderer'; import WebGLRenderer from './renderers/webgl/WebGLRenderer'; +// eslint-disable-next-line valid-jsdoc /** * This helper function will automatically detect which renderer you should be using. * WebGL is the preferred renderer as it is a lot faster. If webGL is not supported by @@ -9,24 +10,32 @@ * * @memberof PIXI * @function autoDetectRenderer - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @return {PIXI.WebGLRenderer|PIXI.CanvasRenderer} Returns WebGL renderer if available, otherwise CanvasRenderer */ -export function autoDetectRenderer(width = 800, height = 600, options, noWebGL) +export function autoDetectRenderer(options, arg1, arg2, arg3) { - if (!noWebGL && utils.isWebGLSupported()) + // Backward-compatible support for noWebGL option + let forceCanvas = options && options.forceCanvas; + + if (arg3 !== undefined) { - return new WebGLRenderer(width, height, options); + forceCanvas = arg3; } - return new CanvasRenderer(width, height, options); + if (!forceCanvas && utils.isWebGLSupported()) + { + return new WebGLRenderer(options, arg1, arg2); + } + + return new CanvasRenderer(options, arg1, arg2); } diff --git a/src/core/const.js b/src/core/const.js index b9e99ae..2c68dee 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -341,3 +341,27 @@ LINEAR_VERTICAL: 0, LINEAR_HORIZONTAL: 1, }; + +/** + * Represents the update priorities used by internal PIXI classes when registered with + * the {@link PIXI.ticker.Ticker} object. Higher priority items are updated first and lower + * priority items, such as render, should go later. + * + * @static + * @constant + * @name UPDATE_PRIORITY + * @memberof PIXI + * @type {object} + * @property {number} INTERACTION=50 Highest priority, used for {@link PIXI.interaction.InteractionManager} + * @property {number} HIGH=25 High priority updating, {@link PIXI.VideoBaseTexture} and {@link PIXI.extras.AnimatedSprite} + * @property {number} NORMAL=0 Default priority for ticker events, see {@link PIXI.ticker.Ticker#add}. + * @property {number} LOW=-25 Low priority used for {@link PIXI.Application} rendering. + * @property {number} UTILITY=-50 Lowest priority used for {@link PIXI.prepare.BasePrepare} utility. + */ +export const UPDATE_PRIORITY = { + INTERACTION: 50, + HIGH: 25, + NORMAL: 0, + LOW: -25, + UTILITY: -50, +}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 4e6c77d..decb42a 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -13,7 +13,6 @@ * * @class * @extends EventEmitter - * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ export default class DisplayObject extends EventEmitter @@ -113,6 +112,29 @@ * @private */ this._mask = null; + + /** + * If the object has been destroyed via destroy(). If true, it should not be used. + * + * @member {boolean} + * @private + * @readonly + */ + this._destroyed = false; + + /** + * Fired when this DisplayObject is added to a Container. + * + * @event PIXI.DisplayObject#added + * @param {PIXI.Container} container - The container added to. + */ + + /** + * Fired when this DisplayObject is removed from a Container. + * + * @event PIXI.DisplayObject#removed + * @param {PIXI.Container} container - The container removed from. + */ } /** @@ -403,6 +425,8 @@ this.interactive = false; this.interactiveChildren = false; + + this._destroyed = true; } /** diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index aafdfe2..f787c4a 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -792,8 +792,8 @@ sprite.worldAlpha = this.worldAlpha * sprite.alpha; sprite.blendMode = this.blendMode; - sprite.texture._frame.width = rect.width; - sprite.texture._frame.height = rect.height; + sprite._texture._frame.width = rect.width; + sprite._texture._frame.height = rect.height; sprite.transform.worldTransform = this.transform.worldTransform; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index a24c775..b9f192d 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -19,11 +19,12 @@ */ export default class SystemRenderer extends EventEmitter { + // eslint-disable-next-line valid-jsdoc /** * @param {string} system - The name of the system this renderer is for. - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -37,27 +38,31 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(system, screenWidth, screenHeight, options) + constructor(system, options, arg2, arg3) { super(); sayHello(system); - // prepare options - if (options) + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') { - for (const i in settings.RENDER_OPTIONS) - { - if (typeof options[i] === 'undefined') - { - options[i] = settings.RENDER_OPTIONS[i]; - } - } + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); } - else - { - options = settings.RENDER_OPTIONS; - } + + // Add the default render options + options = Object.assign({}, settings.RENDER_OPTIONS, options); + + /** + * The supplied constructor options. + * + * @member {Object} + * @readOnly + */ + this.options = options; /** * The type of the renderer. @@ -75,7 +80,7 @@ * * @member {PIXI.Rectangle} */ - this.screen = new Rectangle(0, 0, screenWidth || 800, screenHeight || 600); + this.screen = new Rectangle(0, 0, options.width, options.height); /** * The canvas element that everything is drawn to @@ -279,6 +284,8 @@ this.blendModes = null; + this.options = null; + this.preserveDrawingBuffer = false; this.clearBeforeRender = false; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index e84e49c..691caf0 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -17,10 +17,11 @@ */ export default class CanvasRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -34,9 +35,9 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('Canvas', screenWidth, screenHeight, options); + super('Canvas', options, arg2, arg3); this.type = RENDERER_TYPE.CANVAS; @@ -96,7 +97,19 @@ this.context = null; this.renderingToScreen = false; - this.resize(screenWidth, screenHeight); + this.resize(this.options.width, this.options.height); + + /** + * Fired after rendering finishes. + * + * @event PIXI.CanvasRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.CanvasRenderer#prerender + */ } /** @@ -299,4 +312,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.CanvasRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.CanvasExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.CanvasPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.CanvasRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index e1cf7cd..f6fe123 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -31,11 +31,12 @@ */ export default class WebGLRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** * - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -55,9 +56,9 @@ * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('WebGL', screenWidth, screenHeight, options); + super('WebGL', options, arg2, arg3); /** * The type of this renderer as a standardised const @@ -89,12 +90,6 @@ }; - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ this._backgroundColorRgba[3] = this.transparent ? 0 : 1; this.globalUniforms = new UniformGroup({ @@ -117,6 +112,13 @@ this.initPlugins(); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ if(options.context) { this.context.initFromContext(options.context); @@ -169,6 +171,25 @@ } return this; + + /** + * Fired after rendering finishes. + * + * @event PIXI.WebGLRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.WebGLRenderer#prerender + */ + + /** + * Fired when the WebGL context is set. + * + * @event PIXI.WebGLRenderer#context + * @param {WebGLRenderingContext} gl - WebGL context. + */ } /** @@ -295,4 +316,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.WebGLRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.WebGLRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(WebGLRenderer); diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index 4eb6128..fa3d883 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -47,6 +47,14 @@ * @member {boolean} */ this.enabled = true; + + /** + * If enabled, pixi will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; } /** diff --git a/src/core/renderers/webgl/systems/ContextSystem.js b/src/core/renderers/webgl/systems/ContextSystem.js index ff958c7..8df85ea 100644 --- a/src/core/renderers/webgl/systems/ContextSystem.js +++ b/src/core/renderers/webgl/systems/ContextSystem.js @@ -75,7 +75,8 @@ */ createContext(canvas, options) { - var gl = canvas.getContext('webgl', options) || + var gl = //canvas.getContext('webgl2', options) || + canvas.getContext('webgl', options) || canvas.getContext('experimental-webgl', options); if (!gl) diff --git a/src/core/renderers/webgl/systems/FilterSystem.js b/src/core/renderers/webgl/systems/FilterSystem.js index f47335d..37e28d2 100644 --- a/src/core/renderers/webgl/systems/FilterSystem.js +++ b/src/core/renderers/webgl/systems/FilterSystem.js @@ -111,7 +111,7 @@ // TODO we should fit the rect around the transform.. } - else + else if (filters[0].autoFit) { sourceFrame.fit(filterData.stack[0].destinationFrame); } diff --git a/src/core/renderers/webgl/systems/shader/shader/mapType.js b/src/core/renderers/webgl/systems/shader/shader/mapType.js index 581dace..917538a 100644 --- a/src/core/renderers/webgl/systems/shader/shader/mapType.js +++ b/src/core/renderers/webgl/systems/shader/shader/mapType.js @@ -15,7 +15,7 @@ } } - return GL_TABLE[type]; + return GL_TABLE[type]; }; var GL_TABLE = null; diff --git a/src/core/settings.js b/src/core/settings.js index 18c5877..38ef45c 100644 --- a/src/core/settings.js +++ b/src/core/settings.js @@ -2,6 +2,15 @@ import canUploadSameBuffer from './utils/canUploadSameBuffer'; /** + * User's customizable globals for overriding the default PIXI settings, such + * as a renderer's default resolution, framerate, float percision, etc. + * @example + * // Use the native window resolution as the default resolution + * // will support high-density displays when rendering + * PIXI.settings.RESOLUTION = window.devicePixelRatio. + * + * // Disable interpolation when scaling, will make texture be pixelated + * PIXI.settings.SCALE_MODE = PIXI.SCALE_MODES.NEAREST; * @namespace PIXI.settings */ export default { @@ -101,6 +110,9 @@ * @property {boolean} clearBeforeRender=true * @property {boolean} preserveDrawingBuffer=false * @property {boolean} roundPixels=false + * @property {number} width=800 + * @property {number} height=600 + * @property {boolean} legacy=false */ RENDER_OPTIONS: { view: null, @@ -112,6 +124,9 @@ clearBeforeRender: true, preserveDrawingBuffer: false, roundPixels: false, + width: 800, + height: 600, + legacy: false, }, /** diff --git a/src/core/shader/Program.js b/src/core/shader/Program.js index c0b538b..07b4398 100644 --- a/src/core/shader/Program.js +++ b/src/core/shader/Program.js @@ -1,6 +1,6 @@ import extractUniformsFromSrc from './extractUniformsFromSrc'; import generateUniformsSync from './generateUniformsSync'; -import glCore from 'pixi-gl-core'; +import shaderUtils from '../renderers/webgl/systems/shader/shader'; import { ProgramCache } from '../utils'; import getTestContext from '../utils/getTestContext'; @@ -64,14 +64,17 @@ } else { - vertexSrc = glCore.shader.setPrecision(vertexSrc, 'mediump'); - fragmentSrc = glCore.shader.setPrecision(fragmentSrc, 'mediump'); + vertexSrc = shaderUtils.setPrecision(vertexSrc, 'mediump'); + fragmentSrc = shaderUtils.setPrecision(fragmentSrc, 'mediump'); - const program = glCore.shader.compileProgram(gl, vertexSrc, fragmentSrc); + const program = shaderUtils.compileProgram(gl, vertexSrc, fragmentSrc); + console.log("<<<<<<>>><<"); this.attributeData = this.getAttributeData(program, gl); this.uniformData = this.getUniformData(program, gl); + console.log(this.uniformData); + //gl.deleteProgram(program); } } @@ -95,13 +98,13 @@ for (let i = 0; i < totalAttributes; i++) { const attribData = gl.getActiveAttrib(program, i); - const type = glCore.shader.mapType(gl, attribData.type); + const type = shaderUtils.mapType(gl, attribData.type); /*eslint-disable */ const data = { type: type, name: attribData.name, - size: glCore.shader.mapSize(type), + size: shaderUtils.mapSize(type), location: 0, }; /*eslint-enable */ @@ -137,13 +140,14 @@ // TODO expose this as a prop? // const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); - const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); + //const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); for (let i = 0; i < totalUniforms; i++) { const uniformData = gl.getActiveUniform(program, i); const name = uniformData.name.replace(/\[.*?\]/, ''); - const type = glCore.shader.mapType(gl, uniformData.type); + const type = shaderUtils.mapType(gl, uniformData.type); + console.log('mapping ' + uniformData.type + ' to ' + type); // if (!name.match(maskRegex)) { @@ -151,7 +155,7 @@ uniforms[name] = { type: type, size: uniformData.size, - value: glCore.shader.defaultValue(type, uniformData.size), + value: shaderUtils.defaultValue(type, uniformData.size), }; /*eslint-enable */ } diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index e387e99..b57d095 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -147,12 +147,12 @@ // so if _width is 0 then width was not set.. if (this._width) { - this.scale.x = sign(this.scale.x) * this._width / this.texture.orig.width; + this.scale.x = sign(this.scale.x) * this._width / this._texture.orig.width; } if (this._height) { - this.scale.y = sign(this.scale.y) * this._height / this.texture.orig.height; + this.scale.y = sign(this.scale.y) * this._height / this._texture.orig.height; } } diff --git a/src/core/sprites/canvas/CanvasTinter.js b/src/core/sprites/canvas/CanvasTinter.js index 27e5e0c..fd9de5d 100644 --- a/src/core/sprites/canvas/CanvasTinter.js +++ b/src/core/sprites/canvas/CanvasTinter.js @@ -18,7 +18,7 @@ */ getTintedTexture: (sprite, color) => { - const texture = sprite.texture; + const texture = sprite._texture; color = CanvasTinter.roundColor(color); diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 5a4edda..8b9b797 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -344,6 +344,7 @@ const texture = this._texture; const style = this._style; + const padding = style.trim ? 0 : style.padding; texture.baseTexture.valid = true; texture.baseTexture.resolution = this.resolution; @@ -353,11 +354,11 @@ texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - texture.trim.x = -style.padding; - texture.trim.y = -style.padding; + texture.trim.x = -padding; + texture.trim.y = -padding; - texture.orig.width = texture._frame.width - (style.padding * 2); - texture.orig.height = texture._frame.height - (style.padding * 2); + texture.orig.width = texture._frame.width - (padding * 2); + texture.orig.height = texture._frame.height - (padding * 2); // call sprite onTextureUpdate to update scale if _width or _height were set this._onTextureUpdate(); diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index 853da3d..8a9c34b 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -114,6 +114,38 @@ this.cacheId = null; this.validate(); + + /** + * Fired when a not-immediately-available source finishes loading. + * + * @protected + * @event PIXI.BaseTexture#loaded + * @param {PIXI.BaseTexture} baseTexture - Resource loaded. + */ + + /** + * Fired when a not-immediately-available source fails to load. + * + * @protected + * @event PIXI.BaseTexture#error + * @param {PIXI.BaseTexture} baseTexture - Resource errored. + */ + + /** + * Fired when BaseTexture is updated. + * + * @protected + * @event PIXI.BaseTexture#update + * @param {PIXI.BaseTexture} baseTexture - Instance of texture being updated. + */ + + /** + * Fired when BaseTexture is destroyed. + * + * @protected + * @event PIXI.BaseTexture#dispose + * @param {PIXI.BaseTexture} baseTexture - Instance of texture being destroyed. + */ } setResource(resource) diff --git a/src/core/textures/Spritesheet.js b/src/core/textures/Spritesheet.js index d469c62..9b24f27 100644 --- a/src/core/textures/Spritesheet.js +++ b/src/core/textures/Spritesheet.js @@ -208,6 +208,7 @@ // lets also add the frame to pixi's global cache for fromFrame and fromImage functions TextureCache[i] = this.textures[i]; + this.textures[i].textureCacheId = i; } frameIndex++; diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 0a2c379..2213087 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -147,9 +147,9 @@ /** * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. * - * @event update - * @memberof PIXI.Texture# + * @event PIXI.Texture#update * @protected + * @param {PIXI.Texture} texture - Instance of texture being updated. */ this._updateID = 0; @@ -159,6 +159,15 @@ * @type {Object} */ this.transform = null; + + /** + * The id under which this Texture has been added to the texture cache. This is + * automatically set in certain cases, but may not always be accurate, particularly if + * the texture is in the cache under multiple ids. + * + * @member {string} + */ + this.textureCacheId = null; } /** @@ -225,11 +234,17 @@ this._uvs = null; this.trim = null; this.orig = null; + this.textureCacheId = null; this.valid = false; - this.off('dispose', this.dispose, this); - this.off('update', this.update, this); + for (const prop in TextureCache) + { + if (TextureCache[prop] === this) + { + delete TextureCache[prop]; + } + } } /** @@ -315,14 +330,15 @@ */ static fromLoader(source, imageUrl, name) { - // console.log('added from loader...') + // console.log('added from loader...') const resource = new ImageResource(source);//.from(imageUrl, crossorigin);// document.createElement('img'); - // console.log('base resource ' + resource.width); + // console.log('base resource ' + resource.width); const baseTexture = new BaseTexture(resource, settings.SCALE_MODE, getResolutionOfUrl(imageUrl)); + /// console.log('base width ' + baseTexture.width); const texture = new Texture(baseTexture); @@ -335,6 +351,7 @@ // lets also add the frame to pixi's global cache for fromFrame and fromImage fucntions BaseTextureCache[name] = baseTexture; TextureCache[name] = texture; + texture.textureCacheId = name; // also add references by url if they are different. if (name !== imageUrl) @@ -355,6 +372,10 @@ */ static addTextureToCache(texture, id) { + if (!texture.textureCacheId) + { + texture.textureCacheId = id; + } TextureCache[id] = texture; } @@ -495,6 +516,7 @@ */ Texture.EMPTY = new Texture(new BaseTexture()); removeAllHandlers(Texture.EMPTY); +removeAllHandlers(Texture.EMPTY.baseTexture); /** * A white texture of 10x10 size, used for graphics and other things @@ -505,3 +527,4 @@ */ Texture.WHITE = createWhiteTexture(); removeAllHandlers(Texture.WHITE); +removeAllHandlers(Texture.WHITE.baseTexture); diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index 59eb9fd..a9b9f51 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -1,6 +1,7 @@ import BaseTexture from './BaseTexture'; import { uid, BaseTextureCache } from '../utils'; -import * as ticker from '../ticker'; +import { shared } from '../ticker'; +import { UPDATE_PRIORITY } from '../const'; /** * A texture of a [playing] Video. @@ -125,7 +126,7 @@ if (!this._isAutoUpdating && this.autoUpdate) { - ticker.shared.add(this.update, this); + shared.add(this.update, this, UPDATE_PRIORITY.HIGH); this._isAutoUpdating = true; } } @@ -139,7 +140,7 @@ { if (this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); this._isAutoUpdating = false; } } @@ -187,7 +188,7 @@ { if (this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); } if (this.source && this.source._pixiId) @@ -281,12 +282,12 @@ if (!this._autoUpdate && this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); this._isAutoUpdating = false; } else if (this._autoUpdate && !this._isAutoUpdating) { - ticker.shared.add(this.update, this); + shared.add(this.update, this, UPDATE_PRIORITY.HIGH); this._isAutoUpdating = true; } } diff --git a/src/core/ticker/Ticker.js b/src/core/ticker/Ticker.js index 17a1517..40df138 100644 --- a/src/core/ticker/Ticker.js +++ b/src/core/ticker/Ticker.js @@ -1,12 +1,10 @@ import settings from '../settings'; -import EventEmitter from 'eventemitter3'; - -// Internal event used by composed emitter -const TICK = 'tick'; +import { UPDATE_PRIORITY } from '../const'; +import TickerListener from './TickerListener'; /** * A Ticker class that runs an update loop that other objects listen to. - * This class is composed around an EventEmitter object to add listeners + * This class is composed around listeners * meant for execution on the next requested animation frame. * Animation frames are requested only when necessary, * e.g. When the ticker is started and the emitter has listeners. @@ -22,10 +20,11 @@ constructor() { /** - * Internal emitter used to fire 'tick' event + * The first listener. All new listeners added are chained on this. * @private + * @type {TickerListener} */ - this._emitter = new EventEmitter(); + this._head = new TickerListener(null, null, Infinity); /** * Internal current frame request ID @@ -131,7 +130,7 @@ // Invoke listeners now this.update(time); // Listener side effects may have modified ticker state. - if (this.started && this._requestId === null && this._emitter.listeners(TICK, true)) + if (this.started && this._requestId === null && this._head.next) { this._requestId = requestAnimationFrame(this._tick); } @@ -148,7 +147,7 @@ */ _requestIfNeeded() { - if (this._requestId === null && this._emitter.listeners(TICK, true)) + if (this._requestId === null && this._head.next) { // ensure callbacks get correct delta this.lastTime = performance.now(); @@ -193,35 +192,72 @@ } /** - * Calls {@link module:eventemitter3.EventEmitter#on} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. + * Register a handler for tick events. Calls continuously unless + * it is removed or the ticker is stopped. * * @param {Function} fn - The listener function to be added for updates * @param {Function} [context] - The listener context + * @param {number} [priority=PIXI.UPDATE_PRIORITY.NORMAL] - The priority for emitting * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - add(fn, context) + add(fn, context, priority = UPDATE_PRIORITY.NORMAL) { - this._emitter.on(TICK, fn, context); - - this._startIfPossible(); - - return this; + return this._addListener(new TickerListener(fn, context, priority)); } /** - * Calls {@link module:eventemitter3.EventEmitter#once} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. + * Add a handler for the tick event which is only execute once. * * @param {Function} fn - The listener function to be added for one update * @param {Function} [context] - The listener context + * @param {number} [priority=PIXI.UPDATE_PRIORITY.NORMAL] - The priority for emitting * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - addOnce(fn, context) + addOnce(fn, context, priority = UPDATE_PRIORITY.NORMAL) { - this._emitter.once(TICK, fn, context); + return this._addListener(new TickerListener(fn, context, priority, true)); + } + + /** + * Internally adds the event handler so that it can be sorted by priority. + * Priority allows certain handler (user, AnimatedSprite, Interaction) to be run + * before the rendering. + * + * @private + * @param {TickerListener} listener - Current listener being added. + * @returns {PIXI.ticker.Ticker} This instance of a ticker + */ + _addListener(listener) + { + // For attaching to head + let current = this._head.next; + let previous = this._head; + + // Add the first item + if (!current) + { + listener.connect(previous); + } + else + { + // Go from highest to lowest priority + while (current) + { + if (listener.priority >= current.priority) + { + listener.connect(previous); + break; + } + previous = current; + current = current.next; + } + + // Not yet connected + if (!listener.previous) + { + listener.connect(previous); + } + } this._startIfPossible(); @@ -229,19 +265,33 @@ } /** - * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. - * It checks if the emitter has listeners for 'tick' event. - * If it does, then it cancels the animation frame. + * Removes any handlers matching the function and context parameters. + * If no handlers are left after removing, then it cancels the animation frame. * - * @param {Function} [fn] - The listener function to be removed + * @param {Function} fn - The listener function to be removed * @param {Function} [context] - The listener context to be removed * @returns {PIXI.ticker.Ticker} This instance of a ticker */ remove(fn, context) { - this._emitter.off(TICK, fn, context); + let listener = this._head.next; - if (!this._emitter.listeners(TICK, true)) + while (listener) + { + // We found a match, lets remove it + // no break to delete all possible matches + // incase a listener was added 2+ times + if (listener.match(fn, context)) + { + listener = listener.destroy(); + } + else + { + listener = listener.next; + } + } + + if (!this._head.next) { this._cancelIfNeeded(); } @@ -276,6 +326,25 @@ } /** + * Destroy the ticker and don't use after this. Calling + * this method removes all references to internal events. + */ + destroy() + { + this.stop(); + + let listener = this._head.next; + + while (listener) + { + listener = listener.destroy(true); + } + + this._head.destroy(); + this._head = null; + } + + /** * Triggers an update. An update entails setting the * current {@link PIXI.ticker.Ticker#elapsedMS}, * the current {@link PIXI.ticker.Ticker#deltaTime}, @@ -320,8 +389,22 @@ this.deltaTime = elapsedMS * settings.TARGET_FPMS * this.speed; + // Cache a local reference, in-case ticker is destroyed + // during the emit, we can still check for head.next + const head = this._head; + // Invoke listeners added to internal emitter - this._emitter.emit(TICK, this.deltaTime); + let listener = head.next; + + while (listener) + { + listener = listener.emit(this.deltaTime); + } + + if (!head.next) + { + this._cancelIfNeeded(); + } } else { diff --git a/src/core/ticker/TickerListener.js b/src/core/ticker/TickerListener.js new file mode 100644 index 0000000..2bedb34 --- /dev/null +++ b/src/core/ticker/TickerListener.js @@ -0,0 +1,158 @@ +/** + * Internal class for handling the priority sorting of ticker handlers. + * + * @private + * @class + * @memberof PIXI.ticker + */ +export default class TickerListener +{ + /** + * Constructor + * + * @param {Function} fn - The listener function to be added for one update + * @param {Function} [context=null] - The listener context + * @param {number} [priority=0] - The priority for emitting + * @param {boolean} [once=false] - If the handler should fire once + */ + constructor(fn, context = null, priority = 0, once = false) + { + /** + * The handler function to execute. + * @member {Function} + */ + this.fn = fn; + + /** + * The calling to execute. + * @member {Function} + */ + this.context = context; + + /** + * The current priority. + * @member {number} + */ + this.priority = priority; + + /** + * If this should only execute once. + * @member {boolean} + */ + this.once = once; + + /** + * The next item in chain. + * @member {TickerListener} + */ + this.next = null; + + /** + * The previous item in chain. + * @member {TickerListener} + */ + this.previous = null; + + /** + * `true` if this listener has been destroyed already. + * @member {boolean} + * @private + */ + this._destroyed = false; + } + + /** + * Simple compare function to figure out if a function and context match. + * + * @param {Function} fn - The listener function to be added for one update + * @param {Function} context - The listener context + * @return {boolean} `true` if the listener match the arguments + */ + match(fn, context) + { + context = context || null; + + return this.fn === fn && this.context === context; + } + + /** + * Emit by calling the current function. + * @param {number} deltaTime - time since the last emit. + * @return {TickerListener} Next ticker + */ + emit(deltaTime) + { + if (this.context) + { + this.fn.call(this.context, deltaTime); + } + else + { + this.fn(deltaTime); + } + + if (this.once) + { + this.destroy(); + } + + const redirect = this.next; + + // Soft-destroying should remove + // the next reference + if (this._destroyed) + { + this.next = null; + } + + return redirect; + } + + /** + * Connect to the list. + * @param {TickerListener} previous - Input node, previous listener + */ + connect(previous) + { + this.previous = previous; + if (previous.next) + { + previous.next.previous = this; + } + this.next = previous.next; + previous.next = this; + } + + /** + * Destroy and don't use after this. + * @param {boolean} [hard = false] `true` to remove the `next` reference, this + * is considered a hard destroy. Soft destroy maintains the next reference. + * @return {TickerListener} The listener to redirect while emitting or removing. + */ + destroy(hard = false) + { + this._destroyed = true; + this.fn = null; + this.context = null; + + // Disconnect, hook up next and previous + if (this.previous) + { + this.previous.next = this.next; + } + + if (this.next) + { + this.next.previous = this.previous; + } + + // Redirect to the next item + const redirect = this.previous; + + // Remove references + this.next = hard ? null : redirect; + this.previous = null; + + return redirect; + } +} diff --git a/src/core/ticker/index.js b/src/core/ticker/index.js index 4b3017c..c3020d4 100644 --- a/src/core/ticker/index.js +++ b/src/core/ticker/index.js @@ -45,8 +45,25 @@ const shared = new Ticker(); shared.autoStart = true; +shared.destroy = () => +{ + // protect destroying shared ticker + // this is used by other internal systems + // like AnimatedSprite and InteractionManager +}; /** + * This namespace contains an API for interacting with PIXI's internal global update loop. + * + * This ticker is used for rendering, {@link PIXI.extras.AnimatedSprite AnimatedSprite}, + * {@link PIXI.interaction.InteractionManager InteractionManager} and many other time-based PIXI systems. + * @example + * const ticker = new PIXI.ticker.Ticker(); + * ticker.stop(); + * ticker.add((deltaTime) => { + * // do something every frame + * }); + * ticker.start(); * @namespace PIXI.ticker */ export { shared, Ticker }; diff --git a/src/core/utils/index.js b/src/core/utils/index.js index be44750..7441f7b 100644 --- a/src/core/utils/index.js +++ b/src/core/utils/index.js @@ -9,6 +9,21 @@ let saidHello = false; /** + * Generalized convenience utilities for PIXI. + * @example + * // Extend PIXI's internal Event Emitter. + * class MyEmitter extends PIXI.utils.EventEmitter { + * constructor() { + * super(); + * console.log("Emitter created!"); + * } + * } + * + * // Get info on current device + * console.log(PIXI.utils.isMobile); + * + * // Convert hex color to string + * console.log(PIXI.utils.hex2string(0xff00ff)); // returns: "#ff00ff" * @namespace PIXI.utils */ export { diff --git a/src/deprecation.js b/src/deprecation.js index b9c73b3..33daeec 100644 --- a/src/deprecation.js +++ b/src/deprecation.js @@ -918,6 +918,35 @@ }); /** + * @method + * @name PIXI.prepare.BasePrepare#register + * @see PIXI.prepare.BasePrepare#registerFindHook + * @deprecated since version 4.4.2 + * @param {Function} [addHook] - Function call that takes two parameters: `item:*, queue:Array` + * function must return `true` if it was able to add item to the queue. + * @param {Function} [uploadHook] - Function call that takes two parameters: `prepare:CanvasPrepare, item:*` and + * function must return `true` if it was able to handle upload of item. + * @return {PIXI.BasePrepare} Instance of plugin for chaining. + */ +prepare.BasePrepare.prototype.register = function register(addHook, uploadHook) +{ + warn('renderer.plugins.prepare.register is now deprecated, ' + + 'please use renderer.plugins.prepare.registerFindHook & renderer.plugins.prepare.registerUploadHook'); + + if (addHook) + { + this.registerFindHook(addHook); + } + + if (uploadHook) + { + this.registerUploadHook(uploadHook); + } + + return this; +}; + +/** * The number of graphics or textures to upload to the GPU. * * @name PIXI.prepare.canvas.UPLOADS_PER_FRAME @@ -969,67 +998,73 @@ }, }); -Object.defineProperties(loaders.Resource.prototype, { - isJson: { - get() - { - warn('The isJson property is deprecated, please use `resource.type === Resource.TYPE.JSON`.'); +if (loaders.Loader) +{ + const Resource = loaders.Resource; + const Loader = loaders.Loader; - return this.type === loaders.Loader.Resource.TYPE.JSON; + Object.defineProperties(Resource.prototype, { + isJson: { + get() + { + warn('The isJson property is deprecated, please use `resource.type === Resource.TYPE.JSON`.'); + + return this.type === Resource.TYPE.JSON; + }, }, - }, - isXml: { - get() - { - warn('The isXml property is deprecated, please use `resource.type === Resource.TYPE.XML`.'); + isXml: { + get() + { + warn('The isXml property is deprecated, please use `resource.type === Resource.TYPE.XML`.'); - return this.type === loaders.Loader.Resource.TYPE.XML; + return this.type === Resource.TYPE.XML; + }, }, - }, - isImage: { - get() - { - warn('The isImage property is deprecated, please use `resource.type === Resource.TYPE.IMAGE`.'); + isImage: { + get() + { + warn('The isImage property is deprecated, please use `resource.type === Resource.TYPE.IMAGE`.'); - return this.type === loaders.Loader.Resource.TYPE.IMAGE; + return this.type === Resource.TYPE.IMAGE; + }, }, - }, - isAudio: { - get() - { - warn('The isAudio property is deprecated, please use `resource.type === Resource.TYPE.AUDIO`.'); + isAudio: { + get() + { + warn('The isAudio property is deprecated, please use `resource.type === Resource.TYPE.AUDIO`.'); - return this.type === loaders.Loader.Resource.TYPE.AUDIO; + return this.type === Resource.TYPE.AUDIO; + }, }, - }, - isVideo: { - get() - { - warn('The isVideo property is deprecated, please use `resource.type === Resource.TYPE.VIDEO`.'); + isVideo: { + get() + { + warn('The isVideo property is deprecated, please use `resource.type === Resource.TYPE.VIDEO`.'); - return this.type === loaders.Loader.Resource.TYPE.VIDEO; + return this.type === Resource.TYPE.VIDEO; + }, }, - }, -}); + }); -Object.defineProperties(loaders.Loader.prototype, { - before: { - get() - { - warn('The before() method is deprecated, please use pre().'); + Object.defineProperties(Loader.prototype, { + before: { + get() + { + warn('The before() method is deprecated, please use pre().'); - return this.pre; + return this.pre; + }, }, - }, - after: { - get() - { - warn('The after() method is deprecated, please use use().'); + after: { + get() + { + warn('The after() method is deprecated, please use use().'); - return this.use; + return this.use; + }, }, - }, -}); + }); +} /** * @name PIXI.interaction.interactiveTarget#defaultCursor diff --git a/src/extract/canvas/CanvasExtract.js b/src/extract/canvas/CanvasExtract.js index 941ca05..3ac5aee 100644 --- a/src/extract/canvas/CanvasExtract.js +++ b/src/extract/canvas/CanvasExtract.js @@ -8,7 +8,7 @@ * An instance of this class is automatically created by default, and can be found at renderer.plugins.extract * * @class - * @memberof PIXI + * @memberof PIXI.extract */ export default class CanvasExtract { @@ -21,9 +21,9 @@ /** * Collection of methods for extracting data (image, pixels, etc.) from a display object or render texture * - * @member {PIXI.CanvasExtract} extract + * @member {PIXI.extract.CanvasExtract} extract * @memberof PIXI.CanvasRenderer# - * @see PIXI.CanvasExtract + * @see PIXI.extract.CanvasExtract */ renderer.extract = this; } diff --git a/src/extract/index.js b/src/extract/index.js index 0f2170d..e9478c1 100644 --- a/src/extract/index.js +++ b/src/extract/index.js @@ -1,2 +1,22 @@ +/** + * This namespace provides renderer-specific plugins for exporting content from a renderer. + * For instance, these plugins can be used for saving an Image, Canvas element or for exporting the raw image data (pixels). + * + * Do not instantiate these plugins directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. + * @example + * // Create a new app (will auto-add extract plugin to renderer) + * const app = new PIXI.Application(); + * + * // Draw a red circle + * const graphics = new PIXI.Graphics() + * .beginFill(0xFF0000) + * .drawCircle(0, 0, 50); + * + * // Render the graphics as an HTMLImageElement + * const image = app.renderer.plugins.extract.image(graphics); + * document.body.appendChild(image); + * @namespace PIXI.extract + */ export { default as webgl } from './webgl/WebGLExtract'; export { default as canvas } from './canvas/CanvasExtract'; diff --git a/src/extract/webgl/WebGLExtract.js b/src/extract/webgl/WebGLExtract.js index 60228bb..1c3dd30 100644 --- a/src/extract/webgl/WebGLExtract.js +++ b/src/extract/webgl/WebGLExtract.js @@ -9,7 +9,7 @@ * An instance of this class is automatically created by default, and can be found at renderer.plugins.extract * * @class - * @memberof PIXI + * @memberof PIXI.extract */ export default class WebGLExtract { @@ -22,9 +22,9 @@ /** * Collection of methods for extracting data (image, pixels, etc.) from a display object or render texture * - * @member {PIXI.WebGLExtract} extract + * @member {PIXI.extract.WebGLExtract} extract * @memberof PIXI.WebGLRenderer# - * @see PIXI.WebGLExtract + * @see PIXI.extract.WebGLExtract */ renderer.extract = this; } diff --git a/src/extras/AnimatedSprite.js b/src/extras/AnimatedSprite.js index 13b804f..5297778 100644 --- a/src/extras/AnimatedSprite.js +++ b/src/extras/AnimatedSprite.js @@ -137,7 +137,7 @@ this.playing = true; if (this._autoUpdate) { - core.ticker.shared.add(this.update, this); + core.ticker.shared.add(this.update, this, core.UPDATE_PRIORITY.HIGH); } } diff --git a/src/extras/TextureTransform.js b/src/extras/TextureTransform.js index 78e6e40..16536d2 100644 --- a/src/extras/TextureTransform.js +++ b/src/extras/TextureTransform.js @@ -65,8 +65,36 @@ } /** + * Multiplies uvs array to transform + * @param {Float32Array} uvs mesh uvs + * @param {Float32Array} [out=uvs] output + * @returns {Float32Array} output + */ + multiplyUvs(uvs, out) + { + if (out === undefined) + { + out = uvs; + } + + const mat = this.mapCoord; + + for (let i = 0; i < uvs.length; i += 2) + { + const x = uvs[i]; + const y = uvs[i + 1]; + + out[i] = (x * mat.a) + (y * mat.c) + mat.tx; + out[i + 1] = (x * mat.b) + (y * mat.d) + mat.ty; + } + + return out; + } + + /** * updates matrices if texture was changed * @param {boolean} forceUpdate if true, matrices will be updated any case + * @returns {boolean} whether or not it was updated */ update(forceUpdate) { @@ -74,13 +102,13 @@ if (!tex || !tex.valid) { - return; + return false; } if (!forceUpdate && this._lastTextureID === tex._updateID) { - return; + return false; } this._lastTextureID = tex._updateID; @@ -110,5 +138,7 @@ frame[3] = (tex._frame.y + tex._frame.height - margin + offset) / texBase.height; this.uClampOffset[0] = offset / texBase.realWidth; this.uClampOffset[1] = offset / texBase.realHeight; + + return true; } } diff --git a/src/extras/TilingSprite.js b/src/extras/TilingSprite.js index 1f6014f..a3f027f 100644 --- a/src/extras/TilingSprite.js +++ b/src/extras/TilingSprite.js @@ -180,9 +180,9 @@ const transform = this.worldTransform; const resolution = renderer.resolution; const baseTexture = texture.baseTexture; - const baseTextureResolution = texture.baseTexture.resolution; - const modX = (this.tilePosition.x / this.tileScale.x) % texture._frame.width; - const modY = (this.tilePosition.y / this.tileScale.y) % texture._frame.height; + const baseTextureResolution = baseTexture.resolution; + const modX = ((this.tilePosition.x / this.tileScale.x) % texture._frame.width) * baseTextureResolution; + const modY = ((this.tilePosition.y / this.tileScale.y) % texture._frame.height) * baseTextureResolution; // create a nice shiny pattern! // TODO this needs to be refreshed if texture changes.. diff --git a/src/extras/cacheAsBitmap.js b/src/extras/cacheAsBitmap.js index 5dd5640..aea06a8 100644 --- a/src/extras/cacheAsBitmap.js +++ b/src/extras/cacheAsBitmap.js @@ -227,7 +227,16 @@ this.transform._parentID = -1; // restore the transform of the cached sprite to avoid the nasty flicker.. - this.updateTransform(); + if (!this.parent) + { + this.parent = renderer._tempDisplayObjectParent; + this.updateTransform(); + this.parent = null; + } + else + { + this.updateTransform(); + } // map the hit test.. this.containsPoint = cachedSprite.containsPoint.bind(cachedSprite); @@ -314,7 +323,17 @@ cachedSprite._bounds = this._bounds; cachedSprite.alpha = cacheAlpha; - this.updateTransform(); + if (!this.parent) + { + this.parent = renderer._tempDisplayObjectParent; + this.updateTransform(); + this.parent = null; + } + else + { + this.updateTransform(); + } + this.updateTransform = this.displayObjectUpdateTransform; this._cacheData.sprite = cachedSprite; @@ -358,9 +377,12 @@ * Destroys the cached object. * * @private + * @param {object|boolean} [options] - Options parameter. A boolean will act as if all options + * have been set to that value. + * Used when destroying containers, see the Container.destroy method. */ -DisplayObject.prototype._cacheAsBitmapDestroy = function _cacheAsBitmapDestroy() +DisplayObject.prototype._cacheAsBitmapDestroy = function _cacheAsBitmapDestroy(options) { this.cacheAsBitmap = false; - this.destroy(); + this.destroy(options); }; diff --git a/src/extras/index.js b/src/extras/index.js index bbd8bbf..a34d7ec 100644 --- a/src/extras/index.js +++ b/src/extras/index.js @@ -1,7 +1,7 @@ /** + * Additional PIXI DisplayObjects for animation, tiling and bitmap text. * @namespace PIXI.extras */ -export { default as TextureTransform } from './TextureTransform'; export { default as AnimatedSprite } from './AnimatedSprite'; export { default as TilingSprite } from './TilingSprite'; export { default as TilingSpriteRenderer } from './webgl/TilingSpriteRenderer'; diff --git a/src/extras/webgl/TilingSpriteRenderer.js b/src/extras/webgl/TilingSpriteRenderer.js index aeec172..eee3b5c 100644 --- a/src/extras/webgl/TilingSpriteRenderer.js +++ b/src/extras/webgl/TilingSpriteRenderer.js @@ -11,7 +11,7 @@ * WebGL renderer plugin for tiling sprites * * @class - * @memberof PIXI + * @memberof PIXI.extras * @extends PIXI.ObjectRenderer */ export default class TilingSpriteRenderer extends core.ObjectRenderer @@ -118,7 +118,7 @@ tempMat.invert(); if (isSimple) { - tempMat.append(uv.mapCoord); + tempMat.prepend(uv.mapCoord); } else { diff --git a/src/filters/displacement/DisplacementFilter.js b/src/filters/displacement/DisplacementFilter.js index d5355c3..b5d030c 100644 --- a/src/filters/displacement/DisplacementFilter.js +++ b/src/filters/displacement/DisplacementFilter.js @@ -35,7 +35,7 @@ this.maskSprite = sprite; this.maskMatrix = maskMatrix; - this.uniforms.mapSampler = sprite.texture; + this.uniforms.mapSampler = sprite._texture; this.uniforms.filterMatrix = maskMatrix; this.uniforms.scale = { x: 1, y: 1 }; diff --git a/src/filters/index.js b/src/filters/index.js index f3e8862..7ceb50a 100644 --- a/src/filters/index.js +++ b/src/filters/index.js @@ -1,4 +1,21 @@ /** + * This namespace contains WebGL-only display filters that can be applied + * to DisplayObjects using the {@link PIXI.DisplayObject#filters filters} property. + * @example + * // Create a new application + * const app = new PIXI.Application(); + * + * // Draw a green rectangle + * const rect = new PIXI.Graphics() + * .beginFill(0x00ff00) + * .drawRect(40, 40, 200, 200); + * + * // Add a blur filter + * rect.filters = [new PIXI.filters.BlurFilter()]; + * + * // Display rectangle + * app.stage.addChild(rect); + * document.body.appendChild(app.view); * @namespace PIXI.filters */ export { default as FXAAFilter } from './fxaa/FXAAFilter'; diff --git a/src/index.js b/src/index.js index c553427..77da490 100644 --- a/src/index.js +++ b/src/index.js @@ -20,6 +20,14 @@ import { utils } from './core'; utils.mixins.performMixins(); +/** + * Alias for {@link PIXI.loaders.shared}. + * @name loader + * @memberof PIXI + * @type {PIXI.loader.Loader} + */ +const loader = loaders.shared || null; + export { accessibility, extract, @@ -30,18 +38,8 @@ mesh, particles, prepare, + loader, }; -/** - * A premade instance of the loader that can be used to load resources. - * - * @name loader - * @memberof PIXI - * @property {PIXI.loaders.Loader} - */ -const loader = loaders && loaders.Loader ? new loaders.Loader() : null; // check is there in case user excludes loader lib - -export { loader }; - // Always export pixi globally. global.PIXI = exports; // eslint-disable-line diff --git a/src/interaction/InteractionData.js b/src/interaction/InteractionData.js index 02d53a3..449f8cc 100644 --- a/src/interaction/InteractionData.js +++ b/src/interaction/InteractionData.js @@ -30,7 +30,10 @@ /** * When passed to an event handler, this will be the original DOM Event that was captured * - * @member {Event} + * @see https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent + * @see https://developer.mozilla.org/en-US/docs/Web/API/TouchEvent + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent + * @member {MouseEvent|TouchEvent|PointerEvent} */ this.originalEvent = null; } diff --git a/src/interaction/InteractionManager.js b/src/interaction/InteractionManager.js index 8baf13a..2d75c7c 100644 --- a/src/interaction/InteractionManager.js +++ b/src/interaction/InteractionManager.js @@ -13,6 +13,14 @@ const MOUSE_POINTER_ID = 'MOUSE'; +// helpers for hitTest() - only used inside hitTest() +const hitTestEvent = { + target: null, + data: { + global: null, + }, +}; + /** * The interaction manager deals with mouse, touch and pointer events. Any DisplayObject can be interactive * if its interactive parameter is set to true @@ -245,67 +253,60 @@ this.setTargetElement(this.renderer.view, this.renderer.resolution); /** - * Fired when a pointer device button (usually a mouse button) is pressed on the display + * Fired when a pointer device button (usually a mouse left-button) is pressed on the display * object. * - * @event mousedown - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mousedown + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device secondary button (usually a mouse right-button) is pressed * on the display object. * - * @event rightdown - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightdown + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** - * Fired when a pointer device button (usually a mouse button) is released over the display + * Fired when a pointer device button (usually a mouse left-button) is released over the display * object. * - * @event mouseup - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseup + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device secondary button (usually a mouse right-button) is released * over the display object. * - * @event rightup - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightup + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** - * Fired when a pointer device button (usually a mouse button) is pressed and released on + * Fired when a pointer device button (usually a mouse left-button) is pressed and released on * the display object. * - * @event click - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#click + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device secondary button (usually a mouse right-button) is pressed * and released on the display object. * - * @event rightclick - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightclick + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** - * Fired when a pointer device button (usually a mouse button) is released outside the + * Fired when a pointer device button (usually a mouse left-button) is released outside the * display object that initially registered a * [mousedown]{@link PIXI.interaction.InteractionManager#event:mousedown}. * - * @event mouseupoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** @@ -313,146 +314,362 @@ * outside the display object that initially registered a * [rightdown]{@link PIXI.interaction.InteractionManager#event:rightdown}. * - * @event rightupoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device (usually a mouse) is moved while over the display object * - * @event mousemove - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mousemove + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device (usually a mouse) is moved onto the display object * - * @event mouseover - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseover + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device (usually a mouse) is moved off the display object * - * @event mouseout - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseout + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is pressed on the display object. * - * @event pointerdown - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerdown + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is released over the display object. * - * @event pointerup - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerup + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when the operating system cancels a pointer event * - * @event pointercancel - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointercancel + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is pressed and released on the display object. * - * @event pointertap - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointertap + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is released outside the display object that initially * registered a [pointerdown]{@link PIXI.interaction.InteractionManager#event:pointerdown}. * - * @event pointerupoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device is moved while over the display object * - * @event pointermove - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointermove + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device is moved onto the display object * - * @event pointerover - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerover + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device is moved off the display object * - * @event pointerout - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerout + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is placed on the display object. * - * @event touchstart - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchstart + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is removed from the display object. * - * @event touchend - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchend + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when the operating system cancels a touch * - * @event touchcancel - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchcancel + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is placed and removed from the display object. * - * @event tap - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#tap + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is removed outside of the display object that initially * registered a [touchstart]{@link PIXI.interaction.InteractionManager#event:touchstart}. * - * @event touchendoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchendoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is moved along the display object. * - * @event touchmove - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchmove + * @param {PIXI.interaction.InteractionData} event - Interaction data */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is pressed on the display. + * object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mousedown + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is pressed + * on the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightdown + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is released over the display + * object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseup + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is released + * over the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightup + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is pressed and released on + * the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#click + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is pressed + * and released on the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightclick + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is released outside the + * display object that initially registered a + * [mousedown]{@link PIXI.DisplayObject#event:mousedown}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is released + * outside the display object that initially registered a + * [rightdown]{@link PIXI.DisplayObject#event:rightdown}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device (usually a mouse) is moved while over the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mousemove + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device (usually a mouse) is moved onto the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseover + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device (usually a mouse) is moved off the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseout + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is pressed on the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerdown + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is released over the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerup + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when the operating system cancels a pointer event. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointercancel + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is pressed and released on the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointertap + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is released outside the display object that initially + * registered a [pointerdown]{@link PIXI.DisplayObject#event:pointerdown}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device is moved while over the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointermove + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device is moved onto the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerover + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device is moved off the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerout + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is placed on the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchstart + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is removed from the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchend + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when the operating system cancels a touch. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchcancel + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is placed and removed from the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#tap + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is removed outside of the display object that initially + * registered a [touchstart]{@link PIXI.DisplayObject#event:touchstart}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchendoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is moved along the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchmove + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + } + + /** + * Hit tests a point against the display tree, returning the first interactive object that is hit. + * + * @param {PIXI.Point} globalPoint - A point to hit test with, in global space. + * @param {PIXI.Container} [root] - The root display object to start from. If omitted, defaults + * to the last rendered root of the associated renderer. + * @return {PIXI.DisplayObject} The hit display object, if any. + */ + hitTest(globalPoint, root) + { + // clear the target for our hit test + hitTestEvent.target = null; + // assign the global point + hitTestEvent.data.global = globalPoint; + // ensure safety of the root + if (!root) + { + root = this.renderer._lastObjectRendered; + } + // run the hit test + this.processInteractive(hitTestEvent, root, null, true); + // return our found object - it'll be null if we didn't hit anything + + return hitTestEvent.target; } /** @@ -487,7 +704,7 @@ return; } - core.ticker.shared.add(this.update, this); + core.ticker.shared.add(this.update, this, core.UPDATE_PRIORITY.INTERACTION); if (window.navigator.msPointerEnabled) { @@ -804,8 +1021,6 @@ } } - let keepHitTestingAfterChildren = hitTest; - // ** FREE TIP **! If an object is not interactive or has no buttons in it // (such as a game scene!) set interactiveChildren to false for that displayObject. // This will allow pixi to completely ignore and bypass checking the displayObjects children. @@ -818,7 +1033,9 @@ const child = children[i]; // time to get recursive.. if this function will return if something is hit.. - if (this.processInteractive(interactionEvent, child, func, hitTest, interactiveParent)) + const childHit = this.processInteractive(interactionEvent, child, func, hitTest, interactiveParent); + + if (childHit) { // its a good idea to check if a child has lost its parent. // this means it has been removed whilst looping so its best @@ -827,8 +1044,6 @@ continue; } - hit = true; - // we no longer need to hit test any more objects in this container as we we // now know the parent has been hit interactiveParent = false; @@ -838,36 +1053,41 @@ // This means we no longer need to hit test anything else. We still need to run // through all objects, but we don't need to perform any hit tests. - keepHitTestingAfterChildren = false; - - if (child.interactive) + if (childHit) { - hitTest = false; + if (interactionEvent.target) + { + hitTest = false; + } + hit = true; } - - // we can break now as we have hit an object. } } } - hitTest = keepHitTestingAfterChildren; - // no point running this if the item is not interactive or does not have an interactive parent. if (interactive) { // if we are hit testing (as in we have no hit any objects yet) // We also don't need to worry about hit testing if once of the displayObjects children - // has already been hit! - if (hitTest && !hit) + // has already been hit - but only if it was interactive, otherwise we need to keep + // looking for an interactive child, just in case we hit one + if (hitTest && !interactionEvent.target) { if (displayObject.hitArea) { displayObject.worldTransform.applyInverse(point, this._tempPoint); - hit = displayObject.hitArea.contains(this._tempPoint.x, this._tempPoint.y); + if (displayObject.hitArea.contains(this._tempPoint.x, this._tempPoint.y)) + { + hit = true; + } } else if (displayObject.containsPoint) { - hit = displayObject.containsPoint(point); + if (displayObject.containsPoint(point)) + { + hit = true; + } } } @@ -878,7 +1098,10 @@ interactionEvent.target = displayObject; } - func(interactionEvent, displayObject, hit); + if (func) + { + func(interactionEvent, displayObject, !!hit); + } } } @@ -914,7 +1137,7 @@ { const event = events[i]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -998,7 +1221,7 @@ { const event = events[i]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1098,7 +1321,7 @@ const test = isRightButton ? flags.RIGHT_DOWN : flags.LEFT_DOWN; - const isDown = trackingData !== undefined && (trackingData.flags | test); + const isDown = trackingData !== undefined && (trackingData.flags & test); if (hit) { @@ -1118,11 +1341,11 @@ { if (isRightButton) { - trackingData.rightDown = hit; + trackingData.rightDown = false; } else { - trackingData.leftDown = hit; + trackingData.leftDown = false; } } } @@ -1180,7 +1403,7 @@ { const event = events[i]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1255,7 +1478,7 @@ this.setCursorMode(null); } - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1268,6 +1491,12 @@ { this.emit('mouseout', interactionEvent); } + else + { + // we can get touchleave events after touchend, so we want to make sure we don't + // introduce memory leaks + this.releaseInteractionDataForPointerId(interactionData.identifier); + } } /** @@ -1344,7 +1573,7 @@ // Only mouse and pointer can call onPointerOver, so events will always be length 1 const event = events[0]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1366,12 +1595,14 @@ * Get InteractionData for a given pointerId. Store that data as well * * @private - * @param {number} pointerId - Identifier from a pointer event + * @param {PointerEvent} event - Normalized pointer event, output from normalizeToPointerData * @return {InteractionData} - Interaction data for the given pointer identifier */ - getInteractionDataForPointerId(pointerId) + getInteractionDataForPointerId(event) { - if (pointerId === MOUSE_POINTER_ID) + const pointerId = event.pointerId; + + if (pointerId === MOUSE_POINTER_ID || event.pointerType === 'mouse') { return this.mouse; } diff --git a/.travis.yml b/.travis.yml index 3df8b22..14de334 100644 --- a/.travis.yml +++ b/.travis.yml @@ -110,3 +110,43 @@ on: all_branches: true condition: $TRAVIS_TAG + # Deploy config for latest release + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: dist + upload-dir: "release" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: docs + upload-dir: "release/docs" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: coverage + upload-dir: "release/coverage" + on: + all_branches: true + condition: $TRAVIS_TAG \ No newline at end of file diff --git a/README.md b/README.md index d26fadd..3b85f85 100644 --- a/README.md +++ b/README.md @@ -36,22 +36,14 @@ - Wiki: Other misc tutorials and resources are [on the Wiki](https://github.com/pixijs/pixi.js/wiki/Resources). ### Community ### -- Forums: Check out the [forum] (http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow] (http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. +- Forums: Check out the [forum](http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow](http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. - Inspiration: Check out the [gallery](http://www.pixijs.com/gallery) to see some of the amazing things people have created! - Chat: You can join us on [Gitter](https://gitter.im/pixijs/pixi.js) To chat about Pixi. We also now have a Slack channel. If you would like to join it please Send me an email (mat@goodboydigital.com) and I will invite you in. ### Setup ### -It's easy to get started with Pixi.js! Simply grab the pre-built versions from here: - -Release Branch - Nice and stable Pixi.js -- Unminified: [http://pixijs.download/release/pixi.js] -- Minified: [http://pixijs.download/release/pixi.min.js] - -Develop Branch - The bleeding edge version of Pixi.js -- Unminified: [http://pixijs.download/dev/pixi.js] -- Minified: [http://pixijs.download/dev/pixi.min.js] +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. @@ -70,10 +62,10 @@ #### CDN Install (via cdnjs) ```html - + ``` -_Note: `4.2.2` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ +_Note: `4.4.3` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ ### Demos ### @@ -130,7 +122,7 @@ // and the root stage PIXI.Container. var app = new PIXI.Application(); -// The application will create a canvas element for you that you +// The application will create a canvas element for you that you // can then insert into the DOM. document.body.appendChild(app.view); @@ -150,7 +142,7 @@ // Add the bunny to the scene we are building. app.stage.addChild(bunny); - + // Listen for frame updates app.ticker.add(function() { // each frame we spin the bunny around a bit diff --git a/bower.json b/bower.json deleted file mode 100644 index 41b668c..0000000 --- a/bower.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "pixi.js", - "main": "dist/pixi.min.js", - "ignore": [ - "**/.*", - "docs", - "node_modules", - "bower_components", - "test", - "scripts", - "bower.json", - "inch.json" - ], - "dependencies": { - }, - "devDependencies": { - } -} diff --git a/package.json b/package.json index d35cbb2..308469f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.3.5", + "version": "4.4.4", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ @@ -40,11 +40,11 @@ "lib": "babel src --out-dir lib -s", "predocs": "rimraf docs/**", "docs": "jsdoc -c scripts/jsdoc.conf.json -R README.md", - "publish:patch": "npm version patch --no-git-tag-version && npm publish", - "publish:minor": "npm version minor --no-git-tag-version && npm publish", - "publish:major": "npm version major --no-git-tag-version && npm publish", + "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", - "postpublish": "node scripts/release.js" + "postpublish": "git push && git push --tags" }, "files": [ "dist/", @@ -72,7 +72,6 @@ "electron": "^1.4.15", "eslint": "^3.5.0", "floss": "^2.0.1", - "gh-pages": "^0.11.0", "jaguarjs-jsdoc": "^1.0.1", "js-md5": "^0.4.1", "jsdoc": "^3.4.2", diff --git a/scripts/jsdoc.conf.json b/scripts/jsdoc.conf.json index 553e61c..3f52180 100644 --- a/scripts/jsdoc.conf.json +++ b/scripts/jsdoc.conf.json @@ -43,7 +43,7 @@ }, "markdown" : { "parser" : "gfm", - "hardwrap" : true + "hardwrap" : false }, "opts": { "encoding" : "utf8", diff --git a/scripts/release.js b/scripts/release.js deleted file mode 100644 index 156620c..0000000 --- a/scripts/release.js +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env node -'use strict'; - -// Publish script to push releases of the bin files -// the normally are gitignored -const ghpages = require('gh-pages'); -const path = require('path'); -const packageInfo = require(path.join(__dirname, '..', 'package.json')); - -const options = { - src: [ - 'dist/**/*', - 'lib/**/*', - 'src/**/*', - 'scripts/*', - 'scripts/renders/*', - 'test/**/*', - '*.json', - '*.md', - 'LICENSE', - '.eslintrc', - '.editorconfig', - '.travis.yml', - '.babelrc', - ], - dotfiles: true, - branch: 'release', - message: packageInfo.version, - logger: console.log.bind(console), -}; - -ghpages.publish(process.cwd(), options, (err) => -{ - if (err) - { - console.log(err); - process.exit(1); - - return; - } - - process.exit(0); -}); diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index f16bcb1..220427a 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -21,7 +21,7 @@ const DIV_HOOK_ZINDEX = 2; /** - * The Accessibility manager reacreates the ability to tab and and have content read by screen + * The Accessibility manager recreates the ability to tab and and have content read by screen * readers. This is very important as it can possibly help people with disabilities access pixi * content. * diff --git a/src/accessibility/index.js b/src/accessibility/index.js index 568cfb1..0ae108f 100644 --- a/src/accessibility/index.js +++ b/src/accessibility/index.js @@ -1,4 +1,9 @@ /** + * This namespace contains a renderer plugin for interaction accessibility for end-users + * with physical impairments which require screen-renders, keyboard navigation, etc. + * + * Do not instantiate this plugin directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. * @namespace PIXI.accessibility */ export { default as accessibleTarget } from './accessibleTarget'; diff --git a/src/core/Application.js b/src/core/Application.js index 5340578..2fcc729 100644 --- a/src/core/Application.js +++ b/src/core/Application.js @@ -1,6 +1,8 @@ import { autoDetectRenderer } from './autoDetectRenderer'; import Container from './display/Container'; import { shared, Ticker } from './ticker'; +import settings from './settings'; +import { UPDATE_PRIORITY } from './const'; /** * Convenience class to create a new PIXI application. @@ -22,28 +24,52 @@ */ export default class Application { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experience unexplained flickering try setting this to true. - * @param {boolean} [useSharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedLoader=false] - `true` to use PIXI.loaders.shared, `false` to create new Loader. */ - constructor(width, height, options, noWebGL, useSharedTicker = false) + constructor(options, arg2, arg3, arg4, arg5) { + // Support for constructor(width, height, options, noWebGL, useSharedTicker) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + forceCanvas: !!arg4, + sharedTicker: !!arg5, + }, arg3); + } + + /** + * The default options, so we mixin functionality later. + * @member {object} + * @protected + */ + this._options = options = Object.assign({ + sharedTicker: false, + forceCanvas: false, + sharedLoader: false, + }, options); + /** * WebGL renderer if available, otherwise CanvasRenderer * @member {PIXI.WebGLRenderer|PIXI.CanvasRenderer} */ - this.renderer = autoDetectRenderer(width, height, options, noWebGL); + this.renderer = autoDetectRenderer(options); /** * The root display container that's rendered. @@ -63,7 +89,7 @@ * @member {PIXI.ticker.Ticker} * @default PIXI.ticker.shared */ - this.ticker = useSharedTicker ? shared : new Ticker(); + this.ticker = options.sharedTicker ? shared : new Ticker(); // Start the rendering this.start(); @@ -78,7 +104,7 @@ this._ticker = ticker; if (ticker) { - ticker.add(this.render, this); + ticker.add(this.render, this, UPDATE_PRIORITY.LOW); } } get ticker() // eslint-disable-line require-jsdoc @@ -136,13 +162,18 @@ */ destroy(removeView) { - this.stop(); + const oldTicker = this._ticker; + this.ticker = null; + oldTicker.destroy(); + this.stage.destroy(); this.stage = null; this.renderer.destroy(removeView); this.renderer = null; + + this._options = null; } } diff --git a/src/core/autoDetectRenderer.js b/src/core/autoDetectRenderer.js index 8960425..9ec0364 100644 --- a/src/core/autoDetectRenderer.js +++ b/src/core/autoDetectRenderer.js @@ -2,6 +2,7 @@ import CanvasRenderer from './renderers/canvas/CanvasRenderer'; import WebGLRenderer from './renderers/webgl/WebGLRenderer'; +// eslint-disable-next-line valid-jsdoc /** * This helper function will automatically detect which renderer you should be using. * WebGL is the preferred renderer as it is a lot faster. If webGL is not supported by @@ -9,24 +10,32 @@ * * @memberof PIXI * @function autoDetectRenderer - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @return {PIXI.WebGLRenderer|PIXI.CanvasRenderer} Returns WebGL renderer if available, otherwise CanvasRenderer */ -export function autoDetectRenderer(width = 800, height = 600, options, noWebGL) +export function autoDetectRenderer(options, arg1, arg2, arg3) { - if (!noWebGL && utils.isWebGLSupported()) + // Backward-compatible support for noWebGL option + let forceCanvas = options && options.forceCanvas; + + if (arg3 !== undefined) { - return new WebGLRenderer(width, height, options); + forceCanvas = arg3; } - return new CanvasRenderer(width, height, options); + if (!forceCanvas && utils.isWebGLSupported()) + { + return new WebGLRenderer(options, arg1, arg2); + } + + return new CanvasRenderer(options, arg1, arg2); } diff --git a/src/core/const.js b/src/core/const.js index b9e99ae..2c68dee 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -341,3 +341,27 @@ LINEAR_VERTICAL: 0, LINEAR_HORIZONTAL: 1, }; + +/** + * Represents the update priorities used by internal PIXI classes when registered with + * the {@link PIXI.ticker.Ticker} object. Higher priority items are updated first and lower + * priority items, such as render, should go later. + * + * @static + * @constant + * @name UPDATE_PRIORITY + * @memberof PIXI + * @type {object} + * @property {number} INTERACTION=50 Highest priority, used for {@link PIXI.interaction.InteractionManager} + * @property {number} HIGH=25 High priority updating, {@link PIXI.VideoBaseTexture} and {@link PIXI.extras.AnimatedSprite} + * @property {number} NORMAL=0 Default priority for ticker events, see {@link PIXI.ticker.Ticker#add}. + * @property {number} LOW=-25 Low priority used for {@link PIXI.Application} rendering. + * @property {number} UTILITY=-50 Lowest priority used for {@link PIXI.prepare.BasePrepare} utility. + */ +export const UPDATE_PRIORITY = { + INTERACTION: 50, + HIGH: 25, + NORMAL: 0, + LOW: -25, + UTILITY: -50, +}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 4e6c77d..decb42a 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -13,7 +13,6 @@ * * @class * @extends EventEmitter - * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ export default class DisplayObject extends EventEmitter @@ -113,6 +112,29 @@ * @private */ this._mask = null; + + /** + * If the object has been destroyed via destroy(). If true, it should not be used. + * + * @member {boolean} + * @private + * @readonly + */ + this._destroyed = false; + + /** + * Fired when this DisplayObject is added to a Container. + * + * @event PIXI.DisplayObject#added + * @param {PIXI.Container} container - The container added to. + */ + + /** + * Fired when this DisplayObject is removed from a Container. + * + * @event PIXI.DisplayObject#removed + * @param {PIXI.Container} container - The container removed from. + */ } /** @@ -403,6 +425,8 @@ this.interactive = false; this.interactiveChildren = false; + + this._destroyed = true; } /** diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index aafdfe2..f787c4a 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -792,8 +792,8 @@ sprite.worldAlpha = this.worldAlpha * sprite.alpha; sprite.blendMode = this.blendMode; - sprite.texture._frame.width = rect.width; - sprite.texture._frame.height = rect.height; + sprite._texture._frame.width = rect.width; + sprite._texture._frame.height = rect.height; sprite.transform.worldTransform = this.transform.worldTransform; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index a24c775..b9f192d 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -19,11 +19,12 @@ */ export default class SystemRenderer extends EventEmitter { + // eslint-disable-next-line valid-jsdoc /** * @param {string} system - The name of the system this renderer is for. - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -37,27 +38,31 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(system, screenWidth, screenHeight, options) + constructor(system, options, arg2, arg3) { super(); sayHello(system); - // prepare options - if (options) + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') { - for (const i in settings.RENDER_OPTIONS) - { - if (typeof options[i] === 'undefined') - { - options[i] = settings.RENDER_OPTIONS[i]; - } - } + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); } - else - { - options = settings.RENDER_OPTIONS; - } + + // Add the default render options + options = Object.assign({}, settings.RENDER_OPTIONS, options); + + /** + * The supplied constructor options. + * + * @member {Object} + * @readOnly + */ + this.options = options; /** * The type of the renderer. @@ -75,7 +80,7 @@ * * @member {PIXI.Rectangle} */ - this.screen = new Rectangle(0, 0, screenWidth || 800, screenHeight || 600); + this.screen = new Rectangle(0, 0, options.width, options.height); /** * The canvas element that everything is drawn to @@ -279,6 +284,8 @@ this.blendModes = null; + this.options = null; + this.preserveDrawingBuffer = false; this.clearBeforeRender = false; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index e84e49c..691caf0 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -17,10 +17,11 @@ */ export default class CanvasRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -34,9 +35,9 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('Canvas', screenWidth, screenHeight, options); + super('Canvas', options, arg2, arg3); this.type = RENDERER_TYPE.CANVAS; @@ -96,7 +97,19 @@ this.context = null; this.renderingToScreen = false; - this.resize(screenWidth, screenHeight); + this.resize(this.options.width, this.options.height); + + /** + * Fired after rendering finishes. + * + * @event PIXI.CanvasRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.CanvasRenderer#prerender + */ } /** @@ -299,4 +312,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.CanvasRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.CanvasExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.CanvasPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.CanvasRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index e1cf7cd..f6fe123 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -31,11 +31,12 @@ */ export default class WebGLRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** * - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -55,9 +56,9 @@ * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('WebGL', screenWidth, screenHeight, options); + super('WebGL', options, arg2, arg3); /** * The type of this renderer as a standardised const @@ -89,12 +90,6 @@ }; - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ this._backgroundColorRgba[3] = this.transparent ? 0 : 1; this.globalUniforms = new UniformGroup({ @@ -117,6 +112,13 @@ this.initPlugins(); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ if(options.context) { this.context.initFromContext(options.context); @@ -169,6 +171,25 @@ } return this; + + /** + * Fired after rendering finishes. + * + * @event PIXI.WebGLRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.WebGLRenderer#prerender + */ + + /** + * Fired when the WebGL context is set. + * + * @event PIXI.WebGLRenderer#context + * @param {WebGLRenderingContext} gl - WebGL context. + */ } /** @@ -295,4 +316,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.WebGLRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.WebGLRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(WebGLRenderer); diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index 4eb6128..fa3d883 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -47,6 +47,14 @@ * @member {boolean} */ this.enabled = true; + + /** + * If enabled, pixi will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; } /** diff --git a/src/core/renderers/webgl/systems/ContextSystem.js b/src/core/renderers/webgl/systems/ContextSystem.js index ff958c7..8df85ea 100644 --- a/src/core/renderers/webgl/systems/ContextSystem.js +++ b/src/core/renderers/webgl/systems/ContextSystem.js @@ -75,7 +75,8 @@ */ createContext(canvas, options) { - var gl = canvas.getContext('webgl', options) || + var gl = //canvas.getContext('webgl2', options) || + canvas.getContext('webgl', options) || canvas.getContext('experimental-webgl', options); if (!gl) diff --git a/src/core/renderers/webgl/systems/FilterSystem.js b/src/core/renderers/webgl/systems/FilterSystem.js index f47335d..37e28d2 100644 --- a/src/core/renderers/webgl/systems/FilterSystem.js +++ b/src/core/renderers/webgl/systems/FilterSystem.js @@ -111,7 +111,7 @@ // TODO we should fit the rect around the transform.. } - else + else if (filters[0].autoFit) { sourceFrame.fit(filterData.stack[0].destinationFrame); } diff --git a/src/core/renderers/webgl/systems/shader/shader/mapType.js b/src/core/renderers/webgl/systems/shader/shader/mapType.js index 581dace..917538a 100644 --- a/src/core/renderers/webgl/systems/shader/shader/mapType.js +++ b/src/core/renderers/webgl/systems/shader/shader/mapType.js @@ -15,7 +15,7 @@ } } - return GL_TABLE[type]; + return GL_TABLE[type]; }; var GL_TABLE = null; diff --git a/src/core/settings.js b/src/core/settings.js index 18c5877..38ef45c 100644 --- a/src/core/settings.js +++ b/src/core/settings.js @@ -2,6 +2,15 @@ import canUploadSameBuffer from './utils/canUploadSameBuffer'; /** + * User's customizable globals for overriding the default PIXI settings, such + * as a renderer's default resolution, framerate, float percision, etc. + * @example + * // Use the native window resolution as the default resolution + * // will support high-density displays when rendering + * PIXI.settings.RESOLUTION = window.devicePixelRatio. + * + * // Disable interpolation when scaling, will make texture be pixelated + * PIXI.settings.SCALE_MODE = PIXI.SCALE_MODES.NEAREST; * @namespace PIXI.settings */ export default { @@ -101,6 +110,9 @@ * @property {boolean} clearBeforeRender=true * @property {boolean} preserveDrawingBuffer=false * @property {boolean} roundPixels=false + * @property {number} width=800 + * @property {number} height=600 + * @property {boolean} legacy=false */ RENDER_OPTIONS: { view: null, @@ -112,6 +124,9 @@ clearBeforeRender: true, preserveDrawingBuffer: false, roundPixels: false, + width: 800, + height: 600, + legacy: false, }, /** diff --git a/src/core/shader/Program.js b/src/core/shader/Program.js index c0b538b..07b4398 100644 --- a/src/core/shader/Program.js +++ b/src/core/shader/Program.js @@ -1,6 +1,6 @@ import extractUniformsFromSrc from './extractUniformsFromSrc'; import generateUniformsSync from './generateUniformsSync'; -import glCore from 'pixi-gl-core'; +import shaderUtils from '../renderers/webgl/systems/shader/shader'; import { ProgramCache } from '../utils'; import getTestContext from '../utils/getTestContext'; @@ -64,14 +64,17 @@ } else { - vertexSrc = glCore.shader.setPrecision(vertexSrc, 'mediump'); - fragmentSrc = glCore.shader.setPrecision(fragmentSrc, 'mediump'); + vertexSrc = shaderUtils.setPrecision(vertexSrc, 'mediump'); + fragmentSrc = shaderUtils.setPrecision(fragmentSrc, 'mediump'); - const program = glCore.shader.compileProgram(gl, vertexSrc, fragmentSrc); + const program = shaderUtils.compileProgram(gl, vertexSrc, fragmentSrc); + console.log("<<<<<<>>><<"); this.attributeData = this.getAttributeData(program, gl); this.uniformData = this.getUniformData(program, gl); + console.log(this.uniformData); + //gl.deleteProgram(program); } } @@ -95,13 +98,13 @@ for (let i = 0; i < totalAttributes; i++) { const attribData = gl.getActiveAttrib(program, i); - const type = glCore.shader.mapType(gl, attribData.type); + const type = shaderUtils.mapType(gl, attribData.type); /*eslint-disable */ const data = { type: type, name: attribData.name, - size: glCore.shader.mapSize(type), + size: shaderUtils.mapSize(type), location: 0, }; /*eslint-enable */ @@ -137,13 +140,14 @@ // TODO expose this as a prop? // const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); - const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); + //const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); for (let i = 0; i < totalUniforms; i++) { const uniformData = gl.getActiveUniform(program, i); const name = uniformData.name.replace(/\[.*?\]/, ''); - const type = glCore.shader.mapType(gl, uniformData.type); + const type = shaderUtils.mapType(gl, uniformData.type); + console.log('mapping ' + uniformData.type + ' to ' + type); // if (!name.match(maskRegex)) { @@ -151,7 +155,7 @@ uniforms[name] = { type: type, size: uniformData.size, - value: glCore.shader.defaultValue(type, uniformData.size), + value: shaderUtils.defaultValue(type, uniformData.size), }; /*eslint-enable */ } diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index e387e99..b57d095 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -147,12 +147,12 @@ // so if _width is 0 then width was not set.. if (this._width) { - this.scale.x = sign(this.scale.x) * this._width / this.texture.orig.width; + this.scale.x = sign(this.scale.x) * this._width / this._texture.orig.width; } if (this._height) { - this.scale.y = sign(this.scale.y) * this._height / this.texture.orig.height; + this.scale.y = sign(this.scale.y) * this._height / this._texture.orig.height; } } diff --git a/src/core/sprites/canvas/CanvasTinter.js b/src/core/sprites/canvas/CanvasTinter.js index 27e5e0c..fd9de5d 100644 --- a/src/core/sprites/canvas/CanvasTinter.js +++ b/src/core/sprites/canvas/CanvasTinter.js @@ -18,7 +18,7 @@ */ getTintedTexture: (sprite, color) => { - const texture = sprite.texture; + const texture = sprite._texture; color = CanvasTinter.roundColor(color); diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 5a4edda..8b9b797 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -344,6 +344,7 @@ const texture = this._texture; const style = this._style; + const padding = style.trim ? 0 : style.padding; texture.baseTexture.valid = true; texture.baseTexture.resolution = this.resolution; @@ -353,11 +354,11 @@ texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - texture.trim.x = -style.padding; - texture.trim.y = -style.padding; + texture.trim.x = -padding; + texture.trim.y = -padding; - texture.orig.width = texture._frame.width - (style.padding * 2); - texture.orig.height = texture._frame.height - (style.padding * 2); + texture.orig.width = texture._frame.width - (padding * 2); + texture.orig.height = texture._frame.height - (padding * 2); // call sprite onTextureUpdate to update scale if _width or _height were set this._onTextureUpdate(); diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index 853da3d..8a9c34b 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -114,6 +114,38 @@ this.cacheId = null; this.validate(); + + /** + * Fired when a not-immediately-available source finishes loading. + * + * @protected + * @event PIXI.BaseTexture#loaded + * @param {PIXI.BaseTexture} baseTexture - Resource loaded. + */ + + /** + * Fired when a not-immediately-available source fails to load. + * + * @protected + * @event PIXI.BaseTexture#error + * @param {PIXI.BaseTexture} baseTexture - Resource errored. + */ + + /** + * Fired when BaseTexture is updated. + * + * @protected + * @event PIXI.BaseTexture#update + * @param {PIXI.BaseTexture} baseTexture - Instance of texture being updated. + */ + + /** + * Fired when BaseTexture is destroyed. + * + * @protected + * @event PIXI.BaseTexture#dispose + * @param {PIXI.BaseTexture} baseTexture - Instance of texture being destroyed. + */ } setResource(resource) diff --git a/src/core/textures/Spritesheet.js b/src/core/textures/Spritesheet.js index d469c62..9b24f27 100644 --- a/src/core/textures/Spritesheet.js +++ b/src/core/textures/Spritesheet.js @@ -208,6 +208,7 @@ // lets also add the frame to pixi's global cache for fromFrame and fromImage functions TextureCache[i] = this.textures[i]; + this.textures[i].textureCacheId = i; } frameIndex++; diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 0a2c379..2213087 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -147,9 +147,9 @@ /** * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. * - * @event update - * @memberof PIXI.Texture# + * @event PIXI.Texture#update * @protected + * @param {PIXI.Texture} texture - Instance of texture being updated. */ this._updateID = 0; @@ -159,6 +159,15 @@ * @type {Object} */ this.transform = null; + + /** + * The id under which this Texture has been added to the texture cache. This is + * automatically set in certain cases, but may not always be accurate, particularly if + * the texture is in the cache under multiple ids. + * + * @member {string} + */ + this.textureCacheId = null; } /** @@ -225,11 +234,17 @@ this._uvs = null; this.trim = null; this.orig = null; + this.textureCacheId = null; this.valid = false; - this.off('dispose', this.dispose, this); - this.off('update', this.update, this); + for (const prop in TextureCache) + { + if (TextureCache[prop] === this) + { + delete TextureCache[prop]; + } + } } /** @@ -315,14 +330,15 @@ */ static fromLoader(source, imageUrl, name) { - // console.log('added from loader...') + // console.log('added from loader...') const resource = new ImageResource(source);//.from(imageUrl, crossorigin);// document.createElement('img'); - // console.log('base resource ' + resource.width); + // console.log('base resource ' + resource.width); const baseTexture = new BaseTexture(resource, settings.SCALE_MODE, getResolutionOfUrl(imageUrl)); + /// console.log('base width ' + baseTexture.width); const texture = new Texture(baseTexture); @@ -335,6 +351,7 @@ // lets also add the frame to pixi's global cache for fromFrame and fromImage fucntions BaseTextureCache[name] = baseTexture; TextureCache[name] = texture; + texture.textureCacheId = name; // also add references by url if they are different. if (name !== imageUrl) @@ -355,6 +372,10 @@ */ static addTextureToCache(texture, id) { + if (!texture.textureCacheId) + { + texture.textureCacheId = id; + } TextureCache[id] = texture; } @@ -495,6 +516,7 @@ */ Texture.EMPTY = new Texture(new BaseTexture()); removeAllHandlers(Texture.EMPTY); +removeAllHandlers(Texture.EMPTY.baseTexture); /** * A white texture of 10x10 size, used for graphics and other things @@ -505,3 +527,4 @@ */ Texture.WHITE = createWhiteTexture(); removeAllHandlers(Texture.WHITE); +removeAllHandlers(Texture.WHITE.baseTexture); diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index 59eb9fd..a9b9f51 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -1,6 +1,7 @@ import BaseTexture from './BaseTexture'; import { uid, BaseTextureCache } from '../utils'; -import * as ticker from '../ticker'; +import { shared } from '../ticker'; +import { UPDATE_PRIORITY } from '../const'; /** * A texture of a [playing] Video. @@ -125,7 +126,7 @@ if (!this._isAutoUpdating && this.autoUpdate) { - ticker.shared.add(this.update, this); + shared.add(this.update, this, UPDATE_PRIORITY.HIGH); this._isAutoUpdating = true; } } @@ -139,7 +140,7 @@ { if (this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); this._isAutoUpdating = false; } } @@ -187,7 +188,7 @@ { if (this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); } if (this.source && this.source._pixiId) @@ -281,12 +282,12 @@ if (!this._autoUpdate && this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); this._isAutoUpdating = false; } else if (this._autoUpdate && !this._isAutoUpdating) { - ticker.shared.add(this.update, this); + shared.add(this.update, this, UPDATE_PRIORITY.HIGH); this._isAutoUpdating = true; } } diff --git a/src/core/ticker/Ticker.js b/src/core/ticker/Ticker.js index 17a1517..40df138 100644 --- a/src/core/ticker/Ticker.js +++ b/src/core/ticker/Ticker.js @@ -1,12 +1,10 @@ import settings from '../settings'; -import EventEmitter from 'eventemitter3'; - -// Internal event used by composed emitter -const TICK = 'tick'; +import { UPDATE_PRIORITY } from '../const'; +import TickerListener from './TickerListener'; /** * A Ticker class that runs an update loop that other objects listen to. - * This class is composed around an EventEmitter object to add listeners + * This class is composed around listeners * meant for execution on the next requested animation frame. * Animation frames are requested only when necessary, * e.g. When the ticker is started and the emitter has listeners. @@ -22,10 +20,11 @@ constructor() { /** - * Internal emitter used to fire 'tick' event + * The first listener. All new listeners added are chained on this. * @private + * @type {TickerListener} */ - this._emitter = new EventEmitter(); + this._head = new TickerListener(null, null, Infinity); /** * Internal current frame request ID @@ -131,7 +130,7 @@ // Invoke listeners now this.update(time); // Listener side effects may have modified ticker state. - if (this.started && this._requestId === null && this._emitter.listeners(TICK, true)) + if (this.started && this._requestId === null && this._head.next) { this._requestId = requestAnimationFrame(this._tick); } @@ -148,7 +147,7 @@ */ _requestIfNeeded() { - if (this._requestId === null && this._emitter.listeners(TICK, true)) + if (this._requestId === null && this._head.next) { // ensure callbacks get correct delta this.lastTime = performance.now(); @@ -193,35 +192,72 @@ } /** - * Calls {@link module:eventemitter3.EventEmitter#on} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. + * Register a handler for tick events. Calls continuously unless + * it is removed or the ticker is stopped. * * @param {Function} fn - The listener function to be added for updates * @param {Function} [context] - The listener context + * @param {number} [priority=PIXI.UPDATE_PRIORITY.NORMAL] - The priority for emitting * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - add(fn, context) + add(fn, context, priority = UPDATE_PRIORITY.NORMAL) { - this._emitter.on(TICK, fn, context); - - this._startIfPossible(); - - return this; + return this._addListener(new TickerListener(fn, context, priority)); } /** - * Calls {@link module:eventemitter3.EventEmitter#once} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. + * Add a handler for the tick event which is only execute once. * * @param {Function} fn - The listener function to be added for one update * @param {Function} [context] - The listener context + * @param {number} [priority=PIXI.UPDATE_PRIORITY.NORMAL] - The priority for emitting * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - addOnce(fn, context) + addOnce(fn, context, priority = UPDATE_PRIORITY.NORMAL) { - this._emitter.once(TICK, fn, context); + return this._addListener(new TickerListener(fn, context, priority, true)); + } + + /** + * Internally adds the event handler so that it can be sorted by priority. + * Priority allows certain handler (user, AnimatedSprite, Interaction) to be run + * before the rendering. + * + * @private + * @param {TickerListener} listener - Current listener being added. + * @returns {PIXI.ticker.Ticker} This instance of a ticker + */ + _addListener(listener) + { + // For attaching to head + let current = this._head.next; + let previous = this._head; + + // Add the first item + if (!current) + { + listener.connect(previous); + } + else + { + // Go from highest to lowest priority + while (current) + { + if (listener.priority >= current.priority) + { + listener.connect(previous); + break; + } + previous = current; + current = current.next; + } + + // Not yet connected + if (!listener.previous) + { + listener.connect(previous); + } + } this._startIfPossible(); @@ -229,19 +265,33 @@ } /** - * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. - * It checks if the emitter has listeners for 'tick' event. - * If it does, then it cancels the animation frame. + * Removes any handlers matching the function and context parameters. + * If no handlers are left after removing, then it cancels the animation frame. * - * @param {Function} [fn] - The listener function to be removed + * @param {Function} fn - The listener function to be removed * @param {Function} [context] - The listener context to be removed * @returns {PIXI.ticker.Ticker} This instance of a ticker */ remove(fn, context) { - this._emitter.off(TICK, fn, context); + let listener = this._head.next; - if (!this._emitter.listeners(TICK, true)) + while (listener) + { + // We found a match, lets remove it + // no break to delete all possible matches + // incase a listener was added 2+ times + if (listener.match(fn, context)) + { + listener = listener.destroy(); + } + else + { + listener = listener.next; + } + } + + if (!this._head.next) { this._cancelIfNeeded(); } @@ -276,6 +326,25 @@ } /** + * Destroy the ticker and don't use after this. Calling + * this method removes all references to internal events. + */ + destroy() + { + this.stop(); + + let listener = this._head.next; + + while (listener) + { + listener = listener.destroy(true); + } + + this._head.destroy(); + this._head = null; + } + + /** * Triggers an update. An update entails setting the * current {@link PIXI.ticker.Ticker#elapsedMS}, * the current {@link PIXI.ticker.Ticker#deltaTime}, @@ -320,8 +389,22 @@ this.deltaTime = elapsedMS * settings.TARGET_FPMS * this.speed; + // Cache a local reference, in-case ticker is destroyed + // during the emit, we can still check for head.next + const head = this._head; + // Invoke listeners added to internal emitter - this._emitter.emit(TICK, this.deltaTime); + let listener = head.next; + + while (listener) + { + listener = listener.emit(this.deltaTime); + } + + if (!head.next) + { + this._cancelIfNeeded(); + } } else { diff --git a/src/core/ticker/TickerListener.js b/src/core/ticker/TickerListener.js new file mode 100644 index 0000000..2bedb34 --- /dev/null +++ b/src/core/ticker/TickerListener.js @@ -0,0 +1,158 @@ +/** + * Internal class for handling the priority sorting of ticker handlers. + * + * @private + * @class + * @memberof PIXI.ticker + */ +export default class TickerListener +{ + /** + * Constructor + * + * @param {Function} fn - The listener function to be added for one update + * @param {Function} [context=null] - The listener context + * @param {number} [priority=0] - The priority for emitting + * @param {boolean} [once=false] - If the handler should fire once + */ + constructor(fn, context = null, priority = 0, once = false) + { + /** + * The handler function to execute. + * @member {Function} + */ + this.fn = fn; + + /** + * The calling to execute. + * @member {Function} + */ + this.context = context; + + /** + * The current priority. + * @member {number} + */ + this.priority = priority; + + /** + * If this should only execute once. + * @member {boolean} + */ + this.once = once; + + /** + * The next item in chain. + * @member {TickerListener} + */ + this.next = null; + + /** + * The previous item in chain. + * @member {TickerListener} + */ + this.previous = null; + + /** + * `true` if this listener has been destroyed already. + * @member {boolean} + * @private + */ + this._destroyed = false; + } + + /** + * Simple compare function to figure out if a function and context match. + * + * @param {Function} fn - The listener function to be added for one update + * @param {Function} context - The listener context + * @return {boolean} `true` if the listener match the arguments + */ + match(fn, context) + { + context = context || null; + + return this.fn === fn && this.context === context; + } + + /** + * Emit by calling the current function. + * @param {number} deltaTime - time since the last emit. + * @return {TickerListener} Next ticker + */ + emit(deltaTime) + { + if (this.context) + { + this.fn.call(this.context, deltaTime); + } + else + { + this.fn(deltaTime); + } + + if (this.once) + { + this.destroy(); + } + + const redirect = this.next; + + // Soft-destroying should remove + // the next reference + if (this._destroyed) + { + this.next = null; + } + + return redirect; + } + + /** + * Connect to the list. + * @param {TickerListener} previous - Input node, previous listener + */ + connect(previous) + { + this.previous = previous; + if (previous.next) + { + previous.next.previous = this; + } + this.next = previous.next; + previous.next = this; + } + + /** + * Destroy and don't use after this. + * @param {boolean} [hard = false] `true` to remove the `next` reference, this + * is considered a hard destroy. Soft destroy maintains the next reference. + * @return {TickerListener} The listener to redirect while emitting or removing. + */ + destroy(hard = false) + { + this._destroyed = true; + this.fn = null; + this.context = null; + + // Disconnect, hook up next and previous + if (this.previous) + { + this.previous.next = this.next; + } + + if (this.next) + { + this.next.previous = this.previous; + } + + // Redirect to the next item + const redirect = this.previous; + + // Remove references + this.next = hard ? null : redirect; + this.previous = null; + + return redirect; + } +} diff --git a/src/core/ticker/index.js b/src/core/ticker/index.js index 4b3017c..c3020d4 100644 --- a/src/core/ticker/index.js +++ b/src/core/ticker/index.js @@ -45,8 +45,25 @@ const shared = new Ticker(); shared.autoStart = true; +shared.destroy = () => +{ + // protect destroying shared ticker + // this is used by other internal systems + // like AnimatedSprite and InteractionManager +}; /** + * This namespace contains an API for interacting with PIXI's internal global update loop. + * + * This ticker is used for rendering, {@link PIXI.extras.AnimatedSprite AnimatedSprite}, + * {@link PIXI.interaction.InteractionManager InteractionManager} and many other time-based PIXI systems. + * @example + * const ticker = new PIXI.ticker.Ticker(); + * ticker.stop(); + * ticker.add((deltaTime) => { + * // do something every frame + * }); + * ticker.start(); * @namespace PIXI.ticker */ export { shared, Ticker }; diff --git a/src/core/utils/index.js b/src/core/utils/index.js index be44750..7441f7b 100644 --- a/src/core/utils/index.js +++ b/src/core/utils/index.js @@ -9,6 +9,21 @@ let saidHello = false; /** + * Generalized convenience utilities for PIXI. + * @example + * // Extend PIXI's internal Event Emitter. + * class MyEmitter extends PIXI.utils.EventEmitter { + * constructor() { + * super(); + * console.log("Emitter created!"); + * } + * } + * + * // Get info on current device + * console.log(PIXI.utils.isMobile); + * + * // Convert hex color to string + * console.log(PIXI.utils.hex2string(0xff00ff)); // returns: "#ff00ff" * @namespace PIXI.utils */ export { diff --git a/src/deprecation.js b/src/deprecation.js index b9c73b3..33daeec 100644 --- a/src/deprecation.js +++ b/src/deprecation.js @@ -918,6 +918,35 @@ }); /** + * @method + * @name PIXI.prepare.BasePrepare#register + * @see PIXI.prepare.BasePrepare#registerFindHook + * @deprecated since version 4.4.2 + * @param {Function} [addHook] - Function call that takes two parameters: `item:*, queue:Array` + * function must return `true` if it was able to add item to the queue. + * @param {Function} [uploadHook] - Function call that takes two parameters: `prepare:CanvasPrepare, item:*` and + * function must return `true` if it was able to handle upload of item. + * @return {PIXI.BasePrepare} Instance of plugin for chaining. + */ +prepare.BasePrepare.prototype.register = function register(addHook, uploadHook) +{ + warn('renderer.plugins.prepare.register is now deprecated, ' + + 'please use renderer.plugins.prepare.registerFindHook & renderer.plugins.prepare.registerUploadHook'); + + if (addHook) + { + this.registerFindHook(addHook); + } + + if (uploadHook) + { + this.registerUploadHook(uploadHook); + } + + return this; +}; + +/** * The number of graphics or textures to upload to the GPU. * * @name PIXI.prepare.canvas.UPLOADS_PER_FRAME @@ -969,67 +998,73 @@ }, }); -Object.defineProperties(loaders.Resource.prototype, { - isJson: { - get() - { - warn('The isJson property is deprecated, please use `resource.type === Resource.TYPE.JSON`.'); +if (loaders.Loader) +{ + const Resource = loaders.Resource; + const Loader = loaders.Loader; - return this.type === loaders.Loader.Resource.TYPE.JSON; + Object.defineProperties(Resource.prototype, { + isJson: { + get() + { + warn('The isJson property is deprecated, please use `resource.type === Resource.TYPE.JSON`.'); + + return this.type === Resource.TYPE.JSON; + }, }, - }, - isXml: { - get() - { - warn('The isXml property is deprecated, please use `resource.type === Resource.TYPE.XML`.'); + isXml: { + get() + { + warn('The isXml property is deprecated, please use `resource.type === Resource.TYPE.XML`.'); - return this.type === loaders.Loader.Resource.TYPE.XML; + return this.type === Resource.TYPE.XML; + }, }, - }, - isImage: { - get() - { - warn('The isImage property is deprecated, please use `resource.type === Resource.TYPE.IMAGE`.'); + isImage: { + get() + { + warn('The isImage property is deprecated, please use `resource.type === Resource.TYPE.IMAGE`.'); - return this.type === loaders.Loader.Resource.TYPE.IMAGE; + return this.type === Resource.TYPE.IMAGE; + }, }, - }, - isAudio: { - get() - { - warn('The isAudio property is deprecated, please use `resource.type === Resource.TYPE.AUDIO`.'); + isAudio: { + get() + { + warn('The isAudio property is deprecated, please use `resource.type === Resource.TYPE.AUDIO`.'); - return this.type === loaders.Loader.Resource.TYPE.AUDIO; + return this.type === Resource.TYPE.AUDIO; + }, }, - }, - isVideo: { - get() - { - warn('The isVideo property is deprecated, please use `resource.type === Resource.TYPE.VIDEO`.'); + isVideo: { + get() + { + warn('The isVideo property is deprecated, please use `resource.type === Resource.TYPE.VIDEO`.'); - return this.type === loaders.Loader.Resource.TYPE.VIDEO; + return this.type === Resource.TYPE.VIDEO; + }, }, - }, -}); + }); -Object.defineProperties(loaders.Loader.prototype, { - before: { - get() - { - warn('The before() method is deprecated, please use pre().'); + Object.defineProperties(Loader.prototype, { + before: { + get() + { + warn('The before() method is deprecated, please use pre().'); - return this.pre; + return this.pre; + }, }, - }, - after: { - get() - { - warn('The after() method is deprecated, please use use().'); + after: { + get() + { + warn('The after() method is deprecated, please use use().'); - return this.use; + return this.use; + }, }, - }, -}); + }); +} /** * @name PIXI.interaction.interactiveTarget#defaultCursor diff --git a/src/extract/canvas/CanvasExtract.js b/src/extract/canvas/CanvasExtract.js index 941ca05..3ac5aee 100644 --- a/src/extract/canvas/CanvasExtract.js +++ b/src/extract/canvas/CanvasExtract.js @@ -8,7 +8,7 @@ * An instance of this class is automatically created by default, and can be found at renderer.plugins.extract * * @class - * @memberof PIXI + * @memberof PIXI.extract */ export default class CanvasExtract { @@ -21,9 +21,9 @@ /** * Collection of methods for extracting data (image, pixels, etc.) from a display object or render texture * - * @member {PIXI.CanvasExtract} extract + * @member {PIXI.extract.CanvasExtract} extract * @memberof PIXI.CanvasRenderer# - * @see PIXI.CanvasExtract + * @see PIXI.extract.CanvasExtract */ renderer.extract = this; } diff --git a/src/extract/index.js b/src/extract/index.js index 0f2170d..e9478c1 100644 --- a/src/extract/index.js +++ b/src/extract/index.js @@ -1,2 +1,22 @@ +/** + * This namespace provides renderer-specific plugins for exporting content from a renderer. + * For instance, these plugins can be used for saving an Image, Canvas element or for exporting the raw image data (pixels). + * + * Do not instantiate these plugins directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. + * @example + * // Create a new app (will auto-add extract plugin to renderer) + * const app = new PIXI.Application(); + * + * // Draw a red circle + * const graphics = new PIXI.Graphics() + * .beginFill(0xFF0000) + * .drawCircle(0, 0, 50); + * + * // Render the graphics as an HTMLImageElement + * const image = app.renderer.plugins.extract.image(graphics); + * document.body.appendChild(image); + * @namespace PIXI.extract + */ export { default as webgl } from './webgl/WebGLExtract'; export { default as canvas } from './canvas/CanvasExtract'; diff --git a/src/extract/webgl/WebGLExtract.js b/src/extract/webgl/WebGLExtract.js index 60228bb..1c3dd30 100644 --- a/src/extract/webgl/WebGLExtract.js +++ b/src/extract/webgl/WebGLExtract.js @@ -9,7 +9,7 @@ * An instance of this class is automatically created by default, and can be found at renderer.plugins.extract * * @class - * @memberof PIXI + * @memberof PIXI.extract */ export default class WebGLExtract { @@ -22,9 +22,9 @@ /** * Collection of methods for extracting data (image, pixels, etc.) from a display object or render texture * - * @member {PIXI.WebGLExtract} extract + * @member {PIXI.extract.WebGLExtract} extract * @memberof PIXI.WebGLRenderer# - * @see PIXI.WebGLExtract + * @see PIXI.extract.WebGLExtract */ renderer.extract = this; } diff --git a/src/extras/AnimatedSprite.js b/src/extras/AnimatedSprite.js index 13b804f..5297778 100644 --- a/src/extras/AnimatedSprite.js +++ b/src/extras/AnimatedSprite.js @@ -137,7 +137,7 @@ this.playing = true; if (this._autoUpdate) { - core.ticker.shared.add(this.update, this); + core.ticker.shared.add(this.update, this, core.UPDATE_PRIORITY.HIGH); } } diff --git a/src/extras/TextureTransform.js b/src/extras/TextureTransform.js index 78e6e40..16536d2 100644 --- a/src/extras/TextureTransform.js +++ b/src/extras/TextureTransform.js @@ -65,8 +65,36 @@ } /** + * Multiplies uvs array to transform + * @param {Float32Array} uvs mesh uvs + * @param {Float32Array} [out=uvs] output + * @returns {Float32Array} output + */ + multiplyUvs(uvs, out) + { + if (out === undefined) + { + out = uvs; + } + + const mat = this.mapCoord; + + for (let i = 0; i < uvs.length; i += 2) + { + const x = uvs[i]; + const y = uvs[i + 1]; + + out[i] = (x * mat.a) + (y * mat.c) + mat.tx; + out[i + 1] = (x * mat.b) + (y * mat.d) + mat.ty; + } + + return out; + } + + /** * updates matrices if texture was changed * @param {boolean} forceUpdate if true, matrices will be updated any case + * @returns {boolean} whether or not it was updated */ update(forceUpdate) { @@ -74,13 +102,13 @@ if (!tex || !tex.valid) { - return; + return false; } if (!forceUpdate && this._lastTextureID === tex._updateID) { - return; + return false; } this._lastTextureID = tex._updateID; @@ -110,5 +138,7 @@ frame[3] = (tex._frame.y + tex._frame.height - margin + offset) / texBase.height; this.uClampOffset[0] = offset / texBase.realWidth; this.uClampOffset[1] = offset / texBase.realHeight; + + return true; } } diff --git a/src/extras/TilingSprite.js b/src/extras/TilingSprite.js index 1f6014f..a3f027f 100644 --- a/src/extras/TilingSprite.js +++ b/src/extras/TilingSprite.js @@ -180,9 +180,9 @@ const transform = this.worldTransform; const resolution = renderer.resolution; const baseTexture = texture.baseTexture; - const baseTextureResolution = texture.baseTexture.resolution; - const modX = (this.tilePosition.x / this.tileScale.x) % texture._frame.width; - const modY = (this.tilePosition.y / this.tileScale.y) % texture._frame.height; + const baseTextureResolution = baseTexture.resolution; + const modX = ((this.tilePosition.x / this.tileScale.x) % texture._frame.width) * baseTextureResolution; + const modY = ((this.tilePosition.y / this.tileScale.y) % texture._frame.height) * baseTextureResolution; // create a nice shiny pattern! // TODO this needs to be refreshed if texture changes.. diff --git a/src/extras/cacheAsBitmap.js b/src/extras/cacheAsBitmap.js index 5dd5640..aea06a8 100644 --- a/src/extras/cacheAsBitmap.js +++ b/src/extras/cacheAsBitmap.js @@ -227,7 +227,16 @@ this.transform._parentID = -1; // restore the transform of the cached sprite to avoid the nasty flicker.. - this.updateTransform(); + if (!this.parent) + { + this.parent = renderer._tempDisplayObjectParent; + this.updateTransform(); + this.parent = null; + } + else + { + this.updateTransform(); + } // map the hit test.. this.containsPoint = cachedSprite.containsPoint.bind(cachedSprite); @@ -314,7 +323,17 @@ cachedSprite._bounds = this._bounds; cachedSprite.alpha = cacheAlpha; - this.updateTransform(); + if (!this.parent) + { + this.parent = renderer._tempDisplayObjectParent; + this.updateTransform(); + this.parent = null; + } + else + { + this.updateTransform(); + } + this.updateTransform = this.displayObjectUpdateTransform; this._cacheData.sprite = cachedSprite; @@ -358,9 +377,12 @@ * Destroys the cached object. * * @private + * @param {object|boolean} [options] - Options parameter. A boolean will act as if all options + * have been set to that value. + * Used when destroying containers, see the Container.destroy method. */ -DisplayObject.prototype._cacheAsBitmapDestroy = function _cacheAsBitmapDestroy() +DisplayObject.prototype._cacheAsBitmapDestroy = function _cacheAsBitmapDestroy(options) { this.cacheAsBitmap = false; - this.destroy(); + this.destroy(options); }; diff --git a/src/extras/index.js b/src/extras/index.js index bbd8bbf..a34d7ec 100644 --- a/src/extras/index.js +++ b/src/extras/index.js @@ -1,7 +1,7 @@ /** + * Additional PIXI DisplayObjects for animation, tiling and bitmap text. * @namespace PIXI.extras */ -export { default as TextureTransform } from './TextureTransform'; export { default as AnimatedSprite } from './AnimatedSprite'; export { default as TilingSprite } from './TilingSprite'; export { default as TilingSpriteRenderer } from './webgl/TilingSpriteRenderer'; diff --git a/src/extras/webgl/TilingSpriteRenderer.js b/src/extras/webgl/TilingSpriteRenderer.js index aeec172..eee3b5c 100644 --- a/src/extras/webgl/TilingSpriteRenderer.js +++ b/src/extras/webgl/TilingSpriteRenderer.js @@ -11,7 +11,7 @@ * WebGL renderer plugin for tiling sprites * * @class - * @memberof PIXI + * @memberof PIXI.extras * @extends PIXI.ObjectRenderer */ export default class TilingSpriteRenderer extends core.ObjectRenderer @@ -118,7 +118,7 @@ tempMat.invert(); if (isSimple) { - tempMat.append(uv.mapCoord); + tempMat.prepend(uv.mapCoord); } else { diff --git a/src/filters/displacement/DisplacementFilter.js b/src/filters/displacement/DisplacementFilter.js index d5355c3..b5d030c 100644 --- a/src/filters/displacement/DisplacementFilter.js +++ b/src/filters/displacement/DisplacementFilter.js @@ -35,7 +35,7 @@ this.maskSprite = sprite; this.maskMatrix = maskMatrix; - this.uniforms.mapSampler = sprite.texture; + this.uniforms.mapSampler = sprite._texture; this.uniforms.filterMatrix = maskMatrix; this.uniforms.scale = { x: 1, y: 1 }; diff --git a/src/filters/index.js b/src/filters/index.js index f3e8862..7ceb50a 100644 --- a/src/filters/index.js +++ b/src/filters/index.js @@ -1,4 +1,21 @@ /** + * This namespace contains WebGL-only display filters that can be applied + * to DisplayObjects using the {@link PIXI.DisplayObject#filters filters} property. + * @example + * // Create a new application + * const app = new PIXI.Application(); + * + * // Draw a green rectangle + * const rect = new PIXI.Graphics() + * .beginFill(0x00ff00) + * .drawRect(40, 40, 200, 200); + * + * // Add a blur filter + * rect.filters = [new PIXI.filters.BlurFilter()]; + * + * // Display rectangle + * app.stage.addChild(rect); + * document.body.appendChild(app.view); * @namespace PIXI.filters */ export { default as FXAAFilter } from './fxaa/FXAAFilter'; diff --git a/src/index.js b/src/index.js index c553427..77da490 100644 --- a/src/index.js +++ b/src/index.js @@ -20,6 +20,14 @@ import { utils } from './core'; utils.mixins.performMixins(); +/** + * Alias for {@link PIXI.loaders.shared}. + * @name loader + * @memberof PIXI + * @type {PIXI.loader.Loader} + */ +const loader = loaders.shared || null; + export { accessibility, extract, @@ -30,18 +38,8 @@ mesh, particles, prepare, + loader, }; -/** - * A premade instance of the loader that can be used to load resources. - * - * @name loader - * @memberof PIXI - * @property {PIXI.loaders.Loader} - */ -const loader = loaders && loaders.Loader ? new loaders.Loader() : null; // check is there in case user excludes loader lib - -export { loader }; - // Always export pixi globally. global.PIXI = exports; // eslint-disable-line diff --git a/src/interaction/InteractionData.js b/src/interaction/InteractionData.js index 02d53a3..449f8cc 100644 --- a/src/interaction/InteractionData.js +++ b/src/interaction/InteractionData.js @@ -30,7 +30,10 @@ /** * When passed to an event handler, this will be the original DOM Event that was captured * - * @member {Event} + * @see https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent + * @see https://developer.mozilla.org/en-US/docs/Web/API/TouchEvent + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent + * @member {MouseEvent|TouchEvent|PointerEvent} */ this.originalEvent = null; } diff --git a/src/interaction/InteractionManager.js b/src/interaction/InteractionManager.js index 8baf13a..2d75c7c 100644 --- a/src/interaction/InteractionManager.js +++ b/src/interaction/InteractionManager.js @@ -13,6 +13,14 @@ const MOUSE_POINTER_ID = 'MOUSE'; +// helpers for hitTest() - only used inside hitTest() +const hitTestEvent = { + target: null, + data: { + global: null, + }, +}; + /** * The interaction manager deals with mouse, touch and pointer events. Any DisplayObject can be interactive * if its interactive parameter is set to true @@ -245,67 +253,60 @@ this.setTargetElement(this.renderer.view, this.renderer.resolution); /** - * Fired when a pointer device button (usually a mouse button) is pressed on the display + * Fired when a pointer device button (usually a mouse left-button) is pressed on the display * object. * - * @event mousedown - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mousedown + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device secondary button (usually a mouse right-button) is pressed * on the display object. * - * @event rightdown - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightdown + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** - * Fired when a pointer device button (usually a mouse button) is released over the display + * Fired when a pointer device button (usually a mouse left-button) is released over the display * object. * - * @event mouseup - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseup + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device secondary button (usually a mouse right-button) is released * over the display object. * - * @event rightup - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightup + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** - * Fired when a pointer device button (usually a mouse button) is pressed and released on + * Fired when a pointer device button (usually a mouse left-button) is pressed and released on * the display object. * - * @event click - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#click + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device secondary button (usually a mouse right-button) is pressed * and released on the display object. * - * @event rightclick - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightclick + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** - * Fired when a pointer device button (usually a mouse button) is released outside the + * Fired when a pointer device button (usually a mouse left-button) is released outside the * display object that initially registered a * [mousedown]{@link PIXI.interaction.InteractionManager#event:mousedown}. * - * @event mouseupoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** @@ -313,146 +314,362 @@ * outside the display object that initially registered a * [rightdown]{@link PIXI.interaction.InteractionManager#event:rightdown}. * - * @event rightupoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device (usually a mouse) is moved while over the display object * - * @event mousemove - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mousemove + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device (usually a mouse) is moved onto the display object * - * @event mouseover - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseover + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device (usually a mouse) is moved off the display object * - * @event mouseout - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseout + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is pressed on the display object. * - * @event pointerdown - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerdown + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is released over the display object. * - * @event pointerup - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerup + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when the operating system cancels a pointer event * - * @event pointercancel - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointercancel + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is pressed and released on the display object. * - * @event pointertap - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointertap + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is released outside the display object that initially * registered a [pointerdown]{@link PIXI.interaction.InteractionManager#event:pointerdown}. * - * @event pointerupoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device is moved while over the display object * - * @event pointermove - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointermove + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device is moved onto the display object * - * @event pointerover - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerover + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device is moved off the display object * - * @event pointerout - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerout + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is placed on the display object. * - * @event touchstart - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchstart + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is removed from the display object. * - * @event touchend - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchend + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when the operating system cancels a touch * - * @event touchcancel - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchcancel + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is placed and removed from the display object. * - * @event tap - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#tap + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is removed outside of the display object that initially * registered a [touchstart]{@link PIXI.interaction.InteractionManager#event:touchstart}. * - * @event touchendoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchendoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is moved along the display object. * - * @event touchmove - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchmove + * @param {PIXI.interaction.InteractionData} event - Interaction data */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is pressed on the display. + * object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mousedown + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is pressed + * on the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightdown + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is released over the display + * object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseup + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is released + * over the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightup + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is pressed and released on + * the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#click + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is pressed + * and released on the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightclick + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is released outside the + * display object that initially registered a + * [mousedown]{@link PIXI.DisplayObject#event:mousedown}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is released + * outside the display object that initially registered a + * [rightdown]{@link PIXI.DisplayObject#event:rightdown}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device (usually a mouse) is moved while over the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mousemove + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device (usually a mouse) is moved onto the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseover + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device (usually a mouse) is moved off the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseout + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is pressed on the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerdown + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is released over the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerup + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when the operating system cancels a pointer event. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointercancel + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is pressed and released on the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointertap + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is released outside the display object that initially + * registered a [pointerdown]{@link PIXI.DisplayObject#event:pointerdown}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device is moved while over the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointermove + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device is moved onto the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerover + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device is moved off the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerout + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is placed on the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchstart + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is removed from the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchend + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when the operating system cancels a touch. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchcancel + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is placed and removed from the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#tap + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is removed outside of the display object that initially + * registered a [touchstart]{@link PIXI.DisplayObject#event:touchstart}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchendoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is moved along the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchmove + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + } + + /** + * Hit tests a point against the display tree, returning the first interactive object that is hit. + * + * @param {PIXI.Point} globalPoint - A point to hit test with, in global space. + * @param {PIXI.Container} [root] - The root display object to start from. If omitted, defaults + * to the last rendered root of the associated renderer. + * @return {PIXI.DisplayObject} The hit display object, if any. + */ + hitTest(globalPoint, root) + { + // clear the target for our hit test + hitTestEvent.target = null; + // assign the global point + hitTestEvent.data.global = globalPoint; + // ensure safety of the root + if (!root) + { + root = this.renderer._lastObjectRendered; + } + // run the hit test + this.processInteractive(hitTestEvent, root, null, true); + // return our found object - it'll be null if we didn't hit anything + + return hitTestEvent.target; } /** @@ -487,7 +704,7 @@ return; } - core.ticker.shared.add(this.update, this); + core.ticker.shared.add(this.update, this, core.UPDATE_PRIORITY.INTERACTION); if (window.navigator.msPointerEnabled) { @@ -804,8 +1021,6 @@ } } - let keepHitTestingAfterChildren = hitTest; - // ** FREE TIP **! If an object is not interactive or has no buttons in it // (such as a game scene!) set interactiveChildren to false for that displayObject. // This will allow pixi to completely ignore and bypass checking the displayObjects children. @@ -818,7 +1033,9 @@ const child = children[i]; // time to get recursive.. if this function will return if something is hit.. - if (this.processInteractive(interactionEvent, child, func, hitTest, interactiveParent)) + const childHit = this.processInteractive(interactionEvent, child, func, hitTest, interactiveParent); + + if (childHit) { // its a good idea to check if a child has lost its parent. // this means it has been removed whilst looping so its best @@ -827,8 +1044,6 @@ continue; } - hit = true; - // we no longer need to hit test any more objects in this container as we we // now know the parent has been hit interactiveParent = false; @@ -838,36 +1053,41 @@ // This means we no longer need to hit test anything else. We still need to run // through all objects, but we don't need to perform any hit tests. - keepHitTestingAfterChildren = false; - - if (child.interactive) + if (childHit) { - hitTest = false; + if (interactionEvent.target) + { + hitTest = false; + } + hit = true; } - - // we can break now as we have hit an object. } } } - hitTest = keepHitTestingAfterChildren; - // no point running this if the item is not interactive or does not have an interactive parent. if (interactive) { // if we are hit testing (as in we have no hit any objects yet) // We also don't need to worry about hit testing if once of the displayObjects children - // has already been hit! - if (hitTest && !hit) + // has already been hit - but only if it was interactive, otherwise we need to keep + // looking for an interactive child, just in case we hit one + if (hitTest && !interactionEvent.target) { if (displayObject.hitArea) { displayObject.worldTransform.applyInverse(point, this._tempPoint); - hit = displayObject.hitArea.contains(this._tempPoint.x, this._tempPoint.y); + if (displayObject.hitArea.contains(this._tempPoint.x, this._tempPoint.y)) + { + hit = true; + } } else if (displayObject.containsPoint) { - hit = displayObject.containsPoint(point); + if (displayObject.containsPoint(point)) + { + hit = true; + } } } @@ -878,7 +1098,10 @@ interactionEvent.target = displayObject; } - func(interactionEvent, displayObject, hit); + if (func) + { + func(interactionEvent, displayObject, !!hit); + } } } @@ -914,7 +1137,7 @@ { const event = events[i]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -998,7 +1221,7 @@ { const event = events[i]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1098,7 +1321,7 @@ const test = isRightButton ? flags.RIGHT_DOWN : flags.LEFT_DOWN; - const isDown = trackingData !== undefined && (trackingData.flags | test); + const isDown = trackingData !== undefined && (trackingData.flags & test); if (hit) { @@ -1118,11 +1341,11 @@ { if (isRightButton) { - trackingData.rightDown = hit; + trackingData.rightDown = false; } else { - trackingData.leftDown = hit; + trackingData.leftDown = false; } } } @@ -1180,7 +1403,7 @@ { const event = events[i]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1255,7 +1478,7 @@ this.setCursorMode(null); } - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1268,6 +1491,12 @@ { this.emit('mouseout', interactionEvent); } + else + { + // we can get touchleave events after touchend, so we want to make sure we don't + // introduce memory leaks + this.releaseInteractionDataForPointerId(interactionData.identifier); + } } /** @@ -1344,7 +1573,7 @@ // Only mouse and pointer can call onPointerOver, so events will always be length 1 const event = events[0]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1366,12 +1595,14 @@ * Get InteractionData for a given pointerId. Store that data as well * * @private - * @param {number} pointerId - Identifier from a pointer event + * @param {PointerEvent} event - Normalized pointer event, output from normalizeToPointerData * @return {InteractionData} - Interaction data for the given pointer identifier */ - getInteractionDataForPointerId(pointerId) + getInteractionDataForPointerId(event) { - if (pointerId === MOUSE_POINTER_ID) + const pointerId = event.pointerId; + + if (pointerId === MOUSE_POINTER_ID || event.pointerType === 'mouse') { return this.mouse; } diff --git a/src/interaction/InteractionTrackingData.js b/src/interaction/InteractionTrackingData.js index 6a1f69e..8493b63 100644 --- a/src/interaction/InteractionTrackingData.js +++ b/src/interaction/InteractionTrackingData.js @@ -35,8 +35,10 @@ } /** + * Unique pointer id of the event + * * @readonly - * @type {number} Unique pointer id of the event + * @member {number} */ get pointerId() { diff --git a/.travis.yml b/.travis.yml index 3df8b22..14de334 100644 --- a/.travis.yml +++ b/.travis.yml @@ -110,3 +110,43 @@ on: all_branches: true condition: $TRAVIS_TAG + # Deploy config for latest release + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: dist + upload-dir: "release" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: docs + upload-dir: "release/docs" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: coverage + upload-dir: "release/coverage" + on: + all_branches: true + condition: $TRAVIS_TAG \ No newline at end of file diff --git a/README.md b/README.md index d26fadd..3b85f85 100644 --- a/README.md +++ b/README.md @@ -36,22 +36,14 @@ - Wiki: Other misc tutorials and resources are [on the Wiki](https://github.com/pixijs/pixi.js/wiki/Resources). ### Community ### -- Forums: Check out the [forum] (http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow] (http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. +- Forums: Check out the [forum](http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow](http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. - Inspiration: Check out the [gallery](http://www.pixijs.com/gallery) to see some of the amazing things people have created! - Chat: You can join us on [Gitter](https://gitter.im/pixijs/pixi.js) To chat about Pixi. We also now have a Slack channel. If you would like to join it please Send me an email (mat@goodboydigital.com) and I will invite you in. ### Setup ### -It's easy to get started with Pixi.js! Simply grab the pre-built versions from here: - -Release Branch - Nice and stable Pixi.js -- Unminified: [http://pixijs.download/release/pixi.js] -- Minified: [http://pixijs.download/release/pixi.min.js] - -Develop Branch - The bleeding edge version of Pixi.js -- Unminified: [http://pixijs.download/dev/pixi.js] -- Minified: [http://pixijs.download/dev/pixi.min.js] +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. @@ -70,10 +62,10 @@ #### CDN Install (via cdnjs) ```html - + ``` -_Note: `4.2.2` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ +_Note: `4.4.3` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ ### Demos ### @@ -130,7 +122,7 @@ // and the root stage PIXI.Container. var app = new PIXI.Application(); -// The application will create a canvas element for you that you +// The application will create a canvas element for you that you // can then insert into the DOM. document.body.appendChild(app.view); @@ -150,7 +142,7 @@ // Add the bunny to the scene we are building. app.stage.addChild(bunny); - + // Listen for frame updates app.ticker.add(function() { // each frame we spin the bunny around a bit diff --git a/bower.json b/bower.json deleted file mode 100644 index 41b668c..0000000 --- a/bower.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "pixi.js", - "main": "dist/pixi.min.js", - "ignore": [ - "**/.*", - "docs", - "node_modules", - "bower_components", - "test", - "scripts", - "bower.json", - "inch.json" - ], - "dependencies": { - }, - "devDependencies": { - } -} diff --git a/package.json b/package.json index d35cbb2..308469f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.3.5", + "version": "4.4.4", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ @@ -40,11 +40,11 @@ "lib": "babel src --out-dir lib -s", "predocs": "rimraf docs/**", "docs": "jsdoc -c scripts/jsdoc.conf.json -R README.md", - "publish:patch": "npm version patch --no-git-tag-version && npm publish", - "publish:minor": "npm version minor --no-git-tag-version && npm publish", - "publish:major": "npm version major --no-git-tag-version && npm publish", + "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", - "postpublish": "node scripts/release.js" + "postpublish": "git push && git push --tags" }, "files": [ "dist/", @@ -72,7 +72,6 @@ "electron": "^1.4.15", "eslint": "^3.5.0", "floss": "^2.0.1", - "gh-pages": "^0.11.0", "jaguarjs-jsdoc": "^1.0.1", "js-md5": "^0.4.1", "jsdoc": "^3.4.2", diff --git a/scripts/jsdoc.conf.json b/scripts/jsdoc.conf.json index 553e61c..3f52180 100644 --- a/scripts/jsdoc.conf.json +++ b/scripts/jsdoc.conf.json @@ -43,7 +43,7 @@ }, "markdown" : { "parser" : "gfm", - "hardwrap" : true + "hardwrap" : false }, "opts": { "encoding" : "utf8", diff --git a/scripts/release.js b/scripts/release.js deleted file mode 100644 index 156620c..0000000 --- a/scripts/release.js +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env node -'use strict'; - -// Publish script to push releases of the bin files -// the normally are gitignored -const ghpages = require('gh-pages'); -const path = require('path'); -const packageInfo = require(path.join(__dirname, '..', 'package.json')); - -const options = { - src: [ - 'dist/**/*', - 'lib/**/*', - 'src/**/*', - 'scripts/*', - 'scripts/renders/*', - 'test/**/*', - '*.json', - '*.md', - 'LICENSE', - '.eslintrc', - '.editorconfig', - '.travis.yml', - '.babelrc', - ], - dotfiles: true, - branch: 'release', - message: packageInfo.version, - logger: console.log.bind(console), -}; - -ghpages.publish(process.cwd(), options, (err) => -{ - if (err) - { - console.log(err); - process.exit(1); - - return; - } - - process.exit(0); -}); diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index f16bcb1..220427a 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -21,7 +21,7 @@ const DIV_HOOK_ZINDEX = 2; /** - * The Accessibility manager reacreates the ability to tab and and have content read by screen + * The Accessibility manager recreates the ability to tab and and have content read by screen * readers. This is very important as it can possibly help people with disabilities access pixi * content. * diff --git a/src/accessibility/index.js b/src/accessibility/index.js index 568cfb1..0ae108f 100644 --- a/src/accessibility/index.js +++ b/src/accessibility/index.js @@ -1,4 +1,9 @@ /** + * This namespace contains a renderer plugin for interaction accessibility for end-users + * with physical impairments which require screen-renders, keyboard navigation, etc. + * + * Do not instantiate this plugin directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. * @namespace PIXI.accessibility */ export { default as accessibleTarget } from './accessibleTarget'; diff --git a/src/core/Application.js b/src/core/Application.js index 5340578..2fcc729 100644 --- a/src/core/Application.js +++ b/src/core/Application.js @@ -1,6 +1,8 @@ import { autoDetectRenderer } from './autoDetectRenderer'; import Container from './display/Container'; import { shared, Ticker } from './ticker'; +import settings from './settings'; +import { UPDATE_PRIORITY } from './const'; /** * Convenience class to create a new PIXI application. @@ -22,28 +24,52 @@ */ export default class Application { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experience unexplained flickering try setting this to true. - * @param {boolean} [useSharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedLoader=false] - `true` to use PIXI.loaders.shared, `false` to create new Loader. */ - constructor(width, height, options, noWebGL, useSharedTicker = false) + constructor(options, arg2, arg3, arg4, arg5) { + // Support for constructor(width, height, options, noWebGL, useSharedTicker) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + forceCanvas: !!arg4, + sharedTicker: !!arg5, + }, arg3); + } + + /** + * The default options, so we mixin functionality later. + * @member {object} + * @protected + */ + this._options = options = Object.assign({ + sharedTicker: false, + forceCanvas: false, + sharedLoader: false, + }, options); + /** * WebGL renderer if available, otherwise CanvasRenderer * @member {PIXI.WebGLRenderer|PIXI.CanvasRenderer} */ - this.renderer = autoDetectRenderer(width, height, options, noWebGL); + this.renderer = autoDetectRenderer(options); /** * The root display container that's rendered. @@ -63,7 +89,7 @@ * @member {PIXI.ticker.Ticker} * @default PIXI.ticker.shared */ - this.ticker = useSharedTicker ? shared : new Ticker(); + this.ticker = options.sharedTicker ? shared : new Ticker(); // Start the rendering this.start(); @@ -78,7 +104,7 @@ this._ticker = ticker; if (ticker) { - ticker.add(this.render, this); + ticker.add(this.render, this, UPDATE_PRIORITY.LOW); } } get ticker() // eslint-disable-line require-jsdoc @@ -136,13 +162,18 @@ */ destroy(removeView) { - this.stop(); + const oldTicker = this._ticker; + this.ticker = null; + oldTicker.destroy(); + this.stage.destroy(); this.stage = null; this.renderer.destroy(removeView); this.renderer = null; + + this._options = null; } } diff --git a/src/core/autoDetectRenderer.js b/src/core/autoDetectRenderer.js index 8960425..9ec0364 100644 --- a/src/core/autoDetectRenderer.js +++ b/src/core/autoDetectRenderer.js @@ -2,6 +2,7 @@ import CanvasRenderer from './renderers/canvas/CanvasRenderer'; import WebGLRenderer from './renderers/webgl/WebGLRenderer'; +// eslint-disable-next-line valid-jsdoc /** * This helper function will automatically detect which renderer you should be using. * WebGL is the preferred renderer as it is a lot faster. If webGL is not supported by @@ -9,24 +10,32 @@ * * @memberof PIXI * @function autoDetectRenderer - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @return {PIXI.WebGLRenderer|PIXI.CanvasRenderer} Returns WebGL renderer if available, otherwise CanvasRenderer */ -export function autoDetectRenderer(width = 800, height = 600, options, noWebGL) +export function autoDetectRenderer(options, arg1, arg2, arg3) { - if (!noWebGL && utils.isWebGLSupported()) + // Backward-compatible support for noWebGL option + let forceCanvas = options && options.forceCanvas; + + if (arg3 !== undefined) { - return new WebGLRenderer(width, height, options); + forceCanvas = arg3; } - return new CanvasRenderer(width, height, options); + if (!forceCanvas && utils.isWebGLSupported()) + { + return new WebGLRenderer(options, arg1, arg2); + } + + return new CanvasRenderer(options, arg1, arg2); } diff --git a/src/core/const.js b/src/core/const.js index b9e99ae..2c68dee 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -341,3 +341,27 @@ LINEAR_VERTICAL: 0, LINEAR_HORIZONTAL: 1, }; + +/** + * Represents the update priorities used by internal PIXI classes when registered with + * the {@link PIXI.ticker.Ticker} object. Higher priority items are updated first and lower + * priority items, such as render, should go later. + * + * @static + * @constant + * @name UPDATE_PRIORITY + * @memberof PIXI + * @type {object} + * @property {number} INTERACTION=50 Highest priority, used for {@link PIXI.interaction.InteractionManager} + * @property {number} HIGH=25 High priority updating, {@link PIXI.VideoBaseTexture} and {@link PIXI.extras.AnimatedSprite} + * @property {number} NORMAL=0 Default priority for ticker events, see {@link PIXI.ticker.Ticker#add}. + * @property {number} LOW=-25 Low priority used for {@link PIXI.Application} rendering. + * @property {number} UTILITY=-50 Lowest priority used for {@link PIXI.prepare.BasePrepare} utility. + */ +export const UPDATE_PRIORITY = { + INTERACTION: 50, + HIGH: 25, + NORMAL: 0, + LOW: -25, + UTILITY: -50, +}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 4e6c77d..decb42a 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -13,7 +13,6 @@ * * @class * @extends EventEmitter - * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ export default class DisplayObject extends EventEmitter @@ -113,6 +112,29 @@ * @private */ this._mask = null; + + /** + * If the object has been destroyed via destroy(). If true, it should not be used. + * + * @member {boolean} + * @private + * @readonly + */ + this._destroyed = false; + + /** + * Fired when this DisplayObject is added to a Container. + * + * @event PIXI.DisplayObject#added + * @param {PIXI.Container} container - The container added to. + */ + + /** + * Fired when this DisplayObject is removed from a Container. + * + * @event PIXI.DisplayObject#removed + * @param {PIXI.Container} container - The container removed from. + */ } /** @@ -403,6 +425,8 @@ this.interactive = false; this.interactiveChildren = false; + + this._destroyed = true; } /** diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index aafdfe2..f787c4a 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -792,8 +792,8 @@ sprite.worldAlpha = this.worldAlpha * sprite.alpha; sprite.blendMode = this.blendMode; - sprite.texture._frame.width = rect.width; - sprite.texture._frame.height = rect.height; + sprite._texture._frame.width = rect.width; + sprite._texture._frame.height = rect.height; sprite.transform.worldTransform = this.transform.worldTransform; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index a24c775..b9f192d 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -19,11 +19,12 @@ */ export default class SystemRenderer extends EventEmitter { + // eslint-disable-next-line valid-jsdoc /** * @param {string} system - The name of the system this renderer is for. - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -37,27 +38,31 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(system, screenWidth, screenHeight, options) + constructor(system, options, arg2, arg3) { super(); sayHello(system); - // prepare options - if (options) + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') { - for (const i in settings.RENDER_OPTIONS) - { - if (typeof options[i] === 'undefined') - { - options[i] = settings.RENDER_OPTIONS[i]; - } - } + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); } - else - { - options = settings.RENDER_OPTIONS; - } + + // Add the default render options + options = Object.assign({}, settings.RENDER_OPTIONS, options); + + /** + * The supplied constructor options. + * + * @member {Object} + * @readOnly + */ + this.options = options; /** * The type of the renderer. @@ -75,7 +80,7 @@ * * @member {PIXI.Rectangle} */ - this.screen = new Rectangle(0, 0, screenWidth || 800, screenHeight || 600); + this.screen = new Rectangle(0, 0, options.width, options.height); /** * The canvas element that everything is drawn to @@ -279,6 +284,8 @@ this.blendModes = null; + this.options = null; + this.preserveDrawingBuffer = false; this.clearBeforeRender = false; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index e84e49c..691caf0 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -17,10 +17,11 @@ */ export default class CanvasRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -34,9 +35,9 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('Canvas', screenWidth, screenHeight, options); + super('Canvas', options, arg2, arg3); this.type = RENDERER_TYPE.CANVAS; @@ -96,7 +97,19 @@ this.context = null; this.renderingToScreen = false; - this.resize(screenWidth, screenHeight); + this.resize(this.options.width, this.options.height); + + /** + * Fired after rendering finishes. + * + * @event PIXI.CanvasRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.CanvasRenderer#prerender + */ } /** @@ -299,4 +312,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.CanvasRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.CanvasExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.CanvasPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.CanvasRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index e1cf7cd..f6fe123 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -31,11 +31,12 @@ */ export default class WebGLRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** * - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -55,9 +56,9 @@ * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('WebGL', screenWidth, screenHeight, options); + super('WebGL', options, arg2, arg3); /** * The type of this renderer as a standardised const @@ -89,12 +90,6 @@ }; - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ this._backgroundColorRgba[3] = this.transparent ? 0 : 1; this.globalUniforms = new UniformGroup({ @@ -117,6 +112,13 @@ this.initPlugins(); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ if(options.context) { this.context.initFromContext(options.context); @@ -169,6 +171,25 @@ } return this; + + /** + * Fired after rendering finishes. + * + * @event PIXI.WebGLRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.WebGLRenderer#prerender + */ + + /** + * Fired when the WebGL context is set. + * + * @event PIXI.WebGLRenderer#context + * @param {WebGLRenderingContext} gl - WebGL context. + */ } /** @@ -295,4 +316,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.WebGLRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.WebGLRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(WebGLRenderer); diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index 4eb6128..fa3d883 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -47,6 +47,14 @@ * @member {boolean} */ this.enabled = true; + + /** + * If enabled, pixi will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; } /** diff --git a/src/core/renderers/webgl/systems/ContextSystem.js b/src/core/renderers/webgl/systems/ContextSystem.js index ff958c7..8df85ea 100644 --- a/src/core/renderers/webgl/systems/ContextSystem.js +++ b/src/core/renderers/webgl/systems/ContextSystem.js @@ -75,7 +75,8 @@ */ createContext(canvas, options) { - var gl = canvas.getContext('webgl', options) || + var gl = //canvas.getContext('webgl2', options) || + canvas.getContext('webgl', options) || canvas.getContext('experimental-webgl', options); if (!gl) diff --git a/src/core/renderers/webgl/systems/FilterSystem.js b/src/core/renderers/webgl/systems/FilterSystem.js index f47335d..37e28d2 100644 --- a/src/core/renderers/webgl/systems/FilterSystem.js +++ b/src/core/renderers/webgl/systems/FilterSystem.js @@ -111,7 +111,7 @@ // TODO we should fit the rect around the transform.. } - else + else if (filters[0].autoFit) { sourceFrame.fit(filterData.stack[0].destinationFrame); } diff --git a/src/core/renderers/webgl/systems/shader/shader/mapType.js b/src/core/renderers/webgl/systems/shader/shader/mapType.js index 581dace..917538a 100644 --- a/src/core/renderers/webgl/systems/shader/shader/mapType.js +++ b/src/core/renderers/webgl/systems/shader/shader/mapType.js @@ -15,7 +15,7 @@ } } - return GL_TABLE[type]; + return GL_TABLE[type]; }; var GL_TABLE = null; diff --git a/src/core/settings.js b/src/core/settings.js index 18c5877..38ef45c 100644 --- a/src/core/settings.js +++ b/src/core/settings.js @@ -2,6 +2,15 @@ import canUploadSameBuffer from './utils/canUploadSameBuffer'; /** + * User's customizable globals for overriding the default PIXI settings, such + * as a renderer's default resolution, framerate, float percision, etc. + * @example + * // Use the native window resolution as the default resolution + * // will support high-density displays when rendering + * PIXI.settings.RESOLUTION = window.devicePixelRatio. + * + * // Disable interpolation when scaling, will make texture be pixelated + * PIXI.settings.SCALE_MODE = PIXI.SCALE_MODES.NEAREST; * @namespace PIXI.settings */ export default { @@ -101,6 +110,9 @@ * @property {boolean} clearBeforeRender=true * @property {boolean} preserveDrawingBuffer=false * @property {boolean} roundPixels=false + * @property {number} width=800 + * @property {number} height=600 + * @property {boolean} legacy=false */ RENDER_OPTIONS: { view: null, @@ -112,6 +124,9 @@ clearBeforeRender: true, preserveDrawingBuffer: false, roundPixels: false, + width: 800, + height: 600, + legacy: false, }, /** diff --git a/src/core/shader/Program.js b/src/core/shader/Program.js index c0b538b..07b4398 100644 --- a/src/core/shader/Program.js +++ b/src/core/shader/Program.js @@ -1,6 +1,6 @@ import extractUniformsFromSrc from './extractUniformsFromSrc'; import generateUniformsSync from './generateUniformsSync'; -import glCore from 'pixi-gl-core'; +import shaderUtils from '../renderers/webgl/systems/shader/shader'; import { ProgramCache } from '../utils'; import getTestContext from '../utils/getTestContext'; @@ -64,14 +64,17 @@ } else { - vertexSrc = glCore.shader.setPrecision(vertexSrc, 'mediump'); - fragmentSrc = glCore.shader.setPrecision(fragmentSrc, 'mediump'); + vertexSrc = shaderUtils.setPrecision(vertexSrc, 'mediump'); + fragmentSrc = shaderUtils.setPrecision(fragmentSrc, 'mediump'); - const program = glCore.shader.compileProgram(gl, vertexSrc, fragmentSrc); + const program = shaderUtils.compileProgram(gl, vertexSrc, fragmentSrc); + console.log("<<<<<<>>><<"); this.attributeData = this.getAttributeData(program, gl); this.uniformData = this.getUniformData(program, gl); + console.log(this.uniformData); + //gl.deleteProgram(program); } } @@ -95,13 +98,13 @@ for (let i = 0; i < totalAttributes; i++) { const attribData = gl.getActiveAttrib(program, i); - const type = glCore.shader.mapType(gl, attribData.type); + const type = shaderUtils.mapType(gl, attribData.type); /*eslint-disable */ const data = { type: type, name: attribData.name, - size: glCore.shader.mapSize(type), + size: shaderUtils.mapSize(type), location: 0, }; /*eslint-enable */ @@ -137,13 +140,14 @@ // TODO expose this as a prop? // const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); - const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); + //const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); for (let i = 0; i < totalUniforms; i++) { const uniformData = gl.getActiveUniform(program, i); const name = uniformData.name.replace(/\[.*?\]/, ''); - const type = glCore.shader.mapType(gl, uniformData.type); + const type = shaderUtils.mapType(gl, uniformData.type); + console.log('mapping ' + uniformData.type + ' to ' + type); // if (!name.match(maskRegex)) { @@ -151,7 +155,7 @@ uniforms[name] = { type: type, size: uniformData.size, - value: glCore.shader.defaultValue(type, uniformData.size), + value: shaderUtils.defaultValue(type, uniformData.size), }; /*eslint-enable */ } diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index e387e99..b57d095 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -147,12 +147,12 @@ // so if _width is 0 then width was not set.. if (this._width) { - this.scale.x = sign(this.scale.x) * this._width / this.texture.orig.width; + this.scale.x = sign(this.scale.x) * this._width / this._texture.orig.width; } if (this._height) { - this.scale.y = sign(this.scale.y) * this._height / this.texture.orig.height; + this.scale.y = sign(this.scale.y) * this._height / this._texture.orig.height; } } diff --git a/src/core/sprites/canvas/CanvasTinter.js b/src/core/sprites/canvas/CanvasTinter.js index 27e5e0c..fd9de5d 100644 --- a/src/core/sprites/canvas/CanvasTinter.js +++ b/src/core/sprites/canvas/CanvasTinter.js @@ -18,7 +18,7 @@ */ getTintedTexture: (sprite, color) => { - const texture = sprite.texture; + const texture = sprite._texture; color = CanvasTinter.roundColor(color); diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 5a4edda..8b9b797 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -344,6 +344,7 @@ const texture = this._texture; const style = this._style; + const padding = style.trim ? 0 : style.padding; texture.baseTexture.valid = true; texture.baseTexture.resolution = this.resolution; @@ -353,11 +354,11 @@ texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - texture.trim.x = -style.padding; - texture.trim.y = -style.padding; + texture.trim.x = -padding; + texture.trim.y = -padding; - texture.orig.width = texture._frame.width - (style.padding * 2); - texture.orig.height = texture._frame.height - (style.padding * 2); + texture.orig.width = texture._frame.width - (padding * 2); + texture.orig.height = texture._frame.height - (padding * 2); // call sprite onTextureUpdate to update scale if _width or _height were set this._onTextureUpdate(); diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index 853da3d..8a9c34b 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -114,6 +114,38 @@ this.cacheId = null; this.validate(); + + /** + * Fired when a not-immediately-available source finishes loading. + * + * @protected + * @event PIXI.BaseTexture#loaded + * @param {PIXI.BaseTexture} baseTexture - Resource loaded. + */ + + /** + * Fired when a not-immediately-available source fails to load. + * + * @protected + * @event PIXI.BaseTexture#error + * @param {PIXI.BaseTexture} baseTexture - Resource errored. + */ + + /** + * Fired when BaseTexture is updated. + * + * @protected + * @event PIXI.BaseTexture#update + * @param {PIXI.BaseTexture} baseTexture - Instance of texture being updated. + */ + + /** + * Fired when BaseTexture is destroyed. + * + * @protected + * @event PIXI.BaseTexture#dispose + * @param {PIXI.BaseTexture} baseTexture - Instance of texture being destroyed. + */ } setResource(resource) diff --git a/src/core/textures/Spritesheet.js b/src/core/textures/Spritesheet.js index d469c62..9b24f27 100644 --- a/src/core/textures/Spritesheet.js +++ b/src/core/textures/Spritesheet.js @@ -208,6 +208,7 @@ // lets also add the frame to pixi's global cache for fromFrame and fromImage functions TextureCache[i] = this.textures[i]; + this.textures[i].textureCacheId = i; } frameIndex++; diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 0a2c379..2213087 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -147,9 +147,9 @@ /** * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. * - * @event update - * @memberof PIXI.Texture# + * @event PIXI.Texture#update * @protected + * @param {PIXI.Texture} texture - Instance of texture being updated. */ this._updateID = 0; @@ -159,6 +159,15 @@ * @type {Object} */ this.transform = null; + + /** + * The id under which this Texture has been added to the texture cache. This is + * automatically set in certain cases, but may not always be accurate, particularly if + * the texture is in the cache under multiple ids. + * + * @member {string} + */ + this.textureCacheId = null; } /** @@ -225,11 +234,17 @@ this._uvs = null; this.trim = null; this.orig = null; + this.textureCacheId = null; this.valid = false; - this.off('dispose', this.dispose, this); - this.off('update', this.update, this); + for (const prop in TextureCache) + { + if (TextureCache[prop] === this) + { + delete TextureCache[prop]; + } + } } /** @@ -315,14 +330,15 @@ */ static fromLoader(source, imageUrl, name) { - // console.log('added from loader...') + // console.log('added from loader...') const resource = new ImageResource(source);//.from(imageUrl, crossorigin);// document.createElement('img'); - // console.log('base resource ' + resource.width); + // console.log('base resource ' + resource.width); const baseTexture = new BaseTexture(resource, settings.SCALE_MODE, getResolutionOfUrl(imageUrl)); + /// console.log('base width ' + baseTexture.width); const texture = new Texture(baseTexture); @@ -335,6 +351,7 @@ // lets also add the frame to pixi's global cache for fromFrame and fromImage fucntions BaseTextureCache[name] = baseTexture; TextureCache[name] = texture; + texture.textureCacheId = name; // also add references by url if they are different. if (name !== imageUrl) @@ -355,6 +372,10 @@ */ static addTextureToCache(texture, id) { + if (!texture.textureCacheId) + { + texture.textureCacheId = id; + } TextureCache[id] = texture; } @@ -495,6 +516,7 @@ */ Texture.EMPTY = new Texture(new BaseTexture()); removeAllHandlers(Texture.EMPTY); +removeAllHandlers(Texture.EMPTY.baseTexture); /** * A white texture of 10x10 size, used for graphics and other things @@ -505,3 +527,4 @@ */ Texture.WHITE = createWhiteTexture(); removeAllHandlers(Texture.WHITE); +removeAllHandlers(Texture.WHITE.baseTexture); diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index 59eb9fd..a9b9f51 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -1,6 +1,7 @@ import BaseTexture from './BaseTexture'; import { uid, BaseTextureCache } from '../utils'; -import * as ticker from '../ticker'; +import { shared } from '../ticker'; +import { UPDATE_PRIORITY } from '../const'; /** * A texture of a [playing] Video. @@ -125,7 +126,7 @@ if (!this._isAutoUpdating && this.autoUpdate) { - ticker.shared.add(this.update, this); + shared.add(this.update, this, UPDATE_PRIORITY.HIGH); this._isAutoUpdating = true; } } @@ -139,7 +140,7 @@ { if (this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); this._isAutoUpdating = false; } } @@ -187,7 +188,7 @@ { if (this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); } if (this.source && this.source._pixiId) @@ -281,12 +282,12 @@ if (!this._autoUpdate && this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); this._isAutoUpdating = false; } else if (this._autoUpdate && !this._isAutoUpdating) { - ticker.shared.add(this.update, this); + shared.add(this.update, this, UPDATE_PRIORITY.HIGH); this._isAutoUpdating = true; } } diff --git a/src/core/ticker/Ticker.js b/src/core/ticker/Ticker.js index 17a1517..40df138 100644 --- a/src/core/ticker/Ticker.js +++ b/src/core/ticker/Ticker.js @@ -1,12 +1,10 @@ import settings from '../settings'; -import EventEmitter from 'eventemitter3'; - -// Internal event used by composed emitter -const TICK = 'tick'; +import { UPDATE_PRIORITY } from '../const'; +import TickerListener from './TickerListener'; /** * A Ticker class that runs an update loop that other objects listen to. - * This class is composed around an EventEmitter object to add listeners + * This class is composed around listeners * meant for execution on the next requested animation frame. * Animation frames are requested only when necessary, * e.g. When the ticker is started and the emitter has listeners. @@ -22,10 +20,11 @@ constructor() { /** - * Internal emitter used to fire 'tick' event + * The first listener. All new listeners added are chained on this. * @private + * @type {TickerListener} */ - this._emitter = new EventEmitter(); + this._head = new TickerListener(null, null, Infinity); /** * Internal current frame request ID @@ -131,7 +130,7 @@ // Invoke listeners now this.update(time); // Listener side effects may have modified ticker state. - if (this.started && this._requestId === null && this._emitter.listeners(TICK, true)) + if (this.started && this._requestId === null && this._head.next) { this._requestId = requestAnimationFrame(this._tick); } @@ -148,7 +147,7 @@ */ _requestIfNeeded() { - if (this._requestId === null && this._emitter.listeners(TICK, true)) + if (this._requestId === null && this._head.next) { // ensure callbacks get correct delta this.lastTime = performance.now(); @@ -193,35 +192,72 @@ } /** - * Calls {@link module:eventemitter3.EventEmitter#on} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. + * Register a handler for tick events. Calls continuously unless + * it is removed or the ticker is stopped. * * @param {Function} fn - The listener function to be added for updates * @param {Function} [context] - The listener context + * @param {number} [priority=PIXI.UPDATE_PRIORITY.NORMAL] - The priority for emitting * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - add(fn, context) + add(fn, context, priority = UPDATE_PRIORITY.NORMAL) { - this._emitter.on(TICK, fn, context); - - this._startIfPossible(); - - return this; + return this._addListener(new TickerListener(fn, context, priority)); } /** - * Calls {@link module:eventemitter3.EventEmitter#once} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. + * Add a handler for the tick event which is only execute once. * * @param {Function} fn - The listener function to be added for one update * @param {Function} [context] - The listener context + * @param {number} [priority=PIXI.UPDATE_PRIORITY.NORMAL] - The priority for emitting * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - addOnce(fn, context) + addOnce(fn, context, priority = UPDATE_PRIORITY.NORMAL) { - this._emitter.once(TICK, fn, context); + return this._addListener(new TickerListener(fn, context, priority, true)); + } + + /** + * Internally adds the event handler so that it can be sorted by priority. + * Priority allows certain handler (user, AnimatedSprite, Interaction) to be run + * before the rendering. + * + * @private + * @param {TickerListener} listener - Current listener being added. + * @returns {PIXI.ticker.Ticker} This instance of a ticker + */ + _addListener(listener) + { + // For attaching to head + let current = this._head.next; + let previous = this._head; + + // Add the first item + if (!current) + { + listener.connect(previous); + } + else + { + // Go from highest to lowest priority + while (current) + { + if (listener.priority >= current.priority) + { + listener.connect(previous); + break; + } + previous = current; + current = current.next; + } + + // Not yet connected + if (!listener.previous) + { + listener.connect(previous); + } + } this._startIfPossible(); @@ -229,19 +265,33 @@ } /** - * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. - * It checks if the emitter has listeners for 'tick' event. - * If it does, then it cancels the animation frame. + * Removes any handlers matching the function and context parameters. + * If no handlers are left after removing, then it cancels the animation frame. * - * @param {Function} [fn] - The listener function to be removed + * @param {Function} fn - The listener function to be removed * @param {Function} [context] - The listener context to be removed * @returns {PIXI.ticker.Ticker} This instance of a ticker */ remove(fn, context) { - this._emitter.off(TICK, fn, context); + let listener = this._head.next; - if (!this._emitter.listeners(TICK, true)) + while (listener) + { + // We found a match, lets remove it + // no break to delete all possible matches + // incase a listener was added 2+ times + if (listener.match(fn, context)) + { + listener = listener.destroy(); + } + else + { + listener = listener.next; + } + } + + if (!this._head.next) { this._cancelIfNeeded(); } @@ -276,6 +326,25 @@ } /** + * Destroy the ticker and don't use after this. Calling + * this method removes all references to internal events. + */ + destroy() + { + this.stop(); + + let listener = this._head.next; + + while (listener) + { + listener = listener.destroy(true); + } + + this._head.destroy(); + this._head = null; + } + + /** * Triggers an update. An update entails setting the * current {@link PIXI.ticker.Ticker#elapsedMS}, * the current {@link PIXI.ticker.Ticker#deltaTime}, @@ -320,8 +389,22 @@ this.deltaTime = elapsedMS * settings.TARGET_FPMS * this.speed; + // Cache a local reference, in-case ticker is destroyed + // during the emit, we can still check for head.next + const head = this._head; + // Invoke listeners added to internal emitter - this._emitter.emit(TICK, this.deltaTime); + let listener = head.next; + + while (listener) + { + listener = listener.emit(this.deltaTime); + } + + if (!head.next) + { + this._cancelIfNeeded(); + } } else { diff --git a/src/core/ticker/TickerListener.js b/src/core/ticker/TickerListener.js new file mode 100644 index 0000000..2bedb34 --- /dev/null +++ b/src/core/ticker/TickerListener.js @@ -0,0 +1,158 @@ +/** + * Internal class for handling the priority sorting of ticker handlers. + * + * @private + * @class + * @memberof PIXI.ticker + */ +export default class TickerListener +{ + /** + * Constructor + * + * @param {Function} fn - The listener function to be added for one update + * @param {Function} [context=null] - The listener context + * @param {number} [priority=0] - The priority for emitting + * @param {boolean} [once=false] - If the handler should fire once + */ + constructor(fn, context = null, priority = 0, once = false) + { + /** + * The handler function to execute. + * @member {Function} + */ + this.fn = fn; + + /** + * The calling to execute. + * @member {Function} + */ + this.context = context; + + /** + * The current priority. + * @member {number} + */ + this.priority = priority; + + /** + * If this should only execute once. + * @member {boolean} + */ + this.once = once; + + /** + * The next item in chain. + * @member {TickerListener} + */ + this.next = null; + + /** + * The previous item in chain. + * @member {TickerListener} + */ + this.previous = null; + + /** + * `true` if this listener has been destroyed already. + * @member {boolean} + * @private + */ + this._destroyed = false; + } + + /** + * Simple compare function to figure out if a function and context match. + * + * @param {Function} fn - The listener function to be added for one update + * @param {Function} context - The listener context + * @return {boolean} `true` if the listener match the arguments + */ + match(fn, context) + { + context = context || null; + + return this.fn === fn && this.context === context; + } + + /** + * Emit by calling the current function. + * @param {number} deltaTime - time since the last emit. + * @return {TickerListener} Next ticker + */ + emit(deltaTime) + { + if (this.context) + { + this.fn.call(this.context, deltaTime); + } + else + { + this.fn(deltaTime); + } + + if (this.once) + { + this.destroy(); + } + + const redirect = this.next; + + // Soft-destroying should remove + // the next reference + if (this._destroyed) + { + this.next = null; + } + + return redirect; + } + + /** + * Connect to the list. + * @param {TickerListener} previous - Input node, previous listener + */ + connect(previous) + { + this.previous = previous; + if (previous.next) + { + previous.next.previous = this; + } + this.next = previous.next; + previous.next = this; + } + + /** + * Destroy and don't use after this. + * @param {boolean} [hard = false] `true` to remove the `next` reference, this + * is considered a hard destroy. Soft destroy maintains the next reference. + * @return {TickerListener} The listener to redirect while emitting or removing. + */ + destroy(hard = false) + { + this._destroyed = true; + this.fn = null; + this.context = null; + + // Disconnect, hook up next and previous + if (this.previous) + { + this.previous.next = this.next; + } + + if (this.next) + { + this.next.previous = this.previous; + } + + // Redirect to the next item + const redirect = this.previous; + + // Remove references + this.next = hard ? null : redirect; + this.previous = null; + + return redirect; + } +} diff --git a/src/core/ticker/index.js b/src/core/ticker/index.js index 4b3017c..c3020d4 100644 --- a/src/core/ticker/index.js +++ b/src/core/ticker/index.js @@ -45,8 +45,25 @@ const shared = new Ticker(); shared.autoStart = true; +shared.destroy = () => +{ + // protect destroying shared ticker + // this is used by other internal systems + // like AnimatedSprite and InteractionManager +}; /** + * This namespace contains an API for interacting with PIXI's internal global update loop. + * + * This ticker is used for rendering, {@link PIXI.extras.AnimatedSprite AnimatedSprite}, + * {@link PIXI.interaction.InteractionManager InteractionManager} and many other time-based PIXI systems. + * @example + * const ticker = new PIXI.ticker.Ticker(); + * ticker.stop(); + * ticker.add((deltaTime) => { + * // do something every frame + * }); + * ticker.start(); * @namespace PIXI.ticker */ export { shared, Ticker }; diff --git a/src/core/utils/index.js b/src/core/utils/index.js index be44750..7441f7b 100644 --- a/src/core/utils/index.js +++ b/src/core/utils/index.js @@ -9,6 +9,21 @@ let saidHello = false; /** + * Generalized convenience utilities for PIXI. + * @example + * // Extend PIXI's internal Event Emitter. + * class MyEmitter extends PIXI.utils.EventEmitter { + * constructor() { + * super(); + * console.log("Emitter created!"); + * } + * } + * + * // Get info on current device + * console.log(PIXI.utils.isMobile); + * + * // Convert hex color to string + * console.log(PIXI.utils.hex2string(0xff00ff)); // returns: "#ff00ff" * @namespace PIXI.utils */ export { diff --git a/src/deprecation.js b/src/deprecation.js index b9c73b3..33daeec 100644 --- a/src/deprecation.js +++ b/src/deprecation.js @@ -918,6 +918,35 @@ }); /** + * @method + * @name PIXI.prepare.BasePrepare#register + * @see PIXI.prepare.BasePrepare#registerFindHook + * @deprecated since version 4.4.2 + * @param {Function} [addHook] - Function call that takes two parameters: `item:*, queue:Array` + * function must return `true` if it was able to add item to the queue. + * @param {Function} [uploadHook] - Function call that takes two parameters: `prepare:CanvasPrepare, item:*` and + * function must return `true` if it was able to handle upload of item. + * @return {PIXI.BasePrepare} Instance of plugin for chaining. + */ +prepare.BasePrepare.prototype.register = function register(addHook, uploadHook) +{ + warn('renderer.plugins.prepare.register is now deprecated, ' + + 'please use renderer.plugins.prepare.registerFindHook & renderer.plugins.prepare.registerUploadHook'); + + if (addHook) + { + this.registerFindHook(addHook); + } + + if (uploadHook) + { + this.registerUploadHook(uploadHook); + } + + return this; +}; + +/** * The number of graphics or textures to upload to the GPU. * * @name PIXI.prepare.canvas.UPLOADS_PER_FRAME @@ -969,67 +998,73 @@ }, }); -Object.defineProperties(loaders.Resource.prototype, { - isJson: { - get() - { - warn('The isJson property is deprecated, please use `resource.type === Resource.TYPE.JSON`.'); +if (loaders.Loader) +{ + const Resource = loaders.Resource; + const Loader = loaders.Loader; - return this.type === loaders.Loader.Resource.TYPE.JSON; + Object.defineProperties(Resource.prototype, { + isJson: { + get() + { + warn('The isJson property is deprecated, please use `resource.type === Resource.TYPE.JSON`.'); + + return this.type === Resource.TYPE.JSON; + }, }, - }, - isXml: { - get() - { - warn('The isXml property is deprecated, please use `resource.type === Resource.TYPE.XML`.'); + isXml: { + get() + { + warn('The isXml property is deprecated, please use `resource.type === Resource.TYPE.XML`.'); - return this.type === loaders.Loader.Resource.TYPE.XML; + return this.type === Resource.TYPE.XML; + }, }, - }, - isImage: { - get() - { - warn('The isImage property is deprecated, please use `resource.type === Resource.TYPE.IMAGE`.'); + isImage: { + get() + { + warn('The isImage property is deprecated, please use `resource.type === Resource.TYPE.IMAGE`.'); - return this.type === loaders.Loader.Resource.TYPE.IMAGE; + return this.type === Resource.TYPE.IMAGE; + }, }, - }, - isAudio: { - get() - { - warn('The isAudio property is deprecated, please use `resource.type === Resource.TYPE.AUDIO`.'); + isAudio: { + get() + { + warn('The isAudio property is deprecated, please use `resource.type === Resource.TYPE.AUDIO`.'); - return this.type === loaders.Loader.Resource.TYPE.AUDIO; + return this.type === Resource.TYPE.AUDIO; + }, }, - }, - isVideo: { - get() - { - warn('The isVideo property is deprecated, please use `resource.type === Resource.TYPE.VIDEO`.'); + isVideo: { + get() + { + warn('The isVideo property is deprecated, please use `resource.type === Resource.TYPE.VIDEO`.'); - return this.type === loaders.Loader.Resource.TYPE.VIDEO; + return this.type === Resource.TYPE.VIDEO; + }, }, - }, -}); + }); -Object.defineProperties(loaders.Loader.prototype, { - before: { - get() - { - warn('The before() method is deprecated, please use pre().'); + Object.defineProperties(Loader.prototype, { + before: { + get() + { + warn('The before() method is deprecated, please use pre().'); - return this.pre; + return this.pre; + }, }, - }, - after: { - get() - { - warn('The after() method is deprecated, please use use().'); + after: { + get() + { + warn('The after() method is deprecated, please use use().'); - return this.use; + return this.use; + }, }, - }, -}); + }); +} /** * @name PIXI.interaction.interactiveTarget#defaultCursor diff --git a/src/extract/canvas/CanvasExtract.js b/src/extract/canvas/CanvasExtract.js index 941ca05..3ac5aee 100644 --- a/src/extract/canvas/CanvasExtract.js +++ b/src/extract/canvas/CanvasExtract.js @@ -8,7 +8,7 @@ * An instance of this class is automatically created by default, and can be found at renderer.plugins.extract * * @class - * @memberof PIXI + * @memberof PIXI.extract */ export default class CanvasExtract { @@ -21,9 +21,9 @@ /** * Collection of methods for extracting data (image, pixels, etc.) from a display object or render texture * - * @member {PIXI.CanvasExtract} extract + * @member {PIXI.extract.CanvasExtract} extract * @memberof PIXI.CanvasRenderer# - * @see PIXI.CanvasExtract + * @see PIXI.extract.CanvasExtract */ renderer.extract = this; } diff --git a/src/extract/index.js b/src/extract/index.js index 0f2170d..e9478c1 100644 --- a/src/extract/index.js +++ b/src/extract/index.js @@ -1,2 +1,22 @@ +/** + * This namespace provides renderer-specific plugins for exporting content from a renderer. + * For instance, these plugins can be used for saving an Image, Canvas element or for exporting the raw image data (pixels). + * + * Do not instantiate these plugins directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. + * @example + * // Create a new app (will auto-add extract plugin to renderer) + * const app = new PIXI.Application(); + * + * // Draw a red circle + * const graphics = new PIXI.Graphics() + * .beginFill(0xFF0000) + * .drawCircle(0, 0, 50); + * + * // Render the graphics as an HTMLImageElement + * const image = app.renderer.plugins.extract.image(graphics); + * document.body.appendChild(image); + * @namespace PIXI.extract + */ export { default as webgl } from './webgl/WebGLExtract'; export { default as canvas } from './canvas/CanvasExtract'; diff --git a/src/extract/webgl/WebGLExtract.js b/src/extract/webgl/WebGLExtract.js index 60228bb..1c3dd30 100644 --- a/src/extract/webgl/WebGLExtract.js +++ b/src/extract/webgl/WebGLExtract.js @@ -9,7 +9,7 @@ * An instance of this class is automatically created by default, and can be found at renderer.plugins.extract * * @class - * @memberof PIXI + * @memberof PIXI.extract */ export default class WebGLExtract { @@ -22,9 +22,9 @@ /** * Collection of methods for extracting data (image, pixels, etc.) from a display object or render texture * - * @member {PIXI.WebGLExtract} extract + * @member {PIXI.extract.WebGLExtract} extract * @memberof PIXI.WebGLRenderer# - * @see PIXI.WebGLExtract + * @see PIXI.extract.WebGLExtract */ renderer.extract = this; } diff --git a/src/extras/AnimatedSprite.js b/src/extras/AnimatedSprite.js index 13b804f..5297778 100644 --- a/src/extras/AnimatedSprite.js +++ b/src/extras/AnimatedSprite.js @@ -137,7 +137,7 @@ this.playing = true; if (this._autoUpdate) { - core.ticker.shared.add(this.update, this); + core.ticker.shared.add(this.update, this, core.UPDATE_PRIORITY.HIGH); } } diff --git a/src/extras/TextureTransform.js b/src/extras/TextureTransform.js index 78e6e40..16536d2 100644 --- a/src/extras/TextureTransform.js +++ b/src/extras/TextureTransform.js @@ -65,8 +65,36 @@ } /** + * Multiplies uvs array to transform + * @param {Float32Array} uvs mesh uvs + * @param {Float32Array} [out=uvs] output + * @returns {Float32Array} output + */ + multiplyUvs(uvs, out) + { + if (out === undefined) + { + out = uvs; + } + + const mat = this.mapCoord; + + for (let i = 0; i < uvs.length; i += 2) + { + const x = uvs[i]; + const y = uvs[i + 1]; + + out[i] = (x * mat.a) + (y * mat.c) + mat.tx; + out[i + 1] = (x * mat.b) + (y * mat.d) + mat.ty; + } + + return out; + } + + /** * updates matrices if texture was changed * @param {boolean} forceUpdate if true, matrices will be updated any case + * @returns {boolean} whether or not it was updated */ update(forceUpdate) { @@ -74,13 +102,13 @@ if (!tex || !tex.valid) { - return; + return false; } if (!forceUpdate && this._lastTextureID === tex._updateID) { - return; + return false; } this._lastTextureID = tex._updateID; @@ -110,5 +138,7 @@ frame[3] = (tex._frame.y + tex._frame.height - margin + offset) / texBase.height; this.uClampOffset[0] = offset / texBase.realWidth; this.uClampOffset[1] = offset / texBase.realHeight; + + return true; } } diff --git a/src/extras/TilingSprite.js b/src/extras/TilingSprite.js index 1f6014f..a3f027f 100644 --- a/src/extras/TilingSprite.js +++ b/src/extras/TilingSprite.js @@ -180,9 +180,9 @@ const transform = this.worldTransform; const resolution = renderer.resolution; const baseTexture = texture.baseTexture; - const baseTextureResolution = texture.baseTexture.resolution; - const modX = (this.tilePosition.x / this.tileScale.x) % texture._frame.width; - const modY = (this.tilePosition.y / this.tileScale.y) % texture._frame.height; + const baseTextureResolution = baseTexture.resolution; + const modX = ((this.tilePosition.x / this.tileScale.x) % texture._frame.width) * baseTextureResolution; + const modY = ((this.tilePosition.y / this.tileScale.y) % texture._frame.height) * baseTextureResolution; // create a nice shiny pattern! // TODO this needs to be refreshed if texture changes.. diff --git a/src/extras/cacheAsBitmap.js b/src/extras/cacheAsBitmap.js index 5dd5640..aea06a8 100644 --- a/src/extras/cacheAsBitmap.js +++ b/src/extras/cacheAsBitmap.js @@ -227,7 +227,16 @@ this.transform._parentID = -1; // restore the transform of the cached sprite to avoid the nasty flicker.. - this.updateTransform(); + if (!this.parent) + { + this.parent = renderer._tempDisplayObjectParent; + this.updateTransform(); + this.parent = null; + } + else + { + this.updateTransform(); + } // map the hit test.. this.containsPoint = cachedSprite.containsPoint.bind(cachedSprite); @@ -314,7 +323,17 @@ cachedSprite._bounds = this._bounds; cachedSprite.alpha = cacheAlpha; - this.updateTransform(); + if (!this.parent) + { + this.parent = renderer._tempDisplayObjectParent; + this.updateTransform(); + this.parent = null; + } + else + { + this.updateTransform(); + } + this.updateTransform = this.displayObjectUpdateTransform; this._cacheData.sprite = cachedSprite; @@ -358,9 +377,12 @@ * Destroys the cached object. * * @private + * @param {object|boolean} [options] - Options parameter. A boolean will act as if all options + * have been set to that value. + * Used when destroying containers, see the Container.destroy method. */ -DisplayObject.prototype._cacheAsBitmapDestroy = function _cacheAsBitmapDestroy() +DisplayObject.prototype._cacheAsBitmapDestroy = function _cacheAsBitmapDestroy(options) { this.cacheAsBitmap = false; - this.destroy(); + this.destroy(options); }; diff --git a/src/extras/index.js b/src/extras/index.js index bbd8bbf..a34d7ec 100644 --- a/src/extras/index.js +++ b/src/extras/index.js @@ -1,7 +1,7 @@ /** + * Additional PIXI DisplayObjects for animation, tiling and bitmap text. * @namespace PIXI.extras */ -export { default as TextureTransform } from './TextureTransform'; export { default as AnimatedSprite } from './AnimatedSprite'; export { default as TilingSprite } from './TilingSprite'; export { default as TilingSpriteRenderer } from './webgl/TilingSpriteRenderer'; diff --git a/src/extras/webgl/TilingSpriteRenderer.js b/src/extras/webgl/TilingSpriteRenderer.js index aeec172..eee3b5c 100644 --- a/src/extras/webgl/TilingSpriteRenderer.js +++ b/src/extras/webgl/TilingSpriteRenderer.js @@ -11,7 +11,7 @@ * WebGL renderer plugin for tiling sprites * * @class - * @memberof PIXI + * @memberof PIXI.extras * @extends PIXI.ObjectRenderer */ export default class TilingSpriteRenderer extends core.ObjectRenderer @@ -118,7 +118,7 @@ tempMat.invert(); if (isSimple) { - tempMat.append(uv.mapCoord); + tempMat.prepend(uv.mapCoord); } else { diff --git a/src/filters/displacement/DisplacementFilter.js b/src/filters/displacement/DisplacementFilter.js index d5355c3..b5d030c 100644 --- a/src/filters/displacement/DisplacementFilter.js +++ b/src/filters/displacement/DisplacementFilter.js @@ -35,7 +35,7 @@ this.maskSprite = sprite; this.maskMatrix = maskMatrix; - this.uniforms.mapSampler = sprite.texture; + this.uniforms.mapSampler = sprite._texture; this.uniforms.filterMatrix = maskMatrix; this.uniforms.scale = { x: 1, y: 1 }; diff --git a/src/filters/index.js b/src/filters/index.js index f3e8862..7ceb50a 100644 --- a/src/filters/index.js +++ b/src/filters/index.js @@ -1,4 +1,21 @@ /** + * This namespace contains WebGL-only display filters that can be applied + * to DisplayObjects using the {@link PIXI.DisplayObject#filters filters} property. + * @example + * // Create a new application + * const app = new PIXI.Application(); + * + * // Draw a green rectangle + * const rect = new PIXI.Graphics() + * .beginFill(0x00ff00) + * .drawRect(40, 40, 200, 200); + * + * // Add a blur filter + * rect.filters = [new PIXI.filters.BlurFilter()]; + * + * // Display rectangle + * app.stage.addChild(rect); + * document.body.appendChild(app.view); * @namespace PIXI.filters */ export { default as FXAAFilter } from './fxaa/FXAAFilter'; diff --git a/src/index.js b/src/index.js index c553427..77da490 100644 --- a/src/index.js +++ b/src/index.js @@ -20,6 +20,14 @@ import { utils } from './core'; utils.mixins.performMixins(); +/** + * Alias for {@link PIXI.loaders.shared}. + * @name loader + * @memberof PIXI + * @type {PIXI.loader.Loader} + */ +const loader = loaders.shared || null; + export { accessibility, extract, @@ -30,18 +38,8 @@ mesh, particles, prepare, + loader, }; -/** - * A premade instance of the loader that can be used to load resources. - * - * @name loader - * @memberof PIXI - * @property {PIXI.loaders.Loader} - */ -const loader = loaders && loaders.Loader ? new loaders.Loader() : null; // check is there in case user excludes loader lib - -export { loader }; - // Always export pixi globally. global.PIXI = exports; // eslint-disable-line diff --git a/src/interaction/InteractionData.js b/src/interaction/InteractionData.js index 02d53a3..449f8cc 100644 --- a/src/interaction/InteractionData.js +++ b/src/interaction/InteractionData.js @@ -30,7 +30,10 @@ /** * When passed to an event handler, this will be the original DOM Event that was captured * - * @member {Event} + * @see https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent + * @see https://developer.mozilla.org/en-US/docs/Web/API/TouchEvent + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent + * @member {MouseEvent|TouchEvent|PointerEvent} */ this.originalEvent = null; } diff --git a/src/interaction/InteractionManager.js b/src/interaction/InteractionManager.js index 8baf13a..2d75c7c 100644 --- a/src/interaction/InteractionManager.js +++ b/src/interaction/InteractionManager.js @@ -13,6 +13,14 @@ const MOUSE_POINTER_ID = 'MOUSE'; +// helpers for hitTest() - only used inside hitTest() +const hitTestEvent = { + target: null, + data: { + global: null, + }, +}; + /** * The interaction manager deals with mouse, touch and pointer events. Any DisplayObject can be interactive * if its interactive parameter is set to true @@ -245,67 +253,60 @@ this.setTargetElement(this.renderer.view, this.renderer.resolution); /** - * Fired when a pointer device button (usually a mouse button) is pressed on the display + * Fired when a pointer device button (usually a mouse left-button) is pressed on the display * object. * - * @event mousedown - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mousedown + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device secondary button (usually a mouse right-button) is pressed * on the display object. * - * @event rightdown - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightdown + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** - * Fired when a pointer device button (usually a mouse button) is released over the display + * Fired when a pointer device button (usually a mouse left-button) is released over the display * object. * - * @event mouseup - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseup + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device secondary button (usually a mouse right-button) is released * over the display object. * - * @event rightup - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightup + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** - * Fired when a pointer device button (usually a mouse button) is pressed and released on + * Fired when a pointer device button (usually a mouse left-button) is pressed and released on * the display object. * - * @event click - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#click + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device secondary button (usually a mouse right-button) is pressed * and released on the display object. * - * @event rightclick - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightclick + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** - * Fired when a pointer device button (usually a mouse button) is released outside the + * Fired when a pointer device button (usually a mouse left-button) is released outside the * display object that initially registered a * [mousedown]{@link PIXI.interaction.InteractionManager#event:mousedown}. * - * @event mouseupoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** @@ -313,146 +314,362 @@ * outside the display object that initially registered a * [rightdown]{@link PIXI.interaction.InteractionManager#event:rightdown}. * - * @event rightupoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device (usually a mouse) is moved while over the display object * - * @event mousemove - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mousemove + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device (usually a mouse) is moved onto the display object * - * @event mouseover - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseover + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device (usually a mouse) is moved off the display object * - * @event mouseout - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseout + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is pressed on the display object. * - * @event pointerdown - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerdown + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is released over the display object. * - * @event pointerup - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerup + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when the operating system cancels a pointer event * - * @event pointercancel - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointercancel + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is pressed and released on the display object. * - * @event pointertap - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointertap + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is released outside the display object that initially * registered a [pointerdown]{@link PIXI.interaction.InteractionManager#event:pointerdown}. * - * @event pointerupoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device is moved while over the display object * - * @event pointermove - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointermove + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device is moved onto the display object * - * @event pointerover - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerover + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device is moved off the display object * - * @event pointerout - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerout + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is placed on the display object. * - * @event touchstart - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchstart + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is removed from the display object. * - * @event touchend - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchend + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when the operating system cancels a touch * - * @event touchcancel - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchcancel + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is placed and removed from the display object. * - * @event tap - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#tap + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is removed outside of the display object that initially * registered a [touchstart]{@link PIXI.interaction.InteractionManager#event:touchstart}. * - * @event touchendoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchendoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is moved along the display object. * - * @event touchmove - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchmove + * @param {PIXI.interaction.InteractionData} event - Interaction data */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is pressed on the display. + * object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mousedown + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is pressed + * on the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightdown + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is released over the display + * object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseup + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is released + * over the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightup + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is pressed and released on + * the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#click + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is pressed + * and released on the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightclick + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is released outside the + * display object that initially registered a + * [mousedown]{@link PIXI.DisplayObject#event:mousedown}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is released + * outside the display object that initially registered a + * [rightdown]{@link PIXI.DisplayObject#event:rightdown}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device (usually a mouse) is moved while over the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mousemove + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device (usually a mouse) is moved onto the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseover + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device (usually a mouse) is moved off the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseout + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is pressed on the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerdown + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is released over the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerup + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when the operating system cancels a pointer event. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointercancel + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is pressed and released on the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointertap + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is released outside the display object that initially + * registered a [pointerdown]{@link PIXI.DisplayObject#event:pointerdown}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device is moved while over the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointermove + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device is moved onto the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerover + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device is moved off the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerout + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is placed on the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchstart + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is removed from the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchend + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when the operating system cancels a touch. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchcancel + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is placed and removed from the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#tap + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is removed outside of the display object that initially + * registered a [touchstart]{@link PIXI.DisplayObject#event:touchstart}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchendoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is moved along the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchmove + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + } + + /** + * Hit tests a point against the display tree, returning the first interactive object that is hit. + * + * @param {PIXI.Point} globalPoint - A point to hit test with, in global space. + * @param {PIXI.Container} [root] - The root display object to start from. If omitted, defaults + * to the last rendered root of the associated renderer. + * @return {PIXI.DisplayObject} The hit display object, if any. + */ + hitTest(globalPoint, root) + { + // clear the target for our hit test + hitTestEvent.target = null; + // assign the global point + hitTestEvent.data.global = globalPoint; + // ensure safety of the root + if (!root) + { + root = this.renderer._lastObjectRendered; + } + // run the hit test + this.processInteractive(hitTestEvent, root, null, true); + // return our found object - it'll be null if we didn't hit anything + + return hitTestEvent.target; } /** @@ -487,7 +704,7 @@ return; } - core.ticker.shared.add(this.update, this); + core.ticker.shared.add(this.update, this, core.UPDATE_PRIORITY.INTERACTION); if (window.navigator.msPointerEnabled) { @@ -804,8 +1021,6 @@ } } - let keepHitTestingAfterChildren = hitTest; - // ** FREE TIP **! If an object is not interactive or has no buttons in it // (such as a game scene!) set interactiveChildren to false for that displayObject. // This will allow pixi to completely ignore and bypass checking the displayObjects children. @@ -818,7 +1033,9 @@ const child = children[i]; // time to get recursive.. if this function will return if something is hit.. - if (this.processInteractive(interactionEvent, child, func, hitTest, interactiveParent)) + const childHit = this.processInteractive(interactionEvent, child, func, hitTest, interactiveParent); + + if (childHit) { // its a good idea to check if a child has lost its parent. // this means it has been removed whilst looping so its best @@ -827,8 +1044,6 @@ continue; } - hit = true; - // we no longer need to hit test any more objects in this container as we we // now know the parent has been hit interactiveParent = false; @@ -838,36 +1053,41 @@ // This means we no longer need to hit test anything else. We still need to run // through all objects, but we don't need to perform any hit tests. - keepHitTestingAfterChildren = false; - - if (child.interactive) + if (childHit) { - hitTest = false; + if (interactionEvent.target) + { + hitTest = false; + } + hit = true; } - - // we can break now as we have hit an object. } } } - hitTest = keepHitTestingAfterChildren; - // no point running this if the item is not interactive or does not have an interactive parent. if (interactive) { // if we are hit testing (as in we have no hit any objects yet) // We also don't need to worry about hit testing if once of the displayObjects children - // has already been hit! - if (hitTest && !hit) + // has already been hit - but only if it was interactive, otherwise we need to keep + // looking for an interactive child, just in case we hit one + if (hitTest && !interactionEvent.target) { if (displayObject.hitArea) { displayObject.worldTransform.applyInverse(point, this._tempPoint); - hit = displayObject.hitArea.contains(this._tempPoint.x, this._tempPoint.y); + if (displayObject.hitArea.contains(this._tempPoint.x, this._tempPoint.y)) + { + hit = true; + } } else if (displayObject.containsPoint) { - hit = displayObject.containsPoint(point); + if (displayObject.containsPoint(point)) + { + hit = true; + } } } @@ -878,7 +1098,10 @@ interactionEvent.target = displayObject; } - func(interactionEvent, displayObject, hit); + if (func) + { + func(interactionEvent, displayObject, !!hit); + } } } @@ -914,7 +1137,7 @@ { const event = events[i]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -998,7 +1221,7 @@ { const event = events[i]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1098,7 +1321,7 @@ const test = isRightButton ? flags.RIGHT_DOWN : flags.LEFT_DOWN; - const isDown = trackingData !== undefined && (trackingData.flags | test); + const isDown = trackingData !== undefined && (trackingData.flags & test); if (hit) { @@ -1118,11 +1341,11 @@ { if (isRightButton) { - trackingData.rightDown = hit; + trackingData.rightDown = false; } else { - trackingData.leftDown = hit; + trackingData.leftDown = false; } } } @@ -1180,7 +1403,7 @@ { const event = events[i]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1255,7 +1478,7 @@ this.setCursorMode(null); } - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1268,6 +1491,12 @@ { this.emit('mouseout', interactionEvent); } + else + { + // we can get touchleave events after touchend, so we want to make sure we don't + // introduce memory leaks + this.releaseInteractionDataForPointerId(interactionData.identifier); + } } /** @@ -1344,7 +1573,7 @@ // Only mouse and pointer can call onPointerOver, so events will always be length 1 const event = events[0]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1366,12 +1595,14 @@ * Get InteractionData for a given pointerId. Store that data as well * * @private - * @param {number} pointerId - Identifier from a pointer event + * @param {PointerEvent} event - Normalized pointer event, output from normalizeToPointerData * @return {InteractionData} - Interaction data for the given pointer identifier */ - getInteractionDataForPointerId(pointerId) + getInteractionDataForPointerId(event) { - if (pointerId === MOUSE_POINTER_ID) + const pointerId = event.pointerId; + + if (pointerId === MOUSE_POINTER_ID || event.pointerType === 'mouse') { return this.mouse; } diff --git a/src/interaction/InteractionTrackingData.js b/src/interaction/InteractionTrackingData.js index 6a1f69e..8493b63 100644 --- a/src/interaction/InteractionTrackingData.js +++ b/src/interaction/InteractionTrackingData.js @@ -35,8 +35,10 @@ } /** + * Unique pointer id of the event + * * @readonly - * @type {number} Unique pointer id of the event + * @member {number} */ get pointerId() { diff --git a/src/interaction/index.js b/src/interaction/index.js index 56c5b09..72f7d71 100644 --- a/src/interaction/index.js +++ b/src/interaction/index.js @@ -1,4 +1,8 @@ /** + * This namespace contains a renderer plugin for handling mouse, pointer, and touch events. + * + * Do not instantiate this plugin directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. * @namespace PIXI.interaction */ export { default as InteractionData } from './InteractionData'; diff --git a/.travis.yml b/.travis.yml index 3df8b22..14de334 100644 --- a/.travis.yml +++ b/.travis.yml @@ -110,3 +110,43 @@ on: all_branches: true condition: $TRAVIS_TAG + # Deploy config for latest release + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: dist + upload-dir: "release" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: docs + upload-dir: "release/docs" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: coverage + upload-dir: "release/coverage" + on: + all_branches: true + condition: $TRAVIS_TAG \ No newline at end of file diff --git a/README.md b/README.md index d26fadd..3b85f85 100644 --- a/README.md +++ b/README.md @@ -36,22 +36,14 @@ - Wiki: Other misc tutorials and resources are [on the Wiki](https://github.com/pixijs/pixi.js/wiki/Resources). ### Community ### -- Forums: Check out the [forum] (http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow] (http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. +- Forums: Check out the [forum](http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow](http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. - Inspiration: Check out the [gallery](http://www.pixijs.com/gallery) to see some of the amazing things people have created! - Chat: You can join us on [Gitter](https://gitter.im/pixijs/pixi.js) To chat about Pixi. We also now have a Slack channel. If you would like to join it please Send me an email (mat@goodboydigital.com) and I will invite you in. ### Setup ### -It's easy to get started with Pixi.js! Simply grab the pre-built versions from here: - -Release Branch - Nice and stable Pixi.js -- Unminified: [http://pixijs.download/release/pixi.js] -- Minified: [http://pixijs.download/release/pixi.min.js] - -Develop Branch - The bleeding edge version of Pixi.js -- Unminified: [http://pixijs.download/dev/pixi.js] -- Minified: [http://pixijs.download/dev/pixi.min.js] +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. @@ -70,10 +62,10 @@ #### CDN Install (via cdnjs) ```html - + ``` -_Note: `4.2.2` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ +_Note: `4.4.3` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ ### Demos ### @@ -130,7 +122,7 @@ // and the root stage PIXI.Container. var app = new PIXI.Application(); -// The application will create a canvas element for you that you +// The application will create a canvas element for you that you // can then insert into the DOM. document.body.appendChild(app.view); @@ -150,7 +142,7 @@ // Add the bunny to the scene we are building. app.stage.addChild(bunny); - + // Listen for frame updates app.ticker.add(function() { // each frame we spin the bunny around a bit diff --git a/bower.json b/bower.json deleted file mode 100644 index 41b668c..0000000 --- a/bower.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "pixi.js", - "main": "dist/pixi.min.js", - "ignore": [ - "**/.*", - "docs", - "node_modules", - "bower_components", - "test", - "scripts", - "bower.json", - "inch.json" - ], - "dependencies": { - }, - "devDependencies": { - } -} diff --git a/package.json b/package.json index d35cbb2..308469f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.3.5", + "version": "4.4.4", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ @@ -40,11 +40,11 @@ "lib": "babel src --out-dir lib -s", "predocs": "rimraf docs/**", "docs": "jsdoc -c scripts/jsdoc.conf.json -R README.md", - "publish:patch": "npm version patch --no-git-tag-version && npm publish", - "publish:minor": "npm version minor --no-git-tag-version && npm publish", - "publish:major": "npm version major --no-git-tag-version && npm publish", + "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", - "postpublish": "node scripts/release.js" + "postpublish": "git push && git push --tags" }, "files": [ "dist/", @@ -72,7 +72,6 @@ "electron": "^1.4.15", "eslint": "^3.5.0", "floss": "^2.0.1", - "gh-pages": "^0.11.0", "jaguarjs-jsdoc": "^1.0.1", "js-md5": "^0.4.1", "jsdoc": "^3.4.2", diff --git a/scripts/jsdoc.conf.json b/scripts/jsdoc.conf.json index 553e61c..3f52180 100644 --- a/scripts/jsdoc.conf.json +++ b/scripts/jsdoc.conf.json @@ -43,7 +43,7 @@ }, "markdown" : { "parser" : "gfm", - "hardwrap" : true + "hardwrap" : false }, "opts": { "encoding" : "utf8", diff --git a/scripts/release.js b/scripts/release.js deleted file mode 100644 index 156620c..0000000 --- a/scripts/release.js +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env node -'use strict'; - -// Publish script to push releases of the bin files -// the normally are gitignored -const ghpages = require('gh-pages'); -const path = require('path'); -const packageInfo = require(path.join(__dirname, '..', 'package.json')); - -const options = { - src: [ - 'dist/**/*', - 'lib/**/*', - 'src/**/*', - 'scripts/*', - 'scripts/renders/*', - 'test/**/*', - '*.json', - '*.md', - 'LICENSE', - '.eslintrc', - '.editorconfig', - '.travis.yml', - '.babelrc', - ], - dotfiles: true, - branch: 'release', - message: packageInfo.version, - logger: console.log.bind(console), -}; - -ghpages.publish(process.cwd(), options, (err) => -{ - if (err) - { - console.log(err); - process.exit(1); - - return; - } - - process.exit(0); -}); diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index f16bcb1..220427a 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -21,7 +21,7 @@ const DIV_HOOK_ZINDEX = 2; /** - * The Accessibility manager reacreates the ability to tab and and have content read by screen + * The Accessibility manager recreates the ability to tab and and have content read by screen * readers. This is very important as it can possibly help people with disabilities access pixi * content. * diff --git a/src/accessibility/index.js b/src/accessibility/index.js index 568cfb1..0ae108f 100644 --- a/src/accessibility/index.js +++ b/src/accessibility/index.js @@ -1,4 +1,9 @@ /** + * This namespace contains a renderer plugin for interaction accessibility for end-users + * with physical impairments which require screen-renders, keyboard navigation, etc. + * + * Do not instantiate this plugin directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. * @namespace PIXI.accessibility */ export { default as accessibleTarget } from './accessibleTarget'; diff --git a/src/core/Application.js b/src/core/Application.js index 5340578..2fcc729 100644 --- a/src/core/Application.js +++ b/src/core/Application.js @@ -1,6 +1,8 @@ import { autoDetectRenderer } from './autoDetectRenderer'; import Container from './display/Container'; import { shared, Ticker } from './ticker'; +import settings from './settings'; +import { UPDATE_PRIORITY } from './const'; /** * Convenience class to create a new PIXI application. @@ -22,28 +24,52 @@ */ export default class Application { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experience unexplained flickering try setting this to true. - * @param {boolean} [useSharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedLoader=false] - `true` to use PIXI.loaders.shared, `false` to create new Loader. */ - constructor(width, height, options, noWebGL, useSharedTicker = false) + constructor(options, arg2, arg3, arg4, arg5) { + // Support for constructor(width, height, options, noWebGL, useSharedTicker) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + forceCanvas: !!arg4, + sharedTicker: !!arg5, + }, arg3); + } + + /** + * The default options, so we mixin functionality later. + * @member {object} + * @protected + */ + this._options = options = Object.assign({ + sharedTicker: false, + forceCanvas: false, + sharedLoader: false, + }, options); + /** * WebGL renderer if available, otherwise CanvasRenderer * @member {PIXI.WebGLRenderer|PIXI.CanvasRenderer} */ - this.renderer = autoDetectRenderer(width, height, options, noWebGL); + this.renderer = autoDetectRenderer(options); /** * The root display container that's rendered. @@ -63,7 +89,7 @@ * @member {PIXI.ticker.Ticker} * @default PIXI.ticker.shared */ - this.ticker = useSharedTicker ? shared : new Ticker(); + this.ticker = options.sharedTicker ? shared : new Ticker(); // Start the rendering this.start(); @@ -78,7 +104,7 @@ this._ticker = ticker; if (ticker) { - ticker.add(this.render, this); + ticker.add(this.render, this, UPDATE_PRIORITY.LOW); } } get ticker() // eslint-disable-line require-jsdoc @@ -136,13 +162,18 @@ */ destroy(removeView) { - this.stop(); + const oldTicker = this._ticker; + this.ticker = null; + oldTicker.destroy(); + this.stage.destroy(); this.stage = null; this.renderer.destroy(removeView); this.renderer = null; + + this._options = null; } } diff --git a/src/core/autoDetectRenderer.js b/src/core/autoDetectRenderer.js index 8960425..9ec0364 100644 --- a/src/core/autoDetectRenderer.js +++ b/src/core/autoDetectRenderer.js @@ -2,6 +2,7 @@ import CanvasRenderer from './renderers/canvas/CanvasRenderer'; import WebGLRenderer from './renderers/webgl/WebGLRenderer'; +// eslint-disable-next-line valid-jsdoc /** * This helper function will automatically detect which renderer you should be using. * WebGL is the preferred renderer as it is a lot faster. If webGL is not supported by @@ -9,24 +10,32 @@ * * @memberof PIXI * @function autoDetectRenderer - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @return {PIXI.WebGLRenderer|PIXI.CanvasRenderer} Returns WebGL renderer if available, otherwise CanvasRenderer */ -export function autoDetectRenderer(width = 800, height = 600, options, noWebGL) +export function autoDetectRenderer(options, arg1, arg2, arg3) { - if (!noWebGL && utils.isWebGLSupported()) + // Backward-compatible support for noWebGL option + let forceCanvas = options && options.forceCanvas; + + if (arg3 !== undefined) { - return new WebGLRenderer(width, height, options); + forceCanvas = arg3; } - return new CanvasRenderer(width, height, options); + if (!forceCanvas && utils.isWebGLSupported()) + { + return new WebGLRenderer(options, arg1, arg2); + } + + return new CanvasRenderer(options, arg1, arg2); } diff --git a/src/core/const.js b/src/core/const.js index b9e99ae..2c68dee 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -341,3 +341,27 @@ LINEAR_VERTICAL: 0, LINEAR_HORIZONTAL: 1, }; + +/** + * Represents the update priorities used by internal PIXI classes when registered with + * the {@link PIXI.ticker.Ticker} object. Higher priority items are updated first and lower + * priority items, such as render, should go later. + * + * @static + * @constant + * @name UPDATE_PRIORITY + * @memberof PIXI + * @type {object} + * @property {number} INTERACTION=50 Highest priority, used for {@link PIXI.interaction.InteractionManager} + * @property {number} HIGH=25 High priority updating, {@link PIXI.VideoBaseTexture} and {@link PIXI.extras.AnimatedSprite} + * @property {number} NORMAL=0 Default priority for ticker events, see {@link PIXI.ticker.Ticker#add}. + * @property {number} LOW=-25 Low priority used for {@link PIXI.Application} rendering. + * @property {number} UTILITY=-50 Lowest priority used for {@link PIXI.prepare.BasePrepare} utility. + */ +export const UPDATE_PRIORITY = { + INTERACTION: 50, + HIGH: 25, + NORMAL: 0, + LOW: -25, + UTILITY: -50, +}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 4e6c77d..decb42a 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -13,7 +13,6 @@ * * @class * @extends EventEmitter - * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ export default class DisplayObject extends EventEmitter @@ -113,6 +112,29 @@ * @private */ this._mask = null; + + /** + * If the object has been destroyed via destroy(). If true, it should not be used. + * + * @member {boolean} + * @private + * @readonly + */ + this._destroyed = false; + + /** + * Fired when this DisplayObject is added to a Container. + * + * @event PIXI.DisplayObject#added + * @param {PIXI.Container} container - The container added to. + */ + + /** + * Fired when this DisplayObject is removed from a Container. + * + * @event PIXI.DisplayObject#removed + * @param {PIXI.Container} container - The container removed from. + */ } /** @@ -403,6 +425,8 @@ this.interactive = false; this.interactiveChildren = false; + + this._destroyed = true; } /** diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index aafdfe2..f787c4a 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -792,8 +792,8 @@ sprite.worldAlpha = this.worldAlpha * sprite.alpha; sprite.blendMode = this.blendMode; - sprite.texture._frame.width = rect.width; - sprite.texture._frame.height = rect.height; + sprite._texture._frame.width = rect.width; + sprite._texture._frame.height = rect.height; sprite.transform.worldTransform = this.transform.worldTransform; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index a24c775..b9f192d 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -19,11 +19,12 @@ */ export default class SystemRenderer extends EventEmitter { + // eslint-disable-next-line valid-jsdoc /** * @param {string} system - The name of the system this renderer is for. - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -37,27 +38,31 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(system, screenWidth, screenHeight, options) + constructor(system, options, arg2, arg3) { super(); sayHello(system); - // prepare options - if (options) + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') { - for (const i in settings.RENDER_OPTIONS) - { - if (typeof options[i] === 'undefined') - { - options[i] = settings.RENDER_OPTIONS[i]; - } - } + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); } - else - { - options = settings.RENDER_OPTIONS; - } + + // Add the default render options + options = Object.assign({}, settings.RENDER_OPTIONS, options); + + /** + * The supplied constructor options. + * + * @member {Object} + * @readOnly + */ + this.options = options; /** * The type of the renderer. @@ -75,7 +80,7 @@ * * @member {PIXI.Rectangle} */ - this.screen = new Rectangle(0, 0, screenWidth || 800, screenHeight || 600); + this.screen = new Rectangle(0, 0, options.width, options.height); /** * The canvas element that everything is drawn to @@ -279,6 +284,8 @@ this.blendModes = null; + this.options = null; + this.preserveDrawingBuffer = false; this.clearBeforeRender = false; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index e84e49c..691caf0 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -17,10 +17,11 @@ */ export default class CanvasRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -34,9 +35,9 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('Canvas', screenWidth, screenHeight, options); + super('Canvas', options, arg2, arg3); this.type = RENDERER_TYPE.CANVAS; @@ -96,7 +97,19 @@ this.context = null; this.renderingToScreen = false; - this.resize(screenWidth, screenHeight); + this.resize(this.options.width, this.options.height); + + /** + * Fired after rendering finishes. + * + * @event PIXI.CanvasRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.CanvasRenderer#prerender + */ } /** @@ -299,4 +312,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.CanvasRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.CanvasExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.CanvasPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.CanvasRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index e1cf7cd..f6fe123 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -31,11 +31,12 @@ */ export default class WebGLRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** * - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -55,9 +56,9 @@ * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('WebGL', screenWidth, screenHeight, options); + super('WebGL', options, arg2, arg3); /** * The type of this renderer as a standardised const @@ -89,12 +90,6 @@ }; - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ this._backgroundColorRgba[3] = this.transparent ? 0 : 1; this.globalUniforms = new UniformGroup({ @@ -117,6 +112,13 @@ this.initPlugins(); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ if(options.context) { this.context.initFromContext(options.context); @@ -169,6 +171,25 @@ } return this; + + /** + * Fired after rendering finishes. + * + * @event PIXI.WebGLRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.WebGLRenderer#prerender + */ + + /** + * Fired when the WebGL context is set. + * + * @event PIXI.WebGLRenderer#context + * @param {WebGLRenderingContext} gl - WebGL context. + */ } /** @@ -295,4 +316,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.WebGLRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.WebGLRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(WebGLRenderer); diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index 4eb6128..fa3d883 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -47,6 +47,14 @@ * @member {boolean} */ this.enabled = true; + + /** + * If enabled, pixi will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; } /** diff --git a/src/core/renderers/webgl/systems/ContextSystem.js b/src/core/renderers/webgl/systems/ContextSystem.js index ff958c7..8df85ea 100644 --- a/src/core/renderers/webgl/systems/ContextSystem.js +++ b/src/core/renderers/webgl/systems/ContextSystem.js @@ -75,7 +75,8 @@ */ createContext(canvas, options) { - var gl = canvas.getContext('webgl', options) || + var gl = //canvas.getContext('webgl2', options) || + canvas.getContext('webgl', options) || canvas.getContext('experimental-webgl', options); if (!gl) diff --git a/src/core/renderers/webgl/systems/FilterSystem.js b/src/core/renderers/webgl/systems/FilterSystem.js index f47335d..37e28d2 100644 --- a/src/core/renderers/webgl/systems/FilterSystem.js +++ b/src/core/renderers/webgl/systems/FilterSystem.js @@ -111,7 +111,7 @@ // TODO we should fit the rect around the transform.. } - else + else if (filters[0].autoFit) { sourceFrame.fit(filterData.stack[0].destinationFrame); } diff --git a/src/core/renderers/webgl/systems/shader/shader/mapType.js b/src/core/renderers/webgl/systems/shader/shader/mapType.js index 581dace..917538a 100644 --- a/src/core/renderers/webgl/systems/shader/shader/mapType.js +++ b/src/core/renderers/webgl/systems/shader/shader/mapType.js @@ -15,7 +15,7 @@ } } - return GL_TABLE[type]; + return GL_TABLE[type]; }; var GL_TABLE = null; diff --git a/src/core/settings.js b/src/core/settings.js index 18c5877..38ef45c 100644 --- a/src/core/settings.js +++ b/src/core/settings.js @@ -2,6 +2,15 @@ import canUploadSameBuffer from './utils/canUploadSameBuffer'; /** + * User's customizable globals for overriding the default PIXI settings, such + * as a renderer's default resolution, framerate, float percision, etc. + * @example + * // Use the native window resolution as the default resolution + * // will support high-density displays when rendering + * PIXI.settings.RESOLUTION = window.devicePixelRatio. + * + * // Disable interpolation when scaling, will make texture be pixelated + * PIXI.settings.SCALE_MODE = PIXI.SCALE_MODES.NEAREST; * @namespace PIXI.settings */ export default { @@ -101,6 +110,9 @@ * @property {boolean} clearBeforeRender=true * @property {boolean} preserveDrawingBuffer=false * @property {boolean} roundPixels=false + * @property {number} width=800 + * @property {number} height=600 + * @property {boolean} legacy=false */ RENDER_OPTIONS: { view: null, @@ -112,6 +124,9 @@ clearBeforeRender: true, preserveDrawingBuffer: false, roundPixels: false, + width: 800, + height: 600, + legacy: false, }, /** diff --git a/src/core/shader/Program.js b/src/core/shader/Program.js index c0b538b..07b4398 100644 --- a/src/core/shader/Program.js +++ b/src/core/shader/Program.js @@ -1,6 +1,6 @@ import extractUniformsFromSrc from './extractUniformsFromSrc'; import generateUniformsSync from './generateUniformsSync'; -import glCore from 'pixi-gl-core'; +import shaderUtils from '../renderers/webgl/systems/shader/shader'; import { ProgramCache } from '../utils'; import getTestContext from '../utils/getTestContext'; @@ -64,14 +64,17 @@ } else { - vertexSrc = glCore.shader.setPrecision(vertexSrc, 'mediump'); - fragmentSrc = glCore.shader.setPrecision(fragmentSrc, 'mediump'); + vertexSrc = shaderUtils.setPrecision(vertexSrc, 'mediump'); + fragmentSrc = shaderUtils.setPrecision(fragmentSrc, 'mediump'); - const program = glCore.shader.compileProgram(gl, vertexSrc, fragmentSrc); + const program = shaderUtils.compileProgram(gl, vertexSrc, fragmentSrc); + console.log("<<<<<<>>><<"); this.attributeData = this.getAttributeData(program, gl); this.uniformData = this.getUniformData(program, gl); + console.log(this.uniformData); + //gl.deleteProgram(program); } } @@ -95,13 +98,13 @@ for (let i = 0; i < totalAttributes; i++) { const attribData = gl.getActiveAttrib(program, i); - const type = glCore.shader.mapType(gl, attribData.type); + const type = shaderUtils.mapType(gl, attribData.type); /*eslint-disable */ const data = { type: type, name: attribData.name, - size: glCore.shader.mapSize(type), + size: shaderUtils.mapSize(type), location: 0, }; /*eslint-enable */ @@ -137,13 +140,14 @@ // TODO expose this as a prop? // const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); - const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); + //const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); for (let i = 0; i < totalUniforms; i++) { const uniformData = gl.getActiveUniform(program, i); const name = uniformData.name.replace(/\[.*?\]/, ''); - const type = glCore.shader.mapType(gl, uniformData.type); + const type = shaderUtils.mapType(gl, uniformData.type); + console.log('mapping ' + uniformData.type + ' to ' + type); // if (!name.match(maskRegex)) { @@ -151,7 +155,7 @@ uniforms[name] = { type: type, size: uniformData.size, - value: glCore.shader.defaultValue(type, uniformData.size), + value: shaderUtils.defaultValue(type, uniformData.size), }; /*eslint-enable */ } diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index e387e99..b57d095 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -147,12 +147,12 @@ // so if _width is 0 then width was not set.. if (this._width) { - this.scale.x = sign(this.scale.x) * this._width / this.texture.orig.width; + this.scale.x = sign(this.scale.x) * this._width / this._texture.orig.width; } if (this._height) { - this.scale.y = sign(this.scale.y) * this._height / this.texture.orig.height; + this.scale.y = sign(this.scale.y) * this._height / this._texture.orig.height; } } diff --git a/src/core/sprites/canvas/CanvasTinter.js b/src/core/sprites/canvas/CanvasTinter.js index 27e5e0c..fd9de5d 100644 --- a/src/core/sprites/canvas/CanvasTinter.js +++ b/src/core/sprites/canvas/CanvasTinter.js @@ -18,7 +18,7 @@ */ getTintedTexture: (sprite, color) => { - const texture = sprite.texture; + const texture = sprite._texture; color = CanvasTinter.roundColor(color); diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 5a4edda..8b9b797 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -344,6 +344,7 @@ const texture = this._texture; const style = this._style; + const padding = style.trim ? 0 : style.padding; texture.baseTexture.valid = true; texture.baseTexture.resolution = this.resolution; @@ -353,11 +354,11 @@ texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - texture.trim.x = -style.padding; - texture.trim.y = -style.padding; + texture.trim.x = -padding; + texture.trim.y = -padding; - texture.orig.width = texture._frame.width - (style.padding * 2); - texture.orig.height = texture._frame.height - (style.padding * 2); + texture.orig.width = texture._frame.width - (padding * 2); + texture.orig.height = texture._frame.height - (padding * 2); // call sprite onTextureUpdate to update scale if _width or _height were set this._onTextureUpdate(); diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index 853da3d..8a9c34b 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -114,6 +114,38 @@ this.cacheId = null; this.validate(); + + /** + * Fired when a not-immediately-available source finishes loading. + * + * @protected + * @event PIXI.BaseTexture#loaded + * @param {PIXI.BaseTexture} baseTexture - Resource loaded. + */ + + /** + * Fired when a not-immediately-available source fails to load. + * + * @protected + * @event PIXI.BaseTexture#error + * @param {PIXI.BaseTexture} baseTexture - Resource errored. + */ + + /** + * Fired when BaseTexture is updated. + * + * @protected + * @event PIXI.BaseTexture#update + * @param {PIXI.BaseTexture} baseTexture - Instance of texture being updated. + */ + + /** + * Fired when BaseTexture is destroyed. + * + * @protected + * @event PIXI.BaseTexture#dispose + * @param {PIXI.BaseTexture} baseTexture - Instance of texture being destroyed. + */ } setResource(resource) diff --git a/src/core/textures/Spritesheet.js b/src/core/textures/Spritesheet.js index d469c62..9b24f27 100644 --- a/src/core/textures/Spritesheet.js +++ b/src/core/textures/Spritesheet.js @@ -208,6 +208,7 @@ // lets also add the frame to pixi's global cache for fromFrame and fromImage functions TextureCache[i] = this.textures[i]; + this.textures[i].textureCacheId = i; } frameIndex++; diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 0a2c379..2213087 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -147,9 +147,9 @@ /** * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. * - * @event update - * @memberof PIXI.Texture# + * @event PIXI.Texture#update * @protected + * @param {PIXI.Texture} texture - Instance of texture being updated. */ this._updateID = 0; @@ -159,6 +159,15 @@ * @type {Object} */ this.transform = null; + + /** + * The id under which this Texture has been added to the texture cache. This is + * automatically set in certain cases, but may not always be accurate, particularly if + * the texture is in the cache under multiple ids. + * + * @member {string} + */ + this.textureCacheId = null; } /** @@ -225,11 +234,17 @@ this._uvs = null; this.trim = null; this.orig = null; + this.textureCacheId = null; this.valid = false; - this.off('dispose', this.dispose, this); - this.off('update', this.update, this); + for (const prop in TextureCache) + { + if (TextureCache[prop] === this) + { + delete TextureCache[prop]; + } + } } /** @@ -315,14 +330,15 @@ */ static fromLoader(source, imageUrl, name) { - // console.log('added from loader...') + // console.log('added from loader...') const resource = new ImageResource(source);//.from(imageUrl, crossorigin);// document.createElement('img'); - // console.log('base resource ' + resource.width); + // console.log('base resource ' + resource.width); const baseTexture = new BaseTexture(resource, settings.SCALE_MODE, getResolutionOfUrl(imageUrl)); + /// console.log('base width ' + baseTexture.width); const texture = new Texture(baseTexture); @@ -335,6 +351,7 @@ // lets also add the frame to pixi's global cache for fromFrame and fromImage fucntions BaseTextureCache[name] = baseTexture; TextureCache[name] = texture; + texture.textureCacheId = name; // also add references by url if they are different. if (name !== imageUrl) @@ -355,6 +372,10 @@ */ static addTextureToCache(texture, id) { + if (!texture.textureCacheId) + { + texture.textureCacheId = id; + } TextureCache[id] = texture; } @@ -495,6 +516,7 @@ */ Texture.EMPTY = new Texture(new BaseTexture()); removeAllHandlers(Texture.EMPTY); +removeAllHandlers(Texture.EMPTY.baseTexture); /** * A white texture of 10x10 size, used for graphics and other things @@ -505,3 +527,4 @@ */ Texture.WHITE = createWhiteTexture(); removeAllHandlers(Texture.WHITE); +removeAllHandlers(Texture.WHITE.baseTexture); diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index 59eb9fd..a9b9f51 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -1,6 +1,7 @@ import BaseTexture from './BaseTexture'; import { uid, BaseTextureCache } from '../utils'; -import * as ticker from '../ticker'; +import { shared } from '../ticker'; +import { UPDATE_PRIORITY } from '../const'; /** * A texture of a [playing] Video. @@ -125,7 +126,7 @@ if (!this._isAutoUpdating && this.autoUpdate) { - ticker.shared.add(this.update, this); + shared.add(this.update, this, UPDATE_PRIORITY.HIGH); this._isAutoUpdating = true; } } @@ -139,7 +140,7 @@ { if (this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); this._isAutoUpdating = false; } } @@ -187,7 +188,7 @@ { if (this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); } if (this.source && this.source._pixiId) @@ -281,12 +282,12 @@ if (!this._autoUpdate && this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); this._isAutoUpdating = false; } else if (this._autoUpdate && !this._isAutoUpdating) { - ticker.shared.add(this.update, this); + shared.add(this.update, this, UPDATE_PRIORITY.HIGH); this._isAutoUpdating = true; } } diff --git a/src/core/ticker/Ticker.js b/src/core/ticker/Ticker.js index 17a1517..40df138 100644 --- a/src/core/ticker/Ticker.js +++ b/src/core/ticker/Ticker.js @@ -1,12 +1,10 @@ import settings from '../settings'; -import EventEmitter from 'eventemitter3'; - -// Internal event used by composed emitter -const TICK = 'tick'; +import { UPDATE_PRIORITY } from '../const'; +import TickerListener from './TickerListener'; /** * A Ticker class that runs an update loop that other objects listen to. - * This class is composed around an EventEmitter object to add listeners + * This class is composed around listeners * meant for execution on the next requested animation frame. * Animation frames are requested only when necessary, * e.g. When the ticker is started and the emitter has listeners. @@ -22,10 +20,11 @@ constructor() { /** - * Internal emitter used to fire 'tick' event + * The first listener. All new listeners added are chained on this. * @private + * @type {TickerListener} */ - this._emitter = new EventEmitter(); + this._head = new TickerListener(null, null, Infinity); /** * Internal current frame request ID @@ -131,7 +130,7 @@ // Invoke listeners now this.update(time); // Listener side effects may have modified ticker state. - if (this.started && this._requestId === null && this._emitter.listeners(TICK, true)) + if (this.started && this._requestId === null && this._head.next) { this._requestId = requestAnimationFrame(this._tick); } @@ -148,7 +147,7 @@ */ _requestIfNeeded() { - if (this._requestId === null && this._emitter.listeners(TICK, true)) + if (this._requestId === null && this._head.next) { // ensure callbacks get correct delta this.lastTime = performance.now(); @@ -193,35 +192,72 @@ } /** - * Calls {@link module:eventemitter3.EventEmitter#on} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. + * Register a handler for tick events. Calls continuously unless + * it is removed or the ticker is stopped. * * @param {Function} fn - The listener function to be added for updates * @param {Function} [context] - The listener context + * @param {number} [priority=PIXI.UPDATE_PRIORITY.NORMAL] - The priority for emitting * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - add(fn, context) + add(fn, context, priority = UPDATE_PRIORITY.NORMAL) { - this._emitter.on(TICK, fn, context); - - this._startIfPossible(); - - return this; + return this._addListener(new TickerListener(fn, context, priority)); } /** - * Calls {@link module:eventemitter3.EventEmitter#once} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. + * Add a handler for the tick event which is only execute once. * * @param {Function} fn - The listener function to be added for one update * @param {Function} [context] - The listener context + * @param {number} [priority=PIXI.UPDATE_PRIORITY.NORMAL] - The priority for emitting * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - addOnce(fn, context) + addOnce(fn, context, priority = UPDATE_PRIORITY.NORMAL) { - this._emitter.once(TICK, fn, context); + return this._addListener(new TickerListener(fn, context, priority, true)); + } + + /** + * Internally adds the event handler so that it can be sorted by priority. + * Priority allows certain handler (user, AnimatedSprite, Interaction) to be run + * before the rendering. + * + * @private + * @param {TickerListener} listener - Current listener being added. + * @returns {PIXI.ticker.Ticker} This instance of a ticker + */ + _addListener(listener) + { + // For attaching to head + let current = this._head.next; + let previous = this._head; + + // Add the first item + if (!current) + { + listener.connect(previous); + } + else + { + // Go from highest to lowest priority + while (current) + { + if (listener.priority >= current.priority) + { + listener.connect(previous); + break; + } + previous = current; + current = current.next; + } + + // Not yet connected + if (!listener.previous) + { + listener.connect(previous); + } + } this._startIfPossible(); @@ -229,19 +265,33 @@ } /** - * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. - * It checks if the emitter has listeners for 'tick' event. - * If it does, then it cancels the animation frame. + * Removes any handlers matching the function and context parameters. + * If no handlers are left after removing, then it cancels the animation frame. * - * @param {Function} [fn] - The listener function to be removed + * @param {Function} fn - The listener function to be removed * @param {Function} [context] - The listener context to be removed * @returns {PIXI.ticker.Ticker} This instance of a ticker */ remove(fn, context) { - this._emitter.off(TICK, fn, context); + let listener = this._head.next; - if (!this._emitter.listeners(TICK, true)) + while (listener) + { + // We found a match, lets remove it + // no break to delete all possible matches + // incase a listener was added 2+ times + if (listener.match(fn, context)) + { + listener = listener.destroy(); + } + else + { + listener = listener.next; + } + } + + if (!this._head.next) { this._cancelIfNeeded(); } @@ -276,6 +326,25 @@ } /** + * Destroy the ticker and don't use after this. Calling + * this method removes all references to internal events. + */ + destroy() + { + this.stop(); + + let listener = this._head.next; + + while (listener) + { + listener = listener.destroy(true); + } + + this._head.destroy(); + this._head = null; + } + + /** * Triggers an update. An update entails setting the * current {@link PIXI.ticker.Ticker#elapsedMS}, * the current {@link PIXI.ticker.Ticker#deltaTime}, @@ -320,8 +389,22 @@ this.deltaTime = elapsedMS * settings.TARGET_FPMS * this.speed; + // Cache a local reference, in-case ticker is destroyed + // during the emit, we can still check for head.next + const head = this._head; + // Invoke listeners added to internal emitter - this._emitter.emit(TICK, this.deltaTime); + let listener = head.next; + + while (listener) + { + listener = listener.emit(this.deltaTime); + } + + if (!head.next) + { + this._cancelIfNeeded(); + } } else { diff --git a/src/core/ticker/TickerListener.js b/src/core/ticker/TickerListener.js new file mode 100644 index 0000000..2bedb34 --- /dev/null +++ b/src/core/ticker/TickerListener.js @@ -0,0 +1,158 @@ +/** + * Internal class for handling the priority sorting of ticker handlers. + * + * @private + * @class + * @memberof PIXI.ticker + */ +export default class TickerListener +{ + /** + * Constructor + * + * @param {Function} fn - The listener function to be added for one update + * @param {Function} [context=null] - The listener context + * @param {number} [priority=0] - The priority for emitting + * @param {boolean} [once=false] - If the handler should fire once + */ + constructor(fn, context = null, priority = 0, once = false) + { + /** + * The handler function to execute. + * @member {Function} + */ + this.fn = fn; + + /** + * The calling to execute. + * @member {Function} + */ + this.context = context; + + /** + * The current priority. + * @member {number} + */ + this.priority = priority; + + /** + * If this should only execute once. + * @member {boolean} + */ + this.once = once; + + /** + * The next item in chain. + * @member {TickerListener} + */ + this.next = null; + + /** + * The previous item in chain. + * @member {TickerListener} + */ + this.previous = null; + + /** + * `true` if this listener has been destroyed already. + * @member {boolean} + * @private + */ + this._destroyed = false; + } + + /** + * Simple compare function to figure out if a function and context match. + * + * @param {Function} fn - The listener function to be added for one update + * @param {Function} context - The listener context + * @return {boolean} `true` if the listener match the arguments + */ + match(fn, context) + { + context = context || null; + + return this.fn === fn && this.context === context; + } + + /** + * Emit by calling the current function. + * @param {number} deltaTime - time since the last emit. + * @return {TickerListener} Next ticker + */ + emit(deltaTime) + { + if (this.context) + { + this.fn.call(this.context, deltaTime); + } + else + { + this.fn(deltaTime); + } + + if (this.once) + { + this.destroy(); + } + + const redirect = this.next; + + // Soft-destroying should remove + // the next reference + if (this._destroyed) + { + this.next = null; + } + + return redirect; + } + + /** + * Connect to the list. + * @param {TickerListener} previous - Input node, previous listener + */ + connect(previous) + { + this.previous = previous; + if (previous.next) + { + previous.next.previous = this; + } + this.next = previous.next; + previous.next = this; + } + + /** + * Destroy and don't use after this. + * @param {boolean} [hard = false] `true` to remove the `next` reference, this + * is considered a hard destroy. Soft destroy maintains the next reference. + * @return {TickerListener} The listener to redirect while emitting or removing. + */ + destroy(hard = false) + { + this._destroyed = true; + this.fn = null; + this.context = null; + + // Disconnect, hook up next and previous + if (this.previous) + { + this.previous.next = this.next; + } + + if (this.next) + { + this.next.previous = this.previous; + } + + // Redirect to the next item + const redirect = this.previous; + + // Remove references + this.next = hard ? null : redirect; + this.previous = null; + + return redirect; + } +} diff --git a/src/core/ticker/index.js b/src/core/ticker/index.js index 4b3017c..c3020d4 100644 --- a/src/core/ticker/index.js +++ b/src/core/ticker/index.js @@ -45,8 +45,25 @@ const shared = new Ticker(); shared.autoStart = true; +shared.destroy = () => +{ + // protect destroying shared ticker + // this is used by other internal systems + // like AnimatedSprite and InteractionManager +}; /** + * This namespace contains an API for interacting with PIXI's internal global update loop. + * + * This ticker is used for rendering, {@link PIXI.extras.AnimatedSprite AnimatedSprite}, + * {@link PIXI.interaction.InteractionManager InteractionManager} and many other time-based PIXI systems. + * @example + * const ticker = new PIXI.ticker.Ticker(); + * ticker.stop(); + * ticker.add((deltaTime) => { + * // do something every frame + * }); + * ticker.start(); * @namespace PIXI.ticker */ export { shared, Ticker }; diff --git a/src/core/utils/index.js b/src/core/utils/index.js index be44750..7441f7b 100644 --- a/src/core/utils/index.js +++ b/src/core/utils/index.js @@ -9,6 +9,21 @@ let saidHello = false; /** + * Generalized convenience utilities for PIXI. + * @example + * // Extend PIXI's internal Event Emitter. + * class MyEmitter extends PIXI.utils.EventEmitter { + * constructor() { + * super(); + * console.log("Emitter created!"); + * } + * } + * + * // Get info on current device + * console.log(PIXI.utils.isMobile); + * + * // Convert hex color to string + * console.log(PIXI.utils.hex2string(0xff00ff)); // returns: "#ff00ff" * @namespace PIXI.utils */ export { diff --git a/src/deprecation.js b/src/deprecation.js index b9c73b3..33daeec 100644 --- a/src/deprecation.js +++ b/src/deprecation.js @@ -918,6 +918,35 @@ }); /** + * @method + * @name PIXI.prepare.BasePrepare#register + * @see PIXI.prepare.BasePrepare#registerFindHook + * @deprecated since version 4.4.2 + * @param {Function} [addHook] - Function call that takes two parameters: `item:*, queue:Array` + * function must return `true` if it was able to add item to the queue. + * @param {Function} [uploadHook] - Function call that takes two parameters: `prepare:CanvasPrepare, item:*` and + * function must return `true` if it was able to handle upload of item. + * @return {PIXI.BasePrepare} Instance of plugin for chaining. + */ +prepare.BasePrepare.prototype.register = function register(addHook, uploadHook) +{ + warn('renderer.plugins.prepare.register is now deprecated, ' + + 'please use renderer.plugins.prepare.registerFindHook & renderer.plugins.prepare.registerUploadHook'); + + if (addHook) + { + this.registerFindHook(addHook); + } + + if (uploadHook) + { + this.registerUploadHook(uploadHook); + } + + return this; +}; + +/** * The number of graphics or textures to upload to the GPU. * * @name PIXI.prepare.canvas.UPLOADS_PER_FRAME @@ -969,67 +998,73 @@ }, }); -Object.defineProperties(loaders.Resource.prototype, { - isJson: { - get() - { - warn('The isJson property is deprecated, please use `resource.type === Resource.TYPE.JSON`.'); +if (loaders.Loader) +{ + const Resource = loaders.Resource; + const Loader = loaders.Loader; - return this.type === loaders.Loader.Resource.TYPE.JSON; + Object.defineProperties(Resource.prototype, { + isJson: { + get() + { + warn('The isJson property is deprecated, please use `resource.type === Resource.TYPE.JSON`.'); + + return this.type === Resource.TYPE.JSON; + }, }, - }, - isXml: { - get() - { - warn('The isXml property is deprecated, please use `resource.type === Resource.TYPE.XML`.'); + isXml: { + get() + { + warn('The isXml property is deprecated, please use `resource.type === Resource.TYPE.XML`.'); - return this.type === loaders.Loader.Resource.TYPE.XML; + return this.type === Resource.TYPE.XML; + }, }, - }, - isImage: { - get() - { - warn('The isImage property is deprecated, please use `resource.type === Resource.TYPE.IMAGE`.'); + isImage: { + get() + { + warn('The isImage property is deprecated, please use `resource.type === Resource.TYPE.IMAGE`.'); - return this.type === loaders.Loader.Resource.TYPE.IMAGE; + return this.type === Resource.TYPE.IMAGE; + }, }, - }, - isAudio: { - get() - { - warn('The isAudio property is deprecated, please use `resource.type === Resource.TYPE.AUDIO`.'); + isAudio: { + get() + { + warn('The isAudio property is deprecated, please use `resource.type === Resource.TYPE.AUDIO`.'); - return this.type === loaders.Loader.Resource.TYPE.AUDIO; + return this.type === Resource.TYPE.AUDIO; + }, }, - }, - isVideo: { - get() - { - warn('The isVideo property is deprecated, please use `resource.type === Resource.TYPE.VIDEO`.'); + isVideo: { + get() + { + warn('The isVideo property is deprecated, please use `resource.type === Resource.TYPE.VIDEO`.'); - return this.type === loaders.Loader.Resource.TYPE.VIDEO; + return this.type === Resource.TYPE.VIDEO; + }, }, - }, -}); + }); -Object.defineProperties(loaders.Loader.prototype, { - before: { - get() - { - warn('The before() method is deprecated, please use pre().'); + Object.defineProperties(Loader.prototype, { + before: { + get() + { + warn('The before() method is deprecated, please use pre().'); - return this.pre; + return this.pre; + }, }, - }, - after: { - get() - { - warn('The after() method is deprecated, please use use().'); + after: { + get() + { + warn('The after() method is deprecated, please use use().'); - return this.use; + return this.use; + }, }, - }, -}); + }); +} /** * @name PIXI.interaction.interactiveTarget#defaultCursor diff --git a/src/extract/canvas/CanvasExtract.js b/src/extract/canvas/CanvasExtract.js index 941ca05..3ac5aee 100644 --- a/src/extract/canvas/CanvasExtract.js +++ b/src/extract/canvas/CanvasExtract.js @@ -8,7 +8,7 @@ * An instance of this class is automatically created by default, and can be found at renderer.plugins.extract * * @class - * @memberof PIXI + * @memberof PIXI.extract */ export default class CanvasExtract { @@ -21,9 +21,9 @@ /** * Collection of methods for extracting data (image, pixels, etc.) from a display object or render texture * - * @member {PIXI.CanvasExtract} extract + * @member {PIXI.extract.CanvasExtract} extract * @memberof PIXI.CanvasRenderer# - * @see PIXI.CanvasExtract + * @see PIXI.extract.CanvasExtract */ renderer.extract = this; } diff --git a/src/extract/index.js b/src/extract/index.js index 0f2170d..e9478c1 100644 --- a/src/extract/index.js +++ b/src/extract/index.js @@ -1,2 +1,22 @@ +/** + * This namespace provides renderer-specific plugins for exporting content from a renderer. + * For instance, these plugins can be used for saving an Image, Canvas element or for exporting the raw image data (pixels). + * + * Do not instantiate these plugins directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. + * @example + * // Create a new app (will auto-add extract plugin to renderer) + * const app = new PIXI.Application(); + * + * // Draw a red circle + * const graphics = new PIXI.Graphics() + * .beginFill(0xFF0000) + * .drawCircle(0, 0, 50); + * + * // Render the graphics as an HTMLImageElement + * const image = app.renderer.plugins.extract.image(graphics); + * document.body.appendChild(image); + * @namespace PIXI.extract + */ export { default as webgl } from './webgl/WebGLExtract'; export { default as canvas } from './canvas/CanvasExtract'; diff --git a/src/extract/webgl/WebGLExtract.js b/src/extract/webgl/WebGLExtract.js index 60228bb..1c3dd30 100644 --- a/src/extract/webgl/WebGLExtract.js +++ b/src/extract/webgl/WebGLExtract.js @@ -9,7 +9,7 @@ * An instance of this class is automatically created by default, and can be found at renderer.plugins.extract * * @class - * @memberof PIXI + * @memberof PIXI.extract */ export default class WebGLExtract { @@ -22,9 +22,9 @@ /** * Collection of methods for extracting data (image, pixels, etc.) from a display object or render texture * - * @member {PIXI.WebGLExtract} extract + * @member {PIXI.extract.WebGLExtract} extract * @memberof PIXI.WebGLRenderer# - * @see PIXI.WebGLExtract + * @see PIXI.extract.WebGLExtract */ renderer.extract = this; } diff --git a/src/extras/AnimatedSprite.js b/src/extras/AnimatedSprite.js index 13b804f..5297778 100644 --- a/src/extras/AnimatedSprite.js +++ b/src/extras/AnimatedSprite.js @@ -137,7 +137,7 @@ this.playing = true; if (this._autoUpdate) { - core.ticker.shared.add(this.update, this); + core.ticker.shared.add(this.update, this, core.UPDATE_PRIORITY.HIGH); } } diff --git a/src/extras/TextureTransform.js b/src/extras/TextureTransform.js index 78e6e40..16536d2 100644 --- a/src/extras/TextureTransform.js +++ b/src/extras/TextureTransform.js @@ -65,8 +65,36 @@ } /** + * Multiplies uvs array to transform + * @param {Float32Array} uvs mesh uvs + * @param {Float32Array} [out=uvs] output + * @returns {Float32Array} output + */ + multiplyUvs(uvs, out) + { + if (out === undefined) + { + out = uvs; + } + + const mat = this.mapCoord; + + for (let i = 0; i < uvs.length; i += 2) + { + const x = uvs[i]; + const y = uvs[i + 1]; + + out[i] = (x * mat.a) + (y * mat.c) + mat.tx; + out[i + 1] = (x * mat.b) + (y * mat.d) + mat.ty; + } + + return out; + } + + /** * updates matrices if texture was changed * @param {boolean} forceUpdate if true, matrices will be updated any case + * @returns {boolean} whether or not it was updated */ update(forceUpdate) { @@ -74,13 +102,13 @@ if (!tex || !tex.valid) { - return; + return false; } if (!forceUpdate && this._lastTextureID === tex._updateID) { - return; + return false; } this._lastTextureID = tex._updateID; @@ -110,5 +138,7 @@ frame[3] = (tex._frame.y + tex._frame.height - margin + offset) / texBase.height; this.uClampOffset[0] = offset / texBase.realWidth; this.uClampOffset[1] = offset / texBase.realHeight; + + return true; } } diff --git a/src/extras/TilingSprite.js b/src/extras/TilingSprite.js index 1f6014f..a3f027f 100644 --- a/src/extras/TilingSprite.js +++ b/src/extras/TilingSprite.js @@ -180,9 +180,9 @@ const transform = this.worldTransform; const resolution = renderer.resolution; const baseTexture = texture.baseTexture; - const baseTextureResolution = texture.baseTexture.resolution; - const modX = (this.tilePosition.x / this.tileScale.x) % texture._frame.width; - const modY = (this.tilePosition.y / this.tileScale.y) % texture._frame.height; + const baseTextureResolution = baseTexture.resolution; + const modX = ((this.tilePosition.x / this.tileScale.x) % texture._frame.width) * baseTextureResolution; + const modY = ((this.tilePosition.y / this.tileScale.y) % texture._frame.height) * baseTextureResolution; // create a nice shiny pattern! // TODO this needs to be refreshed if texture changes.. diff --git a/src/extras/cacheAsBitmap.js b/src/extras/cacheAsBitmap.js index 5dd5640..aea06a8 100644 --- a/src/extras/cacheAsBitmap.js +++ b/src/extras/cacheAsBitmap.js @@ -227,7 +227,16 @@ this.transform._parentID = -1; // restore the transform of the cached sprite to avoid the nasty flicker.. - this.updateTransform(); + if (!this.parent) + { + this.parent = renderer._tempDisplayObjectParent; + this.updateTransform(); + this.parent = null; + } + else + { + this.updateTransform(); + } // map the hit test.. this.containsPoint = cachedSprite.containsPoint.bind(cachedSprite); @@ -314,7 +323,17 @@ cachedSprite._bounds = this._bounds; cachedSprite.alpha = cacheAlpha; - this.updateTransform(); + if (!this.parent) + { + this.parent = renderer._tempDisplayObjectParent; + this.updateTransform(); + this.parent = null; + } + else + { + this.updateTransform(); + } + this.updateTransform = this.displayObjectUpdateTransform; this._cacheData.sprite = cachedSprite; @@ -358,9 +377,12 @@ * Destroys the cached object. * * @private + * @param {object|boolean} [options] - Options parameter. A boolean will act as if all options + * have been set to that value. + * Used when destroying containers, see the Container.destroy method. */ -DisplayObject.prototype._cacheAsBitmapDestroy = function _cacheAsBitmapDestroy() +DisplayObject.prototype._cacheAsBitmapDestroy = function _cacheAsBitmapDestroy(options) { this.cacheAsBitmap = false; - this.destroy(); + this.destroy(options); }; diff --git a/src/extras/index.js b/src/extras/index.js index bbd8bbf..a34d7ec 100644 --- a/src/extras/index.js +++ b/src/extras/index.js @@ -1,7 +1,7 @@ /** + * Additional PIXI DisplayObjects for animation, tiling and bitmap text. * @namespace PIXI.extras */ -export { default as TextureTransform } from './TextureTransform'; export { default as AnimatedSprite } from './AnimatedSprite'; export { default as TilingSprite } from './TilingSprite'; export { default as TilingSpriteRenderer } from './webgl/TilingSpriteRenderer'; diff --git a/src/extras/webgl/TilingSpriteRenderer.js b/src/extras/webgl/TilingSpriteRenderer.js index aeec172..eee3b5c 100644 --- a/src/extras/webgl/TilingSpriteRenderer.js +++ b/src/extras/webgl/TilingSpriteRenderer.js @@ -11,7 +11,7 @@ * WebGL renderer plugin for tiling sprites * * @class - * @memberof PIXI + * @memberof PIXI.extras * @extends PIXI.ObjectRenderer */ export default class TilingSpriteRenderer extends core.ObjectRenderer @@ -118,7 +118,7 @@ tempMat.invert(); if (isSimple) { - tempMat.append(uv.mapCoord); + tempMat.prepend(uv.mapCoord); } else { diff --git a/src/filters/displacement/DisplacementFilter.js b/src/filters/displacement/DisplacementFilter.js index d5355c3..b5d030c 100644 --- a/src/filters/displacement/DisplacementFilter.js +++ b/src/filters/displacement/DisplacementFilter.js @@ -35,7 +35,7 @@ this.maskSprite = sprite; this.maskMatrix = maskMatrix; - this.uniforms.mapSampler = sprite.texture; + this.uniforms.mapSampler = sprite._texture; this.uniforms.filterMatrix = maskMatrix; this.uniforms.scale = { x: 1, y: 1 }; diff --git a/src/filters/index.js b/src/filters/index.js index f3e8862..7ceb50a 100644 --- a/src/filters/index.js +++ b/src/filters/index.js @@ -1,4 +1,21 @@ /** + * This namespace contains WebGL-only display filters that can be applied + * to DisplayObjects using the {@link PIXI.DisplayObject#filters filters} property. + * @example + * // Create a new application + * const app = new PIXI.Application(); + * + * // Draw a green rectangle + * const rect = new PIXI.Graphics() + * .beginFill(0x00ff00) + * .drawRect(40, 40, 200, 200); + * + * // Add a blur filter + * rect.filters = [new PIXI.filters.BlurFilter()]; + * + * // Display rectangle + * app.stage.addChild(rect); + * document.body.appendChild(app.view); * @namespace PIXI.filters */ export { default as FXAAFilter } from './fxaa/FXAAFilter'; diff --git a/src/index.js b/src/index.js index c553427..77da490 100644 --- a/src/index.js +++ b/src/index.js @@ -20,6 +20,14 @@ import { utils } from './core'; utils.mixins.performMixins(); +/** + * Alias for {@link PIXI.loaders.shared}. + * @name loader + * @memberof PIXI + * @type {PIXI.loader.Loader} + */ +const loader = loaders.shared || null; + export { accessibility, extract, @@ -30,18 +38,8 @@ mesh, particles, prepare, + loader, }; -/** - * A premade instance of the loader that can be used to load resources. - * - * @name loader - * @memberof PIXI - * @property {PIXI.loaders.Loader} - */ -const loader = loaders && loaders.Loader ? new loaders.Loader() : null; // check is there in case user excludes loader lib - -export { loader }; - // Always export pixi globally. global.PIXI = exports; // eslint-disable-line diff --git a/src/interaction/InteractionData.js b/src/interaction/InteractionData.js index 02d53a3..449f8cc 100644 --- a/src/interaction/InteractionData.js +++ b/src/interaction/InteractionData.js @@ -30,7 +30,10 @@ /** * When passed to an event handler, this will be the original DOM Event that was captured * - * @member {Event} + * @see https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent + * @see https://developer.mozilla.org/en-US/docs/Web/API/TouchEvent + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent + * @member {MouseEvent|TouchEvent|PointerEvent} */ this.originalEvent = null; } diff --git a/src/interaction/InteractionManager.js b/src/interaction/InteractionManager.js index 8baf13a..2d75c7c 100644 --- a/src/interaction/InteractionManager.js +++ b/src/interaction/InteractionManager.js @@ -13,6 +13,14 @@ const MOUSE_POINTER_ID = 'MOUSE'; +// helpers for hitTest() - only used inside hitTest() +const hitTestEvent = { + target: null, + data: { + global: null, + }, +}; + /** * The interaction manager deals with mouse, touch and pointer events. Any DisplayObject can be interactive * if its interactive parameter is set to true @@ -245,67 +253,60 @@ this.setTargetElement(this.renderer.view, this.renderer.resolution); /** - * Fired when a pointer device button (usually a mouse button) is pressed on the display + * Fired when a pointer device button (usually a mouse left-button) is pressed on the display * object. * - * @event mousedown - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mousedown + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device secondary button (usually a mouse right-button) is pressed * on the display object. * - * @event rightdown - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightdown + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** - * Fired when a pointer device button (usually a mouse button) is released over the display + * Fired when a pointer device button (usually a mouse left-button) is released over the display * object. * - * @event mouseup - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseup + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device secondary button (usually a mouse right-button) is released * over the display object. * - * @event rightup - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightup + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** - * Fired when a pointer device button (usually a mouse button) is pressed and released on + * Fired when a pointer device button (usually a mouse left-button) is pressed and released on * the display object. * - * @event click - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#click + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device secondary button (usually a mouse right-button) is pressed * and released on the display object. * - * @event rightclick - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightclick + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** - * Fired when a pointer device button (usually a mouse button) is released outside the + * Fired when a pointer device button (usually a mouse left-button) is released outside the * display object that initially registered a * [mousedown]{@link PIXI.interaction.InteractionManager#event:mousedown}. * - * @event mouseupoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** @@ -313,146 +314,362 @@ * outside the display object that initially registered a * [rightdown]{@link PIXI.interaction.InteractionManager#event:rightdown}. * - * @event rightupoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device (usually a mouse) is moved while over the display object * - * @event mousemove - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mousemove + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device (usually a mouse) is moved onto the display object * - * @event mouseover - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseover + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device (usually a mouse) is moved off the display object * - * @event mouseout - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseout + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is pressed on the display object. * - * @event pointerdown - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerdown + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is released over the display object. * - * @event pointerup - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerup + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when the operating system cancels a pointer event * - * @event pointercancel - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointercancel + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is pressed and released on the display object. * - * @event pointertap - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointertap + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is released outside the display object that initially * registered a [pointerdown]{@link PIXI.interaction.InteractionManager#event:pointerdown}. * - * @event pointerupoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device is moved while over the display object * - * @event pointermove - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointermove + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device is moved onto the display object * - * @event pointerover - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerover + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device is moved off the display object * - * @event pointerout - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerout + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is placed on the display object. * - * @event touchstart - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchstart + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is removed from the display object. * - * @event touchend - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchend + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when the operating system cancels a touch * - * @event touchcancel - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchcancel + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is placed and removed from the display object. * - * @event tap - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#tap + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is removed outside of the display object that initially * registered a [touchstart]{@link PIXI.interaction.InteractionManager#event:touchstart}. * - * @event touchendoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchendoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is moved along the display object. * - * @event touchmove - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchmove + * @param {PIXI.interaction.InteractionData} event - Interaction data */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is pressed on the display. + * object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mousedown + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is pressed + * on the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightdown + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is released over the display + * object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseup + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is released + * over the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightup + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is pressed and released on + * the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#click + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is pressed + * and released on the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightclick + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is released outside the + * display object that initially registered a + * [mousedown]{@link PIXI.DisplayObject#event:mousedown}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is released + * outside the display object that initially registered a + * [rightdown]{@link PIXI.DisplayObject#event:rightdown}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device (usually a mouse) is moved while over the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mousemove + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device (usually a mouse) is moved onto the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseover + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device (usually a mouse) is moved off the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseout + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is pressed on the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerdown + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is released over the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerup + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when the operating system cancels a pointer event. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointercancel + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is pressed and released on the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointertap + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is released outside the display object that initially + * registered a [pointerdown]{@link PIXI.DisplayObject#event:pointerdown}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device is moved while over the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointermove + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device is moved onto the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerover + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device is moved off the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerout + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is placed on the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchstart + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is removed from the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchend + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when the operating system cancels a touch. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchcancel + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is placed and removed from the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#tap + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is removed outside of the display object that initially + * registered a [touchstart]{@link PIXI.DisplayObject#event:touchstart}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchendoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is moved along the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchmove + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + } + + /** + * Hit tests a point against the display tree, returning the first interactive object that is hit. + * + * @param {PIXI.Point} globalPoint - A point to hit test with, in global space. + * @param {PIXI.Container} [root] - The root display object to start from. If omitted, defaults + * to the last rendered root of the associated renderer. + * @return {PIXI.DisplayObject} The hit display object, if any. + */ + hitTest(globalPoint, root) + { + // clear the target for our hit test + hitTestEvent.target = null; + // assign the global point + hitTestEvent.data.global = globalPoint; + // ensure safety of the root + if (!root) + { + root = this.renderer._lastObjectRendered; + } + // run the hit test + this.processInteractive(hitTestEvent, root, null, true); + // return our found object - it'll be null if we didn't hit anything + + return hitTestEvent.target; } /** @@ -487,7 +704,7 @@ return; } - core.ticker.shared.add(this.update, this); + core.ticker.shared.add(this.update, this, core.UPDATE_PRIORITY.INTERACTION); if (window.navigator.msPointerEnabled) { @@ -804,8 +1021,6 @@ } } - let keepHitTestingAfterChildren = hitTest; - // ** FREE TIP **! If an object is not interactive or has no buttons in it // (such as a game scene!) set interactiveChildren to false for that displayObject. // This will allow pixi to completely ignore and bypass checking the displayObjects children. @@ -818,7 +1033,9 @@ const child = children[i]; // time to get recursive.. if this function will return if something is hit.. - if (this.processInteractive(interactionEvent, child, func, hitTest, interactiveParent)) + const childHit = this.processInteractive(interactionEvent, child, func, hitTest, interactiveParent); + + if (childHit) { // its a good idea to check if a child has lost its parent. // this means it has been removed whilst looping so its best @@ -827,8 +1044,6 @@ continue; } - hit = true; - // we no longer need to hit test any more objects in this container as we we // now know the parent has been hit interactiveParent = false; @@ -838,36 +1053,41 @@ // This means we no longer need to hit test anything else. We still need to run // through all objects, but we don't need to perform any hit tests. - keepHitTestingAfterChildren = false; - - if (child.interactive) + if (childHit) { - hitTest = false; + if (interactionEvent.target) + { + hitTest = false; + } + hit = true; } - - // we can break now as we have hit an object. } } } - hitTest = keepHitTestingAfterChildren; - // no point running this if the item is not interactive or does not have an interactive parent. if (interactive) { // if we are hit testing (as in we have no hit any objects yet) // We also don't need to worry about hit testing if once of the displayObjects children - // has already been hit! - if (hitTest && !hit) + // has already been hit - but only if it was interactive, otherwise we need to keep + // looking for an interactive child, just in case we hit one + if (hitTest && !interactionEvent.target) { if (displayObject.hitArea) { displayObject.worldTransform.applyInverse(point, this._tempPoint); - hit = displayObject.hitArea.contains(this._tempPoint.x, this._tempPoint.y); + if (displayObject.hitArea.contains(this._tempPoint.x, this._tempPoint.y)) + { + hit = true; + } } else if (displayObject.containsPoint) { - hit = displayObject.containsPoint(point); + if (displayObject.containsPoint(point)) + { + hit = true; + } } } @@ -878,7 +1098,10 @@ interactionEvent.target = displayObject; } - func(interactionEvent, displayObject, hit); + if (func) + { + func(interactionEvent, displayObject, !!hit); + } } } @@ -914,7 +1137,7 @@ { const event = events[i]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -998,7 +1221,7 @@ { const event = events[i]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1098,7 +1321,7 @@ const test = isRightButton ? flags.RIGHT_DOWN : flags.LEFT_DOWN; - const isDown = trackingData !== undefined && (trackingData.flags | test); + const isDown = trackingData !== undefined && (trackingData.flags & test); if (hit) { @@ -1118,11 +1341,11 @@ { if (isRightButton) { - trackingData.rightDown = hit; + trackingData.rightDown = false; } else { - trackingData.leftDown = hit; + trackingData.leftDown = false; } } } @@ -1180,7 +1403,7 @@ { const event = events[i]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1255,7 +1478,7 @@ this.setCursorMode(null); } - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1268,6 +1491,12 @@ { this.emit('mouseout', interactionEvent); } + else + { + // we can get touchleave events after touchend, so we want to make sure we don't + // introduce memory leaks + this.releaseInteractionDataForPointerId(interactionData.identifier); + } } /** @@ -1344,7 +1573,7 @@ // Only mouse and pointer can call onPointerOver, so events will always be length 1 const event = events[0]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1366,12 +1595,14 @@ * Get InteractionData for a given pointerId. Store that data as well * * @private - * @param {number} pointerId - Identifier from a pointer event + * @param {PointerEvent} event - Normalized pointer event, output from normalizeToPointerData * @return {InteractionData} - Interaction data for the given pointer identifier */ - getInteractionDataForPointerId(pointerId) + getInteractionDataForPointerId(event) { - if (pointerId === MOUSE_POINTER_ID) + const pointerId = event.pointerId; + + if (pointerId === MOUSE_POINTER_ID || event.pointerType === 'mouse') { return this.mouse; } diff --git a/src/interaction/InteractionTrackingData.js b/src/interaction/InteractionTrackingData.js index 6a1f69e..8493b63 100644 --- a/src/interaction/InteractionTrackingData.js +++ b/src/interaction/InteractionTrackingData.js @@ -35,8 +35,10 @@ } /** + * Unique pointer id of the event + * * @readonly - * @type {number} Unique pointer id of the event + * @member {number} */ get pointerId() { diff --git a/src/interaction/index.js b/src/interaction/index.js index 56c5b09..72f7d71 100644 --- a/src/interaction/index.js +++ b/src/interaction/index.js @@ -1,4 +1,8 @@ /** + * This namespace contains a renderer plugin for handling mouse, pointer, and touch events. + * + * Do not instantiate this plugin directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. * @namespace PIXI.interaction */ export { default as InteractionData } from './InteractionData'; diff --git a/src/interaction/interactiveTarget.js b/src/interaction/interactiveTarget.js index d252fba..cebac40 100644 --- a/src/interaction/interactiveTarget.js +++ b/src/interaction/interactiveTarget.js @@ -2,7 +2,7 @@ * Default property values of interactive objects * Used by {@link PIXI.interaction.InteractionManager} to automatically give all DisplayObjects these properties * - * @mixin + * @private * @name interactiveTarget * @memberof PIXI.interaction * @example @@ -14,18 +14,28 @@ * ); */ export default { + /** - * Determines if the displayObject be clicked/touched + * Enable interaction events for the DisplayObject. Touch, pointer and mouse + * events will not be emitted unless `interactive` is set to `true`. * - * @inner {boolean} + * @example + * const sprite = new PIXI.Sprite(texture); + * sprite.interactive = true; + * sprite.on('tap', (event) => { + * //handle event + * }); + * @member {boolean} + * @memberof PIXI.DisplayObject# */ interactive: false, /** * Determines if the children to the displayObject can be clicked/touched - * Setting this to false allows pixi to bypass a recursive hitTest function + * Setting this to false allows pixi to bypass a recursive `hitTest` function * - * @inner {boolean} + * @member {boolean} + * @memberof PIXI.Container# */ interactiveChildren: true, @@ -33,7 +43,12 @@ * Interaction shape. Children will be hit first, then this shape will be checked. * Setting this will cause this shape to be checked in hit tests rather than the displayObject's bounds. * - * @inner {PIXI.Rectangle|PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.RoundedRectangle} + * @example + * const sprite = new PIXI.Sprite(texture); + * sprite.interactive = true; + * sprite.hitArea = new PIXI.Rectangle(0, 0, 100, 100); + * @member {PIXI.Rectangle|PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.RoundedRectangle} + * @memberof PIXI.DisplayObject# */ hitArea: null, @@ -41,8 +56,12 @@ * If enabled, the mouse cursor use the pointer behavior when hovered over the displayObject if it is interactive * Setting this changes the 'cursor' property to `'pointer'`. * + * @example + * const sprite = new PIXI.Sprite(texture); + * sprite.interactive = true; + * sprite.buttonMode = true; * @member {boolean} - * @memberof PIXI.interaction.interactiveTarget# + * @memberof PIXI.DisplayObject# */ get buttonMode() { @@ -64,9 +83,14 @@ * This defines what cursor mode is used when the mouse cursor * is hovered over the displayObject. * + * @example + * const sprite = new PIXI.Sprite(texture); + * sprite.interactive = true; + * sprite.cursor = 'wait'; * @see https://developer.mozilla.org/en/docs/Web/CSS/cursor * - * @inner {string} + * @member {string} + * @memberof PIXI.DisplayObject# */ cursor: null, @@ -74,7 +98,7 @@ * Internal set of all active pointers, by identifier * * @member {Map} - * @memberof PIXI.interaction.interactiveTarget# + * @memberof PIXI.DisplayObject# * @private */ get trackedPointers() @@ -87,7 +111,8 @@ /** * Map of all tracked pointers, by identifier. Use trackedPointers to access. * - * @private {Map} + * @private + * @type {Map} */ _trackedPointers: undefined, }; diff --git a/.travis.yml b/.travis.yml index 3df8b22..14de334 100644 --- a/.travis.yml +++ b/.travis.yml @@ -110,3 +110,43 @@ on: all_branches: true condition: $TRAVIS_TAG + # Deploy config for latest release + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: dist + upload-dir: "release" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: docs + upload-dir: "release/docs" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: coverage + upload-dir: "release/coverage" + on: + all_branches: true + condition: $TRAVIS_TAG \ No newline at end of file diff --git a/README.md b/README.md index d26fadd..3b85f85 100644 --- a/README.md +++ b/README.md @@ -36,22 +36,14 @@ - Wiki: Other misc tutorials and resources are [on the Wiki](https://github.com/pixijs/pixi.js/wiki/Resources). ### Community ### -- Forums: Check out the [forum] (http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow] (http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. +- Forums: Check out the [forum](http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow](http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. - Inspiration: Check out the [gallery](http://www.pixijs.com/gallery) to see some of the amazing things people have created! - Chat: You can join us on [Gitter](https://gitter.im/pixijs/pixi.js) To chat about Pixi. We also now have a Slack channel. If you would like to join it please Send me an email (mat@goodboydigital.com) and I will invite you in. ### Setup ### -It's easy to get started with Pixi.js! Simply grab the pre-built versions from here: - -Release Branch - Nice and stable Pixi.js -- Unminified: [http://pixijs.download/release/pixi.js] -- Minified: [http://pixijs.download/release/pixi.min.js] - -Develop Branch - The bleeding edge version of Pixi.js -- Unminified: [http://pixijs.download/dev/pixi.js] -- Minified: [http://pixijs.download/dev/pixi.min.js] +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. @@ -70,10 +62,10 @@ #### CDN Install (via cdnjs) ```html - + ``` -_Note: `4.2.2` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ +_Note: `4.4.3` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ ### Demos ### @@ -130,7 +122,7 @@ // and the root stage PIXI.Container. var app = new PIXI.Application(); -// The application will create a canvas element for you that you +// The application will create a canvas element for you that you // can then insert into the DOM. document.body.appendChild(app.view); @@ -150,7 +142,7 @@ // Add the bunny to the scene we are building. app.stage.addChild(bunny); - + // Listen for frame updates app.ticker.add(function() { // each frame we spin the bunny around a bit diff --git a/bower.json b/bower.json deleted file mode 100644 index 41b668c..0000000 --- a/bower.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "pixi.js", - "main": "dist/pixi.min.js", - "ignore": [ - "**/.*", - "docs", - "node_modules", - "bower_components", - "test", - "scripts", - "bower.json", - "inch.json" - ], - "dependencies": { - }, - "devDependencies": { - } -} diff --git a/package.json b/package.json index d35cbb2..308469f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.3.5", + "version": "4.4.4", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ @@ -40,11 +40,11 @@ "lib": "babel src --out-dir lib -s", "predocs": "rimraf docs/**", "docs": "jsdoc -c scripts/jsdoc.conf.json -R README.md", - "publish:patch": "npm version patch --no-git-tag-version && npm publish", - "publish:minor": "npm version minor --no-git-tag-version && npm publish", - "publish:major": "npm version major --no-git-tag-version && npm publish", + "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", - "postpublish": "node scripts/release.js" + "postpublish": "git push && git push --tags" }, "files": [ "dist/", @@ -72,7 +72,6 @@ "electron": "^1.4.15", "eslint": "^3.5.0", "floss": "^2.0.1", - "gh-pages": "^0.11.0", "jaguarjs-jsdoc": "^1.0.1", "js-md5": "^0.4.1", "jsdoc": "^3.4.2", diff --git a/scripts/jsdoc.conf.json b/scripts/jsdoc.conf.json index 553e61c..3f52180 100644 --- a/scripts/jsdoc.conf.json +++ b/scripts/jsdoc.conf.json @@ -43,7 +43,7 @@ }, "markdown" : { "parser" : "gfm", - "hardwrap" : true + "hardwrap" : false }, "opts": { "encoding" : "utf8", diff --git a/scripts/release.js b/scripts/release.js deleted file mode 100644 index 156620c..0000000 --- a/scripts/release.js +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env node -'use strict'; - -// Publish script to push releases of the bin files -// the normally are gitignored -const ghpages = require('gh-pages'); -const path = require('path'); -const packageInfo = require(path.join(__dirname, '..', 'package.json')); - -const options = { - src: [ - 'dist/**/*', - 'lib/**/*', - 'src/**/*', - 'scripts/*', - 'scripts/renders/*', - 'test/**/*', - '*.json', - '*.md', - 'LICENSE', - '.eslintrc', - '.editorconfig', - '.travis.yml', - '.babelrc', - ], - dotfiles: true, - branch: 'release', - message: packageInfo.version, - logger: console.log.bind(console), -}; - -ghpages.publish(process.cwd(), options, (err) => -{ - if (err) - { - console.log(err); - process.exit(1); - - return; - } - - process.exit(0); -}); diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index f16bcb1..220427a 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -21,7 +21,7 @@ const DIV_HOOK_ZINDEX = 2; /** - * The Accessibility manager reacreates the ability to tab and and have content read by screen + * The Accessibility manager recreates the ability to tab and and have content read by screen * readers. This is very important as it can possibly help people with disabilities access pixi * content. * diff --git a/src/accessibility/index.js b/src/accessibility/index.js index 568cfb1..0ae108f 100644 --- a/src/accessibility/index.js +++ b/src/accessibility/index.js @@ -1,4 +1,9 @@ /** + * This namespace contains a renderer plugin for interaction accessibility for end-users + * with physical impairments which require screen-renders, keyboard navigation, etc. + * + * Do not instantiate this plugin directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. * @namespace PIXI.accessibility */ export { default as accessibleTarget } from './accessibleTarget'; diff --git a/src/core/Application.js b/src/core/Application.js index 5340578..2fcc729 100644 --- a/src/core/Application.js +++ b/src/core/Application.js @@ -1,6 +1,8 @@ import { autoDetectRenderer } from './autoDetectRenderer'; import Container from './display/Container'; import { shared, Ticker } from './ticker'; +import settings from './settings'; +import { UPDATE_PRIORITY } from './const'; /** * Convenience class to create a new PIXI application. @@ -22,28 +24,52 @@ */ export default class Application { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experience unexplained flickering try setting this to true. - * @param {boolean} [useSharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedLoader=false] - `true` to use PIXI.loaders.shared, `false` to create new Loader. */ - constructor(width, height, options, noWebGL, useSharedTicker = false) + constructor(options, arg2, arg3, arg4, arg5) { + // Support for constructor(width, height, options, noWebGL, useSharedTicker) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + forceCanvas: !!arg4, + sharedTicker: !!arg5, + }, arg3); + } + + /** + * The default options, so we mixin functionality later. + * @member {object} + * @protected + */ + this._options = options = Object.assign({ + sharedTicker: false, + forceCanvas: false, + sharedLoader: false, + }, options); + /** * WebGL renderer if available, otherwise CanvasRenderer * @member {PIXI.WebGLRenderer|PIXI.CanvasRenderer} */ - this.renderer = autoDetectRenderer(width, height, options, noWebGL); + this.renderer = autoDetectRenderer(options); /** * The root display container that's rendered. @@ -63,7 +89,7 @@ * @member {PIXI.ticker.Ticker} * @default PIXI.ticker.shared */ - this.ticker = useSharedTicker ? shared : new Ticker(); + this.ticker = options.sharedTicker ? shared : new Ticker(); // Start the rendering this.start(); @@ -78,7 +104,7 @@ this._ticker = ticker; if (ticker) { - ticker.add(this.render, this); + ticker.add(this.render, this, UPDATE_PRIORITY.LOW); } } get ticker() // eslint-disable-line require-jsdoc @@ -136,13 +162,18 @@ */ destroy(removeView) { - this.stop(); + const oldTicker = this._ticker; + this.ticker = null; + oldTicker.destroy(); + this.stage.destroy(); this.stage = null; this.renderer.destroy(removeView); this.renderer = null; + + this._options = null; } } diff --git a/src/core/autoDetectRenderer.js b/src/core/autoDetectRenderer.js index 8960425..9ec0364 100644 --- a/src/core/autoDetectRenderer.js +++ b/src/core/autoDetectRenderer.js @@ -2,6 +2,7 @@ import CanvasRenderer from './renderers/canvas/CanvasRenderer'; import WebGLRenderer from './renderers/webgl/WebGLRenderer'; +// eslint-disable-next-line valid-jsdoc /** * This helper function will automatically detect which renderer you should be using. * WebGL is the preferred renderer as it is a lot faster. If webGL is not supported by @@ -9,24 +10,32 @@ * * @memberof PIXI * @function autoDetectRenderer - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @return {PIXI.WebGLRenderer|PIXI.CanvasRenderer} Returns WebGL renderer if available, otherwise CanvasRenderer */ -export function autoDetectRenderer(width = 800, height = 600, options, noWebGL) +export function autoDetectRenderer(options, arg1, arg2, arg3) { - if (!noWebGL && utils.isWebGLSupported()) + // Backward-compatible support for noWebGL option + let forceCanvas = options && options.forceCanvas; + + if (arg3 !== undefined) { - return new WebGLRenderer(width, height, options); + forceCanvas = arg3; } - return new CanvasRenderer(width, height, options); + if (!forceCanvas && utils.isWebGLSupported()) + { + return new WebGLRenderer(options, arg1, arg2); + } + + return new CanvasRenderer(options, arg1, arg2); } diff --git a/src/core/const.js b/src/core/const.js index b9e99ae..2c68dee 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -341,3 +341,27 @@ LINEAR_VERTICAL: 0, LINEAR_HORIZONTAL: 1, }; + +/** + * Represents the update priorities used by internal PIXI classes when registered with + * the {@link PIXI.ticker.Ticker} object. Higher priority items are updated first and lower + * priority items, such as render, should go later. + * + * @static + * @constant + * @name UPDATE_PRIORITY + * @memberof PIXI + * @type {object} + * @property {number} INTERACTION=50 Highest priority, used for {@link PIXI.interaction.InteractionManager} + * @property {number} HIGH=25 High priority updating, {@link PIXI.VideoBaseTexture} and {@link PIXI.extras.AnimatedSprite} + * @property {number} NORMAL=0 Default priority for ticker events, see {@link PIXI.ticker.Ticker#add}. + * @property {number} LOW=-25 Low priority used for {@link PIXI.Application} rendering. + * @property {number} UTILITY=-50 Lowest priority used for {@link PIXI.prepare.BasePrepare} utility. + */ +export const UPDATE_PRIORITY = { + INTERACTION: 50, + HIGH: 25, + NORMAL: 0, + LOW: -25, + UTILITY: -50, +}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 4e6c77d..decb42a 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -13,7 +13,6 @@ * * @class * @extends EventEmitter - * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ export default class DisplayObject extends EventEmitter @@ -113,6 +112,29 @@ * @private */ this._mask = null; + + /** + * If the object has been destroyed via destroy(). If true, it should not be used. + * + * @member {boolean} + * @private + * @readonly + */ + this._destroyed = false; + + /** + * Fired when this DisplayObject is added to a Container. + * + * @event PIXI.DisplayObject#added + * @param {PIXI.Container} container - The container added to. + */ + + /** + * Fired when this DisplayObject is removed from a Container. + * + * @event PIXI.DisplayObject#removed + * @param {PIXI.Container} container - The container removed from. + */ } /** @@ -403,6 +425,8 @@ this.interactive = false; this.interactiveChildren = false; + + this._destroyed = true; } /** diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index aafdfe2..f787c4a 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -792,8 +792,8 @@ sprite.worldAlpha = this.worldAlpha * sprite.alpha; sprite.blendMode = this.blendMode; - sprite.texture._frame.width = rect.width; - sprite.texture._frame.height = rect.height; + sprite._texture._frame.width = rect.width; + sprite._texture._frame.height = rect.height; sprite.transform.worldTransform = this.transform.worldTransform; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index a24c775..b9f192d 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -19,11 +19,12 @@ */ export default class SystemRenderer extends EventEmitter { + // eslint-disable-next-line valid-jsdoc /** * @param {string} system - The name of the system this renderer is for. - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -37,27 +38,31 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(system, screenWidth, screenHeight, options) + constructor(system, options, arg2, arg3) { super(); sayHello(system); - // prepare options - if (options) + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') { - for (const i in settings.RENDER_OPTIONS) - { - if (typeof options[i] === 'undefined') - { - options[i] = settings.RENDER_OPTIONS[i]; - } - } + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); } - else - { - options = settings.RENDER_OPTIONS; - } + + // Add the default render options + options = Object.assign({}, settings.RENDER_OPTIONS, options); + + /** + * The supplied constructor options. + * + * @member {Object} + * @readOnly + */ + this.options = options; /** * The type of the renderer. @@ -75,7 +80,7 @@ * * @member {PIXI.Rectangle} */ - this.screen = new Rectangle(0, 0, screenWidth || 800, screenHeight || 600); + this.screen = new Rectangle(0, 0, options.width, options.height); /** * The canvas element that everything is drawn to @@ -279,6 +284,8 @@ this.blendModes = null; + this.options = null; + this.preserveDrawingBuffer = false; this.clearBeforeRender = false; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index e84e49c..691caf0 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -17,10 +17,11 @@ */ export default class CanvasRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -34,9 +35,9 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('Canvas', screenWidth, screenHeight, options); + super('Canvas', options, arg2, arg3); this.type = RENDERER_TYPE.CANVAS; @@ -96,7 +97,19 @@ this.context = null; this.renderingToScreen = false; - this.resize(screenWidth, screenHeight); + this.resize(this.options.width, this.options.height); + + /** + * Fired after rendering finishes. + * + * @event PIXI.CanvasRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.CanvasRenderer#prerender + */ } /** @@ -299,4 +312,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.CanvasRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.CanvasExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.CanvasPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.CanvasRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index e1cf7cd..f6fe123 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -31,11 +31,12 @@ */ export default class WebGLRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** * - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -55,9 +56,9 @@ * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('WebGL', screenWidth, screenHeight, options); + super('WebGL', options, arg2, arg3); /** * The type of this renderer as a standardised const @@ -89,12 +90,6 @@ }; - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ this._backgroundColorRgba[3] = this.transparent ? 0 : 1; this.globalUniforms = new UniformGroup({ @@ -117,6 +112,13 @@ this.initPlugins(); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ if(options.context) { this.context.initFromContext(options.context); @@ -169,6 +171,25 @@ } return this; + + /** + * Fired after rendering finishes. + * + * @event PIXI.WebGLRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.WebGLRenderer#prerender + */ + + /** + * Fired when the WebGL context is set. + * + * @event PIXI.WebGLRenderer#context + * @param {WebGLRenderingContext} gl - WebGL context. + */ } /** @@ -295,4 +316,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.WebGLRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.WebGLRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(WebGLRenderer); diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index 4eb6128..fa3d883 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -47,6 +47,14 @@ * @member {boolean} */ this.enabled = true; + + /** + * If enabled, pixi will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; } /** diff --git a/src/core/renderers/webgl/systems/ContextSystem.js b/src/core/renderers/webgl/systems/ContextSystem.js index ff958c7..8df85ea 100644 --- a/src/core/renderers/webgl/systems/ContextSystem.js +++ b/src/core/renderers/webgl/systems/ContextSystem.js @@ -75,7 +75,8 @@ */ createContext(canvas, options) { - var gl = canvas.getContext('webgl', options) || + var gl = //canvas.getContext('webgl2', options) || + canvas.getContext('webgl', options) || canvas.getContext('experimental-webgl', options); if (!gl) diff --git a/src/core/renderers/webgl/systems/FilterSystem.js b/src/core/renderers/webgl/systems/FilterSystem.js index f47335d..37e28d2 100644 --- a/src/core/renderers/webgl/systems/FilterSystem.js +++ b/src/core/renderers/webgl/systems/FilterSystem.js @@ -111,7 +111,7 @@ // TODO we should fit the rect around the transform.. } - else + else if (filters[0].autoFit) { sourceFrame.fit(filterData.stack[0].destinationFrame); } diff --git a/src/core/renderers/webgl/systems/shader/shader/mapType.js b/src/core/renderers/webgl/systems/shader/shader/mapType.js index 581dace..917538a 100644 --- a/src/core/renderers/webgl/systems/shader/shader/mapType.js +++ b/src/core/renderers/webgl/systems/shader/shader/mapType.js @@ -15,7 +15,7 @@ } } - return GL_TABLE[type]; + return GL_TABLE[type]; }; var GL_TABLE = null; diff --git a/src/core/settings.js b/src/core/settings.js index 18c5877..38ef45c 100644 --- a/src/core/settings.js +++ b/src/core/settings.js @@ -2,6 +2,15 @@ import canUploadSameBuffer from './utils/canUploadSameBuffer'; /** + * User's customizable globals for overriding the default PIXI settings, such + * as a renderer's default resolution, framerate, float percision, etc. + * @example + * // Use the native window resolution as the default resolution + * // will support high-density displays when rendering + * PIXI.settings.RESOLUTION = window.devicePixelRatio. + * + * // Disable interpolation when scaling, will make texture be pixelated + * PIXI.settings.SCALE_MODE = PIXI.SCALE_MODES.NEAREST; * @namespace PIXI.settings */ export default { @@ -101,6 +110,9 @@ * @property {boolean} clearBeforeRender=true * @property {boolean} preserveDrawingBuffer=false * @property {boolean} roundPixels=false + * @property {number} width=800 + * @property {number} height=600 + * @property {boolean} legacy=false */ RENDER_OPTIONS: { view: null, @@ -112,6 +124,9 @@ clearBeforeRender: true, preserveDrawingBuffer: false, roundPixels: false, + width: 800, + height: 600, + legacy: false, }, /** diff --git a/src/core/shader/Program.js b/src/core/shader/Program.js index c0b538b..07b4398 100644 --- a/src/core/shader/Program.js +++ b/src/core/shader/Program.js @@ -1,6 +1,6 @@ import extractUniformsFromSrc from './extractUniformsFromSrc'; import generateUniformsSync from './generateUniformsSync'; -import glCore from 'pixi-gl-core'; +import shaderUtils from '../renderers/webgl/systems/shader/shader'; import { ProgramCache } from '../utils'; import getTestContext from '../utils/getTestContext'; @@ -64,14 +64,17 @@ } else { - vertexSrc = glCore.shader.setPrecision(vertexSrc, 'mediump'); - fragmentSrc = glCore.shader.setPrecision(fragmentSrc, 'mediump'); + vertexSrc = shaderUtils.setPrecision(vertexSrc, 'mediump'); + fragmentSrc = shaderUtils.setPrecision(fragmentSrc, 'mediump'); - const program = glCore.shader.compileProgram(gl, vertexSrc, fragmentSrc); + const program = shaderUtils.compileProgram(gl, vertexSrc, fragmentSrc); + console.log("<<<<<<>>><<"); this.attributeData = this.getAttributeData(program, gl); this.uniformData = this.getUniformData(program, gl); + console.log(this.uniformData); + //gl.deleteProgram(program); } } @@ -95,13 +98,13 @@ for (let i = 0; i < totalAttributes; i++) { const attribData = gl.getActiveAttrib(program, i); - const type = glCore.shader.mapType(gl, attribData.type); + const type = shaderUtils.mapType(gl, attribData.type); /*eslint-disable */ const data = { type: type, name: attribData.name, - size: glCore.shader.mapSize(type), + size: shaderUtils.mapSize(type), location: 0, }; /*eslint-enable */ @@ -137,13 +140,14 @@ // TODO expose this as a prop? // const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); - const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); + //const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); for (let i = 0; i < totalUniforms; i++) { const uniformData = gl.getActiveUniform(program, i); const name = uniformData.name.replace(/\[.*?\]/, ''); - const type = glCore.shader.mapType(gl, uniformData.type); + const type = shaderUtils.mapType(gl, uniformData.type); + console.log('mapping ' + uniformData.type + ' to ' + type); // if (!name.match(maskRegex)) { @@ -151,7 +155,7 @@ uniforms[name] = { type: type, size: uniformData.size, - value: glCore.shader.defaultValue(type, uniformData.size), + value: shaderUtils.defaultValue(type, uniformData.size), }; /*eslint-enable */ } diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index e387e99..b57d095 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -147,12 +147,12 @@ // so if _width is 0 then width was not set.. if (this._width) { - this.scale.x = sign(this.scale.x) * this._width / this.texture.orig.width; + this.scale.x = sign(this.scale.x) * this._width / this._texture.orig.width; } if (this._height) { - this.scale.y = sign(this.scale.y) * this._height / this.texture.orig.height; + this.scale.y = sign(this.scale.y) * this._height / this._texture.orig.height; } } diff --git a/src/core/sprites/canvas/CanvasTinter.js b/src/core/sprites/canvas/CanvasTinter.js index 27e5e0c..fd9de5d 100644 --- a/src/core/sprites/canvas/CanvasTinter.js +++ b/src/core/sprites/canvas/CanvasTinter.js @@ -18,7 +18,7 @@ */ getTintedTexture: (sprite, color) => { - const texture = sprite.texture; + const texture = sprite._texture; color = CanvasTinter.roundColor(color); diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 5a4edda..8b9b797 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -344,6 +344,7 @@ const texture = this._texture; const style = this._style; + const padding = style.trim ? 0 : style.padding; texture.baseTexture.valid = true; texture.baseTexture.resolution = this.resolution; @@ -353,11 +354,11 @@ texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - texture.trim.x = -style.padding; - texture.trim.y = -style.padding; + texture.trim.x = -padding; + texture.trim.y = -padding; - texture.orig.width = texture._frame.width - (style.padding * 2); - texture.orig.height = texture._frame.height - (style.padding * 2); + texture.orig.width = texture._frame.width - (padding * 2); + texture.orig.height = texture._frame.height - (padding * 2); // call sprite onTextureUpdate to update scale if _width or _height were set this._onTextureUpdate(); diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index 853da3d..8a9c34b 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -114,6 +114,38 @@ this.cacheId = null; this.validate(); + + /** + * Fired when a not-immediately-available source finishes loading. + * + * @protected + * @event PIXI.BaseTexture#loaded + * @param {PIXI.BaseTexture} baseTexture - Resource loaded. + */ + + /** + * Fired when a not-immediately-available source fails to load. + * + * @protected + * @event PIXI.BaseTexture#error + * @param {PIXI.BaseTexture} baseTexture - Resource errored. + */ + + /** + * Fired when BaseTexture is updated. + * + * @protected + * @event PIXI.BaseTexture#update + * @param {PIXI.BaseTexture} baseTexture - Instance of texture being updated. + */ + + /** + * Fired when BaseTexture is destroyed. + * + * @protected + * @event PIXI.BaseTexture#dispose + * @param {PIXI.BaseTexture} baseTexture - Instance of texture being destroyed. + */ } setResource(resource) diff --git a/src/core/textures/Spritesheet.js b/src/core/textures/Spritesheet.js index d469c62..9b24f27 100644 --- a/src/core/textures/Spritesheet.js +++ b/src/core/textures/Spritesheet.js @@ -208,6 +208,7 @@ // lets also add the frame to pixi's global cache for fromFrame and fromImage functions TextureCache[i] = this.textures[i]; + this.textures[i].textureCacheId = i; } frameIndex++; diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 0a2c379..2213087 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -147,9 +147,9 @@ /** * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. * - * @event update - * @memberof PIXI.Texture# + * @event PIXI.Texture#update * @protected + * @param {PIXI.Texture} texture - Instance of texture being updated. */ this._updateID = 0; @@ -159,6 +159,15 @@ * @type {Object} */ this.transform = null; + + /** + * The id under which this Texture has been added to the texture cache. This is + * automatically set in certain cases, but may not always be accurate, particularly if + * the texture is in the cache under multiple ids. + * + * @member {string} + */ + this.textureCacheId = null; } /** @@ -225,11 +234,17 @@ this._uvs = null; this.trim = null; this.orig = null; + this.textureCacheId = null; this.valid = false; - this.off('dispose', this.dispose, this); - this.off('update', this.update, this); + for (const prop in TextureCache) + { + if (TextureCache[prop] === this) + { + delete TextureCache[prop]; + } + } } /** @@ -315,14 +330,15 @@ */ static fromLoader(source, imageUrl, name) { - // console.log('added from loader...') + // console.log('added from loader...') const resource = new ImageResource(source);//.from(imageUrl, crossorigin);// document.createElement('img'); - // console.log('base resource ' + resource.width); + // console.log('base resource ' + resource.width); const baseTexture = new BaseTexture(resource, settings.SCALE_MODE, getResolutionOfUrl(imageUrl)); + /// console.log('base width ' + baseTexture.width); const texture = new Texture(baseTexture); @@ -335,6 +351,7 @@ // lets also add the frame to pixi's global cache for fromFrame and fromImage fucntions BaseTextureCache[name] = baseTexture; TextureCache[name] = texture; + texture.textureCacheId = name; // also add references by url if they are different. if (name !== imageUrl) @@ -355,6 +372,10 @@ */ static addTextureToCache(texture, id) { + if (!texture.textureCacheId) + { + texture.textureCacheId = id; + } TextureCache[id] = texture; } @@ -495,6 +516,7 @@ */ Texture.EMPTY = new Texture(new BaseTexture()); removeAllHandlers(Texture.EMPTY); +removeAllHandlers(Texture.EMPTY.baseTexture); /** * A white texture of 10x10 size, used for graphics and other things @@ -505,3 +527,4 @@ */ Texture.WHITE = createWhiteTexture(); removeAllHandlers(Texture.WHITE); +removeAllHandlers(Texture.WHITE.baseTexture); diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index 59eb9fd..a9b9f51 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -1,6 +1,7 @@ import BaseTexture from './BaseTexture'; import { uid, BaseTextureCache } from '../utils'; -import * as ticker from '../ticker'; +import { shared } from '../ticker'; +import { UPDATE_PRIORITY } from '../const'; /** * A texture of a [playing] Video. @@ -125,7 +126,7 @@ if (!this._isAutoUpdating && this.autoUpdate) { - ticker.shared.add(this.update, this); + shared.add(this.update, this, UPDATE_PRIORITY.HIGH); this._isAutoUpdating = true; } } @@ -139,7 +140,7 @@ { if (this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); this._isAutoUpdating = false; } } @@ -187,7 +188,7 @@ { if (this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); } if (this.source && this.source._pixiId) @@ -281,12 +282,12 @@ if (!this._autoUpdate && this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); this._isAutoUpdating = false; } else if (this._autoUpdate && !this._isAutoUpdating) { - ticker.shared.add(this.update, this); + shared.add(this.update, this, UPDATE_PRIORITY.HIGH); this._isAutoUpdating = true; } } diff --git a/src/core/ticker/Ticker.js b/src/core/ticker/Ticker.js index 17a1517..40df138 100644 --- a/src/core/ticker/Ticker.js +++ b/src/core/ticker/Ticker.js @@ -1,12 +1,10 @@ import settings from '../settings'; -import EventEmitter from 'eventemitter3'; - -// Internal event used by composed emitter -const TICK = 'tick'; +import { UPDATE_PRIORITY } from '../const'; +import TickerListener from './TickerListener'; /** * A Ticker class that runs an update loop that other objects listen to. - * This class is composed around an EventEmitter object to add listeners + * This class is composed around listeners * meant for execution on the next requested animation frame. * Animation frames are requested only when necessary, * e.g. When the ticker is started and the emitter has listeners. @@ -22,10 +20,11 @@ constructor() { /** - * Internal emitter used to fire 'tick' event + * The first listener. All new listeners added are chained on this. * @private + * @type {TickerListener} */ - this._emitter = new EventEmitter(); + this._head = new TickerListener(null, null, Infinity); /** * Internal current frame request ID @@ -131,7 +130,7 @@ // Invoke listeners now this.update(time); // Listener side effects may have modified ticker state. - if (this.started && this._requestId === null && this._emitter.listeners(TICK, true)) + if (this.started && this._requestId === null && this._head.next) { this._requestId = requestAnimationFrame(this._tick); } @@ -148,7 +147,7 @@ */ _requestIfNeeded() { - if (this._requestId === null && this._emitter.listeners(TICK, true)) + if (this._requestId === null && this._head.next) { // ensure callbacks get correct delta this.lastTime = performance.now(); @@ -193,35 +192,72 @@ } /** - * Calls {@link module:eventemitter3.EventEmitter#on} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. + * Register a handler for tick events. Calls continuously unless + * it is removed or the ticker is stopped. * * @param {Function} fn - The listener function to be added for updates * @param {Function} [context] - The listener context + * @param {number} [priority=PIXI.UPDATE_PRIORITY.NORMAL] - The priority for emitting * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - add(fn, context) + add(fn, context, priority = UPDATE_PRIORITY.NORMAL) { - this._emitter.on(TICK, fn, context); - - this._startIfPossible(); - - return this; + return this._addListener(new TickerListener(fn, context, priority)); } /** - * Calls {@link module:eventemitter3.EventEmitter#once} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. + * Add a handler for the tick event which is only execute once. * * @param {Function} fn - The listener function to be added for one update * @param {Function} [context] - The listener context + * @param {number} [priority=PIXI.UPDATE_PRIORITY.NORMAL] - The priority for emitting * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - addOnce(fn, context) + addOnce(fn, context, priority = UPDATE_PRIORITY.NORMAL) { - this._emitter.once(TICK, fn, context); + return this._addListener(new TickerListener(fn, context, priority, true)); + } + + /** + * Internally adds the event handler so that it can be sorted by priority. + * Priority allows certain handler (user, AnimatedSprite, Interaction) to be run + * before the rendering. + * + * @private + * @param {TickerListener} listener - Current listener being added. + * @returns {PIXI.ticker.Ticker} This instance of a ticker + */ + _addListener(listener) + { + // For attaching to head + let current = this._head.next; + let previous = this._head; + + // Add the first item + if (!current) + { + listener.connect(previous); + } + else + { + // Go from highest to lowest priority + while (current) + { + if (listener.priority >= current.priority) + { + listener.connect(previous); + break; + } + previous = current; + current = current.next; + } + + // Not yet connected + if (!listener.previous) + { + listener.connect(previous); + } + } this._startIfPossible(); @@ -229,19 +265,33 @@ } /** - * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. - * It checks if the emitter has listeners for 'tick' event. - * If it does, then it cancels the animation frame. + * Removes any handlers matching the function and context parameters. + * If no handlers are left after removing, then it cancels the animation frame. * - * @param {Function} [fn] - The listener function to be removed + * @param {Function} fn - The listener function to be removed * @param {Function} [context] - The listener context to be removed * @returns {PIXI.ticker.Ticker} This instance of a ticker */ remove(fn, context) { - this._emitter.off(TICK, fn, context); + let listener = this._head.next; - if (!this._emitter.listeners(TICK, true)) + while (listener) + { + // We found a match, lets remove it + // no break to delete all possible matches + // incase a listener was added 2+ times + if (listener.match(fn, context)) + { + listener = listener.destroy(); + } + else + { + listener = listener.next; + } + } + + if (!this._head.next) { this._cancelIfNeeded(); } @@ -276,6 +326,25 @@ } /** + * Destroy the ticker and don't use after this. Calling + * this method removes all references to internal events. + */ + destroy() + { + this.stop(); + + let listener = this._head.next; + + while (listener) + { + listener = listener.destroy(true); + } + + this._head.destroy(); + this._head = null; + } + + /** * Triggers an update. An update entails setting the * current {@link PIXI.ticker.Ticker#elapsedMS}, * the current {@link PIXI.ticker.Ticker#deltaTime}, @@ -320,8 +389,22 @@ this.deltaTime = elapsedMS * settings.TARGET_FPMS * this.speed; + // Cache a local reference, in-case ticker is destroyed + // during the emit, we can still check for head.next + const head = this._head; + // Invoke listeners added to internal emitter - this._emitter.emit(TICK, this.deltaTime); + let listener = head.next; + + while (listener) + { + listener = listener.emit(this.deltaTime); + } + + if (!head.next) + { + this._cancelIfNeeded(); + } } else { diff --git a/src/core/ticker/TickerListener.js b/src/core/ticker/TickerListener.js new file mode 100644 index 0000000..2bedb34 --- /dev/null +++ b/src/core/ticker/TickerListener.js @@ -0,0 +1,158 @@ +/** + * Internal class for handling the priority sorting of ticker handlers. + * + * @private + * @class + * @memberof PIXI.ticker + */ +export default class TickerListener +{ + /** + * Constructor + * + * @param {Function} fn - The listener function to be added for one update + * @param {Function} [context=null] - The listener context + * @param {number} [priority=0] - The priority for emitting + * @param {boolean} [once=false] - If the handler should fire once + */ + constructor(fn, context = null, priority = 0, once = false) + { + /** + * The handler function to execute. + * @member {Function} + */ + this.fn = fn; + + /** + * The calling to execute. + * @member {Function} + */ + this.context = context; + + /** + * The current priority. + * @member {number} + */ + this.priority = priority; + + /** + * If this should only execute once. + * @member {boolean} + */ + this.once = once; + + /** + * The next item in chain. + * @member {TickerListener} + */ + this.next = null; + + /** + * The previous item in chain. + * @member {TickerListener} + */ + this.previous = null; + + /** + * `true` if this listener has been destroyed already. + * @member {boolean} + * @private + */ + this._destroyed = false; + } + + /** + * Simple compare function to figure out if a function and context match. + * + * @param {Function} fn - The listener function to be added for one update + * @param {Function} context - The listener context + * @return {boolean} `true` if the listener match the arguments + */ + match(fn, context) + { + context = context || null; + + return this.fn === fn && this.context === context; + } + + /** + * Emit by calling the current function. + * @param {number} deltaTime - time since the last emit. + * @return {TickerListener} Next ticker + */ + emit(deltaTime) + { + if (this.context) + { + this.fn.call(this.context, deltaTime); + } + else + { + this.fn(deltaTime); + } + + if (this.once) + { + this.destroy(); + } + + const redirect = this.next; + + // Soft-destroying should remove + // the next reference + if (this._destroyed) + { + this.next = null; + } + + return redirect; + } + + /** + * Connect to the list. + * @param {TickerListener} previous - Input node, previous listener + */ + connect(previous) + { + this.previous = previous; + if (previous.next) + { + previous.next.previous = this; + } + this.next = previous.next; + previous.next = this; + } + + /** + * Destroy and don't use after this. + * @param {boolean} [hard = false] `true` to remove the `next` reference, this + * is considered a hard destroy. Soft destroy maintains the next reference. + * @return {TickerListener} The listener to redirect while emitting or removing. + */ + destroy(hard = false) + { + this._destroyed = true; + this.fn = null; + this.context = null; + + // Disconnect, hook up next and previous + if (this.previous) + { + this.previous.next = this.next; + } + + if (this.next) + { + this.next.previous = this.previous; + } + + // Redirect to the next item + const redirect = this.previous; + + // Remove references + this.next = hard ? null : redirect; + this.previous = null; + + return redirect; + } +} diff --git a/src/core/ticker/index.js b/src/core/ticker/index.js index 4b3017c..c3020d4 100644 --- a/src/core/ticker/index.js +++ b/src/core/ticker/index.js @@ -45,8 +45,25 @@ const shared = new Ticker(); shared.autoStart = true; +shared.destroy = () => +{ + // protect destroying shared ticker + // this is used by other internal systems + // like AnimatedSprite and InteractionManager +}; /** + * This namespace contains an API for interacting with PIXI's internal global update loop. + * + * This ticker is used for rendering, {@link PIXI.extras.AnimatedSprite AnimatedSprite}, + * {@link PIXI.interaction.InteractionManager InteractionManager} and many other time-based PIXI systems. + * @example + * const ticker = new PIXI.ticker.Ticker(); + * ticker.stop(); + * ticker.add((deltaTime) => { + * // do something every frame + * }); + * ticker.start(); * @namespace PIXI.ticker */ export { shared, Ticker }; diff --git a/src/core/utils/index.js b/src/core/utils/index.js index be44750..7441f7b 100644 --- a/src/core/utils/index.js +++ b/src/core/utils/index.js @@ -9,6 +9,21 @@ let saidHello = false; /** + * Generalized convenience utilities for PIXI. + * @example + * // Extend PIXI's internal Event Emitter. + * class MyEmitter extends PIXI.utils.EventEmitter { + * constructor() { + * super(); + * console.log("Emitter created!"); + * } + * } + * + * // Get info on current device + * console.log(PIXI.utils.isMobile); + * + * // Convert hex color to string + * console.log(PIXI.utils.hex2string(0xff00ff)); // returns: "#ff00ff" * @namespace PIXI.utils */ export { diff --git a/src/deprecation.js b/src/deprecation.js index b9c73b3..33daeec 100644 --- a/src/deprecation.js +++ b/src/deprecation.js @@ -918,6 +918,35 @@ }); /** + * @method + * @name PIXI.prepare.BasePrepare#register + * @see PIXI.prepare.BasePrepare#registerFindHook + * @deprecated since version 4.4.2 + * @param {Function} [addHook] - Function call that takes two parameters: `item:*, queue:Array` + * function must return `true` if it was able to add item to the queue. + * @param {Function} [uploadHook] - Function call that takes two parameters: `prepare:CanvasPrepare, item:*` and + * function must return `true` if it was able to handle upload of item. + * @return {PIXI.BasePrepare} Instance of plugin for chaining. + */ +prepare.BasePrepare.prototype.register = function register(addHook, uploadHook) +{ + warn('renderer.plugins.prepare.register is now deprecated, ' + + 'please use renderer.plugins.prepare.registerFindHook & renderer.plugins.prepare.registerUploadHook'); + + if (addHook) + { + this.registerFindHook(addHook); + } + + if (uploadHook) + { + this.registerUploadHook(uploadHook); + } + + return this; +}; + +/** * The number of graphics or textures to upload to the GPU. * * @name PIXI.prepare.canvas.UPLOADS_PER_FRAME @@ -969,67 +998,73 @@ }, }); -Object.defineProperties(loaders.Resource.prototype, { - isJson: { - get() - { - warn('The isJson property is deprecated, please use `resource.type === Resource.TYPE.JSON`.'); +if (loaders.Loader) +{ + const Resource = loaders.Resource; + const Loader = loaders.Loader; - return this.type === loaders.Loader.Resource.TYPE.JSON; + Object.defineProperties(Resource.prototype, { + isJson: { + get() + { + warn('The isJson property is deprecated, please use `resource.type === Resource.TYPE.JSON`.'); + + return this.type === Resource.TYPE.JSON; + }, }, - }, - isXml: { - get() - { - warn('The isXml property is deprecated, please use `resource.type === Resource.TYPE.XML`.'); + isXml: { + get() + { + warn('The isXml property is deprecated, please use `resource.type === Resource.TYPE.XML`.'); - return this.type === loaders.Loader.Resource.TYPE.XML; + return this.type === Resource.TYPE.XML; + }, }, - }, - isImage: { - get() - { - warn('The isImage property is deprecated, please use `resource.type === Resource.TYPE.IMAGE`.'); + isImage: { + get() + { + warn('The isImage property is deprecated, please use `resource.type === Resource.TYPE.IMAGE`.'); - return this.type === loaders.Loader.Resource.TYPE.IMAGE; + return this.type === Resource.TYPE.IMAGE; + }, }, - }, - isAudio: { - get() - { - warn('The isAudio property is deprecated, please use `resource.type === Resource.TYPE.AUDIO`.'); + isAudio: { + get() + { + warn('The isAudio property is deprecated, please use `resource.type === Resource.TYPE.AUDIO`.'); - return this.type === loaders.Loader.Resource.TYPE.AUDIO; + return this.type === Resource.TYPE.AUDIO; + }, }, - }, - isVideo: { - get() - { - warn('The isVideo property is deprecated, please use `resource.type === Resource.TYPE.VIDEO`.'); + isVideo: { + get() + { + warn('The isVideo property is deprecated, please use `resource.type === Resource.TYPE.VIDEO`.'); - return this.type === loaders.Loader.Resource.TYPE.VIDEO; + return this.type === Resource.TYPE.VIDEO; + }, }, - }, -}); + }); -Object.defineProperties(loaders.Loader.prototype, { - before: { - get() - { - warn('The before() method is deprecated, please use pre().'); + Object.defineProperties(Loader.prototype, { + before: { + get() + { + warn('The before() method is deprecated, please use pre().'); - return this.pre; + return this.pre; + }, }, - }, - after: { - get() - { - warn('The after() method is deprecated, please use use().'); + after: { + get() + { + warn('The after() method is deprecated, please use use().'); - return this.use; + return this.use; + }, }, - }, -}); + }); +} /** * @name PIXI.interaction.interactiveTarget#defaultCursor diff --git a/src/extract/canvas/CanvasExtract.js b/src/extract/canvas/CanvasExtract.js index 941ca05..3ac5aee 100644 --- a/src/extract/canvas/CanvasExtract.js +++ b/src/extract/canvas/CanvasExtract.js @@ -8,7 +8,7 @@ * An instance of this class is automatically created by default, and can be found at renderer.plugins.extract * * @class - * @memberof PIXI + * @memberof PIXI.extract */ export default class CanvasExtract { @@ -21,9 +21,9 @@ /** * Collection of methods for extracting data (image, pixels, etc.) from a display object or render texture * - * @member {PIXI.CanvasExtract} extract + * @member {PIXI.extract.CanvasExtract} extract * @memberof PIXI.CanvasRenderer# - * @see PIXI.CanvasExtract + * @see PIXI.extract.CanvasExtract */ renderer.extract = this; } diff --git a/src/extract/index.js b/src/extract/index.js index 0f2170d..e9478c1 100644 --- a/src/extract/index.js +++ b/src/extract/index.js @@ -1,2 +1,22 @@ +/** + * This namespace provides renderer-specific plugins for exporting content from a renderer. + * For instance, these plugins can be used for saving an Image, Canvas element or for exporting the raw image data (pixels). + * + * Do not instantiate these plugins directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. + * @example + * // Create a new app (will auto-add extract plugin to renderer) + * const app = new PIXI.Application(); + * + * // Draw a red circle + * const graphics = new PIXI.Graphics() + * .beginFill(0xFF0000) + * .drawCircle(0, 0, 50); + * + * // Render the graphics as an HTMLImageElement + * const image = app.renderer.plugins.extract.image(graphics); + * document.body.appendChild(image); + * @namespace PIXI.extract + */ export { default as webgl } from './webgl/WebGLExtract'; export { default as canvas } from './canvas/CanvasExtract'; diff --git a/src/extract/webgl/WebGLExtract.js b/src/extract/webgl/WebGLExtract.js index 60228bb..1c3dd30 100644 --- a/src/extract/webgl/WebGLExtract.js +++ b/src/extract/webgl/WebGLExtract.js @@ -9,7 +9,7 @@ * An instance of this class is automatically created by default, and can be found at renderer.plugins.extract * * @class - * @memberof PIXI + * @memberof PIXI.extract */ export default class WebGLExtract { @@ -22,9 +22,9 @@ /** * Collection of methods for extracting data (image, pixels, etc.) from a display object or render texture * - * @member {PIXI.WebGLExtract} extract + * @member {PIXI.extract.WebGLExtract} extract * @memberof PIXI.WebGLRenderer# - * @see PIXI.WebGLExtract + * @see PIXI.extract.WebGLExtract */ renderer.extract = this; } diff --git a/src/extras/AnimatedSprite.js b/src/extras/AnimatedSprite.js index 13b804f..5297778 100644 --- a/src/extras/AnimatedSprite.js +++ b/src/extras/AnimatedSprite.js @@ -137,7 +137,7 @@ this.playing = true; if (this._autoUpdate) { - core.ticker.shared.add(this.update, this); + core.ticker.shared.add(this.update, this, core.UPDATE_PRIORITY.HIGH); } } diff --git a/src/extras/TextureTransform.js b/src/extras/TextureTransform.js index 78e6e40..16536d2 100644 --- a/src/extras/TextureTransform.js +++ b/src/extras/TextureTransform.js @@ -65,8 +65,36 @@ } /** + * Multiplies uvs array to transform + * @param {Float32Array} uvs mesh uvs + * @param {Float32Array} [out=uvs] output + * @returns {Float32Array} output + */ + multiplyUvs(uvs, out) + { + if (out === undefined) + { + out = uvs; + } + + const mat = this.mapCoord; + + for (let i = 0; i < uvs.length; i += 2) + { + const x = uvs[i]; + const y = uvs[i + 1]; + + out[i] = (x * mat.a) + (y * mat.c) + mat.tx; + out[i + 1] = (x * mat.b) + (y * mat.d) + mat.ty; + } + + return out; + } + + /** * updates matrices if texture was changed * @param {boolean} forceUpdate if true, matrices will be updated any case + * @returns {boolean} whether or not it was updated */ update(forceUpdate) { @@ -74,13 +102,13 @@ if (!tex || !tex.valid) { - return; + return false; } if (!forceUpdate && this._lastTextureID === tex._updateID) { - return; + return false; } this._lastTextureID = tex._updateID; @@ -110,5 +138,7 @@ frame[3] = (tex._frame.y + tex._frame.height - margin + offset) / texBase.height; this.uClampOffset[0] = offset / texBase.realWidth; this.uClampOffset[1] = offset / texBase.realHeight; + + return true; } } diff --git a/src/extras/TilingSprite.js b/src/extras/TilingSprite.js index 1f6014f..a3f027f 100644 --- a/src/extras/TilingSprite.js +++ b/src/extras/TilingSprite.js @@ -180,9 +180,9 @@ const transform = this.worldTransform; const resolution = renderer.resolution; const baseTexture = texture.baseTexture; - const baseTextureResolution = texture.baseTexture.resolution; - const modX = (this.tilePosition.x / this.tileScale.x) % texture._frame.width; - const modY = (this.tilePosition.y / this.tileScale.y) % texture._frame.height; + const baseTextureResolution = baseTexture.resolution; + const modX = ((this.tilePosition.x / this.tileScale.x) % texture._frame.width) * baseTextureResolution; + const modY = ((this.tilePosition.y / this.tileScale.y) % texture._frame.height) * baseTextureResolution; // create a nice shiny pattern! // TODO this needs to be refreshed if texture changes.. diff --git a/src/extras/cacheAsBitmap.js b/src/extras/cacheAsBitmap.js index 5dd5640..aea06a8 100644 --- a/src/extras/cacheAsBitmap.js +++ b/src/extras/cacheAsBitmap.js @@ -227,7 +227,16 @@ this.transform._parentID = -1; // restore the transform of the cached sprite to avoid the nasty flicker.. - this.updateTransform(); + if (!this.parent) + { + this.parent = renderer._tempDisplayObjectParent; + this.updateTransform(); + this.parent = null; + } + else + { + this.updateTransform(); + } // map the hit test.. this.containsPoint = cachedSprite.containsPoint.bind(cachedSprite); @@ -314,7 +323,17 @@ cachedSprite._bounds = this._bounds; cachedSprite.alpha = cacheAlpha; - this.updateTransform(); + if (!this.parent) + { + this.parent = renderer._tempDisplayObjectParent; + this.updateTransform(); + this.parent = null; + } + else + { + this.updateTransform(); + } + this.updateTransform = this.displayObjectUpdateTransform; this._cacheData.sprite = cachedSprite; @@ -358,9 +377,12 @@ * Destroys the cached object. * * @private + * @param {object|boolean} [options] - Options parameter. A boolean will act as if all options + * have been set to that value. + * Used when destroying containers, see the Container.destroy method. */ -DisplayObject.prototype._cacheAsBitmapDestroy = function _cacheAsBitmapDestroy() +DisplayObject.prototype._cacheAsBitmapDestroy = function _cacheAsBitmapDestroy(options) { this.cacheAsBitmap = false; - this.destroy(); + this.destroy(options); }; diff --git a/src/extras/index.js b/src/extras/index.js index bbd8bbf..a34d7ec 100644 --- a/src/extras/index.js +++ b/src/extras/index.js @@ -1,7 +1,7 @@ /** + * Additional PIXI DisplayObjects for animation, tiling and bitmap text. * @namespace PIXI.extras */ -export { default as TextureTransform } from './TextureTransform'; export { default as AnimatedSprite } from './AnimatedSprite'; export { default as TilingSprite } from './TilingSprite'; export { default as TilingSpriteRenderer } from './webgl/TilingSpriteRenderer'; diff --git a/src/extras/webgl/TilingSpriteRenderer.js b/src/extras/webgl/TilingSpriteRenderer.js index aeec172..eee3b5c 100644 --- a/src/extras/webgl/TilingSpriteRenderer.js +++ b/src/extras/webgl/TilingSpriteRenderer.js @@ -11,7 +11,7 @@ * WebGL renderer plugin for tiling sprites * * @class - * @memberof PIXI + * @memberof PIXI.extras * @extends PIXI.ObjectRenderer */ export default class TilingSpriteRenderer extends core.ObjectRenderer @@ -118,7 +118,7 @@ tempMat.invert(); if (isSimple) { - tempMat.append(uv.mapCoord); + tempMat.prepend(uv.mapCoord); } else { diff --git a/src/filters/displacement/DisplacementFilter.js b/src/filters/displacement/DisplacementFilter.js index d5355c3..b5d030c 100644 --- a/src/filters/displacement/DisplacementFilter.js +++ b/src/filters/displacement/DisplacementFilter.js @@ -35,7 +35,7 @@ this.maskSprite = sprite; this.maskMatrix = maskMatrix; - this.uniforms.mapSampler = sprite.texture; + this.uniforms.mapSampler = sprite._texture; this.uniforms.filterMatrix = maskMatrix; this.uniforms.scale = { x: 1, y: 1 }; diff --git a/src/filters/index.js b/src/filters/index.js index f3e8862..7ceb50a 100644 --- a/src/filters/index.js +++ b/src/filters/index.js @@ -1,4 +1,21 @@ /** + * This namespace contains WebGL-only display filters that can be applied + * to DisplayObjects using the {@link PIXI.DisplayObject#filters filters} property. + * @example + * // Create a new application + * const app = new PIXI.Application(); + * + * // Draw a green rectangle + * const rect = new PIXI.Graphics() + * .beginFill(0x00ff00) + * .drawRect(40, 40, 200, 200); + * + * // Add a blur filter + * rect.filters = [new PIXI.filters.BlurFilter()]; + * + * // Display rectangle + * app.stage.addChild(rect); + * document.body.appendChild(app.view); * @namespace PIXI.filters */ export { default as FXAAFilter } from './fxaa/FXAAFilter'; diff --git a/src/index.js b/src/index.js index c553427..77da490 100644 --- a/src/index.js +++ b/src/index.js @@ -20,6 +20,14 @@ import { utils } from './core'; utils.mixins.performMixins(); +/** + * Alias for {@link PIXI.loaders.shared}. + * @name loader + * @memberof PIXI + * @type {PIXI.loader.Loader} + */ +const loader = loaders.shared || null; + export { accessibility, extract, @@ -30,18 +38,8 @@ mesh, particles, prepare, + loader, }; -/** - * A premade instance of the loader that can be used to load resources. - * - * @name loader - * @memberof PIXI - * @property {PIXI.loaders.Loader} - */ -const loader = loaders && loaders.Loader ? new loaders.Loader() : null; // check is there in case user excludes loader lib - -export { loader }; - // Always export pixi globally. global.PIXI = exports; // eslint-disable-line diff --git a/src/interaction/InteractionData.js b/src/interaction/InteractionData.js index 02d53a3..449f8cc 100644 --- a/src/interaction/InteractionData.js +++ b/src/interaction/InteractionData.js @@ -30,7 +30,10 @@ /** * When passed to an event handler, this will be the original DOM Event that was captured * - * @member {Event} + * @see https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent + * @see https://developer.mozilla.org/en-US/docs/Web/API/TouchEvent + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent + * @member {MouseEvent|TouchEvent|PointerEvent} */ this.originalEvent = null; } diff --git a/src/interaction/InteractionManager.js b/src/interaction/InteractionManager.js index 8baf13a..2d75c7c 100644 --- a/src/interaction/InteractionManager.js +++ b/src/interaction/InteractionManager.js @@ -13,6 +13,14 @@ const MOUSE_POINTER_ID = 'MOUSE'; +// helpers for hitTest() - only used inside hitTest() +const hitTestEvent = { + target: null, + data: { + global: null, + }, +}; + /** * The interaction manager deals with mouse, touch and pointer events. Any DisplayObject can be interactive * if its interactive parameter is set to true @@ -245,67 +253,60 @@ this.setTargetElement(this.renderer.view, this.renderer.resolution); /** - * Fired when a pointer device button (usually a mouse button) is pressed on the display + * Fired when a pointer device button (usually a mouse left-button) is pressed on the display * object. * - * @event mousedown - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mousedown + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device secondary button (usually a mouse right-button) is pressed * on the display object. * - * @event rightdown - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightdown + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** - * Fired when a pointer device button (usually a mouse button) is released over the display + * Fired when a pointer device button (usually a mouse left-button) is released over the display * object. * - * @event mouseup - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseup + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device secondary button (usually a mouse right-button) is released * over the display object. * - * @event rightup - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightup + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** - * Fired when a pointer device button (usually a mouse button) is pressed and released on + * Fired when a pointer device button (usually a mouse left-button) is pressed and released on * the display object. * - * @event click - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#click + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device secondary button (usually a mouse right-button) is pressed * and released on the display object. * - * @event rightclick - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightclick + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** - * Fired when a pointer device button (usually a mouse button) is released outside the + * Fired when a pointer device button (usually a mouse left-button) is released outside the * display object that initially registered a * [mousedown]{@link PIXI.interaction.InteractionManager#event:mousedown}. * - * @event mouseupoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** @@ -313,146 +314,362 @@ * outside the display object that initially registered a * [rightdown]{@link PIXI.interaction.InteractionManager#event:rightdown}. * - * @event rightupoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device (usually a mouse) is moved while over the display object * - * @event mousemove - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mousemove + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device (usually a mouse) is moved onto the display object * - * @event mouseover - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseover + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device (usually a mouse) is moved off the display object * - * @event mouseout - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseout + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is pressed on the display object. * - * @event pointerdown - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerdown + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is released over the display object. * - * @event pointerup - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerup + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when the operating system cancels a pointer event * - * @event pointercancel - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointercancel + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is pressed and released on the display object. * - * @event pointertap - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointertap + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is released outside the display object that initially * registered a [pointerdown]{@link PIXI.interaction.InteractionManager#event:pointerdown}. * - * @event pointerupoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device is moved while over the display object * - * @event pointermove - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointermove + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device is moved onto the display object * - * @event pointerover - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerover + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device is moved off the display object * - * @event pointerout - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerout + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is placed on the display object. * - * @event touchstart - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchstart + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is removed from the display object. * - * @event touchend - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchend + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when the operating system cancels a touch * - * @event touchcancel - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchcancel + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is placed and removed from the display object. * - * @event tap - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#tap + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is removed outside of the display object that initially * registered a [touchstart]{@link PIXI.interaction.InteractionManager#event:touchstart}. * - * @event touchendoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchendoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is moved along the display object. * - * @event touchmove - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchmove + * @param {PIXI.interaction.InteractionData} event - Interaction data */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is pressed on the display. + * object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mousedown + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is pressed + * on the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightdown + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is released over the display + * object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseup + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is released + * over the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightup + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is pressed and released on + * the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#click + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is pressed + * and released on the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightclick + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is released outside the + * display object that initially registered a + * [mousedown]{@link PIXI.DisplayObject#event:mousedown}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is released + * outside the display object that initially registered a + * [rightdown]{@link PIXI.DisplayObject#event:rightdown}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device (usually a mouse) is moved while over the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mousemove + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device (usually a mouse) is moved onto the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseover + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device (usually a mouse) is moved off the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseout + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is pressed on the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerdown + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is released over the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerup + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when the operating system cancels a pointer event. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointercancel + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is pressed and released on the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointertap + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is released outside the display object that initially + * registered a [pointerdown]{@link PIXI.DisplayObject#event:pointerdown}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device is moved while over the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointermove + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device is moved onto the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerover + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device is moved off the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerout + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is placed on the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchstart + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is removed from the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchend + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when the operating system cancels a touch. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchcancel + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is placed and removed from the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#tap + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is removed outside of the display object that initially + * registered a [touchstart]{@link PIXI.DisplayObject#event:touchstart}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchendoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is moved along the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchmove + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + } + + /** + * Hit tests a point against the display tree, returning the first interactive object that is hit. + * + * @param {PIXI.Point} globalPoint - A point to hit test with, in global space. + * @param {PIXI.Container} [root] - The root display object to start from. If omitted, defaults + * to the last rendered root of the associated renderer. + * @return {PIXI.DisplayObject} The hit display object, if any. + */ + hitTest(globalPoint, root) + { + // clear the target for our hit test + hitTestEvent.target = null; + // assign the global point + hitTestEvent.data.global = globalPoint; + // ensure safety of the root + if (!root) + { + root = this.renderer._lastObjectRendered; + } + // run the hit test + this.processInteractive(hitTestEvent, root, null, true); + // return our found object - it'll be null if we didn't hit anything + + return hitTestEvent.target; } /** @@ -487,7 +704,7 @@ return; } - core.ticker.shared.add(this.update, this); + core.ticker.shared.add(this.update, this, core.UPDATE_PRIORITY.INTERACTION); if (window.navigator.msPointerEnabled) { @@ -804,8 +1021,6 @@ } } - let keepHitTestingAfterChildren = hitTest; - // ** FREE TIP **! If an object is not interactive or has no buttons in it // (such as a game scene!) set interactiveChildren to false for that displayObject. // This will allow pixi to completely ignore and bypass checking the displayObjects children. @@ -818,7 +1033,9 @@ const child = children[i]; // time to get recursive.. if this function will return if something is hit.. - if (this.processInteractive(interactionEvent, child, func, hitTest, interactiveParent)) + const childHit = this.processInteractive(interactionEvent, child, func, hitTest, interactiveParent); + + if (childHit) { // its a good idea to check if a child has lost its parent. // this means it has been removed whilst looping so its best @@ -827,8 +1044,6 @@ continue; } - hit = true; - // we no longer need to hit test any more objects in this container as we we // now know the parent has been hit interactiveParent = false; @@ -838,36 +1053,41 @@ // This means we no longer need to hit test anything else. We still need to run // through all objects, but we don't need to perform any hit tests. - keepHitTestingAfterChildren = false; - - if (child.interactive) + if (childHit) { - hitTest = false; + if (interactionEvent.target) + { + hitTest = false; + } + hit = true; } - - // we can break now as we have hit an object. } } } - hitTest = keepHitTestingAfterChildren; - // no point running this if the item is not interactive or does not have an interactive parent. if (interactive) { // if we are hit testing (as in we have no hit any objects yet) // We also don't need to worry about hit testing if once of the displayObjects children - // has already been hit! - if (hitTest && !hit) + // has already been hit - but only if it was interactive, otherwise we need to keep + // looking for an interactive child, just in case we hit one + if (hitTest && !interactionEvent.target) { if (displayObject.hitArea) { displayObject.worldTransform.applyInverse(point, this._tempPoint); - hit = displayObject.hitArea.contains(this._tempPoint.x, this._tempPoint.y); + if (displayObject.hitArea.contains(this._tempPoint.x, this._tempPoint.y)) + { + hit = true; + } } else if (displayObject.containsPoint) { - hit = displayObject.containsPoint(point); + if (displayObject.containsPoint(point)) + { + hit = true; + } } } @@ -878,7 +1098,10 @@ interactionEvent.target = displayObject; } - func(interactionEvent, displayObject, hit); + if (func) + { + func(interactionEvent, displayObject, !!hit); + } } } @@ -914,7 +1137,7 @@ { const event = events[i]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -998,7 +1221,7 @@ { const event = events[i]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1098,7 +1321,7 @@ const test = isRightButton ? flags.RIGHT_DOWN : flags.LEFT_DOWN; - const isDown = trackingData !== undefined && (trackingData.flags | test); + const isDown = trackingData !== undefined && (trackingData.flags & test); if (hit) { @@ -1118,11 +1341,11 @@ { if (isRightButton) { - trackingData.rightDown = hit; + trackingData.rightDown = false; } else { - trackingData.leftDown = hit; + trackingData.leftDown = false; } } } @@ -1180,7 +1403,7 @@ { const event = events[i]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1255,7 +1478,7 @@ this.setCursorMode(null); } - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1268,6 +1491,12 @@ { this.emit('mouseout', interactionEvent); } + else + { + // we can get touchleave events after touchend, so we want to make sure we don't + // introduce memory leaks + this.releaseInteractionDataForPointerId(interactionData.identifier); + } } /** @@ -1344,7 +1573,7 @@ // Only mouse and pointer can call onPointerOver, so events will always be length 1 const event = events[0]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1366,12 +1595,14 @@ * Get InteractionData for a given pointerId. Store that data as well * * @private - * @param {number} pointerId - Identifier from a pointer event + * @param {PointerEvent} event - Normalized pointer event, output from normalizeToPointerData * @return {InteractionData} - Interaction data for the given pointer identifier */ - getInteractionDataForPointerId(pointerId) + getInteractionDataForPointerId(event) { - if (pointerId === MOUSE_POINTER_ID) + const pointerId = event.pointerId; + + if (pointerId === MOUSE_POINTER_ID || event.pointerType === 'mouse') { return this.mouse; } diff --git a/src/interaction/InteractionTrackingData.js b/src/interaction/InteractionTrackingData.js index 6a1f69e..8493b63 100644 --- a/src/interaction/InteractionTrackingData.js +++ b/src/interaction/InteractionTrackingData.js @@ -35,8 +35,10 @@ } /** + * Unique pointer id of the event + * * @readonly - * @type {number} Unique pointer id of the event + * @member {number} */ get pointerId() { diff --git a/src/interaction/index.js b/src/interaction/index.js index 56c5b09..72f7d71 100644 --- a/src/interaction/index.js +++ b/src/interaction/index.js @@ -1,4 +1,8 @@ /** + * This namespace contains a renderer plugin for handling mouse, pointer, and touch events. + * + * Do not instantiate this plugin directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. * @namespace PIXI.interaction */ export { default as InteractionData } from './InteractionData'; diff --git a/src/interaction/interactiveTarget.js b/src/interaction/interactiveTarget.js index d252fba..cebac40 100644 --- a/src/interaction/interactiveTarget.js +++ b/src/interaction/interactiveTarget.js @@ -2,7 +2,7 @@ * Default property values of interactive objects * Used by {@link PIXI.interaction.InteractionManager} to automatically give all DisplayObjects these properties * - * @mixin + * @private * @name interactiveTarget * @memberof PIXI.interaction * @example @@ -14,18 +14,28 @@ * ); */ export default { + /** - * Determines if the displayObject be clicked/touched + * Enable interaction events for the DisplayObject. Touch, pointer and mouse + * events will not be emitted unless `interactive` is set to `true`. * - * @inner {boolean} + * @example + * const sprite = new PIXI.Sprite(texture); + * sprite.interactive = true; + * sprite.on('tap', (event) => { + * //handle event + * }); + * @member {boolean} + * @memberof PIXI.DisplayObject# */ interactive: false, /** * Determines if the children to the displayObject can be clicked/touched - * Setting this to false allows pixi to bypass a recursive hitTest function + * Setting this to false allows pixi to bypass a recursive `hitTest` function * - * @inner {boolean} + * @member {boolean} + * @memberof PIXI.Container# */ interactiveChildren: true, @@ -33,7 +43,12 @@ * Interaction shape. Children will be hit first, then this shape will be checked. * Setting this will cause this shape to be checked in hit tests rather than the displayObject's bounds. * - * @inner {PIXI.Rectangle|PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.RoundedRectangle} + * @example + * const sprite = new PIXI.Sprite(texture); + * sprite.interactive = true; + * sprite.hitArea = new PIXI.Rectangle(0, 0, 100, 100); + * @member {PIXI.Rectangle|PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.RoundedRectangle} + * @memberof PIXI.DisplayObject# */ hitArea: null, @@ -41,8 +56,12 @@ * If enabled, the mouse cursor use the pointer behavior when hovered over the displayObject if it is interactive * Setting this changes the 'cursor' property to `'pointer'`. * + * @example + * const sprite = new PIXI.Sprite(texture); + * sprite.interactive = true; + * sprite.buttonMode = true; * @member {boolean} - * @memberof PIXI.interaction.interactiveTarget# + * @memberof PIXI.DisplayObject# */ get buttonMode() { @@ -64,9 +83,14 @@ * This defines what cursor mode is used when the mouse cursor * is hovered over the displayObject. * + * @example + * const sprite = new PIXI.Sprite(texture); + * sprite.interactive = true; + * sprite.cursor = 'wait'; * @see https://developer.mozilla.org/en/docs/Web/CSS/cursor * - * @inner {string} + * @member {string} + * @memberof PIXI.DisplayObject# */ cursor: null, @@ -74,7 +98,7 @@ * Internal set of all active pointers, by identifier * * @member {Map} - * @memberof PIXI.interaction.interactiveTarget# + * @memberof PIXI.DisplayObject# * @private */ get trackedPointers() @@ -87,7 +111,8 @@ /** * Map of all tracked pointers, by identifier. Use trackedPointers to access. * - * @private {Map} + * @private + * @type {Map} */ _trackedPointers: undefined, }; diff --git a/src/loaders/index.js b/src/loaders/index.js index 5189793..daa1fc4 100644 --- a/src/loaders/index.js +++ b/src/loaders/index.js @@ -1,9 +1,22 @@ +import Application from '../core/Application'; +import Loader from './loader'; + /** + * This namespace contains APIs which extends the {@link https://github.com/englercj/resource-loader resource-loader} module + * for loading assets, data, and other resources dynamically. + * @example + * const loader = new PIXI.loaders.Loader(); + * loader.add('bunny', 'data/bunny.png') + * .add('spaceship', 'assets/spritesheet.json'); + * loader.load((loader, resources) => { + * // resources.bunny + * // resources.spaceship + * }); * @namespace PIXI.loaders */ -export { default as Loader } from './loader'; +export { Loader }; export { default as bitmapFontParser, parse as parseBitmapFontData } from './bitmapFontParser'; -export { default as spritesheetParser } from './spritesheetParser'; +export { default as spritesheetParser, getResourcePath } from './spritesheetParser'; export { default as textureParser } from './textureParser'; /** @@ -13,3 +26,55 @@ * @memberof PIXI.loaders */ export { Resource } from 'resource-loader'; + +/** + * A premade instance of the loader that can be used to load resources. + * @name shared + * @memberof PIXI.loaders + * @type {PIXI.loaders.Loader} + */ +const shared = new Loader(); + +shared.destroy = () => +{ + // protect destroying shared loader +}; + +export { shared }; + +// Mixin the loader construction +const AppPrototype = Application.prototype; + +AppPrototype._loader = null; + +/** + * Loader instance to help with asset loading. + * @name PIXI.Application#loader + * @type {PIXI.loaders.Loader} + */ +Object.defineProperty(AppPrototype, 'loader', { + get() + { + if (!this._loader) + { + const sharedLoader = this._options.sharedLoader; + + this._loader = sharedLoader ? shared : new Loader(); + } + + return this._loader; + }, +}); + +// Override the destroy function +// making sure to destroy the current Loader +AppPrototype._parentDestroy = AppPrototype.destroy; +AppPrototype.destroy = function destroy() +{ + if (this._loader) + { + this._loader.destroy(); + this._loader = null; + } + this._parentDestroy(); +}; diff --git a/.travis.yml b/.travis.yml index 3df8b22..14de334 100644 --- a/.travis.yml +++ b/.travis.yml @@ -110,3 +110,43 @@ on: all_branches: true condition: $TRAVIS_TAG + # Deploy config for latest release + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: dist + upload-dir: "release" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: docs + upload-dir: "release/docs" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: coverage + upload-dir: "release/coverage" + on: + all_branches: true + condition: $TRAVIS_TAG \ No newline at end of file diff --git a/README.md b/README.md index d26fadd..3b85f85 100644 --- a/README.md +++ b/README.md @@ -36,22 +36,14 @@ - Wiki: Other misc tutorials and resources are [on the Wiki](https://github.com/pixijs/pixi.js/wiki/Resources). ### Community ### -- Forums: Check out the [forum] (http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow] (http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. +- Forums: Check out the [forum](http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow](http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. - Inspiration: Check out the [gallery](http://www.pixijs.com/gallery) to see some of the amazing things people have created! - Chat: You can join us on [Gitter](https://gitter.im/pixijs/pixi.js) To chat about Pixi. We also now have a Slack channel. If you would like to join it please Send me an email (mat@goodboydigital.com) and I will invite you in. ### Setup ### -It's easy to get started with Pixi.js! Simply grab the pre-built versions from here: - -Release Branch - Nice and stable Pixi.js -- Unminified: [http://pixijs.download/release/pixi.js] -- Minified: [http://pixijs.download/release/pixi.min.js] - -Develop Branch - The bleeding edge version of Pixi.js -- Unminified: [http://pixijs.download/dev/pixi.js] -- Minified: [http://pixijs.download/dev/pixi.min.js] +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. @@ -70,10 +62,10 @@ #### CDN Install (via cdnjs) ```html - + ``` -_Note: `4.2.2` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ +_Note: `4.4.3` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ ### Demos ### @@ -130,7 +122,7 @@ // and the root stage PIXI.Container. var app = new PIXI.Application(); -// The application will create a canvas element for you that you +// The application will create a canvas element for you that you // can then insert into the DOM. document.body.appendChild(app.view); @@ -150,7 +142,7 @@ // Add the bunny to the scene we are building. app.stage.addChild(bunny); - + // Listen for frame updates app.ticker.add(function() { // each frame we spin the bunny around a bit diff --git a/bower.json b/bower.json deleted file mode 100644 index 41b668c..0000000 --- a/bower.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "pixi.js", - "main": "dist/pixi.min.js", - "ignore": [ - "**/.*", - "docs", - "node_modules", - "bower_components", - "test", - "scripts", - "bower.json", - "inch.json" - ], - "dependencies": { - }, - "devDependencies": { - } -} diff --git a/package.json b/package.json index d35cbb2..308469f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.3.5", + "version": "4.4.4", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ @@ -40,11 +40,11 @@ "lib": "babel src --out-dir lib -s", "predocs": "rimraf docs/**", "docs": "jsdoc -c scripts/jsdoc.conf.json -R README.md", - "publish:patch": "npm version patch --no-git-tag-version && npm publish", - "publish:minor": "npm version minor --no-git-tag-version && npm publish", - "publish:major": "npm version major --no-git-tag-version && npm publish", + "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", - "postpublish": "node scripts/release.js" + "postpublish": "git push && git push --tags" }, "files": [ "dist/", @@ -72,7 +72,6 @@ "electron": "^1.4.15", "eslint": "^3.5.0", "floss": "^2.0.1", - "gh-pages": "^0.11.0", "jaguarjs-jsdoc": "^1.0.1", "js-md5": "^0.4.1", "jsdoc": "^3.4.2", diff --git a/scripts/jsdoc.conf.json b/scripts/jsdoc.conf.json index 553e61c..3f52180 100644 --- a/scripts/jsdoc.conf.json +++ b/scripts/jsdoc.conf.json @@ -43,7 +43,7 @@ }, "markdown" : { "parser" : "gfm", - "hardwrap" : true + "hardwrap" : false }, "opts": { "encoding" : "utf8", diff --git a/scripts/release.js b/scripts/release.js deleted file mode 100644 index 156620c..0000000 --- a/scripts/release.js +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env node -'use strict'; - -// Publish script to push releases of the bin files -// the normally are gitignored -const ghpages = require('gh-pages'); -const path = require('path'); -const packageInfo = require(path.join(__dirname, '..', 'package.json')); - -const options = { - src: [ - 'dist/**/*', - 'lib/**/*', - 'src/**/*', - 'scripts/*', - 'scripts/renders/*', - 'test/**/*', - '*.json', - '*.md', - 'LICENSE', - '.eslintrc', - '.editorconfig', - '.travis.yml', - '.babelrc', - ], - dotfiles: true, - branch: 'release', - message: packageInfo.version, - logger: console.log.bind(console), -}; - -ghpages.publish(process.cwd(), options, (err) => -{ - if (err) - { - console.log(err); - process.exit(1); - - return; - } - - process.exit(0); -}); diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index f16bcb1..220427a 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -21,7 +21,7 @@ const DIV_HOOK_ZINDEX = 2; /** - * The Accessibility manager reacreates the ability to tab and and have content read by screen + * The Accessibility manager recreates the ability to tab and and have content read by screen * readers. This is very important as it can possibly help people with disabilities access pixi * content. * diff --git a/src/accessibility/index.js b/src/accessibility/index.js index 568cfb1..0ae108f 100644 --- a/src/accessibility/index.js +++ b/src/accessibility/index.js @@ -1,4 +1,9 @@ /** + * This namespace contains a renderer plugin for interaction accessibility for end-users + * with physical impairments which require screen-renders, keyboard navigation, etc. + * + * Do not instantiate this plugin directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. * @namespace PIXI.accessibility */ export { default as accessibleTarget } from './accessibleTarget'; diff --git a/src/core/Application.js b/src/core/Application.js index 5340578..2fcc729 100644 --- a/src/core/Application.js +++ b/src/core/Application.js @@ -1,6 +1,8 @@ import { autoDetectRenderer } from './autoDetectRenderer'; import Container from './display/Container'; import { shared, Ticker } from './ticker'; +import settings from './settings'; +import { UPDATE_PRIORITY } from './const'; /** * Convenience class to create a new PIXI application. @@ -22,28 +24,52 @@ */ export default class Application { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experience unexplained flickering try setting this to true. - * @param {boolean} [useSharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedLoader=false] - `true` to use PIXI.loaders.shared, `false` to create new Loader. */ - constructor(width, height, options, noWebGL, useSharedTicker = false) + constructor(options, arg2, arg3, arg4, arg5) { + // Support for constructor(width, height, options, noWebGL, useSharedTicker) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + forceCanvas: !!arg4, + sharedTicker: !!arg5, + }, arg3); + } + + /** + * The default options, so we mixin functionality later. + * @member {object} + * @protected + */ + this._options = options = Object.assign({ + sharedTicker: false, + forceCanvas: false, + sharedLoader: false, + }, options); + /** * WebGL renderer if available, otherwise CanvasRenderer * @member {PIXI.WebGLRenderer|PIXI.CanvasRenderer} */ - this.renderer = autoDetectRenderer(width, height, options, noWebGL); + this.renderer = autoDetectRenderer(options); /** * The root display container that's rendered. @@ -63,7 +89,7 @@ * @member {PIXI.ticker.Ticker} * @default PIXI.ticker.shared */ - this.ticker = useSharedTicker ? shared : new Ticker(); + this.ticker = options.sharedTicker ? shared : new Ticker(); // Start the rendering this.start(); @@ -78,7 +104,7 @@ this._ticker = ticker; if (ticker) { - ticker.add(this.render, this); + ticker.add(this.render, this, UPDATE_PRIORITY.LOW); } } get ticker() // eslint-disable-line require-jsdoc @@ -136,13 +162,18 @@ */ destroy(removeView) { - this.stop(); + const oldTicker = this._ticker; + this.ticker = null; + oldTicker.destroy(); + this.stage.destroy(); this.stage = null; this.renderer.destroy(removeView); this.renderer = null; + + this._options = null; } } diff --git a/src/core/autoDetectRenderer.js b/src/core/autoDetectRenderer.js index 8960425..9ec0364 100644 --- a/src/core/autoDetectRenderer.js +++ b/src/core/autoDetectRenderer.js @@ -2,6 +2,7 @@ import CanvasRenderer from './renderers/canvas/CanvasRenderer'; import WebGLRenderer from './renderers/webgl/WebGLRenderer'; +// eslint-disable-next-line valid-jsdoc /** * This helper function will automatically detect which renderer you should be using. * WebGL is the preferred renderer as it is a lot faster. If webGL is not supported by @@ -9,24 +10,32 @@ * * @memberof PIXI * @function autoDetectRenderer - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @return {PIXI.WebGLRenderer|PIXI.CanvasRenderer} Returns WebGL renderer if available, otherwise CanvasRenderer */ -export function autoDetectRenderer(width = 800, height = 600, options, noWebGL) +export function autoDetectRenderer(options, arg1, arg2, arg3) { - if (!noWebGL && utils.isWebGLSupported()) + // Backward-compatible support for noWebGL option + let forceCanvas = options && options.forceCanvas; + + if (arg3 !== undefined) { - return new WebGLRenderer(width, height, options); + forceCanvas = arg3; } - return new CanvasRenderer(width, height, options); + if (!forceCanvas && utils.isWebGLSupported()) + { + return new WebGLRenderer(options, arg1, arg2); + } + + return new CanvasRenderer(options, arg1, arg2); } diff --git a/src/core/const.js b/src/core/const.js index b9e99ae..2c68dee 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -341,3 +341,27 @@ LINEAR_VERTICAL: 0, LINEAR_HORIZONTAL: 1, }; + +/** + * Represents the update priorities used by internal PIXI classes when registered with + * the {@link PIXI.ticker.Ticker} object. Higher priority items are updated first and lower + * priority items, such as render, should go later. + * + * @static + * @constant + * @name UPDATE_PRIORITY + * @memberof PIXI + * @type {object} + * @property {number} INTERACTION=50 Highest priority, used for {@link PIXI.interaction.InteractionManager} + * @property {number} HIGH=25 High priority updating, {@link PIXI.VideoBaseTexture} and {@link PIXI.extras.AnimatedSprite} + * @property {number} NORMAL=0 Default priority for ticker events, see {@link PIXI.ticker.Ticker#add}. + * @property {number} LOW=-25 Low priority used for {@link PIXI.Application} rendering. + * @property {number} UTILITY=-50 Lowest priority used for {@link PIXI.prepare.BasePrepare} utility. + */ +export const UPDATE_PRIORITY = { + INTERACTION: 50, + HIGH: 25, + NORMAL: 0, + LOW: -25, + UTILITY: -50, +}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 4e6c77d..decb42a 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -13,7 +13,6 @@ * * @class * @extends EventEmitter - * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ export default class DisplayObject extends EventEmitter @@ -113,6 +112,29 @@ * @private */ this._mask = null; + + /** + * If the object has been destroyed via destroy(). If true, it should not be used. + * + * @member {boolean} + * @private + * @readonly + */ + this._destroyed = false; + + /** + * Fired when this DisplayObject is added to a Container. + * + * @event PIXI.DisplayObject#added + * @param {PIXI.Container} container - The container added to. + */ + + /** + * Fired when this DisplayObject is removed from a Container. + * + * @event PIXI.DisplayObject#removed + * @param {PIXI.Container} container - The container removed from. + */ } /** @@ -403,6 +425,8 @@ this.interactive = false; this.interactiveChildren = false; + + this._destroyed = true; } /** diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index aafdfe2..f787c4a 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -792,8 +792,8 @@ sprite.worldAlpha = this.worldAlpha * sprite.alpha; sprite.blendMode = this.blendMode; - sprite.texture._frame.width = rect.width; - sprite.texture._frame.height = rect.height; + sprite._texture._frame.width = rect.width; + sprite._texture._frame.height = rect.height; sprite.transform.worldTransform = this.transform.worldTransform; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index a24c775..b9f192d 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -19,11 +19,12 @@ */ export default class SystemRenderer extends EventEmitter { + // eslint-disable-next-line valid-jsdoc /** * @param {string} system - The name of the system this renderer is for. - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -37,27 +38,31 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(system, screenWidth, screenHeight, options) + constructor(system, options, arg2, arg3) { super(); sayHello(system); - // prepare options - if (options) + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') { - for (const i in settings.RENDER_OPTIONS) - { - if (typeof options[i] === 'undefined') - { - options[i] = settings.RENDER_OPTIONS[i]; - } - } + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); } - else - { - options = settings.RENDER_OPTIONS; - } + + // Add the default render options + options = Object.assign({}, settings.RENDER_OPTIONS, options); + + /** + * The supplied constructor options. + * + * @member {Object} + * @readOnly + */ + this.options = options; /** * The type of the renderer. @@ -75,7 +80,7 @@ * * @member {PIXI.Rectangle} */ - this.screen = new Rectangle(0, 0, screenWidth || 800, screenHeight || 600); + this.screen = new Rectangle(0, 0, options.width, options.height); /** * The canvas element that everything is drawn to @@ -279,6 +284,8 @@ this.blendModes = null; + this.options = null; + this.preserveDrawingBuffer = false; this.clearBeforeRender = false; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index e84e49c..691caf0 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -17,10 +17,11 @@ */ export default class CanvasRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -34,9 +35,9 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('Canvas', screenWidth, screenHeight, options); + super('Canvas', options, arg2, arg3); this.type = RENDERER_TYPE.CANVAS; @@ -96,7 +97,19 @@ this.context = null; this.renderingToScreen = false; - this.resize(screenWidth, screenHeight); + this.resize(this.options.width, this.options.height); + + /** + * Fired after rendering finishes. + * + * @event PIXI.CanvasRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.CanvasRenderer#prerender + */ } /** @@ -299,4 +312,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.CanvasRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.CanvasExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.CanvasPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.CanvasRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index e1cf7cd..f6fe123 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -31,11 +31,12 @@ */ export default class WebGLRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** * - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -55,9 +56,9 @@ * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('WebGL', screenWidth, screenHeight, options); + super('WebGL', options, arg2, arg3); /** * The type of this renderer as a standardised const @@ -89,12 +90,6 @@ }; - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ this._backgroundColorRgba[3] = this.transparent ? 0 : 1; this.globalUniforms = new UniformGroup({ @@ -117,6 +112,13 @@ this.initPlugins(); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ if(options.context) { this.context.initFromContext(options.context); @@ -169,6 +171,25 @@ } return this; + + /** + * Fired after rendering finishes. + * + * @event PIXI.WebGLRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.WebGLRenderer#prerender + */ + + /** + * Fired when the WebGL context is set. + * + * @event PIXI.WebGLRenderer#context + * @param {WebGLRenderingContext} gl - WebGL context. + */ } /** @@ -295,4 +316,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.WebGLRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.WebGLRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(WebGLRenderer); diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index 4eb6128..fa3d883 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -47,6 +47,14 @@ * @member {boolean} */ this.enabled = true; + + /** + * If enabled, pixi will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; } /** diff --git a/src/core/renderers/webgl/systems/ContextSystem.js b/src/core/renderers/webgl/systems/ContextSystem.js index ff958c7..8df85ea 100644 --- a/src/core/renderers/webgl/systems/ContextSystem.js +++ b/src/core/renderers/webgl/systems/ContextSystem.js @@ -75,7 +75,8 @@ */ createContext(canvas, options) { - var gl = canvas.getContext('webgl', options) || + var gl = //canvas.getContext('webgl2', options) || + canvas.getContext('webgl', options) || canvas.getContext('experimental-webgl', options); if (!gl) diff --git a/src/core/renderers/webgl/systems/FilterSystem.js b/src/core/renderers/webgl/systems/FilterSystem.js index f47335d..37e28d2 100644 --- a/src/core/renderers/webgl/systems/FilterSystem.js +++ b/src/core/renderers/webgl/systems/FilterSystem.js @@ -111,7 +111,7 @@ // TODO we should fit the rect around the transform.. } - else + else if (filters[0].autoFit) { sourceFrame.fit(filterData.stack[0].destinationFrame); } diff --git a/src/core/renderers/webgl/systems/shader/shader/mapType.js b/src/core/renderers/webgl/systems/shader/shader/mapType.js index 581dace..917538a 100644 --- a/src/core/renderers/webgl/systems/shader/shader/mapType.js +++ b/src/core/renderers/webgl/systems/shader/shader/mapType.js @@ -15,7 +15,7 @@ } } - return GL_TABLE[type]; + return GL_TABLE[type]; }; var GL_TABLE = null; diff --git a/src/core/settings.js b/src/core/settings.js index 18c5877..38ef45c 100644 --- a/src/core/settings.js +++ b/src/core/settings.js @@ -2,6 +2,15 @@ import canUploadSameBuffer from './utils/canUploadSameBuffer'; /** + * User's customizable globals for overriding the default PIXI settings, such + * as a renderer's default resolution, framerate, float percision, etc. + * @example + * // Use the native window resolution as the default resolution + * // will support high-density displays when rendering + * PIXI.settings.RESOLUTION = window.devicePixelRatio. + * + * // Disable interpolation when scaling, will make texture be pixelated + * PIXI.settings.SCALE_MODE = PIXI.SCALE_MODES.NEAREST; * @namespace PIXI.settings */ export default { @@ -101,6 +110,9 @@ * @property {boolean} clearBeforeRender=true * @property {boolean} preserveDrawingBuffer=false * @property {boolean} roundPixels=false + * @property {number} width=800 + * @property {number} height=600 + * @property {boolean} legacy=false */ RENDER_OPTIONS: { view: null, @@ -112,6 +124,9 @@ clearBeforeRender: true, preserveDrawingBuffer: false, roundPixels: false, + width: 800, + height: 600, + legacy: false, }, /** diff --git a/src/core/shader/Program.js b/src/core/shader/Program.js index c0b538b..07b4398 100644 --- a/src/core/shader/Program.js +++ b/src/core/shader/Program.js @@ -1,6 +1,6 @@ import extractUniformsFromSrc from './extractUniformsFromSrc'; import generateUniformsSync from './generateUniformsSync'; -import glCore from 'pixi-gl-core'; +import shaderUtils from '../renderers/webgl/systems/shader/shader'; import { ProgramCache } from '../utils'; import getTestContext from '../utils/getTestContext'; @@ -64,14 +64,17 @@ } else { - vertexSrc = glCore.shader.setPrecision(vertexSrc, 'mediump'); - fragmentSrc = glCore.shader.setPrecision(fragmentSrc, 'mediump'); + vertexSrc = shaderUtils.setPrecision(vertexSrc, 'mediump'); + fragmentSrc = shaderUtils.setPrecision(fragmentSrc, 'mediump'); - const program = glCore.shader.compileProgram(gl, vertexSrc, fragmentSrc); + const program = shaderUtils.compileProgram(gl, vertexSrc, fragmentSrc); + console.log("<<<<<<>>><<"); this.attributeData = this.getAttributeData(program, gl); this.uniformData = this.getUniformData(program, gl); + console.log(this.uniformData); + //gl.deleteProgram(program); } } @@ -95,13 +98,13 @@ for (let i = 0; i < totalAttributes; i++) { const attribData = gl.getActiveAttrib(program, i); - const type = glCore.shader.mapType(gl, attribData.type); + const type = shaderUtils.mapType(gl, attribData.type); /*eslint-disable */ const data = { type: type, name: attribData.name, - size: glCore.shader.mapSize(type), + size: shaderUtils.mapSize(type), location: 0, }; /*eslint-enable */ @@ -137,13 +140,14 @@ // TODO expose this as a prop? // const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); - const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); + //const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); for (let i = 0; i < totalUniforms; i++) { const uniformData = gl.getActiveUniform(program, i); const name = uniformData.name.replace(/\[.*?\]/, ''); - const type = glCore.shader.mapType(gl, uniformData.type); + const type = shaderUtils.mapType(gl, uniformData.type); + console.log('mapping ' + uniformData.type + ' to ' + type); // if (!name.match(maskRegex)) { @@ -151,7 +155,7 @@ uniforms[name] = { type: type, size: uniformData.size, - value: glCore.shader.defaultValue(type, uniformData.size), + value: shaderUtils.defaultValue(type, uniformData.size), }; /*eslint-enable */ } diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index e387e99..b57d095 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -147,12 +147,12 @@ // so if _width is 0 then width was not set.. if (this._width) { - this.scale.x = sign(this.scale.x) * this._width / this.texture.orig.width; + this.scale.x = sign(this.scale.x) * this._width / this._texture.orig.width; } if (this._height) { - this.scale.y = sign(this.scale.y) * this._height / this.texture.orig.height; + this.scale.y = sign(this.scale.y) * this._height / this._texture.orig.height; } } diff --git a/src/core/sprites/canvas/CanvasTinter.js b/src/core/sprites/canvas/CanvasTinter.js index 27e5e0c..fd9de5d 100644 --- a/src/core/sprites/canvas/CanvasTinter.js +++ b/src/core/sprites/canvas/CanvasTinter.js @@ -18,7 +18,7 @@ */ getTintedTexture: (sprite, color) => { - const texture = sprite.texture; + const texture = sprite._texture; color = CanvasTinter.roundColor(color); diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 5a4edda..8b9b797 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -344,6 +344,7 @@ const texture = this._texture; const style = this._style; + const padding = style.trim ? 0 : style.padding; texture.baseTexture.valid = true; texture.baseTexture.resolution = this.resolution; @@ -353,11 +354,11 @@ texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - texture.trim.x = -style.padding; - texture.trim.y = -style.padding; + texture.trim.x = -padding; + texture.trim.y = -padding; - texture.orig.width = texture._frame.width - (style.padding * 2); - texture.orig.height = texture._frame.height - (style.padding * 2); + texture.orig.width = texture._frame.width - (padding * 2); + texture.orig.height = texture._frame.height - (padding * 2); // call sprite onTextureUpdate to update scale if _width or _height were set this._onTextureUpdate(); diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index 853da3d..8a9c34b 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -114,6 +114,38 @@ this.cacheId = null; this.validate(); + + /** + * Fired when a not-immediately-available source finishes loading. + * + * @protected + * @event PIXI.BaseTexture#loaded + * @param {PIXI.BaseTexture} baseTexture - Resource loaded. + */ + + /** + * Fired when a not-immediately-available source fails to load. + * + * @protected + * @event PIXI.BaseTexture#error + * @param {PIXI.BaseTexture} baseTexture - Resource errored. + */ + + /** + * Fired when BaseTexture is updated. + * + * @protected + * @event PIXI.BaseTexture#update + * @param {PIXI.BaseTexture} baseTexture - Instance of texture being updated. + */ + + /** + * Fired when BaseTexture is destroyed. + * + * @protected + * @event PIXI.BaseTexture#dispose + * @param {PIXI.BaseTexture} baseTexture - Instance of texture being destroyed. + */ } setResource(resource) diff --git a/src/core/textures/Spritesheet.js b/src/core/textures/Spritesheet.js index d469c62..9b24f27 100644 --- a/src/core/textures/Spritesheet.js +++ b/src/core/textures/Spritesheet.js @@ -208,6 +208,7 @@ // lets also add the frame to pixi's global cache for fromFrame and fromImage functions TextureCache[i] = this.textures[i]; + this.textures[i].textureCacheId = i; } frameIndex++; diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 0a2c379..2213087 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -147,9 +147,9 @@ /** * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. * - * @event update - * @memberof PIXI.Texture# + * @event PIXI.Texture#update * @protected + * @param {PIXI.Texture} texture - Instance of texture being updated. */ this._updateID = 0; @@ -159,6 +159,15 @@ * @type {Object} */ this.transform = null; + + /** + * The id under which this Texture has been added to the texture cache. This is + * automatically set in certain cases, but may not always be accurate, particularly if + * the texture is in the cache under multiple ids. + * + * @member {string} + */ + this.textureCacheId = null; } /** @@ -225,11 +234,17 @@ this._uvs = null; this.trim = null; this.orig = null; + this.textureCacheId = null; this.valid = false; - this.off('dispose', this.dispose, this); - this.off('update', this.update, this); + for (const prop in TextureCache) + { + if (TextureCache[prop] === this) + { + delete TextureCache[prop]; + } + } } /** @@ -315,14 +330,15 @@ */ static fromLoader(source, imageUrl, name) { - // console.log('added from loader...') + // console.log('added from loader...') const resource = new ImageResource(source);//.from(imageUrl, crossorigin);// document.createElement('img'); - // console.log('base resource ' + resource.width); + // console.log('base resource ' + resource.width); const baseTexture = new BaseTexture(resource, settings.SCALE_MODE, getResolutionOfUrl(imageUrl)); + /// console.log('base width ' + baseTexture.width); const texture = new Texture(baseTexture); @@ -335,6 +351,7 @@ // lets also add the frame to pixi's global cache for fromFrame and fromImage fucntions BaseTextureCache[name] = baseTexture; TextureCache[name] = texture; + texture.textureCacheId = name; // also add references by url if they are different. if (name !== imageUrl) @@ -355,6 +372,10 @@ */ static addTextureToCache(texture, id) { + if (!texture.textureCacheId) + { + texture.textureCacheId = id; + } TextureCache[id] = texture; } @@ -495,6 +516,7 @@ */ Texture.EMPTY = new Texture(new BaseTexture()); removeAllHandlers(Texture.EMPTY); +removeAllHandlers(Texture.EMPTY.baseTexture); /** * A white texture of 10x10 size, used for graphics and other things @@ -505,3 +527,4 @@ */ Texture.WHITE = createWhiteTexture(); removeAllHandlers(Texture.WHITE); +removeAllHandlers(Texture.WHITE.baseTexture); diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index 59eb9fd..a9b9f51 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -1,6 +1,7 @@ import BaseTexture from './BaseTexture'; import { uid, BaseTextureCache } from '../utils'; -import * as ticker from '../ticker'; +import { shared } from '../ticker'; +import { UPDATE_PRIORITY } from '../const'; /** * A texture of a [playing] Video. @@ -125,7 +126,7 @@ if (!this._isAutoUpdating && this.autoUpdate) { - ticker.shared.add(this.update, this); + shared.add(this.update, this, UPDATE_PRIORITY.HIGH); this._isAutoUpdating = true; } } @@ -139,7 +140,7 @@ { if (this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); this._isAutoUpdating = false; } } @@ -187,7 +188,7 @@ { if (this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); } if (this.source && this.source._pixiId) @@ -281,12 +282,12 @@ if (!this._autoUpdate && this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); this._isAutoUpdating = false; } else if (this._autoUpdate && !this._isAutoUpdating) { - ticker.shared.add(this.update, this); + shared.add(this.update, this, UPDATE_PRIORITY.HIGH); this._isAutoUpdating = true; } } diff --git a/src/core/ticker/Ticker.js b/src/core/ticker/Ticker.js index 17a1517..40df138 100644 --- a/src/core/ticker/Ticker.js +++ b/src/core/ticker/Ticker.js @@ -1,12 +1,10 @@ import settings from '../settings'; -import EventEmitter from 'eventemitter3'; - -// Internal event used by composed emitter -const TICK = 'tick'; +import { UPDATE_PRIORITY } from '../const'; +import TickerListener from './TickerListener'; /** * A Ticker class that runs an update loop that other objects listen to. - * This class is composed around an EventEmitter object to add listeners + * This class is composed around listeners * meant for execution on the next requested animation frame. * Animation frames are requested only when necessary, * e.g. When the ticker is started and the emitter has listeners. @@ -22,10 +20,11 @@ constructor() { /** - * Internal emitter used to fire 'tick' event + * The first listener. All new listeners added are chained on this. * @private + * @type {TickerListener} */ - this._emitter = new EventEmitter(); + this._head = new TickerListener(null, null, Infinity); /** * Internal current frame request ID @@ -131,7 +130,7 @@ // Invoke listeners now this.update(time); // Listener side effects may have modified ticker state. - if (this.started && this._requestId === null && this._emitter.listeners(TICK, true)) + if (this.started && this._requestId === null && this._head.next) { this._requestId = requestAnimationFrame(this._tick); } @@ -148,7 +147,7 @@ */ _requestIfNeeded() { - if (this._requestId === null && this._emitter.listeners(TICK, true)) + if (this._requestId === null && this._head.next) { // ensure callbacks get correct delta this.lastTime = performance.now(); @@ -193,35 +192,72 @@ } /** - * Calls {@link module:eventemitter3.EventEmitter#on} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. + * Register a handler for tick events. Calls continuously unless + * it is removed or the ticker is stopped. * * @param {Function} fn - The listener function to be added for updates * @param {Function} [context] - The listener context + * @param {number} [priority=PIXI.UPDATE_PRIORITY.NORMAL] - The priority for emitting * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - add(fn, context) + add(fn, context, priority = UPDATE_PRIORITY.NORMAL) { - this._emitter.on(TICK, fn, context); - - this._startIfPossible(); - - return this; + return this._addListener(new TickerListener(fn, context, priority)); } /** - * Calls {@link module:eventemitter3.EventEmitter#once} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. + * Add a handler for the tick event which is only execute once. * * @param {Function} fn - The listener function to be added for one update * @param {Function} [context] - The listener context + * @param {number} [priority=PIXI.UPDATE_PRIORITY.NORMAL] - The priority for emitting * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - addOnce(fn, context) + addOnce(fn, context, priority = UPDATE_PRIORITY.NORMAL) { - this._emitter.once(TICK, fn, context); + return this._addListener(new TickerListener(fn, context, priority, true)); + } + + /** + * Internally adds the event handler so that it can be sorted by priority. + * Priority allows certain handler (user, AnimatedSprite, Interaction) to be run + * before the rendering. + * + * @private + * @param {TickerListener} listener - Current listener being added. + * @returns {PIXI.ticker.Ticker} This instance of a ticker + */ + _addListener(listener) + { + // For attaching to head + let current = this._head.next; + let previous = this._head; + + // Add the first item + if (!current) + { + listener.connect(previous); + } + else + { + // Go from highest to lowest priority + while (current) + { + if (listener.priority >= current.priority) + { + listener.connect(previous); + break; + } + previous = current; + current = current.next; + } + + // Not yet connected + if (!listener.previous) + { + listener.connect(previous); + } + } this._startIfPossible(); @@ -229,19 +265,33 @@ } /** - * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. - * It checks if the emitter has listeners for 'tick' event. - * If it does, then it cancels the animation frame. + * Removes any handlers matching the function and context parameters. + * If no handlers are left after removing, then it cancels the animation frame. * - * @param {Function} [fn] - The listener function to be removed + * @param {Function} fn - The listener function to be removed * @param {Function} [context] - The listener context to be removed * @returns {PIXI.ticker.Ticker} This instance of a ticker */ remove(fn, context) { - this._emitter.off(TICK, fn, context); + let listener = this._head.next; - if (!this._emitter.listeners(TICK, true)) + while (listener) + { + // We found a match, lets remove it + // no break to delete all possible matches + // incase a listener was added 2+ times + if (listener.match(fn, context)) + { + listener = listener.destroy(); + } + else + { + listener = listener.next; + } + } + + if (!this._head.next) { this._cancelIfNeeded(); } @@ -276,6 +326,25 @@ } /** + * Destroy the ticker and don't use after this. Calling + * this method removes all references to internal events. + */ + destroy() + { + this.stop(); + + let listener = this._head.next; + + while (listener) + { + listener = listener.destroy(true); + } + + this._head.destroy(); + this._head = null; + } + + /** * Triggers an update. An update entails setting the * current {@link PIXI.ticker.Ticker#elapsedMS}, * the current {@link PIXI.ticker.Ticker#deltaTime}, @@ -320,8 +389,22 @@ this.deltaTime = elapsedMS * settings.TARGET_FPMS * this.speed; + // Cache a local reference, in-case ticker is destroyed + // during the emit, we can still check for head.next + const head = this._head; + // Invoke listeners added to internal emitter - this._emitter.emit(TICK, this.deltaTime); + let listener = head.next; + + while (listener) + { + listener = listener.emit(this.deltaTime); + } + + if (!head.next) + { + this._cancelIfNeeded(); + } } else { diff --git a/src/core/ticker/TickerListener.js b/src/core/ticker/TickerListener.js new file mode 100644 index 0000000..2bedb34 --- /dev/null +++ b/src/core/ticker/TickerListener.js @@ -0,0 +1,158 @@ +/** + * Internal class for handling the priority sorting of ticker handlers. + * + * @private + * @class + * @memberof PIXI.ticker + */ +export default class TickerListener +{ + /** + * Constructor + * + * @param {Function} fn - The listener function to be added for one update + * @param {Function} [context=null] - The listener context + * @param {number} [priority=0] - The priority for emitting + * @param {boolean} [once=false] - If the handler should fire once + */ + constructor(fn, context = null, priority = 0, once = false) + { + /** + * The handler function to execute. + * @member {Function} + */ + this.fn = fn; + + /** + * The calling to execute. + * @member {Function} + */ + this.context = context; + + /** + * The current priority. + * @member {number} + */ + this.priority = priority; + + /** + * If this should only execute once. + * @member {boolean} + */ + this.once = once; + + /** + * The next item in chain. + * @member {TickerListener} + */ + this.next = null; + + /** + * The previous item in chain. + * @member {TickerListener} + */ + this.previous = null; + + /** + * `true` if this listener has been destroyed already. + * @member {boolean} + * @private + */ + this._destroyed = false; + } + + /** + * Simple compare function to figure out if a function and context match. + * + * @param {Function} fn - The listener function to be added for one update + * @param {Function} context - The listener context + * @return {boolean} `true` if the listener match the arguments + */ + match(fn, context) + { + context = context || null; + + return this.fn === fn && this.context === context; + } + + /** + * Emit by calling the current function. + * @param {number} deltaTime - time since the last emit. + * @return {TickerListener} Next ticker + */ + emit(deltaTime) + { + if (this.context) + { + this.fn.call(this.context, deltaTime); + } + else + { + this.fn(deltaTime); + } + + if (this.once) + { + this.destroy(); + } + + const redirect = this.next; + + // Soft-destroying should remove + // the next reference + if (this._destroyed) + { + this.next = null; + } + + return redirect; + } + + /** + * Connect to the list. + * @param {TickerListener} previous - Input node, previous listener + */ + connect(previous) + { + this.previous = previous; + if (previous.next) + { + previous.next.previous = this; + } + this.next = previous.next; + previous.next = this; + } + + /** + * Destroy and don't use after this. + * @param {boolean} [hard = false] `true` to remove the `next` reference, this + * is considered a hard destroy. Soft destroy maintains the next reference. + * @return {TickerListener} The listener to redirect while emitting or removing. + */ + destroy(hard = false) + { + this._destroyed = true; + this.fn = null; + this.context = null; + + // Disconnect, hook up next and previous + if (this.previous) + { + this.previous.next = this.next; + } + + if (this.next) + { + this.next.previous = this.previous; + } + + // Redirect to the next item + const redirect = this.previous; + + // Remove references + this.next = hard ? null : redirect; + this.previous = null; + + return redirect; + } +} diff --git a/src/core/ticker/index.js b/src/core/ticker/index.js index 4b3017c..c3020d4 100644 --- a/src/core/ticker/index.js +++ b/src/core/ticker/index.js @@ -45,8 +45,25 @@ const shared = new Ticker(); shared.autoStart = true; +shared.destroy = () => +{ + // protect destroying shared ticker + // this is used by other internal systems + // like AnimatedSprite and InteractionManager +}; /** + * This namespace contains an API for interacting with PIXI's internal global update loop. + * + * This ticker is used for rendering, {@link PIXI.extras.AnimatedSprite AnimatedSprite}, + * {@link PIXI.interaction.InteractionManager InteractionManager} and many other time-based PIXI systems. + * @example + * const ticker = new PIXI.ticker.Ticker(); + * ticker.stop(); + * ticker.add((deltaTime) => { + * // do something every frame + * }); + * ticker.start(); * @namespace PIXI.ticker */ export { shared, Ticker }; diff --git a/src/core/utils/index.js b/src/core/utils/index.js index be44750..7441f7b 100644 --- a/src/core/utils/index.js +++ b/src/core/utils/index.js @@ -9,6 +9,21 @@ let saidHello = false; /** + * Generalized convenience utilities for PIXI. + * @example + * // Extend PIXI's internal Event Emitter. + * class MyEmitter extends PIXI.utils.EventEmitter { + * constructor() { + * super(); + * console.log("Emitter created!"); + * } + * } + * + * // Get info on current device + * console.log(PIXI.utils.isMobile); + * + * // Convert hex color to string + * console.log(PIXI.utils.hex2string(0xff00ff)); // returns: "#ff00ff" * @namespace PIXI.utils */ export { diff --git a/src/deprecation.js b/src/deprecation.js index b9c73b3..33daeec 100644 --- a/src/deprecation.js +++ b/src/deprecation.js @@ -918,6 +918,35 @@ }); /** + * @method + * @name PIXI.prepare.BasePrepare#register + * @see PIXI.prepare.BasePrepare#registerFindHook + * @deprecated since version 4.4.2 + * @param {Function} [addHook] - Function call that takes two parameters: `item:*, queue:Array` + * function must return `true` if it was able to add item to the queue. + * @param {Function} [uploadHook] - Function call that takes two parameters: `prepare:CanvasPrepare, item:*` and + * function must return `true` if it was able to handle upload of item. + * @return {PIXI.BasePrepare} Instance of plugin for chaining. + */ +prepare.BasePrepare.prototype.register = function register(addHook, uploadHook) +{ + warn('renderer.plugins.prepare.register is now deprecated, ' + + 'please use renderer.plugins.prepare.registerFindHook & renderer.plugins.prepare.registerUploadHook'); + + if (addHook) + { + this.registerFindHook(addHook); + } + + if (uploadHook) + { + this.registerUploadHook(uploadHook); + } + + return this; +}; + +/** * The number of graphics or textures to upload to the GPU. * * @name PIXI.prepare.canvas.UPLOADS_PER_FRAME @@ -969,67 +998,73 @@ }, }); -Object.defineProperties(loaders.Resource.prototype, { - isJson: { - get() - { - warn('The isJson property is deprecated, please use `resource.type === Resource.TYPE.JSON`.'); +if (loaders.Loader) +{ + const Resource = loaders.Resource; + const Loader = loaders.Loader; - return this.type === loaders.Loader.Resource.TYPE.JSON; + Object.defineProperties(Resource.prototype, { + isJson: { + get() + { + warn('The isJson property is deprecated, please use `resource.type === Resource.TYPE.JSON`.'); + + return this.type === Resource.TYPE.JSON; + }, }, - }, - isXml: { - get() - { - warn('The isXml property is deprecated, please use `resource.type === Resource.TYPE.XML`.'); + isXml: { + get() + { + warn('The isXml property is deprecated, please use `resource.type === Resource.TYPE.XML`.'); - return this.type === loaders.Loader.Resource.TYPE.XML; + return this.type === Resource.TYPE.XML; + }, }, - }, - isImage: { - get() - { - warn('The isImage property is deprecated, please use `resource.type === Resource.TYPE.IMAGE`.'); + isImage: { + get() + { + warn('The isImage property is deprecated, please use `resource.type === Resource.TYPE.IMAGE`.'); - return this.type === loaders.Loader.Resource.TYPE.IMAGE; + return this.type === Resource.TYPE.IMAGE; + }, }, - }, - isAudio: { - get() - { - warn('The isAudio property is deprecated, please use `resource.type === Resource.TYPE.AUDIO`.'); + isAudio: { + get() + { + warn('The isAudio property is deprecated, please use `resource.type === Resource.TYPE.AUDIO`.'); - return this.type === loaders.Loader.Resource.TYPE.AUDIO; + return this.type === Resource.TYPE.AUDIO; + }, }, - }, - isVideo: { - get() - { - warn('The isVideo property is deprecated, please use `resource.type === Resource.TYPE.VIDEO`.'); + isVideo: { + get() + { + warn('The isVideo property is deprecated, please use `resource.type === Resource.TYPE.VIDEO`.'); - return this.type === loaders.Loader.Resource.TYPE.VIDEO; + return this.type === Resource.TYPE.VIDEO; + }, }, - }, -}); + }); -Object.defineProperties(loaders.Loader.prototype, { - before: { - get() - { - warn('The before() method is deprecated, please use pre().'); + Object.defineProperties(Loader.prototype, { + before: { + get() + { + warn('The before() method is deprecated, please use pre().'); - return this.pre; + return this.pre; + }, }, - }, - after: { - get() - { - warn('The after() method is deprecated, please use use().'); + after: { + get() + { + warn('The after() method is deprecated, please use use().'); - return this.use; + return this.use; + }, }, - }, -}); + }); +} /** * @name PIXI.interaction.interactiveTarget#defaultCursor diff --git a/src/extract/canvas/CanvasExtract.js b/src/extract/canvas/CanvasExtract.js index 941ca05..3ac5aee 100644 --- a/src/extract/canvas/CanvasExtract.js +++ b/src/extract/canvas/CanvasExtract.js @@ -8,7 +8,7 @@ * An instance of this class is automatically created by default, and can be found at renderer.plugins.extract * * @class - * @memberof PIXI + * @memberof PIXI.extract */ export default class CanvasExtract { @@ -21,9 +21,9 @@ /** * Collection of methods for extracting data (image, pixels, etc.) from a display object or render texture * - * @member {PIXI.CanvasExtract} extract + * @member {PIXI.extract.CanvasExtract} extract * @memberof PIXI.CanvasRenderer# - * @see PIXI.CanvasExtract + * @see PIXI.extract.CanvasExtract */ renderer.extract = this; } diff --git a/src/extract/index.js b/src/extract/index.js index 0f2170d..e9478c1 100644 --- a/src/extract/index.js +++ b/src/extract/index.js @@ -1,2 +1,22 @@ +/** + * This namespace provides renderer-specific plugins for exporting content from a renderer. + * For instance, these plugins can be used for saving an Image, Canvas element or for exporting the raw image data (pixels). + * + * Do not instantiate these plugins directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. + * @example + * // Create a new app (will auto-add extract plugin to renderer) + * const app = new PIXI.Application(); + * + * // Draw a red circle + * const graphics = new PIXI.Graphics() + * .beginFill(0xFF0000) + * .drawCircle(0, 0, 50); + * + * // Render the graphics as an HTMLImageElement + * const image = app.renderer.plugins.extract.image(graphics); + * document.body.appendChild(image); + * @namespace PIXI.extract + */ export { default as webgl } from './webgl/WebGLExtract'; export { default as canvas } from './canvas/CanvasExtract'; diff --git a/src/extract/webgl/WebGLExtract.js b/src/extract/webgl/WebGLExtract.js index 60228bb..1c3dd30 100644 --- a/src/extract/webgl/WebGLExtract.js +++ b/src/extract/webgl/WebGLExtract.js @@ -9,7 +9,7 @@ * An instance of this class is automatically created by default, and can be found at renderer.plugins.extract * * @class - * @memberof PIXI + * @memberof PIXI.extract */ export default class WebGLExtract { @@ -22,9 +22,9 @@ /** * Collection of methods for extracting data (image, pixels, etc.) from a display object or render texture * - * @member {PIXI.WebGLExtract} extract + * @member {PIXI.extract.WebGLExtract} extract * @memberof PIXI.WebGLRenderer# - * @see PIXI.WebGLExtract + * @see PIXI.extract.WebGLExtract */ renderer.extract = this; } diff --git a/src/extras/AnimatedSprite.js b/src/extras/AnimatedSprite.js index 13b804f..5297778 100644 --- a/src/extras/AnimatedSprite.js +++ b/src/extras/AnimatedSprite.js @@ -137,7 +137,7 @@ this.playing = true; if (this._autoUpdate) { - core.ticker.shared.add(this.update, this); + core.ticker.shared.add(this.update, this, core.UPDATE_PRIORITY.HIGH); } } diff --git a/src/extras/TextureTransform.js b/src/extras/TextureTransform.js index 78e6e40..16536d2 100644 --- a/src/extras/TextureTransform.js +++ b/src/extras/TextureTransform.js @@ -65,8 +65,36 @@ } /** + * Multiplies uvs array to transform + * @param {Float32Array} uvs mesh uvs + * @param {Float32Array} [out=uvs] output + * @returns {Float32Array} output + */ + multiplyUvs(uvs, out) + { + if (out === undefined) + { + out = uvs; + } + + const mat = this.mapCoord; + + for (let i = 0; i < uvs.length; i += 2) + { + const x = uvs[i]; + const y = uvs[i + 1]; + + out[i] = (x * mat.a) + (y * mat.c) + mat.tx; + out[i + 1] = (x * mat.b) + (y * mat.d) + mat.ty; + } + + return out; + } + + /** * updates matrices if texture was changed * @param {boolean} forceUpdate if true, matrices will be updated any case + * @returns {boolean} whether or not it was updated */ update(forceUpdate) { @@ -74,13 +102,13 @@ if (!tex || !tex.valid) { - return; + return false; } if (!forceUpdate && this._lastTextureID === tex._updateID) { - return; + return false; } this._lastTextureID = tex._updateID; @@ -110,5 +138,7 @@ frame[3] = (tex._frame.y + tex._frame.height - margin + offset) / texBase.height; this.uClampOffset[0] = offset / texBase.realWidth; this.uClampOffset[1] = offset / texBase.realHeight; + + return true; } } diff --git a/src/extras/TilingSprite.js b/src/extras/TilingSprite.js index 1f6014f..a3f027f 100644 --- a/src/extras/TilingSprite.js +++ b/src/extras/TilingSprite.js @@ -180,9 +180,9 @@ const transform = this.worldTransform; const resolution = renderer.resolution; const baseTexture = texture.baseTexture; - const baseTextureResolution = texture.baseTexture.resolution; - const modX = (this.tilePosition.x / this.tileScale.x) % texture._frame.width; - const modY = (this.tilePosition.y / this.tileScale.y) % texture._frame.height; + const baseTextureResolution = baseTexture.resolution; + const modX = ((this.tilePosition.x / this.tileScale.x) % texture._frame.width) * baseTextureResolution; + const modY = ((this.tilePosition.y / this.tileScale.y) % texture._frame.height) * baseTextureResolution; // create a nice shiny pattern! // TODO this needs to be refreshed if texture changes.. diff --git a/src/extras/cacheAsBitmap.js b/src/extras/cacheAsBitmap.js index 5dd5640..aea06a8 100644 --- a/src/extras/cacheAsBitmap.js +++ b/src/extras/cacheAsBitmap.js @@ -227,7 +227,16 @@ this.transform._parentID = -1; // restore the transform of the cached sprite to avoid the nasty flicker.. - this.updateTransform(); + if (!this.parent) + { + this.parent = renderer._tempDisplayObjectParent; + this.updateTransform(); + this.parent = null; + } + else + { + this.updateTransform(); + } // map the hit test.. this.containsPoint = cachedSprite.containsPoint.bind(cachedSprite); @@ -314,7 +323,17 @@ cachedSprite._bounds = this._bounds; cachedSprite.alpha = cacheAlpha; - this.updateTransform(); + if (!this.parent) + { + this.parent = renderer._tempDisplayObjectParent; + this.updateTransform(); + this.parent = null; + } + else + { + this.updateTransform(); + } + this.updateTransform = this.displayObjectUpdateTransform; this._cacheData.sprite = cachedSprite; @@ -358,9 +377,12 @@ * Destroys the cached object. * * @private + * @param {object|boolean} [options] - Options parameter. A boolean will act as if all options + * have been set to that value. + * Used when destroying containers, see the Container.destroy method. */ -DisplayObject.prototype._cacheAsBitmapDestroy = function _cacheAsBitmapDestroy() +DisplayObject.prototype._cacheAsBitmapDestroy = function _cacheAsBitmapDestroy(options) { this.cacheAsBitmap = false; - this.destroy(); + this.destroy(options); }; diff --git a/src/extras/index.js b/src/extras/index.js index bbd8bbf..a34d7ec 100644 --- a/src/extras/index.js +++ b/src/extras/index.js @@ -1,7 +1,7 @@ /** + * Additional PIXI DisplayObjects for animation, tiling and bitmap text. * @namespace PIXI.extras */ -export { default as TextureTransform } from './TextureTransform'; export { default as AnimatedSprite } from './AnimatedSprite'; export { default as TilingSprite } from './TilingSprite'; export { default as TilingSpriteRenderer } from './webgl/TilingSpriteRenderer'; diff --git a/src/extras/webgl/TilingSpriteRenderer.js b/src/extras/webgl/TilingSpriteRenderer.js index aeec172..eee3b5c 100644 --- a/src/extras/webgl/TilingSpriteRenderer.js +++ b/src/extras/webgl/TilingSpriteRenderer.js @@ -11,7 +11,7 @@ * WebGL renderer plugin for tiling sprites * * @class - * @memberof PIXI + * @memberof PIXI.extras * @extends PIXI.ObjectRenderer */ export default class TilingSpriteRenderer extends core.ObjectRenderer @@ -118,7 +118,7 @@ tempMat.invert(); if (isSimple) { - tempMat.append(uv.mapCoord); + tempMat.prepend(uv.mapCoord); } else { diff --git a/src/filters/displacement/DisplacementFilter.js b/src/filters/displacement/DisplacementFilter.js index d5355c3..b5d030c 100644 --- a/src/filters/displacement/DisplacementFilter.js +++ b/src/filters/displacement/DisplacementFilter.js @@ -35,7 +35,7 @@ this.maskSprite = sprite; this.maskMatrix = maskMatrix; - this.uniforms.mapSampler = sprite.texture; + this.uniforms.mapSampler = sprite._texture; this.uniforms.filterMatrix = maskMatrix; this.uniforms.scale = { x: 1, y: 1 }; diff --git a/src/filters/index.js b/src/filters/index.js index f3e8862..7ceb50a 100644 --- a/src/filters/index.js +++ b/src/filters/index.js @@ -1,4 +1,21 @@ /** + * This namespace contains WebGL-only display filters that can be applied + * to DisplayObjects using the {@link PIXI.DisplayObject#filters filters} property. + * @example + * // Create a new application + * const app = new PIXI.Application(); + * + * // Draw a green rectangle + * const rect = new PIXI.Graphics() + * .beginFill(0x00ff00) + * .drawRect(40, 40, 200, 200); + * + * // Add a blur filter + * rect.filters = [new PIXI.filters.BlurFilter()]; + * + * // Display rectangle + * app.stage.addChild(rect); + * document.body.appendChild(app.view); * @namespace PIXI.filters */ export { default as FXAAFilter } from './fxaa/FXAAFilter'; diff --git a/src/index.js b/src/index.js index c553427..77da490 100644 --- a/src/index.js +++ b/src/index.js @@ -20,6 +20,14 @@ import { utils } from './core'; utils.mixins.performMixins(); +/** + * Alias for {@link PIXI.loaders.shared}. + * @name loader + * @memberof PIXI + * @type {PIXI.loader.Loader} + */ +const loader = loaders.shared || null; + export { accessibility, extract, @@ -30,18 +38,8 @@ mesh, particles, prepare, + loader, }; -/** - * A premade instance of the loader that can be used to load resources. - * - * @name loader - * @memberof PIXI - * @property {PIXI.loaders.Loader} - */ -const loader = loaders && loaders.Loader ? new loaders.Loader() : null; // check is there in case user excludes loader lib - -export { loader }; - // Always export pixi globally. global.PIXI = exports; // eslint-disable-line diff --git a/src/interaction/InteractionData.js b/src/interaction/InteractionData.js index 02d53a3..449f8cc 100644 --- a/src/interaction/InteractionData.js +++ b/src/interaction/InteractionData.js @@ -30,7 +30,10 @@ /** * When passed to an event handler, this will be the original DOM Event that was captured * - * @member {Event} + * @see https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent + * @see https://developer.mozilla.org/en-US/docs/Web/API/TouchEvent + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent + * @member {MouseEvent|TouchEvent|PointerEvent} */ this.originalEvent = null; } diff --git a/src/interaction/InteractionManager.js b/src/interaction/InteractionManager.js index 8baf13a..2d75c7c 100644 --- a/src/interaction/InteractionManager.js +++ b/src/interaction/InteractionManager.js @@ -13,6 +13,14 @@ const MOUSE_POINTER_ID = 'MOUSE'; +// helpers for hitTest() - only used inside hitTest() +const hitTestEvent = { + target: null, + data: { + global: null, + }, +}; + /** * The interaction manager deals with mouse, touch and pointer events. Any DisplayObject can be interactive * if its interactive parameter is set to true @@ -245,67 +253,60 @@ this.setTargetElement(this.renderer.view, this.renderer.resolution); /** - * Fired when a pointer device button (usually a mouse button) is pressed on the display + * Fired when a pointer device button (usually a mouse left-button) is pressed on the display * object. * - * @event mousedown - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mousedown + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device secondary button (usually a mouse right-button) is pressed * on the display object. * - * @event rightdown - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightdown + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** - * Fired when a pointer device button (usually a mouse button) is released over the display + * Fired when a pointer device button (usually a mouse left-button) is released over the display * object. * - * @event mouseup - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseup + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device secondary button (usually a mouse right-button) is released * over the display object. * - * @event rightup - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightup + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** - * Fired when a pointer device button (usually a mouse button) is pressed and released on + * Fired when a pointer device button (usually a mouse left-button) is pressed and released on * the display object. * - * @event click - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#click + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device secondary button (usually a mouse right-button) is pressed * and released on the display object. * - * @event rightclick - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightclick + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** - * Fired when a pointer device button (usually a mouse button) is released outside the + * Fired when a pointer device button (usually a mouse left-button) is released outside the * display object that initially registered a * [mousedown]{@link PIXI.interaction.InteractionManager#event:mousedown}. * - * @event mouseupoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** @@ -313,146 +314,362 @@ * outside the display object that initially registered a * [rightdown]{@link PIXI.interaction.InteractionManager#event:rightdown}. * - * @event rightupoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device (usually a mouse) is moved while over the display object * - * @event mousemove - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mousemove + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device (usually a mouse) is moved onto the display object * - * @event mouseover - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseover + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device (usually a mouse) is moved off the display object * - * @event mouseout - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseout + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is pressed on the display object. * - * @event pointerdown - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerdown + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is released over the display object. * - * @event pointerup - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerup + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when the operating system cancels a pointer event * - * @event pointercancel - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointercancel + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is pressed and released on the display object. * - * @event pointertap - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointertap + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is released outside the display object that initially * registered a [pointerdown]{@link PIXI.interaction.InteractionManager#event:pointerdown}. * - * @event pointerupoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device is moved while over the display object * - * @event pointermove - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointermove + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device is moved onto the display object * - * @event pointerover - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerover + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device is moved off the display object * - * @event pointerout - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerout + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is placed on the display object. * - * @event touchstart - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchstart + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is removed from the display object. * - * @event touchend - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchend + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when the operating system cancels a touch * - * @event touchcancel - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchcancel + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is placed and removed from the display object. * - * @event tap - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#tap + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is removed outside of the display object that initially * registered a [touchstart]{@link PIXI.interaction.InteractionManager#event:touchstart}. * - * @event touchendoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchendoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is moved along the display object. * - * @event touchmove - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchmove + * @param {PIXI.interaction.InteractionData} event - Interaction data */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is pressed on the display. + * object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mousedown + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is pressed + * on the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightdown + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is released over the display + * object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseup + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is released + * over the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightup + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is pressed and released on + * the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#click + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is pressed + * and released on the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightclick + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is released outside the + * display object that initially registered a + * [mousedown]{@link PIXI.DisplayObject#event:mousedown}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is released + * outside the display object that initially registered a + * [rightdown]{@link PIXI.DisplayObject#event:rightdown}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device (usually a mouse) is moved while over the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mousemove + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device (usually a mouse) is moved onto the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseover + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device (usually a mouse) is moved off the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseout + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is pressed on the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerdown + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is released over the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerup + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when the operating system cancels a pointer event. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointercancel + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is pressed and released on the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointertap + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is released outside the display object that initially + * registered a [pointerdown]{@link PIXI.DisplayObject#event:pointerdown}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device is moved while over the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointermove + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device is moved onto the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerover + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device is moved off the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerout + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is placed on the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchstart + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is removed from the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchend + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when the operating system cancels a touch. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchcancel + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is placed and removed from the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#tap + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is removed outside of the display object that initially + * registered a [touchstart]{@link PIXI.DisplayObject#event:touchstart}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchendoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is moved along the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchmove + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + } + + /** + * Hit tests a point against the display tree, returning the first interactive object that is hit. + * + * @param {PIXI.Point} globalPoint - A point to hit test with, in global space. + * @param {PIXI.Container} [root] - The root display object to start from. If omitted, defaults + * to the last rendered root of the associated renderer. + * @return {PIXI.DisplayObject} The hit display object, if any. + */ + hitTest(globalPoint, root) + { + // clear the target for our hit test + hitTestEvent.target = null; + // assign the global point + hitTestEvent.data.global = globalPoint; + // ensure safety of the root + if (!root) + { + root = this.renderer._lastObjectRendered; + } + // run the hit test + this.processInteractive(hitTestEvent, root, null, true); + // return our found object - it'll be null if we didn't hit anything + + return hitTestEvent.target; } /** @@ -487,7 +704,7 @@ return; } - core.ticker.shared.add(this.update, this); + core.ticker.shared.add(this.update, this, core.UPDATE_PRIORITY.INTERACTION); if (window.navigator.msPointerEnabled) { @@ -804,8 +1021,6 @@ } } - let keepHitTestingAfterChildren = hitTest; - // ** FREE TIP **! If an object is not interactive or has no buttons in it // (such as a game scene!) set interactiveChildren to false for that displayObject. // This will allow pixi to completely ignore and bypass checking the displayObjects children. @@ -818,7 +1033,9 @@ const child = children[i]; // time to get recursive.. if this function will return if something is hit.. - if (this.processInteractive(interactionEvent, child, func, hitTest, interactiveParent)) + const childHit = this.processInteractive(interactionEvent, child, func, hitTest, interactiveParent); + + if (childHit) { // its a good idea to check if a child has lost its parent. // this means it has been removed whilst looping so its best @@ -827,8 +1044,6 @@ continue; } - hit = true; - // we no longer need to hit test any more objects in this container as we we // now know the parent has been hit interactiveParent = false; @@ -838,36 +1053,41 @@ // This means we no longer need to hit test anything else. We still need to run // through all objects, but we don't need to perform any hit tests. - keepHitTestingAfterChildren = false; - - if (child.interactive) + if (childHit) { - hitTest = false; + if (interactionEvent.target) + { + hitTest = false; + } + hit = true; } - - // we can break now as we have hit an object. } } } - hitTest = keepHitTestingAfterChildren; - // no point running this if the item is not interactive or does not have an interactive parent. if (interactive) { // if we are hit testing (as in we have no hit any objects yet) // We also don't need to worry about hit testing if once of the displayObjects children - // has already been hit! - if (hitTest && !hit) + // has already been hit - but only if it was interactive, otherwise we need to keep + // looking for an interactive child, just in case we hit one + if (hitTest && !interactionEvent.target) { if (displayObject.hitArea) { displayObject.worldTransform.applyInverse(point, this._tempPoint); - hit = displayObject.hitArea.contains(this._tempPoint.x, this._tempPoint.y); + if (displayObject.hitArea.contains(this._tempPoint.x, this._tempPoint.y)) + { + hit = true; + } } else if (displayObject.containsPoint) { - hit = displayObject.containsPoint(point); + if (displayObject.containsPoint(point)) + { + hit = true; + } } } @@ -878,7 +1098,10 @@ interactionEvent.target = displayObject; } - func(interactionEvent, displayObject, hit); + if (func) + { + func(interactionEvent, displayObject, !!hit); + } } } @@ -914,7 +1137,7 @@ { const event = events[i]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -998,7 +1221,7 @@ { const event = events[i]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1098,7 +1321,7 @@ const test = isRightButton ? flags.RIGHT_DOWN : flags.LEFT_DOWN; - const isDown = trackingData !== undefined && (trackingData.flags | test); + const isDown = trackingData !== undefined && (trackingData.flags & test); if (hit) { @@ -1118,11 +1341,11 @@ { if (isRightButton) { - trackingData.rightDown = hit; + trackingData.rightDown = false; } else { - trackingData.leftDown = hit; + trackingData.leftDown = false; } } } @@ -1180,7 +1403,7 @@ { const event = events[i]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1255,7 +1478,7 @@ this.setCursorMode(null); } - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1268,6 +1491,12 @@ { this.emit('mouseout', interactionEvent); } + else + { + // we can get touchleave events after touchend, so we want to make sure we don't + // introduce memory leaks + this.releaseInteractionDataForPointerId(interactionData.identifier); + } } /** @@ -1344,7 +1573,7 @@ // Only mouse and pointer can call onPointerOver, so events will always be length 1 const event = events[0]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1366,12 +1595,14 @@ * Get InteractionData for a given pointerId. Store that data as well * * @private - * @param {number} pointerId - Identifier from a pointer event + * @param {PointerEvent} event - Normalized pointer event, output from normalizeToPointerData * @return {InteractionData} - Interaction data for the given pointer identifier */ - getInteractionDataForPointerId(pointerId) + getInteractionDataForPointerId(event) { - if (pointerId === MOUSE_POINTER_ID) + const pointerId = event.pointerId; + + if (pointerId === MOUSE_POINTER_ID || event.pointerType === 'mouse') { return this.mouse; } diff --git a/src/interaction/InteractionTrackingData.js b/src/interaction/InteractionTrackingData.js index 6a1f69e..8493b63 100644 --- a/src/interaction/InteractionTrackingData.js +++ b/src/interaction/InteractionTrackingData.js @@ -35,8 +35,10 @@ } /** + * Unique pointer id of the event + * * @readonly - * @type {number} Unique pointer id of the event + * @member {number} */ get pointerId() { diff --git a/src/interaction/index.js b/src/interaction/index.js index 56c5b09..72f7d71 100644 --- a/src/interaction/index.js +++ b/src/interaction/index.js @@ -1,4 +1,8 @@ /** + * This namespace contains a renderer plugin for handling mouse, pointer, and touch events. + * + * Do not instantiate this plugin directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. * @namespace PIXI.interaction */ export { default as InteractionData } from './InteractionData'; diff --git a/src/interaction/interactiveTarget.js b/src/interaction/interactiveTarget.js index d252fba..cebac40 100644 --- a/src/interaction/interactiveTarget.js +++ b/src/interaction/interactiveTarget.js @@ -2,7 +2,7 @@ * Default property values of interactive objects * Used by {@link PIXI.interaction.InteractionManager} to automatically give all DisplayObjects these properties * - * @mixin + * @private * @name interactiveTarget * @memberof PIXI.interaction * @example @@ -14,18 +14,28 @@ * ); */ export default { + /** - * Determines if the displayObject be clicked/touched + * Enable interaction events for the DisplayObject. Touch, pointer and mouse + * events will not be emitted unless `interactive` is set to `true`. * - * @inner {boolean} + * @example + * const sprite = new PIXI.Sprite(texture); + * sprite.interactive = true; + * sprite.on('tap', (event) => { + * //handle event + * }); + * @member {boolean} + * @memberof PIXI.DisplayObject# */ interactive: false, /** * Determines if the children to the displayObject can be clicked/touched - * Setting this to false allows pixi to bypass a recursive hitTest function + * Setting this to false allows pixi to bypass a recursive `hitTest` function * - * @inner {boolean} + * @member {boolean} + * @memberof PIXI.Container# */ interactiveChildren: true, @@ -33,7 +43,12 @@ * Interaction shape. Children will be hit first, then this shape will be checked. * Setting this will cause this shape to be checked in hit tests rather than the displayObject's bounds. * - * @inner {PIXI.Rectangle|PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.RoundedRectangle} + * @example + * const sprite = new PIXI.Sprite(texture); + * sprite.interactive = true; + * sprite.hitArea = new PIXI.Rectangle(0, 0, 100, 100); + * @member {PIXI.Rectangle|PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.RoundedRectangle} + * @memberof PIXI.DisplayObject# */ hitArea: null, @@ -41,8 +56,12 @@ * If enabled, the mouse cursor use the pointer behavior when hovered over the displayObject if it is interactive * Setting this changes the 'cursor' property to `'pointer'`. * + * @example + * const sprite = new PIXI.Sprite(texture); + * sprite.interactive = true; + * sprite.buttonMode = true; * @member {boolean} - * @memberof PIXI.interaction.interactiveTarget# + * @memberof PIXI.DisplayObject# */ get buttonMode() { @@ -64,9 +83,14 @@ * This defines what cursor mode is used when the mouse cursor * is hovered over the displayObject. * + * @example + * const sprite = new PIXI.Sprite(texture); + * sprite.interactive = true; + * sprite.cursor = 'wait'; * @see https://developer.mozilla.org/en/docs/Web/CSS/cursor * - * @inner {string} + * @member {string} + * @memberof PIXI.DisplayObject# */ cursor: null, @@ -74,7 +98,7 @@ * Internal set of all active pointers, by identifier * * @member {Map} - * @memberof PIXI.interaction.interactiveTarget# + * @memberof PIXI.DisplayObject# * @private */ get trackedPointers() @@ -87,7 +111,8 @@ /** * Map of all tracked pointers, by identifier. Use trackedPointers to access. * - * @private {Map} + * @private + * @type {Map} */ _trackedPointers: undefined, }; diff --git a/src/loaders/index.js b/src/loaders/index.js index 5189793..daa1fc4 100644 --- a/src/loaders/index.js +++ b/src/loaders/index.js @@ -1,9 +1,22 @@ +import Application from '../core/Application'; +import Loader from './loader'; + /** + * This namespace contains APIs which extends the {@link https://github.com/englercj/resource-loader resource-loader} module + * for loading assets, data, and other resources dynamically. + * @example + * const loader = new PIXI.loaders.Loader(); + * loader.add('bunny', 'data/bunny.png') + * .add('spaceship', 'assets/spritesheet.json'); + * loader.load((loader, resources) => { + * // resources.bunny + * // resources.spaceship + * }); * @namespace PIXI.loaders */ -export { default as Loader } from './loader'; +export { Loader }; export { default as bitmapFontParser, parse as parseBitmapFontData } from './bitmapFontParser'; -export { default as spritesheetParser } from './spritesheetParser'; +export { default as spritesheetParser, getResourcePath } from './spritesheetParser'; export { default as textureParser } from './textureParser'; /** @@ -13,3 +26,55 @@ * @memberof PIXI.loaders */ export { Resource } from 'resource-loader'; + +/** + * A premade instance of the loader that can be used to load resources. + * @name shared + * @memberof PIXI.loaders + * @type {PIXI.loaders.Loader} + */ +const shared = new Loader(); + +shared.destroy = () => +{ + // protect destroying shared loader +}; + +export { shared }; + +// Mixin the loader construction +const AppPrototype = Application.prototype; + +AppPrototype._loader = null; + +/** + * Loader instance to help with asset loading. + * @name PIXI.Application#loader + * @type {PIXI.loaders.Loader} + */ +Object.defineProperty(AppPrototype, 'loader', { + get() + { + if (!this._loader) + { + const sharedLoader = this._options.sharedLoader; + + this._loader = sharedLoader ? shared : new Loader(); + } + + return this._loader; + }, +}); + +// Override the destroy function +// making sure to destroy the current Loader +AppPrototype._parentDestroy = AppPrototype.destroy; +AppPrototype.destroy = function destroy() +{ + if (this._loader) + { + this._loader.destroy(); + this._loader = null; + } + this._parentDestroy(); +}; diff --git a/src/loaders/loader.js b/src/loaders/loader.js index 767a411..59b8000 100644 --- a/src/loaders/loader.js +++ b/src/loaders/loader.js @@ -90,6 +90,15 @@ { Loader._pixiMiddleware.push(fn); } + + /** + * Destroy the loader, removes references. + */ + destroy() + { + this.removeAllListeners(); + this.reset(); + } } // Copy EE3 prototype (mixin) diff --git a/.travis.yml b/.travis.yml index 3df8b22..14de334 100644 --- a/.travis.yml +++ b/.travis.yml @@ -110,3 +110,43 @@ on: all_branches: true condition: $TRAVIS_TAG + # Deploy config for latest release + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: dist + upload-dir: "release" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: docs + upload-dir: "release/docs" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: coverage + upload-dir: "release/coverage" + on: + all_branches: true + condition: $TRAVIS_TAG \ No newline at end of file diff --git a/README.md b/README.md index d26fadd..3b85f85 100644 --- a/README.md +++ b/README.md @@ -36,22 +36,14 @@ - Wiki: Other misc tutorials and resources are [on the Wiki](https://github.com/pixijs/pixi.js/wiki/Resources). ### Community ### -- Forums: Check out the [forum] (http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow] (http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. +- Forums: Check out the [forum](http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow](http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. - Inspiration: Check out the [gallery](http://www.pixijs.com/gallery) to see some of the amazing things people have created! - Chat: You can join us on [Gitter](https://gitter.im/pixijs/pixi.js) To chat about Pixi. We also now have a Slack channel. If you would like to join it please Send me an email (mat@goodboydigital.com) and I will invite you in. ### Setup ### -It's easy to get started with Pixi.js! Simply grab the pre-built versions from here: - -Release Branch - Nice and stable Pixi.js -- Unminified: [http://pixijs.download/release/pixi.js] -- Minified: [http://pixijs.download/release/pixi.min.js] - -Develop Branch - The bleeding edge version of Pixi.js -- Unminified: [http://pixijs.download/dev/pixi.js] -- Minified: [http://pixijs.download/dev/pixi.min.js] +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. @@ -70,10 +62,10 @@ #### CDN Install (via cdnjs) ```html - + ``` -_Note: `4.2.2` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ +_Note: `4.4.3` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ ### Demos ### @@ -130,7 +122,7 @@ // and the root stage PIXI.Container. var app = new PIXI.Application(); -// The application will create a canvas element for you that you +// The application will create a canvas element for you that you // can then insert into the DOM. document.body.appendChild(app.view); @@ -150,7 +142,7 @@ // Add the bunny to the scene we are building. app.stage.addChild(bunny); - + // Listen for frame updates app.ticker.add(function() { // each frame we spin the bunny around a bit diff --git a/bower.json b/bower.json deleted file mode 100644 index 41b668c..0000000 --- a/bower.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "pixi.js", - "main": "dist/pixi.min.js", - "ignore": [ - "**/.*", - "docs", - "node_modules", - "bower_components", - "test", - "scripts", - "bower.json", - "inch.json" - ], - "dependencies": { - }, - "devDependencies": { - } -} diff --git a/package.json b/package.json index d35cbb2..308469f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.3.5", + "version": "4.4.4", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ @@ -40,11 +40,11 @@ "lib": "babel src --out-dir lib -s", "predocs": "rimraf docs/**", "docs": "jsdoc -c scripts/jsdoc.conf.json -R README.md", - "publish:patch": "npm version patch --no-git-tag-version && npm publish", - "publish:minor": "npm version minor --no-git-tag-version && npm publish", - "publish:major": "npm version major --no-git-tag-version && npm publish", + "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", - "postpublish": "node scripts/release.js" + "postpublish": "git push && git push --tags" }, "files": [ "dist/", @@ -72,7 +72,6 @@ "electron": "^1.4.15", "eslint": "^3.5.0", "floss": "^2.0.1", - "gh-pages": "^0.11.0", "jaguarjs-jsdoc": "^1.0.1", "js-md5": "^0.4.1", "jsdoc": "^3.4.2", diff --git a/scripts/jsdoc.conf.json b/scripts/jsdoc.conf.json index 553e61c..3f52180 100644 --- a/scripts/jsdoc.conf.json +++ b/scripts/jsdoc.conf.json @@ -43,7 +43,7 @@ }, "markdown" : { "parser" : "gfm", - "hardwrap" : true + "hardwrap" : false }, "opts": { "encoding" : "utf8", diff --git a/scripts/release.js b/scripts/release.js deleted file mode 100644 index 156620c..0000000 --- a/scripts/release.js +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env node -'use strict'; - -// Publish script to push releases of the bin files -// the normally are gitignored -const ghpages = require('gh-pages'); -const path = require('path'); -const packageInfo = require(path.join(__dirname, '..', 'package.json')); - -const options = { - src: [ - 'dist/**/*', - 'lib/**/*', - 'src/**/*', - 'scripts/*', - 'scripts/renders/*', - 'test/**/*', - '*.json', - '*.md', - 'LICENSE', - '.eslintrc', - '.editorconfig', - '.travis.yml', - '.babelrc', - ], - dotfiles: true, - branch: 'release', - message: packageInfo.version, - logger: console.log.bind(console), -}; - -ghpages.publish(process.cwd(), options, (err) => -{ - if (err) - { - console.log(err); - process.exit(1); - - return; - } - - process.exit(0); -}); diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index f16bcb1..220427a 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -21,7 +21,7 @@ const DIV_HOOK_ZINDEX = 2; /** - * The Accessibility manager reacreates the ability to tab and and have content read by screen + * The Accessibility manager recreates the ability to tab and and have content read by screen * readers. This is very important as it can possibly help people with disabilities access pixi * content. * diff --git a/src/accessibility/index.js b/src/accessibility/index.js index 568cfb1..0ae108f 100644 --- a/src/accessibility/index.js +++ b/src/accessibility/index.js @@ -1,4 +1,9 @@ /** + * This namespace contains a renderer plugin for interaction accessibility for end-users + * with physical impairments which require screen-renders, keyboard navigation, etc. + * + * Do not instantiate this plugin directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. * @namespace PIXI.accessibility */ export { default as accessibleTarget } from './accessibleTarget'; diff --git a/src/core/Application.js b/src/core/Application.js index 5340578..2fcc729 100644 --- a/src/core/Application.js +++ b/src/core/Application.js @@ -1,6 +1,8 @@ import { autoDetectRenderer } from './autoDetectRenderer'; import Container from './display/Container'; import { shared, Ticker } from './ticker'; +import settings from './settings'; +import { UPDATE_PRIORITY } from './const'; /** * Convenience class to create a new PIXI application. @@ -22,28 +24,52 @@ */ export default class Application { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experience unexplained flickering try setting this to true. - * @param {boolean} [useSharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedLoader=false] - `true` to use PIXI.loaders.shared, `false` to create new Loader. */ - constructor(width, height, options, noWebGL, useSharedTicker = false) + constructor(options, arg2, arg3, arg4, arg5) { + // Support for constructor(width, height, options, noWebGL, useSharedTicker) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + forceCanvas: !!arg4, + sharedTicker: !!arg5, + }, arg3); + } + + /** + * The default options, so we mixin functionality later. + * @member {object} + * @protected + */ + this._options = options = Object.assign({ + sharedTicker: false, + forceCanvas: false, + sharedLoader: false, + }, options); + /** * WebGL renderer if available, otherwise CanvasRenderer * @member {PIXI.WebGLRenderer|PIXI.CanvasRenderer} */ - this.renderer = autoDetectRenderer(width, height, options, noWebGL); + this.renderer = autoDetectRenderer(options); /** * The root display container that's rendered. @@ -63,7 +89,7 @@ * @member {PIXI.ticker.Ticker} * @default PIXI.ticker.shared */ - this.ticker = useSharedTicker ? shared : new Ticker(); + this.ticker = options.sharedTicker ? shared : new Ticker(); // Start the rendering this.start(); @@ -78,7 +104,7 @@ this._ticker = ticker; if (ticker) { - ticker.add(this.render, this); + ticker.add(this.render, this, UPDATE_PRIORITY.LOW); } } get ticker() // eslint-disable-line require-jsdoc @@ -136,13 +162,18 @@ */ destroy(removeView) { - this.stop(); + const oldTicker = this._ticker; + this.ticker = null; + oldTicker.destroy(); + this.stage.destroy(); this.stage = null; this.renderer.destroy(removeView); this.renderer = null; + + this._options = null; } } diff --git a/src/core/autoDetectRenderer.js b/src/core/autoDetectRenderer.js index 8960425..9ec0364 100644 --- a/src/core/autoDetectRenderer.js +++ b/src/core/autoDetectRenderer.js @@ -2,6 +2,7 @@ import CanvasRenderer from './renderers/canvas/CanvasRenderer'; import WebGLRenderer from './renderers/webgl/WebGLRenderer'; +// eslint-disable-next-line valid-jsdoc /** * This helper function will automatically detect which renderer you should be using. * WebGL is the preferred renderer as it is a lot faster. If webGL is not supported by @@ -9,24 +10,32 @@ * * @memberof PIXI * @function autoDetectRenderer - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @return {PIXI.WebGLRenderer|PIXI.CanvasRenderer} Returns WebGL renderer if available, otherwise CanvasRenderer */ -export function autoDetectRenderer(width = 800, height = 600, options, noWebGL) +export function autoDetectRenderer(options, arg1, arg2, arg3) { - if (!noWebGL && utils.isWebGLSupported()) + // Backward-compatible support for noWebGL option + let forceCanvas = options && options.forceCanvas; + + if (arg3 !== undefined) { - return new WebGLRenderer(width, height, options); + forceCanvas = arg3; } - return new CanvasRenderer(width, height, options); + if (!forceCanvas && utils.isWebGLSupported()) + { + return new WebGLRenderer(options, arg1, arg2); + } + + return new CanvasRenderer(options, arg1, arg2); } diff --git a/src/core/const.js b/src/core/const.js index b9e99ae..2c68dee 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -341,3 +341,27 @@ LINEAR_VERTICAL: 0, LINEAR_HORIZONTAL: 1, }; + +/** + * Represents the update priorities used by internal PIXI classes when registered with + * the {@link PIXI.ticker.Ticker} object. Higher priority items are updated first and lower + * priority items, such as render, should go later. + * + * @static + * @constant + * @name UPDATE_PRIORITY + * @memberof PIXI + * @type {object} + * @property {number} INTERACTION=50 Highest priority, used for {@link PIXI.interaction.InteractionManager} + * @property {number} HIGH=25 High priority updating, {@link PIXI.VideoBaseTexture} and {@link PIXI.extras.AnimatedSprite} + * @property {number} NORMAL=0 Default priority for ticker events, see {@link PIXI.ticker.Ticker#add}. + * @property {number} LOW=-25 Low priority used for {@link PIXI.Application} rendering. + * @property {number} UTILITY=-50 Lowest priority used for {@link PIXI.prepare.BasePrepare} utility. + */ +export const UPDATE_PRIORITY = { + INTERACTION: 50, + HIGH: 25, + NORMAL: 0, + LOW: -25, + UTILITY: -50, +}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 4e6c77d..decb42a 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -13,7 +13,6 @@ * * @class * @extends EventEmitter - * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ export default class DisplayObject extends EventEmitter @@ -113,6 +112,29 @@ * @private */ this._mask = null; + + /** + * If the object has been destroyed via destroy(). If true, it should not be used. + * + * @member {boolean} + * @private + * @readonly + */ + this._destroyed = false; + + /** + * Fired when this DisplayObject is added to a Container. + * + * @event PIXI.DisplayObject#added + * @param {PIXI.Container} container - The container added to. + */ + + /** + * Fired when this DisplayObject is removed from a Container. + * + * @event PIXI.DisplayObject#removed + * @param {PIXI.Container} container - The container removed from. + */ } /** @@ -403,6 +425,8 @@ this.interactive = false; this.interactiveChildren = false; + + this._destroyed = true; } /** diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index aafdfe2..f787c4a 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -792,8 +792,8 @@ sprite.worldAlpha = this.worldAlpha * sprite.alpha; sprite.blendMode = this.blendMode; - sprite.texture._frame.width = rect.width; - sprite.texture._frame.height = rect.height; + sprite._texture._frame.width = rect.width; + sprite._texture._frame.height = rect.height; sprite.transform.worldTransform = this.transform.worldTransform; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index a24c775..b9f192d 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -19,11 +19,12 @@ */ export default class SystemRenderer extends EventEmitter { + // eslint-disable-next-line valid-jsdoc /** * @param {string} system - The name of the system this renderer is for. - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -37,27 +38,31 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(system, screenWidth, screenHeight, options) + constructor(system, options, arg2, arg3) { super(); sayHello(system); - // prepare options - if (options) + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') { - for (const i in settings.RENDER_OPTIONS) - { - if (typeof options[i] === 'undefined') - { - options[i] = settings.RENDER_OPTIONS[i]; - } - } + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); } - else - { - options = settings.RENDER_OPTIONS; - } + + // Add the default render options + options = Object.assign({}, settings.RENDER_OPTIONS, options); + + /** + * The supplied constructor options. + * + * @member {Object} + * @readOnly + */ + this.options = options; /** * The type of the renderer. @@ -75,7 +80,7 @@ * * @member {PIXI.Rectangle} */ - this.screen = new Rectangle(0, 0, screenWidth || 800, screenHeight || 600); + this.screen = new Rectangle(0, 0, options.width, options.height); /** * The canvas element that everything is drawn to @@ -279,6 +284,8 @@ this.blendModes = null; + this.options = null; + this.preserveDrawingBuffer = false; this.clearBeforeRender = false; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index e84e49c..691caf0 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -17,10 +17,11 @@ */ export default class CanvasRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -34,9 +35,9 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('Canvas', screenWidth, screenHeight, options); + super('Canvas', options, arg2, arg3); this.type = RENDERER_TYPE.CANVAS; @@ -96,7 +97,19 @@ this.context = null; this.renderingToScreen = false; - this.resize(screenWidth, screenHeight); + this.resize(this.options.width, this.options.height); + + /** + * Fired after rendering finishes. + * + * @event PIXI.CanvasRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.CanvasRenderer#prerender + */ } /** @@ -299,4 +312,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.CanvasRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.CanvasExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.CanvasPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.CanvasRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index e1cf7cd..f6fe123 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -31,11 +31,12 @@ */ export default class WebGLRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** * - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -55,9 +56,9 @@ * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('WebGL', screenWidth, screenHeight, options); + super('WebGL', options, arg2, arg3); /** * The type of this renderer as a standardised const @@ -89,12 +90,6 @@ }; - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ this._backgroundColorRgba[3] = this.transparent ? 0 : 1; this.globalUniforms = new UniformGroup({ @@ -117,6 +112,13 @@ this.initPlugins(); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ if(options.context) { this.context.initFromContext(options.context); @@ -169,6 +171,25 @@ } return this; + + /** + * Fired after rendering finishes. + * + * @event PIXI.WebGLRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.WebGLRenderer#prerender + */ + + /** + * Fired when the WebGL context is set. + * + * @event PIXI.WebGLRenderer#context + * @param {WebGLRenderingContext} gl - WebGL context. + */ } /** @@ -295,4 +316,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.WebGLRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.WebGLRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(WebGLRenderer); diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index 4eb6128..fa3d883 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -47,6 +47,14 @@ * @member {boolean} */ this.enabled = true; + + /** + * If enabled, pixi will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; } /** diff --git a/src/core/renderers/webgl/systems/ContextSystem.js b/src/core/renderers/webgl/systems/ContextSystem.js index ff958c7..8df85ea 100644 --- a/src/core/renderers/webgl/systems/ContextSystem.js +++ b/src/core/renderers/webgl/systems/ContextSystem.js @@ -75,7 +75,8 @@ */ createContext(canvas, options) { - var gl = canvas.getContext('webgl', options) || + var gl = //canvas.getContext('webgl2', options) || + canvas.getContext('webgl', options) || canvas.getContext('experimental-webgl', options); if (!gl) diff --git a/src/core/renderers/webgl/systems/FilterSystem.js b/src/core/renderers/webgl/systems/FilterSystem.js index f47335d..37e28d2 100644 --- a/src/core/renderers/webgl/systems/FilterSystem.js +++ b/src/core/renderers/webgl/systems/FilterSystem.js @@ -111,7 +111,7 @@ // TODO we should fit the rect around the transform.. } - else + else if (filters[0].autoFit) { sourceFrame.fit(filterData.stack[0].destinationFrame); } diff --git a/src/core/renderers/webgl/systems/shader/shader/mapType.js b/src/core/renderers/webgl/systems/shader/shader/mapType.js index 581dace..917538a 100644 --- a/src/core/renderers/webgl/systems/shader/shader/mapType.js +++ b/src/core/renderers/webgl/systems/shader/shader/mapType.js @@ -15,7 +15,7 @@ } } - return GL_TABLE[type]; + return GL_TABLE[type]; }; var GL_TABLE = null; diff --git a/src/core/settings.js b/src/core/settings.js index 18c5877..38ef45c 100644 --- a/src/core/settings.js +++ b/src/core/settings.js @@ -2,6 +2,15 @@ import canUploadSameBuffer from './utils/canUploadSameBuffer'; /** + * User's customizable globals for overriding the default PIXI settings, such + * as a renderer's default resolution, framerate, float percision, etc. + * @example + * // Use the native window resolution as the default resolution + * // will support high-density displays when rendering + * PIXI.settings.RESOLUTION = window.devicePixelRatio. + * + * // Disable interpolation when scaling, will make texture be pixelated + * PIXI.settings.SCALE_MODE = PIXI.SCALE_MODES.NEAREST; * @namespace PIXI.settings */ export default { @@ -101,6 +110,9 @@ * @property {boolean} clearBeforeRender=true * @property {boolean} preserveDrawingBuffer=false * @property {boolean} roundPixels=false + * @property {number} width=800 + * @property {number} height=600 + * @property {boolean} legacy=false */ RENDER_OPTIONS: { view: null, @@ -112,6 +124,9 @@ clearBeforeRender: true, preserveDrawingBuffer: false, roundPixels: false, + width: 800, + height: 600, + legacy: false, }, /** diff --git a/src/core/shader/Program.js b/src/core/shader/Program.js index c0b538b..07b4398 100644 --- a/src/core/shader/Program.js +++ b/src/core/shader/Program.js @@ -1,6 +1,6 @@ import extractUniformsFromSrc from './extractUniformsFromSrc'; import generateUniformsSync from './generateUniformsSync'; -import glCore from 'pixi-gl-core'; +import shaderUtils from '../renderers/webgl/systems/shader/shader'; import { ProgramCache } from '../utils'; import getTestContext from '../utils/getTestContext'; @@ -64,14 +64,17 @@ } else { - vertexSrc = glCore.shader.setPrecision(vertexSrc, 'mediump'); - fragmentSrc = glCore.shader.setPrecision(fragmentSrc, 'mediump'); + vertexSrc = shaderUtils.setPrecision(vertexSrc, 'mediump'); + fragmentSrc = shaderUtils.setPrecision(fragmentSrc, 'mediump'); - const program = glCore.shader.compileProgram(gl, vertexSrc, fragmentSrc); + const program = shaderUtils.compileProgram(gl, vertexSrc, fragmentSrc); + console.log("<<<<<<>>><<"); this.attributeData = this.getAttributeData(program, gl); this.uniformData = this.getUniformData(program, gl); + console.log(this.uniformData); + //gl.deleteProgram(program); } } @@ -95,13 +98,13 @@ for (let i = 0; i < totalAttributes; i++) { const attribData = gl.getActiveAttrib(program, i); - const type = glCore.shader.mapType(gl, attribData.type); + const type = shaderUtils.mapType(gl, attribData.type); /*eslint-disable */ const data = { type: type, name: attribData.name, - size: glCore.shader.mapSize(type), + size: shaderUtils.mapSize(type), location: 0, }; /*eslint-enable */ @@ -137,13 +140,14 @@ // TODO expose this as a prop? // const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); - const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); + //const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); for (let i = 0; i < totalUniforms; i++) { const uniformData = gl.getActiveUniform(program, i); const name = uniformData.name.replace(/\[.*?\]/, ''); - const type = glCore.shader.mapType(gl, uniformData.type); + const type = shaderUtils.mapType(gl, uniformData.type); + console.log('mapping ' + uniformData.type + ' to ' + type); // if (!name.match(maskRegex)) { @@ -151,7 +155,7 @@ uniforms[name] = { type: type, size: uniformData.size, - value: glCore.shader.defaultValue(type, uniformData.size), + value: shaderUtils.defaultValue(type, uniformData.size), }; /*eslint-enable */ } diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index e387e99..b57d095 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -147,12 +147,12 @@ // so if _width is 0 then width was not set.. if (this._width) { - this.scale.x = sign(this.scale.x) * this._width / this.texture.orig.width; + this.scale.x = sign(this.scale.x) * this._width / this._texture.orig.width; } if (this._height) { - this.scale.y = sign(this.scale.y) * this._height / this.texture.orig.height; + this.scale.y = sign(this.scale.y) * this._height / this._texture.orig.height; } } diff --git a/src/core/sprites/canvas/CanvasTinter.js b/src/core/sprites/canvas/CanvasTinter.js index 27e5e0c..fd9de5d 100644 --- a/src/core/sprites/canvas/CanvasTinter.js +++ b/src/core/sprites/canvas/CanvasTinter.js @@ -18,7 +18,7 @@ */ getTintedTexture: (sprite, color) => { - const texture = sprite.texture; + const texture = sprite._texture; color = CanvasTinter.roundColor(color); diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 5a4edda..8b9b797 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -344,6 +344,7 @@ const texture = this._texture; const style = this._style; + const padding = style.trim ? 0 : style.padding; texture.baseTexture.valid = true; texture.baseTexture.resolution = this.resolution; @@ -353,11 +354,11 @@ texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - texture.trim.x = -style.padding; - texture.trim.y = -style.padding; + texture.trim.x = -padding; + texture.trim.y = -padding; - texture.orig.width = texture._frame.width - (style.padding * 2); - texture.orig.height = texture._frame.height - (style.padding * 2); + texture.orig.width = texture._frame.width - (padding * 2); + texture.orig.height = texture._frame.height - (padding * 2); // call sprite onTextureUpdate to update scale if _width or _height were set this._onTextureUpdate(); diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index 853da3d..8a9c34b 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -114,6 +114,38 @@ this.cacheId = null; this.validate(); + + /** + * Fired when a not-immediately-available source finishes loading. + * + * @protected + * @event PIXI.BaseTexture#loaded + * @param {PIXI.BaseTexture} baseTexture - Resource loaded. + */ + + /** + * Fired when a not-immediately-available source fails to load. + * + * @protected + * @event PIXI.BaseTexture#error + * @param {PIXI.BaseTexture} baseTexture - Resource errored. + */ + + /** + * Fired when BaseTexture is updated. + * + * @protected + * @event PIXI.BaseTexture#update + * @param {PIXI.BaseTexture} baseTexture - Instance of texture being updated. + */ + + /** + * Fired when BaseTexture is destroyed. + * + * @protected + * @event PIXI.BaseTexture#dispose + * @param {PIXI.BaseTexture} baseTexture - Instance of texture being destroyed. + */ } setResource(resource) diff --git a/src/core/textures/Spritesheet.js b/src/core/textures/Spritesheet.js index d469c62..9b24f27 100644 --- a/src/core/textures/Spritesheet.js +++ b/src/core/textures/Spritesheet.js @@ -208,6 +208,7 @@ // lets also add the frame to pixi's global cache for fromFrame and fromImage functions TextureCache[i] = this.textures[i]; + this.textures[i].textureCacheId = i; } frameIndex++; diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 0a2c379..2213087 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -147,9 +147,9 @@ /** * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. * - * @event update - * @memberof PIXI.Texture# + * @event PIXI.Texture#update * @protected + * @param {PIXI.Texture} texture - Instance of texture being updated. */ this._updateID = 0; @@ -159,6 +159,15 @@ * @type {Object} */ this.transform = null; + + /** + * The id under which this Texture has been added to the texture cache. This is + * automatically set in certain cases, but may not always be accurate, particularly if + * the texture is in the cache under multiple ids. + * + * @member {string} + */ + this.textureCacheId = null; } /** @@ -225,11 +234,17 @@ this._uvs = null; this.trim = null; this.orig = null; + this.textureCacheId = null; this.valid = false; - this.off('dispose', this.dispose, this); - this.off('update', this.update, this); + for (const prop in TextureCache) + { + if (TextureCache[prop] === this) + { + delete TextureCache[prop]; + } + } } /** @@ -315,14 +330,15 @@ */ static fromLoader(source, imageUrl, name) { - // console.log('added from loader...') + // console.log('added from loader...') const resource = new ImageResource(source);//.from(imageUrl, crossorigin);// document.createElement('img'); - // console.log('base resource ' + resource.width); + // console.log('base resource ' + resource.width); const baseTexture = new BaseTexture(resource, settings.SCALE_MODE, getResolutionOfUrl(imageUrl)); + /// console.log('base width ' + baseTexture.width); const texture = new Texture(baseTexture); @@ -335,6 +351,7 @@ // lets also add the frame to pixi's global cache for fromFrame and fromImage fucntions BaseTextureCache[name] = baseTexture; TextureCache[name] = texture; + texture.textureCacheId = name; // also add references by url if they are different. if (name !== imageUrl) @@ -355,6 +372,10 @@ */ static addTextureToCache(texture, id) { + if (!texture.textureCacheId) + { + texture.textureCacheId = id; + } TextureCache[id] = texture; } @@ -495,6 +516,7 @@ */ Texture.EMPTY = new Texture(new BaseTexture()); removeAllHandlers(Texture.EMPTY); +removeAllHandlers(Texture.EMPTY.baseTexture); /** * A white texture of 10x10 size, used for graphics and other things @@ -505,3 +527,4 @@ */ Texture.WHITE = createWhiteTexture(); removeAllHandlers(Texture.WHITE); +removeAllHandlers(Texture.WHITE.baseTexture); diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index 59eb9fd..a9b9f51 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -1,6 +1,7 @@ import BaseTexture from './BaseTexture'; import { uid, BaseTextureCache } from '../utils'; -import * as ticker from '../ticker'; +import { shared } from '../ticker'; +import { UPDATE_PRIORITY } from '../const'; /** * A texture of a [playing] Video. @@ -125,7 +126,7 @@ if (!this._isAutoUpdating && this.autoUpdate) { - ticker.shared.add(this.update, this); + shared.add(this.update, this, UPDATE_PRIORITY.HIGH); this._isAutoUpdating = true; } } @@ -139,7 +140,7 @@ { if (this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); this._isAutoUpdating = false; } } @@ -187,7 +188,7 @@ { if (this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); } if (this.source && this.source._pixiId) @@ -281,12 +282,12 @@ if (!this._autoUpdate && this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); this._isAutoUpdating = false; } else if (this._autoUpdate && !this._isAutoUpdating) { - ticker.shared.add(this.update, this); + shared.add(this.update, this, UPDATE_PRIORITY.HIGH); this._isAutoUpdating = true; } } diff --git a/src/core/ticker/Ticker.js b/src/core/ticker/Ticker.js index 17a1517..40df138 100644 --- a/src/core/ticker/Ticker.js +++ b/src/core/ticker/Ticker.js @@ -1,12 +1,10 @@ import settings from '../settings'; -import EventEmitter from 'eventemitter3'; - -// Internal event used by composed emitter -const TICK = 'tick'; +import { UPDATE_PRIORITY } from '../const'; +import TickerListener from './TickerListener'; /** * A Ticker class that runs an update loop that other objects listen to. - * This class is composed around an EventEmitter object to add listeners + * This class is composed around listeners * meant for execution on the next requested animation frame. * Animation frames are requested only when necessary, * e.g. When the ticker is started and the emitter has listeners. @@ -22,10 +20,11 @@ constructor() { /** - * Internal emitter used to fire 'tick' event + * The first listener. All new listeners added are chained on this. * @private + * @type {TickerListener} */ - this._emitter = new EventEmitter(); + this._head = new TickerListener(null, null, Infinity); /** * Internal current frame request ID @@ -131,7 +130,7 @@ // Invoke listeners now this.update(time); // Listener side effects may have modified ticker state. - if (this.started && this._requestId === null && this._emitter.listeners(TICK, true)) + if (this.started && this._requestId === null && this._head.next) { this._requestId = requestAnimationFrame(this._tick); } @@ -148,7 +147,7 @@ */ _requestIfNeeded() { - if (this._requestId === null && this._emitter.listeners(TICK, true)) + if (this._requestId === null && this._head.next) { // ensure callbacks get correct delta this.lastTime = performance.now(); @@ -193,35 +192,72 @@ } /** - * Calls {@link module:eventemitter3.EventEmitter#on} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. + * Register a handler for tick events. Calls continuously unless + * it is removed or the ticker is stopped. * * @param {Function} fn - The listener function to be added for updates * @param {Function} [context] - The listener context + * @param {number} [priority=PIXI.UPDATE_PRIORITY.NORMAL] - The priority for emitting * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - add(fn, context) + add(fn, context, priority = UPDATE_PRIORITY.NORMAL) { - this._emitter.on(TICK, fn, context); - - this._startIfPossible(); - - return this; + return this._addListener(new TickerListener(fn, context, priority)); } /** - * Calls {@link module:eventemitter3.EventEmitter#once} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. + * Add a handler for the tick event which is only execute once. * * @param {Function} fn - The listener function to be added for one update * @param {Function} [context] - The listener context + * @param {number} [priority=PIXI.UPDATE_PRIORITY.NORMAL] - The priority for emitting * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - addOnce(fn, context) + addOnce(fn, context, priority = UPDATE_PRIORITY.NORMAL) { - this._emitter.once(TICK, fn, context); + return this._addListener(new TickerListener(fn, context, priority, true)); + } + + /** + * Internally adds the event handler so that it can be sorted by priority. + * Priority allows certain handler (user, AnimatedSprite, Interaction) to be run + * before the rendering. + * + * @private + * @param {TickerListener} listener - Current listener being added. + * @returns {PIXI.ticker.Ticker} This instance of a ticker + */ + _addListener(listener) + { + // For attaching to head + let current = this._head.next; + let previous = this._head; + + // Add the first item + if (!current) + { + listener.connect(previous); + } + else + { + // Go from highest to lowest priority + while (current) + { + if (listener.priority >= current.priority) + { + listener.connect(previous); + break; + } + previous = current; + current = current.next; + } + + // Not yet connected + if (!listener.previous) + { + listener.connect(previous); + } + } this._startIfPossible(); @@ -229,19 +265,33 @@ } /** - * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. - * It checks if the emitter has listeners for 'tick' event. - * If it does, then it cancels the animation frame. + * Removes any handlers matching the function and context parameters. + * If no handlers are left after removing, then it cancels the animation frame. * - * @param {Function} [fn] - The listener function to be removed + * @param {Function} fn - The listener function to be removed * @param {Function} [context] - The listener context to be removed * @returns {PIXI.ticker.Ticker} This instance of a ticker */ remove(fn, context) { - this._emitter.off(TICK, fn, context); + let listener = this._head.next; - if (!this._emitter.listeners(TICK, true)) + while (listener) + { + // We found a match, lets remove it + // no break to delete all possible matches + // incase a listener was added 2+ times + if (listener.match(fn, context)) + { + listener = listener.destroy(); + } + else + { + listener = listener.next; + } + } + + if (!this._head.next) { this._cancelIfNeeded(); } @@ -276,6 +326,25 @@ } /** + * Destroy the ticker and don't use after this. Calling + * this method removes all references to internal events. + */ + destroy() + { + this.stop(); + + let listener = this._head.next; + + while (listener) + { + listener = listener.destroy(true); + } + + this._head.destroy(); + this._head = null; + } + + /** * Triggers an update. An update entails setting the * current {@link PIXI.ticker.Ticker#elapsedMS}, * the current {@link PIXI.ticker.Ticker#deltaTime}, @@ -320,8 +389,22 @@ this.deltaTime = elapsedMS * settings.TARGET_FPMS * this.speed; + // Cache a local reference, in-case ticker is destroyed + // during the emit, we can still check for head.next + const head = this._head; + // Invoke listeners added to internal emitter - this._emitter.emit(TICK, this.deltaTime); + let listener = head.next; + + while (listener) + { + listener = listener.emit(this.deltaTime); + } + + if (!head.next) + { + this._cancelIfNeeded(); + } } else { diff --git a/src/core/ticker/TickerListener.js b/src/core/ticker/TickerListener.js new file mode 100644 index 0000000..2bedb34 --- /dev/null +++ b/src/core/ticker/TickerListener.js @@ -0,0 +1,158 @@ +/** + * Internal class for handling the priority sorting of ticker handlers. + * + * @private + * @class + * @memberof PIXI.ticker + */ +export default class TickerListener +{ + /** + * Constructor + * + * @param {Function} fn - The listener function to be added for one update + * @param {Function} [context=null] - The listener context + * @param {number} [priority=0] - The priority for emitting + * @param {boolean} [once=false] - If the handler should fire once + */ + constructor(fn, context = null, priority = 0, once = false) + { + /** + * The handler function to execute. + * @member {Function} + */ + this.fn = fn; + + /** + * The calling to execute. + * @member {Function} + */ + this.context = context; + + /** + * The current priority. + * @member {number} + */ + this.priority = priority; + + /** + * If this should only execute once. + * @member {boolean} + */ + this.once = once; + + /** + * The next item in chain. + * @member {TickerListener} + */ + this.next = null; + + /** + * The previous item in chain. + * @member {TickerListener} + */ + this.previous = null; + + /** + * `true` if this listener has been destroyed already. + * @member {boolean} + * @private + */ + this._destroyed = false; + } + + /** + * Simple compare function to figure out if a function and context match. + * + * @param {Function} fn - The listener function to be added for one update + * @param {Function} context - The listener context + * @return {boolean} `true` if the listener match the arguments + */ + match(fn, context) + { + context = context || null; + + return this.fn === fn && this.context === context; + } + + /** + * Emit by calling the current function. + * @param {number} deltaTime - time since the last emit. + * @return {TickerListener} Next ticker + */ + emit(deltaTime) + { + if (this.context) + { + this.fn.call(this.context, deltaTime); + } + else + { + this.fn(deltaTime); + } + + if (this.once) + { + this.destroy(); + } + + const redirect = this.next; + + // Soft-destroying should remove + // the next reference + if (this._destroyed) + { + this.next = null; + } + + return redirect; + } + + /** + * Connect to the list. + * @param {TickerListener} previous - Input node, previous listener + */ + connect(previous) + { + this.previous = previous; + if (previous.next) + { + previous.next.previous = this; + } + this.next = previous.next; + previous.next = this; + } + + /** + * Destroy and don't use after this. + * @param {boolean} [hard = false] `true` to remove the `next` reference, this + * is considered a hard destroy. Soft destroy maintains the next reference. + * @return {TickerListener} The listener to redirect while emitting or removing. + */ + destroy(hard = false) + { + this._destroyed = true; + this.fn = null; + this.context = null; + + // Disconnect, hook up next and previous + if (this.previous) + { + this.previous.next = this.next; + } + + if (this.next) + { + this.next.previous = this.previous; + } + + // Redirect to the next item + const redirect = this.previous; + + // Remove references + this.next = hard ? null : redirect; + this.previous = null; + + return redirect; + } +} diff --git a/src/core/ticker/index.js b/src/core/ticker/index.js index 4b3017c..c3020d4 100644 --- a/src/core/ticker/index.js +++ b/src/core/ticker/index.js @@ -45,8 +45,25 @@ const shared = new Ticker(); shared.autoStart = true; +shared.destroy = () => +{ + // protect destroying shared ticker + // this is used by other internal systems + // like AnimatedSprite and InteractionManager +}; /** + * This namespace contains an API for interacting with PIXI's internal global update loop. + * + * This ticker is used for rendering, {@link PIXI.extras.AnimatedSprite AnimatedSprite}, + * {@link PIXI.interaction.InteractionManager InteractionManager} and many other time-based PIXI systems. + * @example + * const ticker = new PIXI.ticker.Ticker(); + * ticker.stop(); + * ticker.add((deltaTime) => { + * // do something every frame + * }); + * ticker.start(); * @namespace PIXI.ticker */ export { shared, Ticker }; diff --git a/src/core/utils/index.js b/src/core/utils/index.js index be44750..7441f7b 100644 --- a/src/core/utils/index.js +++ b/src/core/utils/index.js @@ -9,6 +9,21 @@ let saidHello = false; /** + * Generalized convenience utilities for PIXI. + * @example + * // Extend PIXI's internal Event Emitter. + * class MyEmitter extends PIXI.utils.EventEmitter { + * constructor() { + * super(); + * console.log("Emitter created!"); + * } + * } + * + * // Get info on current device + * console.log(PIXI.utils.isMobile); + * + * // Convert hex color to string + * console.log(PIXI.utils.hex2string(0xff00ff)); // returns: "#ff00ff" * @namespace PIXI.utils */ export { diff --git a/src/deprecation.js b/src/deprecation.js index b9c73b3..33daeec 100644 --- a/src/deprecation.js +++ b/src/deprecation.js @@ -918,6 +918,35 @@ }); /** + * @method + * @name PIXI.prepare.BasePrepare#register + * @see PIXI.prepare.BasePrepare#registerFindHook + * @deprecated since version 4.4.2 + * @param {Function} [addHook] - Function call that takes two parameters: `item:*, queue:Array` + * function must return `true` if it was able to add item to the queue. + * @param {Function} [uploadHook] - Function call that takes two parameters: `prepare:CanvasPrepare, item:*` and + * function must return `true` if it was able to handle upload of item. + * @return {PIXI.BasePrepare} Instance of plugin for chaining. + */ +prepare.BasePrepare.prototype.register = function register(addHook, uploadHook) +{ + warn('renderer.plugins.prepare.register is now deprecated, ' + + 'please use renderer.plugins.prepare.registerFindHook & renderer.plugins.prepare.registerUploadHook'); + + if (addHook) + { + this.registerFindHook(addHook); + } + + if (uploadHook) + { + this.registerUploadHook(uploadHook); + } + + return this; +}; + +/** * The number of graphics or textures to upload to the GPU. * * @name PIXI.prepare.canvas.UPLOADS_PER_FRAME @@ -969,67 +998,73 @@ }, }); -Object.defineProperties(loaders.Resource.prototype, { - isJson: { - get() - { - warn('The isJson property is deprecated, please use `resource.type === Resource.TYPE.JSON`.'); +if (loaders.Loader) +{ + const Resource = loaders.Resource; + const Loader = loaders.Loader; - return this.type === loaders.Loader.Resource.TYPE.JSON; + Object.defineProperties(Resource.prototype, { + isJson: { + get() + { + warn('The isJson property is deprecated, please use `resource.type === Resource.TYPE.JSON`.'); + + return this.type === Resource.TYPE.JSON; + }, }, - }, - isXml: { - get() - { - warn('The isXml property is deprecated, please use `resource.type === Resource.TYPE.XML`.'); + isXml: { + get() + { + warn('The isXml property is deprecated, please use `resource.type === Resource.TYPE.XML`.'); - return this.type === loaders.Loader.Resource.TYPE.XML; + return this.type === Resource.TYPE.XML; + }, }, - }, - isImage: { - get() - { - warn('The isImage property is deprecated, please use `resource.type === Resource.TYPE.IMAGE`.'); + isImage: { + get() + { + warn('The isImage property is deprecated, please use `resource.type === Resource.TYPE.IMAGE`.'); - return this.type === loaders.Loader.Resource.TYPE.IMAGE; + return this.type === Resource.TYPE.IMAGE; + }, }, - }, - isAudio: { - get() - { - warn('The isAudio property is deprecated, please use `resource.type === Resource.TYPE.AUDIO`.'); + isAudio: { + get() + { + warn('The isAudio property is deprecated, please use `resource.type === Resource.TYPE.AUDIO`.'); - return this.type === loaders.Loader.Resource.TYPE.AUDIO; + return this.type === Resource.TYPE.AUDIO; + }, }, - }, - isVideo: { - get() - { - warn('The isVideo property is deprecated, please use `resource.type === Resource.TYPE.VIDEO`.'); + isVideo: { + get() + { + warn('The isVideo property is deprecated, please use `resource.type === Resource.TYPE.VIDEO`.'); - return this.type === loaders.Loader.Resource.TYPE.VIDEO; + return this.type === Resource.TYPE.VIDEO; + }, }, - }, -}); + }); -Object.defineProperties(loaders.Loader.prototype, { - before: { - get() - { - warn('The before() method is deprecated, please use pre().'); + Object.defineProperties(Loader.prototype, { + before: { + get() + { + warn('The before() method is deprecated, please use pre().'); - return this.pre; + return this.pre; + }, }, - }, - after: { - get() - { - warn('The after() method is deprecated, please use use().'); + after: { + get() + { + warn('The after() method is deprecated, please use use().'); - return this.use; + return this.use; + }, }, - }, -}); + }); +} /** * @name PIXI.interaction.interactiveTarget#defaultCursor diff --git a/src/extract/canvas/CanvasExtract.js b/src/extract/canvas/CanvasExtract.js index 941ca05..3ac5aee 100644 --- a/src/extract/canvas/CanvasExtract.js +++ b/src/extract/canvas/CanvasExtract.js @@ -8,7 +8,7 @@ * An instance of this class is automatically created by default, and can be found at renderer.plugins.extract * * @class - * @memberof PIXI + * @memberof PIXI.extract */ export default class CanvasExtract { @@ -21,9 +21,9 @@ /** * Collection of methods for extracting data (image, pixels, etc.) from a display object or render texture * - * @member {PIXI.CanvasExtract} extract + * @member {PIXI.extract.CanvasExtract} extract * @memberof PIXI.CanvasRenderer# - * @see PIXI.CanvasExtract + * @see PIXI.extract.CanvasExtract */ renderer.extract = this; } diff --git a/src/extract/index.js b/src/extract/index.js index 0f2170d..e9478c1 100644 --- a/src/extract/index.js +++ b/src/extract/index.js @@ -1,2 +1,22 @@ +/** + * This namespace provides renderer-specific plugins for exporting content from a renderer. + * For instance, these plugins can be used for saving an Image, Canvas element or for exporting the raw image data (pixels). + * + * Do not instantiate these plugins directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. + * @example + * // Create a new app (will auto-add extract plugin to renderer) + * const app = new PIXI.Application(); + * + * // Draw a red circle + * const graphics = new PIXI.Graphics() + * .beginFill(0xFF0000) + * .drawCircle(0, 0, 50); + * + * // Render the graphics as an HTMLImageElement + * const image = app.renderer.plugins.extract.image(graphics); + * document.body.appendChild(image); + * @namespace PIXI.extract + */ export { default as webgl } from './webgl/WebGLExtract'; export { default as canvas } from './canvas/CanvasExtract'; diff --git a/src/extract/webgl/WebGLExtract.js b/src/extract/webgl/WebGLExtract.js index 60228bb..1c3dd30 100644 --- a/src/extract/webgl/WebGLExtract.js +++ b/src/extract/webgl/WebGLExtract.js @@ -9,7 +9,7 @@ * An instance of this class is automatically created by default, and can be found at renderer.plugins.extract * * @class - * @memberof PIXI + * @memberof PIXI.extract */ export default class WebGLExtract { @@ -22,9 +22,9 @@ /** * Collection of methods for extracting data (image, pixels, etc.) from a display object or render texture * - * @member {PIXI.WebGLExtract} extract + * @member {PIXI.extract.WebGLExtract} extract * @memberof PIXI.WebGLRenderer# - * @see PIXI.WebGLExtract + * @see PIXI.extract.WebGLExtract */ renderer.extract = this; } diff --git a/src/extras/AnimatedSprite.js b/src/extras/AnimatedSprite.js index 13b804f..5297778 100644 --- a/src/extras/AnimatedSprite.js +++ b/src/extras/AnimatedSprite.js @@ -137,7 +137,7 @@ this.playing = true; if (this._autoUpdate) { - core.ticker.shared.add(this.update, this); + core.ticker.shared.add(this.update, this, core.UPDATE_PRIORITY.HIGH); } } diff --git a/src/extras/TextureTransform.js b/src/extras/TextureTransform.js index 78e6e40..16536d2 100644 --- a/src/extras/TextureTransform.js +++ b/src/extras/TextureTransform.js @@ -65,8 +65,36 @@ } /** + * Multiplies uvs array to transform + * @param {Float32Array} uvs mesh uvs + * @param {Float32Array} [out=uvs] output + * @returns {Float32Array} output + */ + multiplyUvs(uvs, out) + { + if (out === undefined) + { + out = uvs; + } + + const mat = this.mapCoord; + + for (let i = 0; i < uvs.length; i += 2) + { + const x = uvs[i]; + const y = uvs[i + 1]; + + out[i] = (x * mat.a) + (y * mat.c) + mat.tx; + out[i + 1] = (x * mat.b) + (y * mat.d) + mat.ty; + } + + return out; + } + + /** * updates matrices if texture was changed * @param {boolean} forceUpdate if true, matrices will be updated any case + * @returns {boolean} whether or not it was updated */ update(forceUpdate) { @@ -74,13 +102,13 @@ if (!tex || !tex.valid) { - return; + return false; } if (!forceUpdate && this._lastTextureID === tex._updateID) { - return; + return false; } this._lastTextureID = tex._updateID; @@ -110,5 +138,7 @@ frame[3] = (tex._frame.y + tex._frame.height - margin + offset) / texBase.height; this.uClampOffset[0] = offset / texBase.realWidth; this.uClampOffset[1] = offset / texBase.realHeight; + + return true; } } diff --git a/src/extras/TilingSprite.js b/src/extras/TilingSprite.js index 1f6014f..a3f027f 100644 --- a/src/extras/TilingSprite.js +++ b/src/extras/TilingSprite.js @@ -180,9 +180,9 @@ const transform = this.worldTransform; const resolution = renderer.resolution; const baseTexture = texture.baseTexture; - const baseTextureResolution = texture.baseTexture.resolution; - const modX = (this.tilePosition.x / this.tileScale.x) % texture._frame.width; - const modY = (this.tilePosition.y / this.tileScale.y) % texture._frame.height; + const baseTextureResolution = baseTexture.resolution; + const modX = ((this.tilePosition.x / this.tileScale.x) % texture._frame.width) * baseTextureResolution; + const modY = ((this.tilePosition.y / this.tileScale.y) % texture._frame.height) * baseTextureResolution; // create a nice shiny pattern! // TODO this needs to be refreshed if texture changes.. diff --git a/src/extras/cacheAsBitmap.js b/src/extras/cacheAsBitmap.js index 5dd5640..aea06a8 100644 --- a/src/extras/cacheAsBitmap.js +++ b/src/extras/cacheAsBitmap.js @@ -227,7 +227,16 @@ this.transform._parentID = -1; // restore the transform of the cached sprite to avoid the nasty flicker.. - this.updateTransform(); + if (!this.parent) + { + this.parent = renderer._tempDisplayObjectParent; + this.updateTransform(); + this.parent = null; + } + else + { + this.updateTransform(); + } // map the hit test.. this.containsPoint = cachedSprite.containsPoint.bind(cachedSprite); @@ -314,7 +323,17 @@ cachedSprite._bounds = this._bounds; cachedSprite.alpha = cacheAlpha; - this.updateTransform(); + if (!this.parent) + { + this.parent = renderer._tempDisplayObjectParent; + this.updateTransform(); + this.parent = null; + } + else + { + this.updateTransform(); + } + this.updateTransform = this.displayObjectUpdateTransform; this._cacheData.sprite = cachedSprite; @@ -358,9 +377,12 @@ * Destroys the cached object. * * @private + * @param {object|boolean} [options] - Options parameter. A boolean will act as if all options + * have been set to that value. + * Used when destroying containers, see the Container.destroy method. */ -DisplayObject.prototype._cacheAsBitmapDestroy = function _cacheAsBitmapDestroy() +DisplayObject.prototype._cacheAsBitmapDestroy = function _cacheAsBitmapDestroy(options) { this.cacheAsBitmap = false; - this.destroy(); + this.destroy(options); }; diff --git a/src/extras/index.js b/src/extras/index.js index bbd8bbf..a34d7ec 100644 --- a/src/extras/index.js +++ b/src/extras/index.js @@ -1,7 +1,7 @@ /** + * Additional PIXI DisplayObjects for animation, tiling and bitmap text. * @namespace PIXI.extras */ -export { default as TextureTransform } from './TextureTransform'; export { default as AnimatedSprite } from './AnimatedSprite'; export { default as TilingSprite } from './TilingSprite'; export { default as TilingSpriteRenderer } from './webgl/TilingSpriteRenderer'; diff --git a/src/extras/webgl/TilingSpriteRenderer.js b/src/extras/webgl/TilingSpriteRenderer.js index aeec172..eee3b5c 100644 --- a/src/extras/webgl/TilingSpriteRenderer.js +++ b/src/extras/webgl/TilingSpriteRenderer.js @@ -11,7 +11,7 @@ * WebGL renderer plugin for tiling sprites * * @class - * @memberof PIXI + * @memberof PIXI.extras * @extends PIXI.ObjectRenderer */ export default class TilingSpriteRenderer extends core.ObjectRenderer @@ -118,7 +118,7 @@ tempMat.invert(); if (isSimple) { - tempMat.append(uv.mapCoord); + tempMat.prepend(uv.mapCoord); } else { diff --git a/src/filters/displacement/DisplacementFilter.js b/src/filters/displacement/DisplacementFilter.js index d5355c3..b5d030c 100644 --- a/src/filters/displacement/DisplacementFilter.js +++ b/src/filters/displacement/DisplacementFilter.js @@ -35,7 +35,7 @@ this.maskSprite = sprite; this.maskMatrix = maskMatrix; - this.uniforms.mapSampler = sprite.texture; + this.uniforms.mapSampler = sprite._texture; this.uniforms.filterMatrix = maskMatrix; this.uniforms.scale = { x: 1, y: 1 }; diff --git a/src/filters/index.js b/src/filters/index.js index f3e8862..7ceb50a 100644 --- a/src/filters/index.js +++ b/src/filters/index.js @@ -1,4 +1,21 @@ /** + * This namespace contains WebGL-only display filters that can be applied + * to DisplayObjects using the {@link PIXI.DisplayObject#filters filters} property. + * @example + * // Create a new application + * const app = new PIXI.Application(); + * + * // Draw a green rectangle + * const rect = new PIXI.Graphics() + * .beginFill(0x00ff00) + * .drawRect(40, 40, 200, 200); + * + * // Add a blur filter + * rect.filters = [new PIXI.filters.BlurFilter()]; + * + * // Display rectangle + * app.stage.addChild(rect); + * document.body.appendChild(app.view); * @namespace PIXI.filters */ export { default as FXAAFilter } from './fxaa/FXAAFilter'; diff --git a/src/index.js b/src/index.js index c553427..77da490 100644 --- a/src/index.js +++ b/src/index.js @@ -20,6 +20,14 @@ import { utils } from './core'; utils.mixins.performMixins(); +/** + * Alias for {@link PIXI.loaders.shared}. + * @name loader + * @memberof PIXI + * @type {PIXI.loader.Loader} + */ +const loader = loaders.shared || null; + export { accessibility, extract, @@ -30,18 +38,8 @@ mesh, particles, prepare, + loader, }; -/** - * A premade instance of the loader that can be used to load resources. - * - * @name loader - * @memberof PIXI - * @property {PIXI.loaders.Loader} - */ -const loader = loaders && loaders.Loader ? new loaders.Loader() : null; // check is there in case user excludes loader lib - -export { loader }; - // Always export pixi globally. global.PIXI = exports; // eslint-disable-line diff --git a/src/interaction/InteractionData.js b/src/interaction/InteractionData.js index 02d53a3..449f8cc 100644 --- a/src/interaction/InteractionData.js +++ b/src/interaction/InteractionData.js @@ -30,7 +30,10 @@ /** * When passed to an event handler, this will be the original DOM Event that was captured * - * @member {Event} + * @see https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent + * @see https://developer.mozilla.org/en-US/docs/Web/API/TouchEvent + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent + * @member {MouseEvent|TouchEvent|PointerEvent} */ this.originalEvent = null; } diff --git a/src/interaction/InteractionManager.js b/src/interaction/InteractionManager.js index 8baf13a..2d75c7c 100644 --- a/src/interaction/InteractionManager.js +++ b/src/interaction/InteractionManager.js @@ -13,6 +13,14 @@ const MOUSE_POINTER_ID = 'MOUSE'; +// helpers for hitTest() - only used inside hitTest() +const hitTestEvent = { + target: null, + data: { + global: null, + }, +}; + /** * The interaction manager deals with mouse, touch and pointer events. Any DisplayObject can be interactive * if its interactive parameter is set to true @@ -245,67 +253,60 @@ this.setTargetElement(this.renderer.view, this.renderer.resolution); /** - * Fired when a pointer device button (usually a mouse button) is pressed on the display + * Fired when a pointer device button (usually a mouse left-button) is pressed on the display * object. * - * @event mousedown - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mousedown + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device secondary button (usually a mouse right-button) is pressed * on the display object. * - * @event rightdown - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightdown + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** - * Fired when a pointer device button (usually a mouse button) is released over the display + * Fired when a pointer device button (usually a mouse left-button) is released over the display * object. * - * @event mouseup - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseup + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device secondary button (usually a mouse right-button) is released * over the display object. * - * @event rightup - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightup + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** - * Fired when a pointer device button (usually a mouse button) is pressed and released on + * Fired when a pointer device button (usually a mouse left-button) is pressed and released on * the display object. * - * @event click - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#click + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device secondary button (usually a mouse right-button) is pressed * and released on the display object. * - * @event rightclick - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightclick + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** - * Fired when a pointer device button (usually a mouse button) is released outside the + * Fired when a pointer device button (usually a mouse left-button) is released outside the * display object that initially registered a * [mousedown]{@link PIXI.interaction.InteractionManager#event:mousedown}. * - * @event mouseupoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** @@ -313,146 +314,362 @@ * outside the display object that initially registered a * [rightdown]{@link PIXI.interaction.InteractionManager#event:rightdown}. * - * @event rightupoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device (usually a mouse) is moved while over the display object * - * @event mousemove - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mousemove + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device (usually a mouse) is moved onto the display object * - * @event mouseover - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseover + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device (usually a mouse) is moved off the display object * - * @event mouseout - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseout + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is pressed on the display object. * - * @event pointerdown - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerdown + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is released over the display object. * - * @event pointerup - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerup + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when the operating system cancels a pointer event * - * @event pointercancel - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointercancel + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is pressed and released on the display object. * - * @event pointertap - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointertap + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is released outside the display object that initially * registered a [pointerdown]{@link PIXI.interaction.InteractionManager#event:pointerdown}. * - * @event pointerupoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device is moved while over the display object * - * @event pointermove - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointermove + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device is moved onto the display object * - * @event pointerover - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerover + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device is moved off the display object * - * @event pointerout - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerout + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is placed on the display object. * - * @event touchstart - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchstart + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is removed from the display object. * - * @event touchend - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchend + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when the operating system cancels a touch * - * @event touchcancel - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchcancel + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is placed and removed from the display object. * - * @event tap - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#tap + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is removed outside of the display object that initially * registered a [touchstart]{@link PIXI.interaction.InteractionManager#event:touchstart}. * - * @event touchendoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchendoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is moved along the display object. * - * @event touchmove - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchmove + * @param {PIXI.interaction.InteractionData} event - Interaction data */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is pressed on the display. + * object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mousedown + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is pressed + * on the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightdown + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is released over the display + * object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseup + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is released + * over the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightup + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is pressed and released on + * the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#click + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is pressed + * and released on the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightclick + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is released outside the + * display object that initially registered a + * [mousedown]{@link PIXI.DisplayObject#event:mousedown}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is released + * outside the display object that initially registered a + * [rightdown]{@link PIXI.DisplayObject#event:rightdown}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device (usually a mouse) is moved while over the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mousemove + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device (usually a mouse) is moved onto the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseover + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device (usually a mouse) is moved off the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseout + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is pressed on the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerdown + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is released over the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerup + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when the operating system cancels a pointer event. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointercancel + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is pressed and released on the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointertap + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is released outside the display object that initially + * registered a [pointerdown]{@link PIXI.DisplayObject#event:pointerdown}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device is moved while over the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointermove + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device is moved onto the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerover + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device is moved off the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerout + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is placed on the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchstart + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is removed from the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchend + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when the operating system cancels a touch. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchcancel + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is placed and removed from the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#tap + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is removed outside of the display object that initially + * registered a [touchstart]{@link PIXI.DisplayObject#event:touchstart}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchendoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is moved along the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchmove + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + } + + /** + * Hit tests a point against the display tree, returning the first interactive object that is hit. + * + * @param {PIXI.Point} globalPoint - A point to hit test with, in global space. + * @param {PIXI.Container} [root] - The root display object to start from. If omitted, defaults + * to the last rendered root of the associated renderer. + * @return {PIXI.DisplayObject} The hit display object, if any. + */ + hitTest(globalPoint, root) + { + // clear the target for our hit test + hitTestEvent.target = null; + // assign the global point + hitTestEvent.data.global = globalPoint; + // ensure safety of the root + if (!root) + { + root = this.renderer._lastObjectRendered; + } + // run the hit test + this.processInteractive(hitTestEvent, root, null, true); + // return our found object - it'll be null if we didn't hit anything + + return hitTestEvent.target; } /** @@ -487,7 +704,7 @@ return; } - core.ticker.shared.add(this.update, this); + core.ticker.shared.add(this.update, this, core.UPDATE_PRIORITY.INTERACTION); if (window.navigator.msPointerEnabled) { @@ -804,8 +1021,6 @@ } } - let keepHitTestingAfterChildren = hitTest; - // ** FREE TIP **! If an object is not interactive or has no buttons in it // (such as a game scene!) set interactiveChildren to false for that displayObject. // This will allow pixi to completely ignore and bypass checking the displayObjects children. @@ -818,7 +1033,9 @@ const child = children[i]; // time to get recursive.. if this function will return if something is hit.. - if (this.processInteractive(interactionEvent, child, func, hitTest, interactiveParent)) + const childHit = this.processInteractive(interactionEvent, child, func, hitTest, interactiveParent); + + if (childHit) { // its a good idea to check if a child has lost its parent. // this means it has been removed whilst looping so its best @@ -827,8 +1044,6 @@ continue; } - hit = true; - // we no longer need to hit test any more objects in this container as we we // now know the parent has been hit interactiveParent = false; @@ -838,36 +1053,41 @@ // This means we no longer need to hit test anything else. We still need to run // through all objects, but we don't need to perform any hit tests. - keepHitTestingAfterChildren = false; - - if (child.interactive) + if (childHit) { - hitTest = false; + if (interactionEvent.target) + { + hitTest = false; + } + hit = true; } - - // we can break now as we have hit an object. } } } - hitTest = keepHitTestingAfterChildren; - // no point running this if the item is not interactive or does not have an interactive parent. if (interactive) { // if we are hit testing (as in we have no hit any objects yet) // We also don't need to worry about hit testing if once of the displayObjects children - // has already been hit! - if (hitTest && !hit) + // has already been hit - but only if it was interactive, otherwise we need to keep + // looking for an interactive child, just in case we hit one + if (hitTest && !interactionEvent.target) { if (displayObject.hitArea) { displayObject.worldTransform.applyInverse(point, this._tempPoint); - hit = displayObject.hitArea.contains(this._tempPoint.x, this._tempPoint.y); + if (displayObject.hitArea.contains(this._tempPoint.x, this._tempPoint.y)) + { + hit = true; + } } else if (displayObject.containsPoint) { - hit = displayObject.containsPoint(point); + if (displayObject.containsPoint(point)) + { + hit = true; + } } } @@ -878,7 +1098,10 @@ interactionEvent.target = displayObject; } - func(interactionEvent, displayObject, hit); + if (func) + { + func(interactionEvent, displayObject, !!hit); + } } } @@ -914,7 +1137,7 @@ { const event = events[i]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -998,7 +1221,7 @@ { const event = events[i]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1098,7 +1321,7 @@ const test = isRightButton ? flags.RIGHT_DOWN : flags.LEFT_DOWN; - const isDown = trackingData !== undefined && (trackingData.flags | test); + const isDown = trackingData !== undefined && (trackingData.flags & test); if (hit) { @@ -1118,11 +1341,11 @@ { if (isRightButton) { - trackingData.rightDown = hit; + trackingData.rightDown = false; } else { - trackingData.leftDown = hit; + trackingData.leftDown = false; } } } @@ -1180,7 +1403,7 @@ { const event = events[i]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1255,7 +1478,7 @@ this.setCursorMode(null); } - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1268,6 +1491,12 @@ { this.emit('mouseout', interactionEvent); } + else + { + // we can get touchleave events after touchend, so we want to make sure we don't + // introduce memory leaks + this.releaseInteractionDataForPointerId(interactionData.identifier); + } } /** @@ -1344,7 +1573,7 @@ // Only mouse and pointer can call onPointerOver, so events will always be length 1 const event = events[0]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1366,12 +1595,14 @@ * Get InteractionData for a given pointerId. Store that data as well * * @private - * @param {number} pointerId - Identifier from a pointer event + * @param {PointerEvent} event - Normalized pointer event, output from normalizeToPointerData * @return {InteractionData} - Interaction data for the given pointer identifier */ - getInteractionDataForPointerId(pointerId) + getInteractionDataForPointerId(event) { - if (pointerId === MOUSE_POINTER_ID) + const pointerId = event.pointerId; + + if (pointerId === MOUSE_POINTER_ID || event.pointerType === 'mouse') { return this.mouse; } diff --git a/src/interaction/InteractionTrackingData.js b/src/interaction/InteractionTrackingData.js index 6a1f69e..8493b63 100644 --- a/src/interaction/InteractionTrackingData.js +++ b/src/interaction/InteractionTrackingData.js @@ -35,8 +35,10 @@ } /** + * Unique pointer id of the event + * * @readonly - * @type {number} Unique pointer id of the event + * @member {number} */ get pointerId() { diff --git a/src/interaction/index.js b/src/interaction/index.js index 56c5b09..72f7d71 100644 --- a/src/interaction/index.js +++ b/src/interaction/index.js @@ -1,4 +1,8 @@ /** + * This namespace contains a renderer plugin for handling mouse, pointer, and touch events. + * + * Do not instantiate this plugin directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. * @namespace PIXI.interaction */ export { default as InteractionData } from './InteractionData'; diff --git a/src/interaction/interactiveTarget.js b/src/interaction/interactiveTarget.js index d252fba..cebac40 100644 --- a/src/interaction/interactiveTarget.js +++ b/src/interaction/interactiveTarget.js @@ -2,7 +2,7 @@ * Default property values of interactive objects * Used by {@link PIXI.interaction.InteractionManager} to automatically give all DisplayObjects these properties * - * @mixin + * @private * @name interactiveTarget * @memberof PIXI.interaction * @example @@ -14,18 +14,28 @@ * ); */ export default { + /** - * Determines if the displayObject be clicked/touched + * Enable interaction events for the DisplayObject. Touch, pointer and mouse + * events will not be emitted unless `interactive` is set to `true`. * - * @inner {boolean} + * @example + * const sprite = new PIXI.Sprite(texture); + * sprite.interactive = true; + * sprite.on('tap', (event) => { + * //handle event + * }); + * @member {boolean} + * @memberof PIXI.DisplayObject# */ interactive: false, /** * Determines if the children to the displayObject can be clicked/touched - * Setting this to false allows pixi to bypass a recursive hitTest function + * Setting this to false allows pixi to bypass a recursive `hitTest` function * - * @inner {boolean} + * @member {boolean} + * @memberof PIXI.Container# */ interactiveChildren: true, @@ -33,7 +43,12 @@ * Interaction shape. Children will be hit first, then this shape will be checked. * Setting this will cause this shape to be checked in hit tests rather than the displayObject's bounds. * - * @inner {PIXI.Rectangle|PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.RoundedRectangle} + * @example + * const sprite = new PIXI.Sprite(texture); + * sprite.interactive = true; + * sprite.hitArea = new PIXI.Rectangle(0, 0, 100, 100); + * @member {PIXI.Rectangle|PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.RoundedRectangle} + * @memberof PIXI.DisplayObject# */ hitArea: null, @@ -41,8 +56,12 @@ * If enabled, the mouse cursor use the pointer behavior when hovered over the displayObject if it is interactive * Setting this changes the 'cursor' property to `'pointer'`. * + * @example + * const sprite = new PIXI.Sprite(texture); + * sprite.interactive = true; + * sprite.buttonMode = true; * @member {boolean} - * @memberof PIXI.interaction.interactiveTarget# + * @memberof PIXI.DisplayObject# */ get buttonMode() { @@ -64,9 +83,14 @@ * This defines what cursor mode is used when the mouse cursor * is hovered over the displayObject. * + * @example + * const sprite = new PIXI.Sprite(texture); + * sprite.interactive = true; + * sprite.cursor = 'wait'; * @see https://developer.mozilla.org/en/docs/Web/CSS/cursor * - * @inner {string} + * @member {string} + * @memberof PIXI.DisplayObject# */ cursor: null, @@ -74,7 +98,7 @@ * Internal set of all active pointers, by identifier * * @member {Map} - * @memberof PIXI.interaction.interactiveTarget# + * @memberof PIXI.DisplayObject# * @private */ get trackedPointers() @@ -87,7 +111,8 @@ /** * Map of all tracked pointers, by identifier. Use trackedPointers to access. * - * @private {Map} + * @private + * @type {Map} */ _trackedPointers: undefined, }; diff --git a/src/loaders/index.js b/src/loaders/index.js index 5189793..daa1fc4 100644 --- a/src/loaders/index.js +++ b/src/loaders/index.js @@ -1,9 +1,22 @@ +import Application from '../core/Application'; +import Loader from './loader'; + /** + * This namespace contains APIs which extends the {@link https://github.com/englercj/resource-loader resource-loader} module + * for loading assets, data, and other resources dynamically. + * @example + * const loader = new PIXI.loaders.Loader(); + * loader.add('bunny', 'data/bunny.png') + * .add('spaceship', 'assets/spritesheet.json'); + * loader.load((loader, resources) => { + * // resources.bunny + * // resources.spaceship + * }); * @namespace PIXI.loaders */ -export { default as Loader } from './loader'; +export { Loader }; export { default as bitmapFontParser, parse as parseBitmapFontData } from './bitmapFontParser'; -export { default as spritesheetParser } from './spritesheetParser'; +export { default as spritesheetParser, getResourcePath } from './spritesheetParser'; export { default as textureParser } from './textureParser'; /** @@ -13,3 +26,55 @@ * @memberof PIXI.loaders */ export { Resource } from 'resource-loader'; + +/** + * A premade instance of the loader that can be used to load resources. + * @name shared + * @memberof PIXI.loaders + * @type {PIXI.loaders.Loader} + */ +const shared = new Loader(); + +shared.destroy = () => +{ + // protect destroying shared loader +}; + +export { shared }; + +// Mixin the loader construction +const AppPrototype = Application.prototype; + +AppPrototype._loader = null; + +/** + * Loader instance to help with asset loading. + * @name PIXI.Application#loader + * @type {PIXI.loaders.Loader} + */ +Object.defineProperty(AppPrototype, 'loader', { + get() + { + if (!this._loader) + { + const sharedLoader = this._options.sharedLoader; + + this._loader = sharedLoader ? shared : new Loader(); + } + + return this._loader; + }, +}); + +// Override the destroy function +// making sure to destroy the current Loader +AppPrototype._parentDestroy = AppPrototype.destroy; +AppPrototype.destroy = function destroy() +{ + if (this._loader) + { + this._loader.destroy(); + this._loader = null; + } + this._parentDestroy(); +}; diff --git a/src/loaders/loader.js b/src/loaders/loader.js index 767a411..59b8000 100644 --- a/src/loaders/loader.js +++ b/src/loaders/loader.js @@ -90,6 +90,15 @@ { Loader._pixiMiddleware.push(fn); } + + /** + * Destroy the loader, removes references. + */ + destroy() + { + this.removeAllListeners(); + this.reset(); + } } // Copy EE3 prototype (mixin) diff --git a/src/loaders/spritesheetParser.js b/src/loaders/spritesheetParser.js index e57dfb2..eda9584 100644 --- a/src/loaders/spritesheetParser.js +++ b/src/loaders/spritesheetParser.js @@ -1,12 +1,11 @@ import { Resource } from 'resource-loader'; -import path from 'path'; +import url from 'url'; import { Spritesheet } from '../core'; export default function () { return function spritesheetParser(resource, next) { - let resourcePath; const imageResourceName = `${resource.name}_image`; // skip if no data, its not json, it isn't spritesheet data, or the image resource already exists @@ -28,15 +27,7 @@ parentResource: resource, }; - // Prepend url path unless the resource image is a data url - if (resource.isDataUrl) - { - resourcePath = resource.data.meta.image; - } - else - { - resourcePath = `${path.dirname(resource.url.replace(this.baseUrl, ''))}/${resource.data.meta.image}`; - } + const resourcePath = getResourcePath(resource, this.baseUrl); // load the image for this sheet this.add(imageResourceName, resourcePath, loadOptions, function onImageLoad(res) @@ -56,3 +47,14 @@ }); }; } + +export function getResourcePath(resource, baseUrl) +{ + // Prepend url path unless the resource image is a data url + if (resource.isDataUrl) + { + return resource.data.meta.image; + } + + return url.resolve(resource.url.replace(baseUrl, ''), resource.data.meta.image); +} diff --git a/.travis.yml b/.travis.yml index 3df8b22..14de334 100644 --- a/.travis.yml +++ b/.travis.yml @@ -110,3 +110,43 @@ on: all_branches: true condition: $TRAVIS_TAG + # Deploy config for latest release + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: dist + upload-dir: "release" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: docs + upload-dir: "release/docs" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: coverage + upload-dir: "release/coverage" + on: + all_branches: true + condition: $TRAVIS_TAG \ No newline at end of file diff --git a/README.md b/README.md index d26fadd..3b85f85 100644 --- a/README.md +++ b/README.md @@ -36,22 +36,14 @@ - Wiki: Other misc tutorials and resources are [on the Wiki](https://github.com/pixijs/pixi.js/wiki/Resources). ### Community ### -- Forums: Check out the [forum] (http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow] (http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. +- Forums: Check out the [forum](http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow](http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. - Inspiration: Check out the [gallery](http://www.pixijs.com/gallery) to see some of the amazing things people have created! - Chat: You can join us on [Gitter](https://gitter.im/pixijs/pixi.js) To chat about Pixi. We also now have a Slack channel. If you would like to join it please Send me an email (mat@goodboydigital.com) and I will invite you in. ### Setup ### -It's easy to get started with Pixi.js! Simply grab the pre-built versions from here: - -Release Branch - Nice and stable Pixi.js -- Unminified: [http://pixijs.download/release/pixi.js] -- Minified: [http://pixijs.download/release/pixi.min.js] - -Develop Branch - The bleeding edge version of Pixi.js -- Unminified: [http://pixijs.download/dev/pixi.js] -- Minified: [http://pixijs.download/dev/pixi.min.js] +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. @@ -70,10 +62,10 @@ #### CDN Install (via cdnjs) ```html - + ``` -_Note: `4.2.2` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ +_Note: `4.4.3` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ ### Demos ### @@ -130,7 +122,7 @@ // and the root stage PIXI.Container. var app = new PIXI.Application(); -// The application will create a canvas element for you that you +// The application will create a canvas element for you that you // can then insert into the DOM. document.body.appendChild(app.view); @@ -150,7 +142,7 @@ // Add the bunny to the scene we are building. app.stage.addChild(bunny); - + // Listen for frame updates app.ticker.add(function() { // each frame we spin the bunny around a bit diff --git a/bower.json b/bower.json deleted file mode 100644 index 41b668c..0000000 --- a/bower.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "pixi.js", - "main": "dist/pixi.min.js", - "ignore": [ - "**/.*", - "docs", - "node_modules", - "bower_components", - "test", - "scripts", - "bower.json", - "inch.json" - ], - "dependencies": { - }, - "devDependencies": { - } -} diff --git a/package.json b/package.json index d35cbb2..308469f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.3.5", + "version": "4.4.4", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ @@ -40,11 +40,11 @@ "lib": "babel src --out-dir lib -s", "predocs": "rimraf docs/**", "docs": "jsdoc -c scripts/jsdoc.conf.json -R README.md", - "publish:patch": "npm version patch --no-git-tag-version && npm publish", - "publish:minor": "npm version minor --no-git-tag-version && npm publish", - "publish:major": "npm version major --no-git-tag-version && npm publish", + "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", - "postpublish": "node scripts/release.js" + "postpublish": "git push && git push --tags" }, "files": [ "dist/", @@ -72,7 +72,6 @@ "electron": "^1.4.15", "eslint": "^3.5.0", "floss": "^2.0.1", - "gh-pages": "^0.11.0", "jaguarjs-jsdoc": "^1.0.1", "js-md5": "^0.4.1", "jsdoc": "^3.4.2", diff --git a/scripts/jsdoc.conf.json b/scripts/jsdoc.conf.json index 553e61c..3f52180 100644 --- a/scripts/jsdoc.conf.json +++ b/scripts/jsdoc.conf.json @@ -43,7 +43,7 @@ }, "markdown" : { "parser" : "gfm", - "hardwrap" : true + "hardwrap" : false }, "opts": { "encoding" : "utf8", diff --git a/scripts/release.js b/scripts/release.js deleted file mode 100644 index 156620c..0000000 --- a/scripts/release.js +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env node -'use strict'; - -// Publish script to push releases of the bin files -// the normally are gitignored -const ghpages = require('gh-pages'); -const path = require('path'); -const packageInfo = require(path.join(__dirname, '..', 'package.json')); - -const options = { - src: [ - 'dist/**/*', - 'lib/**/*', - 'src/**/*', - 'scripts/*', - 'scripts/renders/*', - 'test/**/*', - '*.json', - '*.md', - 'LICENSE', - '.eslintrc', - '.editorconfig', - '.travis.yml', - '.babelrc', - ], - dotfiles: true, - branch: 'release', - message: packageInfo.version, - logger: console.log.bind(console), -}; - -ghpages.publish(process.cwd(), options, (err) => -{ - if (err) - { - console.log(err); - process.exit(1); - - return; - } - - process.exit(0); -}); diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index f16bcb1..220427a 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -21,7 +21,7 @@ const DIV_HOOK_ZINDEX = 2; /** - * The Accessibility manager reacreates the ability to tab and and have content read by screen + * The Accessibility manager recreates the ability to tab and and have content read by screen * readers. This is very important as it can possibly help people with disabilities access pixi * content. * diff --git a/src/accessibility/index.js b/src/accessibility/index.js index 568cfb1..0ae108f 100644 --- a/src/accessibility/index.js +++ b/src/accessibility/index.js @@ -1,4 +1,9 @@ /** + * This namespace contains a renderer plugin for interaction accessibility for end-users + * with physical impairments which require screen-renders, keyboard navigation, etc. + * + * Do not instantiate this plugin directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. * @namespace PIXI.accessibility */ export { default as accessibleTarget } from './accessibleTarget'; diff --git a/src/core/Application.js b/src/core/Application.js index 5340578..2fcc729 100644 --- a/src/core/Application.js +++ b/src/core/Application.js @@ -1,6 +1,8 @@ import { autoDetectRenderer } from './autoDetectRenderer'; import Container from './display/Container'; import { shared, Ticker } from './ticker'; +import settings from './settings'; +import { UPDATE_PRIORITY } from './const'; /** * Convenience class to create a new PIXI application. @@ -22,28 +24,52 @@ */ export default class Application { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experience unexplained flickering try setting this to true. - * @param {boolean} [useSharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedLoader=false] - `true` to use PIXI.loaders.shared, `false` to create new Loader. */ - constructor(width, height, options, noWebGL, useSharedTicker = false) + constructor(options, arg2, arg3, arg4, arg5) { + // Support for constructor(width, height, options, noWebGL, useSharedTicker) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + forceCanvas: !!arg4, + sharedTicker: !!arg5, + }, arg3); + } + + /** + * The default options, so we mixin functionality later. + * @member {object} + * @protected + */ + this._options = options = Object.assign({ + sharedTicker: false, + forceCanvas: false, + sharedLoader: false, + }, options); + /** * WebGL renderer if available, otherwise CanvasRenderer * @member {PIXI.WebGLRenderer|PIXI.CanvasRenderer} */ - this.renderer = autoDetectRenderer(width, height, options, noWebGL); + this.renderer = autoDetectRenderer(options); /** * The root display container that's rendered. @@ -63,7 +89,7 @@ * @member {PIXI.ticker.Ticker} * @default PIXI.ticker.shared */ - this.ticker = useSharedTicker ? shared : new Ticker(); + this.ticker = options.sharedTicker ? shared : new Ticker(); // Start the rendering this.start(); @@ -78,7 +104,7 @@ this._ticker = ticker; if (ticker) { - ticker.add(this.render, this); + ticker.add(this.render, this, UPDATE_PRIORITY.LOW); } } get ticker() // eslint-disable-line require-jsdoc @@ -136,13 +162,18 @@ */ destroy(removeView) { - this.stop(); + const oldTicker = this._ticker; + this.ticker = null; + oldTicker.destroy(); + this.stage.destroy(); this.stage = null; this.renderer.destroy(removeView); this.renderer = null; + + this._options = null; } } diff --git a/src/core/autoDetectRenderer.js b/src/core/autoDetectRenderer.js index 8960425..9ec0364 100644 --- a/src/core/autoDetectRenderer.js +++ b/src/core/autoDetectRenderer.js @@ -2,6 +2,7 @@ import CanvasRenderer from './renderers/canvas/CanvasRenderer'; import WebGLRenderer from './renderers/webgl/WebGLRenderer'; +// eslint-disable-next-line valid-jsdoc /** * This helper function will automatically detect which renderer you should be using. * WebGL is the preferred renderer as it is a lot faster. If webGL is not supported by @@ -9,24 +10,32 @@ * * @memberof PIXI * @function autoDetectRenderer - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @return {PIXI.WebGLRenderer|PIXI.CanvasRenderer} Returns WebGL renderer if available, otherwise CanvasRenderer */ -export function autoDetectRenderer(width = 800, height = 600, options, noWebGL) +export function autoDetectRenderer(options, arg1, arg2, arg3) { - if (!noWebGL && utils.isWebGLSupported()) + // Backward-compatible support for noWebGL option + let forceCanvas = options && options.forceCanvas; + + if (arg3 !== undefined) { - return new WebGLRenderer(width, height, options); + forceCanvas = arg3; } - return new CanvasRenderer(width, height, options); + if (!forceCanvas && utils.isWebGLSupported()) + { + return new WebGLRenderer(options, arg1, arg2); + } + + return new CanvasRenderer(options, arg1, arg2); } diff --git a/src/core/const.js b/src/core/const.js index b9e99ae..2c68dee 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -341,3 +341,27 @@ LINEAR_VERTICAL: 0, LINEAR_HORIZONTAL: 1, }; + +/** + * Represents the update priorities used by internal PIXI classes when registered with + * the {@link PIXI.ticker.Ticker} object. Higher priority items are updated first and lower + * priority items, such as render, should go later. + * + * @static + * @constant + * @name UPDATE_PRIORITY + * @memberof PIXI + * @type {object} + * @property {number} INTERACTION=50 Highest priority, used for {@link PIXI.interaction.InteractionManager} + * @property {number} HIGH=25 High priority updating, {@link PIXI.VideoBaseTexture} and {@link PIXI.extras.AnimatedSprite} + * @property {number} NORMAL=0 Default priority for ticker events, see {@link PIXI.ticker.Ticker#add}. + * @property {number} LOW=-25 Low priority used for {@link PIXI.Application} rendering. + * @property {number} UTILITY=-50 Lowest priority used for {@link PIXI.prepare.BasePrepare} utility. + */ +export const UPDATE_PRIORITY = { + INTERACTION: 50, + HIGH: 25, + NORMAL: 0, + LOW: -25, + UTILITY: -50, +}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 4e6c77d..decb42a 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -13,7 +13,6 @@ * * @class * @extends EventEmitter - * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ export default class DisplayObject extends EventEmitter @@ -113,6 +112,29 @@ * @private */ this._mask = null; + + /** + * If the object has been destroyed via destroy(). If true, it should not be used. + * + * @member {boolean} + * @private + * @readonly + */ + this._destroyed = false; + + /** + * Fired when this DisplayObject is added to a Container. + * + * @event PIXI.DisplayObject#added + * @param {PIXI.Container} container - The container added to. + */ + + /** + * Fired when this DisplayObject is removed from a Container. + * + * @event PIXI.DisplayObject#removed + * @param {PIXI.Container} container - The container removed from. + */ } /** @@ -403,6 +425,8 @@ this.interactive = false; this.interactiveChildren = false; + + this._destroyed = true; } /** diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index aafdfe2..f787c4a 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -792,8 +792,8 @@ sprite.worldAlpha = this.worldAlpha * sprite.alpha; sprite.blendMode = this.blendMode; - sprite.texture._frame.width = rect.width; - sprite.texture._frame.height = rect.height; + sprite._texture._frame.width = rect.width; + sprite._texture._frame.height = rect.height; sprite.transform.worldTransform = this.transform.worldTransform; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index a24c775..b9f192d 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -19,11 +19,12 @@ */ export default class SystemRenderer extends EventEmitter { + // eslint-disable-next-line valid-jsdoc /** * @param {string} system - The name of the system this renderer is for. - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -37,27 +38,31 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(system, screenWidth, screenHeight, options) + constructor(system, options, arg2, arg3) { super(); sayHello(system); - // prepare options - if (options) + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') { - for (const i in settings.RENDER_OPTIONS) - { - if (typeof options[i] === 'undefined') - { - options[i] = settings.RENDER_OPTIONS[i]; - } - } + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); } - else - { - options = settings.RENDER_OPTIONS; - } + + // Add the default render options + options = Object.assign({}, settings.RENDER_OPTIONS, options); + + /** + * The supplied constructor options. + * + * @member {Object} + * @readOnly + */ + this.options = options; /** * The type of the renderer. @@ -75,7 +80,7 @@ * * @member {PIXI.Rectangle} */ - this.screen = new Rectangle(0, 0, screenWidth || 800, screenHeight || 600); + this.screen = new Rectangle(0, 0, options.width, options.height); /** * The canvas element that everything is drawn to @@ -279,6 +284,8 @@ this.blendModes = null; + this.options = null; + this.preserveDrawingBuffer = false; this.clearBeforeRender = false; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index e84e49c..691caf0 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -17,10 +17,11 @@ */ export default class CanvasRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -34,9 +35,9 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('Canvas', screenWidth, screenHeight, options); + super('Canvas', options, arg2, arg3); this.type = RENDERER_TYPE.CANVAS; @@ -96,7 +97,19 @@ this.context = null; this.renderingToScreen = false; - this.resize(screenWidth, screenHeight); + this.resize(this.options.width, this.options.height); + + /** + * Fired after rendering finishes. + * + * @event PIXI.CanvasRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.CanvasRenderer#prerender + */ } /** @@ -299,4 +312,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.CanvasRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.CanvasExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.CanvasPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.CanvasRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index e1cf7cd..f6fe123 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -31,11 +31,12 @@ */ export default class WebGLRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** * - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -55,9 +56,9 @@ * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('WebGL', screenWidth, screenHeight, options); + super('WebGL', options, arg2, arg3); /** * The type of this renderer as a standardised const @@ -89,12 +90,6 @@ }; - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ this._backgroundColorRgba[3] = this.transparent ? 0 : 1; this.globalUniforms = new UniformGroup({ @@ -117,6 +112,13 @@ this.initPlugins(); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ if(options.context) { this.context.initFromContext(options.context); @@ -169,6 +171,25 @@ } return this; + + /** + * Fired after rendering finishes. + * + * @event PIXI.WebGLRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.WebGLRenderer#prerender + */ + + /** + * Fired when the WebGL context is set. + * + * @event PIXI.WebGLRenderer#context + * @param {WebGLRenderingContext} gl - WebGL context. + */ } /** @@ -295,4 +316,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.WebGLRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.WebGLRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(WebGLRenderer); diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index 4eb6128..fa3d883 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -47,6 +47,14 @@ * @member {boolean} */ this.enabled = true; + + /** + * If enabled, pixi will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; } /** diff --git a/src/core/renderers/webgl/systems/ContextSystem.js b/src/core/renderers/webgl/systems/ContextSystem.js index ff958c7..8df85ea 100644 --- a/src/core/renderers/webgl/systems/ContextSystem.js +++ b/src/core/renderers/webgl/systems/ContextSystem.js @@ -75,7 +75,8 @@ */ createContext(canvas, options) { - var gl = canvas.getContext('webgl', options) || + var gl = //canvas.getContext('webgl2', options) || + canvas.getContext('webgl', options) || canvas.getContext('experimental-webgl', options); if (!gl) diff --git a/src/core/renderers/webgl/systems/FilterSystem.js b/src/core/renderers/webgl/systems/FilterSystem.js index f47335d..37e28d2 100644 --- a/src/core/renderers/webgl/systems/FilterSystem.js +++ b/src/core/renderers/webgl/systems/FilterSystem.js @@ -111,7 +111,7 @@ // TODO we should fit the rect around the transform.. } - else + else if (filters[0].autoFit) { sourceFrame.fit(filterData.stack[0].destinationFrame); } diff --git a/src/core/renderers/webgl/systems/shader/shader/mapType.js b/src/core/renderers/webgl/systems/shader/shader/mapType.js index 581dace..917538a 100644 --- a/src/core/renderers/webgl/systems/shader/shader/mapType.js +++ b/src/core/renderers/webgl/systems/shader/shader/mapType.js @@ -15,7 +15,7 @@ } } - return GL_TABLE[type]; + return GL_TABLE[type]; }; var GL_TABLE = null; diff --git a/src/core/settings.js b/src/core/settings.js index 18c5877..38ef45c 100644 --- a/src/core/settings.js +++ b/src/core/settings.js @@ -2,6 +2,15 @@ import canUploadSameBuffer from './utils/canUploadSameBuffer'; /** + * User's customizable globals for overriding the default PIXI settings, such + * as a renderer's default resolution, framerate, float percision, etc. + * @example + * // Use the native window resolution as the default resolution + * // will support high-density displays when rendering + * PIXI.settings.RESOLUTION = window.devicePixelRatio. + * + * // Disable interpolation when scaling, will make texture be pixelated + * PIXI.settings.SCALE_MODE = PIXI.SCALE_MODES.NEAREST; * @namespace PIXI.settings */ export default { @@ -101,6 +110,9 @@ * @property {boolean} clearBeforeRender=true * @property {boolean} preserveDrawingBuffer=false * @property {boolean} roundPixels=false + * @property {number} width=800 + * @property {number} height=600 + * @property {boolean} legacy=false */ RENDER_OPTIONS: { view: null, @@ -112,6 +124,9 @@ clearBeforeRender: true, preserveDrawingBuffer: false, roundPixels: false, + width: 800, + height: 600, + legacy: false, }, /** diff --git a/src/core/shader/Program.js b/src/core/shader/Program.js index c0b538b..07b4398 100644 --- a/src/core/shader/Program.js +++ b/src/core/shader/Program.js @@ -1,6 +1,6 @@ import extractUniformsFromSrc from './extractUniformsFromSrc'; import generateUniformsSync from './generateUniformsSync'; -import glCore from 'pixi-gl-core'; +import shaderUtils from '../renderers/webgl/systems/shader/shader'; import { ProgramCache } from '../utils'; import getTestContext from '../utils/getTestContext'; @@ -64,14 +64,17 @@ } else { - vertexSrc = glCore.shader.setPrecision(vertexSrc, 'mediump'); - fragmentSrc = glCore.shader.setPrecision(fragmentSrc, 'mediump'); + vertexSrc = shaderUtils.setPrecision(vertexSrc, 'mediump'); + fragmentSrc = shaderUtils.setPrecision(fragmentSrc, 'mediump'); - const program = glCore.shader.compileProgram(gl, vertexSrc, fragmentSrc); + const program = shaderUtils.compileProgram(gl, vertexSrc, fragmentSrc); + console.log("<<<<<<>>><<"); this.attributeData = this.getAttributeData(program, gl); this.uniformData = this.getUniformData(program, gl); + console.log(this.uniformData); + //gl.deleteProgram(program); } } @@ -95,13 +98,13 @@ for (let i = 0; i < totalAttributes; i++) { const attribData = gl.getActiveAttrib(program, i); - const type = glCore.shader.mapType(gl, attribData.type); + const type = shaderUtils.mapType(gl, attribData.type); /*eslint-disable */ const data = { type: type, name: attribData.name, - size: glCore.shader.mapSize(type), + size: shaderUtils.mapSize(type), location: 0, }; /*eslint-enable */ @@ -137,13 +140,14 @@ // TODO expose this as a prop? // const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); - const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); + //const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); for (let i = 0; i < totalUniforms; i++) { const uniformData = gl.getActiveUniform(program, i); const name = uniformData.name.replace(/\[.*?\]/, ''); - const type = glCore.shader.mapType(gl, uniformData.type); + const type = shaderUtils.mapType(gl, uniformData.type); + console.log('mapping ' + uniformData.type + ' to ' + type); // if (!name.match(maskRegex)) { @@ -151,7 +155,7 @@ uniforms[name] = { type: type, size: uniformData.size, - value: glCore.shader.defaultValue(type, uniformData.size), + value: shaderUtils.defaultValue(type, uniformData.size), }; /*eslint-enable */ } diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index e387e99..b57d095 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -147,12 +147,12 @@ // so if _width is 0 then width was not set.. if (this._width) { - this.scale.x = sign(this.scale.x) * this._width / this.texture.orig.width; + this.scale.x = sign(this.scale.x) * this._width / this._texture.orig.width; } if (this._height) { - this.scale.y = sign(this.scale.y) * this._height / this.texture.orig.height; + this.scale.y = sign(this.scale.y) * this._height / this._texture.orig.height; } } diff --git a/src/core/sprites/canvas/CanvasTinter.js b/src/core/sprites/canvas/CanvasTinter.js index 27e5e0c..fd9de5d 100644 --- a/src/core/sprites/canvas/CanvasTinter.js +++ b/src/core/sprites/canvas/CanvasTinter.js @@ -18,7 +18,7 @@ */ getTintedTexture: (sprite, color) => { - const texture = sprite.texture; + const texture = sprite._texture; color = CanvasTinter.roundColor(color); diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 5a4edda..8b9b797 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -344,6 +344,7 @@ const texture = this._texture; const style = this._style; + const padding = style.trim ? 0 : style.padding; texture.baseTexture.valid = true; texture.baseTexture.resolution = this.resolution; @@ -353,11 +354,11 @@ texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - texture.trim.x = -style.padding; - texture.trim.y = -style.padding; + texture.trim.x = -padding; + texture.trim.y = -padding; - texture.orig.width = texture._frame.width - (style.padding * 2); - texture.orig.height = texture._frame.height - (style.padding * 2); + texture.orig.width = texture._frame.width - (padding * 2); + texture.orig.height = texture._frame.height - (padding * 2); // call sprite onTextureUpdate to update scale if _width or _height were set this._onTextureUpdate(); diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index 853da3d..8a9c34b 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -114,6 +114,38 @@ this.cacheId = null; this.validate(); + + /** + * Fired when a not-immediately-available source finishes loading. + * + * @protected + * @event PIXI.BaseTexture#loaded + * @param {PIXI.BaseTexture} baseTexture - Resource loaded. + */ + + /** + * Fired when a not-immediately-available source fails to load. + * + * @protected + * @event PIXI.BaseTexture#error + * @param {PIXI.BaseTexture} baseTexture - Resource errored. + */ + + /** + * Fired when BaseTexture is updated. + * + * @protected + * @event PIXI.BaseTexture#update + * @param {PIXI.BaseTexture} baseTexture - Instance of texture being updated. + */ + + /** + * Fired when BaseTexture is destroyed. + * + * @protected + * @event PIXI.BaseTexture#dispose + * @param {PIXI.BaseTexture} baseTexture - Instance of texture being destroyed. + */ } setResource(resource) diff --git a/src/core/textures/Spritesheet.js b/src/core/textures/Spritesheet.js index d469c62..9b24f27 100644 --- a/src/core/textures/Spritesheet.js +++ b/src/core/textures/Spritesheet.js @@ -208,6 +208,7 @@ // lets also add the frame to pixi's global cache for fromFrame and fromImage functions TextureCache[i] = this.textures[i]; + this.textures[i].textureCacheId = i; } frameIndex++; diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 0a2c379..2213087 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -147,9 +147,9 @@ /** * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. * - * @event update - * @memberof PIXI.Texture# + * @event PIXI.Texture#update * @protected + * @param {PIXI.Texture} texture - Instance of texture being updated. */ this._updateID = 0; @@ -159,6 +159,15 @@ * @type {Object} */ this.transform = null; + + /** + * The id under which this Texture has been added to the texture cache. This is + * automatically set in certain cases, but may not always be accurate, particularly if + * the texture is in the cache under multiple ids. + * + * @member {string} + */ + this.textureCacheId = null; } /** @@ -225,11 +234,17 @@ this._uvs = null; this.trim = null; this.orig = null; + this.textureCacheId = null; this.valid = false; - this.off('dispose', this.dispose, this); - this.off('update', this.update, this); + for (const prop in TextureCache) + { + if (TextureCache[prop] === this) + { + delete TextureCache[prop]; + } + } } /** @@ -315,14 +330,15 @@ */ static fromLoader(source, imageUrl, name) { - // console.log('added from loader...') + // console.log('added from loader...') const resource = new ImageResource(source);//.from(imageUrl, crossorigin);// document.createElement('img'); - // console.log('base resource ' + resource.width); + // console.log('base resource ' + resource.width); const baseTexture = new BaseTexture(resource, settings.SCALE_MODE, getResolutionOfUrl(imageUrl)); + /// console.log('base width ' + baseTexture.width); const texture = new Texture(baseTexture); @@ -335,6 +351,7 @@ // lets also add the frame to pixi's global cache for fromFrame and fromImage fucntions BaseTextureCache[name] = baseTexture; TextureCache[name] = texture; + texture.textureCacheId = name; // also add references by url if they are different. if (name !== imageUrl) @@ -355,6 +372,10 @@ */ static addTextureToCache(texture, id) { + if (!texture.textureCacheId) + { + texture.textureCacheId = id; + } TextureCache[id] = texture; } @@ -495,6 +516,7 @@ */ Texture.EMPTY = new Texture(new BaseTexture()); removeAllHandlers(Texture.EMPTY); +removeAllHandlers(Texture.EMPTY.baseTexture); /** * A white texture of 10x10 size, used for graphics and other things @@ -505,3 +527,4 @@ */ Texture.WHITE = createWhiteTexture(); removeAllHandlers(Texture.WHITE); +removeAllHandlers(Texture.WHITE.baseTexture); diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index 59eb9fd..a9b9f51 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -1,6 +1,7 @@ import BaseTexture from './BaseTexture'; import { uid, BaseTextureCache } from '../utils'; -import * as ticker from '../ticker'; +import { shared } from '../ticker'; +import { UPDATE_PRIORITY } from '../const'; /** * A texture of a [playing] Video. @@ -125,7 +126,7 @@ if (!this._isAutoUpdating && this.autoUpdate) { - ticker.shared.add(this.update, this); + shared.add(this.update, this, UPDATE_PRIORITY.HIGH); this._isAutoUpdating = true; } } @@ -139,7 +140,7 @@ { if (this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); this._isAutoUpdating = false; } } @@ -187,7 +188,7 @@ { if (this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); } if (this.source && this.source._pixiId) @@ -281,12 +282,12 @@ if (!this._autoUpdate && this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); this._isAutoUpdating = false; } else if (this._autoUpdate && !this._isAutoUpdating) { - ticker.shared.add(this.update, this); + shared.add(this.update, this, UPDATE_PRIORITY.HIGH); this._isAutoUpdating = true; } } diff --git a/src/core/ticker/Ticker.js b/src/core/ticker/Ticker.js index 17a1517..40df138 100644 --- a/src/core/ticker/Ticker.js +++ b/src/core/ticker/Ticker.js @@ -1,12 +1,10 @@ import settings from '../settings'; -import EventEmitter from 'eventemitter3'; - -// Internal event used by composed emitter -const TICK = 'tick'; +import { UPDATE_PRIORITY } from '../const'; +import TickerListener from './TickerListener'; /** * A Ticker class that runs an update loop that other objects listen to. - * This class is composed around an EventEmitter object to add listeners + * This class is composed around listeners * meant for execution on the next requested animation frame. * Animation frames are requested only when necessary, * e.g. When the ticker is started and the emitter has listeners. @@ -22,10 +20,11 @@ constructor() { /** - * Internal emitter used to fire 'tick' event + * The first listener. All new listeners added are chained on this. * @private + * @type {TickerListener} */ - this._emitter = new EventEmitter(); + this._head = new TickerListener(null, null, Infinity); /** * Internal current frame request ID @@ -131,7 +130,7 @@ // Invoke listeners now this.update(time); // Listener side effects may have modified ticker state. - if (this.started && this._requestId === null && this._emitter.listeners(TICK, true)) + if (this.started && this._requestId === null && this._head.next) { this._requestId = requestAnimationFrame(this._tick); } @@ -148,7 +147,7 @@ */ _requestIfNeeded() { - if (this._requestId === null && this._emitter.listeners(TICK, true)) + if (this._requestId === null && this._head.next) { // ensure callbacks get correct delta this.lastTime = performance.now(); @@ -193,35 +192,72 @@ } /** - * Calls {@link module:eventemitter3.EventEmitter#on} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. + * Register a handler for tick events. Calls continuously unless + * it is removed or the ticker is stopped. * * @param {Function} fn - The listener function to be added for updates * @param {Function} [context] - The listener context + * @param {number} [priority=PIXI.UPDATE_PRIORITY.NORMAL] - The priority for emitting * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - add(fn, context) + add(fn, context, priority = UPDATE_PRIORITY.NORMAL) { - this._emitter.on(TICK, fn, context); - - this._startIfPossible(); - - return this; + return this._addListener(new TickerListener(fn, context, priority)); } /** - * Calls {@link module:eventemitter3.EventEmitter#once} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. + * Add a handler for the tick event which is only execute once. * * @param {Function} fn - The listener function to be added for one update * @param {Function} [context] - The listener context + * @param {number} [priority=PIXI.UPDATE_PRIORITY.NORMAL] - The priority for emitting * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - addOnce(fn, context) + addOnce(fn, context, priority = UPDATE_PRIORITY.NORMAL) { - this._emitter.once(TICK, fn, context); + return this._addListener(new TickerListener(fn, context, priority, true)); + } + + /** + * Internally adds the event handler so that it can be sorted by priority. + * Priority allows certain handler (user, AnimatedSprite, Interaction) to be run + * before the rendering. + * + * @private + * @param {TickerListener} listener - Current listener being added. + * @returns {PIXI.ticker.Ticker} This instance of a ticker + */ + _addListener(listener) + { + // For attaching to head + let current = this._head.next; + let previous = this._head; + + // Add the first item + if (!current) + { + listener.connect(previous); + } + else + { + // Go from highest to lowest priority + while (current) + { + if (listener.priority >= current.priority) + { + listener.connect(previous); + break; + } + previous = current; + current = current.next; + } + + // Not yet connected + if (!listener.previous) + { + listener.connect(previous); + } + } this._startIfPossible(); @@ -229,19 +265,33 @@ } /** - * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. - * It checks if the emitter has listeners for 'tick' event. - * If it does, then it cancels the animation frame. + * Removes any handlers matching the function and context parameters. + * If no handlers are left after removing, then it cancels the animation frame. * - * @param {Function} [fn] - The listener function to be removed + * @param {Function} fn - The listener function to be removed * @param {Function} [context] - The listener context to be removed * @returns {PIXI.ticker.Ticker} This instance of a ticker */ remove(fn, context) { - this._emitter.off(TICK, fn, context); + let listener = this._head.next; - if (!this._emitter.listeners(TICK, true)) + while (listener) + { + // We found a match, lets remove it + // no break to delete all possible matches + // incase a listener was added 2+ times + if (listener.match(fn, context)) + { + listener = listener.destroy(); + } + else + { + listener = listener.next; + } + } + + if (!this._head.next) { this._cancelIfNeeded(); } @@ -276,6 +326,25 @@ } /** + * Destroy the ticker and don't use after this. Calling + * this method removes all references to internal events. + */ + destroy() + { + this.stop(); + + let listener = this._head.next; + + while (listener) + { + listener = listener.destroy(true); + } + + this._head.destroy(); + this._head = null; + } + + /** * Triggers an update. An update entails setting the * current {@link PIXI.ticker.Ticker#elapsedMS}, * the current {@link PIXI.ticker.Ticker#deltaTime}, @@ -320,8 +389,22 @@ this.deltaTime = elapsedMS * settings.TARGET_FPMS * this.speed; + // Cache a local reference, in-case ticker is destroyed + // during the emit, we can still check for head.next + const head = this._head; + // Invoke listeners added to internal emitter - this._emitter.emit(TICK, this.deltaTime); + let listener = head.next; + + while (listener) + { + listener = listener.emit(this.deltaTime); + } + + if (!head.next) + { + this._cancelIfNeeded(); + } } else { diff --git a/src/core/ticker/TickerListener.js b/src/core/ticker/TickerListener.js new file mode 100644 index 0000000..2bedb34 --- /dev/null +++ b/src/core/ticker/TickerListener.js @@ -0,0 +1,158 @@ +/** + * Internal class for handling the priority sorting of ticker handlers. + * + * @private + * @class + * @memberof PIXI.ticker + */ +export default class TickerListener +{ + /** + * Constructor + * + * @param {Function} fn - The listener function to be added for one update + * @param {Function} [context=null] - The listener context + * @param {number} [priority=0] - The priority for emitting + * @param {boolean} [once=false] - If the handler should fire once + */ + constructor(fn, context = null, priority = 0, once = false) + { + /** + * The handler function to execute. + * @member {Function} + */ + this.fn = fn; + + /** + * The calling to execute. + * @member {Function} + */ + this.context = context; + + /** + * The current priority. + * @member {number} + */ + this.priority = priority; + + /** + * If this should only execute once. + * @member {boolean} + */ + this.once = once; + + /** + * The next item in chain. + * @member {TickerListener} + */ + this.next = null; + + /** + * The previous item in chain. + * @member {TickerListener} + */ + this.previous = null; + + /** + * `true` if this listener has been destroyed already. + * @member {boolean} + * @private + */ + this._destroyed = false; + } + + /** + * Simple compare function to figure out if a function and context match. + * + * @param {Function} fn - The listener function to be added for one update + * @param {Function} context - The listener context + * @return {boolean} `true` if the listener match the arguments + */ + match(fn, context) + { + context = context || null; + + return this.fn === fn && this.context === context; + } + + /** + * Emit by calling the current function. + * @param {number} deltaTime - time since the last emit. + * @return {TickerListener} Next ticker + */ + emit(deltaTime) + { + if (this.context) + { + this.fn.call(this.context, deltaTime); + } + else + { + this.fn(deltaTime); + } + + if (this.once) + { + this.destroy(); + } + + const redirect = this.next; + + // Soft-destroying should remove + // the next reference + if (this._destroyed) + { + this.next = null; + } + + return redirect; + } + + /** + * Connect to the list. + * @param {TickerListener} previous - Input node, previous listener + */ + connect(previous) + { + this.previous = previous; + if (previous.next) + { + previous.next.previous = this; + } + this.next = previous.next; + previous.next = this; + } + + /** + * Destroy and don't use after this. + * @param {boolean} [hard = false] `true` to remove the `next` reference, this + * is considered a hard destroy. Soft destroy maintains the next reference. + * @return {TickerListener} The listener to redirect while emitting or removing. + */ + destroy(hard = false) + { + this._destroyed = true; + this.fn = null; + this.context = null; + + // Disconnect, hook up next and previous + if (this.previous) + { + this.previous.next = this.next; + } + + if (this.next) + { + this.next.previous = this.previous; + } + + // Redirect to the next item + const redirect = this.previous; + + // Remove references + this.next = hard ? null : redirect; + this.previous = null; + + return redirect; + } +} diff --git a/src/core/ticker/index.js b/src/core/ticker/index.js index 4b3017c..c3020d4 100644 --- a/src/core/ticker/index.js +++ b/src/core/ticker/index.js @@ -45,8 +45,25 @@ const shared = new Ticker(); shared.autoStart = true; +shared.destroy = () => +{ + // protect destroying shared ticker + // this is used by other internal systems + // like AnimatedSprite and InteractionManager +}; /** + * This namespace contains an API for interacting with PIXI's internal global update loop. + * + * This ticker is used for rendering, {@link PIXI.extras.AnimatedSprite AnimatedSprite}, + * {@link PIXI.interaction.InteractionManager InteractionManager} and many other time-based PIXI systems. + * @example + * const ticker = new PIXI.ticker.Ticker(); + * ticker.stop(); + * ticker.add((deltaTime) => { + * // do something every frame + * }); + * ticker.start(); * @namespace PIXI.ticker */ export { shared, Ticker }; diff --git a/src/core/utils/index.js b/src/core/utils/index.js index be44750..7441f7b 100644 --- a/src/core/utils/index.js +++ b/src/core/utils/index.js @@ -9,6 +9,21 @@ let saidHello = false; /** + * Generalized convenience utilities for PIXI. + * @example + * // Extend PIXI's internal Event Emitter. + * class MyEmitter extends PIXI.utils.EventEmitter { + * constructor() { + * super(); + * console.log("Emitter created!"); + * } + * } + * + * // Get info on current device + * console.log(PIXI.utils.isMobile); + * + * // Convert hex color to string + * console.log(PIXI.utils.hex2string(0xff00ff)); // returns: "#ff00ff" * @namespace PIXI.utils */ export { diff --git a/src/deprecation.js b/src/deprecation.js index b9c73b3..33daeec 100644 --- a/src/deprecation.js +++ b/src/deprecation.js @@ -918,6 +918,35 @@ }); /** + * @method + * @name PIXI.prepare.BasePrepare#register + * @see PIXI.prepare.BasePrepare#registerFindHook + * @deprecated since version 4.4.2 + * @param {Function} [addHook] - Function call that takes two parameters: `item:*, queue:Array` + * function must return `true` if it was able to add item to the queue. + * @param {Function} [uploadHook] - Function call that takes two parameters: `prepare:CanvasPrepare, item:*` and + * function must return `true` if it was able to handle upload of item. + * @return {PIXI.BasePrepare} Instance of plugin for chaining. + */ +prepare.BasePrepare.prototype.register = function register(addHook, uploadHook) +{ + warn('renderer.plugins.prepare.register is now deprecated, ' + + 'please use renderer.plugins.prepare.registerFindHook & renderer.plugins.prepare.registerUploadHook'); + + if (addHook) + { + this.registerFindHook(addHook); + } + + if (uploadHook) + { + this.registerUploadHook(uploadHook); + } + + return this; +}; + +/** * The number of graphics or textures to upload to the GPU. * * @name PIXI.prepare.canvas.UPLOADS_PER_FRAME @@ -969,67 +998,73 @@ }, }); -Object.defineProperties(loaders.Resource.prototype, { - isJson: { - get() - { - warn('The isJson property is deprecated, please use `resource.type === Resource.TYPE.JSON`.'); +if (loaders.Loader) +{ + const Resource = loaders.Resource; + const Loader = loaders.Loader; - return this.type === loaders.Loader.Resource.TYPE.JSON; + Object.defineProperties(Resource.prototype, { + isJson: { + get() + { + warn('The isJson property is deprecated, please use `resource.type === Resource.TYPE.JSON`.'); + + return this.type === Resource.TYPE.JSON; + }, }, - }, - isXml: { - get() - { - warn('The isXml property is deprecated, please use `resource.type === Resource.TYPE.XML`.'); + isXml: { + get() + { + warn('The isXml property is deprecated, please use `resource.type === Resource.TYPE.XML`.'); - return this.type === loaders.Loader.Resource.TYPE.XML; + return this.type === Resource.TYPE.XML; + }, }, - }, - isImage: { - get() - { - warn('The isImage property is deprecated, please use `resource.type === Resource.TYPE.IMAGE`.'); + isImage: { + get() + { + warn('The isImage property is deprecated, please use `resource.type === Resource.TYPE.IMAGE`.'); - return this.type === loaders.Loader.Resource.TYPE.IMAGE; + return this.type === Resource.TYPE.IMAGE; + }, }, - }, - isAudio: { - get() - { - warn('The isAudio property is deprecated, please use `resource.type === Resource.TYPE.AUDIO`.'); + isAudio: { + get() + { + warn('The isAudio property is deprecated, please use `resource.type === Resource.TYPE.AUDIO`.'); - return this.type === loaders.Loader.Resource.TYPE.AUDIO; + return this.type === Resource.TYPE.AUDIO; + }, }, - }, - isVideo: { - get() - { - warn('The isVideo property is deprecated, please use `resource.type === Resource.TYPE.VIDEO`.'); + isVideo: { + get() + { + warn('The isVideo property is deprecated, please use `resource.type === Resource.TYPE.VIDEO`.'); - return this.type === loaders.Loader.Resource.TYPE.VIDEO; + return this.type === Resource.TYPE.VIDEO; + }, }, - }, -}); + }); -Object.defineProperties(loaders.Loader.prototype, { - before: { - get() - { - warn('The before() method is deprecated, please use pre().'); + Object.defineProperties(Loader.prototype, { + before: { + get() + { + warn('The before() method is deprecated, please use pre().'); - return this.pre; + return this.pre; + }, }, - }, - after: { - get() - { - warn('The after() method is deprecated, please use use().'); + after: { + get() + { + warn('The after() method is deprecated, please use use().'); - return this.use; + return this.use; + }, }, - }, -}); + }); +} /** * @name PIXI.interaction.interactiveTarget#defaultCursor diff --git a/src/extract/canvas/CanvasExtract.js b/src/extract/canvas/CanvasExtract.js index 941ca05..3ac5aee 100644 --- a/src/extract/canvas/CanvasExtract.js +++ b/src/extract/canvas/CanvasExtract.js @@ -8,7 +8,7 @@ * An instance of this class is automatically created by default, and can be found at renderer.plugins.extract * * @class - * @memberof PIXI + * @memberof PIXI.extract */ export default class CanvasExtract { @@ -21,9 +21,9 @@ /** * Collection of methods for extracting data (image, pixels, etc.) from a display object or render texture * - * @member {PIXI.CanvasExtract} extract + * @member {PIXI.extract.CanvasExtract} extract * @memberof PIXI.CanvasRenderer# - * @see PIXI.CanvasExtract + * @see PIXI.extract.CanvasExtract */ renderer.extract = this; } diff --git a/src/extract/index.js b/src/extract/index.js index 0f2170d..e9478c1 100644 --- a/src/extract/index.js +++ b/src/extract/index.js @@ -1,2 +1,22 @@ +/** + * This namespace provides renderer-specific plugins for exporting content from a renderer. + * For instance, these plugins can be used for saving an Image, Canvas element or for exporting the raw image data (pixels). + * + * Do not instantiate these plugins directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. + * @example + * // Create a new app (will auto-add extract plugin to renderer) + * const app = new PIXI.Application(); + * + * // Draw a red circle + * const graphics = new PIXI.Graphics() + * .beginFill(0xFF0000) + * .drawCircle(0, 0, 50); + * + * // Render the graphics as an HTMLImageElement + * const image = app.renderer.plugins.extract.image(graphics); + * document.body.appendChild(image); + * @namespace PIXI.extract + */ export { default as webgl } from './webgl/WebGLExtract'; export { default as canvas } from './canvas/CanvasExtract'; diff --git a/src/extract/webgl/WebGLExtract.js b/src/extract/webgl/WebGLExtract.js index 60228bb..1c3dd30 100644 --- a/src/extract/webgl/WebGLExtract.js +++ b/src/extract/webgl/WebGLExtract.js @@ -9,7 +9,7 @@ * An instance of this class is automatically created by default, and can be found at renderer.plugins.extract * * @class - * @memberof PIXI + * @memberof PIXI.extract */ export default class WebGLExtract { @@ -22,9 +22,9 @@ /** * Collection of methods for extracting data (image, pixels, etc.) from a display object or render texture * - * @member {PIXI.WebGLExtract} extract + * @member {PIXI.extract.WebGLExtract} extract * @memberof PIXI.WebGLRenderer# - * @see PIXI.WebGLExtract + * @see PIXI.extract.WebGLExtract */ renderer.extract = this; } diff --git a/src/extras/AnimatedSprite.js b/src/extras/AnimatedSprite.js index 13b804f..5297778 100644 --- a/src/extras/AnimatedSprite.js +++ b/src/extras/AnimatedSprite.js @@ -137,7 +137,7 @@ this.playing = true; if (this._autoUpdate) { - core.ticker.shared.add(this.update, this); + core.ticker.shared.add(this.update, this, core.UPDATE_PRIORITY.HIGH); } } diff --git a/src/extras/TextureTransform.js b/src/extras/TextureTransform.js index 78e6e40..16536d2 100644 --- a/src/extras/TextureTransform.js +++ b/src/extras/TextureTransform.js @@ -65,8 +65,36 @@ } /** + * Multiplies uvs array to transform + * @param {Float32Array} uvs mesh uvs + * @param {Float32Array} [out=uvs] output + * @returns {Float32Array} output + */ + multiplyUvs(uvs, out) + { + if (out === undefined) + { + out = uvs; + } + + const mat = this.mapCoord; + + for (let i = 0; i < uvs.length; i += 2) + { + const x = uvs[i]; + const y = uvs[i + 1]; + + out[i] = (x * mat.a) + (y * mat.c) + mat.tx; + out[i + 1] = (x * mat.b) + (y * mat.d) + mat.ty; + } + + return out; + } + + /** * updates matrices if texture was changed * @param {boolean} forceUpdate if true, matrices will be updated any case + * @returns {boolean} whether or not it was updated */ update(forceUpdate) { @@ -74,13 +102,13 @@ if (!tex || !tex.valid) { - return; + return false; } if (!forceUpdate && this._lastTextureID === tex._updateID) { - return; + return false; } this._lastTextureID = tex._updateID; @@ -110,5 +138,7 @@ frame[3] = (tex._frame.y + tex._frame.height - margin + offset) / texBase.height; this.uClampOffset[0] = offset / texBase.realWidth; this.uClampOffset[1] = offset / texBase.realHeight; + + return true; } } diff --git a/src/extras/TilingSprite.js b/src/extras/TilingSprite.js index 1f6014f..a3f027f 100644 --- a/src/extras/TilingSprite.js +++ b/src/extras/TilingSprite.js @@ -180,9 +180,9 @@ const transform = this.worldTransform; const resolution = renderer.resolution; const baseTexture = texture.baseTexture; - const baseTextureResolution = texture.baseTexture.resolution; - const modX = (this.tilePosition.x / this.tileScale.x) % texture._frame.width; - const modY = (this.tilePosition.y / this.tileScale.y) % texture._frame.height; + const baseTextureResolution = baseTexture.resolution; + const modX = ((this.tilePosition.x / this.tileScale.x) % texture._frame.width) * baseTextureResolution; + const modY = ((this.tilePosition.y / this.tileScale.y) % texture._frame.height) * baseTextureResolution; // create a nice shiny pattern! // TODO this needs to be refreshed if texture changes.. diff --git a/src/extras/cacheAsBitmap.js b/src/extras/cacheAsBitmap.js index 5dd5640..aea06a8 100644 --- a/src/extras/cacheAsBitmap.js +++ b/src/extras/cacheAsBitmap.js @@ -227,7 +227,16 @@ this.transform._parentID = -1; // restore the transform of the cached sprite to avoid the nasty flicker.. - this.updateTransform(); + if (!this.parent) + { + this.parent = renderer._tempDisplayObjectParent; + this.updateTransform(); + this.parent = null; + } + else + { + this.updateTransform(); + } // map the hit test.. this.containsPoint = cachedSprite.containsPoint.bind(cachedSprite); @@ -314,7 +323,17 @@ cachedSprite._bounds = this._bounds; cachedSprite.alpha = cacheAlpha; - this.updateTransform(); + if (!this.parent) + { + this.parent = renderer._tempDisplayObjectParent; + this.updateTransform(); + this.parent = null; + } + else + { + this.updateTransform(); + } + this.updateTransform = this.displayObjectUpdateTransform; this._cacheData.sprite = cachedSprite; @@ -358,9 +377,12 @@ * Destroys the cached object. * * @private + * @param {object|boolean} [options] - Options parameter. A boolean will act as if all options + * have been set to that value. + * Used when destroying containers, see the Container.destroy method. */ -DisplayObject.prototype._cacheAsBitmapDestroy = function _cacheAsBitmapDestroy() +DisplayObject.prototype._cacheAsBitmapDestroy = function _cacheAsBitmapDestroy(options) { this.cacheAsBitmap = false; - this.destroy(); + this.destroy(options); }; diff --git a/src/extras/index.js b/src/extras/index.js index bbd8bbf..a34d7ec 100644 --- a/src/extras/index.js +++ b/src/extras/index.js @@ -1,7 +1,7 @@ /** + * Additional PIXI DisplayObjects for animation, tiling and bitmap text. * @namespace PIXI.extras */ -export { default as TextureTransform } from './TextureTransform'; export { default as AnimatedSprite } from './AnimatedSprite'; export { default as TilingSprite } from './TilingSprite'; export { default as TilingSpriteRenderer } from './webgl/TilingSpriteRenderer'; diff --git a/src/extras/webgl/TilingSpriteRenderer.js b/src/extras/webgl/TilingSpriteRenderer.js index aeec172..eee3b5c 100644 --- a/src/extras/webgl/TilingSpriteRenderer.js +++ b/src/extras/webgl/TilingSpriteRenderer.js @@ -11,7 +11,7 @@ * WebGL renderer plugin for tiling sprites * * @class - * @memberof PIXI + * @memberof PIXI.extras * @extends PIXI.ObjectRenderer */ export default class TilingSpriteRenderer extends core.ObjectRenderer @@ -118,7 +118,7 @@ tempMat.invert(); if (isSimple) { - tempMat.append(uv.mapCoord); + tempMat.prepend(uv.mapCoord); } else { diff --git a/src/filters/displacement/DisplacementFilter.js b/src/filters/displacement/DisplacementFilter.js index d5355c3..b5d030c 100644 --- a/src/filters/displacement/DisplacementFilter.js +++ b/src/filters/displacement/DisplacementFilter.js @@ -35,7 +35,7 @@ this.maskSprite = sprite; this.maskMatrix = maskMatrix; - this.uniforms.mapSampler = sprite.texture; + this.uniforms.mapSampler = sprite._texture; this.uniforms.filterMatrix = maskMatrix; this.uniforms.scale = { x: 1, y: 1 }; diff --git a/src/filters/index.js b/src/filters/index.js index f3e8862..7ceb50a 100644 --- a/src/filters/index.js +++ b/src/filters/index.js @@ -1,4 +1,21 @@ /** + * This namespace contains WebGL-only display filters that can be applied + * to DisplayObjects using the {@link PIXI.DisplayObject#filters filters} property. + * @example + * // Create a new application + * const app = new PIXI.Application(); + * + * // Draw a green rectangle + * const rect = new PIXI.Graphics() + * .beginFill(0x00ff00) + * .drawRect(40, 40, 200, 200); + * + * // Add a blur filter + * rect.filters = [new PIXI.filters.BlurFilter()]; + * + * // Display rectangle + * app.stage.addChild(rect); + * document.body.appendChild(app.view); * @namespace PIXI.filters */ export { default as FXAAFilter } from './fxaa/FXAAFilter'; diff --git a/src/index.js b/src/index.js index c553427..77da490 100644 --- a/src/index.js +++ b/src/index.js @@ -20,6 +20,14 @@ import { utils } from './core'; utils.mixins.performMixins(); +/** + * Alias for {@link PIXI.loaders.shared}. + * @name loader + * @memberof PIXI + * @type {PIXI.loader.Loader} + */ +const loader = loaders.shared || null; + export { accessibility, extract, @@ -30,18 +38,8 @@ mesh, particles, prepare, + loader, }; -/** - * A premade instance of the loader that can be used to load resources. - * - * @name loader - * @memberof PIXI - * @property {PIXI.loaders.Loader} - */ -const loader = loaders && loaders.Loader ? new loaders.Loader() : null; // check is there in case user excludes loader lib - -export { loader }; - // Always export pixi globally. global.PIXI = exports; // eslint-disable-line diff --git a/src/interaction/InteractionData.js b/src/interaction/InteractionData.js index 02d53a3..449f8cc 100644 --- a/src/interaction/InteractionData.js +++ b/src/interaction/InteractionData.js @@ -30,7 +30,10 @@ /** * When passed to an event handler, this will be the original DOM Event that was captured * - * @member {Event} + * @see https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent + * @see https://developer.mozilla.org/en-US/docs/Web/API/TouchEvent + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent + * @member {MouseEvent|TouchEvent|PointerEvent} */ this.originalEvent = null; } diff --git a/src/interaction/InteractionManager.js b/src/interaction/InteractionManager.js index 8baf13a..2d75c7c 100644 --- a/src/interaction/InteractionManager.js +++ b/src/interaction/InteractionManager.js @@ -13,6 +13,14 @@ const MOUSE_POINTER_ID = 'MOUSE'; +// helpers for hitTest() - only used inside hitTest() +const hitTestEvent = { + target: null, + data: { + global: null, + }, +}; + /** * The interaction manager deals with mouse, touch and pointer events. Any DisplayObject can be interactive * if its interactive parameter is set to true @@ -245,67 +253,60 @@ this.setTargetElement(this.renderer.view, this.renderer.resolution); /** - * Fired when a pointer device button (usually a mouse button) is pressed on the display + * Fired when a pointer device button (usually a mouse left-button) is pressed on the display * object. * - * @event mousedown - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mousedown + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device secondary button (usually a mouse right-button) is pressed * on the display object. * - * @event rightdown - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightdown + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** - * Fired when a pointer device button (usually a mouse button) is released over the display + * Fired when a pointer device button (usually a mouse left-button) is released over the display * object. * - * @event mouseup - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseup + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device secondary button (usually a mouse right-button) is released * over the display object. * - * @event rightup - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightup + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** - * Fired when a pointer device button (usually a mouse button) is pressed and released on + * Fired when a pointer device button (usually a mouse left-button) is pressed and released on * the display object. * - * @event click - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#click + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device secondary button (usually a mouse right-button) is pressed * and released on the display object. * - * @event rightclick - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightclick + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** - * Fired when a pointer device button (usually a mouse button) is released outside the + * Fired when a pointer device button (usually a mouse left-button) is released outside the * display object that initially registered a * [mousedown]{@link PIXI.interaction.InteractionManager#event:mousedown}. * - * @event mouseupoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** @@ -313,146 +314,362 @@ * outside the display object that initially registered a * [rightdown]{@link PIXI.interaction.InteractionManager#event:rightdown}. * - * @event rightupoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device (usually a mouse) is moved while over the display object * - * @event mousemove - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mousemove + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device (usually a mouse) is moved onto the display object * - * @event mouseover - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseover + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device (usually a mouse) is moved off the display object * - * @event mouseout - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseout + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is pressed on the display object. * - * @event pointerdown - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerdown + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is released over the display object. * - * @event pointerup - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerup + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when the operating system cancels a pointer event * - * @event pointercancel - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointercancel + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is pressed and released on the display object. * - * @event pointertap - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointertap + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is released outside the display object that initially * registered a [pointerdown]{@link PIXI.interaction.InteractionManager#event:pointerdown}. * - * @event pointerupoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device is moved while over the display object * - * @event pointermove - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointermove + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device is moved onto the display object * - * @event pointerover - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerover + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device is moved off the display object * - * @event pointerout - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerout + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is placed on the display object. * - * @event touchstart - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchstart + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is removed from the display object. * - * @event touchend - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchend + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when the operating system cancels a touch * - * @event touchcancel - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchcancel + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is placed and removed from the display object. * - * @event tap - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#tap + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is removed outside of the display object that initially * registered a [touchstart]{@link PIXI.interaction.InteractionManager#event:touchstart}. * - * @event touchendoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchendoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is moved along the display object. * - * @event touchmove - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchmove + * @param {PIXI.interaction.InteractionData} event - Interaction data */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is pressed on the display. + * object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mousedown + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is pressed + * on the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightdown + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is released over the display + * object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseup + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is released + * over the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightup + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is pressed and released on + * the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#click + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is pressed + * and released on the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightclick + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is released outside the + * display object that initially registered a + * [mousedown]{@link PIXI.DisplayObject#event:mousedown}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is released + * outside the display object that initially registered a + * [rightdown]{@link PIXI.DisplayObject#event:rightdown}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device (usually a mouse) is moved while over the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mousemove + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device (usually a mouse) is moved onto the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseover + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device (usually a mouse) is moved off the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseout + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is pressed on the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerdown + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is released over the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerup + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when the operating system cancels a pointer event. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointercancel + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is pressed and released on the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointertap + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is released outside the display object that initially + * registered a [pointerdown]{@link PIXI.DisplayObject#event:pointerdown}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device is moved while over the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointermove + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device is moved onto the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerover + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device is moved off the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerout + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is placed on the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchstart + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is removed from the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchend + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when the operating system cancels a touch. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchcancel + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is placed and removed from the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#tap + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is removed outside of the display object that initially + * registered a [touchstart]{@link PIXI.DisplayObject#event:touchstart}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchendoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is moved along the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchmove + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + } + + /** + * Hit tests a point against the display tree, returning the first interactive object that is hit. + * + * @param {PIXI.Point} globalPoint - A point to hit test with, in global space. + * @param {PIXI.Container} [root] - The root display object to start from. If omitted, defaults + * to the last rendered root of the associated renderer. + * @return {PIXI.DisplayObject} The hit display object, if any. + */ + hitTest(globalPoint, root) + { + // clear the target for our hit test + hitTestEvent.target = null; + // assign the global point + hitTestEvent.data.global = globalPoint; + // ensure safety of the root + if (!root) + { + root = this.renderer._lastObjectRendered; + } + // run the hit test + this.processInteractive(hitTestEvent, root, null, true); + // return our found object - it'll be null if we didn't hit anything + + return hitTestEvent.target; } /** @@ -487,7 +704,7 @@ return; } - core.ticker.shared.add(this.update, this); + core.ticker.shared.add(this.update, this, core.UPDATE_PRIORITY.INTERACTION); if (window.navigator.msPointerEnabled) { @@ -804,8 +1021,6 @@ } } - let keepHitTestingAfterChildren = hitTest; - // ** FREE TIP **! If an object is not interactive or has no buttons in it // (such as a game scene!) set interactiveChildren to false for that displayObject. // This will allow pixi to completely ignore and bypass checking the displayObjects children. @@ -818,7 +1033,9 @@ const child = children[i]; // time to get recursive.. if this function will return if something is hit.. - if (this.processInteractive(interactionEvent, child, func, hitTest, interactiveParent)) + const childHit = this.processInteractive(interactionEvent, child, func, hitTest, interactiveParent); + + if (childHit) { // its a good idea to check if a child has lost its parent. // this means it has been removed whilst looping so its best @@ -827,8 +1044,6 @@ continue; } - hit = true; - // we no longer need to hit test any more objects in this container as we we // now know the parent has been hit interactiveParent = false; @@ -838,36 +1053,41 @@ // This means we no longer need to hit test anything else. We still need to run // through all objects, but we don't need to perform any hit tests. - keepHitTestingAfterChildren = false; - - if (child.interactive) + if (childHit) { - hitTest = false; + if (interactionEvent.target) + { + hitTest = false; + } + hit = true; } - - // we can break now as we have hit an object. } } } - hitTest = keepHitTestingAfterChildren; - // no point running this if the item is not interactive or does not have an interactive parent. if (interactive) { // if we are hit testing (as in we have no hit any objects yet) // We also don't need to worry about hit testing if once of the displayObjects children - // has already been hit! - if (hitTest && !hit) + // has already been hit - but only if it was interactive, otherwise we need to keep + // looking for an interactive child, just in case we hit one + if (hitTest && !interactionEvent.target) { if (displayObject.hitArea) { displayObject.worldTransform.applyInverse(point, this._tempPoint); - hit = displayObject.hitArea.contains(this._tempPoint.x, this._tempPoint.y); + if (displayObject.hitArea.contains(this._tempPoint.x, this._tempPoint.y)) + { + hit = true; + } } else if (displayObject.containsPoint) { - hit = displayObject.containsPoint(point); + if (displayObject.containsPoint(point)) + { + hit = true; + } } } @@ -878,7 +1098,10 @@ interactionEvent.target = displayObject; } - func(interactionEvent, displayObject, hit); + if (func) + { + func(interactionEvent, displayObject, !!hit); + } } } @@ -914,7 +1137,7 @@ { const event = events[i]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -998,7 +1221,7 @@ { const event = events[i]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1098,7 +1321,7 @@ const test = isRightButton ? flags.RIGHT_DOWN : flags.LEFT_DOWN; - const isDown = trackingData !== undefined && (trackingData.flags | test); + const isDown = trackingData !== undefined && (trackingData.flags & test); if (hit) { @@ -1118,11 +1341,11 @@ { if (isRightButton) { - trackingData.rightDown = hit; + trackingData.rightDown = false; } else { - trackingData.leftDown = hit; + trackingData.leftDown = false; } } } @@ -1180,7 +1403,7 @@ { const event = events[i]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1255,7 +1478,7 @@ this.setCursorMode(null); } - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1268,6 +1491,12 @@ { this.emit('mouseout', interactionEvent); } + else + { + // we can get touchleave events after touchend, so we want to make sure we don't + // introduce memory leaks + this.releaseInteractionDataForPointerId(interactionData.identifier); + } } /** @@ -1344,7 +1573,7 @@ // Only mouse and pointer can call onPointerOver, so events will always be length 1 const event = events[0]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1366,12 +1595,14 @@ * Get InteractionData for a given pointerId. Store that data as well * * @private - * @param {number} pointerId - Identifier from a pointer event + * @param {PointerEvent} event - Normalized pointer event, output from normalizeToPointerData * @return {InteractionData} - Interaction data for the given pointer identifier */ - getInteractionDataForPointerId(pointerId) + getInteractionDataForPointerId(event) { - if (pointerId === MOUSE_POINTER_ID) + const pointerId = event.pointerId; + + if (pointerId === MOUSE_POINTER_ID || event.pointerType === 'mouse') { return this.mouse; } diff --git a/src/interaction/InteractionTrackingData.js b/src/interaction/InteractionTrackingData.js index 6a1f69e..8493b63 100644 --- a/src/interaction/InteractionTrackingData.js +++ b/src/interaction/InteractionTrackingData.js @@ -35,8 +35,10 @@ } /** + * Unique pointer id of the event + * * @readonly - * @type {number} Unique pointer id of the event + * @member {number} */ get pointerId() { diff --git a/src/interaction/index.js b/src/interaction/index.js index 56c5b09..72f7d71 100644 --- a/src/interaction/index.js +++ b/src/interaction/index.js @@ -1,4 +1,8 @@ /** + * This namespace contains a renderer plugin for handling mouse, pointer, and touch events. + * + * Do not instantiate this plugin directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. * @namespace PIXI.interaction */ export { default as InteractionData } from './InteractionData'; diff --git a/src/interaction/interactiveTarget.js b/src/interaction/interactiveTarget.js index d252fba..cebac40 100644 --- a/src/interaction/interactiveTarget.js +++ b/src/interaction/interactiveTarget.js @@ -2,7 +2,7 @@ * Default property values of interactive objects * Used by {@link PIXI.interaction.InteractionManager} to automatically give all DisplayObjects these properties * - * @mixin + * @private * @name interactiveTarget * @memberof PIXI.interaction * @example @@ -14,18 +14,28 @@ * ); */ export default { + /** - * Determines if the displayObject be clicked/touched + * Enable interaction events for the DisplayObject. Touch, pointer and mouse + * events will not be emitted unless `interactive` is set to `true`. * - * @inner {boolean} + * @example + * const sprite = new PIXI.Sprite(texture); + * sprite.interactive = true; + * sprite.on('tap', (event) => { + * //handle event + * }); + * @member {boolean} + * @memberof PIXI.DisplayObject# */ interactive: false, /** * Determines if the children to the displayObject can be clicked/touched - * Setting this to false allows pixi to bypass a recursive hitTest function + * Setting this to false allows pixi to bypass a recursive `hitTest` function * - * @inner {boolean} + * @member {boolean} + * @memberof PIXI.Container# */ interactiveChildren: true, @@ -33,7 +43,12 @@ * Interaction shape. Children will be hit first, then this shape will be checked. * Setting this will cause this shape to be checked in hit tests rather than the displayObject's bounds. * - * @inner {PIXI.Rectangle|PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.RoundedRectangle} + * @example + * const sprite = new PIXI.Sprite(texture); + * sprite.interactive = true; + * sprite.hitArea = new PIXI.Rectangle(0, 0, 100, 100); + * @member {PIXI.Rectangle|PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.RoundedRectangle} + * @memberof PIXI.DisplayObject# */ hitArea: null, @@ -41,8 +56,12 @@ * If enabled, the mouse cursor use the pointer behavior when hovered over the displayObject if it is interactive * Setting this changes the 'cursor' property to `'pointer'`. * + * @example + * const sprite = new PIXI.Sprite(texture); + * sprite.interactive = true; + * sprite.buttonMode = true; * @member {boolean} - * @memberof PIXI.interaction.interactiveTarget# + * @memberof PIXI.DisplayObject# */ get buttonMode() { @@ -64,9 +83,14 @@ * This defines what cursor mode is used when the mouse cursor * is hovered over the displayObject. * + * @example + * const sprite = new PIXI.Sprite(texture); + * sprite.interactive = true; + * sprite.cursor = 'wait'; * @see https://developer.mozilla.org/en/docs/Web/CSS/cursor * - * @inner {string} + * @member {string} + * @memberof PIXI.DisplayObject# */ cursor: null, @@ -74,7 +98,7 @@ * Internal set of all active pointers, by identifier * * @member {Map} - * @memberof PIXI.interaction.interactiveTarget# + * @memberof PIXI.DisplayObject# * @private */ get trackedPointers() @@ -87,7 +111,8 @@ /** * Map of all tracked pointers, by identifier. Use trackedPointers to access. * - * @private {Map} + * @private + * @type {Map} */ _trackedPointers: undefined, }; diff --git a/src/loaders/index.js b/src/loaders/index.js index 5189793..daa1fc4 100644 --- a/src/loaders/index.js +++ b/src/loaders/index.js @@ -1,9 +1,22 @@ +import Application from '../core/Application'; +import Loader from './loader'; + /** + * This namespace contains APIs which extends the {@link https://github.com/englercj/resource-loader resource-loader} module + * for loading assets, data, and other resources dynamically. + * @example + * const loader = new PIXI.loaders.Loader(); + * loader.add('bunny', 'data/bunny.png') + * .add('spaceship', 'assets/spritesheet.json'); + * loader.load((loader, resources) => { + * // resources.bunny + * // resources.spaceship + * }); * @namespace PIXI.loaders */ -export { default as Loader } from './loader'; +export { Loader }; export { default as bitmapFontParser, parse as parseBitmapFontData } from './bitmapFontParser'; -export { default as spritesheetParser } from './spritesheetParser'; +export { default as spritesheetParser, getResourcePath } from './spritesheetParser'; export { default as textureParser } from './textureParser'; /** @@ -13,3 +26,55 @@ * @memberof PIXI.loaders */ export { Resource } from 'resource-loader'; + +/** + * A premade instance of the loader that can be used to load resources. + * @name shared + * @memberof PIXI.loaders + * @type {PIXI.loaders.Loader} + */ +const shared = new Loader(); + +shared.destroy = () => +{ + // protect destroying shared loader +}; + +export { shared }; + +// Mixin the loader construction +const AppPrototype = Application.prototype; + +AppPrototype._loader = null; + +/** + * Loader instance to help with asset loading. + * @name PIXI.Application#loader + * @type {PIXI.loaders.Loader} + */ +Object.defineProperty(AppPrototype, 'loader', { + get() + { + if (!this._loader) + { + const sharedLoader = this._options.sharedLoader; + + this._loader = sharedLoader ? shared : new Loader(); + } + + return this._loader; + }, +}); + +// Override the destroy function +// making sure to destroy the current Loader +AppPrototype._parentDestroy = AppPrototype.destroy; +AppPrototype.destroy = function destroy() +{ + if (this._loader) + { + this._loader.destroy(); + this._loader = null; + } + this._parentDestroy(); +}; diff --git a/src/loaders/loader.js b/src/loaders/loader.js index 767a411..59b8000 100644 --- a/src/loaders/loader.js +++ b/src/loaders/loader.js @@ -90,6 +90,15 @@ { Loader._pixiMiddleware.push(fn); } + + /** + * Destroy the loader, removes references. + */ + destroy() + { + this.removeAllListeners(); + this.reset(); + } } // Copy EE3 prototype (mixin) diff --git a/src/loaders/spritesheetParser.js b/src/loaders/spritesheetParser.js index e57dfb2..eda9584 100644 --- a/src/loaders/spritesheetParser.js +++ b/src/loaders/spritesheetParser.js @@ -1,12 +1,11 @@ import { Resource } from 'resource-loader'; -import path from 'path'; +import url from 'url'; import { Spritesheet } from '../core'; export default function () { return function spritesheetParser(resource, next) { - let resourcePath; const imageResourceName = `${resource.name}_image`; // skip if no data, its not json, it isn't spritesheet data, or the image resource already exists @@ -28,15 +27,7 @@ parentResource: resource, }; - // Prepend url path unless the resource image is a data url - if (resource.isDataUrl) - { - resourcePath = resource.data.meta.image; - } - else - { - resourcePath = `${path.dirname(resource.url.replace(this.baseUrl, ''))}/${resource.data.meta.image}`; - } + const resourcePath = getResourcePath(resource, this.baseUrl); // load the image for this sheet this.add(imageResourceName, resourcePath, loadOptions, function onImageLoad(res) @@ -56,3 +47,14 @@ }); }; } + +export function getResourcePath(resource, baseUrl) +{ + // Prepend url path unless the resource image is a data url + if (resource.isDataUrl) + { + return resource.data.meta.image; + } + + return url.resolve(resource.url.replace(baseUrl, ''), resource.data.meta.image); +} diff --git a/src/mesh/canvas/CanvasMeshRenderer.js b/src/mesh/canvas/CanvasMeshRenderer.js index 297205f..f7899e1 100644 --- a/src/mesh/canvas/CanvasMeshRenderer.js +++ b/src/mesh/canvas/CanvasMeshRenderer.js @@ -134,12 +134,33 @@ const textureWidth = base.width; const textureHeight = base.height; - const u0 = uvs[index0] * base.width; - const u1 = uvs[index1] * base.width; - const u2 = uvs[index2] * base.width; - const v0 = uvs[index0 + 1] * base.height; - const v1 = uvs[index1 + 1] * base.height; - const v2 = uvs[index2 + 1] * base.height; + let u0; + let u1; + let u2; + let v0; + let v1; + let v2; + + if (mesh.uploadUvTransform) + { + const ut = mesh._uvTransform.mapCoord; + + u0 = ((uvs[index0] * ut.a) + (uvs[index0 + 1] * ut.c) + ut.tx) * base.width; + u1 = ((uvs[index1] * ut.a) + (uvs[index1 + 1] * ut.c) + ut.tx) * base.width; + u2 = ((uvs[index2] * ut.a) + (uvs[index2 + 1] * ut.c) + ut.tx) * base.width; + v0 = ((uvs[index0] * ut.b) + (uvs[index0 + 1] * ut.d) + ut.ty) * base.height; + v1 = ((uvs[index1] * ut.b) + (uvs[index1 + 1] * ut.d) + ut.ty) * base.height; + v2 = ((uvs[index2] * ut.b) + (uvs[index2 + 1] * ut.d) + ut.ty) * base.height; + } + else + { + u0 = uvs[index0] * base.width; + u1 = uvs[index1] * base.width; + u2 = uvs[index2] * base.width; + v0 = uvs[index0 + 1] * base.height; + v1 = uvs[index1 + 1] * base.height; + v2 = uvs[index2 + 1] * base.height; + } let x0 = vertices[index0]; let x1 = vertices[index1]; diff --git a/.travis.yml b/.travis.yml index 3df8b22..14de334 100644 --- a/.travis.yml +++ b/.travis.yml @@ -110,3 +110,43 @@ on: all_branches: true condition: $TRAVIS_TAG + # Deploy config for latest release + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: dist + upload-dir: "release" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: docs + upload-dir: "release/docs" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: coverage + upload-dir: "release/coverage" + on: + all_branches: true + condition: $TRAVIS_TAG \ No newline at end of file diff --git a/README.md b/README.md index d26fadd..3b85f85 100644 --- a/README.md +++ b/README.md @@ -36,22 +36,14 @@ - Wiki: Other misc tutorials and resources are [on the Wiki](https://github.com/pixijs/pixi.js/wiki/Resources). ### Community ### -- Forums: Check out the [forum] (http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow] (http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. +- Forums: Check out the [forum](http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow](http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. - Inspiration: Check out the [gallery](http://www.pixijs.com/gallery) to see some of the amazing things people have created! - Chat: You can join us on [Gitter](https://gitter.im/pixijs/pixi.js) To chat about Pixi. We also now have a Slack channel. If you would like to join it please Send me an email (mat@goodboydigital.com) and I will invite you in. ### Setup ### -It's easy to get started with Pixi.js! Simply grab the pre-built versions from here: - -Release Branch - Nice and stable Pixi.js -- Unminified: [http://pixijs.download/release/pixi.js] -- Minified: [http://pixijs.download/release/pixi.min.js] - -Develop Branch - The bleeding edge version of Pixi.js -- Unminified: [http://pixijs.download/dev/pixi.js] -- Minified: [http://pixijs.download/dev/pixi.min.js] +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. @@ -70,10 +62,10 @@ #### CDN Install (via cdnjs) ```html - + ``` -_Note: `4.2.2` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ +_Note: `4.4.3` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ ### Demos ### @@ -130,7 +122,7 @@ // and the root stage PIXI.Container. var app = new PIXI.Application(); -// The application will create a canvas element for you that you +// The application will create a canvas element for you that you // can then insert into the DOM. document.body.appendChild(app.view); @@ -150,7 +142,7 @@ // Add the bunny to the scene we are building. app.stage.addChild(bunny); - + // Listen for frame updates app.ticker.add(function() { // each frame we spin the bunny around a bit diff --git a/bower.json b/bower.json deleted file mode 100644 index 41b668c..0000000 --- a/bower.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "pixi.js", - "main": "dist/pixi.min.js", - "ignore": [ - "**/.*", - "docs", - "node_modules", - "bower_components", - "test", - "scripts", - "bower.json", - "inch.json" - ], - "dependencies": { - }, - "devDependencies": { - } -} diff --git a/package.json b/package.json index d35cbb2..308469f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.3.5", + "version": "4.4.4", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ @@ -40,11 +40,11 @@ "lib": "babel src --out-dir lib -s", "predocs": "rimraf docs/**", "docs": "jsdoc -c scripts/jsdoc.conf.json -R README.md", - "publish:patch": "npm version patch --no-git-tag-version && npm publish", - "publish:minor": "npm version minor --no-git-tag-version && npm publish", - "publish:major": "npm version major --no-git-tag-version && npm publish", + "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", - "postpublish": "node scripts/release.js" + "postpublish": "git push && git push --tags" }, "files": [ "dist/", @@ -72,7 +72,6 @@ "electron": "^1.4.15", "eslint": "^3.5.0", "floss": "^2.0.1", - "gh-pages": "^0.11.0", "jaguarjs-jsdoc": "^1.0.1", "js-md5": "^0.4.1", "jsdoc": "^3.4.2", diff --git a/scripts/jsdoc.conf.json b/scripts/jsdoc.conf.json index 553e61c..3f52180 100644 --- a/scripts/jsdoc.conf.json +++ b/scripts/jsdoc.conf.json @@ -43,7 +43,7 @@ }, "markdown" : { "parser" : "gfm", - "hardwrap" : true + "hardwrap" : false }, "opts": { "encoding" : "utf8", diff --git a/scripts/release.js b/scripts/release.js deleted file mode 100644 index 156620c..0000000 --- a/scripts/release.js +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env node -'use strict'; - -// Publish script to push releases of the bin files -// the normally are gitignored -const ghpages = require('gh-pages'); -const path = require('path'); -const packageInfo = require(path.join(__dirname, '..', 'package.json')); - -const options = { - src: [ - 'dist/**/*', - 'lib/**/*', - 'src/**/*', - 'scripts/*', - 'scripts/renders/*', - 'test/**/*', - '*.json', - '*.md', - 'LICENSE', - '.eslintrc', - '.editorconfig', - '.travis.yml', - '.babelrc', - ], - dotfiles: true, - branch: 'release', - message: packageInfo.version, - logger: console.log.bind(console), -}; - -ghpages.publish(process.cwd(), options, (err) => -{ - if (err) - { - console.log(err); - process.exit(1); - - return; - } - - process.exit(0); -}); diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index f16bcb1..220427a 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -21,7 +21,7 @@ const DIV_HOOK_ZINDEX = 2; /** - * The Accessibility manager reacreates the ability to tab and and have content read by screen + * The Accessibility manager recreates the ability to tab and and have content read by screen * readers. This is very important as it can possibly help people with disabilities access pixi * content. * diff --git a/src/accessibility/index.js b/src/accessibility/index.js index 568cfb1..0ae108f 100644 --- a/src/accessibility/index.js +++ b/src/accessibility/index.js @@ -1,4 +1,9 @@ /** + * This namespace contains a renderer plugin for interaction accessibility for end-users + * with physical impairments which require screen-renders, keyboard navigation, etc. + * + * Do not instantiate this plugin directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. * @namespace PIXI.accessibility */ export { default as accessibleTarget } from './accessibleTarget'; diff --git a/src/core/Application.js b/src/core/Application.js index 5340578..2fcc729 100644 --- a/src/core/Application.js +++ b/src/core/Application.js @@ -1,6 +1,8 @@ import { autoDetectRenderer } from './autoDetectRenderer'; import Container from './display/Container'; import { shared, Ticker } from './ticker'; +import settings from './settings'; +import { UPDATE_PRIORITY } from './const'; /** * Convenience class to create a new PIXI application. @@ -22,28 +24,52 @@ */ export default class Application { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experience unexplained flickering try setting this to true. - * @param {boolean} [useSharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedLoader=false] - `true` to use PIXI.loaders.shared, `false` to create new Loader. */ - constructor(width, height, options, noWebGL, useSharedTicker = false) + constructor(options, arg2, arg3, arg4, arg5) { + // Support for constructor(width, height, options, noWebGL, useSharedTicker) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + forceCanvas: !!arg4, + sharedTicker: !!arg5, + }, arg3); + } + + /** + * The default options, so we mixin functionality later. + * @member {object} + * @protected + */ + this._options = options = Object.assign({ + sharedTicker: false, + forceCanvas: false, + sharedLoader: false, + }, options); + /** * WebGL renderer if available, otherwise CanvasRenderer * @member {PIXI.WebGLRenderer|PIXI.CanvasRenderer} */ - this.renderer = autoDetectRenderer(width, height, options, noWebGL); + this.renderer = autoDetectRenderer(options); /** * The root display container that's rendered. @@ -63,7 +89,7 @@ * @member {PIXI.ticker.Ticker} * @default PIXI.ticker.shared */ - this.ticker = useSharedTicker ? shared : new Ticker(); + this.ticker = options.sharedTicker ? shared : new Ticker(); // Start the rendering this.start(); @@ -78,7 +104,7 @@ this._ticker = ticker; if (ticker) { - ticker.add(this.render, this); + ticker.add(this.render, this, UPDATE_PRIORITY.LOW); } } get ticker() // eslint-disable-line require-jsdoc @@ -136,13 +162,18 @@ */ destroy(removeView) { - this.stop(); + const oldTicker = this._ticker; + this.ticker = null; + oldTicker.destroy(); + this.stage.destroy(); this.stage = null; this.renderer.destroy(removeView); this.renderer = null; + + this._options = null; } } diff --git a/src/core/autoDetectRenderer.js b/src/core/autoDetectRenderer.js index 8960425..9ec0364 100644 --- a/src/core/autoDetectRenderer.js +++ b/src/core/autoDetectRenderer.js @@ -2,6 +2,7 @@ import CanvasRenderer from './renderers/canvas/CanvasRenderer'; import WebGLRenderer from './renderers/webgl/WebGLRenderer'; +// eslint-disable-next-line valid-jsdoc /** * This helper function will automatically detect which renderer you should be using. * WebGL is the preferred renderer as it is a lot faster. If webGL is not supported by @@ -9,24 +10,32 @@ * * @memberof PIXI * @function autoDetectRenderer - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @return {PIXI.WebGLRenderer|PIXI.CanvasRenderer} Returns WebGL renderer if available, otherwise CanvasRenderer */ -export function autoDetectRenderer(width = 800, height = 600, options, noWebGL) +export function autoDetectRenderer(options, arg1, arg2, arg3) { - if (!noWebGL && utils.isWebGLSupported()) + // Backward-compatible support for noWebGL option + let forceCanvas = options && options.forceCanvas; + + if (arg3 !== undefined) { - return new WebGLRenderer(width, height, options); + forceCanvas = arg3; } - return new CanvasRenderer(width, height, options); + if (!forceCanvas && utils.isWebGLSupported()) + { + return new WebGLRenderer(options, arg1, arg2); + } + + return new CanvasRenderer(options, arg1, arg2); } diff --git a/src/core/const.js b/src/core/const.js index b9e99ae..2c68dee 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -341,3 +341,27 @@ LINEAR_VERTICAL: 0, LINEAR_HORIZONTAL: 1, }; + +/** + * Represents the update priorities used by internal PIXI classes when registered with + * the {@link PIXI.ticker.Ticker} object. Higher priority items are updated first and lower + * priority items, such as render, should go later. + * + * @static + * @constant + * @name UPDATE_PRIORITY + * @memberof PIXI + * @type {object} + * @property {number} INTERACTION=50 Highest priority, used for {@link PIXI.interaction.InteractionManager} + * @property {number} HIGH=25 High priority updating, {@link PIXI.VideoBaseTexture} and {@link PIXI.extras.AnimatedSprite} + * @property {number} NORMAL=0 Default priority for ticker events, see {@link PIXI.ticker.Ticker#add}. + * @property {number} LOW=-25 Low priority used for {@link PIXI.Application} rendering. + * @property {number} UTILITY=-50 Lowest priority used for {@link PIXI.prepare.BasePrepare} utility. + */ +export const UPDATE_PRIORITY = { + INTERACTION: 50, + HIGH: 25, + NORMAL: 0, + LOW: -25, + UTILITY: -50, +}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 4e6c77d..decb42a 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -13,7 +13,6 @@ * * @class * @extends EventEmitter - * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ export default class DisplayObject extends EventEmitter @@ -113,6 +112,29 @@ * @private */ this._mask = null; + + /** + * If the object has been destroyed via destroy(). If true, it should not be used. + * + * @member {boolean} + * @private + * @readonly + */ + this._destroyed = false; + + /** + * Fired when this DisplayObject is added to a Container. + * + * @event PIXI.DisplayObject#added + * @param {PIXI.Container} container - The container added to. + */ + + /** + * Fired when this DisplayObject is removed from a Container. + * + * @event PIXI.DisplayObject#removed + * @param {PIXI.Container} container - The container removed from. + */ } /** @@ -403,6 +425,8 @@ this.interactive = false; this.interactiveChildren = false; + + this._destroyed = true; } /** diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index aafdfe2..f787c4a 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -792,8 +792,8 @@ sprite.worldAlpha = this.worldAlpha * sprite.alpha; sprite.blendMode = this.blendMode; - sprite.texture._frame.width = rect.width; - sprite.texture._frame.height = rect.height; + sprite._texture._frame.width = rect.width; + sprite._texture._frame.height = rect.height; sprite.transform.worldTransform = this.transform.worldTransform; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index a24c775..b9f192d 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -19,11 +19,12 @@ */ export default class SystemRenderer extends EventEmitter { + // eslint-disable-next-line valid-jsdoc /** * @param {string} system - The name of the system this renderer is for. - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -37,27 +38,31 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(system, screenWidth, screenHeight, options) + constructor(system, options, arg2, arg3) { super(); sayHello(system); - // prepare options - if (options) + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') { - for (const i in settings.RENDER_OPTIONS) - { - if (typeof options[i] === 'undefined') - { - options[i] = settings.RENDER_OPTIONS[i]; - } - } + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); } - else - { - options = settings.RENDER_OPTIONS; - } + + // Add the default render options + options = Object.assign({}, settings.RENDER_OPTIONS, options); + + /** + * The supplied constructor options. + * + * @member {Object} + * @readOnly + */ + this.options = options; /** * The type of the renderer. @@ -75,7 +80,7 @@ * * @member {PIXI.Rectangle} */ - this.screen = new Rectangle(0, 0, screenWidth || 800, screenHeight || 600); + this.screen = new Rectangle(0, 0, options.width, options.height); /** * The canvas element that everything is drawn to @@ -279,6 +284,8 @@ this.blendModes = null; + this.options = null; + this.preserveDrawingBuffer = false; this.clearBeforeRender = false; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index e84e49c..691caf0 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -17,10 +17,11 @@ */ export default class CanvasRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -34,9 +35,9 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('Canvas', screenWidth, screenHeight, options); + super('Canvas', options, arg2, arg3); this.type = RENDERER_TYPE.CANVAS; @@ -96,7 +97,19 @@ this.context = null; this.renderingToScreen = false; - this.resize(screenWidth, screenHeight); + this.resize(this.options.width, this.options.height); + + /** + * Fired after rendering finishes. + * + * @event PIXI.CanvasRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.CanvasRenderer#prerender + */ } /** @@ -299,4 +312,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.CanvasRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.CanvasExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.CanvasPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.CanvasRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index e1cf7cd..f6fe123 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -31,11 +31,12 @@ */ export default class WebGLRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** * - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -55,9 +56,9 @@ * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('WebGL', screenWidth, screenHeight, options); + super('WebGL', options, arg2, arg3); /** * The type of this renderer as a standardised const @@ -89,12 +90,6 @@ }; - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ this._backgroundColorRgba[3] = this.transparent ? 0 : 1; this.globalUniforms = new UniformGroup({ @@ -117,6 +112,13 @@ this.initPlugins(); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ if(options.context) { this.context.initFromContext(options.context); @@ -169,6 +171,25 @@ } return this; + + /** + * Fired after rendering finishes. + * + * @event PIXI.WebGLRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.WebGLRenderer#prerender + */ + + /** + * Fired when the WebGL context is set. + * + * @event PIXI.WebGLRenderer#context + * @param {WebGLRenderingContext} gl - WebGL context. + */ } /** @@ -295,4 +316,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.WebGLRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.WebGLRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(WebGLRenderer); diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index 4eb6128..fa3d883 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -47,6 +47,14 @@ * @member {boolean} */ this.enabled = true; + + /** + * If enabled, pixi will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; } /** diff --git a/src/core/renderers/webgl/systems/ContextSystem.js b/src/core/renderers/webgl/systems/ContextSystem.js index ff958c7..8df85ea 100644 --- a/src/core/renderers/webgl/systems/ContextSystem.js +++ b/src/core/renderers/webgl/systems/ContextSystem.js @@ -75,7 +75,8 @@ */ createContext(canvas, options) { - var gl = canvas.getContext('webgl', options) || + var gl = //canvas.getContext('webgl2', options) || + canvas.getContext('webgl', options) || canvas.getContext('experimental-webgl', options); if (!gl) diff --git a/src/core/renderers/webgl/systems/FilterSystem.js b/src/core/renderers/webgl/systems/FilterSystem.js index f47335d..37e28d2 100644 --- a/src/core/renderers/webgl/systems/FilterSystem.js +++ b/src/core/renderers/webgl/systems/FilterSystem.js @@ -111,7 +111,7 @@ // TODO we should fit the rect around the transform.. } - else + else if (filters[0].autoFit) { sourceFrame.fit(filterData.stack[0].destinationFrame); } diff --git a/src/core/renderers/webgl/systems/shader/shader/mapType.js b/src/core/renderers/webgl/systems/shader/shader/mapType.js index 581dace..917538a 100644 --- a/src/core/renderers/webgl/systems/shader/shader/mapType.js +++ b/src/core/renderers/webgl/systems/shader/shader/mapType.js @@ -15,7 +15,7 @@ } } - return GL_TABLE[type]; + return GL_TABLE[type]; }; var GL_TABLE = null; diff --git a/src/core/settings.js b/src/core/settings.js index 18c5877..38ef45c 100644 --- a/src/core/settings.js +++ b/src/core/settings.js @@ -2,6 +2,15 @@ import canUploadSameBuffer from './utils/canUploadSameBuffer'; /** + * User's customizable globals for overriding the default PIXI settings, such + * as a renderer's default resolution, framerate, float percision, etc. + * @example + * // Use the native window resolution as the default resolution + * // will support high-density displays when rendering + * PIXI.settings.RESOLUTION = window.devicePixelRatio. + * + * // Disable interpolation when scaling, will make texture be pixelated + * PIXI.settings.SCALE_MODE = PIXI.SCALE_MODES.NEAREST; * @namespace PIXI.settings */ export default { @@ -101,6 +110,9 @@ * @property {boolean} clearBeforeRender=true * @property {boolean} preserveDrawingBuffer=false * @property {boolean} roundPixels=false + * @property {number} width=800 + * @property {number} height=600 + * @property {boolean} legacy=false */ RENDER_OPTIONS: { view: null, @@ -112,6 +124,9 @@ clearBeforeRender: true, preserveDrawingBuffer: false, roundPixels: false, + width: 800, + height: 600, + legacy: false, }, /** diff --git a/src/core/shader/Program.js b/src/core/shader/Program.js index c0b538b..07b4398 100644 --- a/src/core/shader/Program.js +++ b/src/core/shader/Program.js @@ -1,6 +1,6 @@ import extractUniformsFromSrc from './extractUniformsFromSrc'; import generateUniformsSync from './generateUniformsSync'; -import glCore from 'pixi-gl-core'; +import shaderUtils from '../renderers/webgl/systems/shader/shader'; import { ProgramCache } from '../utils'; import getTestContext from '../utils/getTestContext'; @@ -64,14 +64,17 @@ } else { - vertexSrc = glCore.shader.setPrecision(vertexSrc, 'mediump'); - fragmentSrc = glCore.shader.setPrecision(fragmentSrc, 'mediump'); + vertexSrc = shaderUtils.setPrecision(vertexSrc, 'mediump'); + fragmentSrc = shaderUtils.setPrecision(fragmentSrc, 'mediump'); - const program = glCore.shader.compileProgram(gl, vertexSrc, fragmentSrc); + const program = shaderUtils.compileProgram(gl, vertexSrc, fragmentSrc); + console.log("<<<<<<>>><<"); this.attributeData = this.getAttributeData(program, gl); this.uniformData = this.getUniformData(program, gl); + console.log(this.uniformData); + //gl.deleteProgram(program); } } @@ -95,13 +98,13 @@ for (let i = 0; i < totalAttributes; i++) { const attribData = gl.getActiveAttrib(program, i); - const type = glCore.shader.mapType(gl, attribData.type); + const type = shaderUtils.mapType(gl, attribData.type); /*eslint-disable */ const data = { type: type, name: attribData.name, - size: glCore.shader.mapSize(type), + size: shaderUtils.mapSize(type), location: 0, }; /*eslint-enable */ @@ -137,13 +140,14 @@ // TODO expose this as a prop? // const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); - const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); + //const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); for (let i = 0; i < totalUniforms; i++) { const uniformData = gl.getActiveUniform(program, i); const name = uniformData.name.replace(/\[.*?\]/, ''); - const type = glCore.shader.mapType(gl, uniformData.type); + const type = shaderUtils.mapType(gl, uniformData.type); + console.log('mapping ' + uniformData.type + ' to ' + type); // if (!name.match(maskRegex)) { @@ -151,7 +155,7 @@ uniforms[name] = { type: type, size: uniformData.size, - value: glCore.shader.defaultValue(type, uniformData.size), + value: shaderUtils.defaultValue(type, uniformData.size), }; /*eslint-enable */ } diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index e387e99..b57d095 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -147,12 +147,12 @@ // so if _width is 0 then width was not set.. if (this._width) { - this.scale.x = sign(this.scale.x) * this._width / this.texture.orig.width; + this.scale.x = sign(this.scale.x) * this._width / this._texture.orig.width; } if (this._height) { - this.scale.y = sign(this.scale.y) * this._height / this.texture.orig.height; + this.scale.y = sign(this.scale.y) * this._height / this._texture.orig.height; } } diff --git a/src/core/sprites/canvas/CanvasTinter.js b/src/core/sprites/canvas/CanvasTinter.js index 27e5e0c..fd9de5d 100644 --- a/src/core/sprites/canvas/CanvasTinter.js +++ b/src/core/sprites/canvas/CanvasTinter.js @@ -18,7 +18,7 @@ */ getTintedTexture: (sprite, color) => { - const texture = sprite.texture; + const texture = sprite._texture; color = CanvasTinter.roundColor(color); diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 5a4edda..8b9b797 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -344,6 +344,7 @@ const texture = this._texture; const style = this._style; + const padding = style.trim ? 0 : style.padding; texture.baseTexture.valid = true; texture.baseTexture.resolution = this.resolution; @@ -353,11 +354,11 @@ texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - texture.trim.x = -style.padding; - texture.trim.y = -style.padding; + texture.trim.x = -padding; + texture.trim.y = -padding; - texture.orig.width = texture._frame.width - (style.padding * 2); - texture.orig.height = texture._frame.height - (style.padding * 2); + texture.orig.width = texture._frame.width - (padding * 2); + texture.orig.height = texture._frame.height - (padding * 2); // call sprite onTextureUpdate to update scale if _width or _height were set this._onTextureUpdate(); diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index 853da3d..8a9c34b 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -114,6 +114,38 @@ this.cacheId = null; this.validate(); + + /** + * Fired when a not-immediately-available source finishes loading. + * + * @protected + * @event PIXI.BaseTexture#loaded + * @param {PIXI.BaseTexture} baseTexture - Resource loaded. + */ + + /** + * Fired when a not-immediately-available source fails to load. + * + * @protected + * @event PIXI.BaseTexture#error + * @param {PIXI.BaseTexture} baseTexture - Resource errored. + */ + + /** + * Fired when BaseTexture is updated. + * + * @protected + * @event PIXI.BaseTexture#update + * @param {PIXI.BaseTexture} baseTexture - Instance of texture being updated. + */ + + /** + * Fired when BaseTexture is destroyed. + * + * @protected + * @event PIXI.BaseTexture#dispose + * @param {PIXI.BaseTexture} baseTexture - Instance of texture being destroyed. + */ } setResource(resource) diff --git a/src/core/textures/Spritesheet.js b/src/core/textures/Spritesheet.js index d469c62..9b24f27 100644 --- a/src/core/textures/Spritesheet.js +++ b/src/core/textures/Spritesheet.js @@ -208,6 +208,7 @@ // lets also add the frame to pixi's global cache for fromFrame and fromImage functions TextureCache[i] = this.textures[i]; + this.textures[i].textureCacheId = i; } frameIndex++; diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 0a2c379..2213087 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -147,9 +147,9 @@ /** * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. * - * @event update - * @memberof PIXI.Texture# + * @event PIXI.Texture#update * @protected + * @param {PIXI.Texture} texture - Instance of texture being updated. */ this._updateID = 0; @@ -159,6 +159,15 @@ * @type {Object} */ this.transform = null; + + /** + * The id under which this Texture has been added to the texture cache. This is + * automatically set in certain cases, but may not always be accurate, particularly if + * the texture is in the cache under multiple ids. + * + * @member {string} + */ + this.textureCacheId = null; } /** @@ -225,11 +234,17 @@ this._uvs = null; this.trim = null; this.orig = null; + this.textureCacheId = null; this.valid = false; - this.off('dispose', this.dispose, this); - this.off('update', this.update, this); + for (const prop in TextureCache) + { + if (TextureCache[prop] === this) + { + delete TextureCache[prop]; + } + } } /** @@ -315,14 +330,15 @@ */ static fromLoader(source, imageUrl, name) { - // console.log('added from loader...') + // console.log('added from loader...') const resource = new ImageResource(source);//.from(imageUrl, crossorigin);// document.createElement('img'); - // console.log('base resource ' + resource.width); + // console.log('base resource ' + resource.width); const baseTexture = new BaseTexture(resource, settings.SCALE_MODE, getResolutionOfUrl(imageUrl)); + /// console.log('base width ' + baseTexture.width); const texture = new Texture(baseTexture); @@ -335,6 +351,7 @@ // lets also add the frame to pixi's global cache for fromFrame and fromImage fucntions BaseTextureCache[name] = baseTexture; TextureCache[name] = texture; + texture.textureCacheId = name; // also add references by url if they are different. if (name !== imageUrl) @@ -355,6 +372,10 @@ */ static addTextureToCache(texture, id) { + if (!texture.textureCacheId) + { + texture.textureCacheId = id; + } TextureCache[id] = texture; } @@ -495,6 +516,7 @@ */ Texture.EMPTY = new Texture(new BaseTexture()); removeAllHandlers(Texture.EMPTY); +removeAllHandlers(Texture.EMPTY.baseTexture); /** * A white texture of 10x10 size, used for graphics and other things @@ -505,3 +527,4 @@ */ Texture.WHITE = createWhiteTexture(); removeAllHandlers(Texture.WHITE); +removeAllHandlers(Texture.WHITE.baseTexture); diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index 59eb9fd..a9b9f51 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -1,6 +1,7 @@ import BaseTexture from './BaseTexture'; import { uid, BaseTextureCache } from '../utils'; -import * as ticker from '../ticker'; +import { shared } from '../ticker'; +import { UPDATE_PRIORITY } from '../const'; /** * A texture of a [playing] Video. @@ -125,7 +126,7 @@ if (!this._isAutoUpdating && this.autoUpdate) { - ticker.shared.add(this.update, this); + shared.add(this.update, this, UPDATE_PRIORITY.HIGH); this._isAutoUpdating = true; } } @@ -139,7 +140,7 @@ { if (this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); this._isAutoUpdating = false; } } @@ -187,7 +188,7 @@ { if (this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); } if (this.source && this.source._pixiId) @@ -281,12 +282,12 @@ if (!this._autoUpdate && this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); this._isAutoUpdating = false; } else if (this._autoUpdate && !this._isAutoUpdating) { - ticker.shared.add(this.update, this); + shared.add(this.update, this, UPDATE_PRIORITY.HIGH); this._isAutoUpdating = true; } } diff --git a/src/core/ticker/Ticker.js b/src/core/ticker/Ticker.js index 17a1517..40df138 100644 --- a/src/core/ticker/Ticker.js +++ b/src/core/ticker/Ticker.js @@ -1,12 +1,10 @@ import settings from '../settings'; -import EventEmitter from 'eventemitter3'; - -// Internal event used by composed emitter -const TICK = 'tick'; +import { UPDATE_PRIORITY } from '../const'; +import TickerListener from './TickerListener'; /** * A Ticker class that runs an update loop that other objects listen to. - * This class is composed around an EventEmitter object to add listeners + * This class is composed around listeners * meant for execution on the next requested animation frame. * Animation frames are requested only when necessary, * e.g. When the ticker is started and the emitter has listeners. @@ -22,10 +20,11 @@ constructor() { /** - * Internal emitter used to fire 'tick' event + * The first listener. All new listeners added are chained on this. * @private + * @type {TickerListener} */ - this._emitter = new EventEmitter(); + this._head = new TickerListener(null, null, Infinity); /** * Internal current frame request ID @@ -131,7 +130,7 @@ // Invoke listeners now this.update(time); // Listener side effects may have modified ticker state. - if (this.started && this._requestId === null && this._emitter.listeners(TICK, true)) + if (this.started && this._requestId === null && this._head.next) { this._requestId = requestAnimationFrame(this._tick); } @@ -148,7 +147,7 @@ */ _requestIfNeeded() { - if (this._requestId === null && this._emitter.listeners(TICK, true)) + if (this._requestId === null && this._head.next) { // ensure callbacks get correct delta this.lastTime = performance.now(); @@ -193,35 +192,72 @@ } /** - * Calls {@link module:eventemitter3.EventEmitter#on} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. + * Register a handler for tick events. Calls continuously unless + * it is removed or the ticker is stopped. * * @param {Function} fn - The listener function to be added for updates * @param {Function} [context] - The listener context + * @param {number} [priority=PIXI.UPDATE_PRIORITY.NORMAL] - The priority for emitting * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - add(fn, context) + add(fn, context, priority = UPDATE_PRIORITY.NORMAL) { - this._emitter.on(TICK, fn, context); - - this._startIfPossible(); - - return this; + return this._addListener(new TickerListener(fn, context, priority)); } /** - * Calls {@link module:eventemitter3.EventEmitter#once} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. + * Add a handler for the tick event which is only execute once. * * @param {Function} fn - The listener function to be added for one update * @param {Function} [context] - The listener context + * @param {number} [priority=PIXI.UPDATE_PRIORITY.NORMAL] - The priority for emitting * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - addOnce(fn, context) + addOnce(fn, context, priority = UPDATE_PRIORITY.NORMAL) { - this._emitter.once(TICK, fn, context); + return this._addListener(new TickerListener(fn, context, priority, true)); + } + + /** + * Internally adds the event handler so that it can be sorted by priority. + * Priority allows certain handler (user, AnimatedSprite, Interaction) to be run + * before the rendering. + * + * @private + * @param {TickerListener} listener - Current listener being added. + * @returns {PIXI.ticker.Ticker} This instance of a ticker + */ + _addListener(listener) + { + // For attaching to head + let current = this._head.next; + let previous = this._head; + + // Add the first item + if (!current) + { + listener.connect(previous); + } + else + { + // Go from highest to lowest priority + while (current) + { + if (listener.priority >= current.priority) + { + listener.connect(previous); + break; + } + previous = current; + current = current.next; + } + + // Not yet connected + if (!listener.previous) + { + listener.connect(previous); + } + } this._startIfPossible(); @@ -229,19 +265,33 @@ } /** - * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. - * It checks if the emitter has listeners for 'tick' event. - * If it does, then it cancels the animation frame. + * Removes any handlers matching the function and context parameters. + * If no handlers are left after removing, then it cancels the animation frame. * - * @param {Function} [fn] - The listener function to be removed + * @param {Function} fn - The listener function to be removed * @param {Function} [context] - The listener context to be removed * @returns {PIXI.ticker.Ticker} This instance of a ticker */ remove(fn, context) { - this._emitter.off(TICK, fn, context); + let listener = this._head.next; - if (!this._emitter.listeners(TICK, true)) + while (listener) + { + // We found a match, lets remove it + // no break to delete all possible matches + // incase a listener was added 2+ times + if (listener.match(fn, context)) + { + listener = listener.destroy(); + } + else + { + listener = listener.next; + } + } + + if (!this._head.next) { this._cancelIfNeeded(); } @@ -276,6 +326,25 @@ } /** + * Destroy the ticker and don't use after this. Calling + * this method removes all references to internal events. + */ + destroy() + { + this.stop(); + + let listener = this._head.next; + + while (listener) + { + listener = listener.destroy(true); + } + + this._head.destroy(); + this._head = null; + } + + /** * Triggers an update. An update entails setting the * current {@link PIXI.ticker.Ticker#elapsedMS}, * the current {@link PIXI.ticker.Ticker#deltaTime}, @@ -320,8 +389,22 @@ this.deltaTime = elapsedMS * settings.TARGET_FPMS * this.speed; + // Cache a local reference, in-case ticker is destroyed + // during the emit, we can still check for head.next + const head = this._head; + // Invoke listeners added to internal emitter - this._emitter.emit(TICK, this.deltaTime); + let listener = head.next; + + while (listener) + { + listener = listener.emit(this.deltaTime); + } + + if (!head.next) + { + this._cancelIfNeeded(); + } } else { diff --git a/src/core/ticker/TickerListener.js b/src/core/ticker/TickerListener.js new file mode 100644 index 0000000..2bedb34 --- /dev/null +++ b/src/core/ticker/TickerListener.js @@ -0,0 +1,158 @@ +/** + * Internal class for handling the priority sorting of ticker handlers. + * + * @private + * @class + * @memberof PIXI.ticker + */ +export default class TickerListener +{ + /** + * Constructor + * + * @param {Function} fn - The listener function to be added for one update + * @param {Function} [context=null] - The listener context + * @param {number} [priority=0] - The priority for emitting + * @param {boolean} [once=false] - If the handler should fire once + */ + constructor(fn, context = null, priority = 0, once = false) + { + /** + * The handler function to execute. + * @member {Function} + */ + this.fn = fn; + + /** + * The calling to execute. + * @member {Function} + */ + this.context = context; + + /** + * The current priority. + * @member {number} + */ + this.priority = priority; + + /** + * If this should only execute once. + * @member {boolean} + */ + this.once = once; + + /** + * The next item in chain. + * @member {TickerListener} + */ + this.next = null; + + /** + * The previous item in chain. + * @member {TickerListener} + */ + this.previous = null; + + /** + * `true` if this listener has been destroyed already. + * @member {boolean} + * @private + */ + this._destroyed = false; + } + + /** + * Simple compare function to figure out if a function and context match. + * + * @param {Function} fn - The listener function to be added for one update + * @param {Function} context - The listener context + * @return {boolean} `true` if the listener match the arguments + */ + match(fn, context) + { + context = context || null; + + return this.fn === fn && this.context === context; + } + + /** + * Emit by calling the current function. + * @param {number} deltaTime - time since the last emit. + * @return {TickerListener} Next ticker + */ + emit(deltaTime) + { + if (this.context) + { + this.fn.call(this.context, deltaTime); + } + else + { + this.fn(deltaTime); + } + + if (this.once) + { + this.destroy(); + } + + const redirect = this.next; + + // Soft-destroying should remove + // the next reference + if (this._destroyed) + { + this.next = null; + } + + return redirect; + } + + /** + * Connect to the list. + * @param {TickerListener} previous - Input node, previous listener + */ + connect(previous) + { + this.previous = previous; + if (previous.next) + { + previous.next.previous = this; + } + this.next = previous.next; + previous.next = this; + } + + /** + * Destroy and don't use after this. + * @param {boolean} [hard = false] `true` to remove the `next` reference, this + * is considered a hard destroy. Soft destroy maintains the next reference. + * @return {TickerListener} The listener to redirect while emitting or removing. + */ + destroy(hard = false) + { + this._destroyed = true; + this.fn = null; + this.context = null; + + // Disconnect, hook up next and previous + if (this.previous) + { + this.previous.next = this.next; + } + + if (this.next) + { + this.next.previous = this.previous; + } + + // Redirect to the next item + const redirect = this.previous; + + // Remove references + this.next = hard ? null : redirect; + this.previous = null; + + return redirect; + } +} diff --git a/src/core/ticker/index.js b/src/core/ticker/index.js index 4b3017c..c3020d4 100644 --- a/src/core/ticker/index.js +++ b/src/core/ticker/index.js @@ -45,8 +45,25 @@ const shared = new Ticker(); shared.autoStart = true; +shared.destroy = () => +{ + // protect destroying shared ticker + // this is used by other internal systems + // like AnimatedSprite and InteractionManager +}; /** + * This namespace contains an API for interacting with PIXI's internal global update loop. + * + * This ticker is used for rendering, {@link PIXI.extras.AnimatedSprite AnimatedSprite}, + * {@link PIXI.interaction.InteractionManager InteractionManager} and many other time-based PIXI systems. + * @example + * const ticker = new PIXI.ticker.Ticker(); + * ticker.stop(); + * ticker.add((deltaTime) => { + * // do something every frame + * }); + * ticker.start(); * @namespace PIXI.ticker */ export { shared, Ticker }; diff --git a/src/core/utils/index.js b/src/core/utils/index.js index be44750..7441f7b 100644 --- a/src/core/utils/index.js +++ b/src/core/utils/index.js @@ -9,6 +9,21 @@ let saidHello = false; /** + * Generalized convenience utilities for PIXI. + * @example + * // Extend PIXI's internal Event Emitter. + * class MyEmitter extends PIXI.utils.EventEmitter { + * constructor() { + * super(); + * console.log("Emitter created!"); + * } + * } + * + * // Get info on current device + * console.log(PIXI.utils.isMobile); + * + * // Convert hex color to string + * console.log(PIXI.utils.hex2string(0xff00ff)); // returns: "#ff00ff" * @namespace PIXI.utils */ export { diff --git a/src/deprecation.js b/src/deprecation.js index b9c73b3..33daeec 100644 --- a/src/deprecation.js +++ b/src/deprecation.js @@ -918,6 +918,35 @@ }); /** + * @method + * @name PIXI.prepare.BasePrepare#register + * @see PIXI.prepare.BasePrepare#registerFindHook + * @deprecated since version 4.4.2 + * @param {Function} [addHook] - Function call that takes two parameters: `item:*, queue:Array` + * function must return `true` if it was able to add item to the queue. + * @param {Function} [uploadHook] - Function call that takes two parameters: `prepare:CanvasPrepare, item:*` and + * function must return `true` if it was able to handle upload of item. + * @return {PIXI.BasePrepare} Instance of plugin for chaining. + */ +prepare.BasePrepare.prototype.register = function register(addHook, uploadHook) +{ + warn('renderer.plugins.prepare.register is now deprecated, ' + + 'please use renderer.plugins.prepare.registerFindHook & renderer.plugins.prepare.registerUploadHook'); + + if (addHook) + { + this.registerFindHook(addHook); + } + + if (uploadHook) + { + this.registerUploadHook(uploadHook); + } + + return this; +}; + +/** * The number of graphics or textures to upload to the GPU. * * @name PIXI.prepare.canvas.UPLOADS_PER_FRAME @@ -969,67 +998,73 @@ }, }); -Object.defineProperties(loaders.Resource.prototype, { - isJson: { - get() - { - warn('The isJson property is deprecated, please use `resource.type === Resource.TYPE.JSON`.'); +if (loaders.Loader) +{ + const Resource = loaders.Resource; + const Loader = loaders.Loader; - return this.type === loaders.Loader.Resource.TYPE.JSON; + Object.defineProperties(Resource.prototype, { + isJson: { + get() + { + warn('The isJson property is deprecated, please use `resource.type === Resource.TYPE.JSON`.'); + + return this.type === Resource.TYPE.JSON; + }, }, - }, - isXml: { - get() - { - warn('The isXml property is deprecated, please use `resource.type === Resource.TYPE.XML`.'); + isXml: { + get() + { + warn('The isXml property is deprecated, please use `resource.type === Resource.TYPE.XML`.'); - return this.type === loaders.Loader.Resource.TYPE.XML; + return this.type === Resource.TYPE.XML; + }, }, - }, - isImage: { - get() - { - warn('The isImage property is deprecated, please use `resource.type === Resource.TYPE.IMAGE`.'); + isImage: { + get() + { + warn('The isImage property is deprecated, please use `resource.type === Resource.TYPE.IMAGE`.'); - return this.type === loaders.Loader.Resource.TYPE.IMAGE; + return this.type === Resource.TYPE.IMAGE; + }, }, - }, - isAudio: { - get() - { - warn('The isAudio property is deprecated, please use `resource.type === Resource.TYPE.AUDIO`.'); + isAudio: { + get() + { + warn('The isAudio property is deprecated, please use `resource.type === Resource.TYPE.AUDIO`.'); - return this.type === loaders.Loader.Resource.TYPE.AUDIO; + return this.type === Resource.TYPE.AUDIO; + }, }, - }, - isVideo: { - get() - { - warn('The isVideo property is deprecated, please use `resource.type === Resource.TYPE.VIDEO`.'); + isVideo: { + get() + { + warn('The isVideo property is deprecated, please use `resource.type === Resource.TYPE.VIDEO`.'); - return this.type === loaders.Loader.Resource.TYPE.VIDEO; + return this.type === Resource.TYPE.VIDEO; + }, }, - }, -}); + }); -Object.defineProperties(loaders.Loader.prototype, { - before: { - get() - { - warn('The before() method is deprecated, please use pre().'); + Object.defineProperties(Loader.prototype, { + before: { + get() + { + warn('The before() method is deprecated, please use pre().'); - return this.pre; + return this.pre; + }, }, - }, - after: { - get() - { - warn('The after() method is deprecated, please use use().'); + after: { + get() + { + warn('The after() method is deprecated, please use use().'); - return this.use; + return this.use; + }, }, - }, -}); + }); +} /** * @name PIXI.interaction.interactiveTarget#defaultCursor diff --git a/src/extract/canvas/CanvasExtract.js b/src/extract/canvas/CanvasExtract.js index 941ca05..3ac5aee 100644 --- a/src/extract/canvas/CanvasExtract.js +++ b/src/extract/canvas/CanvasExtract.js @@ -8,7 +8,7 @@ * An instance of this class is automatically created by default, and can be found at renderer.plugins.extract * * @class - * @memberof PIXI + * @memberof PIXI.extract */ export default class CanvasExtract { @@ -21,9 +21,9 @@ /** * Collection of methods for extracting data (image, pixels, etc.) from a display object or render texture * - * @member {PIXI.CanvasExtract} extract + * @member {PIXI.extract.CanvasExtract} extract * @memberof PIXI.CanvasRenderer# - * @see PIXI.CanvasExtract + * @see PIXI.extract.CanvasExtract */ renderer.extract = this; } diff --git a/src/extract/index.js b/src/extract/index.js index 0f2170d..e9478c1 100644 --- a/src/extract/index.js +++ b/src/extract/index.js @@ -1,2 +1,22 @@ +/** + * This namespace provides renderer-specific plugins for exporting content from a renderer. + * For instance, these plugins can be used for saving an Image, Canvas element or for exporting the raw image data (pixels). + * + * Do not instantiate these plugins directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. + * @example + * // Create a new app (will auto-add extract plugin to renderer) + * const app = new PIXI.Application(); + * + * // Draw a red circle + * const graphics = new PIXI.Graphics() + * .beginFill(0xFF0000) + * .drawCircle(0, 0, 50); + * + * // Render the graphics as an HTMLImageElement + * const image = app.renderer.plugins.extract.image(graphics); + * document.body.appendChild(image); + * @namespace PIXI.extract + */ export { default as webgl } from './webgl/WebGLExtract'; export { default as canvas } from './canvas/CanvasExtract'; diff --git a/src/extract/webgl/WebGLExtract.js b/src/extract/webgl/WebGLExtract.js index 60228bb..1c3dd30 100644 --- a/src/extract/webgl/WebGLExtract.js +++ b/src/extract/webgl/WebGLExtract.js @@ -9,7 +9,7 @@ * An instance of this class is automatically created by default, and can be found at renderer.plugins.extract * * @class - * @memberof PIXI + * @memberof PIXI.extract */ export default class WebGLExtract { @@ -22,9 +22,9 @@ /** * Collection of methods for extracting data (image, pixels, etc.) from a display object or render texture * - * @member {PIXI.WebGLExtract} extract + * @member {PIXI.extract.WebGLExtract} extract * @memberof PIXI.WebGLRenderer# - * @see PIXI.WebGLExtract + * @see PIXI.extract.WebGLExtract */ renderer.extract = this; } diff --git a/src/extras/AnimatedSprite.js b/src/extras/AnimatedSprite.js index 13b804f..5297778 100644 --- a/src/extras/AnimatedSprite.js +++ b/src/extras/AnimatedSprite.js @@ -137,7 +137,7 @@ this.playing = true; if (this._autoUpdate) { - core.ticker.shared.add(this.update, this); + core.ticker.shared.add(this.update, this, core.UPDATE_PRIORITY.HIGH); } } diff --git a/src/extras/TextureTransform.js b/src/extras/TextureTransform.js index 78e6e40..16536d2 100644 --- a/src/extras/TextureTransform.js +++ b/src/extras/TextureTransform.js @@ -65,8 +65,36 @@ } /** + * Multiplies uvs array to transform + * @param {Float32Array} uvs mesh uvs + * @param {Float32Array} [out=uvs] output + * @returns {Float32Array} output + */ + multiplyUvs(uvs, out) + { + if (out === undefined) + { + out = uvs; + } + + const mat = this.mapCoord; + + for (let i = 0; i < uvs.length; i += 2) + { + const x = uvs[i]; + const y = uvs[i + 1]; + + out[i] = (x * mat.a) + (y * mat.c) + mat.tx; + out[i + 1] = (x * mat.b) + (y * mat.d) + mat.ty; + } + + return out; + } + + /** * updates matrices if texture was changed * @param {boolean} forceUpdate if true, matrices will be updated any case + * @returns {boolean} whether or not it was updated */ update(forceUpdate) { @@ -74,13 +102,13 @@ if (!tex || !tex.valid) { - return; + return false; } if (!forceUpdate && this._lastTextureID === tex._updateID) { - return; + return false; } this._lastTextureID = tex._updateID; @@ -110,5 +138,7 @@ frame[3] = (tex._frame.y + tex._frame.height - margin + offset) / texBase.height; this.uClampOffset[0] = offset / texBase.realWidth; this.uClampOffset[1] = offset / texBase.realHeight; + + return true; } } diff --git a/src/extras/TilingSprite.js b/src/extras/TilingSprite.js index 1f6014f..a3f027f 100644 --- a/src/extras/TilingSprite.js +++ b/src/extras/TilingSprite.js @@ -180,9 +180,9 @@ const transform = this.worldTransform; const resolution = renderer.resolution; const baseTexture = texture.baseTexture; - const baseTextureResolution = texture.baseTexture.resolution; - const modX = (this.tilePosition.x / this.tileScale.x) % texture._frame.width; - const modY = (this.tilePosition.y / this.tileScale.y) % texture._frame.height; + const baseTextureResolution = baseTexture.resolution; + const modX = ((this.tilePosition.x / this.tileScale.x) % texture._frame.width) * baseTextureResolution; + const modY = ((this.tilePosition.y / this.tileScale.y) % texture._frame.height) * baseTextureResolution; // create a nice shiny pattern! // TODO this needs to be refreshed if texture changes.. diff --git a/src/extras/cacheAsBitmap.js b/src/extras/cacheAsBitmap.js index 5dd5640..aea06a8 100644 --- a/src/extras/cacheAsBitmap.js +++ b/src/extras/cacheAsBitmap.js @@ -227,7 +227,16 @@ this.transform._parentID = -1; // restore the transform of the cached sprite to avoid the nasty flicker.. - this.updateTransform(); + if (!this.parent) + { + this.parent = renderer._tempDisplayObjectParent; + this.updateTransform(); + this.parent = null; + } + else + { + this.updateTransform(); + } // map the hit test.. this.containsPoint = cachedSprite.containsPoint.bind(cachedSprite); @@ -314,7 +323,17 @@ cachedSprite._bounds = this._bounds; cachedSprite.alpha = cacheAlpha; - this.updateTransform(); + if (!this.parent) + { + this.parent = renderer._tempDisplayObjectParent; + this.updateTransform(); + this.parent = null; + } + else + { + this.updateTransform(); + } + this.updateTransform = this.displayObjectUpdateTransform; this._cacheData.sprite = cachedSprite; @@ -358,9 +377,12 @@ * Destroys the cached object. * * @private + * @param {object|boolean} [options] - Options parameter. A boolean will act as if all options + * have been set to that value. + * Used when destroying containers, see the Container.destroy method. */ -DisplayObject.prototype._cacheAsBitmapDestroy = function _cacheAsBitmapDestroy() +DisplayObject.prototype._cacheAsBitmapDestroy = function _cacheAsBitmapDestroy(options) { this.cacheAsBitmap = false; - this.destroy(); + this.destroy(options); }; diff --git a/src/extras/index.js b/src/extras/index.js index bbd8bbf..a34d7ec 100644 --- a/src/extras/index.js +++ b/src/extras/index.js @@ -1,7 +1,7 @@ /** + * Additional PIXI DisplayObjects for animation, tiling and bitmap text. * @namespace PIXI.extras */ -export { default as TextureTransform } from './TextureTransform'; export { default as AnimatedSprite } from './AnimatedSprite'; export { default as TilingSprite } from './TilingSprite'; export { default as TilingSpriteRenderer } from './webgl/TilingSpriteRenderer'; diff --git a/src/extras/webgl/TilingSpriteRenderer.js b/src/extras/webgl/TilingSpriteRenderer.js index aeec172..eee3b5c 100644 --- a/src/extras/webgl/TilingSpriteRenderer.js +++ b/src/extras/webgl/TilingSpriteRenderer.js @@ -11,7 +11,7 @@ * WebGL renderer plugin for tiling sprites * * @class - * @memberof PIXI + * @memberof PIXI.extras * @extends PIXI.ObjectRenderer */ export default class TilingSpriteRenderer extends core.ObjectRenderer @@ -118,7 +118,7 @@ tempMat.invert(); if (isSimple) { - tempMat.append(uv.mapCoord); + tempMat.prepend(uv.mapCoord); } else { diff --git a/src/filters/displacement/DisplacementFilter.js b/src/filters/displacement/DisplacementFilter.js index d5355c3..b5d030c 100644 --- a/src/filters/displacement/DisplacementFilter.js +++ b/src/filters/displacement/DisplacementFilter.js @@ -35,7 +35,7 @@ this.maskSprite = sprite; this.maskMatrix = maskMatrix; - this.uniforms.mapSampler = sprite.texture; + this.uniforms.mapSampler = sprite._texture; this.uniforms.filterMatrix = maskMatrix; this.uniforms.scale = { x: 1, y: 1 }; diff --git a/src/filters/index.js b/src/filters/index.js index f3e8862..7ceb50a 100644 --- a/src/filters/index.js +++ b/src/filters/index.js @@ -1,4 +1,21 @@ /** + * This namespace contains WebGL-only display filters that can be applied + * to DisplayObjects using the {@link PIXI.DisplayObject#filters filters} property. + * @example + * // Create a new application + * const app = new PIXI.Application(); + * + * // Draw a green rectangle + * const rect = new PIXI.Graphics() + * .beginFill(0x00ff00) + * .drawRect(40, 40, 200, 200); + * + * // Add a blur filter + * rect.filters = [new PIXI.filters.BlurFilter()]; + * + * // Display rectangle + * app.stage.addChild(rect); + * document.body.appendChild(app.view); * @namespace PIXI.filters */ export { default as FXAAFilter } from './fxaa/FXAAFilter'; diff --git a/src/index.js b/src/index.js index c553427..77da490 100644 --- a/src/index.js +++ b/src/index.js @@ -20,6 +20,14 @@ import { utils } from './core'; utils.mixins.performMixins(); +/** + * Alias for {@link PIXI.loaders.shared}. + * @name loader + * @memberof PIXI + * @type {PIXI.loader.Loader} + */ +const loader = loaders.shared || null; + export { accessibility, extract, @@ -30,18 +38,8 @@ mesh, particles, prepare, + loader, }; -/** - * A premade instance of the loader that can be used to load resources. - * - * @name loader - * @memberof PIXI - * @property {PIXI.loaders.Loader} - */ -const loader = loaders && loaders.Loader ? new loaders.Loader() : null; // check is there in case user excludes loader lib - -export { loader }; - // Always export pixi globally. global.PIXI = exports; // eslint-disable-line diff --git a/src/interaction/InteractionData.js b/src/interaction/InteractionData.js index 02d53a3..449f8cc 100644 --- a/src/interaction/InteractionData.js +++ b/src/interaction/InteractionData.js @@ -30,7 +30,10 @@ /** * When passed to an event handler, this will be the original DOM Event that was captured * - * @member {Event} + * @see https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent + * @see https://developer.mozilla.org/en-US/docs/Web/API/TouchEvent + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent + * @member {MouseEvent|TouchEvent|PointerEvent} */ this.originalEvent = null; } diff --git a/src/interaction/InteractionManager.js b/src/interaction/InteractionManager.js index 8baf13a..2d75c7c 100644 --- a/src/interaction/InteractionManager.js +++ b/src/interaction/InteractionManager.js @@ -13,6 +13,14 @@ const MOUSE_POINTER_ID = 'MOUSE'; +// helpers for hitTest() - only used inside hitTest() +const hitTestEvent = { + target: null, + data: { + global: null, + }, +}; + /** * The interaction manager deals with mouse, touch and pointer events. Any DisplayObject can be interactive * if its interactive parameter is set to true @@ -245,67 +253,60 @@ this.setTargetElement(this.renderer.view, this.renderer.resolution); /** - * Fired when a pointer device button (usually a mouse button) is pressed on the display + * Fired when a pointer device button (usually a mouse left-button) is pressed on the display * object. * - * @event mousedown - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mousedown + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device secondary button (usually a mouse right-button) is pressed * on the display object. * - * @event rightdown - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightdown + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** - * Fired when a pointer device button (usually a mouse button) is released over the display + * Fired when a pointer device button (usually a mouse left-button) is released over the display * object. * - * @event mouseup - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseup + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device secondary button (usually a mouse right-button) is released * over the display object. * - * @event rightup - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightup + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** - * Fired when a pointer device button (usually a mouse button) is pressed and released on + * Fired when a pointer device button (usually a mouse left-button) is pressed and released on * the display object. * - * @event click - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#click + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device secondary button (usually a mouse right-button) is pressed * and released on the display object. * - * @event rightclick - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightclick + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** - * Fired when a pointer device button (usually a mouse button) is released outside the + * Fired when a pointer device button (usually a mouse left-button) is released outside the * display object that initially registered a * [mousedown]{@link PIXI.interaction.InteractionManager#event:mousedown}. * - * @event mouseupoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** @@ -313,146 +314,362 @@ * outside the display object that initially registered a * [rightdown]{@link PIXI.interaction.InteractionManager#event:rightdown}. * - * @event rightupoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device (usually a mouse) is moved while over the display object * - * @event mousemove - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mousemove + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device (usually a mouse) is moved onto the display object * - * @event mouseover - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseover + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device (usually a mouse) is moved off the display object * - * @event mouseout - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseout + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is pressed on the display object. * - * @event pointerdown - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerdown + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is released over the display object. * - * @event pointerup - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerup + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when the operating system cancels a pointer event * - * @event pointercancel - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointercancel + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is pressed and released on the display object. * - * @event pointertap - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointertap + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is released outside the display object that initially * registered a [pointerdown]{@link PIXI.interaction.InteractionManager#event:pointerdown}. * - * @event pointerupoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device is moved while over the display object * - * @event pointermove - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointermove + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device is moved onto the display object * - * @event pointerover - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerover + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device is moved off the display object * - * @event pointerout - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerout + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is placed on the display object. * - * @event touchstart - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchstart + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is removed from the display object. * - * @event touchend - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchend + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when the operating system cancels a touch * - * @event touchcancel - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchcancel + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is placed and removed from the display object. * - * @event tap - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#tap + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is removed outside of the display object that initially * registered a [touchstart]{@link PIXI.interaction.InteractionManager#event:touchstart}. * - * @event touchendoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchendoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is moved along the display object. * - * @event touchmove - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchmove + * @param {PIXI.interaction.InteractionData} event - Interaction data */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is pressed on the display. + * object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mousedown + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is pressed + * on the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightdown + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is released over the display + * object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseup + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is released + * over the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightup + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is pressed and released on + * the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#click + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is pressed + * and released on the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightclick + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is released outside the + * display object that initially registered a + * [mousedown]{@link PIXI.DisplayObject#event:mousedown}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is released + * outside the display object that initially registered a + * [rightdown]{@link PIXI.DisplayObject#event:rightdown}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device (usually a mouse) is moved while over the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mousemove + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device (usually a mouse) is moved onto the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseover + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device (usually a mouse) is moved off the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseout + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is pressed on the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerdown + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is released over the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerup + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when the operating system cancels a pointer event. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointercancel + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is pressed and released on the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointertap + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is released outside the display object that initially + * registered a [pointerdown]{@link PIXI.DisplayObject#event:pointerdown}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device is moved while over the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointermove + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device is moved onto the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerover + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device is moved off the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerout + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is placed on the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchstart + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is removed from the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchend + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when the operating system cancels a touch. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchcancel + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is placed and removed from the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#tap + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is removed outside of the display object that initially + * registered a [touchstart]{@link PIXI.DisplayObject#event:touchstart}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchendoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is moved along the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchmove + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + } + + /** + * Hit tests a point against the display tree, returning the first interactive object that is hit. + * + * @param {PIXI.Point} globalPoint - A point to hit test with, in global space. + * @param {PIXI.Container} [root] - The root display object to start from. If omitted, defaults + * to the last rendered root of the associated renderer. + * @return {PIXI.DisplayObject} The hit display object, if any. + */ + hitTest(globalPoint, root) + { + // clear the target for our hit test + hitTestEvent.target = null; + // assign the global point + hitTestEvent.data.global = globalPoint; + // ensure safety of the root + if (!root) + { + root = this.renderer._lastObjectRendered; + } + // run the hit test + this.processInteractive(hitTestEvent, root, null, true); + // return our found object - it'll be null if we didn't hit anything + + return hitTestEvent.target; } /** @@ -487,7 +704,7 @@ return; } - core.ticker.shared.add(this.update, this); + core.ticker.shared.add(this.update, this, core.UPDATE_PRIORITY.INTERACTION); if (window.navigator.msPointerEnabled) { @@ -804,8 +1021,6 @@ } } - let keepHitTestingAfterChildren = hitTest; - // ** FREE TIP **! If an object is not interactive or has no buttons in it // (such as a game scene!) set interactiveChildren to false for that displayObject. // This will allow pixi to completely ignore and bypass checking the displayObjects children. @@ -818,7 +1033,9 @@ const child = children[i]; // time to get recursive.. if this function will return if something is hit.. - if (this.processInteractive(interactionEvent, child, func, hitTest, interactiveParent)) + const childHit = this.processInteractive(interactionEvent, child, func, hitTest, interactiveParent); + + if (childHit) { // its a good idea to check if a child has lost its parent. // this means it has been removed whilst looping so its best @@ -827,8 +1044,6 @@ continue; } - hit = true; - // we no longer need to hit test any more objects in this container as we we // now know the parent has been hit interactiveParent = false; @@ -838,36 +1053,41 @@ // This means we no longer need to hit test anything else. We still need to run // through all objects, but we don't need to perform any hit tests. - keepHitTestingAfterChildren = false; - - if (child.interactive) + if (childHit) { - hitTest = false; + if (interactionEvent.target) + { + hitTest = false; + } + hit = true; } - - // we can break now as we have hit an object. } } } - hitTest = keepHitTestingAfterChildren; - // no point running this if the item is not interactive or does not have an interactive parent. if (interactive) { // if we are hit testing (as in we have no hit any objects yet) // We also don't need to worry about hit testing if once of the displayObjects children - // has already been hit! - if (hitTest && !hit) + // has already been hit - but only if it was interactive, otherwise we need to keep + // looking for an interactive child, just in case we hit one + if (hitTest && !interactionEvent.target) { if (displayObject.hitArea) { displayObject.worldTransform.applyInverse(point, this._tempPoint); - hit = displayObject.hitArea.contains(this._tempPoint.x, this._tempPoint.y); + if (displayObject.hitArea.contains(this._tempPoint.x, this._tempPoint.y)) + { + hit = true; + } } else if (displayObject.containsPoint) { - hit = displayObject.containsPoint(point); + if (displayObject.containsPoint(point)) + { + hit = true; + } } } @@ -878,7 +1098,10 @@ interactionEvent.target = displayObject; } - func(interactionEvent, displayObject, hit); + if (func) + { + func(interactionEvent, displayObject, !!hit); + } } } @@ -914,7 +1137,7 @@ { const event = events[i]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -998,7 +1221,7 @@ { const event = events[i]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1098,7 +1321,7 @@ const test = isRightButton ? flags.RIGHT_DOWN : flags.LEFT_DOWN; - const isDown = trackingData !== undefined && (trackingData.flags | test); + const isDown = trackingData !== undefined && (trackingData.flags & test); if (hit) { @@ -1118,11 +1341,11 @@ { if (isRightButton) { - trackingData.rightDown = hit; + trackingData.rightDown = false; } else { - trackingData.leftDown = hit; + trackingData.leftDown = false; } } } @@ -1180,7 +1403,7 @@ { const event = events[i]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1255,7 +1478,7 @@ this.setCursorMode(null); } - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1268,6 +1491,12 @@ { this.emit('mouseout', interactionEvent); } + else + { + // we can get touchleave events after touchend, so we want to make sure we don't + // introduce memory leaks + this.releaseInteractionDataForPointerId(interactionData.identifier); + } } /** @@ -1344,7 +1573,7 @@ // Only mouse and pointer can call onPointerOver, so events will always be length 1 const event = events[0]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1366,12 +1595,14 @@ * Get InteractionData for a given pointerId. Store that data as well * * @private - * @param {number} pointerId - Identifier from a pointer event + * @param {PointerEvent} event - Normalized pointer event, output from normalizeToPointerData * @return {InteractionData} - Interaction data for the given pointer identifier */ - getInteractionDataForPointerId(pointerId) + getInteractionDataForPointerId(event) { - if (pointerId === MOUSE_POINTER_ID) + const pointerId = event.pointerId; + + if (pointerId === MOUSE_POINTER_ID || event.pointerType === 'mouse') { return this.mouse; } diff --git a/src/interaction/InteractionTrackingData.js b/src/interaction/InteractionTrackingData.js index 6a1f69e..8493b63 100644 --- a/src/interaction/InteractionTrackingData.js +++ b/src/interaction/InteractionTrackingData.js @@ -35,8 +35,10 @@ } /** + * Unique pointer id of the event + * * @readonly - * @type {number} Unique pointer id of the event + * @member {number} */ get pointerId() { diff --git a/src/interaction/index.js b/src/interaction/index.js index 56c5b09..72f7d71 100644 --- a/src/interaction/index.js +++ b/src/interaction/index.js @@ -1,4 +1,8 @@ /** + * This namespace contains a renderer plugin for handling mouse, pointer, and touch events. + * + * Do not instantiate this plugin directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. * @namespace PIXI.interaction */ export { default as InteractionData } from './InteractionData'; diff --git a/src/interaction/interactiveTarget.js b/src/interaction/interactiveTarget.js index d252fba..cebac40 100644 --- a/src/interaction/interactiveTarget.js +++ b/src/interaction/interactiveTarget.js @@ -2,7 +2,7 @@ * Default property values of interactive objects * Used by {@link PIXI.interaction.InteractionManager} to automatically give all DisplayObjects these properties * - * @mixin + * @private * @name interactiveTarget * @memberof PIXI.interaction * @example @@ -14,18 +14,28 @@ * ); */ export default { + /** - * Determines if the displayObject be clicked/touched + * Enable interaction events for the DisplayObject. Touch, pointer and mouse + * events will not be emitted unless `interactive` is set to `true`. * - * @inner {boolean} + * @example + * const sprite = new PIXI.Sprite(texture); + * sprite.interactive = true; + * sprite.on('tap', (event) => { + * //handle event + * }); + * @member {boolean} + * @memberof PIXI.DisplayObject# */ interactive: false, /** * Determines if the children to the displayObject can be clicked/touched - * Setting this to false allows pixi to bypass a recursive hitTest function + * Setting this to false allows pixi to bypass a recursive `hitTest` function * - * @inner {boolean} + * @member {boolean} + * @memberof PIXI.Container# */ interactiveChildren: true, @@ -33,7 +43,12 @@ * Interaction shape. Children will be hit first, then this shape will be checked. * Setting this will cause this shape to be checked in hit tests rather than the displayObject's bounds. * - * @inner {PIXI.Rectangle|PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.RoundedRectangle} + * @example + * const sprite = new PIXI.Sprite(texture); + * sprite.interactive = true; + * sprite.hitArea = new PIXI.Rectangle(0, 0, 100, 100); + * @member {PIXI.Rectangle|PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.RoundedRectangle} + * @memberof PIXI.DisplayObject# */ hitArea: null, @@ -41,8 +56,12 @@ * If enabled, the mouse cursor use the pointer behavior when hovered over the displayObject if it is interactive * Setting this changes the 'cursor' property to `'pointer'`. * + * @example + * const sprite = new PIXI.Sprite(texture); + * sprite.interactive = true; + * sprite.buttonMode = true; * @member {boolean} - * @memberof PIXI.interaction.interactiveTarget# + * @memberof PIXI.DisplayObject# */ get buttonMode() { @@ -64,9 +83,14 @@ * This defines what cursor mode is used when the mouse cursor * is hovered over the displayObject. * + * @example + * const sprite = new PIXI.Sprite(texture); + * sprite.interactive = true; + * sprite.cursor = 'wait'; * @see https://developer.mozilla.org/en/docs/Web/CSS/cursor * - * @inner {string} + * @member {string} + * @memberof PIXI.DisplayObject# */ cursor: null, @@ -74,7 +98,7 @@ * Internal set of all active pointers, by identifier * * @member {Map} - * @memberof PIXI.interaction.interactiveTarget# + * @memberof PIXI.DisplayObject# * @private */ get trackedPointers() @@ -87,7 +111,8 @@ /** * Map of all tracked pointers, by identifier. Use trackedPointers to access. * - * @private {Map} + * @private + * @type {Map} */ _trackedPointers: undefined, }; diff --git a/src/loaders/index.js b/src/loaders/index.js index 5189793..daa1fc4 100644 --- a/src/loaders/index.js +++ b/src/loaders/index.js @@ -1,9 +1,22 @@ +import Application from '../core/Application'; +import Loader from './loader'; + /** + * This namespace contains APIs which extends the {@link https://github.com/englercj/resource-loader resource-loader} module + * for loading assets, data, and other resources dynamically. + * @example + * const loader = new PIXI.loaders.Loader(); + * loader.add('bunny', 'data/bunny.png') + * .add('spaceship', 'assets/spritesheet.json'); + * loader.load((loader, resources) => { + * // resources.bunny + * // resources.spaceship + * }); * @namespace PIXI.loaders */ -export { default as Loader } from './loader'; +export { Loader }; export { default as bitmapFontParser, parse as parseBitmapFontData } from './bitmapFontParser'; -export { default as spritesheetParser } from './spritesheetParser'; +export { default as spritesheetParser, getResourcePath } from './spritesheetParser'; export { default as textureParser } from './textureParser'; /** @@ -13,3 +26,55 @@ * @memberof PIXI.loaders */ export { Resource } from 'resource-loader'; + +/** + * A premade instance of the loader that can be used to load resources. + * @name shared + * @memberof PIXI.loaders + * @type {PIXI.loaders.Loader} + */ +const shared = new Loader(); + +shared.destroy = () => +{ + // protect destroying shared loader +}; + +export { shared }; + +// Mixin the loader construction +const AppPrototype = Application.prototype; + +AppPrototype._loader = null; + +/** + * Loader instance to help with asset loading. + * @name PIXI.Application#loader + * @type {PIXI.loaders.Loader} + */ +Object.defineProperty(AppPrototype, 'loader', { + get() + { + if (!this._loader) + { + const sharedLoader = this._options.sharedLoader; + + this._loader = sharedLoader ? shared : new Loader(); + } + + return this._loader; + }, +}); + +// Override the destroy function +// making sure to destroy the current Loader +AppPrototype._parentDestroy = AppPrototype.destroy; +AppPrototype.destroy = function destroy() +{ + if (this._loader) + { + this._loader.destroy(); + this._loader = null; + } + this._parentDestroy(); +}; diff --git a/src/loaders/loader.js b/src/loaders/loader.js index 767a411..59b8000 100644 --- a/src/loaders/loader.js +++ b/src/loaders/loader.js @@ -90,6 +90,15 @@ { Loader._pixiMiddleware.push(fn); } + + /** + * Destroy the loader, removes references. + */ + destroy() + { + this.removeAllListeners(); + this.reset(); + } } // Copy EE3 prototype (mixin) diff --git a/src/loaders/spritesheetParser.js b/src/loaders/spritesheetParser.js index e57dfb2..eda9584 100644 --- a/src/loaders/spritesheetParser.js +++ b/src/loaders/spritesheetParser.js @@ -1,12 +1,11 @@ import { Resource } from 'resource-loader'; -import path from 'path'; +import url from 'url'; import { Spritesheet } from '../core'; export default function () { return function spritesheetParser(resource, next) { - let resourcePath; const imageResourceName = `${resource.name}_image`; // skip if no data, its not json, it isn't spritesheet data, or the image resource already exists @@ -28,15 +27,7 @@ parentResource: resource, }; - // Prepend url path unless the resource image is a data url - if (resource.isDataUrl) - { - resourcePath = resource.data.meta.image; - } - else - { - resourcePath = `${path.dirname(resource.url.replace(this.baseUrl, ''))}/${resource.data.meta.image}`; - } + const resourcePath = getResourcePath(resource, this.baseUrl); // load the image for this sheet this.add(imageResourceName, resourcePath, loadOptions, function onImageLoad(res) @@ -56,3 +47,14 @@ }); }; } + +export function getResourcePath(resource, baseUrl) +{ + // Prepend url path unless the resource image is a data url + if (resource.isDataUrl) + { + return resource.data.meta.image; + } + + return url.resolve(resource.url.replace(baseUrl, ''), resource.data.meta.image); +} diff --git a/src/mesh/canvas/CanvasMeshRenderer.js b/src/mesh/canvas/CanvasMeshRenderer.js index 297205f..f7899e1 100644 --- a/src/mesh/canvas/CanvasMeshRenderer.js +++ b/src/mesh/canvas/CanvasMeshRenderer.js @@ -134,12 +134,33 @@ const textureWidth = base.width; const textureHeight = base.height; - const u0 = uvs[index0] * base.width; - const u1 = uvs[index1] * base.width; - const u2 = uvs[index2] * base.width; - const v0 = uvs[index0 + 1] * base.height; - const v1 = uvs[index1 + 1] * base.height; - const v2 = uvs[index2 + 1] * base.height; + let u0; + let u1; + let u2; + let v0; + let v1; + let v2; + + if (mesh.uploadUvTransform) + { + const ut = mesh._uvTransform.mapCoord; + + u0 = ((uvs[index0] * ut.a) + (uvs[index0 + 1] * ut.c) + ut.tx) * base.width; + u1 = ((uvs[index1] * ut.a) + (uvs[index1 + 1] * ut.c) + ut.tx) * base.width; + u2 = ((uvs[index2] * ut.a) + (uvs[index2 + 1] * ut.c) + ut.tx) * base.width; + v0 = ((uvs[index0] * ut.b) + (uvs[index0 + 1] * ut.d) + ut.ty) * base.height; + v1 = ((uvs[index1] * ut.b) + (uvs[index1 + 1] * ut.d) + ut.ty) * base.height; + v2 = ((uvs[index2] * ut.b) + (uvs[index2 + 1] * ut.d) + ut.ty) * base.height; + } + else + { + u0 = uvs[index0] * base.width; + u1 = uvs[index1] * base.width; + u2 = uvs[index2] * base.width; + v0 = uvs[index0 + 1] * base.height; + v1 = uvs[index1 + 1] * base.height; + v2 = uvs[index2 + 1] * base.height; + } let x0 = vertices[index0]; let x1 = vertices[index1]; diff --git a/src/mesh/webgl/MeshRenderer.js b/src/mesh/webgl/MeshRenderer.js index 78d24c3..ab99fcd 100644 --- a/src/mesh/webgl/MeshRenderer.js +++ b/src/mesh/webgl/MeshRenderer.js @@ -2,6 +2,8 @@ const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; +const matrixIdentity = core.Matrix.IDENTITY; + /** * WebGL renderer plugin for tiling sprites * @@ -64,6 +66,7 @@ mesh.geometry.glVertexArrayObjects[this.CONTEXT_UID].draw(mesh.drawMode, mesh.size, mesh.start, mesh.geometry.instanceCount); } + /** * draws mesh * @param {PIXI.mesh.RawMesh} mesh mesh instance diff --git a/.travis.yml b/.travis.yml index 3df8b22..14de334 100644 --- a/.travis.yml +++ b/.travis.yml @@ -110,3 +110,43 @@ on: all_branches: true condition: $TRAVIS_TAG + # Deploy config for latest release + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: dist + upload-dir: "release" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: docs + upload-dir: "release/docs" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: coverage + upload-dir: "release/coverage" + on: + all_branches: true + condition: $TRAVIS_TAG \ No newline at end of file diff --git a/README.md b/README.md index d26fadd..3b85f85 100644 --- a/README.md +++ b/README.md @@ -36,22 +36,14 @@ - Wiki: Other misc tutorials and resources are [on the Wiki](https://github.com/pixijs/pixi.js/wiki/Resources). ### Community ### -- Forums: Check out the [forum] (http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow] (http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. +- Forums: Check out the [forum](http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow](http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. - Inspiration: Check out the [gallery](http://www.pixijs.com/gallery) to see some of the amazing things people have created! - Chat: You can join us on [Gitter](https://gitter.im/pixijs/pixi.js) To chat about Pixi. We also now have a Slack channel. If you would like to join it please Send me an email (mat@goodboydigital.com) and I will invite you in. ### Setup ### -It's easy to get started with Pixi.js! Simply grab the pre-built versions from here: - -Release Branch - Nice and stable Pixi.js -- Unminified: [http://pixijs.download/release/pixi.js] -- Minified: [http://pixijs.download/release/pixi.min.js] - -Develop Branch - The bleeding edge version of Pixi.js -- Unminified: [http://pixijs.download/dev/pixi.js] -- Minified: [http://pixijs.download/dev/pixi.min.js] +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. @@ -70,10 +62,10 @@ #### CDN Install (via cdnjs) ```html - + ``` -_Note: `4.2.2` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ +_Note: `4.4.3` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ ### Demos ### @@ -130,7 +122,7 @@ // and the root stage PIXI.Container. var app = new PIXI.Application(); -// The application will create a canvas element for you that you +// The application will create a canvas element for you that you // can then insert into the DOM. document.body.appendChild(app.view); @@ -150,7 +142,7 @@ // Add the bunny to the scene we are building. app.stage.addChild(bunny); - + // Listen for frame updates app.ticker.add(function() { // each frame we spin the bunny around a bit diff --git a/bower.json b/bower.json deleted file mode 100644 index 41b668c..0000000 --- a/bower.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "pixi.js", - "main": "dist/pixi.min.js", - "ignore": [ - "**/.*", - "docs", - "node_modules", - "bower_components", - "test", - "scripts", - "bower.json", - "inch.json" - ], - "dependencies": { - }, - "devDependencies": { - } -} diff --git a/package.json b/package.json index d35cbb2..308469f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.3.5", + "version": "4.4.4", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ @@ -40,11 +40,11 @@ "lib": "babel src --out-dir lib -s", "predocs": "rimraf docs/**", "docs": "jsdoc -c scripts/jsdoc.conf.json -R README.md", - "publish:patch": "npm version patch --no-git-tag-version && npm publish", - "publish:minor": "npm version minor --no-git-tag-version && npm publish", - "publish:major": "npm version major --no-git-tag-version && npm publish", + "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", - "postpublish": "node scripts/release.js" + "postpublish": "git push && git push --tags" }, "files": [ "dist/", @@ -72,7 +72,6 @@ "electron": "^1.4.15", "eslint": "^3.5.0", "floss": "^2.0.1", - "gh-pages": "^0.11.0", "jaguarjs-jsdoc": "^1.0.1", "js-md5": "^0.4.1", "jsdoc": "^3.4.2", diff --git a/scripts/jsdoc.conf.json b/scripts/jsdoc.conf.json index 553e61c..3f52180 100644 --- a/scripts/jsdoc.conf.json +++ b/scripts/jsdoc.conf.json @@ -43,7 +43,7 @@ }, "markdown" : { "parser" : "gfm", - "hardwrap" : true + "hardwrap" : false }, "opts": { "encoding" : "utf8", diff --git a/scripts/release.js b/scripts/release.js deleted file mode 100644 index 156620c..0000000 --- a/scripts/release.js +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env node -'use strict'; - -// Publish script to push releases of the bin files -// the normally are gitignored -const ghpages = require('gh-pages'); -const path = require('path'); -const packageInfo = require(path.join(__dirname, '..', 'package.json')); - -const options = { - src: [ - 'dist/**/*', - 'lib/**/*', - 'src/**/*', - 'scripts/*', - 'scripts/renders/*', - 'test/**/*', - '*.json', - '*.md', - 'LICENSE', - '.eslintrc', - '.editorconfig', - '.travis.yml', - '.babelrc', - ], - dotfiles: true, - branch: 'release', - message: packageInfo.version, - logger: console.log.bind(console), -}; - -ghpages.publish(process.cwd(), options, (err) => -{ - if (err) - { - console.log(err); - process.exit(1); - - return; - } - - process.exit(0); -}); diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index f16bcb1..220427a 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -21,7 +21,7 @@ const DIV_HOOK_ZINDEX = 2; /** - * The Accessibility manager reacreates the ability to tab and and have content read by screen + * The Accessibility manager recreates the ability to tab and and have content read by screen * readers. This is very important as it can possibly help people with disabilities access pixi * content. * diff --git a/src/accessibility/index.js b/src/accessibility/index.js index 568cfb1..0ae108f 100644 --- a/src/accessibility/index.js +++ b/src/accessibility/index.js @@ -1,4 +1,9 @@ /** + * This namespace contains a renderer plugin for interaction accessibility for end-users + * with physical impairments which require screen-renders, keyboard navigation, etc. + * + * Do not instantiate this plugin directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. * @namespace PIXI.accessibility */ export { default as accessibleTarget } from './accessibleTarget'; diff --git a/src/core/Application.js b/src/core/Application.js index 5340578..2fcc729 100644 --- a/src/core/Application.js +++ b/src/core/Application.js @@ -1,6 +1,8 @@ import { autoDetectRenderer } from './autoDetectRenderer'; import Container from './display/Container'; import { shared, Ticker } from './ticker'; +import settings from './settings'; +import { UPDATE_PRIORITY } from './const'; /** * Convenience class to create a new PIXI application. @@ -22,28 +24,52 @@ */ export default class Application { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experience unexplained flickering try setting this to true. - * @param {boolean} [useSharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedLoader=false] - `true` to use PIXI.loaders.shared, `false` to create new Loader. */ - constructor(width, height, options, noWebGL, useSharedTicker = false) + constructor(options, arg2, arg3, arg4, arg5) { + // Support for constructor(width, height, options, noWebGL, useSharedTicker) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + forceCanvas: !!arg4, + sharedTicker: !!arg5, + }, arg3); + } + + /** + * The default options, so we mixin functionality later. + * @member {object} + * @protected + */ + this._options = options = Object.assign({ + sharedTicker: false, + forceCanvas: false, + sharedLoader: false, + }, options); + /** * WebGL renderer if available, otherwise CanvasRenderer * @member {PIXI.WebGLRenderer|PIXI.CanvasRenderer} */ - this.renderer = autoDetectRenderer(width, height, options, noWebGL); + this.renderer = autoDetectRenderer(options); /** * The root display container that's rendered. @@ -63,7 +89,7 @@ * @member {PIXI.ticker.Ticker} * @default PIXI.ticker.shared */ - this.ticker = useSharedTicker ? shared : new Ticker(); + this.ticker = options.sharedTicker ? shared : new Ticker(); // Start the rendering this.start(); @@ -78,7 +104,7 @@ this._ticker = ticker; if (ticker) { - ticker.add(this.render, this); + ticker.add(this.render, this, UPDATE_PRIORITY.LOW); } } get ticker() // eslint-disable-line require-jsdoc @@ -136,13 +162,18 @@ */ destroy(removeView) { - this.stop(); + const oldTicker = this._ticker; + this.ticker = null; + oldTicker.destroy(); + this.stage.destroy(); this.stage = null; this.renderer.destroy(removeView); this.renderer = null; + + this._options = null; } } diff --git a/src/core/autoDetectRenderer.js b/src/core/autoDetectRenderer.js index 8960425..9ec0364 100644 --- a/src/core/autoDetectRenderer.js +++ b/src/core/autoDetectRenderer.js @@ -2,6 +2,7 @@ import CanvasRenderer from './renderers/canvas/CanvasRenderer'; import WebGLRenderer from './renderers/webgl/WebGLRenderer'; +// eslint-disable-next-line valid-jsdoc /** * This helper function will automatically detect which renderer you should be using. * WebGL is the preferred renderer as it is a lot faster. If webGL is not supported by @@ -9,24 +10,32 @@ * * @memberof PIXI * @function autoDetectRenderer - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @return {PIXI.WebGLRenderer|PIXI.CanvasRenderer} Returns WebGL renderer if available, otherwise CanvasRenderer */ -export function autoDetectRenderer(width = 800, height = 600, options, noWebGL) +export function autoDetectRenderer(options, arg1, arg2, arg3) { - if (!noWebGL && utils.isWebGLSupported()) + // Backward-compatible support for noWebGL option + let forceCanvas = options && options.forceCanvas; + + if (arg3 !== undefined) { - return new WebGLRenderer(width, height, options); + forceCanvas = arg3; } - return new CanvasRenderer(width, height, options); + if (!forceCanvas && utils.isWebGLSupported()) + { + return new WebGLRenderer(options, arg1, arg2); + } + + return new CanvasRenderer(options, arg1, arg2); } diff --git a/src/core/const.js b/src/core/const.js index b9e99ae..2c68dee 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -341,3 +341,27 @@ LINEAR_VERTICAL: 0, LINEAR_HORIZONTAL: 1, }; + +/** + * Represents the update priorities used by internal PIXI classes when registered with + * the {@link PIXI.ticker.Ticker} object. Higher priority items are updated first and lower + * priority items, such as render, should go later. + * + * @static + * @constant + * @name UPDATE_PRIORITY + * @memberof PIXI + * @type {object} + * @property {number} INTERACTION=50 Highest priority, used for {@link PIXI.interaction.InteractionManager} + * @property {number} HIGH=25 High priority updating, {@link PIXI.VideoBaseTexture} and {@link PIXI.extras.AnimatedSprite} + * @property {number} NORMAL=0 Default priority for ticker events, see {@link PIXI.ticker.Ticker#add}. + * @property {number} LOW=-25 Low priority used for {@link PIXI.Application} rendering. + * @property {number} UTILITY=-50 Lowest priority used for {@link PIXI.prepare.BasePrepare} utility. + */ +export const UPDATE_PRIORITY = { + INTERACTION: 50, + HIGH: 25, + NORMAL: 0, + LOW: -25, + UTILITY: -50, +}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 4e6c77d..decb42a 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -13,7 +13,6 @@ * * @class * @extends EventEmitter - * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ export default class DisplayObject extends EventEmitter @@ -113,6 +112,29 @@ * @private */ this._mask = null; + + /** + * If the object has been destroyed via destroy(). If true, it should not be used. + * + * @member {boolean} + * @private + * @readonly + */ + this._destroyed = false; + + /** + * Fired when this DisplayObject is added to a Container. + * + * @event PIXI.DisplayObject#added + * @param {PIXI.Container} container - The container added to. + */ + + /** + * Fired when this DisplayObject is removed from a Container. + * + * @event PIXI.DisplayObject#removed + * @param {PIXI.Container} container - The container removed from. + */ } /** @@ -403,6 +425,8 @@ this.interactive = false; this.interactiveChildren = false; + + this._destroyed = true; } /** diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index aafdfe2..f787c4a 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -792,8 +792,8 @@ sprite.worldAlpha = this.worldAlpha * sprite.alpha; sprite.blendMode = this.blendMode; - sprite.texture._frame.width = rect.width; - sprite.texture._frame.height = rect.height; + sprite._texture._frame.width = rect.width; + sprite._texture._frame.height = rect.height; sprite.transform.worldTransform = this.transform.worldTransform; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index a24c775..b9f192d 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -19,11 +19,12 @@ */ export default class SystemRenderer extends EventEmitter { + // eslint-disable-next-line valid-jsdoc /** * @param {string} system - The name of the system this renderer is for. - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -37,27 +38,31 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(system, screenWidth, screenHeight, options) + constructor(system, options, arg2, arg3) { super(); sayHello(system); - // prepare options - if (options) + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') { - for (const i in settings.RENDER_OPTIONS) - { - if (typeof options[i] === 'undefined') - { - options[i] = settings.RENDER_OPTIONS[i]; - } - } + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); } - else - { - options = settings.RENDER_OPTIONS; - } + + // Add the default render options + options = Object.assign({}, settings.RENDER_OPTIONS, options); + + /** + * The supplied constructor options. + * + * @member {Object} + * @readOnly + */ + this.options = options; /** * The type of the renderer. @@ -75,7 +80,7 @@ * * @member {PIXI.Rectangle} */ - this.screen = new Rectangle(0, 0, screenWidth || 800, screenHeight || 600); + this.screen = new Rectangle(0, 0, options.width, options.height); /** * The canvas element that everything is drawn to @@ -279,6 +284,8 @@ this.blendModes = null; + this.options = null; + this.preserveDrawingBuffer = false; this.clearBeforeRender = false; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index e84e49c..691caf0 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -17,10 +17,11 @@ */ export default class CanvasRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -34,9 +35,9 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('Canvas', screenWidth, screenHeight, options); + super('Canvas', options, arg2, arg3); this.type = RENDERER_TYPE.CANVAS; @@ -96,7 +97,19 @@ this.context = null; this.renderingToScreen = false; - this.resize(screenWidth, screenHeight); + this.resize(this.options.width, this.options.height); + + /** + * Fired after rendering finishes. + * + * @event PIXI.CanvasRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.CanvasRenderer#prerender + */ } /** @@ -299,4 +312,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.CanvasRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.CanvasExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.CanvasPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.CanvasRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index e1cf7cd..f6fe123 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -31,11 +31,12 @@ */ export default class WebGLRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** * - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -55,9 +56,9 @@ * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('WebGL', screenWidth, screenHeight, options); + super('WebGL', options, arg2, arg3); /** * The type of this renderer as a standardised const @@ -89,12 +90,6 @@ }; - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ this._backgroundColorRgba[3] = this.transparent ? 0 : 1; this.globalUniforms = new UniformGroup({ @@ -117,6 +112,13 @@ this.initPlugins(); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ if(options.context) { this.context.initFromContext(options.context); @@ -169,6 +171,25 @@ } return this; + + /** + * Fired after rendering finishes. + * + * @event PIXI.WebGLRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.WebGLRenderer#prerender + */ + + /** + * Fired when the WebGL context is set. + * + * @event PIXI.WebGLRenderer#context + * @param {WebGLRenderingContext} gl - WebGL context. + */ } /** @@ -295,4 +316,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.WebGLRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.WebGLRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(WebGLRenderer); diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index 4eb6128..fa3d883 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -47,6 +47,14 @@ * @member {boolean} */ this.enabled = true; + + /** + * If enabled, pixi will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; } /** diff --git a/src/core/renderers/webgl/systems/ContextSystem.js b/src/core/renderers/webgl/systems/ContextSystem.js index ff958c7..8df85ea 100644 --- a/src/core/renderers/webgl/systems/ContextSystem.js +++ b/src/core/renderers/webgl/systems/ContextSystem.js @@ -75,7 +75,8 @@ */ createContext(canvas, options) { - var gl = canvas.getContext('webgl', options) || + var gl = //canvas.getContext('webgl2', options) || + canvas.getContext('webgl', options) || canvas.getContext('experimental-webgl', options); if (!gl) diff --git a/src/core/renderers/webgl/systems/FilterSystem.js b/src/core/renderers/webgl/systems/FilterSystem.js index f47335d..37e28d2 100644 --- a/src/core/renderers/webgl/systems/FilterSystem.js +++ b/src/core/renderers/webgl/systems/FilterSystem.js @@ -111,7 +111,7 @@ // TODO we should fit the rect around the transform.. } - else + else if (filters[0].autoFit) { sourceFrame.fit(filterData.stack[0].destinationFrame); } diff --git a/src/core/renderers/webgl/systems/shader/shader/mapType.js b/src/core/renderers/webgl/systems/shader/shader/mapType.js index 581dace..917538a 100644 --- a/src/core/renderers/webgl/systems/shader/shader/mapType.js +++ b/src/core/renderers/webgl/systems/shader/shader/mapType.js @@ -15,7 +15,7 @@ } } - return GL_TABLE[type]; + return GL_TABLE[type]; }; var GL_TABLE = null; diff --git a/src/core/settings.js b/src/core/settings.js index 18c5877..38ef45c 100644 --- a/src/core/settings.js +++ b/src/core/settings.js @@ -2,6 +2,15 @@ import canUploadSameBuffer from './utils/canUploadSameBuffer'; /** + * User's customizable globals for overriding the default PIXI settings, such + * as a renderer's default resolution, framerate, float percision, etc. + * @example + * // Use the native window resolution as the default resolution + * // will support high-density displays when rendering + * PIXI.settings.RESOLUTION = window.devicePixelRatio. + * + * // Disable interpolation when scaling, will make texture be pixelated + * PIXI.settings.SCALE_MODE = PIXI.SCALE_MODES.NEAREST; * @namespace PIXI.settings */ export default { @@ -101,6 +110,9 @@ * @property {boolean} clearBeforeRender=true * @property {boolean} preserveDrawingBuffer=false * @property {boolean} roundPixels=false + * @property {number} width=800 + * @property {number} height=600 + * @property {boolean} legacy=false */ RENDER_OPTIONS: { view: null, @@ -112,6 +124,9 @@ clearBeforeRender: true, preserveDrawingBuffer: false, roundPixels: false, + width: 800, + height: 600, + legacy: false, }, /** diff --git a/src/core/shader/Program.js b/src/core/shader/Program.js index c0b538b..07b4398 100644 --- a/src/core/shader/Program.js +++ b/src/core/shader/Program.js @@ -1,6 +1,6 @@ import extractUniformsFromSrc from './extractUniformsFromSrc'; import generateUniformsSync from './generateUniformsSync'; -import glCore from 'pixi-gl-core'; +import shaderUtils from '../renderers/webgl/systems/shader/shader'; import { ProgramCache } from '../utils'; import getTestContext from '../utils/getTestContext'; @@ -64,14 +64,17 @@ } else { - vertexSrc = glCore.shader.setPrecision(vertexSrc, 'mediump'); - fragmentSrc = glCore.shader.setPrecision(fragmentSrc, 'mediump'); + vertexSrc = shaderUtils.setPrecision(vertexSrc, 'mediump'); + fragmentSrc = shaderUtils.setPrecision(fragmentSrc, 'mediump'); - const program = glCore.shader.compileProgram(gl, vertexSrc, fragmentSrc); + const program = shaderUtils.compileProgram(gl, vertexSrc, fragmentSrc); + console.log("<<<<<<>>><<"); this.attributeData = this.getAttributeData(program, gl); this.uniformData = this.getUniformData(program, gl); + console.log(this.uniformData); + //gl.deleteProgram(program); } } @@ -95,13 +98,13 @@ for (let i = 0; i < totalAttributes; i++) { const attribData = gl.getActiveAttrib(program, i); - const type = glCore.shader.mapType(gl, attribData.type); + const type = shaderUtils.mapType(gl, attribData.type); /*eslint-disable */ const data = { type: type, name: attribData.name, - size: glCore.shader.mapSize(type), + size: shaderUtils.mapSize(type), location: 0, }; /*eslint-enable */ @@ -137,13 +140,14 @@ // TODO expose this as a prop? // const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); - const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); + //const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); for (let i = 0; i < totalUniforms; i++) { const uniformData = gl.getActiveUniform(program, i); const name = uniformData.name.replace(/\[.*?\]/, ''); - const type = glCore.shader.mapType(gl, uniformData.type); + const type = shaderUtils.mapType(gl, uniformData.type); + console.log('mapping ' + uniformData.type + ' to ' + type); // if (!name.match(maskRegex)) { @@ -151,7 +155,7 @@ uniforms[name] = { type: type, size: uniformData.size, - value: glCore.shader.defaultValue(type, uniformData.size), + value: shaderUtils.defaultValue(type, uniformData.size), }; /*eslint-enable */ } diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index e387e99..b57d095 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -147,12 +147,12 @@ // so if _width is 0 then width was not set.. if (this._width) { - this.scale.x = sign(this.scale.x) * this._width / this.texture.orig.width; + this.scale.x = sign(this.scale.x) * this._width / this._texture.orig.width; } if (this._height) { - this.scale.y = sign(this.scale.y) * this._height / this.texture.orig.height; + this.scale.y = sign(this.scale.y) * this._height / this._texture.orig.height; } } diff --git a/src/core/sprites/canvas/CanvasTinter.js b/src/core/sprites/canvas/CanvasTinter.js index 27e5e0c..fd9de5d 100644 --- a/src/core/sprites/canvas/CanvasTinter.js +++ b/src/core/sprites/canvas/CanvasTinter.js @@ -18,7 +18,7 @@ */ getTintedTexture: (sprite, color) => { - const texture = sprite.texture; + const texture = sprite._texture; color = CanvasTinter.roundColor(color); diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 5a4edda..8b9b797 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -344,6 +344,7 @@ const texture = this._texture; const style = this._style; + const padding = style.trim ? 0 : style.padding; texture.baseTexture.valid = true; texture.baseTexture.resolution = this.resolution; @@ -353,11 +354,11 @@ texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - texture.trim.x = -style.padding; - texture.trim.y = -style.padding; + texture.trim.x = -padding; + texture.trim.y = -padding; - texture.orig.width = texture._frame.width - (style.padding * 2); - texture.orig.height = texture._frame.height - (style.padding * 2); + texture.orig.width = texture._frame.width - (padding * 2); + texture.orig.height = texture._frame.height - (padding * 2); // call sprite onTextureUpdate to update scale if _width or _height were set this._onTextureUpdate(); diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index 853da3d..8a9c34b 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -114,6 +114,38 @@ this.cacheId = null; this.validate(); + + /** + * Fired when a not-immediately-available source finishes loading. + * + * @protected + * @event PIXI.BaseTexture#loaded + * @param {PIXI.BaseTexture} baseTexture - Resource loaded. + */ + + /** + * Fired when a not-immediately-available source fails to load. + * + * @protected + * @event PIXI.BaseTexture#error + * @param {PIXI.BaseTexture} baseTexture - Resource errored. + */ + + /** + * Fired when BaseTexture is updated. + * + * @protected + * @event PIXI.BaseTexture#update + * @param {PIXI.BaseTexture} baseTexture - Instance of texture being updated. + */ + + /** + * Fired when BaseTexture is destroyed. + * + * @protected + * @event PIXI.BaseTexture#dispose + * @param {PIXI.BaseTexture} baseTexture - Instance of texture being destroyed. + */ } setResource(resource) diff --git a/src/core/textures/Spritesheet.js b/src/core/textures/Spritesheet.js index d469c62..9b24f27 100644 --- a/src/core/textures/Spritesheet.js +++ b/src/core/textures/Spritesheet.js @@ -208,6 +208,7 @@ // lets also add the frame to pixi's global cache for fromFrame and fromImage functions TextureCache[i] = this.textures[i]; + this.textures[i].textureCacheId = i; } frameIndex++; diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 0a2c379..2213087 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -147,9 +147,9 @@ /** * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. * - * @event update - * @memberof PIXI.Texture# + * @event PIXI.Texture#update * @protected + * @param {PIXI.Texture} texture - Instance of texture being updated. */ this._updateID = 0; @@ -159,6 +159,15 @@ * @type {Object} */ this.transform = null; + + /** + * The id under which this Texture has been added to the texture cache. This is + * automatically set in certain cases, but may not always be accurate, particularly if + * the texture is in the cache under multiple ids. + * + * @member {string} + */ + this.textureCacheId = null; } /** @@ -225,11 +234,17 @@ this._uvs = null; this.trim = null; this.orig = null; + this.textureCacheId = null; this.valid = false; - this.off('dispose', this.dispose, this); - this.off('update', this.update, this); + for (const prop in TextureCache) + { + if (TextureCache[prop] === this) + { + delete TextureCache[prop]; + } + } } /** @@ -315,14 +330,15 @@ */ static fromLoader(source, imageUrl, name) { - // console.log('added from loader...') + // console.log('added from loader...') const resource = new ImageResource(source);//.from(imageUrl, crossorigin);// document.createElement('img'); - // console.log('base resource ' + resource.width); + // console.log('base resource ' + resource.width); const baseTexture = new BaseTexture(resource, settings.SCALE_MODE, getResolutionOfUrl(imageUrl)); + /// console.log('base width ' + baseTexture.width); const texture = new Texture(baseTexture); @@ -335,6 +351,7 @@ // lets also add the frame to pixi's global cache for fromFrame and fromImage fucntions BaseTextureCache[name] = baseTexture; TextureCache[name] = texture; + texture.textureCacheId = name; // also add references by url if they are different. if (name !== imageUrl) @@ -355,6 +372,10 @@ */ static addTextureToCache(texture, id) { + if (!texture.textureCacheId) + { + texture.textureCacheId = id; + } TextureCache[id] = texture; } @@ -495,6 +516,7 @@ */ Texture.EMPTY = new Texture(new BaseTexture()); removeAllHandlers(Texture.EMPTY); +removeAllHandlers(Texture.EMPTY.baseTexture); /** * A white texture of 10x10 size, used for graphics and other things @@ -505,3 +527,4 @@ */ Texture.WHITE = createWhiteTexture(); removeAllHandlers(Texture.WHITE); +removeAllHandlers(Texture.WHITE.baseTexture); diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index 59eb9fd..a9b9f51 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -1,6 +1,7 @@ import BaseTexture from './BaseTexture'; import { uid, BaseTextureCache } from '../utils'; -import * as ticker from '../ticker'; +import { shared } from '../ticker'; +import { UPDATE_PRIORITY } from '../const'; /** * A texture of a [playing] Video. @@ -125,7 +126,7 @@ if (!this._isAutoUpdating && this.autoUpdate) { - ticker.shared.add(this.update, this); + shared.add(this.update, this, UPDATE_PRIORITY.HIGH); this._isAutoUpdating = true; } } @@ -139,7 +140,7 @@ { if (this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); this._isAutoUpdating = false; } } @@ -187,7 +188,7 @@ { if (this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); } if (this.source && this.source._pixiId) @@ -281,12 +282,12 @@ if (!this._autoUpdate && this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); this._isAutoUpdating = false; } else if (this._autoUpdate && !this._isAutoUpdating) { - ticker.shared.add(this.update, this); + shared.add(this.update, this, UPDATE_PRIORITY.HIGH); this._isAutoUpdating = true; } } diff --git a/src/core/ticker/Ticker.js b/src/core/ticker/Ticker.js index 17a1517..40df138 100644 --- a/src/core/ticker/Ticker.js +++ b/src/core/ticker/Ticker.js @@ -1,12 +1,10 @@ import settings from '../settings'; -import EventEmitter from 'eventemitter3'; - -// Internal event used by composed emitter -const TICK = 'tick'; +import { UPDATE_PRIORITY } from '../const'; +import TickerListener from './TickerListener'; /** * A Ticker class that runs an update loop that other objects listen to. - * This class is composed around an EventEmitter object to add listeners + * This class is composed around listeners * meant for execution on the next requested animation frame. * Animation frames are requested only when necessary, * e.g. When the ticker is started and the emitter has listeners. @@ -22,10 +20,11 @@ constructor() { /** - * Internal emitter used to fire 'tick' event + * The first listener. All new listeners added are chained on this. * @private + * @type {TickerListener} */ - this._emitter = new EventEmitter(); + this._head = new TickerListener(null, null, Infinity); /** * Internal current frame request ID @@ -131,7 +130,7 @@ // Invoke listeners now this.update(time); // Listener side effects may have modified ticker state. - if (this.started && this._requestId === null && this._emitter.listeners(TICK, true)) + if (this.started && this._requestId === null && this._head.next) { this._requestId = requestAnimationFrame(this._tick); } @@ -148,7 +147,7 @@ */ _requestIfNeeded() { - if (this._requestId === null && this._emitter.listeners(TICK, true)) + if (this._requestId === null && this._head.next) { // ensure callbacks get correct delta this.lastTime = performance.now(); @@ -193,35 +192,72 @@ } /** - * Calls {@link module:eventemitter3.EventEmitter#on} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. + * Register a handler for tick events. Calls continuously unless + * it is removed or the ticker is stopped. * * @param {Function} fn - The listener function to be added for updates * @param {Function} [context] - The listener context + * @param {number} [priority=PIXI.UPDATE_PRIORITY.NORMAL] - The priority for emitting * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - add(fn, context) + add(fn, context, priority = UPDATE_PRIORITY.NORMAL) { - this._emitter.on(TICK, fn, context); - - this._startIfPossible(); - - return this; + return this._addListener(new TickerListener(fn, context, priority)); } /** - * Calls {@link module:eventemitter3.EventEmitter#once} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. + * Add a handler for the tick event which is only execute once. * * @param {Function} fn - The listener function to be added for one update * @param {Function} [context] - The listener context + * @param {number} [priority=PIXI.UPDATE_PRIORITY.NORMAL] - The priority for emitting * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - addOnce(fn, context) + addOnce(fn, context, priority = UPDATE_PRIORITY.NORMAL) { - this._emitter.once(TICK, fn, context); + return this._addListener(new TickerListener(fn, context, priority, true)); + } + + /** + * Internally adds the event handler so that it can be sorted by priority. + * Priority allows certain handler (user, AnimatedSprite, Interaction) to be run + * before the rendering. + * + * @private + * @param {TickerListener} listener - Current listener being added. + * @returns {PIXI.ticker.Ticker} This instance of a ticker + */ + _addListener(listener) + { + // For attaching to head + let current = this._head.next; + let previous = this._head; + + // Add the first item + if (!current) + { + listener.connect(previous); + } + else + { + // Go from highest to lowest priority + while (current) + { + if (listener.priority >= current.priority) + { + listener.connect(previous); + break; + } + previous = current; + current = current.next; + } + + // Not yet connected + if (!listener.previous) + { + listener.connect(previous); + } + } this._startIfPossible(); @@ -229,19 +265,33 @@ } /** - * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. - * It checks if the emitter has listeners for 'tick' event. - * If it does, then it cancels the animation frame. + * Removes any handlers matching the function and context parameters. + * If no handlers are left after removing, then it cancels the animation frame. * - * @param {Function} [fn] - The listener function to be removed + * @param {Function} fn - The listener function to be removed * @param {Function} [context] - The listener context to be removed * @returns {PIXI.ticker.Ticker} This instance of a ticker */ remove(fn, context) { - this._emitter.off(TICK, fn, context); + let listener = this._head.next; - if (!this._emitter.listeners(TICK, true)) + while (listener) + { + // We found a match, lets remove it + // no break to delete all possible matches + // incase a listener was added 2+ times + if (listener.match(fn, context)) + { + listener = listener.destroy(); + } + else + { + listener = listener.next; + } + } + + if (!this._head.next) { this._cancelIfNeeded(); } @@ -276,6 +326,25 @@ } /** + * Destroy the ticker and don't use after this. Calling + * this method removes all references to internal events. + */ + destroy() + { + this.stop(); + + let listener = this._head.next; + + while (listener) + { + listener = listener.destroy(true); + } + + this._head.destroy(); + this._head = null; + } + + /** * Triggers an update. An update entails setting the * current {@link PIXI.ticker.Ticker#elapsedMS}, * the current {@link PIXI.ticker.Ticker#deltaTime}, @@ -320,8 +389,22 @@ this.deltaTime = elapsedMS * settings.TARGET_FPMS * this.speed; + // Cache a local reference, in-case ticker is destroyed + // during the emit, we can still check for head.next + const head = this._head; + // Invoke listeners added to internal emitter - this._emitter.emit(TICK, this.deltaTime); + let listener = head.next; + + while (listener) + { + listener = listener.emit(this.deltaTime); + } + + if (!head.next) + { + this._cancelIfNeeded(); + } } else { diff --git a/src/core/ticker/TickerListener.js b/src/core/ticker/TickerListener.js new file mode 100644 index 0000000..2bedb34 --- /dev/null +++ b/src/core/ticker/TickerListener.js @@ -0,0 +1,158 @@ +/** + * Internal class for handling the priority sorting of ticker handlers. + * + * @private + * @class + * @memberof PIXI.ticker + */ +export default class TickerListener +{ + /** + * Constructor + * + * @param {Function} fn - The listener function to be added for one update + * @param {Function} [context=null] - The listener context + * @param {number} [priority=0] - The priority for emitting + * @param {boolean} [once=false] - If the handler should fire once + */ + constructor(fn, context = null, priority = 0, once = false) + { + /** + * The handler function to execute. + * @member {Function} + */ + this.fn = fn; + + /** + * The calling to execute. + * @member {Function} + */ + this.context = context; + + /** + * The current priority. + * @member {number} + */ + this.priority = priority; + + /** + * If this should only execute once. + * @member {boolean} + */ + this.once = once; + + /** + * The next item in chain. + * @member {TickerListener} + */ + this.next = null; + + /** + * The previous item in chain. + * @member {TickerListener} + */ + this.previous = null; + + /** + * `true` if this listener has been destroyed already. + * @member {boolean} + * @private + */ + this._destroyed = false; + } + + /** + * Simple compare function to figure out if a function and context match. + * + * @param {Function} fn - The listener function to be added for one update + * @param {Function} context - The listener context + * @return {boolean} `true` if the listener match the arguments + */ + match(fn, context) + { + context = context || null; + + return this.fn === fn && this.context === context; + } + + /** + * Emit by calling the current function. + * @param {number} deltaTime - time since the last emit. + * @return {TickerListener} Next ticker + */ + emit(deltaTime) + { + if (this.context) + { + this.fn.call(this.context, deltaTime); + } + else + { + this.fn(deltaTime); + } + + if (this.once) + { + this.destroy(); + } + + const redirect = this.next; + + // Soft-destroying should remove + // the next reference + if (this._destroyed) + { + this.next = null; + } + + return redirect; + } + + /** + * Connect to the list. + * @param {TickerListener} previous - Input node, previous listener + */ + connect(previous) + { + this.previous = previous; + if (previous.next) + { + previous.next.previous = this; + } + this.next = previous.next; + previous.next = this; + } + + /** + * Destroy and don't use after this. + * @param {boolean} [hard = false] `true` to remove the `next` reference, this + * is considered a hard destroy. Soft destroy maintains the next reference. + * @return {TickerListener} The listener to redirect while emitting or removing. + */ + destroy(hard = false) + { + this._destroyed = true; + this.fn = null; + this.context = null; + + // Disconnect, hook up next and previous + if (this.previous) + { + this.previous.next = this.next; + } + + if (this.next) + { + this.next.previous = this.previous; + } + + // Redirect to the next item + const redirect = this.previous; + + // Remove references + this.next = hard ? null : redirect; + this.previous = null; + + return redirect; + } +} diff --git a/src/core/ticker/index.js b/src/core/ticker/index.js index 4b3017c..c3020d4 100644 --- a/src/core/ticker/index.js +++ b/src/core/ticker/index.js @@ -45,8 +45,25 @@ const shared = new Ticker(); shared.autoStart = true; +shared.destroy = () => +{ + // protect destroying shared ticker + // this is used by other internal systems + // like AnimatedSprite and InteractionManager +}; /** + * This namespace contains an API for interacting with PIXI's internal global update loop. + * + * This ticker is used for rendering, {@link PIXI.extras.AnimatedSprite AnimatedSprite}, + * {@link PIXI.interaction.InteractionManager InteractionManager} and many other time-based PIXI systems. + * @example + * const ticker = new PIXI.ticker.Ticker(); + * ticker.stop(); + * ticker.add((deltaTime) => { + * // do something every frame + * }); + * ticker.start(); * @namespace PIXI.ticker */ export { shared, Ticker }; diff --git a/src/core/utils/index.js b/src/core/utils/index.js index be44750..7441f7b 100644 --- a/src/core/utils/index.js +++ b/src/core/utils/index.js @@ -9,6 +9,21 @@ let saidHello = false; /** + * Generalized convenience utilities for PIXI. + * @example + * // Extend PIXI's internal Event Emitter. + * class MyEmitter extends PIXI.utils.EventEmitter { + * constructor() { + * super(); + * console.log("Emitter created!"); + * } + * } + * + * // Get info on current device + * console.log(PIXI.utils.isMobile); + * + * // Convert hex color to string + * console.log(PIXI.utils.hex2string(0xff00ff)); // returns: "#ff00ff" * @namespace PIXI.utils */ export { diff --git a/src/deprecation.js b/src/deprecation.js index b9c73b3..33daeec 100644 --- a/src/deprecation.js +++ b/src/deprecation.js @@ -918,6 +918,35 @@ }); /** + * @method + * @name PIXI.prepare.BasePrepare#register + * @see PIXI.prepare.BasePrepare#registerFindHook + * @deprecated since version 4.4.2 + * @param {Function} [addHook] - Function call that takes two parameters: `item:*, queue:Array` + * function must return `true` if it was able to add item to the queue. + * @param {Function} [uploadHook] - Function call that takes two parameters: `prepare:CanvasPrepare, item:*` and + * function must return `true` if it was able to handle upload of item. + * @return {PIXI.BasePrepare} Instance of plugin for chaining. + */ +prepare.BasePrepare.prototype.register = function register(addHook, uploadHook) +{ + warn('renderer.plugins.prepare.register is now deprecated, ' + + 'please use renderer.plugins.prepare.registerFindHook & renderer.plugins.prepare.registerUploadHook'); + + if (addHook) + { + this.registerFindHook(addHook); + } + + if (uploadHook) + { + this.registerUploadHook(uploadHook); + } + + return this; +}; + +/** * The number of graphics or textures to upload to the GPU. * * @name PIXI.prepare.canvas.UPLOADS_PER_FRAME @@ -969,67 +998,73 @@ }, }); -Object.defineProperties(loaders.Resource.prototype, { - isJson: { - get() - { - warn('The isJson property is deprecated, please use `resource.type === Resource.TYPE.JSON`.'); +if (loaders.Loader) +{ + const Resource = loaders.Resource; + const Loader = loaders.Loader; - return this.type === loaders.Loader.Resource.TYPE.JSON; + Object.defineProperties(Resource.prototype, { + isJson: { + get() + { + warn('The isJson property is deprecated, please use `resource.type === Resource.TYPE.JSON`.'); + + return this.type === Resource.TYPE.JSON; + }, }, - }, - isXml: { - get() - { - warn('The isXml property is deprecated, please use `resource.type === Resource.TYPE.XML`.'); + isXml: { + get() + { + warn('The isXml property is deprecated, please use `resource.type === Resource.TYPE.XML`.'); - return this.type === loaders.Loader.Resource.TYPE.XML; + return this.type === Resource.TYPE.XML; + }, }, - }, - isImage: { - get() - { - warn('The isImage property is deprecated, please use `resource.type === Resource.TYPE.IMAGE`.'); + isImage: { + get() + { + warn('The isImage property is deprecated, please use `resource.type === Resource.TYPE.IMAGE`.'); - return this.type === loaders.Loader.Resource.TYPE.IMAGE; + return this.type === Resource.TYPE.IMAGE; + }, }, - }, - isAudio: { - get() - { - warn('The isAudio property is deprecated, please use `resource.type === Resource.TYPE.AUDIO`.'); + isAudio: { + get() + { + warn('The isAudio property is deprecated, please use `resource.type === Resource.TYPE.AUDIO`.'); - return this.type === loaders.Loader.Resource.TYPE.AUDIO; + return this.type === Resource.TYPE.AUDIO; + }, }, - }, - isVideo: { - get() - { - warn('The isVideo property is deprecated, please use `resource.type === Resource.TYPE.VIDEO`.'); + isVideo: { + get() + { + warn('The isVideo property is deprecated, please use `resource.type === Resource.TYPE.VIDEO`.'); - return this.type === loaders.Loader.Resource.TYPE.VIDEO; + return this.type === Resource.TYPE.VIDEO; + }, }, - }, -}); + }); -Object.defineProperties(loaders.Loader.prototype, { - before: { - get() - { - warn('The before() method is deprecated, please use pre().'); + Object.defineProperties(Loader.prototype, { + before: { + get() + { + warn('The before() method is deprecated, please use pre().'); - return this.pre; + return this.pre; + }, }, - }, - after: { - get() - { - warn('The after() method is deprecated, please use use().'); + after: { + get() + { + warn('The after() method is deprecated, please use use().'); - return this.use; + return this.use; + }, }, - }, -}); + }); +} /** * @name PIXI.interaction.interactiveTarget#defaultCursor diff --git a/src/extract/canvas/CanvasExtract.js b/src/extract/canvas/CanvasExtract.js index 941ca05..3ac5aee 100644 --- a/src/extract/canvas/CanvasExtract.js +++ b/src/extract/canvas/CanvasExtract.js @@ -8,7 +8,7 @@ * An instance of this class is automatically created by default, and can be found at renderer.plugins.extract * * @class - * @memberof PIXI + * @memberof PIXI.extract */ export default class CanvasExtract { @@ -21,9 +21,9 @@ /** * Collection of methods for extracting data (image, pixels, etc.) from a display object or render texture * - * @member {PIXI.CanvasExtract} extract + * @member {PIXI.extract.CanvasExtract} extract * @memberof PIXI.CanvasRenderer# - * @see PIXI.CanvasExtract + * @see PIXI.extract.CanvasExtract */ renderer.extract = this; } diff --git a/src/extract/index.js b/src/extract/index.js index 0f2170d..e9478c1 100644 --- a/src/extract/index.js +++ b/src/extract/index.js @@ -1,2 +1,22 @@ +/** + * This namespace provides renderer-specific plugins for exporting content from a renderer. + * For instance, these plugins can be used for saving an Image, Canvas element or for exporting the raw image data (pixels). + * + * Do not instantiate these plugins directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. + * @example + * // Create a new app (will auto-add extract plugin to renderer) + * const app = new PIXI.Application(); + * + * // Draw a red circle + * const graphics = new PIXI.Graphics() + * .beginFill(0xFF0000) + * .drawCircle(0, 0, 50); + * + * // Render the graphics as an HTMLImageElement + * const image = app.renderer.plugins.extract.image(graphics); + * document.body.appendChild(image); + * @namespace PIXI.extract + */ export { default as webgl } from './webgl/WebGLExtract'; export { default as canvas } from './canvas/CanvasExtract'; diff --git a/src/extract/webgl/WebGLExtract.js b/src/extract/webgl/WebGLExtract.js index 60228bb..1c3dd30 100644 --- a/src/extract/webgl/WebGLExtract.js +++ b/src/extract/webgl/WebGLExtract.js @@ -9,7 +9,7 @@ * An instance of this class is automatically created by default, and can be found at renderer.plugins.extract * * @class - * @memberof PIXI + * @memberof PIXI.extract */ export default class WebGLExtract { @@ -22,9 +22,9 @@ /** * Collection of methods for extracting data (image, pixels, etc.) from a display object or render texture * - * @member {PIXI.WebGLExtract} extract + * @member {PIXI.extract.WebGLExtract} extract * @memberof PIXI.WebGLRenderer# - * @see PIXI.WebGLExtract + * @see PIXI.extract.WebGLExtract */ renderer.extract = this; } diff --git a/src/extras/AnimatedSprite.js b/src/extras/AnimatedSprite.js index 13b804f..5297778 100644 --- a/src/extras/AnimatedSprite.js +++ b/src/extras/AnimatedSprite.js @@ -137,7 +137,7 @@ this.playing = true; if (this._autoUpdate) { - core.ticker.shared.add(this.update, this); + core.ticker.shared.add(this.update, this, core.UPDATE_PRIORITY.HIGH); } } diff --git a/src/extras/TextureTransform.js b/src/extras/TextureTransform.js index 78e6e40..16536d2 100644 --- a/src/extras/TextureTransform.js +++ b/src/extras/TextureTransform.js @@ -65,8 +65,36 @@ } /** + * Multiplies uvs array to transform + * @param {Float32Array} uvs mesh uvs + * @param {Float32Array} [out=uvs] output + * @returns {Float32Array} output + */ + multiplyUvs(uvs, out) + { + if (out === undefined) + { + out = uvs; + } + + const mat = this.mapCoord; + + for (let i = 0; i < uvs.length; i += 2) + { + const x = uvs[i]; + const y = uvs[i + 1]; + + out[i] = (x * mat.a) + (y * mat.c) + mat.tx; + out[i + 1] = (x * mat.b) + (y * mat.d) + mat.ty; + } + + return out; + } + + /** * updates matrices if texture was changed * @param {boolean} forceUpdate if true, matrices will be updated any case + * @returns {boolean} whether or not it was updated */ update(forceUpdate) { @@ -74,13 +102,13 @@ if (!tex || !tex.valid) { - return; + return false; } if (!forceUpdate && this._lastTextureID === tex._updateID) { - return; + return false; } this._lastTextureID = tex._updateID; @@ -110,5 +138,7 @@ frame[3] = (tex._frame.y + tex._frame.height - margin + offset) / texBase.height; this.uClampOffset[0] = offset / texBase.realWidth; this.uClampOffset[1] = offset / texBase.realHeight; + + return true; } } diff --git a/src/extras/TilingSprite.js b/src/extras/TilingSprite.js index 1f6014f..a3f027f 100644 --- a/src/extras/TilingSprite.js +++ b/src/extras/TilingSprite.js @@ -180,9 +180,9 @@ const transform = this.worldTransform; const resolution = renderer.resolution; const baseTexture = texture.baseTexture; - const baseTextureResolution = texture.baseTexture.resolution; - const modX = (this.tilePosition.x / this.tileScale.x) % texture._frame.width; - const modY = (this.tilePosition.y / this.tileScale.y) % texture._frame.height; + const baseTextureResolution = baseTexture.resolution; + const modX = ((this.tilePosition.x / this.tileScale.x) % texture._frame.width) * baseTextureResolution; + const modY = ((this.tilePosition.y / this.tileScale.y) % texture._frame.height) * baseTextureResolution; // create a nice shiny pattern! // TODO this needs to be refreshed if texture changes.. diff --git a/src/extras/cacheAsBitmap.js b/src/extras/cacheAsBitmap.js index 5dd5640..aea06a8 100644 --- a/src/extras/cacheAsBitmap.js +++ b/src/extras/cacheAsBitmap.js @@ -227,7 +227,16 @@ this.transform._parentID = -1; // restore the transform of the cached sprite to avoid the nasty flicker.. - this.updateTransform(); + if (!this.parent) + { + this.parent = renderer._tempDisplayObjectParent; + this.updateTransform(); + this.parent = null; + } + else + { + this.updateTransform(); + } // map the hit test.. this.containsPoint = cachedSprite.containsPoint.bind(cachedSprite); @@ -314,7 +323,17 @@ cachedSprite._bounds = this._bounds; cachedSprite.alpha = cacheAlpha; - this.updateTransform(); + if (!this.parent) + { + this.parent = renderer._tempDisplayObjectParent; + this.updateTransform(); + this.parent = null; + } + else + { + this.updateTransform(); + } + this.updateTransform = this.displayObjectUpdateTransform; this._cacheData.sprite = cachedSprite; @@ -358,9 +377,12 @@ * Destroys the cached object. * * @private + * @param {object|boolean} [options] - Options parameter. A boolean will act as if all options + * have been set to that value. + * Used when destroying containers, see the Container.destroy method. */ -DisplayObject.prototype._cacheAsBitmapDestroy = function _cacheAsBitmapDestroy() +DisplayObject.prototype._cacheAsBitmapDestroy = function _cacheAsBitmapDestroy(options) { this.cacheAsBitmap = false; - this.destroy(); + this.destroy(options); }; diff --git a/src/extras/index.js b/src/extras/index.js index bbd8bbf..a34d7ec 100644 --- a/src/extras/index.js +++ b/src/extras/index.js @@ -1,7 +1,7 @@ /** + * Additional PIXI DisplayObjects for animation, tiling and bitmap text. * @namespace PIXI.extras */ -export { default as TextureTransform } from './TextureTransform'; export { default as AnimatedSprite } from './AnimatedSprite'; export { default as TilingSprite } from './TilingSprite'; export { default as TilingSpriteRenderer } from './webgl/TilingSpriteRenderer'; diff --git a/src/extras/webgl/TilingSpriteRenderer.js b/src/extras/webgl/TilingSpriteRenderer.js index aeec172..eee3b5c 100644 --- a/src/extras/webgl/TilingSpriteRenderer.js +++ b/src/extras/webgl/TilingSpriteRenderer.js @@ -11,7 +11,7 @@ * WebGL renderer plugin for tiling sprites * * @class - * @memberof PIXI + * @memberof PIXI.extras * @extends PIXI.ObjectRenderer */ export default class TilingSpriteRenderer extends core.ObjectRenderer @@ -118,7 +118,7 @@ tempMat.invert(); if (isSimple) { - tempMat.append(uv.mapCoord); + tempMat.prepend(uv.mapCoord); } else { diff --git a/src/filters/displacement/DisplacementFilter.js b/src/filters/displacement/DisplacementFilter.js index d5355c3..b5d030c 100644 --- a/src/filters/displacement/DisplacementFilter.js +++ b/src/filters/displacement/DisplacementFilter.js @@ -35,7 +35,7 @@ this.maskSprite = sprite; this.maskMatrix = maskMatrix; - this.uniforms.mapSampler = sprite.texture; + this.uniforms.mapSampler = sprite._texture; this.uniforms.filterMatrix = maskMatrix; this.uniforms.scale = { x: 1, y: 1 }; diff --git a/src/filters/index.js b/src/filters/index.js index f3e8862..7ceb50a 100644 --- a/src/filters/index.js +++ b/src/filters/index.js @@ -1,4 +1,21 @@ /** + * This namespace contains WebGL-only display filters that can be applied + * to DisplayObjects using the {@link PIXI.DisplayObject#filters filters} property. + * @example + * // Create a new application + * const app = new PIXI.Application(); + * + * // Draw a green rectangle + * const rect = new PIXI.Graphics() + * .beginFill(0x00ff00) + * .drawRect(40, 40, 200, 200); + * + * // Add a blur filter + * rect.filters = [new PIXI.filters.BlurFilter()]; + * + * // Display rectangle + * app.stage.addChild(rect); + * document.body.appendChild(app.view); * @namespace PIXI.filters */ export { default as FXAAFilter } from './fxaa/FXAAFilter'; diff --git a/src/index.js b/src/index.js index c553427..77da490 100644 --- a/src/index.js +++ b/src/index.js @@ -20,6 +20,14 @@ import { utils } from './core'; utils.mixins.performMixins(); +/** + * Alias for {@link PIXI.loaders.shared}. + * @name loader + * @memberof PIXI + * @type {PIXI.loader.Loader} + */ +const loader = loaders.shared || null; + export { accessibility, extract, @@ -30,18 +38,8 @@ mesh, particles, prepare, + loader, }; -/** - * A premade instance of the loader that can be used to load resources. - * - * @name loader - * @memberof PIXI - * @property {PIXI.loaders.Loader} - */ -const loader = loaders && loaders.Loader ? new loaders.Loader() : null; // check is there in case user excludes loader lib - -export { loader }; - // Always export pixi globally. global.PIXI = exports; // eslint-disable-line diff --git a/src/interaction/InteractionData.js b/src/interaction/InteractionData.js index 02d53a3..449f8cc 100644 --- a/src/interaction/InteractionData.js +++ b/src/interaction/InteractionData.js @@ -30,7 +30,10 @@ /** * When passed to an event handler, this will be the original DOM Event that was captured * - * @member {Event} + * @see https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent + * @see https://developer.mozilla.org/en-US/docs/Web/API/TouchEvent + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent + * @member {MouseEvent|TouchEvent|PointerEvent} */ this.originalEvent = null; } diff --git a/src/interaction/InteractionManager.js b/src/interaction/InteractionManager.js index 8baf13a..2d75c7c 100644 --- a/src/interaction/InteractionManager.js +++ b/src/interaction/InteractionManager.js @@ -13,6 +13,14 @@ const MOUSE_POINTER_ID = 'MOUSE'; +// helpers for hitTest() - only used inside hitTest() +const hitTestEvent = { + target: null, + data: { + global: null, + }, +}; + /** * The interaction manager deals with mouse, touch and pointer events. Any DisplayObject can be interactive * if its interactive parameter is set to true @@ -245,67 +253,60 @@ this.setTargetElement(this.renderer.view, this.renderer.resolution); /** - * Fired when a pointer device button (usually a mouse button) is pressed on the display + * Fired when a pointer device button (usually a mouse left-button) is pressed on the display * object. * - * @event mousedown - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mousedown + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device secondary button (usually a mouse right-button) is pressed * on the display object. * - * @event rightdown - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightdown + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** - * Fired when a pointer device button (usually a mouse button) is released over the display + * Fired when a pointer device button (usually a mouse left-button) is released over the display * object. * - * @event mouseup - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseup + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device secondary button (usually a mouse right-button) is released * over the display object. * - * @event rightup - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightup + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** - * Fired when a pointer device button (usually a mouse button) is pressed and released on + * Fired when a pointer device button (usually a mouse left-button) is pressed and released on * the display object. * - * @event click - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#click + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device secondary button (usually a mouse right-button) is pressed * and released on the display object. * - * @event rightclick - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightclick + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** - * Fired when a pointer device button (usually a mouse button) is released outside the + * Fired when a pointer device button (usually a mouse left-button) is released outside the * display object that initially registered a * [mousedown]{@link PIXI.interaction.InteractionManager#event:mousedown}. * - * @event mouseupoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** @@ -313,146 +314,362 @@ * outside the display object that initially registered a * [rightdown]{@link PIXI.interaction.InteractionManager#event:rightdown}. * - * @event rightupoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device (usually a mouse) is moved while over the display object * - * @event mousemove - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mousemove + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device (usually a mouse) is moved onto the display object * - * @event mouseover - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseover + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device (usually a mouse) is moved off the display object * - * @event mouseout - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseout + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is pressed on the display object. * - * @event pointerdown - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerdown + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is released over the display object. * - * @event pointerup - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerup + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when the operating system cancels a pointer event * - * @event pointercancel - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointercancel + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is pressed and released on the display object. * - * @event pointertap - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointertap + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is released outside the display object that initially * registered a [pointerdown]{@link PIXI.interaction.InteractionManager#event:pointerdown}. * - * @event pointerupoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device is moved while over the display object * - * @event pointermove - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointermove + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device is moved onto the display object * - * @event pointerover - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerover + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device is moved off the display object * - * @event pointerout - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerout + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is placed on the display object. * - * @event touchstart - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchstart + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is removed from the display object. * - * @event touchend - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchend + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when the operating system cancels a touch * - * @event touchcancel - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchcancel + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is placed and removed from the display object. * - * @event tap - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#tap + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is removed outside of the display object that initially * registered a [touchstart]{@link PIXI.interaction.InteractionManager#event:touchstart}. * - * @event touchendoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchendoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is moved along the display object. * - * @event touchmove - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchmove + * @param {PIXI.interaction.InteractionData} event - Interaction data */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is pressed on the display. + * object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mousedown + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is pressed + * on the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightdown + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is released over the display + * object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseup + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is released + * over the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightup + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is pressed and released on + * the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#click + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is pressed + * and released on the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightclick + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is released outside the + * display object that initially registered a + * [mousedown]{@link PIXI.DisplayObject#event:mousedown}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is released + * outside the display object that initially registered a + * [rightdown]{@link PIXI.DisplayObject#event:rightdown}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device (usually a mouse) is moved while over the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mousemove + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device (usually a mouse) is moved onto the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseover + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device (usually a mouse) is moved off the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseout + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is pressed on the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerdown + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is released over the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerup + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when the operating system cancels a pointer event. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointercancel + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is pressed and released on the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointertap + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is released outside the display object that initially + * registered a [pointerdown]{@link PIXI.DisplayObject#event:pointerdown}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device is moved while over the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointermove + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device is moved onto the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerover + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device is moved off the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerout + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is placed on the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchstart + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is removed from the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchend + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when the operating system cancels a touch. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchcancel + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is placed and removed from the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#tap + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is removed outside of the display object that initially + * registered a [touchstart]{@link PIXI.DisplayObject#event:touchstart}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchendoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is moved along the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchmove + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + } + + /** + * Hit tests a point against the display tree, returning the first interactive object that is hit. + * + * @param {PIXI.Point} globalPoint - A point to hit test with, in global space. + * @param {PIXI.Container} [root] - The root display object to start from. If omitted, defaults + * to the last rendered root of the associated renderer. + * @return {PIXI.DisplayObject} The hit display object, if any. + */ + hitTest(globalPoint, root) + { + // clear the target for our hit test + hitTestEvent.target = null; + // assign the global point + hitTestEvent.data.global = globalPoint; + // ensure safety of the root + if (!root) + { + root = this.renderer._lastObjectRendered; + } + // run the hit test + this.processInteractive(hitTestEvent, root, null, true); + // return our found object - it'll be null if we didn't hit anything + + return hitTestEvent.target; } /** @@ -487,7 +704,7 @@ return; } - core.ticker.shared.add(this.update, this); + core.ticker.shared.add(this.update, this, core.UPDATE_PRIORITY.INTERACTION); if (window.navigator.msPointerEnabled) { @@ -804,8 +1021,6 @@ } } - let keepHitTestingAfterChildren = hitTest; - // ** FREE TIP **! If an object is not interactive or has no buttons in it // (such as a game scene!) set interactiveChildren to false for that displayObject. // This will allow pixi to completely ignore and bypass checking the displayObjects children. @@ -818,7 +1033,9 @@ const child = children[i]; // time to get recursive.. if this function will return if something is hit.. - if (this.processInteractive(interactionEvent, child, func, hitTest, interactiveParent)) + const childHit = this.processInteractive(interactionEvent, child, func, hitTest, interactiveParent); + + if (childHit) { // its a good idea to check if a child has lost its parent. // this means it has been removed whilst looping so its best @@ -827,8 +1044,6 @@ continue; } - hit = true; - // we no longer need to hit test any more objects in this container as we we // now know the parent has been hit interactiveParent = false; @@ -838,36 +1053,41 @@ // This means we no longer need to hit test anything else. We still need to run // through all objects, but we don't need to perform any hit tests. - keepHitTestingAfterChildren = false; - - if (child.interactive) + if (childHit) { - hitTest = false; + if (interactionEvent.target) + { + hitTest = false; + } + hit = true; } - - // we can break now as we have hit an object. } } } - hitTest = keepHitTestingAfterChildren; - // no point running this if the item is not interactive or does not have an interactive parent. if (interactive) { // if we are hit testing (as in we have no hit any objects yet) // We also don't need to worry about hit testing if once of the displayObjects children - // has already been hit! - if (hitTest && !hit) + // has already been hit - but only if it was interactive, otherwise we need to keep + // looking for an interactive child, just in case we hit one + if (hitTest && !interactionEvent.target) { if (displayObject.hitArea) { displayObject.worldTransform.applyInverse(point, this._tempPoint); - hit = displayObject.hitArea.contains(this._tempPoint.x, this._tempPoint.y); + if (displayObject.hitArea.contains(this._tempPoint.x, this._tempPoint.y)) + { + hit = true; + } } else if (displayObject.containsPoint) { - hit = displayObject.containsPoint(point); + if (displayObject.containsPoint(point)) + { + hit = true; + } } } @@ -878,7 +1098,10 @@ interactionEvent.target = displayObject; } - func(interactionEvent, displayObject, hit); + if (func) + { + func(interactionEvent, displayObject, !!hit); + } } } @@ -914,7 +1137,7 @@ { const event = events[i]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -998,7 +1221,7 @@ { const event = events[i]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1098,7 +1321,7 @@ const test = isRightButton ? flags.RIGHT_DOWN : flags.LEFT_DOWN; - const isDown = trackingData !== undefined && (trackingData.flags | test); + const isDown = trackingData !== undefined && (trackingData.flags & test); if (hit) { @@ -1118,11 +1341,11 @@ { if (isRightButton) { - trackingData.rightDown = hit; + trackingData.rightDown = false; } else { - trackingData.leftDown = hit; + trackingData.leftDown = false; } } } @@ -1180,7 +1403,7 @@ { const event = events[i]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1255,7 +1478,7 @@ this.setCursorMode(null); } - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1268,6 +1491,12 @@ { this.emit('mouseout', interactionEvent); } + else + { + // we can get touchleave events after touchend, so we want to make sure we don't + // introduce memory leaks + this.releaseInteractionDataForPointerId(interactionData.identifier); + } } /** @@ -1344,7 +1573,7 @@ // Only mouse and pointer can call onPointerOver, so events will always be length 1 const event = events[0]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1366,12 +1595,14 @@ * Get InteractionData for a given pointerId. Store that data as well * * @private - * @param {number} pointerId - Identifier from a pointer event + * @param {PointerEvent} event - Normalized pointer event, output from normalizeToPointerData * @return {InteractionData} - Interaction data for the given pointer identifier */ - getInteractionDataForPointerId(pointerId) + getInteractionDataForPointerId(event) { - if (pointerId === MOUSE_POINTER_ID) + const pointerId = event.pointerId; + + if (pointerId === MOUSE_POINTER_ID || event.pointerType === 'mouse') { return this.mouse; } diff --git a/src/interaction/InteractionTrackingData.js b/src/interaction/InteractionTrackingData.js index 6a1f69e..8493b63 100644 --- a/src/interaction/InteractionTrackingData.js +++ b/src/interaction/InteractionTrackingData.js @@ -35,8 +35,10 @@ } /** + * Unique pointer id of the event + * * @readonly - * @type {number} Unique pointer id of the event + * @member {number} */ get pointerId() { diff --git a/src/interaction/index.js b/src/interaction/index.js index 56c5b09..72f7d71 100644 --- a/src/interaction/index.js +++ b/src/interaction/index.js @@ -1,4 +1,8 @@ /** + * This namespace contains a renderer plugin for handling mouse, pointer, and touch events. + * + * Do not instantiate this plugin directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. * @namespace PIXI.interaction */ export { default as InteractionData } from './InteractionData'; diff --git a/src/interaction/interactiveTarget.js b/src/interaction/interactiveTarget.js index d252fba..cebac40 100644 --- a/src/interaction/interactiveTarget.js +++ b/src/interaction/interactiveTarget.js @@ -2,7 +2,7 @@ * Default property values of interactive objects * Used by {@link PIXI.interaction.InteractionManager} to automatically give all DisplayObjects these properties * - * @mixin + * @private * @name interactiveTarget * @memberof PIXI.interaction * @example @@ -14,18 +14,28 @@ * ); */ export default { + /** - * Determines if the displayObject be clicked/touched + * Enable interaction events for the DisplayObject. Touch, pointer and mouse + * events will not be emitted unless `interactive` is set to `true`. * - * @inner {boolean} + * @example + * const sprite = new PIXI.Sprite(texture); + * sprite.interactive = true; + * sprite.on('tap', (event) => { + * //handle event + * }); + * @member {boolean} + * @memberof PIXI.DisplayObject# */ interactive: false, /** * Determines if the children to the displayObject can be clicked/touched - * Setting this to false allows pixi to bypass a recursive hitTest function + * Setting this to false allows pixi to bypass a recursive `hitTest` function * - * @inner {boolean} + * @member {boolean} + * @memberof PIXI.Container# */ interactiveChildren: true, @@ -33,7 +43,12 @@ * Interaction shape. Children will be hit first, then this shape will be checked. * Setting this will cause this shape to be checked in hit tests rather than the displayObject's bounds. * - * @inner {PIXI.Rectangle|PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.RoundedRectangle} + * @example + * const sprite = new PIXI.Sprite(texture); + * sprite.interactive = true; + * sprite.hitArea = new PIXI.Rectangle(0, 0, 100, 100); + * @member {PIXI.Rectangle|PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.RoundedRectangle} + * @memberof PIXI.DisplayObject# */ hitArea: null, @@ -41,8 +56,12 @@ * If enabled, the mouse cursor use the pointer behavior when hovered over the displayObject if it is interactive * Setting this changes the 'cursor' property to `'pointer'`. * + * @example + * const sprite = new PIXI.Sprite(texture); + * sprite.interactive = true; + * sprite.buttonMode = true; * @member {boolean} - * @memberof PIXI.interaction.interactiveTarget# + * @memberof PIXI.DisplayObject# */ get buttonMode() { @@ -64,9 +83,14 @@ * This defines what cursor mode is used when the mouse cursor * is hovered over the displayObject. * + * @example + * const sprite = new PIXI.Sprite(texture); + * sprite.interactive = true; + * sprite.cursor = 'wait'; * @see https://developer.mozilla.org/en/docs/Web/CSS/cursor * - * @inner {string} + * @member {string} + * @memberof PIXI.DisplayObject# */ cursor: null, @@ -74,7 +98,7 @@ * Internal set of all active pointers, by identifier * * @member {Map} - * @memberof PIXI.interaction.interactiveTarget# + * @memberof PIXI.DisplayObject# * @private */ get trackedPointers() @@ -87,7 +111,8 @@ /** * Map of all tracked pointers, by identifier. Use trackedPointers to access. * - * @private {Map} + * @private + * @type {Map} */ _trackedPointers: undefined, }; diff --git a/src/loaders/index.js b/src/loaders/index.js index 5189793..daa1fc4 100644 --- a/src/loaders/index.js +++ b/src/loaders/index.js @@ -1,9 +1,22 @@ +import Application from '../core/Application'; +import Loader from './loader'; + /** + * This namespace contains APIs which extends the {@link https://github.com/englercj/resource-loader resource-loader} module + * for loading assets, data, and other resources dynamically. + * @example + * const loader = new PIXI.loaders.Loader(); + * loader.add('bunny', 'data/bunny.png') + * .add('spaceship', 'assets/spritesheet.json'); + * loader.load((loader, resources) => { + * // resources.bunny + * // resources.spaceship + * }); * @namespace PIXI.loaders */ -export { default as Loader } from './loader'; +export { Loader }; export { default as bitmapFontParser, parse as parseBitmapFontData } from './bitmapFontParser'; -export { default as spritesheetParser } from './spritesheetParser'; +export { default as spritesheetParser, getResourcePath } from './spritesheetParser'; export { default as textureParser } from './textureParser'; /** @@ -13,3 +26,55 @@ * @memberof PIXI.loaders */ export { Resource } from 'resource-loader'; + +/** + * A premade instance of the loader that can be used to load resources. + * @name shared + * @memberof PIXI.loaders + * @type {PIXI.loaders.Loader} + */ +const shared = new Loader(); + +shared.destroy = () => +{ + // protect destroying shared loader +}; + +export { shared }; + +// Mixin the loader construction +const AppPrototype = Application.prototype; + +AppPrototype._loader = null; + +/** + * Loader instance to help with asset loading. + * @name PIXI.Application#loader + * @type {PIXI.loaders.Loader} + */ +Object.defineProperty(AppPrototype, 'loader', { + get() + { + if (!this._loader) + { + const sharedLoader = this._options.sharedLoader; + + this._loader = sharedLoader ? shared : new Loader(); + } + + return this._loader; + }, +}); + +// Override the destroy function +// making sure to destroy the current Loader +AppPrototype._parentDestroy = AppPrototype.destroy; +AppPrototype.destroy = function destroy() +{ + if (this._loader) + { + this._loader.destroy(); + this._loader = null; + } + this._parentDestroy(); +}; diff --git a/src/loaders/loader.js b/src/loaders/loader.js index 767a411..59b8000 100644 --- a/src/loaders/loader.js +++ b/src/loaders/loader.js @@ -90,6 +90,15 @@ { Loader._pixiMiddleware.push(fn); } + + /** + * Destroy the loader, removes references. + */ + destroy() + { + this.removeAllListeners(); + this.reset(); + } } // Copy EE3 prototype (mixin) diff --git a/src/loaders/spritesheetParser.js b/src/loaders/spritesheetParser.js index e57dfb2..eda9584 100644 --- a/src/loaders/spritesheetParser.js +++ b/src/loaders/spritesheetParser.js @@ -1,12 +1,11 @@ import { Resource } from 'resource-loader'; -import path from 'path'; +import url from 'url'; import { Spritesheet } from '../core'; export default function () { return function spritesheetParser(resource, next) { - let resourcePath; const imageResourceName = `${resource.name}_image`; // skip if no data, its not json, it isn't spritesheet data, or the image resource already exists @@ -28,15 +27,7 @@ parentResource: resource, }; - // Prepend url path unless the resource image is a data url - if (resource.isDataUrl) - { - resourcePath = resource.data.meta.image; - } - else - { - resourcePath = `${path.dirname(resource.url.replace(this.baseUrl, ''))}/${resource.data.meta.image}`; - } + const resourcePath = getResourcePath(resource, this.baseUrl); // load the image for this sheet this.add(imageResourceName, resourcePath, loadOptions, function onImageLoad(res) @@ -56,3 +47,14 @@ }); }; } + +export function getResourcePath(resource, baseUrl) +{ + // Prepend url path unless the resource image is a data url + if (resource.isDataUrl) + { + return resource.data.meta.image; + } + + return url.resolve(resource.url.replace(baseUrl, ''), resource.data.meta.image); +} diff --git a/src/mesh/canvas/CanvasMeshRenderer.js b/src/mesh/canvas/CanvasMeshRenderer.js index 297205f..f7899e1 100644 --- a/src/mesh/canvas/CanvasMeshRenderer.js +++ b/src/mesh/canvas/CanvasMeshRenderer.js @@ -134,12 +134,33 @@ const textureWidth = base.width; const textureHeight = base.height; - const u0 = uvs[index0] * base.width; - const u1 = uvs[index1] * base.width; - const u2 = uvs[index2] * base.width; - const v0 = uvs[index0 + 1] * base.height; - const v1 = uvs[index1 + 1] * base.height; - const v2 = uvs[index2 + 1] * base.height; + let u0; + let u1; + let u2; + let v0; + let v1; + let v2; + + if (mesh.uploadUvTransform) + { + const ut = mesh._uvTransform.mapCoord; + + u0 = ((uvs[index0] * ut.a) + (uvs[index0 + 1] * ut.c) + ut.tx) * base.width; + u1 = ((uvs[index1] * ut.a) + (uvs[index1 + 1] * ut.c) + ut.tx) * base.width; + u2 = ((uvs[index2] * ut.a) + (uvs[index2 + 1] * ut.c) + ut.tx) * base.width; + v0 = ((uvs[index0] * ut.b) + (uvs[index0 + 1] * ut.d) + ut.ty) * base.height; + v1 = ((uvs[index1] * ut.b) + (uvs[index1 + 1] * ut.d) + ut.ty) * base.height; + v2 = ((uvs[index2] * ut.b) + (uvs[index2 + 1] * ut.d) + ut.ty) * base.height; + } + else + { + u0 = uvs[index0] * base.width; + u1 = uvs[index1] * base.width; + u2 = uvs[index2] * base.width; + v0 = uvs[index0 + 1] * base.height; + v1 = uvs[index1 + 1] * base.height; + v2 = uvs[index2 + 1] * base.height; + } let x0 = vertices[index0]; let x1 = vertices[index1]; diff --git a/src/mesh/webgl/MeshRenderer.js b/src/mesh/webgl/MeshRenderer.js index 78d24c3..ab99fcd 100644 --- a/src/mesh/webgl/MeshRenderer.js +++ b/src/mesh/webgl/MeshRenderer.js @@ -2,6 +2,8 @@ const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; +const matrixIdentity = core.Matrix.IDENTITY; + /** * WebGL renderer plugin for tiling sprites * @@ -64,6 +66,7 @@ mesh.geometry.glVertexArrayObjects[this.CONTEXT_UID].draw(mesh.drawMode, mesh.size, mesh.start, mesh.geometry.instanceCount); } + /** * draws mesh * @param {PIXI.mesh.RawMesh} mesh mesh instance diff --git a/src/mesh/webgl/mesh.vert b/src/mesh/webgl/mesh.vert index a337aef..acc096c 100644 --- a/src/mesh/webgl/mesh.vert +++ b/src/mesh/webgl/mesh.vert @@ -1,8 +1,9 @@ attribute vec2 aVertexPosition; attribute vec2 aTextureCoord; -uniform mat3 translationMatrix; uniform mat3 projectionMatrix; +uniform mat3 translationMatrix; +uniform mat3 uTransform; varying vec2 vTextureCoord; @@ -10,5 +11,5 @@ { gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - vTextureCoord = aTextureCoord; + vTextureCoord = (uTransform * vec3(aTextureCoord, 1.0)).xy; } diff --git a/.travis.yml b/.travis.yml index 3df8b22..14de334 100644 --- a/.travis.yml +++ b/.travis.yml @@ -110,3 +110,43 @@ on: all_branches: true condition: $TRAVIS_TAG + # Deploy config for latest release + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: dist + upload-dir: "release" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: docs + upload-dir: "release/docs" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: coverage + upload-dir: "release/coverage" + on: + all_branches: true + condition: $TRAVIS_TAG \ No newline at end of file diff --git a/README.md b/README.md index d26fadd..3b85f85 100644 --- a/README.md +++ b/README.md @@ -36,22 +36,14 @@ - Wiki: Other misc tutorials and resources are [on the Wiki](https://github.com/pixijs/pixi.js/wiki/Resources). ### Community ### -- Forums: Check out the [forum] (http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow] (http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. +- Forums: Check out the [forum](http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow](http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. - Inspiration: Check out the [gallery](http://www.pixijs.com/gallery) to see some of the amazing things people have created! - Chat: You can join us on [Gitter](https://gitter.im/pixijs/pixi.js) To chat about Pixi. We also now have a Slack channel. If you would like to join it please Send me an email (mat@goodboydigital.com) and I will invite you in. ### Setup ### -It's easy to get started with Pixi.js! Simply grab the pre-built versions from here: - -Release Branch - Nice and stable Pixi.js -- Unminified: [http://pixijs.download/release/pixi.js] -- Minified: [http://pixijs.download/release/pixi.min.js] - -Develop Branch - The bleeding edge version of Pixi.js -- Unminified: [http://pixijs.download/dev/pixi.js] -- Minified: [http://pixijs.download/dev/pixi.min.js] +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. @@ -70,10 +62,10 @@ #### CDN Install (via cdnjs) ```html - + ``` -_Note: `4.2.2` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ +_Note: `4.4.3` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ ### Demos ### @@ -130,7 +122,7 @@ // and the root stage PIXI.Container. var app = new PIXI.Application(); -// The application will create a canvas element for you that you +// The application will create a canvas element for you that you // can then insert into the DOM. document.body.appendChild(app.view); @@ -150,7 +142,7 @@ // Add the bunny to the scene we are building. app.stage.addChild(bunny); - + // Listen for frame updates app.ticker.add(function() { // each frame we spin the bunny around a bit diff --git a/bower.json b/bower.json deleted file mode 100644 index 41b668c..0000000 --- a/bower.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "pixi.js", - "main": "dist/pixi.min.js", - "ignore": [ - "**/.*", - "docs", - "node_modules", - "bower_components", - "test", - "scripts", - "bower.json", - "inch.json" - ], - "dependencies": { - }, - "devDependencies": { - } -} diff --git a/package.json b/package.json index d35cbb2..308469f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.3.5", + "version": "4.4.4", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ @@ -40,11 +40,11 @@ "lib": "babel src --out-dir lib -s", "predocs": "rimraf docs/**", "docs": "jsdoc -c scripts/jsdoc.conf.json -R README.md", - "publish:patch": "npm version patch --no-git-tag-version && npm publish", - "publish:minor": "npm version minor --no-git-tag-version && npm publish", - "publish:major": "npm version major --no-git-tag-version && npm publish", + "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", - "postpublish": "node scripts/release.js" + "postpublish": "git push && git push --tags" }, "files": [ "dist/", @@ -72,7 +72,6 @@ "electron": "^1.4.15", "eslint": "^3.5.0", "floss": "^2.0.1", - "gh-pages": "^0.11.0", "jaguarjs-jsdoc": "^1.0.1", "js-md5": "^0.4.1", "jsdoc": "^3.4.2", diff --git a/scripts/jsdoc.conf.json b/scripts/jsdoc.conf.json index 553e61c..3f52180 100644 --- a/scripts/jsdoc.conf.json +++ b/scripts/jsdoc.conf.json @@ -43,7 +43,7 @@ }, "markdown" : { "parser" : "gfm", - "hardwrap" : true + "hardwrap" : false }, "opts": { "encoding" : "utf8", diff --git a/scripts/release.js b/scripts/release.js deleted file mode 100644 index 156620c..0000000 --- a/scripts/release.js +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env node -'use strict'; - -// Publish script to push releases of the bin files -// the normally are gitignored -const ghpages = require('gh-pages'); -const path = require('path'); -const packageInfo = require(path.join(__dirname, '..', 'package.json')); - -const options = { - src: [ - 'dist/**/*', - 'lib/**/*', - 'src/**/*', - 'scripts/*', - 'scripts/renders/*', - 'test/**/*', - '*.json', - '*.md', - 'LICENSE', - '.eslintrc', - '.editorconfig', - '.travis.yml', - '.babelrc', - ], - dotfiles: true, - branch: 'release', - message: packageInfo.version, - logger: console.log.bind(console), -}; - -ghpages.publish(process.cwd(), options, (err) => -{ - if (err) - { - console.log(err); - process.exit(1); - - return; - } - - process.exit(0); -}); diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index f16bcb1..220427a 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -21,7 +21,7 @@ const DIV_HOOK_ZINDEX = 2; /** - * The Accessibility manager reacreates the ability to tab and and have content read by screen + * The Accessibility manager recreates the ability to tab and and have content read by screen * readers. This is very important as it can possibly help people with disabilities access pixi * content. * diff --git a/src/accessibility/index.js b/src/accessibility/index.js index 568cfb1..0ae108f 100644 --- a/src/accessibility/index.js +++ b/src/accessibility/index.js @@ -1,4 +1,9 @@ /** + * This namespace contains a renderer plugin for interaction accessibility for end-users + * with physical impairments which require screen-renders, keyboard navigation, etc. + * + * Do not instantiate this plugin directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. * @namespace PIXI.accessibility */ export { default as accessibleTarget } from './accessibleTarget'; diff --git a/src/core/Application.js b/src/core/Application.js index 5340578..2fcc729 100644 --- a/src/core/Application.js +++ b/src/core/Application.js @@ -1,6 +1,8 @@ import { autoDetectRenderer } from './autoDetectRenderer'; import Container from './display/Container'; import { shared, Ticker } from './ticker'; +import settings from './settings'; +import { UPDATE_PRIORITY } from './const'; /** * Convenience class to create a new PIXI application. @@ -22,28 +24,52 @@ */ export default class Application { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experience unexplained flickering try setting this to true. - * @param {boolean} [useSharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedLoader=false] - `true` to use PIXI.loaders.shared, `false` to create new Loader. */ - constructor(width, height, options, noWebGL, useSharedTicker = false) + constructor(options, arg2, arg3, arg4, arg5) { + // Support for constructor(width, height, options, noWebGL, useSharedTicker) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + forceCanvas: !!arg4, + sharedTicker: !!arg5, + }, arg3); + } + + /** + * The default options, so we mixin functionality later. + * @member {object} + * @protected + */ + this._options = options = Object.assign({ + sharedTicker: false, + forceCanvas: false, + sharedLoader: false, + }, options); + /** * WebGL renderer if available, otherwise CanvasRenderer * @member {PIXI.WebGLRenderer|PIXI.CanvasRenderer} */ - this.renderer = autoDetectRenderer(width, height, options, noWebGL); + this.renderer = autoDetectRenderer(options); /** * The root display container that's rendered. @@ -63,7 +89,7 @@ * @member {PIXI.ticker.Ticker} * @default PIXI.ticker.shared */ - this.ticker = useSharedTicker ? shared : new Ticker(); + this.ticker = options.sharedTicker ? shared : new Ticker(); // Start the rendering this.start(); @@ -78,7 +104,7 @@ this._ticker = ticker; if (ticker) { - ticker.add(this.render, this); + ticker.add(this.render, this, UPDATE_PRIORITY.LOW); } } get ticker() // eslint-disable-line require-jsdoc @@ -136,13 +162,18 @@ */ destroy(removeView) { - this.stop(); + const oldTicker = this._ticker; + this.ticker = null; + oldTicker.destroy(); + this.stage.destroy(); this.stage = null; this.renderer.destroy(removeView); this.renderer = null; + + this._options = null; } } diff --git a/src/core/autoDetectRenderer.js b/src/core/autoDetectRenderer.js index 8960425..9ec0364 100644 --- a/src/core/autoDetectRenderer.js +++ b/src/core/autoDetectRenderer.js @@ -2,6 +2,7 @@ import CanvasRenderer from './renderers/canvas/CanvasRenderer'; import WebGLRenderer from './renderers/webgl/WebGLRenderer'; +// eslint-disable-next-line valid-jsdoc /** * This helper function will automatically detect which renderer you should be using. * WebGL is the preferred renderer as it is a lot faster. If webGL is not supported by @@ -9,24 +10,32 @@ * * @memberof PIXI * @function autoDetectRenderer - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @return {PIXI.WebGLRenderer|PIXI.CanvasRenderer} Returns WebGL renderer if available, otherwise CanvasRenderer */ -export function autoDetectRenderer(width = 800, height = 600, options, noWebGL) +export function autoDetectRenderer(options, arg1, arg2, arg3) { - if (!noWebGL && utils.isWebGLSupported()) + // Backward-compatible support for noWebGL option + let forceCanvas = options && options.forceCanvas; + + if (arg3 !== undefined) { - return new WebGLRenderer(width, height, options); + forceCanvas = arg3; } - return new CanvasRenderer(width, height, options); + if (!forceCanvas && utils.isWebGLSupported()) + { + return new WebGLRenderer(options, arg1, arg2); + } + + return new CanvasRenderer(options, arg1, arg2); } diff --git a/src/core/const.js b/src/core/const.js index b9e99ae..2c68dee 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -341,3 +341,27 @@ LINEAR_VERTICAL: 0, LINEAR_HORIZONTAL: 1, }; + +/** + * Represents the update priorities used by internal PIXI classes when registered with + * the {@link PIXI.ticker.Ticker} object. Higher priority items are updated first and lower + * priority items, such as render, should go later. + * + * @static + * @constant + * @name UPDATE_PRIORITY + * @memberof PIXI + * @type {object} + * @property {number} INTERACTION=50 Highest priority, used for {@link PIXI.interaction.InteractionManager} + * @property {number} HIGH=25 High priority updating, {@link PIXI.VideoBaseTexture} and {@link PIXI.extras.AnimatedSprite} + * @property {number} NORMAL=0 Default priority for ticker events, see {@link PIXI.ticker.Ticker#add}. + * @property {number} LOW=-25 Low priority used for {@link PIXI.Application} rendering. + * @property {number} UTILITY=-50 Lowest priority used for {@link PIXI.prepare.BasePrepare} utility. + */ +export const UPDATE_PRIORITY = { + INTERACTION: 50, + HIGH: 25, + NORMAL: 0, + LOW: -25, + UTILITY: -50, +}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 4e6c77d..decb42a 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -13,7 +13,6 @@ * * @class * @extends EventEmitter - * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ export default class DisplayObject extends EventEmitter @@ -113,6 +112,29 @@ * @private */ this._mask = null; + + /** + * If the object has been destroyed via destroy(). If true, it should not be used. + * + * @member {boolean} + * @private + * @readonly + */ + this._destroyed = false; + + /** + * Fired when this DisplayObject is added to a Container. + * + * @event PIXI.DisplayObject#added + * @param {PIXI.Container} container - The container added to. + */ + + /** + * Fired when this DisplayObject is removed from a Container. + * + * @event PIXI.DisplayObject#removed + * @param {PIXI.Container} container - The container removed from. + */ } /** @@ -403,6 +425,8 @@ this.interactive = false; this.interactiveChildren = false; + + this._destroyed = true; } /** diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index aafdfe2..f787c4a 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -792,8 +792,8 @@ sprite.worldAlpha = this.worldAlpha * sprite.alpha; sprite.blendMode = this.blendMode; - sprite.texture._frame.width = rect.width; - sprite.texture._frame.height = rect.height; + sprite._texture._frame.width = rect.width; + sprite._texture._frame.height = rect.height; sprite.transform.worldTransform = this.transform.worldTransform; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index a24c775..b9f192d 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -19,11 +19,12 @@ */ export default class SystemRenderer extends EventEmitter { + // eslint-disable-next-line valid-jsdoc /** * @param {string} system - The name of the system this renderer is for. - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -37,27 +38,31 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(system, screenWidth, screenHeight, options) + constructor(system, options, arg2, arg3) { super(); sayHello(system); - // prepare options - if (options) + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') { - for (const i in settings.RENDER_OPTIONS) - { - if (typeof options[i] === 'undefined') - { - options[i] = settings.RENDER_OPTIONS[i]; - } - } + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); } - else - { - options = settings.RENDER_OPTIONS; - } + + // Add the default render options + options = Object.assign({}, settings.RENDER_OPTIONS, options); + + /** + * The supplied constructor options. + * + * @member {Object} + * @readOnly + */ + this.options = options; /** * The type of the renderer. @@ -75,7 +80,7 @@ * * @member {PIXI.Rectangle} */ - this.screen = new Rectangle(0, 0, screenWidth || 800, screenHeight || 600); + this.screen = new Rectangle(0, 0, options.width, options.height); /** * The canvas element that everything is drawn to @@ -279,6 +284,8 @@ this.blendModes = null; + this.options = null; + this.preserveDrawingBuffer = false; this.clearBeforeRender = false; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index e84e49c..691caf0 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -17,10 +17,11 @@ */ export default class CanvasRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -34,9 +35,9 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('Canvas', screenWidth, screenHeight, options); + super('Canvas', options, arg2, arg3); this.type = RENDERER_TYPE.CANVAS; @@ -96,7 +97,19 @@ this.context = null; this.renderingToScreen = false; - this.resize(screenWidth, screenHeight); + this.resize(this.options.width, this.options.height); + + /** + * Fired after rendering finishes. + * + * @event PIXI.CanvasRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.CanvasRenderer#prerender + */ } /** @@ -299,4 +312,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.CanvasRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.CanvasExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.CanvasPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.CanvasRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index e1cf7cd..f6fe123 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -31,11 +31,12 @@ */ export default class WebGLRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** * - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -55,9 +56,9 @@ * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('WebGL', screenWidth, screenHeight, options); + super('WebGL', options, arg2, arg3); /** * The type of this renderer as a standardised const @@ -89,12 +90,6 @@ }; - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ this._backgroundColorRgba[3] = this.transparent ? 0 : 1; this.globalUniforms = new UniformGroup({ @@ -117,6 +112,13 @@ this.initPlugins(); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ if(options.context) { this.context.initFromContext(options.context); @@ -169,6 +171,25 @@ } return this; + + /** + * Fired after rendering finishes. + * + * @event PIXI.WebGLRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.WebGLRenderer#prerender + */ + + /** + * Fired when the WebGL context is set. + * + * @event PIXI.WebGLRenderer#context + * @param {WebGLRenderingContext} gl - WebGL context. + */ } /** @@ -295,4 +316,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.WebGLRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.WebGLRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(WebGLRenderer); diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index 4eb6128..fa3d883 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -47,6 +47,14 @@ * @member {boolean} */ this.enabled = true; + + /** + * If enabled, pixi will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; } /** diff --git a/src/core/renderers/webgl/systems/ContextSystem.js b/src/core/renderers/webgl/systems/ContextSystem.js index ff958c7..8df85ea 100644 --- a/src/core/renderers/webgl/systems/ContextSystem.js +++ b/src/core/renderers/webgl/systems/ContextSystem.js @@ -75,7 +75,8 @@ */ createContext(canvas, options) { - var gl = canvas.getContext('webgl', options) || + var gl = //canvas.getContext('webgl2', options) || + canvas.getContext('webgl', options) || canvas.getContext('experimental-webgl', options); if (!gl) diff --git a/src/core/renderers/webgl/systems/FilterSystem.js b/src/core/renderers/webgl/systems/FilterSystem.js index f47335d..37e28d2 100644 --- a/src/core/renderers/webgl/systems/FilterSystem.js +++ b/src/core/renderers/webgl/systems/FilterSystem.js @@ -111,7 +111,7 @@ // TODO we should fit the rect around the transform.. } - else + else if (filters[0].autoFit) { sourceFrame.fit(filterData.stack[0].destinationFrame); } diff --git a/src/core/renderers/webgl/systems/shader/shader/mapType.js b/src/core/renderers/webgl/systems/shader/shader/mapType.js index 581dace..917538a 100644 --- a/src/core/renderers/webgl/systems/shader/shader/mapType.js +++ b/src/core/renderers/webgl/systems/shader/shader/mapType.js @@ -15,7 +15,7 @@ } } - return GL_TABLE[type]; + return GL_TABLE[type]; }; var GL_TABLE = null; diff --git a/src/core/settings.js b/src/core/settings.js index 18c5877..38ef45c 100644 --- a/src/core/settings.js +++ b/src/core/settings.js @@ -2,6 +2,15 @@ import canUploadSameBuffer from './utils/canUploadSameBuffer'; /** + * User's customizable globals for overriding the default PIXI settings, such + * as a renderer's default resolution, framerate, float percision, etc. + * @example + * // Use the native window resolution as the default resolution + * // will support high-density displays when rendering + * PIXI.settings.RESOLUTION = window.devicePixelRatio. + * + * // Disable interpolation when scaling, will make texture be pixelated + * PIXI.settings.SCALE_MODE = PIXI.SCALE_MODES.NEAREST; * @namespace PIXI.settings */ export default { @@ -101,6 +110,9 @@ * @property {boolean} clearBeforeRender=true * @property {boolean} preserveDrawingBuffer=false * @property {boolean} roundPixels=false + * @property {number} width=800 + * @property {number} height=600 + * @property {boolean} legacy=false */ RENDER_OPTIONS: { view: null, @@ -112,6 +124,9 @@ clearBeforeRender: true, preserveDrawingBuffer: false, roundPixels: false, + width: 800, + height: 600, + legacy: false, }, /** diff --git a/src/core/shader/Program.js b/src/core/shader/Program.js index c0b538b..07b4398 100644 --- a/src/core/shader/Program.js +++ b/src/core/shader/Program.js @@ -1,6 +1,6 @@ import extractUniformsFromSrc from './extractUniformsFromSrc'; import generateUniformsSync from './generateUniformsSync'; -import glCore from 'pixi-gl-core'; +import shaderUtils from '../renderers/webgl/systems/shader/shader'; import { ProgramCache } from '../utils'; import getTestContext from '../utils/getTestContext'; @@ -64,14 +64,17 @@ } else { - vertexSrc = glCore.shader.setPrecision(vertexSrc, 'mediump'); - fragmentSrc = glCore.shader.setPrecision(fragmentSrc, 'mediump'); + vertexSrc = shaderUtils.setPrecision(vertexSrc, 'mediump'); + fragmentSrc = shaderUtils.setPrecision(fragmentSrc, 'mediump'); - const program = glCore.shader.compileProgram(gl, vertexSrc, fragmentSrc); + const program = shaderUtils.compileProgram(gl, vertexSrc, fragmentSrc); + console.log("<<<<<<>>><<"); this.attributeData = this.getAttributeData(program, gl); this.uniformData = this.getUniformData(program, gl); + console.log(this.uniformData); + //gl.deleteProgram(program); } } @@ -95,13 +98,13 @@ for (let i = 0; i < totalAttributes; i++) { const attribData = gl.getActiveAttrib(program, i); - const type = glCore.shader.mapType(gl, attribData.type); + const type = shaderUtils.mapType(gl, attribData.type); /*eslint-disable */ const data = { type: type, name: attribData.name, - size: glCore.shader.mapSize(type), + size: shaderUtils.mapSize(type), location: 0, }; /*eslint-enable */ @@ -137,13 +140,14 @@ // TODO expose this as a prop? // const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); - const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); + //const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); for (let i = 0; i < totalUniforms; i++) { const uniformData = gl.getActiveUniform(program, i); const name = uniformData.name.replace(/\[.*?\]/, ''); - const type = glCore.shader.mapType(gl, uniformData.type); + const type = shaderUtils.mapType(gl, uniformData.type); + console.log('mapping ' + uniformData.type + ' to ' + type); // if (!name.match(maskRegex)) { @@ -151,7 +155,7 @@ uniforms[name] = { type: type, size: uniformData.size, - value: glCore.shader.defaultValue(type, uniformData.size), + value: shaderUtils.defaultValue(type, uniformData.size), }; /*eslint-enable */ } diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index e387e99..b57d095 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -147,12 +147,12 @@ // so if _width is 0 then width was not set.. if (this._width) { - this.scale.x = sign(this.scale.x) * this._width / this.texture.orig.width; + this.scale.x = sign(this.scale.x) * this._width / this._texture.orig.width; } if (this._height) { - this.scale.y = sign(this.scale.y) * this._height / this.texture.orig.height; + this.scale.y = sign(this.scale.y) * this._height / this._texture.orig.height; } } diff --git a/src/core/sprites/canvas/CanvasTinter.js b/src/core/sprites/canvas/CanvasTinter.js index 27e5e0c..fd9de5d 100644 --- a/src/core/sprites/canvas/CanvasTinter.js +++ b/src/core/sprites/canvas/CanvasTinter.js @@ -18,7 +18,7 @@ */ getTintedTexture: (sprite, color) => { - const texture = sprite.texture; + const texture = sprite._texture; color = CanvasTinter.roundColor(color); diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 5a4edda..8b9b797 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -344,6 +344,7 @@ const texture = this._texture; const style = this._style; + const padding = style.trim ? 0 : style.padding; texture.baseTexture.valid = true; texture.baseTexture.resolution = this.resolution; @@ -353,11 +354,11 @@ texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - texture.trim.x = -style.padding; - texture.trim.y = -style.padding; + texture.trim.x = -padding; + texture.trim.y = -padding; - texture.orig.width = texture._frame.width - (style.padding * 2); - texture.orig.height = texture._frame.height - (style.padding * 2); + texture.orig.width = texture._frame.width - (padding * 2); + texture.orig.height = texture._frame.height - (padding * 2); // call sprite onTextureUpdate to update scale if _width or _height were set this._onTextureUpdate(); diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index 853da3d..8a9c34b 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -114,6 +114,38 @@ this.cacheId = null; this.validate(); + + /** + * Fired when a not-immediately-available source finishes loading. + * + * @protected + * @event PIXI.BaseTexture#loaded + * @param {PIXI.BaseTexture} baseTexture - Resource loaded. + */ + + /** + * Fired when a not-immediately-available source fails to load. + * + * @protected + * @event PIXI.BaseTexture#error + * @param {PIXI.BaseTexture} baseTexture - Resource errored. + */ + + /** + * Fired when BaseTexture is updated. + * + * @protected + * @event PIXI.BaseTexture#update + * @param {PIXI.BaseTexture} baseTexture - Instance of texture being updated. + */ + + /** + * Fired when BaseTexture is destroyed. + * + * @protected + * @event PIXI.BaseTexture#dispose + * @param {PIXI.BaseTexture} baseTexture - Instance of texture being destroyed. + */ } setResource(resource) diff --git a/src/core/textures/Spritesheet.js b/src/core/textures/Spritesheet.js index d469c62..9b24f27 100644 --- a/src/core/textures/Spritesheet.js +++ b/src/core/textures/Spritesheet.js @@ -208,6 +208,7 @@ // lets also add the frame to pixi's global cache for fromFrame and fromImage functions TextureCache[i] = this.textures[i]; + this.textures[i].textureCacheId = i; } frameIndex++; diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 0a2c379..2213087 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -147,9 +147,9 @@ /** * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. * - * @event update - * @memberof PIXI.Texture# + * @event PIXI.Texture#update * @protected + * @param {PIXI.Texture} texture - Instance of texture being updated. */ this._updateID = 0; @@ -159,6 +159,15 @@ * @type {Object} */ this.transform = null; + + /** + * The id under which this Texture has been added to the texture cache. This is + * automatically set in certain cases, but may not always be accurate, particularly if + * the texture is in the cache under multiple ids. + * + * @member {string} + */ + this.textureCacheId = null; } /** @@ -225,11 +234,17 @@ this._uvs = null; this.trim = null; this.orig = null; + this.textureCacheId = null; this.valid = false; - this.off('dispose', this.dispose, this); - this.off('update', this.update, this); + for (const prop in TextureCache) + { + if (TextureCache[prop] === this) + { + delete TextureCache[prop]; + } + } } /** @@ -315,14 +330,15 @@ */ static fromLoader(source, imageUrl, name) { - // console.log('added from loader...') + // console.log('added from loader...') const resource = new ImageResource(source);//.from(imageUrl, crossorigin);// document.createElement('img'); - // console.log('base resource ' + resource.width); + // console.log('base resource ' + resource.width); const baseTexture = new BaseTexture(resource, settings.SCALE_MODE, getResolutionOfUrl(imageUrl)); + /// console.log('base width ' + baseTexture.width); const texture = new Texture(baseTexture); @@ -335,6 +351,7 @@ // lets also add the frame to pixi's global cache for fromFrame and fromImage fucntions BaseTextureCache[name] = baseTexture; TextureCache[name] = texture; + texture.textureCacheId = name; // also add references by url if they are different. if (name !== imageUrl) @@ -355,6 +372,10 @@ */ static addTextureToCache(texture, id) { + if (!texture.textureCacheId) + { + texture.textureCacheId = id; + } TextureCache[id] = texture; } @@ -495,6 +516,7 @@ */ Texture.EMPTY = new Texture(new BaseTexture()); removeAllHandlers(Texture.EMPTY); +removeAllHandlers(Texture.EMPTY.baseTexture); /** * A white texture of 10x10 size, used for graphics and other things @@ -505,3 +527,4 @@ */ Texture.WHITE = createWhiteTexture(); removeAllHandlers(Texture.WHITE); +removeAllHandlers(Texture.WHITE.baseTexture); diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index 59eb9fd..a9b9f51 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -1,6 +1,7 @@ import BaseTexture from './BaseTexture'; import { uid, BaseTextureCache } from '../utils'; -import * as ticker from '../ticker'; +import { shared } from '../ticker'; +import { UPDATE_PRIORITY } from '../const'; /** * A texture of a [playing] Video. @@ -125,7 +126,7 @@ if (!this._isAutoUpdating && this.autoUpdate) { - ticker.shared.add(this.update, this); + shared.add(this.update, this, UPDATE_PRIORITY.HIGH); this._isAutoUpdating = true; } } @@ -139,7 +140,7 @@ { if (this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); this._isAutoUpdating = false; } } @@ -187,7 +188,7 @@ { if (this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); } if (this.source && this.source._pixiId) @@ -281,12 +282,12 @@ if (!this._autoUpdate && this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); this._isAutoUpdating = false; } else if (this._autoUpdate && !this._isAutoUpdating) { - ticker.shared.add(this.update, this); + shared.add(this.update, this, UPDATE_PRIORITY.HIGH); this._isAutoUpdating = true; } } diff --git a/src/core/ticker/Ticker.js b/src/core/ticker/Ticker.js index 17a1517..40df138 100644 --- a/src/core/ticker/Ticker.js +++ b/src/core/ticker/Ticker.js @@ -1,12 +1,10 @@ import settings from '../settings'; -import EventEmitter from 'eventemitter3'; - -// Internal event used by composed emitter -const TICK = 'tick'; +import { UPDATE_PRIORITY } from '../const'; +import TickerListener from './TickerListener'; /** * A Ticker class that runs an update loop that other objects listen to. - * This class is composed around an EventEmitter object to add listeners + * This class is composed around listeners * meant for execution on the next requested animation frame. * Animation frames are requested only when necessary, * e.g. When the ticker is started and the emitter has listeners. @@ -22,10 +20,11 @@ constructor() { /** - * Internal emitter used to fire 'tick' event + * The first listener. All new listeners added are chained on this. * @private + * @type {TickerListener} */ - this._emitter = new EventEmitter(); + this._head = new TickerListener(null, null, Infinity); /** * Internal current frame request ID @@ -131,7 +130,7 @@ // Invoke listeners now this.update(time); // Listener side effects may have modified ticker state. - if (this.started && this._requestId === null && this._emitter.listeners(TICK, true)) + if (this.started && this._requestId === null && this._head.next) { this._requestId = requestAnimationFrame(this._tick); } @@ -148,7 +147,7 @@ */ _requestIfNeeded() { - if (this._requestId === null && this._emitter.listeners(TICK, true)) + if (this._requestId === null && this._head.next) { // ensure callbacks get correct delta this.lastTime = performance.now(); @@ -193,35 +192,72 @@ } /** - * Calls {@link module:eventemitter3.EventEmitter#on} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. + * Register a handler for tick events. Calls continuously unless + * it is removed or the ticker is stopped. * * @param {Function} fn - The listener function to be added for updates * @param {Function} [context] - The listener context + * @param {number} [priority=PIXI.UPDATE_PRIORITY.NORMAL] - The priority for emitting * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - add(fn, context) + add(fn, context, priority = UPDATE_PRIORITY.NORMAL) { - this._emitter.on(TICK, fn, context); - - this._startIfPossible(); - - return this; + return this._addListener(new TickerListener(fn, context, priority)); } /** - * Calls {@link module:eventemitter3.EventEmitter#once} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. + * Add a handler for the tick event which is only execute once. * * @param {Function} fn - The listener function to be added for one update * @param {Function} [context] - The listener context + * @param {number} [priority=PIXI.UPDATE_PRIORITY.NORMAL] - The priority for emitting * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - addOnce(fn, context) + addOnce(fn, context, priority = UPDATE_PRIORITY.NORMAL) { - this._emitter.once(TICK, fn, context); + return this._addListener(new TickerListener(fn, context, priority, true)); + } + + /** + * Internally adds the event handler so that it can be sorted by priority. + * Priority allows certain handler (user, AnimatedSprite, Interaction) to be run + * before the rendering. + * + * @private + * @param {TickerListener} listener - Current listener being added. + * @returns {PIXI.ticker.Ticker} This instance of a ticker + */ + _addListener(listener) + { + // For attaching to head + let current = this._head.next; + let previous = this._head; + + // Add the first item + if (!current) + { + listener.connect(previous); + } + else + { + // Go from highest to lowest priority + while (current) + { + if (listener.priority >= current.priority) + { + listener.connect(previous); + break; + } + previous = current; + current = current.next; + } + + // Not yet connected + if (!listener.previous) + { + listener.connect(previous); + } + } this._startIfPossible(); @@ -229,19 +265,33 @@ } /** - * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. - * It checks if the emitter has listeners for 'tick' event. - * If it does, then it cancels the animation frame. + * Removes any handlers matching the function and context parameters. + * If no handlers are left after removing, then it cancels the animation frame. * - * @param {Function} [fn] - The listener function to be removed + * @param {Function} fn - The listener function to be removed * @param {Function} [context] - The listener context to be removed * @returns {PIXI.ticker.Ticker} This instance of a ticker */ remove(fn, context) { - this._emitter.off(TICK, fn, context); + let listener = this._head.next; - if (!this._emitter.listeners(TICK, true)) + while (listener) + { + // We found a match, lets remove it + // no break to delete all possible matches + // incase a listener was added 2+ times + if (listener.match(fn, context)) + { + listener = listener.destroy(); + } + else + { + listener = listener.next; + } + } + + if (!this._head.next) { this._cancelIfNeeded(); } @@ -276,6 +326,25 @@ } /** + * Destroy the ticker and don't use after this. Calling + * this method removes all references to internal events. + */ + destroy() + { + this.stop(); + + let listener = this._head.next; + + while (listener) + { + listener = listener.destroy(true); + } + + this._head.destroy(); + this._head = null; + } + + /** * Triggers an update. An update entails setting the * current {@link PIXI.ticker.Ticker#elapsedMS}, * the current {@link PIXI.ticker.Ticker#deltaTime}, @@ -320,8 +389,22 @@ this.deltaTime = elapsedMS * settings.TARGET_FPMS * this.speed; + // Cache a local reference, in-case ticker is destroyed + // during the emit, we can still check for head.next + const head = this._head; + // Invoke listeners added to internal emitter - this._emitter.emit(TICK, this.deltaTime); + let listener = head.next; + + while (listener) + { + listener = listener.emit(this.deltaTime); + } + + if (!head.next) + { + this._cancelIfNeeded(); + } } else { diff --git a/src/core/ticker/TickerListener.js b/src/core/ticker/TickerListener.js new file mode 100644 index 0000000..2bedb34 --- /dev/null +++ b/src/core/ticker/TickerListener.js @@ -0,0 +1,158 @@ +/** + * Internal class for handling the priority sorting of ticker handlers. + * + * @private + * @class + * @memberof PIXI.ticker + */ +export default class TickerListener +{ + /** + * Constructor + * + * @param {Function} fn - The listener function to be added for one update + * @param {Function} [context=null] - The listener context + * @param {number} [priority=0] - The priority for emitting + * @param {boolean} [once=false] - If the handler should fire once + */ + constructor(fn, context = null, priority = 0, once = false) + { + /** + * The handler function to execute. + * @member {Function} + */ + this.fn = fn; + + /** + * The calling to execute. + * @member {Function} + */ + this.context = context; + + /** + * The current priority. + * @member {number} + */ + this.priority = priority; + + /** + * If this should only execute once. + * @member {boolean} + */ + this.once = once; + + /** + * The next item in chain. + * @member {TickerListener} + */ + this.next = null; + + /** + * The previous item in chain. + * @member {TickerListener} + */ + this.previous = null; + + /** + * `true` if this listener has been destroyed already. + * @member {boolean} + * @private + */ + this._destroyed = false; + } + + /** + * Simple compare function to figure out if a function and context match. + * + * @param {Function} fn - The listener function to be added for one update + * @param {Function} context - The listener context + * @return {boolean} `true` if the listener match the arguments + */ + match(fn, context) + { + context = context || null; + + return this.fn === fn && this.context === context; + } + + /** + * Emit by calling the current function. + * @param {number} deltaTime - time since the last emit. + * @return {TickerListener} Next ticker + */ + emit(deltaTime) + { + if (this.context) + { + this.fn.call(this.context, deltaTime); + } + else + { + this.fn(deltaTime); + } + + if (this.once) + { + this.destroy(); + } + + const redirect = this.next; + + // Soft-destroying should remove + // the next reference + if (this._destroyed) + { + this.next = null; + } + + return redirect; + } + + /** + * Connect to the list. + * @param {TickerListener} previous - Input node, previous listener + */ + connect(previous) + { + this.previous = previous; + if (previous.next) + { + previous.next.previous = this; + } + this.next = previous.next; + previous.next = this; + } + + /** + * Destroy and don't use after this. + * @param {boolean} [hard = false] `true` to remove the `next` reference, this + * is considered a hard destroy. Soft destroy maintains the next reference. + * @return {TickerListener} The listener to redirect while emitting or removing. + */ + destroy(hard = false) + { + this._destroyed = true; + this.fn = null; + this.context = null; + + // Disconnect, hook up next and previous + if (this.previous) + { + this.previous.next = this.next; + } + + if (this.next) + { + this.next.previous = this.previous; + } + + // Redirect to the next item + const redirect = this.previous; + + // Remove references + this.next = hard ? null : redirect; + this.previous = null; + + return redirect; + } +} diff --git a/src/core/ticker/index.js b/src/core/ticker/index.js index 4b3017c..c3020d4 100644 --- a/src/core/ticker/index.js +++ b/src/core/ticker/index.js @@ -45,8 +45,25 @@ const shared = new Ticker(); shared.autoStart = true; +shared.destroy = () => +{ + // protect destroying shared ticker + // this is used by other internal systems + // like AnimatedSprite and InteractionManager +}; /** + * This namespace contains an API for interacting with PIXI's internal global update loop. + * + * This ticker is used for rendering, {@link PIXI.extras.AnimatedSprite AnimatedSprite}, + * {@link PIXI.interaction.InteractionManager InteractionManager} and many other time-based PIXI systems. + * @example + * const ticker = new PIXI.ticker.Ticker(); + * ticker.stop(); + * ticker.add((deltaTime) => { + * // do something every frame + * }); + * ticker.start(); * @namespace PIXI.ticker */ export { shared, Ticker }; diff --git a/src/core/utils/index.js b/src/core/utils/index.js index be44750..7441f7b 100644 --- a/src/core/utils/index.js +++ b/src/core/utils/index.js @@ -9,6 +9,21 @@ let saidHello = false; /** + * Generalized convenience utilities for PIXI. + * @example + * // Extend PIXI's internal Event Emitter. + * class MyEmitter extends PIXI.utils.EventEmitter { + * constructor() { + * super(); + * console.log("Emitter created!"); + * } + * } + * + * // Get info on current device + * console.log(PIXI.utils.isMobile); + * + * // Convert hex color to string + * console.log(PIXI.utils.hex2string(0xff00ff)); // returns: "#ff00ff" * @namespace PIXI.utils */ export { diff --git a/src/deprecation.js b/src/deprecation.js index b9c73b3..33daeec 100644 --- a/src/deprecation.js +++ b/src/deprecation.js @@ -918,6 +918,35 @@ }); /** + * @method + * @name PIXI.prepare.BasePrepare#register + * @see PIXI.prepare.BasePrepare#registerFindHook + * @deprecated since version 4.4.2 + * @param {Function} [addHook] - Function call that takes two parameters: `item:*, queue:Array` + * function must return `true` if it was able to add item to the queue. + * @param {Function} [uploadHook] - Function call that takes two parameters: `prepare:CanvasPrepare, item:*` and + * function must return `true` if it was able to handle upload of item. + * @return {PIXI.BasePrepare} Instance of plugin for chaining. + */ +prepare.BasePrepare.prototype.register = function register(addHook, uploadHook) +{ + warn('renderer.plugins.prepare.register is now deprecated, ' + + 'please use renderer.plugins.prepare.registerFindHook & renderer.plugins.prepare.registerUploadHook'); + + if (addHook) + { + this.registerFindHook(addHook); + } + + if (uploadHook) + { + this.registerUploadHook(uploadHook); + } + + return this; +}; + +/** * The number of graphics or textures to upload to the GPU. * * @name PIXI.prepare.canvas.UPLOADS_PER_FRAME @@ -969,67 +998,73 @@ }, }); -Object.defineProperties(loaders.Resource.prototype, { - isJson: { - get() - { - warn('The isJson property is deprecated, please use `resource.type === Resource.TYPE.JSON`.'); +if (loaders.Loader) +{ + const Resource = loaders.Resource; + const Loader = loaders.Loader; - return this.type === loaders.Loader.Resource.TYPE.JSON; + Object.defineProperties(Resource.prototype, { + isJson: { + get() + { + warn('The isJson property is deprecated, please use `resource.type === Resource.TYPE.JSON`.'); + + return this.type === Resource.TYPE.JSON; + }, }, - }, - isXml: { - get() - { - warn('The isXml property is deprecated, please use `resource.type === Resource.TYPE.XML`.'); + isXml: { + get() + { + warn('The isXml property is deprecated, please use `resource.type === Resource.TYPE.XML`.'); - return this.type === loaders.Loader.Resource.TYPE.XML; + return this.type === Resource.TYPE.XML; + }, }, - }, - isImage: { - get() - { - warn('The isImage property is deprecated, please use `resource.type === Resource.TYPE.IMAGE`.'); + isImage: { + get() + { + warn('The isImage property is deprecated, please use `resource.type === Resource.TYPE.IMAGE`.'); - return this.type === loaders.Loader.Resource.TYPE.IMAGE; + return this.type === Resource.TYPE.IMAGE; + }, }, - }, - isAudio: { - get() - { - warn('The isAudio property is deprecated, please use `resource.type === Resource.TYPE.AUDIO`.'); + isAudio: { + get() + { + warn('The isAudio property is deprecated, please use `resource.type === Resource.TYPE.AUDIO`.'); - return this.type === loaders.Loader.Resource.TYPE.AUDIO; + return this.type === Resource.TYPE.AUDIO; + }, }, - }, - isVideo: { - get() - { - warn('The isVideo property is deprecated, please use `resource.type === Resource.TYPE.VIDEO`.'); + isVideo: { + get() + { + warn('The isVideo property is deprecated, please use `resource.type === Resource.TYPE.VIDEO`.'); - return this.type === loaders.Loader.Resource.TYPE.VIDEO; + return this.type === Resource.TYPE.VIDEO; + }, }, - }, -}); + }); -Object.defineProperties(loaders.Loader.prototype, { - before: { - get() - { - warn('The before() method is deprecated, please use pre().'); + Object.defineProperties(Loader.prototype, { + before: { + get() + { + warn('The before() method is deprecated, please use pre().'); - return this.pre; + return this.pre; + }, }, - }, - after: { - get() - { - warn('The after() method is deprecated, please use use().'); + after: { + get() + { + warn('The after() method is deprecated, please use use().'); - return this.use; + return this.use; + }, }, - }, -}); + }); +} /** * @name PIXI.interaction.interactiveTarget#defaultCursor diff --git a/src/extract/canvas/CanvasExtract.js b/src/extract/canvas/CanvasExtract.js index 941ca05..3ac5aee 100644 --- a/src/extract/canvas/CanvasExtract.js +++ b/src/extract/canvas/CanvasExtract.js @@ -8,7 +8,7 @@ * An instance of this class is automatically created by default, and can be found at renderer.plugins.extract * * @class - * @memberof PIXI + * @memberof PIXI.extract */ export default class CanvasExtract { @@ -21,9 +21,9 @@ /** * Collection of methods for extracting data (image, pixels, etc.) from a display object or render texture * - * @member {PIXI.CanvasExtract} extract + * @member {PIXI.extract.CanvasExtract} extract * @memberof PIXI.CanvasRenderer# - * @see PIXI.CanvasExtract + * @see PIXI.extract.CanvasExtract */ renderer.extract = this; } diff --git a/src/extract/index.js b/src/extract/index.js index 0f2170d..e9478c1 100644 --- a/src/extract/index.js +++ b/src/extract/index.js @@ -1,2 +1,22 @@ +/** + * This namespace provides renderer-specific plugins for exporting content from a renderer. + * For instance, these plugins can be used for saving an Image, Canvas element or for exporting the raw image data (pixels). + * + * Do not instantiate these plugins directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. + * @example + * // Create a new app (will auto-add extract plugin to renderer) + * const app = new PIXI.Application(); + * + * // Draw a red circle + * const graphics = new PIXI.Graphics() + * .beginFill(0xFF0000) + * .drawCircle(0, 0, 50); + * + * // Render the graphics as an HTMLImageElement + * const image = app.renderer.plugins.extract.image(graphics); + * document.body.appendChild(image); + * @namespace PIXI.extract + */ export { default as webgl } from './webgl/WebGLExtract'; export { default as canvas } from './canvas/CanvasExtract'; diff --git a/src/extract/webgl/WebGLExtract.js b/src/extract/webgl/WebGLExtract.js index 60228bb..1c3dd30 100644 --- a/src/extract/webgl/WebGLExtract.js +++ b/src/extract/webgl/WebGLExtract.js @@ -9,7 +9,7 @@ * An instance of this class is automatically created by default, and can be found at renderer.plugins.extract * * @class - * @memberof PIXI + * @memberof PIXI.extract */ export default class WebGLExtract { @@ -22,9 +22,9 @@ /** * Collection of methods for extracting data (image, pixels, etc.) from a display object or render texture * - * @member {PIXI.WebGLExtract} extract + * @member {PIXI.extract.WebGLExtract} extract * @memberof PIXI.WebGLRenderer# - * @see PIXI.WebGLExtract + * @see PIXI.extract.WebGLExtract */ renderer.extract = this; } diff --git a/src/extras/AnimatedSprite.js b/src/extras/AnimatedSprite.js index 13b804f..5297778 100644 --- a/src/extras/AnimatedSprite.js +++ b/src/extras/AnimatedSprite.js @@ -137,7 +137,7 @@ this.playing = true; if (this._autoUpdate) { - core.ticker.shared.add(this.update, this); + core.ticker.shared.add(this.update, this, core.UPDATE_PRIORITY.HIGH); } } diff --git a/src/extras/TextureTransform.js b/src/extras/TextureTransform.js index 78e6e40..16536d2 100644 --- a/src/extras/TextureTransform.js +++ b/src/extras/TextureTransform.js @@ -65,8 +65,36 @@ } /** + * Multiplies uvs array to transform + * @param {Float32Array} uvs mesh uvs + * @param {Float32Array} [out=uvs] output + * @returns {Float32Array} output + */ + multiplyUvs(uvs, out) + { + if (out === undefined) + { + out = uvs; + } + + const mat = this.mapCoord; + + for (let i = 0; i < uvs.length; i += 2) + { + const x = uvs[i]; + const y = uvs[i + 1]; + + out[i] = (x * mat.a) + (y * mat.c) + mat.tx; + out[i + 1] = (x * mat.b) + (y * mat.d) + mat.ty; + } + + return out; + } + + /** * updates matrices if texture was changed * @param {boolean} forceUpdate if true, matrices will be updated any case + * @returns {boolean} whether or not it was updated */ update(forceUpdate) { @@ -74,13 +102,13 @@ if (!tex || !tex.valid) { - return; + return false; } if (!forceUpdate && this._lastTextureID === tex._updateID) { - return; + return false; } this._lastTextureID = tex._updateID; @@ -110,5 +138,7 @@ frame[3] = (tex._frame.y + tex._frame.height - margin + offset) / texBase.height; this.uClampOffset[0] = offset / texBase.realWidth; this.uClampOffset[1] = offset / texBase.realHeight; + + return true; } } diff --git a/src/extras/TilingSprite.js b/src/extras/TilingSprite.js index 1f6014f..a3f027f 100644 --- a/src/extras/TilingSprite.js +++ b/src/extras/TilingSprite.js @@ -180,9 +180,9 @@ const transform = this.worldTransform; const resolution = renderer.resolution; const baseTexture = texture.baseTexture; - const baseTextureResolution = texture.baseTexture.resolution; - const modX = (this.tilePosition.x / this.tileScale.x) % texture._frame.width; - const modY = (this.tilePosition.y / this.tileScale.y) % texture._frame.height; + const baseTextureResolution = baseTexture.resolution; + const modX = ((this.tilePosition.x / this.tileScale.x) % texture._frame.width) * baseTextureResolution; + const modY = ((this.tilePosition.y / this.tileScale.y) % texture._frame.height) * baseTextureResolution; // create a nice shiny pattern! // TODO this needs to be refreshed if texture changes.. diff --git a/src/extras/cacheAsBitmap.js b/src/extras/cacheAsBitmap.js index 5dd5640..aea06a8 100644 --- a/src/extras/cacheAsBitmap.js +++ b/src/extras/cacheAsBitmap.js @@ -227,7 +227,16 @@ this.transform._parentID = -1; // restore the transform of the cached sprite to avoid the nasty flicker.. - this.updateTransform(); + if (!this.parent) + { + this.parent = renderer._tempDisplayObjectParent; + this.updateTransform(); + this.parent = null; + } + else + { + this.updateTransform(); + } // map the hit test.. this.containsPoint = cachedSprite.containsPoint.bind(cachedSprite); @@ -314,7 +323,17 @@ cachedSprite._bounds = this._bounds; cachedSprite.alpha = cacheAlpha; - this.updateTransform(); + if (!this.parent) + { + this.parent = renderer._tempDisplayObjectParent; + this.updateTransform(); + this.parent = null; + } + else + { + this.updateTransform(); + } + this.updateTransform = this.displayObjectUpdateTransform; this._cacheData.sprite = cachedSprite; @@ -358,9 +377,12 @@ * Destroys the cached object. * * @private + * @param {object|boolean} [options] - Options parameter. A boolean will act as if all options + * have been set to that value. + * Used when destroying containers, see the Container.destroy method. */ -DisplayObject.prototype._cacheAsBitmapDestroy = function _cacheAsBitmapDestroy() +DisplayObject.prototype._cacheAsBitmapDestroy = function _cacheAsBitmapDestroy(options) { this.cacheAsBitmap = false; - this.destroy(); + this.destroy(options); }; diff --git a/src/extras/index.js b/src/extras/index.js index bbd8bbf..a34d7ec 100644 --- a/src/extras/index.js +++ b/src/extras/index.js @@ -1,7 +1,7 @@ /** + * Additional PIXI DisplayObjects for animation, tiling and bitmap text. * @namespace PIXI.extras */ -export { default as TextureTransform } from './TextureTransform'; export { default as AnimatedSprite } from './AnimatedSprite'; export { default as TilingSprite } from './TilingSprite'; export { default as TilingSpriteRenderer } from './webgl/TilingSpriteRenderer'; diff --git a/src/extras/webgl/TilingSpriteRenderer.js b/src/extras/webgl/TilingSpriteRenderer.js index aeec172..eee3b5c 100644 --- a/src/extras/webgl/TilingSpriteRenderer.js +++ b/src/extras/webgl/TilingSpriteRenderer.js @@ -11,7 +11,7 @@ * WebGL renderer plugin for tiling sprites * * @class - * @memberof PIXI + * @memberof PIXI.extras * @extends PIXI.ObjectRenderer */ export default class TilingSpriteRenderer extends core.ObjectRenderer @@ -118,7 +118,7 @@ tempMat.invert(); if (isSimple) { - tempMat.append(uv.mapCoord); + tempMat.prepend(uv.mapCoord); } else { diff --git a/src/filters/displacement/DisplacementFilter.js b/src/filters/displacement/DisplacementFilter.js index d5355c3..b5d030c 100644 --- a/src/filters/displacement/DisplacementFilter.js +++ b/src/filters/displacement/DisplacementFilter.js @@ -35,7 +35,7 @@ this.maskSprite = sprite; this.maskMatrix = maskMatrix; - this.uniforms.mapSampler = sprite.texture; + this.uniforms.mapSampler = sprite._texture; this.uniforms.filterMatrix = maskMatrix; this.uniforms.scale = { x: 1, y: 1 }; diff --git a/src/filters/index.js b/src/filters/index.js index f3e8862..7ceb50a 100644 --- a/src/filters/index.js +++ b/src/filters/index.js @@ -1,4 +1,21 @@ /** + * This namespace contains WebGL-only display filters that can be applied + * to DisplayObjects using the {@link PIXI.DisplayObject#filters filters} property. + * @example + * // Create a new application + * const app = new PIXI.Application(); + * + * // Draw a green rectangle + * const rect = new PIXI.Graphics() + * .beginFill(0x00ff00) + * .drawRect(40, 40, 200, 200); + * + * // Add a blur filter + * rect.filters = [new PIXI.filters.BlurFilter()]; + * + * // Display rectangle + * app.stage.addChild(rect); + * document.body.appendChild(app.view); * @namespace PIXI.filters */ export { default as FXAAFilter } from './fxaa/FXAAFilter'; diff --git a/src/index.js b/src/index.js index c553427..77da490 100644 --- a/src/index.js +++ b/src/index.js @@ -20,6 +20,14 @@ import { utils } from './core'; utils.mixins.performMixins(); +/** + * Alias for {@link PIXI.loaders.shared}. + * @name loader + * @memberof PIXI + * @type {PIXI.loader.Loader} + */ +const loader = loaders.shared || null; + export { accessibility, extract, @@ -30,18 +38,8 @@ mesh, particles, prepare, + loader, }; -/** - * A premade instance of the loader that can be used to load resources. - * - * @name loader - * @memberof PIXI - * @property {PIXI.loaders.Loader} - */ -const loader = loaders && loaders.Loader ? new loaders.Loader() : null; // check is there in case user excludes loader lib - -export { loader }; - // Always export pixi globally. global.PIXI = exports; // eslint-disable-line diff --git a/src/interaction/InteractionData.js b/src/interaction/InteractionData.js index 02d53a3..449f8cc 100644 --- a/src/interaction/InteractionData.js +++ b/src/interaction/InteractionData.js @@ -30,7 +30,10 @@ /** * When passed to an event handler, this will be the original DOM Event that was captured * - * @member {Event} + * @see https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent + * @see https://developer.mozilla.org/en-US/docs/Web/API/TouchEvent + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent + * @member {MouseEvent|TouchEvent|PointerEvent} */ this.originalEvent = null; } diff --git a/src/interaction/InteractionManager.js b/src/interaction/InteractionManager.js index 8baf13a..2d75c7c 100644 --- a/src/interaction/InteractionManager.js +++ b/src/interaction/InteractionManager.js @@ -13,6 +13,14 @@ const MOUSE_POINTER_ID = 'MOUSE'; +// helpers for hitTest() - only used inside hitTest() +const hitTestEvent = { + target: null, + data: { + global: null, + }, +}; + /** * The interaction manager deals with mouse, touch and pointer events. Any DisplayObject can be interactive * if its interactive parameter is set to true @@ -245,67 +253,60 @@ this.setTargetElement(this.renderer.view, this.renderer.resolution); /** - * Fired when a pointer device button (usually a mouse button) is pressed on the display + * Fired when a pointer device button (usually a mouse left-button) is pressed on the display * object. * - * @event mousedown - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mousedown + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device secondary button (usually a mouse right-button) is pressed * on the display object. * - * @event rightdown - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightdown + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** - * Fired when a pointer device button (usually a mouse button) is released over the display + * Fired when a pointer device button (usually a mouse left-button) is released over the display * object. * - * @event mouseup - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseup + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device secondary button (usually a mouse right-button) is released * over the display object. * - * @event rightup - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightup + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** - * Fired when a pointer device button (usually a mouse button) is pressed and released on + * Fired when a pointer device button (usually a mouse left-button) is pressed and released on * the display object. * - * @event click - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#click + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device secondary button (usually a mouse right-button) is pressed * and released on the display object. * - * @event rightclick - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightclick + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** - * Fired when a pointer device button (usually a mouse button) is released outside the + * Fired when a pointer device button (usually a mouse left-button) is released outside the * display object that initially registered a * [mousedown]{@link PIXI.interaction.InteractionManager#event:mousedown}. * - * @event mouseupoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** @@ -313,146 +314,362 @@ * outside the display object that initially registered a * [rightdown]{@link PIXI.interaction.InteractionManager#event:rightdown}. * - * @event rightupoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device (usually a mouse) is moved while over the display object * - * @event mousemove - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mousemove + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device (usually a mouse) is moved onto the display object * - * @event mouseover - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseover + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device (usually a mouse) is moved off the display object * - * @event mouseout - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseout + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is pressed on the display object. * - * @event pointerdown - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerdown + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is released over the display object. * - * @event pointerup - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerup + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when the operating system cancels a pointer event * - * @event pointercancel - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointercancel + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is pressed and released on the display object. * - * @event pointertap - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointertap + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is released outside the display object that initially * registered a [pointerdown]{@link PIXI.interaction.InteractionManager#event:pointerdown}. * - * @event pointerupoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device is moved while over the display object * - * @event pointermove - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointermove + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device is moved onto the display object * - * @event pointerover - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerover + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device is moved off the display object * - * @event pointerout - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerout + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is placed on the display object. * - * @event touchstart - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchstart + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is removed from the display object. * - * @event touchend - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchend + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when the operating system cancels a touch * - * @event touchcancel - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchcancel + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is placed and removed from the display object. * - * @event tap - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#tap + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is removed outside of the display object that initially * registered a [touchstart]{@link PIXI.interaction.InteractionManager#event:touchstart}. * - * @event touchendoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchendoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is moved along the display object. * - * @event touchmove - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchmove + * @param {PIXI.interaction.InteractionData} event - Interaction data */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is pressed on the display. + * object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mousedown + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is pressed + * on the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightdown + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is released over the display + * object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseup + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is released + * over the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightup + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is pressed and released on + * the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#click + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is pressed + * and released on the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightclick + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is released outside the + * display object that initially registered a + * [mousedown]{@link PIXI.DisplayObject#event:mousedown}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is released + * outside the display object that initially registered a + * [rightdown]{@link PIXI.DisplayObject#event:rightdown}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device (usually a mouse) is moved while over the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mousemove + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device (usually a mouse) is moved onto the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseover + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device (usually a mouse) is moved off the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseout + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is pressed on the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerdown + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is released over the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerup + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when the operating system cancels a pointer event. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointercancel + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is pressed and released on the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointertap + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is released outside the display object that initially + * registered a [pointerdown]{@link PIXI.DisplayObject#event:pointerdown}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device is moved while over the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointermove + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device is moved onto the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerover + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device is moved off the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerout + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is placed on the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchstart + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is removed from the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchend + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when the operating system cancels a touch. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchcancel + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is placed and removed from the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#tap + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is removed outside of the display object that initially + * registered a [touchstart]{@link PIXI.DisplayObject#event:touchstart}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchendoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is moved along the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchmove + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + } + + /** + * Hit tests a point against the display tree, returning the first interactive object that is hit. + * + * @param {PIXI.Point} globalPoint - A point to hit test with, in global space. + * @param {PIXI.Container} [root] - The root display object to start from. If omitted, defaults + * to the last rendered root of the associated renderer. + * @return {PIXI.DisplayObject} The hit display object, if any. + */ + hitTest(globalPoint, root) + { + // clear the target for our hit test + hitTestEvent.target = null; + // assign the global point + hitTestEvent.data.global = globalPoint; + // ensure safety of the root + if (!root) + { + root = this.renderer._lastObjectRendered; + } + // run the hit test + this.processInteractive(hitTestEvent, root, null, true); + // return our found object - it'll be null if we didn't hit anything + + return hitTestEvent.target; } /** @@ -487,7 +704,7 @@ return; } - core.ticker.shared.add(this.update, this); + core.ticker.shared.add(this.update, this, core.UPDATE_PRIORITY.INTERACTION); if (window.navigator.msPointerEnabled) { @@ -804,8 +1021,6 @@ } } - let keepHitTestingAfterChildren = hitTest; - // ** FREE TIP **! If an object is not interactive or has no buttons in it // (such as a game scene!) set interactiveChildren to false for that displayObject. // This will allow pixi to completely ignore and bypass checking the displayObjects children. @@ -818,7 +1033,9 @@ const child = children[i]; // time to get recursive.. if this function will return if something is hit.. - if (this.processInteractive(interactionEvent, child, func, hitTest, interactiveParent)) + const childHit = this.processInteractive(interactionEvent, child, func, hitTest, interactiveParent); + + if (childHit) { // its a good idea to check if a child has lost its parent. // this means it has been removed whilst looping so its best @@ -827,8 +1044,6 @@ continue; } - hit = true; - // we no longer need to hit test any more objects in this container as we we // now know the parent has been hit interactiveParent = false; @@ -838,36 +1053,41 @@ // This means we no longer need to hit test anything else. We still need to run // through all objects, but we don't need to perform any hit tests. - keepHitTestingAfterChildren = false; - - if (child.interactive) + if (childHit) { - hitTest = false; + if (interactionEvent.target) + { + hitTest = false; + } + hit = true; } - - // we can break now as we have hit an object. } } } - hitTest = keepHitTestingAfterChildren; - // no point running this if the item is not interactive or does not have an interactive parent. if (interactive) { // if we are hit testing (as in we have no hit any objects yet) // We also don't need to worry about hit testing if once of the displayObjects children - // has already been hit! - if (hitTest && !hit) + // has already been hit - but only if it was interactive, otherwise we need to keep + // looking for an interactive child, just in case we hit one + if (hitTest && !interactionEvent.target) { if (displayObject.hitArea) { displayObject.worldTransform.applyInverse(point, this._tempPoint); - hit = displayObject.hitArea.contains(this._tempPoint.x, this._tempPoint.y); + if (displayObject.hitArea.contains(this._tempPoint.x, this._tempPoint.y)) + { + hit = true; + } } else if (displayObject.containsPoint) { - hit = displayObject.containsPoint(point); + if (displayObject.containsPoint(point)) + { + hit = true; + } } } @@ -878,7 +1098,10 @@ interactionEvent.target = displayObject; } - func(interactionEvent, displayObject, hit); + if (func) + { + func(interactionEvent, displayObject, !!hit); + } } } @@ -914,7 +1137,7 @@ { const event = events[i]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -998,7 +1221,7 @@ { const event = events[i]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1098,7 +1321,7 @@ const test = isRightButton ? flags.RIGHT_DOWN : flags.LEFT_DOWN; - const isDown = trackingData !== undefined && (trackingData.flags | test); + const isDown = trackingData !== undefined && (trackingData.flags & test); if (hit) { @@ -1118,11 +1341,11 @@ { if (isRightButton) { - trackingData.rightDown = hit; + trackingData.rightDown = false; } else { - trackingData.leftDown = hit; + trackingData.leftDown = false; } } } @@ -1180,7 +1403,7 @@ { const event = events[i]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1255,7 +1478,7 @@ this.setCursorMode(null); } - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1268,6 +1491,12 @@ { this.emit('mouseout', interactionEvent); } + else + { + // we can get touchleave events after touchend, so we want to make sure we don't + // introduce memory leaks + this.releaseInteractionDataForPointerId(interactionData.identifier); + } } /** @@ -1344,7 +1573,7 @@ // Only mouse and pointer can call onPointerOver, so events will always be length 1 const event = events[0]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1366,12 +1595,14 @@ * Get InteractionData for a given pointerId. Store that data as well * * @private - * @param {number} pointerId - Identifier from a pointer event + * @param {PointerEvent} event - Normalized pointer event, output from normalizeToPointerData * @return {InteractionData} - Interaction data for the given pointer identifier */ - getInteractionDataForPointerId(pointerId) + getInteractionDataForPointerId(event) { - if (pointerId === MOUSE_POINTER_ID) + const pointerId = event.pointerId; + + if (pointerId === MOUSE_POINTER_ID || event.pointerType === 'mouse') { return this.mouse; } diff --git a/src/interaction/InteractionTrackingData.js b/src/interaction/InteractionTrackingData.js index 6a1f69e..8493b63 100644 --- a/src/interaction/InteractionTrackingData.js +++ b/src/interaction/InteractionTrackingData.js @@ -35,8 +35,10 @@ } /** + * Unique pointer id of the event + * * @readonly - * @type {number} Unique pointer id of the event + * @member {number} */ get pointerId() { diff --git a/src/interaction/index.js b/src/interaction/index.js index 56c5b09..72f7d71 100644 --- a/src/interaction/index.js +++ b/src/interaction/index.js @@ -1,4 +1,8 @@ /** + * This namespace contains a renderer plugin for handling mouse, pointer, and touch events. + * + * Do not instantiate this plugin directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. * @namespace PIXI.interaction */ export { default as InteractionData } from './InteractionData'; diff --git a/src/interaction/interactiveTarget.js b/src/interaction/interactiveTarget.js index d252fba..cebac40 100644 --- a/src/interaction/interactiveTarget.js +++ b/src/interaction/interactiveTarget.js @@ -2,7 +2,7 @@ * Default property values of interactive objects * Used by {@link PIXI.interaction.InteractionManager} to automatically give all DisplayObjects these properties * - * @mixin + * @private * @name interactiveTarget * @memberof PIXI.interaction * @example @@ -14,18 +14,28 @@ * ); */ export default { + /** - * Determines if the displayObject be clicked/touched + * Enable interaction events for the DisplayObject. Touch, pointer and mouse + * events will not be emitted unless `interactive` is set to `true`. * - * @inner {boolean} + * @example + * const sprite = new PIXI.Sprite(texture); + * sprite.interactive = true; + * sprite.on('tap', (event) => { + * //handle event + * }); + * @member {boolean} + * @memberof PIXI.DisplayObject# */ interactive: false, /** * Determines if the children to the displayObject can be clicked/touched - * Setting this to false allows pixi to bypass a recursive hitTest function + * Setting this to false allows pixi to bypass a recursive `hitTest` function * - * @inner {boolean} + * @member {boolean} + * @memberof PIXI.Container# */ interactiveChildren: true, @@ -33,7 +43,12 @@ * Interaction shape. Children will be hit first, then this shape will be checked. * Setting this will cause this shape to be checked in hit tests rather than the displayObject's bounds. * - * @inner {PIXI.Rectangle|PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.RoundedRectangle} + * @example + * const sprite = new PIXI.Sprite(texture); + * sprite.interactive = true; + * sprite.hitArea = new PIXI.Rectangle(0, 0, 100, 100); + * @member {PIXI.Rectangle|PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.RoundedRectangle} + * @memberof PIXI.DisplayObject# */ hitArea: null, @@ -41,8 +56,12 @@ * If enabled, the mouse cursor use the pointer behavior when hovered over the displayObject if it is interactive * Setting this changes the 'cursor' property to `'pointer'`. * + * @example + * const sprite = new PIXI.Sprite(texture); + * sprite.interactive = true; + * sprite.buttonMode = true; * @member {boolean} - * @memberof PIXI.interaction.interactiveTarget# + * @memberof PIXI.DisplayObject# */ get buttonMode() { @@ -64,9 +83,14 @@ * This defines what cursor mode is used when the mouse cursor * is hovered over the displayObject. * + * @example + * const sprite = new PIXI.Sprite(texture); + * sprite.interactive = true; + * sprite.cursor = 'wait'; * @see https://developer.mozilla.org/en/docs/Web/CSS/cursor * - * @inner {string} + * @member {string} + * @memberof PIXI.DisplayObject# */ cursor: null, @@ -74,7 +98,7 @@ * Internal set of all active pointers, by identifier * * @member {Map} - * @memberof PIXI.interaction.interactiveTarget# + * @memberof PIXI.DisplayObject# * @private */ get trackedPointers() @@ -87,7 +111,8 @@ /** * Map of all tracked pointers, by identifier. Use trackedPointers to access. * - * @private {Map} + * @private + * @type {Map} */ _trackedPointers: undefined, }; diff --git a/src/loaders/index.js b/src/loaders/index.js index 5189793..daa1fc4 100644 --- a/src/loaders/index.js +++ b/src/loaders/index.js @@ -1,9 +1,22 @@ +import Application from '../core/Application'; +import Loader from './loader'; + /** + * This namespace contains APIs which extends the {@link https://github.com/englercj/resource-loader resource-loader} module + * for loading assets, data, and other resources dynamically. + * @example + * const loader = new PIXI.loaders.Loader(); + * loader.add('bunny', 'data/bunny.png') + * .add('spaceship', 'assets/spritesheet.json'); + * loader.load((loader, resources) => { + * // resources.bunny + * // resources.spaceship + * }); * @namespace PIXI.loaders */ -export { default as Loader } from './loader'; +export { Loader }; export { default as bitmapFontParser, parse as parseBitmapFontData } from './bitmapFontParser'; -export { default as spritesheetParser } from './spritesheetParser'; +export { default as spritesheetParser, getResourcePath } from './spritesheetParser'; export { default as textureParser } from './textureParser'; /** @@ -13,3 +26,55 @@ * @memberof PIXI.loaders */ export { Resource } from 'resource-loader'; + +/** + * A premade instance of the loader that can be used to load resources. + * @name shared + * @memberof PIXI.loaders + * @type {PIXI.loaders.Loader} + */ +const shared = new Loader(); + +shared.destroy = () => +{ + // protect destroying shared loader +}; + +export { shared }; + +// Mixin the loader construction +const AppPrototype = Application.prototype; + +AppPrototype._loader = null; + +/** + * Loader instance to help with asset loading. + * @name PIXI.Application#loader + * @type {PIXI.loaders.Loader} + */ +Object.defineProperty(AppPrototype, 'loader', { + get() + { + if (!this._loader) + { + const sharedLoader = this._options.sharedLoader; + + this._loader = sharedLoader ? shared : new Loader(); + } + + return this._loader; + }, +}); + +// Override the destroy function +// making sure to destroy the current Loader +AppPrototype._parentDestroy = AppPrototype.destroy; +AppPrototype.destroy = function destroy() +{ + if (this._loader) + { + this._loader.destroy(); + this._loader = null; + } + this._parentDestroy(); +}; diff --git a/src/loaders/loader.js b/src/loaders/loader.js index 767a411..59b8000 100644 --- a/src/loaders/loader.js +++ b/src/loaders/loader.js @@ -90,6 +90,15 @@ { Loader._pixiMiddleware.push(fn); } + + /** + * Destroy the loader, removes references. + */ + destroy() + { + this.removeAllListeners(); + this.reset(); + } } // Copy EE3 prototype (mixin) diff --git a/src/loaders/spritesheetParser.js b/src/loaders/spritesheetParser.js index e57dfb2..eda9584 100644 --- a/src/loaders/spritesheetParser.js +++ b/src/loaders/spritesheetParser.js @@ -1,12 +1,11 @@ import { Resource } from 'resource-loader'; -import path from 'path'; +import url from 'url'; import { Spritesheet } from '../core'; export default function () { return function spritesheetParser(resource, next) { - let resourcePath; const imageResourceName = `${resource.name}_image`; // skip if no data, its not json, it isn't spritesheet data, or the image resource already exists @@ -28,15 +27,7 @@ parentResource: resource, }; - // Prepend url path unless the resource image is a data url - if (resource.isDataUrl) - { - resourcePath = resource.data.meta.image; - } - else - { - resourcePath = `${path.dirname(resource.url.replace(this.baseUrl, ''))}/${resource.data.meta.image}`; - } + const resourcePath = getResourcePath(resource, this.baseUrl); // load the image for this sheet this.add(imageResourceName, resourcePath, loadOptions, function onImageLoad(res) @@ -56,3 +47,14 @@ }); }; } + +export function getResourcePath(resource, baseUrl) +{ + // Prepend url path unless the resource image is a data url + if (resource.isDataUrl) + { + return resource.data.meta.image; + } + + return url.resolve(resource.url.replace(baseUrl, ''), resource.data.meta.image); +} diff --git a/src/mesh/canvas/CanvasMeshRenderer.js b/src/mesh/canvas/CanvasMeshRenderer.js index 297205f..f7899e1 100644 --- a/src/mesh/canvas/CanvasMeshRenderer.js +++ b/src/mesh/canvas/CanvasMeshRenderer.js @@ -134,12 +134,33 @@ const textureWidth = base.width; const textureHeight = base.height; - const u0 = uvs[index0] * base.width; - const u1 = uvs[index1] * base.width; - const u2 = uvs[index2] * base.width; - const v0 = uvs[index0 + 1] * base.height; - const v1 = uvs[index1 + 1] * base.height; - const v2 = uvs[index2 + 1] * base.height; + let u0; + let u1; + let u2; + let v0; + let v1; + let v2; + + if (mesh.uploadUvTransform) + { + const ut = mesh._uvTransform.mapCoord; + + u0 = ((uvs[index0] * ut.a) + (uvs[index0 + 1] * ut.c) + ut.tx) * base.width; + u1 = ((uvs[index1] * ut.a) + (uvs[index1 + 1] * ut.c) + ut.tx) * base.width; + u2 = ((uvs[index2] * ut.a) + (uvs[index2 + 1] * ut.c) + ut.tx) * base.width; + v0 = ((uvs[index0] * ut.b) + (uvs[index0 + 1] * ut.d) + ut.ty) * base.height; + v1 = ((uvs[index1] * ut.b) + (uvs[index1 + 1] * ut.d) + ut.ty) * base.height; + v2 = ((uvs[index2] * ut.b) + (uvs[index2 + 1] * ut.d) + ut.ty) * base.height; + } + else + { + u0 = uvs[index0] * base.width; + u1 = uvs[index1] * base.width; + u2 = uvs[index2] * base.width; + v0 = uvs[index0 + 1] * base.height; + v1 = uvs[index1 + 1] * base.height; + v2 = uvs[index2 + 1] * base.height; + } let x0 = vertices[index0]; let x1 = vertices[index1]; diff --git a/src/mesh/webgl/MeshRenderer.js b/src/mesh/webgl/MeshRenderer.js index 78d24c3..ab99fcd 100644 --- a/src/mesh/webgl/MeshRenderer.js +++ b/src/mesh/webgl/MeshRenderer.js @@ -2,6 +2,8 @@ const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; +const matrixIdentity = core.Matrix.IDENTITY; + /** * WebGL renderer plugin for tiling sprites * @@ -64,6 +66,7 @@ mesh.geometry.glVertexArrayObjects[this.CONTEXT_UID].draw(mesh.drawMode, mesh.size, mesh.start, mesh.geometry.instanceCount); } + /** * draws mesh * @param {PIXI.mesh.RawMesh} mesh mesh instance diff --git a/src/mesh/webgl/mesh.vert b/src/mesh/webgl/mesh.vert index a337aef..acc096c 100644 --- a/src/mesh/webgl/mesh.vert +++ b/src/mesh/webgl/mesh.vert @@ -1,8 +1,9 @@ attribute vec2 aVertexPosition; attribute vec2 aTextureCoord; -uniform mat3 translationMatrix; uniform mat3 projectionMatrix; +uniform mat3 translationMatrix; +uniform mat3 uTransform; varying vec2 vTextureCoord; @@ -10,5 +11,5 @@ { gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - vTextureCoord = aTextureCoord; + vTextureCoord = (uTransform * vec3(aTextureCoord, 1.0)).xy; } diff --git a/src/particles/ParticleContainer.js b/src/particles/ParticleContainer.js index 67796cf..63f7791 100644 --- a/src/particles/ParticleContainer.js +++ b/src/particles/ParticleContainer.js @@ -313,10 +313,10 @@ frame.y * resolution, frame.width * resolution, frame.height * resolution, - positionX * resolution, - positionY * resolution, - finalWidth * resolution, - finalHeight * resolution + positionX * renderer.resolution, + positionY * renderer.resolution, + finalWidth * renderer.resolution, + finalHeight * renderer.resolution ); } } diff --git a/.travis.yml b/.travis.yml index 3df8b22..14de334 100644 --- a/.travis.yml +++ b/.travis.yml @@ -110,3 +110,43 @@ on: all_branches: true condition: $TRAVIS_TAG + # Deploy config for latest release + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: dist + upload-dir: "release" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: docs + upload-dir: "release/docs" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: coverage + upload-dir: "release/coverage" + on: + all_branches: true + condition: $TRAVIS_TAG \ No newline at end of file diff --git a/README.md b/README.md index d26fadd..3b85f85 100644 --- a/README.md +++ b/README.md @@ -36,22 +36,14 @@ - Wiki: Other misc tutorials and resources are [on the Wiki](https://github.com/pixijs/pixi.js/wiki/Resources). ### Community ### -- Forums: Check out the [forum] (http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow] (http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. +- Forums: Check out the [forum](http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow](http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. - Inspiration: Check out the [gallery](http://www.pixijs.com/gallery) to see some of the amazing things people have created! - Chat: You can join us on [Gitter](https://gitter.im/pixijs/pixi.js) To chat about Pixi. We also now have a Slack channel. If you would like to join it please Send me an email (mat@goodboydigital.com) and I will invite you in. ### Setup ### -It's easy to get started with Pixi.js! Simply grab the pre-built versions from here: - -Release Branch - Nice and stable Pixi.js -- Unminified: [http://pixijs.download/release/pixi.js] -- Minified: [http://pixijs.download/release/pixi.min.js] - -Develop Branch - The bleeding edge version of Pixi.js -- Unminified: [http://pixijs.download/dev/pixi.js] -- Minified: [http://pixijs.download/dev/pixi.min.js] +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. @@ -70,10 +62,10 @@ #### CDN Install (via cdnjs) ```html - + ``` -_Note: `4.2.2` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ +_Note: `4.4.3` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ ### Demos ### @@ -130,7 +122,7 @@ // and the root stage PIXI.Container. var app = new PIXI.Application(); -// The application will create a canvas element for you that you +// The application will create a canvas element for you that you // can then insert into the DOM. document.body.appendChild(app.view); @@ -150,7 +142,7 @@ // Add the bunny to the scene we are building. app.stage.addChild(bunny); - + // Listen for frame updates app.ticker.add(function() { // each frame we spin the bunny around a bit diff --git a/bower.json b/bower.json deleted file mode 100644 index 41b668c..0000000 --- a/bower.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "pixi.js", - "main": "dist/pixi.min.js", - "ignore": [ - "**/.*", - "docs", - "node_modules", - "bower_components", - "test", - "scripts", - "bower.json", - "inch.json" - ], - "dependencies": { - }, - "devDependencies": { - } -} diff --git a/package.json b/package.json index d35cbb2..308469f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.3.5", + "version": "4.4.4", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ @@ -40,11 +40,11 @@ "lib": "babel src --out-dir lib -s", "predocs": "rimraf docs/**", "docs": "jsdoc -c scripts/jsdoc.conf.json -R README.md", - "publish:patch": "npm version patch --no-git-tag-version && npm publish", - "publish:minor": "npm version minor --no-git-tag-version && npm publish", - "publish:major": "npm version major --no-git-tag-version && npm publish", + "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", - "postpublish": "node scripts/release.js" + "postpublish": "git push && git push --tags" }, "files": [ "dist/", @@ -72,7 +72,6 @@ "electron": "^1.4.15", "eslint": "^3.5.0", "floss": "^2.0.1", - "gh-pages": "^0.11.0", "jaguarjs-jsdoc": "^1.0.1", "js-md5": "^0.4.1", "jsdoc": "^3.4.2", diff --git a/scripts/jsdoc.conf.json b/scripts/jsdoc.conf.json index 553e61c..3f52180 100644 --- a/scripts/jsdoc.conf.json +++ b/scripts/jsdoc.conf.json @@ -43,7 +43,7 @@ }, "markdown" : { "parser" : "gfm", - "hardwrap" : true + "hardwrap" : false }, "opts": { "encoding" : "utf8", diff --git a/scripts/release.js b/scripts/release.js deleted file mode 100644 index 156620c..0000000 --- a/scripts/release.js +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env node -'use strict'; - -// Publish script to push releases of the bin files -// the normally are gitignored -const ghpages = require('gh-pages'); -const path = require('path'); -const packageInfo = require(path.join(__dirname, '..', 'package.json')); - -const options = { - src: [ - 'dist/**/*', - 'lib/**/*', - 'src/**/*', - 'scripts/*', - 'scripts/renders/*', - 'test/**/*', - '*.json', - '*.md', - 'LICENSE', - '.eslintrc', - '.editorconfig', - '.travis.yml', - '.babelrc', - ], - dotfiles: true, - branch: 'release', - message: packageInfo.version, - logger: console.log.bind(console), -}; - -ghpages.publish(process.cwd(), options, (err) => -{ - if (err) - { - console.log(err); - process.exit(1); - - return; - } - - process.exit(0); -}); diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index f16bcb1..220427a 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -21,7 +21,7 @@ const DIV_HOOK_ZINDEX = 2; /** - * The Accessibility manager reacreates the ability to tab and and have content read by screen + * The Accessibility manager recreates the ability to tab and and have content read by screen * readers. This is very important as it can possibly help people with disabilities access pixi * content. * diff --git a/src/accessibility/index.js b/src/accessibility/index.js index 568cfb1..0ae108f 100644 --- a/src/accessibility/index.js +++ b/src/accessibility/index.js @@ -1,4 +1,9 @@ /** + * This namespace contains a renderer plugin for interaction accessibility for end-users + * with physical impairments which require screen-renders, keyboard navigation, etc. + * + * Do not instantiate this plugin directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. * @namespace PIXI.accessibility */ export { default as accessibleTarget } from './accessibleTarget'; diff --git a/src/core/Application.js b/src/core/Application.js index 5340578..2fcc729 100644 --- a/src/core/Application.js +++ b/src/core/Application.js @@ -1,6 +1,8 @@ import { autoDetectRenderer } from './autoDetectRenderer'; import Container from './display/Container'; import { shared, Ticker } from './ticker'; +import settings from './settings'; +import { UPDATE_PRIORITY } from './const'; /** * Convenience class to create a new PIXI application. @@ -22,28 +24,52 @@ */ export default class Application { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experience unexplained flickering try setting this to true. - * @param {boolean} [useSharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedLoader=false] - `true` to use PIXI.loaders.shared, `false` to create new Loader. */ - constructor(width, height, options, noWebGL, useSharedTicker = false) + constructor(options, arg2, arg3, arg4, arg5) { + // Support for constructor(width, height, options, noWebGL, useSharedTicker) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + forceCanvas: !!arg4, + sharedTicker: !!arg5, + }, arg3); + } + + /** + * The default options, so we mixin functionality later. + * @member {object} + * @protected + */ + this._options = options = Object.assign({ + sharedTicker: false, + forceCanvas: false, + sharedLoader: false, + }, options); + /** * WebGL renderer if available, otherwise CanvasRenderer * @member {PIXI.WebGLRenderer|PIXI.CanvasRenderer} */ - this.renderer = autoDetectRenderer(width, height, options, noWebGL); + this.renderer = autoDetectRenderer(options); /** * The root display container that's rendered. @@ -63,7 +89,7 @@ * @member {PIXI.ticker.Ticker} * @default PIXI.ticker.shared */ - this.ticker = useSharedTicker ? shared : new Ticker(); + this.ticker = options.sharedTicker ? shared : new Ticker(); // Start the rendering this.start(); @@ -78,7 +104,7 @@ this._ticker = ticker; if (ticker) { - ticker.add(this.render, this); + ticker.add(this.render, this, UPDATE_PRIORITY.LOW); } } get ticker() // eslint-disable-line require-jsdoc @@ -136,13 +162,18 @@ */ destroy(removeView) { - this.stop(); + const oldTicker = this._ticker; + this.ticker = null; + oldTicker.destroy(); + this.stage.destroy(); this.stage = null; this.renderer.destroy(removeView); this.renderer = null; + + this._options = null; } } diff --git a/src/core/autoDetectRenderer.js b/src/core/autoDetectRenderer.js index 8960425..9ec0364 100644 --- a/src/core/autoDetectRenderer.js +++ b/src/core/autoDetectRenderer.js @@ -2,6 +2,7 @@ import CanvasRenderer from './renderers/canvas/CanvasRenderer'; import WebGLRenderer from './renderers/webgl/WebGLRenderer'; +// eslint-disable-next-line valid-jsdoc /** * This helper function will automatically detect which renderer you should be using. * WebGL is the preferred renderer as it is a lot faster. If webGL is not supported by @@ -9,24 +10,32 @@ * * @memberof PIXI * @function autoDetectRenderer - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @return {PIXI.WebGLRenderer|PIXI.CanvasRenderer} Returns WebGL renderer if available, otherwise CanvasRenderer */ -export function autoDetectRenderer(width = 800, height = 600, options, noWebGL) +export function autoDetectRenderer(options, arg1, arg2, arg3) { - if (!noWebGL && utils.isWebGLSupported()) + // Backward-compatible support for noWebGL option + let forceCanvas = options && options.forceCanvas; + + if (arg3 !== undefined) { - return new WebGLRenderer(width, height, options); + forceCanvas = arg3; } - return new CanvasRenderer(width, height, options); + if (!forceCanvas && utils.isWebGLSupported()) + { + return new WebGLRenderer(options, arg1, arg2); + } + + return new CanvasRenderer(options, arg1, arg2); } diff --git a/src/core/const.js b/src/core/const.js index b9e99ae..2c68dee 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -341,3 +341,27 @@ LINEAR_VERTICAL: 0, LINEAR_HORIZONTAL: 1, }; + +/** + * Represents the update priorities used by internal PIXI classes when registered with + * the {@link PIXI.ticker.Ticker} object. Higher priority items are updated first and lower + * priority items, such as render, should go later. + * + * @static + * @constant + * @name UPDATE_PRIORITY + * @memberof PIXI + * @type {object} + * @property {number} INTERACTION=50 Highest priority, used for {@link PIXI.interaction.InteractionManager} + * @property {number} HIGH=25 High priority updating, {@link PIXI.VideoBaseTexture} and {@link PIXI.extras.AnimatedSprite} + * @property {number} NORMAL=0 Default priority for ticker events, see {@link PIXI.ticker.Ticker#add}. + * @property {number} LOW=-25 Low priority used for {@link PIXI.Application} rendering. + * @property {number} UTILITY=-50 Lowest priority used for {@link PIXI.prepare.BasePrepare} utility. + */ +export const UPDATE_PRIORITY = { + INTERACTION: 50, + HIGH: 25, + NORMAL: 0, + LOW: -25, + UTILITY: -50, +}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 4e6c77d..decb42a 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -13,7 +13,6 @@ * * @class * @extends EventEmitter - * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ export default class DisplayObject extends EventEmitter @@ -113,6 +112,29 @@ * @private */ this._mask = null; + + /** + * If the object has been destroyed via destroy(). If true, it should not be used. + * + * @member {boolean} + * @private + * @readonly + */ + this._destroyed = false; + + /** + * Fired when this DisplayObject is added to a Container. + * + * @event PIXI.DisplayObject#added + * @param {PIXI.Container} container - The container added to. + */ + + /** + * Fired when this DisplayObject is removed from a Container. + * + * @event PIXI.DisplayObject#removed + * @param {PIXI.Container} container - The container removed from. + */ } /** @@ -403,6 +425,8 @@ this.interactive = false; this.interactiveChildren = false; + + this._destroyed = true; } /** diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index aafdfe2..f787c4a 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -792,8 +792,8 @@ sprite.worldAlpha = this.worldAlpha * sprite.alpha; sprite.blendMode = this.blendMode; - sprite.texture._frame.width = rect.width; - sprite.texture._frame.height = rect.height; + sprite._texture._frame.width = rect.width; + sprite._texture._frame.height = rect.height; sprite.transform.worldTransform = this.transform.worldTransform; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index a24c775..b9f192d 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -19,11 +19,12 @@ */ export default class SystemRenderer extends EventEmitter { + // eslint-disable-next-line valid-jsdoc /** * @param {string} system - The name of the system this renderer is for. - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -37,27 +38,31 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(system, screenWidth, screenHeight, options) + constructor(system, options, arg2, arg3) { super(); sayHello(system); - // prepare options - if (options) + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') { - for (const i in settings.RENDER_OPTIONS) - { - if (typeof options[i] === 'undefined') - { - options[i] = settings.RENDER_OPTIONS[i]; - } - } + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); } - else - { - options = settings.RENDER_OPTIONS; - } + + // Add the default render options + options = Object.assign({}, settings.RENDER_OPTIONS, options); + + /** + * The supplied constructor options. + * + * @member {Object} + * @readOnly + */ + this.options = options; /** * The type of the renderer. @@ -75,7 +80,7 @@ * * @member {PIXI.Rectangle} */ - this.screen = new Rectangle(0, 0, screenWidth || 800, screenHeight || 600); + this.screen = new Rectangle(0, 0, options.width, options.height); /** * The canvas element that everything is drawn to @@ -279,6 +284,8 @@ this.blendModes = null; + this.options = null; + this.preserveDrawingBuffer = false; this.clearBeforeRender = false; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index e84e49c..691caf0 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -17,10 +17,11 @@ */ export default class CanvasRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -34,9 +35,9 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('Canvas', screenWidth, screenHeight, options); + super('Canvas', options, arg2, arg3); this.type = RENDERER_TYPE.CANVAS; @@ -96,7 +97,19 @@ this.context = null; this.renderingToScreen = false; - this.resize(screenWidth, screenHeight); + this.resize(this.options.width, this.options.height); + + /** + * Fired after rendering finishes. + * + * @event PIXI.CanvasRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.CanvasRenderer#prerender + */ } /** @@ -299,4 +312,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.CanvasRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.CanvasExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.CanvasPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.CanvasRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index e1cf7cd..f6fe123 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -31,11 +31,12 @@ */ export default class WebGLRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** * - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -55,9 +56,9 @@ * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('WebGL', screenWidth, screenHeight, options); + super('WebGL', options, arg2, arg3); /** * The type of this renderer as a standardised const @@ -89,12 +90,6 @@ }; - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ this._backgroundColorRgba[3] = this.transparent ? 0 : 1; this.globalUniforms = new UniformGroup({ @@ -117,6 +112,13 @@ this.initPlugins(); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ if(options.context) { this.context.initFromContext(options.context); @@ -169,6 +171,25 @@ } return this; + + /** + * Fired after rendering finishes. + * + * @event PIXI.WebGLRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.WebGLRenderer#prerender + */ + + /** + * Fired when the WebGL context is set. + * + * @event PIXI.WebGLRenderer#context + * @param {WebGLRenderingContext} gl - WebGL context. + */ } /** @@ -295,4 +316,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.WebGLRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.WebGLRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(WebGLRenderer); diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index 4eb6128..fa3d883 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -47,6 +47,14 @@ * @member {boolean} */ this.enabled = true; + + /** + * If enabled, pixi will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; } /** diff --git a/src/core/renderers/webgl/systems/ContextSystem.js b/src/core/renderers/webgl/systems/ContextSystem.js index ff958c7..8df85ea 100644 --- a/src/core/renderers/webgl/systems/ContextSystem.js +++ b/src/core/renderers/webgl/systems/ContextSystem.js @@ -75,7 +75,8 @@ */ createContext(canvas, options) { - var gl = canvas.getContext('webgl', options) || + var gl = //canvas.getContext('webgl2', options) || + canvas.getContext('webgl', options) || canvas.getContext('experimental-webgl', options); if (!gl) diff --git a/src/core/renderers/webgl/systems/FilterSystem.js b/src/core/renderers/webgl/systems/FilterSystem.js index f47335d..37e28d2 100644 --- a/src/core/renderers/webgl/systems/FilterSystem.js +++ b/src/core/renderers/webgl/systems/FilterSystem.js @@ -111,7 +111,7 @@ // TODO we should fit the rect around the transform.. } - else + else if (filters[0].autoFit) { sourceFrame.fit(filterData.stack[0].destinationFrame); } diff --git a/src/core/renderers/webgl/systems/shader/shader/mapType.js b/src/core/renderers/webgl/systems/shader/shader/mapType.js index 581dace..917538a 100644 --- a/src/core/renderers/webgl/systems/shader/shader/mapType.js +++ b/src/core/renderers/webgl/systems/shader/shader/mapType.js @@ -15,7 +15,7 @@ } } - return GL_TABLE[type]; + return GL_TABLE[type]; }; var GL_TABLE = null; diff --git a/src/core/settings.js b/src/core/settings.js index 18c5877..38ef45c 100644 --- a/src/core/settings.js +++ b/src/core/settings.js @@ -2,6 +2,15 @@ import canUploadSameBuffer from './utils/canUploadSameBuffer'; /** + * User's customizable globals for overriding the default PIXI settings, such + * as a renderer's default resolution, framerate, float percision, etc. + * @example + * // Use the native window resolution as the default resolution + * // will support high-density displays when rendering + * PIXI.settings.RESOLUTION = window.devicePixelRatio. + * + * // Disable interpolation when scaling, will make texture be pixelated + * PIXI.settings.SCALE_MODE = PIXI.SCALE_MODES.NEAREST; * @namespace PIXI.settings */ export default { @@ -101,6 +110,9 @@ * @property {boolean} clearBeforeRender=true * @property {boolean} preserveDrawingBuffer=false * @property {boolean} roundPixels=false + * @property {number} width=800 + * @property {number} height=600 + * @property {boolean} legacy=false */ RENDER_OPTIONS: { view: null, @@ -112,6 +124,9 @@ clearBeforeRender: true, preserveDrawingBuffer: false, roundPixels: false, + width: 800, + height: 600, + legacy: false, }, /** diff --git a/src/core/shader/Program.js b/src/core/shader/Program.js index c0b538b..07b4398 100644 --- a/src/core/shader/Program.js +++ b/src/core/shader/Program.js @@ -1,6 +1,6 @@ import extractUniformsFromSrc from './extractUniformsFromSrc'; import generateUniformsSync from './generateUniformsSync'; -import glCore from 'pixi-gl-core'; +import shaderUtils from '../renderers/webgl/systems/shader/shader'; import { ProgramCache } from '../utils'; import getTestContext from '../utils/getTestContext'; @@ -64,14 +64,17 @@ } else { - vertexSrc = glCore.shader.setPrecision(vertexSrc, 'mediump'); - fragmentSrc = glCore.shader.setPrecision(fragmentSrc, 'mediump'); + vertexSrc = shaderUtils.setPrecision(vertexSrc, 'mediump'); + fragmentSrc = shaderUtils.setPrecision(fragmentSrc, 'mediump'); - const program = glCore.shader.compileProgram(gl, vertexSrc, fragmentSrc); + const program = shaderUtils.compileProgram(gl, vertexSrc, fragmentSrc); + console.log("<<<<<<>>><<"); this.attributeData = this.getAttributeData(program, gl); this.uniformData = this.getUniformData(program, gl); + console.log(this.uniformData); + //gl.deleteProgram(program); } } @@ -95,13 +98,13 @@ for (let i = 0; i < totalAttributes; i++) { const attribData = gl.getActiveAttrib(program, i); - const type = glCore.shader.mapType(gl, attribData.type); + const type = shaderUtils.mapType(gl, attribData.type); /*eslint-disable */ const data = { type: type, name: attribData.name, - size: glCore.shader.mapSize(type), + size: shaderUtils.mapSize(type), location: 0, }; /*eslint-enable */ @@ -137,13 +140,14 @@ // TODO expose this as a prop? // const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); - const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); + //const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); for (let i = 0; i < totalUniforms; i++) { const uniformData = gl.getActiveUniform(program, i); const name = uniformData.name.replace(/\[.*?\]/, ''); - const type = glCore.shader.mapType(gl, uniformData.type); + const type = shaderUtils.mapType(gl, uniformData.type); + console.log('mapping ' + uniformData.type + ' to ' + type); // if (!name.match(maskRegex)) { @@ -151,7 +155,7 @@ uniforms[name] = { type: type, size: uniformData.size, - value: glCore.shader.defaultValue(type, uniformData.size), + value: shaderUtils.defaultValue(type, uniformData.size), }; /*eslint-enable */ } diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index e387e99..b57d095 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -147,12 +147,12 @@ // so if _width is 0 then width was not set.. if (this._width) { - this.scale.x = sign(this.scale.x) * this._width / this.texture.orig.width; + this.scale.x = sign(this.scale.x) * this._width / this._texture.orig.width; } if (this._height) { - this.scale.y = sign(this.scale.y) * this._height / this.texture.orig.height; + this.scale.y = sign(this.scale.y) * this._height / this._texture.orig.height; } } diff --git a/src/core/sprites/canvas/CanvasTinter.js b/src/core/sprites/canvas/CanvasTinter.js index 27e5e0c..fd9de5d 100644 --- a/src/core/sprites/canvas/CanvasTinter.js +++ b/src/core/sprites/canvas/CanvasTinter.js @@ -18,7 +18,7 @@ */ getTintedTexture: (sprite, color) => { - const texture = sprite.texture; + const texture = sprite._texture; color = CanvasTinter.roundColor(color); diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 5a4edda..8b9b797 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -344,6 +344,7 @@ const texture = this._texture; const style = this._style; + const padding = style.trim ? 0 : style.padding; texture.baseTexture.valid = true; texture.baseTexture.resolution = this.resolution; @@ -353,11 +354,11 @@ texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - texture.trim.x = -style.padding; - texture.trim.y = -style.padding; + texture.trim.x = -padding; + texture.trim.y = -padding; - texture.orig.width = texture._frame.width - (style.padding * 2); - texture.orig.height = texture._frame.height - (style.padding * 2); + texture.orig.width = texture._frame.width - (padding * 2); + texture.orig.height = texture._frame.height - (padding * 2); // call sprite onTextureUpdate to update scale if _width or _height were set this._onTextureUpdate(); diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index 853da3d..8a9c34b 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -114,6 +114,38 @@ this.cacheId = null; this.validate(); + + /** + * Fired when a not-immediately-available source finishes loading. + * + * @protected + * @event PIXI.BaseTexture#loaded + * @param {PIXI.BaseTexture} baseTexture - Resource loaded. + */ + + /** + * Fired when a not-immediately-available source fails to load. + * + * @protected + * @event PIXI.BaseTexture#error + * @param {PIXI.BaseTexture} baseTexture - Resource errored. + */ + + /** + * Fired when BaseTexture is updated. + * + * @protected + * @event PIXI.BaseTexture#update + * @param {PIXI.BaseTexture} baseTexture - Instance of texture being updated. + */ + + /** + * Fired when BaseTexture is destroyed. + * + * @protected + * @event PIXI.BaseTexture#dispose + * @param {PIXI.BaseTexture} baseTexture - Instance of texture being destroyed. + */ } setResource(resource) diff --git a/src/core/textures/Spritesheet.js b/src/core/textures/Spritesheet.js index d469c62..9b24f27 100644 --- a/src/core/textures/Spritesheet.js +++ b/src/core/textures/Spritesheet.js @@ -208,6 +208,7 @@ // lets also add the frame to pixi's global cache for fromFrame and fromImage functions TextureCache[i] = this.textures[i]; + this.textures[i].textureCacheId = i; } frameIndex++; diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 0a2c379..2213087 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -147,9 +147,9 @@ /** * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. * - * @event update - * @memberof PIXI.Texture# + * @event PIXI.Texture#update * @protected + * @param {PIXI.Texture} texture - Instance of texture being updated. */ this._updateID = 0; @@ -159,6 +159,15 @@ * @type {Object} */ this.transform = null; + + /** + * The id under which this Texture has been added to the texture cache. This is + * automatically set in certain cases, but may not always be accurate, particularly if + * the texture is in the cache under multiple ids. + * + * @member {string} + */ + this.textureCacheId = null; } /** @@ -225,11 +234,17 @@ this._uvs = null; this.trim = null; this.orig = null; + this.textureCacheId = null; this.valid = false; - this.off('dispose', this.dispose, this); - this.off('update', this.update, this); + for (const prop in TextureCache) + { + if (TextureCache[prop] === this) + { + delete TextureCache[prop]; + } + } } /** @@ -315,14 +330,15 @@ */ static fromLoader(source, imageUrl, name) { - // console.log('added from loader...') + // console.log('added from loader...') const resource = new ImageResource(source);//.from(imageUrl, crossorigin);// document.createElement('img'); - // console.log('base resource ' + resource.width); + // console.log('base resource ' + resource.width); const baseTexture = new BaseTexture(resource, settings.SCALE_MODE, getResolutionOfUrl(imageUrl)); + /// console.log('base width ' + baseTexture.width); const texture = new Texture(baseTexture); @@ -335,6 +351,7 @@ // lets also add the frame to pixi's global cache for fromFrame and fromImage fucntions BaseTextureCache[name] = baseTexture; TextureCache[name] = texture; + texture.textureCacheId = name; // also add references by url if they are different. if (name !== imageUrl) @@ -355,6 +372,10 @@ */ static addTextureToCache(texture, id) { + if (!texture.textureCacheId) + { + texture.textureCacheId = id; + } TextureCache[id] = texture; } @@ -495,6 +516,7 @@ */ Texture.EMPTY = new Texture(new BaseTexture()); removeAllHandlers(Texture.EMPTY); +removeAllHandlers(Texture.EMPTY.baseTexture); /** * A white texture of 10x10 size, used for graphics and other things @@ -505,3 +527,4 @@ */ Texture.WHITE = createWhiteTexture(); removeAllHandlers(Texture.WHITE); +removeAllHandlers(Texture.WHITE.baseTexture); diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index 59eb9fd..a9b9f51 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -1,6 +1,7 @@ import BaseTexture from './BaseTexture'; import { uid, BaseTextureCache } from '../utils'; -import * as ticker from '../ticker'; +import { shared } from '../ticker'; +import { UPDATE_PRIORITY } from '../const'; /** * A texture of a [playing] Video. @@ -125,7 +126,7 @@ if (!this._isAutoUpdating && this.autoUpdate) { - ticker.shared.add(this.update, this); + shared.add(this.update, this, UPDATE_PRIORITY.HIGH); this._isAutoUpdating = true; } } @@ -139,7 +140,7 @@ { if (this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); this._isAutoUpdating = false; } } @@ -187,7 +188,7 @@ { if (this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); } if (this.source && this.source._pixiId) @@ -281,12 +282,12 @@ if (!this._autoUpdate && this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); this._isAutoUpdating = false; } else if (this._autoUpdate && !this._isAutoUpdating) { - ticker.shared.add(this.update, this); + shared.add(this.update, this, UPDATE_PRIORITY.HIGH); this._isAutoUpdating = true; } } diff --git a/src/core/ticker/Ticker.js b/src/core/ticker/Ticker.js index 17a1517..40df138 100644 --- a/src/core/ticker/Ticker.js +++ b/src/core/ticker/Ticker.js @@ -1,12 +1,10 @@ import settings from '../settings'; -import EventEmitter from 'eventemitter3'; - -// Internal event used by composed emitter -const TICK = 'tick'; +import { UPDATE_PRIORITY } from '../const'; +import TickerListener from './TickerListener'; /** * A Ticker class that runs an update loop that other objects listen to. - * This class is composed around an EventEmitter object to add listeners + * This class is composed around listeners * meant for execution on the next requested animation frame. * Animation frames are requested only when necessary, * e.g. When the ticker is started and the emitter has listeners. @@ -22,10 +20,11 @@ constructor() { /** - * Internal emitter used to fire 'tick' event + * The first listener. All new listeners added are chained on this. * @private + * @type {TickerListener} */ - this._emitter = new EventEmitter(); + this._head = new TickerListener(null, null, Infinity); /** * Internal current frame request ID @@ -131,7 +130,7 @@ // Invoke listeners now this.update(time); // Listener side effects may have modified ticker state. - if (this.started && this._requestId === null && this._emitter.listeners(TICK, true)) + if (this.started && this._requestId === null && this._head.next) { this._requestId = requestAnimationFrame(this._tick); } @@ -148,7 +147,7 @@ */ _requestIfNeeded() { - if (this._requestId === null && this._emitter.listeners(TICK, true)) + if (this._requestId === null && this._head.next) { // ensure callbacks get correct delta this.lastTime = performance.now(); @@ -193,35 +192,72 @@ } /** - * Calls {@link module:eventemitter3.EventEmitter#on} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. + * Register a handler for tick events. Calls continuously unless + * it is removed or the ticker is stopped. * * @param {Function} fn - The listener function to be added for updates * @param {Function} [context] - The listener context + * @param {number} [priority=PIXI.UPDATE_PRIORITY.NORMAL] - The priority for emitting * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - add(fn, context) + add(fn, context, priority = UPDATE_PRIORITY.NORMAL) { - this._emitter.on(TICK, fn, context); - - this._startIfPossible(); - - return this; + return this._addListener(new TickerListener(fn, context, priority)); } /** - * Calls {@link module:eventemitter3.EventEmitter#once} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. + * Add a handler for the tick event which is only execute once. * * @param {Function} fn - The listener function to be added for one update * @param {Function} [context] - The listener context + * @param {number} [priority=PIXI.UPDATE_PRIORITY.NORMAL] - The priority for emitting * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - addOnce(fn, context) + addOnce(fn, context, priority = UPDATE_PRIORITY.NORMAL) { - this._emitter.once(TICK, fn, context); + return this._addListener(new TickerListener(fn, context, priority, true)); + } + + /** + * Internally adds the event handler so that it can be sorted by priority. + * Priority allows certain handler (user, AnimatedSprite, Interaction) to be run + * before the rendering. + * + * @private + * @param {TickerListener} listener - Current listener being added. + * @returns {PIXI.ticker.Ticker} This instance of a ticker + */ + _addListener(listener) + { + // For attaching to head + let current = this._head.next; + let previous = this._head; + + // Add the first item + if (!current) + { + listener.connect(previous); + } + else + { + // Go from highest to lowest priority + while (current) + { + if (listener.priority >= current.priority) + { + listener.connect(previous); + break; + } + previous = current; + current = current.next; + } + + // Not yet connected + if (!listener.previous) + { + listener.connect(previous); + } + } this._startIfPossible(); @@ -229,19 +265,33 @@ } /** - * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. - * It checks if the emitter has listeners for 'tick' event. - * If it does, then it cancels the animation frame. + * Removes any handlers matching the function and context parameters. + * If no handlers are left after removing, then it cancels the animation frame. * - * @param {Function} [fn] - The listener function to be removed + * @param {Function} fn - The listener function to be removed * @param {Function} [context] - The listener context to be removed * @returns {PIXI.ticker.Ticker} This instance of a ticker */ remove(fn, context) { - this._emitter.off(TICK, fn, context); + let listener = this._head.next; - if (!this._emitter.listeners(TICK, true)) + while (listener) + { + // We found a match, lets remove it + // no break to delete all possible matches + // incase a listener was added 2+ times + if (listener.match(fn, context)) + { + listener = listener.destroy(); + } + else + { + listener = listener.next; + } + } + + if (!this._head.next) { this._cancelIfNeeded(); } @@ -276,6 +326,25 @@ } /** + * Destroy the ticker and don't use after this. Calling + * this method removes all references to internal events. + */ + destroy() + { + this.stop(); + + let listener = this._head.next; + + while (listener) + { + listener = listener.destroy(true); + } + + this._head.destroy(); + this._head = null; + } + + /** * Triggers an update. An update entails setting the * current {@link PIXI.ticker.Ticker#elapsedMS}, * the current {@link PIXI.ticker.Ticker#deltaTime}, @@ -320,8 +389,22 @@ this.deltaTime = elapsedMS * settings.TARGET_FPMS * this.speed; + // Cache a local reference, in-case ticker is destroyed + // during the emit, we can still check for head.next + const head = this._head; + // Invoke listeners added to internal emitter - this._emitter.emit(TICK, this.deltaTime); + let listener = head.next; + + while (listener) + { + listener = listener.emit(this.deltaTime); + } + + if (!head.next) + { + this._cancelIfNeeded(); + } } else { diff --git a/src/core/ticker/TickerListener.js b/src/core/ticker/TickerListener.js new file mode 100644 index 0000000..2bedb34 --- /dev/null +++ b/src/core/ticker/TickerListener.js @@ -0,0 +1,158 @@ +/** + * Internal class for handling the priority sorting of ticker handlers. + * + * @private + * @class + * @memberof PIXI.ticker + */ +export default class TickerListener +{ + /** + * Constructor + * + * @param {Function} fn - The listener function to be added for one update + * @param {Function} [context=null] - The listener context + * @param {number} [priority=0] - The priority for emitting + * @param {boolean} [once=false] - If the handler should fire once + */ + constructor(fn, context = null, priority = 0, once = false) + { + /** + * The handler function to execute. + * @member {Function} + */ + this.fn = fn; + + /** + * The calling to execute. + * @member {Function} + */ + this.context = context; + + /** + * The current priority. + * @member {number} + */ + this.priority = priority; + + /** + * If this should only execute once. + * @member {boolean} + */ + this.once = once; + + /** + * The next item in chain. + * @member {TickerListener} + */ + this.next = null; + + /** + * The previous item in chain. + * @member {TickerListener} + */ + this.previous = null; + + /** + * `true` if this listener has been destroyed already. + * @member {boolean} + * @private + */ + this._destroyed = false; + } + + /** + * Simple compare function to figure out if a function and context match. + * + * @param {Function} fn - The listener function to be added for one update + * @param {Function} context - The listener context + * @return {boolean} `true` if the listener match the arguments + */ + match(fn, context) + { + context = context || null; + + return this.fn === fn && this.context === context; + } + + /** + * Emit by calling the current function. + * @param {number} deltaTime - time since the last emit. + * @return {TickerListener} Next ticker + */ + emit(deltaTime) + { + if (this.context) + { + this.fn.call(this.context, deltaTime); + } + else + { + this.fn(deltaTime); + } + + if (this.once) + { + this.destroy(); + } + + const redirect = this.next; + + // Soft-destroying should remove + // the next reference + if (this._destroyed) + { + this.next = null; + } + + return redirect; + } + + /** + * Connect to the list. + * @param {TickerListener} previous - Input node, previous listener + */ + connect(previous) + { + this.previous = previous; + if (previous.next) + { + previous.next.previous = this; + } + this.next = previous.next; + previous.next = this; + } + + /** + * Destroy and don't use after this. + * @param {boolean} [hard = false] `true` to remove the `next` reference, this + * is considered a hard destroy. Soft destroy maintains the next reference. + * @return {TickerListener} The listener to redirect while emitting or removing. + */ + destroy(hard = false) + { + this._destroyed = true; + this.fn = null; + this.context = null; + + // Disconnect, hook up next and previous + if (this.previous) + { + this.previous.next = this.next; + } + + if (this.next) + { + this.next.previous = this.previous; + } + + // Redirect to the next item + const redirect = this.previous; + + // Remove references + this.next = hard ? null : redirect; + this.previous = null; + + return redirect; + } +} diff --git a/src/core/ticker/index.js b/src/core/ticker/index.js index 4b3017c..c3020d4 100644 --- a/src/core/ticker/index.js +++ b/src/core/ticker/index.js @@ -45,8 +45,25 @@ const shared = new Ticker(); shared.autoStart = true; +shared.destroy = () => +{ + // protect destroying shared ticker + // this is used by other internal systems + // like AnimatedSprite and InteractionManager +}; /** + * This namespace contains an API for interacting with PIXI's internal global update loop. + * + * This ticker is used for rendering, {@link PIXI.extras.AnimatedSprite AnimatedSprite}, + * {@link PIXI.interaction.InteractionManager InteractionManager} and many other time-based PIXI systems. + * @example + * const ticker = new PIXI.ticker.Ticker(); + * ticker.stop(); + * ticker.add((deltaTime) => { + * // do something every frame + * }); + * ticker.start(); * @namespace PIXI.ticker */ export { shared, Ticker }; diff --git a/src/core/utils/index.js b/src/core/utils/index.js index be44750..7441f7b 100644 --- a/src/core/utils/index.js +++ b/src/core/utils/index.js @@ -9,6 +9,21 @@ let saidHello = false; /** + * Generalized convenience utilities for PIXI. + * @example + * // Extend PIXI's internal Event Emitter. + * class MyEmitter extends PIXI.utils.EventEmitter { + * constructor() { + * super(); + * console.log("Emitter created!"); + * } + * } + * + * // Get info on current device + * console.log(PIXI.utils.isMobile); + * + * // Convert hex color to string + * console.log(PIXI.utils.hex2string(0xff00ff)); // returns: "#ff00ff" * @namespace PIXI.utils */ export { diff --git a/src/deprecation.js b/src/deprecation.js index b9c73b3..33daeec 100644 --- a/src/deprecation.js +++ b/src/deprecation.js @@ -918,6 +918,35 @@ }); /** + * @method + * @name PIXI.prepare.BasePrepare#register + * @see PIXI.prepare.BasePrepare#registerFindHook + * @deprecated since version 4.4.2 + * @param {Function} [addHook] - Function call that takes two parameters: `item:*, queue:Array` + * function must return `true` if it was able to add item to the queue. + * @param {Function} [uploadHook] - Function call that takes two parameters: `prepare:CanvasPrepare, item:*` and + * function must return `true` if it was able to handle upload of item. + * @return {PIXI.BasePrepare} Instance of plugin for chaining. + */ +prepare.BasePrepare.prototype.register = function register(addHook, uploadHook) +{ + warn('renderer.plugins.prepare.register is now deprecated, ' + + 'please use renderer.plugins.prepare.registerFindHook & renderer.plugins.prepare.registerUploadHook'); + + if (addHook) + { + this.registerFindHook(addHook); + } + + if (uploadHook) + { + this.registerUploadHook(uploadHook); + } + + return this; +}; + +/** * The number of graphics or textures to upload to the GPU. * * @name PIXI.prepare.canvas.UPLOADS_PER_FRAME @@ -969,67 +998,73 @@ }, }); -Object.defineProperties(loaders.Resource.prototype, { - isJson: { - get() - { - warn('The isJson property is deprecated, please use `resource.type === Resource.TYPE.JSON`.'); +if (loaders.Loader) +{ + const Resource = loaders.Resource; + const Loader = loaders.Loader; - return this.type === loaders.Loader.Resource.TYPE.JSON; + Object.defineProperties(Resource.prototype, { + isJson: { + get() + { + warn('The isJson property is deprecated, please use `resource.type === Resource.TYPE.JSON`.'); + + return this.type === Resource.TYPE.JSON; + }, }, - }, - isXml: { - get() - { - warn('The isXml property is deprecated, please use `resource.type === Resource.TYPE.XML`.'); + isXml: { + get() + { + warn('The isXml property is deprecated, please use `resource.type === Resource.TYPE.XML`.'); - return this.type === loaders.Loader.Resource.TYPE.XML; + return this.type === Resource.TYPE.XML; + }, }, - }, - isImage: { - get() - { - warn('The isImage property is deprecated, please use `resource.type === Resource.TYPE.IMAGE`.'); + isImage: { + get() + { + warn('The isImage property is deprecated, please use `resource.type === Resource.TYPE.IMAGE`.'); - return this.type === loaders.Loader.Resource.TYPE.IMAGE; + return this.type === Resource.TYPE.IMAGE; + }, }, - }, - isAudio: { - get() - { - warn('The isAudio property is deprecated, please use `resource.type === Resource.TYPE.AUDIO`.'); + isAudio: { + get() + { + warn('The isAudio property is deprecated, please use `resource.type === Resource.TYPE.AUDIO`.'); - return this.type === loaders.Loader.Resource.TYPE.AUDIO; + return this.type === Resource.TYPE.AUDIO; + }, }, - }, - isVideo: { - get() - { - warn('The isVideo property is deprecated, please use `resource.type === Resource.TYPE.VIDEO`.'); + isVideo: { + get() + { + warn('The isVideo property is deprecated, please use `resource.type === Resource.TYPE.VIDEO`.'); - return this.type === loaders.Loader.Resource.TYPE.VIDEO; + return this.type === Resource.TYPE.VIDEO; + }, }, - }, -}); + }); -Object.defineProperties(loaders.Loader.prototype, { - before: { - get() - { - warn('The before() method is deprecated, please use pre().'); + Object.defineProperties(Loader.prototype, { + before: { + get() + { + warn('The before() method is deprecated, please use pre().'); - return this.pre; + return this.pre; + }, }, - }, - after: { - get() - { - warn('The after() method is deprecated, please use use().'); + after: { + get() + { + warn('The after() method is deprecated, please use use().'); - return this.use; + return this.use; + }, }, - }, -}); + }); +} /** * @name PIXI.interaction.interactiveTarget#defaultCursor diff --git a/src/extract/canvas/CanvasExtract.js b/src/extract/canvas/CanvasExtract.js index 941ca05..3ac5aee 100644 --- a/src/extract/canvas/CanvasExtract.js +++ b/src/extract/canvas/CanvasExtract.js @@ -8,7 +8,7 @@ * An instance of this class is automatically created by default, and can be found at renderer.plugins.extract * * @class - * @memberof PIXI + * @memberof PIXI.extract */ export default class CanvasExtract { @@ -21,9 +21,9 @@ /** * Collection of methods for extracting data (image, pixels, etc.) from a display object or render texture * - * @member {PIXI.CanvasExtract} extract + * @member {PIXI.extract.CanvasExtract} extract * @memberof PIXI.CanvasRenderer# - * @see PIXI.CanvasExtract + * @see PIXI.extract.CanvasExtract */ renderer.extract = this; } diff --git a/src/extract/index.js b/src/extract/index.js index 0f2170d..e9478c1 100644 --- a/src/extract/index.js +++ b/src/extract/index.js @@ -1,2 +1,22 @@ +/** + * This namespace provides renderer-specific plugins for exporting content from a renderer. + * For instance, these plugins can be used for saving an Image, Canvas element or for exporting the raw image data (pixels). + * + * Do not instantiate these plugins directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. + * @example + * // Create a new app (will auto-add extract plugin to renderer) + * const app = new PIXI.Application(); + * + * // Draw a red circle + * const graphics = new PIXI.Graphics() + * .beginFill(0xFF0000) + * .drawCircle(0, 0, 50); + * + * // Render the graphics as an HTMLImageElement + * const image = app.renderer.plugins.extract.image(graphics); + * document.body.appendChild(image); + * @namespace PIXI.extract + */ export { default as webgl } from './webgl/WebGLExtract'; export { default as canvas } from './canvas/CanvasExtract'; diff --git a/src/extract/webgl/WebGLExtract.js b/src/extract/webgl/WebGLExtract.js index 60228bb..1c3dd30 100644 --- a/src/extract/webgl/WebGLExtract.js +++ b/src/extract/webgl/WebGLExtract.js @@ -9,7 +9,7 @@ * An instance of this class is automatically created by default, and can be found at renderer.plugins.extract * * @class - * @memberof PIXI + * @memberof PIXI.extract */ export default class WebGLExtract { @@ -22,9 +22,9 @@ /** * Collection of methods for extracting data (image, pixels, etc.) from a display object or render texture * - * @member {PIXI.WebGLExtract} extract + * @member {PIXI.extract.WebGLExtract} extract * @memberof PIXI.WebGLRenderer# - * @see PIXI.WebGLExtract + * @see PIXI.extract.WebGLExtract */ renderer.extract = this; } diff --git a/src/extras/AnimatedSprite.js b/src/extras/AnimatedSprite.js index 13b804f..5297778 100644 --- a/src/extras/AnimatedSprite.js +++ b/src/extras/AnimatedSprite.js @@ -137,7 +137,7 @@ this.playing = true; if (this._autoUpdate) { - core.ticker.shared.add(this.update, this); + core.ticker.shared.add(this.update, this, core.UPDATE_PRIORITY.HIGH); } } diff --git a/src/extras/TextureTransform.js b/src/extras/TextureTransform.js index 78e6e40..16536d2 100644 --- a/src/extras/TextureTransform.js +++ b/src/extras/TextureTransform.js @@ -65,8 +65,36 @@ } /** + * Multiplies uvs array to transform + * @param {Float32Array} uvs mesh uvs + * @param {Float32Array} [out=uvs] output + * @returns {Float32Array} output + */ + multiplyUvs(uvs, out) + { + if (out === undefined) + { + out = uvs; + } + + const mat = this.mapCoord; + + for (let i = 0; i < uvs.length; i += 2) + { + const x = uvs[i]; + const y = uvs[i + 1]; + + out[i] = (x * mat.a) + (y * mat.c) + mat.tx; + out[i + 1] = (x * mat.b) + (y * mat.d) + mat.ty; + } + + return out; + } + + /** * updates matrices if texture was changed * @param {boolean} forceUpdate if true, matrices will be updated any case + * @returns {boolean} whether or not it was updated */ update(forceUpdate) { @@ -74,13 +102,13 @@ if (!tex || !tex.valid) { - return; + return false; } if (!forceUpdate && this._lastTextureID === tex._updateID) { - return; + return false; } this._lastTextureID = tex._updateID; @@ -110,5 +138,7 @@ frame[3] = (tex._frame.y + tex._frame.height - margin + offset) / texBase.height; this.uClampOffset[0] = offset / texBase.realWidth; this.uClampOffset[1] = offset / texBase.realHeight; + + return true; } } diff --git a/src/extras/TilingSprite.js b/src/extras/TilingSprite.js index 1f6014f..a3f027f 100644 --- a/src/extras/TilingSprite.js +++ b/src/extras/TilingSprite.js @@ -180,9 +180,9 @@ const transform = this.worldTransform; const resolution = renderer.resolution; const baseTexture = texture.baseTexture; - const baseTextureResolution = texture.baseTexture.resolution; - const modX = (this.tilePosition.x / this.tileScale.x) % texture._frame.width; - const modY = (this.tilePosition.y / this.tileScale.y) % texture._frame.height; + const baseTextureResolution = baseTexture.resolution; + const modX = ((this.tilePosition.x / this.tileScale.x) % texture._frame.width) * baseTextureResolution; + const modY = ((this.tilePosition.y / this.tileScale.y) % texture._frame.height) * baseTextureResolution; // create a nice shiny pattern! // TODO this needs to be refreshed if texture changes.. diff --git a/src/extras/cacheAsBitmap.js b/src/extras/cacheAsBitmap.js index 5dd5640..aea06a8 100644 --- a/src/extras/cacheAsBitmap.js +++ b/src/extras/cacheAsBitmap.js @@ -227,7 +227,16 @@ this.transform._parentID = -1; // restore the transform of the cached sprite to avoid the nasty flicker.. - this.updateTransform(); + if (!this.parent) + { + this.parent = renderer._tempDisplayObjectParent; + this.updateTransform(); + this.parent = null; + } + else + { + this.updateTransform(); + } // map the hit test.. this.containsPoint = cachedSprite.containsPoint.bind(cachedSprite); @@ -314,7 +323,17 @@ cachedSprite._bounds = this._bounds; cachedSprite.alpha = cacheAlpha; - this.updateTransform(); + if (!this.parent) + { + this.parent = renderer._tempDisplayObjectParent; + this.updateTransform(); + this.parent = null; + } + else + { + this.updateTransform(); + } + this.updateTransform = this.displayObjectUpdateTransform; this._cacheData.sprite = cachedSprite; @@ -358,9 +377,12 @@ * Destroys the cached object. * * @private + * @param {object|boolean} [options] - Options parameter. A boolean will act as if all options + * have been set to that value. + * Used when destroying containers, see the Container.destroy method. */ -DisplayObject.prototype._cacheAsBitmapDestroy = function _cacheAsBitmapDestroy() +DisplayObject.prototype._cacheAsBitmapDestroy = function _cacheAsBitmapDestroy(options) { this.cacheAsBitmap = false; - this.destroy(); + this.destroy(options); }; diff --git a/src/extras/index.js b/src/extras/index.js index bbd8bbf..a34d7ec 100644 --- a/src/extras/index.js +++ b/src/extras/index.js @@ -1,7 +1,7 @@ /** + * Additional PIXI DisplayObjects for animation, tiling and bitmap text. * @namespace PIXI.extras */ -export { default as TextureTransform } from './TextureTransform'; export { default as AnimatedSprite } from './AnimatedSprite'; export { default as TilingSprite } from './TilingSprite'; export { default as TilingSpriteRenderer } from './webgl/TilingSpriteRenderer'; diff --git a/src/extras/webgl/TilingSpriteRenderer.js b/src/extras/webgl/TilingSpriteRenderer.js index aeec172..eee3b5c 100644 --- a/src/extras/webgl/TilingSpriteRenderer.js +++ b/src/extras/webgl/TilingSpriteRenderer.js @@ -11,7 +11,7 @@ * WebGL renderer plugin for tiling sprites * * @class - * @memberof PIXI + * @memberof PIXI.extras * @extends PIXI.ObjectRenderer */ export default class TilingSpriteRenderer extends core.ObjectRenderer @@ -118,7 +118,7 @@ tempMat.invert(); if (isSimple) { - tempMat.append(uv.mapCoord); + tempMat.prepend(uv.mapCoord); } else { diff --git a/src/filters/displacement/DisplacementFilter.js b/src/filters/displacement/DisplacementFilter.js index d5355c3..b5d030c 100644 --- a/src/filters/displacement/DisplacementFilter.js +++ b/src/filters/displacement/DisplacementFilter.js @@ -35,7 +35,7 @@ this.maskSprite = sprite; this.maskMatrix = maskMatrix; - this.uniforms.mapSampler = sprite.texture; + this.uniforms.mapSampler = sprite._texture; this.uniforms.filterMatrix = maskMatrix; this.uniforms.scale = { x: 1, y: 1 }; diff --git a/src/filters/index.js b/src/filters/index.js index f3e8862..7ceb50a 100644 --- a/src/filters/index.js +++ b/src/filters/index.js @@ -1,4 +1,21 @@ /** + * This namespace contains WebGL-only display filters that can be applied + * to DisplayObjects using the {@link PIXI.DisplayObject#filters filters} property. + * @example + * // Create a new application + * const app = new PIXI.Application(); + * + * // Draw a green rectangle + * const rect = new PIXI.Graphics() + * .beginFill(0x00ff00) + * .drawRect(40, 40, 200, 200); + * + * // Add a blur filter + * rect.filters = [new PIXI.filters.BlurFilter()]; + * + * // Display rectangle + * app.stage.addChild(rect); + * document.body.appendChild(app.view); * @namespace PIXI.filters */ export { default as FXAAFilter } from './fxaa/FXAAFilter'; diff --git a/src/index.js b/src/index.js index c553427..77da490 100644 --- a/src/index.js +++ b/src/index.js @@ -20,6 +20,14 @@ import { utils } from './core'; utils.mixins.performMixins(); +/** + * Alias for {@link PIXI.loaders.shared}. + * @name loader + * @memberof PIXI + * @type {PIXI.loader.Loader} + */ +const loader = loaders.shared || null; + export { accessibility, extract, @@ -30,18 +38,8 @@ mesh, particles, prepare, + loader, }; -/** - * A premade instance of the loader that can be used to load resources. - * - * @name loader - * @memberof PIXI - * @property {PIXI.loaders.Loader} - */ -const loader = loaders && loaders.Loader ? new loaders.Loader() : null; // check is there in case user excludes loader lib - -export { loader }; - // Always export pixi globally. global.PIXI = exports; // eslint-disable-line diff --git a/src/interaction/InteractionData.js b/src/interaction/InteractionData.js index 02d53a3..449f8cc 100644 --- a/src/interaction/InteractionData.js +++ b/src/interaction/InteractionData.js @@ -30,7 +30,10 @@ /** * When passed to an event handler, this will be the original DOM Event that was captured * - * @member {Event} + * @see https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent + * @see https://developer.mozilla.org/en-US/docs/Web/API/TouchEvent + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent + * @member {MouseEvent|TouchEvent|PointerEvent} */ this.originalEvent = null; } diff --git a/src/interaction/InteractionManager.js b/src/interaction/InteractionManager.js index 8baf13a..2d75c7c 100644 --- a/src/interaction/InteractionManager.js +++ b/src/interaction/InteractionManager.js @@ -13,6 +13,14 @@ const MOUSE_POINTER_ID = 'MOUSE'; +// helpers for hitTest() - only used inside hitTest() +const hitTestEvent = { + target: null, + data: { + global: null, + }, +}; + /** * The interaction manager deals with mouse, touch and pointer events. Any DisplayObject can be interactive * if its interactive parameter is set to true @@ -245,67 +253,60 @@ this.setTargetElement(this.renderer.view, this.renderer.resolution); /** - * Fired when a pointer device button (usually a mouse button) is pressed on the display + * Fired when a pointer device button (usually a mouse left-button) is pressed on the display * object. * - * @event mousedown - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mousedown + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device secondary button (usually a mouse right-button) is pressed * on the display object. * - * @event rightdown - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightdown + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** - * Fired when a pointer device button (usually a mouse button) is released over the display + * Fired when a pointer device button (usually a mouse left-button) is released over the display * object. * - * @event mouseup - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseup + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device secondary button (usually a mouse right-button) is released * over the display object. * - * @event rightup - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightup + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** - * Fired when a pointer device button (usually a mouse button) is pressed and released on + * Fired when a pointer device button (usually a mouse left-button) is pressed and released on * the display object. * - * @event click - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#click + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device secondary button (usually a mouse right-button) is pressed * and released on the display object. * - * @event rightclick - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightclick + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** - * Fired when a pointer device button (usually a mouse button) is released outside the + * Fired when a pointer device button (usually a mouse left-button) is released outside the * display object that initially registered a * [mousedown]{@link PIXI.interaction.InteractionManager#event:mousedown}. * - * @event mouseupoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** @@ -313,146 +314,362 @@ * outside the display object that initially registered a * [rightdown]{@link PIXI.interaction.InteractionManager#event:rightdown}. * - * @event rightupoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device (usually a mouse) is moved while over the display object * - * @event mousemove - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mousemove + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device (usually a mouse) is moved onto the display object * - * @event mouseover - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseover + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device (usually a mouse) is moved off the display object * - * @event mouseout - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseout + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is pressed on the display object. * - * @event pointerdown - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerdown + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is released over the display object. * - * @event pointerup - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerup + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when the operating system cancels a pointer event * - * @event pointercancel - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointercancel + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is pressed and released on the display object. * - * @event pointertap - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointertap + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is released outside the display object that initially * registered a [pointerdown]{@link PIXI.interaction.InteractionManager#event:pointerdown}. * - * @event pointerupoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device is moved while over the display object * - * @event pointermove - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointermove + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device is moved onto the display object * - * @event pointerover - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerover + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device is moved off the display object * - * @event pointerout - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerout + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is placed on the display object. * - * @event touchstart - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchstart + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is removed from the display object. * - * @event touchend - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchend + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when the operating system cancels a touch * - * @event touchcancel - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchcancel + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is placed and removed from the display object. * - * @event tap - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#tap + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is removed outside of the display object that initially * registered a [touchstart]{@link PIXI.interaction.InteractionManager#event:touchstart}. * - * @event touchendoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchendoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is moved along the display object. * - * @event touchmove - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchmove + * @param {PIXI.interaction.InteractionData} event - Interaction data */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is pressed on the display. + * object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mousedown + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is pressed + * on the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightdown + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is released over the display + * object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseup + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is released + * over the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightup + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is pressed and released on + * the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#click + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is pressed + * and released on the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightclick + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is released outside the + * display object that initially registered a + * [mousedown]{@link PIXI.DisplayObject#event:mousedown}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is released + * outside the display object that initially registered a + * [rightdown]{@link PIXI.DisplayObject#event:rightdown}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device (usually a mouse) is moved while over the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mousemove + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device (usually a mouse) is moved onto the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseover + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device (usually a mouse) is moved off the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseout + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is pressed on the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerdown + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is released over the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerup + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when the operating system cancels a pointer event. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointercancel + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is pressed and released on the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointertap + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is released outside the display object that initially + * registered a [pointerdown]{@link PIXI.DisplayObject#event:pointerdown}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device is moved while over the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointermove + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device is moved onto the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerover + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device is moved off the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerout + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is placed on the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchstart + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is removed from the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchend + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when the operating system cancels a touch. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchcancel + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is placed and removed from the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#tap + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is removed outside of the display object that initially + * registered a [touchstart]{@link PIXI.DisplayObject#event:touchstart}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchendoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is moved along the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchmove + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + } + + /** + * Hit tests a point against the display tree, returning the first interactive object that is hit. + * + * @param {PIXI.Point} globalPoint - A point to hit test with, in global space. + * @param {PIXI.Container} [root] - The root display object to start from. If omitted, defaults + * to the last rendered root of the associated renderer. + * @return {PIXI.DisplayObject} The hit display object, if any. + */ + hitTest(globalPoint, root) + { + // clear the target for our hit test + hitTestEvent.target = null; + // assign the global point + hitTestEvent.data.global = globalPoint; + // ensure safety of the root + if (!root) + { + root = this.renderer._lastObjectRendered; + } + // run the hit test + this.processInteractive(hitTestEvent, root, null, true); + // return our found object - it'll be null if we didn't hit anything + + return hitTestEvent.target; } /** @@ -487,7 +704,7 @@ return; } - core.ticker.shared.add(this.update, this); + core.ticker.shared.add(this.update, this, core.UPDATE_PRIORITY.INTERACTION); if (window.navigator.msPointerEnabled) { @@ -804,8 +1021,6 @@ } } - let keepHitTestingAfterChildren = hitTest; - // ** FREE TIP **! If an object is not interactive or has no buttons in it // (such as a game scene!) set interactiveChildren to false for that displayObject. // This will allow pixi to completely ignore and bypass checking the displayObjects children. @@ -818,7 +1033,9 @@ const child = children[i]; // time to get recursive.. if this function will return if something is hit.. - if (this.processInteractive(interactionEvent, child, func, hitTest, interactiveParent)) + const childHit = this.processInteractive(interactionEvent, child, func, hitTest, interactiveParent); + + if (childHit) { // its a good idea to check if a child has lost its parent. // this means it has been removed whilst looping so its best @@ -827,8 +1044,6 @@ continue; } - hit = true; - // we no longer need to hit test any more objects in this container as we we // now know the parent has been hit interactiveParent = false; @@ -838,36 +1053,41 @@ // This means we no longer need to hit test anything else. We still need to run // through all objects, but we don't need to perform any hit tests. - keepHitTestingAfterChildren = false; - - if (child.interactive) + if (childHit) { - hitTest = false; + if (interactionEvent.target) + { + hitTest = false; + } + hit = true; } - - // we can break now as we have hit an object. } } } - hitTest = keepHitTestingAfterChildren; - // no point running this if the item is not interactive or does not have an interactive parent. if (interactive) { // if we are hit testing (as in we have no hit any objects yet) // We also don't need to worry about hit testing if once of the displayObjects children - // has already been hit! - if (hitTest && !hit) + // has already been hit - but only if it was interactive, otherwise we need to keep + // looking for an interactive child, just in case we hit one + if (hitTest && !interactionEvent.target) { if (displayObject.hitArea) { displayObject.worldTransform.applyInverse(point, this._tempPoint); - hit = displayObject.hitArea.contains(this._tempPoint.x, this._tempPoint.y); + if (displayObject.hitArea.contains(this._tempPoint.x, this._tempPoint.y)) + { + hit = true; + } } else if (displayObject.containsPoint) { - hit = displayObject.containsPoint(point); + if (displayObject.containsPoint(point)) + { + hit = true; + } } } @@ -878,7 +1098,10 @@ interactionEvent.target = displayObject; } - func(interactionEvent, displayObject, hit); + if (func) + { + func(interactionEvent, displayObject, !!hit); + } } } @@ -914,7 +1137,7 @@ { const event = events[i]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -998,7 +1221,7 @@ { const event = events[i]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1098,7 +1321,7 @@ const test = isRightButton ? flags.RIGHT_DOWN : flags.LEFT_DOWN; - const isDown = trackingData !== undefined && (trackingData.flags | test); + const isDown = trackingData !== undefined && (trackingData.flags & test); if (hit) { @@ -1118,11 +1341,11 @@ { if (isRightButton) { - trackingData.rightDown = hit; + trackingData.rightDown = false; } else { - trackingData.leftDown = hit; + trackingData.leftDown = false; } } } @@ -1180,7 +1403,7 @@ { const event = events[i]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1255,7 +1478,7 @@ this.setCursorMode(null); } - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1268,6 +1491,12 @@ { this.emit('mouseout', interactionEvent); } + else + { + // we can get touchleave events after touchend, so we want to make sure we don't + // introduce memory leaks + this.releaseInteractionDataForPointerId(interactionData.identifier); + } } /** @@ -1344,7 +1573,7 @@ // Only mouse and pointer can call onPointerOver, so events will always be length 1 const event = events[0]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1366,12 +1595,14 @@ * Get InteractionData for a given pointerId. Store that data as well * * @private - * @param {number} pointerId - Identifier from a pointer event + * @param {PointerEvent} event - Normalized pointer event, output from normalizeToPointerData * @return {InteractionData} - Interaction data for the given pointer identifier */ - getInteractionDataForPointerId(pointerId) + getInteractionDataForPointerId(event) { - if (pointerId === MOUSE_POINTER_ID) + const pointerId = event.pointerId; + + if (pointerId === MOUSE_POINTER_ID || event.pointerType === 'mouse') { return this.mouse; } diff --git a/src/interaction/InteractionTrackingData.js b/src/interaction/InteractionTrackingData.js index 6a1f69e..8493b63 100644 --- a/src/interaction/InteractionTrackingData.js +++ b/src/interaction/InteractionTrackingData.js @@ -35,8 +35,10 @@ } /** + * Unique pointer id of the event + * * @readonly - * @type {number} Unique pointer id of the event + * @member {number} */ get pointerId() { diff --git a/src/interaction/index.js b/src/interaction/index.js index 56c5b09..72f7d71 100644 --- a/src/interaction/index.js +++ b/src/interaction/index.js @@ -1,4 +1,8 @@ /** + * This namespace contains a renderer plugin for handling mouse, pointer, and touch events. + * + * Do not instantiate this plugin directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. * @namespace PIXI.interaction */ export { default as InteractionData } from './InteractionData'; diff --git a/src/interaction/interactiveTarget.js b/src/interaction/interactiveTarget.js index d252fba..cebac40 100644 --- a/src/interaction/interactiveTarget.js +++ b/src/interaction/interactiveTarget.js @@ -2,7 +2,7 @@ * Default property values of interactive objects * Used by {@link PIXI.interaction.InteractionManager} to automatically give all DisplayObjects these properties * - * @mixin + * @private * @name interactiveTarget * @memberof PIXI.interaction * @example @@ -14,18 +14,28 @@ * ); */ export default { + /** - * Determines if the displayObject be clicked/touched + * Enable interaction events for the DisplayObject. Touch, pointer and mouse + * events will not be emitted unless `interactive` is set to `true`. * - * @inner {boolean} + * @example + * const sprite = new PIXI.Sprite(texture); + * sprite.interactive = true; + * sprite.on('tap', (event) => { + * //handle event + * }); + * @member {boolean} + * @memberof PIXI.DisplayObject# */ interactive: false, /** * Determines if the children to the displayObject can be clicked/touched - * Setting this to false allows pixi to bypass a recursive hitTest function + * Setting this to false allows pixi to bypass a recursive `hitTest` function * - * @inner {boolean} + * @member {boolean} + * @memberof PIXI.Container# */ interactiveChildren: true, @@ -33,7 +43,12 @@ * Interaction shape. Children will be hit first, then this shape will be checked. * Setting this will cause this shape to be checked in hit tests rather than the displayObject's bounds. * - * @inner {PIXI.Rectangle|PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.RoundedRectangle} + * @example + * const sprite = new PIXI.Sprite(texture); + * sprite.interactive = true; + * sprite.hitArea = new PIXI.Rectangle(0, 0, 100, 100); + * @member {PIXI.Rectangle|PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.RoundedRectangle} + * @memberof PIXI.DisplayObject# */ hitArea: null, @@ -41,8 +56,12 @@ * If enabled, the mouse cursor use the pointer behavior when hovered over the displayObject if it is interactive * Setting this changes the 'cursor' property to `'pointer'`. * + * @example + * const sprite = new PIXI.Sprite(texture); + * sprite.interactive = true; + * sprite.buttonMode = true; * @member {boolean} - * @memberof PIXI.interaction.interactiveTarget# + * @memberof PIXI.DisplayObject# */ get buttonMode() { @@ -64,9 +83,14 @@ * This defines what cursor mode is used when the mouse cursor * is hovered over the displayObject. * + * @example + * const sprite = new PIXI.Sprite(texture); + * sprite.interactive = true; + * sprite.cursor = 'wait'; * @see https://developer.mozilla.org/en/docs/Web/CSS/cursor * - * @inner {string} + * @member {string} + * @memberof PIXI.DisplayObject# */ cursor: null, @@ -74,7 +98,7 @@ * Internal set of all active pointers, by identifier * * @member {Map} - * @memberof PIXI.interaction.interactiveTarget# + * @memberof PIXI.DisplayObject# * @private */ get trackedPointers() @@ -87,7 +111,8 @@ /** * Map of all tracked pointers, by identifier. Use trackedPointers to access. * - * @private {Map} + * @private + * @type {Map} */ _trackedPointers: undefined, }; diff --git a/src/loaders/index.js b/src/loaders/index.js index 5189793..daa1fc4 100644 --- a/src/loaders/index.js +++ b/src/loaders/index.js @@ -1,9 +1,22 @@ +import Application from '../core/Application'; +import Loader from './loader'; + /** + * This namespace contains APIs which extends the {@link https://github.com/englercj/resource-loader resource-loader} module + * for loading assets, data, and other resources dynamically. + * @example + * const loader = new PIXI.loaders.Loader(); + * loader.add('bunny', 'data/bunny.png') + * .add('spaceship', 'assets/spritesheet.json'); + * loader.load((loader, resources) => { + * // resources.bunny + * // resources.spaceship + * }); * @namespace PIXI.loaders */ -export { default as Loader } from './loader'; +export { Loader }; export { default as bitmapFontParser, parse as parseBitmapFontData } from './bitmapFontParser'; -export { default as spritesheetParser } from './spritesheetParser'; +export { default as spritesheetParser, getResourcePath } from './spritesheetParser'; export { default as textureParser } from './textureParser'; /** @@ -13,3 +26,55 @@ * @memberof PIXI.loaders */ export { Resource } from 'resource-loader'; + +/** + * A premade instance of the loader that can be used to load resources. + * @name shared + * @memberof PIXI.loaders + * @type {PIXI.loaders.Loader} + */ +const shared = new Loader(); + +shared.destroy = () => +{ + // protect destroying shared loader +}; + +export { shared }; + +// Mixin the loader construction +const AppPrototype = Application.prototype; + +AppPrototype._loader = null; + +/** + * Loader instance to help with asset loading. + * @name PIXI.Application#loader + * @type {PIXI.loaders.Loader} + */ +Object.defineProperty(AppPrototype, 'loader', { + get() + { + if (!this._loader) + { + const sharedLoader = this._options.sharedLoader; + + this._loader = sharedLoader ? shared : new Loader(); + } + + return this._loader; + }, +}); + +// Override the destroy function +// making sure to destroy the current Loader +AppPrototype._parentDestroy = AppPrototype.destroy; +AppPrototype.destroy = function destroy() +{ + if (this._loader) + { + this._loader.destroy(); + this._loader = null; + } + this._parentDestroy(); +}; diff --git a/src/loaders/loader.js b/src/loaders/loader.js index 767a411..59b8000 100644 --- a/src/loaders/loader.js +++ b/src/loaders/loader.js @@ -90,6 +90,15 @@ { Loader._pixiMiddleware.push(fn); } + + /** + * Destroy the loader, removes references. + */ + destroy() + { + this.removeAllListeners(); + this.reset(); + } } // Copy EE3 prototype (mixin) diff --git a/src/loaders/spritesheetParser.js b/src/loaders/spritesheetParser.js index e57dfb2..eda9584 100644 --- a/src/loaders/spritesheetParser.js +++ b/src/loaders/spritesheetParser.js @@ -1,12 +1,11 @@ import { Resource } from 'resource-loader'; -import path from 'path'; +import url from 'url'; import { Spritesheet } from '../core'; export default function () { return function spritesheetParser(resource, next) { - let resourcePath; const imageResourceName = `${resource.name}_image`; // skip if no data, its not json, it isn't spritesheet data, or the image resource already exists @@ -28,15 +27,7 @@ parentResource: resource, }; - // Prepend url path unless the resource image is a data url - if (resource.isDataUrl) - { - resourcePath = resource.data.meta.image; - } - else - { - resourcePath = `${path.dirname(resource.url.replace(this.baseUrl, ''))}/${resource.data.meta.image}`; - } + const resourcePath = getResourcePath(resource, this.baseUrl); // load the image for this sheet this.add(imageResourceName, resourcePath, loadOptions, function onImageLoad(res) @@ -56,3 +47,14 @@ }); }; } + +export function getResourcePath(resource, baseUrl) +{ + // Prepend url path unless the resource image is a data url + if (resource.isDataUrl) + { + return resource.data.meta.image; + } + + return url.resolve(resource.url.replace(baseUrl, ''), resource.data.meta.image); +} diff --git a/src/mesh/canvas/CanvasMeshRenderer.js b/src/mesh/canvas/CanvasMeshRenderer.js index 297205f..f7899e1 100644 --- a/src/mesh/canvas/CanvasMeshRenderer.js +++ b/src/mesh/canvas/CanvasMeshRenderer.js @@ -134,12 +134,33 @@ const textureWidth = base.width; const textureHeight = base.height; - const u0 = uvs[index0] * base.width; - const u1 = uvs[index1] * base.width; - const u2 = uvs[index2] * base.width; - const v0 = uvs[index0 + 1] * base.height; - const v1 = uvs[index1 + 1] * base.height; - const v2 = uvs[index2 + 1] * base.height; + let u0; + let u1; + let u2; + let v0; + let v1; + let v2; + + if (mesh.uploadUvTransform) + { + const ut = mesh._uvTransform.mapCoord; + + u0 = ((uvs[index0] * ut.a) + (uvs[index0 + 1] * ut.c) + ut.tx) * base.width; + u1 = ((uvs[index1] * ut.a) + (uvs[index1 + 1] * ut.c) + ut.tx) * base.width; + u2 = ((uvs[index2] * ut.a) + (uvs[index2 + 1] * ut.c) + ut.tx) * base.width; + v0 = ((uvs[index0] * ut.b) + (uvs[index0 + 1] * ut.d) + ut.ty) * base.height; + v1 = ((uvs[index1] * ut.b) + (uvs[index1 + 1] * ut.d) + ut.ty) * base.height; + v2 = ((uvs[index2] * ut.b) + (uvs[index2 + 1] * ut.d) + ut.ty) * base.height; + } + else + { + u0 = uvs[index0] * base.width; + u1 = uvs[index1] * base.width; + u2 = uvs[index2] * base.width; + v0 = uvs[index0 + 1] * base.height; + v1 = uvs[index1 + 1] * base.height; + v2 = uvs[index2 + 1] * base.height; + } let x0 = vertices[index0]; let x1 = vertices[index1]; diff --git a/src/mesh/webgl/MeshRenderer.js b/src/mesh/webgl/MeshRenderer.js index 78d24c3..ab99fcd 100644 --- a/src/mesh/webgl/MeshRenderer.js +++ b/src/mesh/webgl/MeshRenderer.js @@ -2,6 +2,8 @@ const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; +const matrixIdentity = core.Matrix.IDENTITY; + /** * WebGL renderer plugin for tiling sprites * @@ -64,6 +66,7 @@ mesh.geometry.glVertexArrayObjects[this.CONTEXT_UID].draw(mesh.drawMode, mesh.size, mesh.start, mesh.geometry.instanceCount); } + /** * draws mesh * @param {PIXI.mesh.RawMesh} mesh mesh instance diff --git a/src/mesh/webgl/mesh.vert b/src/mesh/webgl/mesh.vert index a337aef..acc096c 100644 --- a/src/mesh/webgl/mesh.vert +++ b/src/mesh/webgl/mesh.vert @@ -1,8 +1,9 @@ attribute vec2 aVertexPosition; attribute vec2 aTextureCoord; -uniform mat3 translationMatrix; uniform mat3 projectionMatrix; +uniform mat3 translationMatrix; +uniform mat3 uTransform; varying vec2 vTextureCoord; @@ -10,5 +11,5 @@ { gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - vTextureCoord = aTextureCoord; + vTextureCoord = (uTransform * vec3(aTextureCoord, 1.0)).xy; } diff --git a/src/particles/ParticleContainer.js b/src/particles/ParticleContainer.js index 67796cf..63f7791 100644 --- a/src/particles/ParticleContainer.js +++ b/src/particles/ParticleContainer.js @@ -313,10 +313,10 @@ frame.y * resolution, frame.width * resolution, frame.height * resolution, - positionX * resolution, - positionY * resolution, - finalWidth * resolution, - finalHeight * resolution + positionX * renderer.resolution, + positionY * renderer.resolution, + finalWidth * renderer.resolution, + finalHeight * renderer.resolution ); } } diff --git a/src/prepare/BasePrepare.js b/src/prepare/BasePrepare.js index 09eb5e5..e45c4dc 100644 --- a/src/prepare/BasePrepare.js +++ b/src/prepare/BasePrepare.js @@ -18,6 +18,18 @@ * basic queuing functionality and is extended by {@link PIXI.prepare.WebGLPrepare} and {@link PIXI.prepare.CanvasPrepare} * to provide preparation capabilities specific to their respective renderers. * + * @example + * // Create a sprite + * const sprite = new PIXI.Sprite.fromImage('something.png'); + * + * // Load object into GPU + * app.renderer.plugins.prepare.upload(sprite, () => { + * + * //Texture(s) has been uploaded to GPU + * app.stage.addChild(sprite); + * + * }) + * * @abstract * @class * @memberof PIXI.prepare @@ -100,8 +112,16 @@ this.prepareItems(); }; - this.register(findText, drawText); - this.register(findTextStyle, calculateTextStyle); + // hooks to find the correct texture + this.registerFindHook(findText); + this.registerFindHook(findTextStyle); + this.registerFindHook(findMultipleBaseTextures); + this.registerFindHook(findBaseTexture); + this.registerFindHook(findTexture); + + // upload hooks + this.registerUploadHook(drawText); + this.registerUploadHook(calculateTextStyle); } /** @@ -138,7 +158,7 @@ if (!this.ticking) { this.ticking = true; - SharedTicker.addOnce(this.tick, this); + SharedTicker.addOnce(this.tick, this, core.UPDATE_PRIORITY.UTILITY); } } else if (done) @@ -172,13 +192,16 @@ const item = this.queue[0]; let uploaded = false; - for (let i = 0, len = this.uploadHooks.length; i < len; i++) + if (item && !item._destroyed) { - if (this.uploadHooks[i](this.uploadHookHelper, item)) + for (let i = 0, len = this.uploadHooks.length; i < len; i++) { - this.queue.shift(); - uploaded = true; - break; + if (this.uploadHooks[i](this.uploadHookHelper, item)) + { + this.queue.shift(); + uploaded = true; + break; + } } } @@ -205,26 +228,36 @@ else { // if we are not finished, on the next rAF do this again - SharedTicker.addOnce(this.tick, this); + SharedTicker.addOnce(this.tick, this, core.UPDATE_PRIORITY.UTILITY); } } /** - * Adds hooks for finding and uploading items. + * Adds hooks for finding items. * - * @param {Function} [addHook] - Function call that takes two parameters: `item:*, queue:Array` - function must return `true` if it was able to add item to the queue. - * @param {Function} [uploadHook] - Function call that takes two parameters: `prepare:CanvasPrepare, item:*` and - * function must return `true` if it was able to handle upload of item. - * @return {PIXI.CanvasPrepare} Instance of plugin for chaining. + * @param {Function} addHook - Function call that takes two parameters: `item:*, queue:Array` + * function must return `true` if it was able to add item to the queue. + * @return {PIXI.BasePrepare} Instance of plugin for chaining. */ - register(addHook, uploadHook) + registerFindHook(addHook) { if (addHook) { this.addHooks.push(addHook); } + return this; + } + + /** + * Adds hooks for uploading items. + * + * @param {Function} uploadHook - Function call that takes two parameters: `prepare:CanvasPrepare, item:*` and + * function must return `true` if it was able to handle upload of item. + * @return {PIXI.BasePrepare} Instance of plugin for chaining. + */ + registerUploadHook(uploadHook) + { if (uploadHook) { this.uploadHooks.push(uploadHook); @@ -287,6 +320,88 @@ } /** + * Built-in hook to find multiple textures from objects like AnimatedSprites. + * + * @private + * @param {PIXI.DisplayObject} item - Display object to check + * @param {Array<*>} queue - Collection of items to upload + * @return {boolean} if a PIXI.Texture object was found. + */ +function findMultipleBaseTextures(item, queue) +{ + let result = false; + + // Objects with mutliple textures + if (item && item._textures && item._textures.length) + { + for (let i = 0; i < item._textures.length; i++) + { + if (item._textures[i] instanceof core.Texture) + { + const baseTexture = item._textures[i].baseTexture; + + if (queue.indexOf(baseTexture) === -1) + { + queue.push(baseTexture); + result = true; + } + } + } + } + + return result; +} + +/** + * Built-in hook to find BaseTextures from Sprites. + * + * @private + * @param {PIXI.DisplayObject} item - Display object to check + * @param {Array<*>} queue - Collection of items to upload + * @return {boolean} if a PIXI.Texture object was found. + */ +function findBaseTexture(item, queue) +{ + // Objects with textures, like Sprites/Text + if (item instanceof core.BaseTexture) + { + if (queue.indexOf(item) === -1) + { + queue.push(item); + } + + return true; + } + + return false; +} + +/** + * Built-in hook to find textures from objects. + * + * @private + * @param {PIXI.DisplayObject} item - Display object to check + * @param {Array<*>} queue - Collection of items to upload + * @return {boolean} if a PIXI.Texture object was found. + */ +function findTexture(item, queue) +{ + if (item._texture && item._texture instanceof core.Texture) + { + const texture = item._texture.baseTexture; + + if (queue.indexOf(texture) === -1) + { + queue.push(texture); + } + + return true; + } + + return false; +} + +/** * Built-in hook to draw PIXI.Text to its texture. * * @private diff --git a/.travis.yml b/.travis.yml index 3df8b22..14de334 100644 --- a/.travis.yml +++ b/.travis.yml @@ -110,3 +110,43 @@ on: all_branches: true condition: $TRAVIS_TAG + # Deploy config for latest release + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: dist + upload-dir: "release" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: docs + upload-dir: "release/docs" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: coverage + upload-dir: "release/coverage" + on: + all_branches: true + condition: $TRAVIS_TAG \ No newline at end of file diff --git a/README.md b/README.md index d26fadd..3b85f85 100644 --- a/README.md +++ b/README.md @@ -36,22 +36,14 @@ - Wiki: Other misc tutorials and resources are [on the Wiki](https://github.com/pixijs/pixi.js/wiki/Resources). ### Community ### -- Forums: Check out the [forum] (http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow] (http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. +- Forums: Check out the [forum](http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow](http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. - Inspiration: Check out the [gallery](http://www.pixijs.com/gallery) to see some of the amazing things people have created! - Chat: You can join us on [Gitter](https://gitter.im/pixijs/pixi.js) To chat about Pixi. We also now have a Slack channel. If you would like to join it please Send me an email (mat@goodboydigital.com) and I will invite you in. ### Setup ### -It's easy to get started with Pixi.js! Simply grab the pre-built versions from here: - -Release Branch - Nice and stable Pixi.js -- Unminified: [http://pixijs.download/release/pixi.js] -- Minified: [http://pixijs.download/release/pixi.min.js] - -Develop Branch - The bleeding edge version of Pixi.js -- Unminified: [http://pixijs.download/dev/pixi.js] -- Minified: [http://pixijs.download/dev/pixi.min.js] +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. @@ -70,10 +62,10 @@ #### CDN Install (via cdnjs) ```html - + ``` -_Note: `4.2.2` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ +_Note: `4.4.3` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ ### Demos ### @@ -130,7 +122,7 @@ // and the root stage PIXI.Container. var app = new PIXI.Application(); -// The application will create a canvas element for you that you +// The application will create a canvas element for you that you // can then insert into the DOM. document.body.appendChild(app.view); @@ -150,7 +142,7 @@ // Add the bunny to the scene we are building. app.stage.addChild(bunny); - + // Listen for frame updates app.ticker.add(function() { // each frame we spin the bunny around a bit diff --git a/bower.json b/bower.json deleted file mode 100644 index 41b668c..0000000 --- a/bower.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "pixi.js", - "main": "dist/pixi.min.js", - "ignore": [ - "**/.*", - "docs", - "node_modules", - "bower_components", - "test", - "scripts", - "bower.json", - "inch.json" - ], - "dependencies": { - }, - "devDependencies": { - } -} diff --git a/package.json b/package.json index d35cbb2..308469f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.3.5", + "version": "4.4.4", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ @@ -40,11 +40,11 @@ "lib": "babel src --out-dir lib -s", "predocs": "rimraf docs/**", "docs": "jsdoc -c scripts/jsdoc.conf.json -R README.md", - "publish:patch": "npm version patch --no-git-tag-version && npm publish", - "publish:minor": "npm version minor --no-git-tag-version && npm publish", - "publish:major": "npm version major --no-git-tag-version && npm publish", + "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", - "postpublish": "node scripts/release.js" + "postpublish": "git push && git push --tags" }, "files": [ "dist/", @@ -72,7 +72,6 @@ "electron": "^1.4.15", "eslint": "^3.5.0", "floss": "^2.0.1", - "gh-pages": "^0.11.0", "jaguarjs-jsdoc": "^1.0.1", "js-md5": "^0.4.1", "jsdoc": "^3.4.2", diff --git a/scripts/jsdoc.conf.json b/scripts/jsdoc.conf.json index 553e61c..3f52180 100644 --- a/scripts/jsdoc.conf.json +++ b/scripts/jsdoc.conf.json @@ -43,7 +43,7 @@ }, "markdown" : { "parser" : "gfm", - "hardwrap" : true + "hardwrap" : false }, "opts": { "encoding" : "utf8", diff --git a/scripts/release.js b/scripts/release.js deleted file mode 100644 index 156620c..0000000 --- a/scripts/release.js +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env node -'use strict'; - -// Publish script to push releases of the bin files -// the normally are gitignored -const ghpages = require('gh-pages'); -const path = require('path'); -const packageInfo = require(path.join(__dirname, '..', 'package.json')); - -const options = { - src: [ - 'dist/**/*', - 'lib/**/*', - 'src/**/*', - 'scripts/*', - 'scripts/renders/*', - 'test/**/*', - '*.json', - '*.md', - 'LICENSE', - '.eslintrc', - '.editorconfig', - '.travis.yml', - '.babelrc', - ], - dotfiles: true, - branch: 'release', - message: packageInfo.version, - logger: console.log.bind(console), -}; - -ghpages.publish(process.cwd(), options, (err) => -{ - if (err) - { - console.log(err); - process.exit(1); - - return; - } - - process.exit(0); -}); diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index f16bcb1..220427a 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -21,7 +21,7 @@ const DIV_HOOK_ZINDEX = 2; /** - * The Accessibility manager reacreates the ability to tab and and have content read by screen + * The Accessibility manager recreates the ability to tab and and have content read by screen * readers. This is very important as it can possibly help people with disabilities access pixi * content. * diff --git a/src/accessibility/index.js b/src/accessibility/index.js index 568cfb1..0ae108f 100644 --- a/src/accessibility/index.js +++ b/src/accessibility/index.js @@ -1,4 +1,9 @@ /** + * This namespace contains a renderer plugin for interaction accessibility for end-users + * with physical impairments which require screen-renders, keyboard navigation, etc. + * + * Do not instantiate this plugin directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. * @namespace PIXI.accessibility */ export { default as accessibleTarget } from './accessibleTarget'; diff --git a/src/core/Application.js b/src/core/Application.js index 5340578..2fcc729 100644 --- a/src/core/Application.js +++ b/src/core/Application.js @@ -1,6 +1,8 @@ import { autoDetectRenderer } from './autoDetectRenderer'; import Container from './display/Container'; import { shared, Ticker } from './ticker'; +import settings from './settings'; +import { UPDATE_PRIORITY } from './const'; /** * Convenience class to create a new PIXI application. @@ -22,28 +24,52 @@ */ export default class Application { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experience unexplained flickering try setting this to true. - * @param {boolean} [useSharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedLoader=false] - `true` to use PIXI.loaders.shared, `false` to create new Loader. */ - constructor(width, height, options, noWebGL, useSharedTicker = false) + constructor(options, arg2, arg3, arg4, arg5) { + // Support for constructor(width, height, options, noWebGL, useSharedTicker) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + forceCanvas: !!arg4, + sharedTicker: !!arg5, + }, arg3); + } + + /** + * The default options, so we mixin functionality later. + * @member {object} + * @protected + */ + this._options = options = Object.assign({ + sharedTicker: false, + forceCanvas: false, + sharedLoader: false, + }, options); + /** * WebGL renderer if available, otherwise CanvasRenderer * @member {PIXI.WebGLRenderer|PIXI.CanvasRenderer} */ - this.renderer = autoDetectRenderer(width, height, options, noWebGL); + this.renderer = autoDetectRenderer(options); /** * The root display container that's rendered. @@ -63,7 +89,7 @@ * @member {PIXI.ticker.Ticker} * @default PIXI.ticker.shared */ - this.ticker = useSharedTicker ? shared : new Ticker(); + this.ticker = options.sharedTicker ? shared : new Ticker(); // Start the rendering this.start(); @@ -78,7 +104,7 @@ this._ticker = ticker; if (ticker) { - ticker.add(this.render, this); + ticker.add(this.render, this, UPDATE_PRIORITY.LOW); } } get ticker() // eslint-disable-line require-jsdoc @@ -136,13 +162,18 @@ */ destroy(removeView) { - this.stop(); + const oldTicker = this._ticker; + this.ticker = null; + oldTicker.destroy(); + this.stage.destroy(); this.stage = null; this.renderer.destroy(removeView); this.renderer = null; + + this._options = null; } } diff --git a/src/core/autoDetectRenderer.js b/src/core/autoDetectRenderer.js index 8960425..9ec0364 100644 --- a/src/core/autoDetectRenderer.js +++ b/src/core/autoDetectRenderer.js @@ -2,6 +2,7 @@ import CanvasRenderer from './renderers/canvas/CanvasRenderer'; import WebGLRenderer from './renderers/webgl/WebGLRenderer'; +// eslint-disable-next-line valid-jsdoc /** * This helper function will automatically detect which renderer you should be using. * WebGL is the preferred renderer as it is a lot faster. If webGL is not supported by @@ -9,24 +10,32 @@ * * @memberof PIXI * @function autoDetectRenderer - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @return {PIXI.WebGLRenderer|PIXI.CanvasRenderer} Returns WebGL renderer if available, otherwise CanvasRenderer */ -export function autoDetectRenderer(width = 800, height = 600, options, noWebGL) +export function autoDetectRenderer(options, arg1, arg2, arg3) { - if (!noWebGL && utils.isWebGLSupported()) + // Backward-compatible support for noWebGL option + let forceCanvas = options && options.forceCanvas; + + if (arg3 !== undefined) { - return new WebGLRenderer(width, height, options); + forceCanvas = arg3; } - return new CanvasRenderer(width, height, options); + if (!forceCanvas && utils.isWebGLSupported()) + { + return new WebGLRenderer(options, arg1, arg2); + } + + return new CanvasRenderer(options, arg1, arg2); } diff --git a/src/core/const.js b/src/core/const.js index b9e99ae..2c68dee 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -341,3 +341,27 @@ LINEAR_VERTICAL: 0, LINEAR_HORIZONTAL: 1, }; + +/** + * Represents the update priorities used by internal PIXI classes when registered with + * the {@link PIXI.ticker.Ticker} object. Higher priority items are updated first and lower + * priority items, such as render, should go later. + * + * @static + * @constant + * @name UPDATE_PRIORITY + * @memberof PIXI + * @type {object} + * @property {number} INTERACTION=50 Highest priority, used for {@link PIXI.interaction.InteractionManager} + * @property {number} HIGH=25 High priority updating, {@link PIXI.VideoBaseTexture} and {@link PIXI.extras.AnimatedSprite} + * @property {number} NORMAL=0 Default priority for ticker events, see {@link PIXI.ticker.Ticker#add}. + * @property {number} LOW=-25 Low priority used for {@link PIXI.Application} rendering. + * @property {number} UTILITY=-50 Lowest priority used for {@link PIXI.prepare.BasePrepare} utility. + */ +export const UPDATE_PRIORITY = { + INTERACTION: 50, + HIGH: 25, + NORMAL: 0, + LOW: -25, + UTILITY: -50, +}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 4e6c77d..decb42a 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -13,7 +13,6 @@ * * @class * @extends EventEmitter - * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ export default class DisplayObject extends EventEmitter @@ -113,6 +112,29 @@ * @private */ this._mask = null; + + /** + * If the object has been destroyed via destroy(). If true, it should not be used. + * + * @member {boolean} + * @private + * @readonly + */ + this._destroyed = false; + + /** + * Fired when this DisplayObject is added to a Container. + * + * @event PIXI.DisplayObject#added + * @param {PIXI.Container} container - The container added to. + */ + + /** + * Fired when this DisplayObject is removed from a Container. + * + * @event PIXI.DisplayObject#removed + * @param {PIXI.Container} container - The container removed from. + */ } /** @@ -403,6 +425,8 @@ this.interactive = false; this.interactiveChildren = false; + + this._destroyed = true; } /** diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index aafdfe2..f787c4a 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -792,8 +792,8 @@ sprite.worldAlpha = this.worldAlpha * sprite.alpha; sprite.blendMode = this.blendMode; - sprite.texture._frame.width = rect.width; - sprite.texture._frame.height = rect.height; + sprite._texture._frame.width = rect.width; + sprite._texture._frame.height = rect.height; sprite.transform.worldTransform = this.transform.worldTransform; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index a24c775..b9f192d 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -19,11 +19,12 @@ */ export default class SystemRenderer extends EventEmitter { + // eslint-disable-next-line valid-jsdoc /** * @param {string} system - The name of the system this renderer is for. - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -37,27 +38,31 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(system, screenWidth, screenHeight, options) + constructor(system, options, arg2, arg3) { super(); sayHello(system); - // prepare options - if (options) + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') { - for (const i in settings.RENDER_OPTIONS) - { - if (typeof options[i] === 'undefined') - { - options[i] = settings.RENDER_OPTIONS[i]; - } - } + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); } - else - { - options = settings.RENDER_OPTIONS; - } + + // Add the default render options + options = Object.assign({}, settings.RENDER_OPTIONS, options); + + /** + * The supplied constructor options. + * + * @member {Object} + * @readOnly + */ + this.options = options; /** * The type of the renderer. @@ -75,7 +80,7 @@ * * @member {PIXI.Rectangle} */ - this.screen = new Rectangle(0, 0, screenWidth || 800, screenHeight || 600); + this.screen = new Rectangle(0, 0, options.width, options.height); /** * The canvas element that everything is drawn to @@ -279,6 +284,8 @@ this.blendModes = null; + this.options = null; + this.preserveDrawingBuffer = false; this.clearBeforeRender = false; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index e84e49c..691caf0 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -17,10 +17,11 @@ */ export default class CanvasRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -34,9 +35,9 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('Canvas', screenWidth, screenHeight, options); + super('Canvas', options, arg2, arg3); this.type = RENDERER_TYPE.CANVAS; @@ -96,7 +97,19 @@ this.context = null; this.renderingToScreen = false; - this.resize(screenWidth, screenHeight); + this.resize(this.options.width, this.options.height); + + /** + * Fired after rendering finishes. + * + * @event PIXI.CanvasRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.CanvasRenderer#prerender + */ } /** @@ -299,4 +312,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.CanvasRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.CanvasExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.CanvasPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.CanvasRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index e1cf7cd..f6fe123 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -31,11 +31,12 @@ */ export default class WebGLRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** * - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -55,9 +56,9 @@ * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('WebGL', screenWidth, screenHeight, options); + super('WebGL', options, arg2, arg3); /** * The type of this renderer as a standardised const @@ -89,12 +90,6 @@ }; - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ this._backgroundColorRgba[3] = this.transparent ? 0 : 1; this.globalUniforms = new UniformGroup({ @@ -117,6 +112,13 @@ this.initPlugins(); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ if(options.context) { this.context.initFromContext(options.context); @@ -169,6 +171,25 @@ } return this; + + /** + * Fired after rendering finishes. + * + * @event PIXI.WebGLRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.WebGLRenderer#prerender + */ + + /** + * Fired when the WebGL context is set. + * + * @event PIXI.WebGLRenderer#context + * @param {WebGLRenderingContext} gl - WebGL context. + */ } /** @@ -295,4 +316,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.WebGLRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.WebGLRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(WebGLRenderer); diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index 4eb6128..fa3d883 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -47,6 +47,14 @@ * @member {boolean} */ this.enabled = true; + + /** + * If enabled, pixi will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; } /** diff --git a/src/core/renderers/webgl/systems/ContextSystem.js b/src/core/renderers/webgl/systems/ContextSystem.js index ff958c7..8df85ea 100644 --- a/src/core/renderers/webgl/systems/ContextSystem.js +++ b/src/core/renderers/webgl/systems/ContextSystem.js @@ -75,7 +75,8 @@ */ createContext(canvas, options) { - var gl = canvas.getContext('webgl', options) || + var gl = //canvas.getContext('webgl2', options) || + canvas.getContext('webgl', options) || canvas.getContext('experimental-webgl', options); if (!gl) diff --git a/src/core/renderers/webgl/systems/FilterSystem.js b/src/core/renderers/webgl/systems/FilterSystem.js index f47335d..37e28d2 100644 --- a/src/core/renderers/webgl/systems/FilterSystem.js +++ b/src/core/renderers/webgl/systems/FilterSystem.js @@ -111,7 +111,7 @@ // TODO we should fit the rect around the transform.. } - else + else if (filters[0].autoFit) { sourceFrame.fit(filterData.stack[0].destinationFrame); } diff --git a/src/core/renderers/webgl/systems/shader/shader/mapType.js b/src/core/renderers/webgl/systems/shader/shader/mapType.js index 581dace..917538a 100644 --- a/src/core/renderers/webgl/systems/shader/shader/mapType.js +++ b/src/core/renderers/webgl/systems/shader/shader/mapType.js @@ -15,7 +15,7 @@ } } - return GL_TABLE[type]; + return GL_TABLE[type]; }; var GL_TABLE = null; diff --git a/src/core/settings.js b/src/core/settings.js index 18c5877..38ef45c 100644 --- a/src/core/settings.js +++ b/src/core/settings.js @@ -2,6 +2,15 @@ import canUploadSameBuffer from './utils/canUploadSameBuffer'; /** + * User's customizable globals for overriding the default PIXI settings, such + * as a renderer's default resolution, framerate, float percision, etc. + * @example + * // Use the native window resolution as the default resolution + * // will support high-density displays when rendering + * PIXI.settings.RESOLUTION = window.devicePixelRatio. + * + * // Disable interpolation when scaling, will make texture be pixelated + * PIXI.settings.SCALE_MODE = PIXI.SCALE_MODES.NEAREST; * @namespace PIXI.settings */ export default { @@ -101,6 +110,9 @@ * @property {boolean} clearBeforeRender=true * @property {boolean} preserveDrawingBuffer=false * @property {boolean} roundPixels=false + * @property {number} width=800 + * @property {number} height=600 + * @property {boolean} legacy=false */ RENDER_OPTIONS: { view: null, @@ -112,6 +124,9 @@ clearBeforeRender: true, preserveDrawingBuffer: false, roundPixels: false, + width: 800, + height: 600, + legacy: false, }, /** diff --git a/src/core/shader/Program.js b/src/core/shader/Program.js index c0b538b..07b4398 100644 --- a/src/core/shader/Program.js +++ b/src/core/shader/Program.js @@ -1,6 +1,6 @@ import extractUniformsFromSrc from './extractUniformsFromSrc'; import generateUniformsSync from './generateUniformsSync'; -import glCore from 'pixi-gl-core'; +import shaderUtils from '../renderers/webgl/systems/shader/shader'; import { ProgramCache } from '../utils'; import getTestContext from '../utils/getTestContext'; @@ -64,14 +64,17 @@ } else { - vertexSrc = glCore.shader.setPrecision(vertexSrc, 'mediump'); - fragmentSrc = glCore.shader.setPrecision(fragmentSrc, 'mediump'); + vertexSrc = shaderUtils.setPrecision(vertexSrc, 'mediump'); + fragmentSrc = shaderUtils.setPrecision(fragmentSrc, 'mediump'); - const program = glCore.shader.compileProgram(gl, vertexSrc, fragmentSrc); + const program = shaderUtils.compileProgram(gl, vertexSrc, fragmentSrc); + console.log("<<<<<<>>><<"); this.attributeData = this.getAttributeData(program, gl); this.uniformData = this.getUniformData(program, gl); + console.log(this.uniformData); + //gl.deleteProgram(program); } } @@ -95,13 +98,13 @@ for (let i = 0; i < totalAttributes; i++) { const attribData = gl.getActiveAttrib(program, i); - const type = glCore.shader.mapType(gl, attribData.type); + const type = shaderUtils.mapType(gl, attribData.type); /*eslint-disable */ const data = { type: type, name: attribData.name, - size: glCore.shader.mapSize(type), + size: shaderUtils.mapSize(type), location: 0, }; /*eslint-enable */ @@ -137,13 +140,14 @@ // TODO expose this as a prop? // const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); - const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); + //const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); for (let i = 0; i < totalUniforms; i++) { const uniformData = gl.getActiveUniform(program, i); const name = uniformData.name.replace(/\[.*?\]/, ''); - const type = glCore.shader.mapType(gl, uniformData.type); + const type = shaderUtils.mapType(gl, uniformData.type); + console.log('mapping ' + uniformData.type + ' to ' + type); // if (!name.match(maskRegex)) { @@ -151,7 +155,7 @@ uniforms[name] = { type: type, size: uniformData.size, - value: glCore.shader.defaultValue(type, uniformData.size), + value: shaderUtils.defaultValue(type, uniformData.size), }; /*eslint-enable */ } diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index e387e99..b57d095 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -147,12 +147,12 @@ // so if _width is 0 then width was not set.. if (this._width) { - this.scale.x = sign(this.scale.x) * this._width / this.texture.orig.width; + this.scale.x = sign(this.scale.x) * this._width / this._texture.orig.width; } if (this._height) { - this.scale.y = sign(this.scale.y) * this._height / this.texture.orig.height; + this.scale.y = sign(this.scale.y) * this._height / this._texture.orig.height; } } diff --git a/src/core/sprites/canvas/CanvasTinter.js b/src/core/sprites/canvas/CanvasTinter.js index 27e5e0c..fd9de5d 100644 --- a/src/core/sprites/canvas/CanvasTinter.js +++ b/src/core/sprites/canvas/CanvasTinter.js @@ -18,7 +18,7 @@ */ getTintedTexture: (sprite, color) => { - const texture = sprite.texture; + const texture = sprite._texture; color = CanvasTinter.roundColor(color); diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 5a4edda..8b9b797 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -344,6 +344,7 @@ const texture = this._texture; const style = this._style; + const padding = style.trim ? 0 : style.padding; texture.baseTexture.valid = true; texture.baseTexture.resolution = this.resolution; @@ -353,11 +354,11 @@ texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - texture.trim.x = -style.padding; - texture.trim.y = -style.padding; + texture.trim.x = -padding; + texture.trim.y = -padding; - texture.orig.width = texture._frame.width - (style.padding * 2); - texture.orig.height = texture._frame.height - (style.padding * 2); + texture.orig.width = texture._frame.width - (padding * 2); + texture.orig.height = texture._frame.height - (padding * 2); // call sprite onTextureUpdate to update scale if _width or _height were set this._onTextureUpdate(); diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index 853da3d..8a9c34b 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -114,6 +114,38 @@ this.cacheId = null; this.validate(); + + /** + * Fired when a not-immediately-available source finishes loading. + * + * @protected + * @event PIXI.BaseTexture#loaded + * @param {PIXI.BaseTexture} baseTexture - Resource loaded. + */ + + /** + * Fired when a not-immediately-available source fails to load. + * + * @protected + * @event PIXI.BaseTexture#error + * @param {PIXI.BaseTexture} baseTexture - Resource errored. + */ + + /** + * Fired when BaseTexture is updated. + * + * @protected + * @event PIXI.BaseTexture#update + * @param {PIXI.BaseTexture} baseTexture - Instance of texture being updated. + */ + + /** + * Fired when BaseTexture is destroyed. + * + * @protected + * @event PIXI.BaseTexture#dispose + * @param {PIXI.BaseTexture} baseTexture - Instance of texture being destroyed. + */ } setResource(resource) diff --git a/src/core/textures/Spritesheet.js b/src/core/textures/Spritesheet.js index d469c62..9b24f27 100644 --- a/src/core/textures/Spritesheet.js +++ b/src/core/textures/Spritesheet.js @@ -208,6 +208,7 @@ // lets also add the frame to pixi's global cache for fromFrame and fromImage functions TextureCache[i] = this.textures[i]; + this.textures[i].textureCacheId = i; } frameIndex++; diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 0a2c379..2213087 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -147,9 +147,9 @@ /** * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. * - * @event update - * @memberof PIXI.Texture# + * @event PIXI.Texture#update * @protected + * @param {PIXI.Texture} texture - Instance of texture being updated. */ this._updateID = 0; @@ -159,6 +159,15 @@ * @type {Object} */ this.transform = null; + + /** + * The id under which this Texture has been added to the texture cache. This is + * automatically set in certain cases, but may not always be accurate, particularly if + * the texture is in the cache under multiple ids. + * + * @member {string} + */ + this.textureCacheId = null; } /** @@ -225,11 +234,17 @@ this._uvs = null; this.trim = null; this.orig = null; + this.textureCacheId = null; this.valid = false; - this.off('dispose', this.dispose, this); - this.off('update', this.update, this); + for (const prop in TextureCache) + { + if (TextureCache[prop] === this) + { + delete TextureCache[prop]; + } + } } /** @@ -315,14 +330,15 @@ */ static fromLoader(source, imageUrl, name) { - // console.log('added from loader...') + // console.log('added from loader...') const resource = new ImageResource(source);//.from(imageUrl, crossorigin);// document.createElement('img'); - // console.log('base resource ' + resource.width); + // console.log('base resource ' + resource.width); const baseTexture = new BaseTexture(resource, settings.SCALE_MODE, getResolutionOfUrl(imageUrl)); + /// console.log('base width ' + baseTexture.width); const texture = new Texture(baseTexture); @@ -335,6 +351,7 @@ // lets also add the frame to pixi's global cache for fromFrame and fromImage fucntions BaseTextureCache[name] = baseTexture; TextureCache[name] = texture; + texture.textureCacheId = name; // also add references by url if they are different. if (name !== imageUrl) @@ -355,6 +372,10 @@ */ static addTextureToCache(texture, id) { + if (!texture.textureCacheId) + { + texture.textureCacheId = id; + } TextureCache[id] = texture; } @@ -495,6 +516,7 @@ */ Texture.EMPTY = new Texture(new BaseTexture()); removeAllHandlers(Texture.EMPTY); +removeAllHandlers(Texture.EMPTY.baseTexture); /** * A white texture of 10x10 size, used for graphics and other things @@ -505,3 +527,4 @@ */ Texture.WHITE = createWhiteTexture(); removeAllHandlers(Texture.WHITE); +removeAllHandlers(Texture.WHITE.baseTexture); diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index 59eb9fd..a9b9f51 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -1,6 +1,7 @@ import BaseTexture from './BaseTexture'; import { uid, BaseTextureCache } from '../utils'; -import * as ticker from '../ticker'; +import { shared } from '../ticker'; +import { UPDATE_PRIORITY } from '../const'; /** * A texture of a [playing] Video. @@ -125,7 +126,7 @@ if (!this._isAutoUpdating && this.autoUpdate) { - ticker.shared.add(this.update, this); + shared.add(this.update, this, UPDATE_PRIORITY.HIGH); this._isAutoUpdating = true; } } @@ -139,7 +140,7 @@ { if (this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); this._isAutoUpdating = false; } } @@ -187,7 +188,7 @@ { if (this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); } if (this.source && this.source._pixiId) @@ -281,12 +282,12 @@ if (!this._autoUpdate && this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); this._isAutoUpdating = false; } else if (this._autoUpdate && !this._isAutoUpdating) { - ticker.shared.add(this.update, this); + shared.add(this.update, this, UPDATE_PRIORITY.HIGH); this._isAutoUpdating = true; } } diff --git a/src/core/ticker/Ticker.js b/src/core/ticker/Ticker.js index 17a1517..40df138 100644 --- a/src/core/ticker/Ticker.js +++ b/src/core/ticker/Ticker.js @@ -1,12 +1,10 @@ import settings from '../settings'; -import EventEmitter from 'eventemitter3'; - -// Internal event used by composed emitter -const TICK = 'tick'; +import { UPDATE_PRIORITY } from '../const'; +import TickerListener from './TickerListener'; /** * A Ticker class that runs an update loop that other objects listen to. - * This class is composed around an EventEmitter object to add listeners + * This class is composed around listeners * meant for execution on the next requested animation frame. * Animation frames are requested only when necessary, * e.g. When the ticker is started and the emitter has listeners. @@ -22,10 +20,11 @@ constructor() { /** - * Internal emitter used to fire 'tick' event + * The first listener. All new listeners added are chained on this. * @private + * @type {TickerListener} */ - this._emitter = new EventEmitter(); + this._head = new TickerListener(null, null, Infinity); /** * Internal current frame request ID @@ -131,7 +130,7 @@ // Invoke listeners now this.update(time); // Listener side effects may have modified ticker state. - if (this.started && this._requestId === null && this._emitter.listeners(TICK, true)) + if (this.started && this._requestId === null && this._head.next) { this._requestId = requestAnimationFrame(this._tick); } @@ -148,7 +147,7 @@ */ _requestIfNeeded() { - if (this._requestId === null && this._emitter.listeners(TICK, true)) + if (this._requestId === null && this._head.next) { // ensure callbacks get correct delta this.lastTime = performance.now(); @@ -193,35 +192,72 @@ } /** - * Calls {@link module:eventemitter3.EventEmitter#on} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. + * Register a handler for tick events. Calls continuously unless + * it is removed or the ticker is stopped. * * @param {Function} fn - The listener function to be added for updates * @param {Function} [context] - The listener context + * @param {number} [priority=PIXI.UPDATE_PRIORITY.NORMAL] - The priority for emitting * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - add(fn, context) + add(fn, context, priority = UPDATE_PRIORITY.NORMAL) { - this._emitter.on(TICK, fn, context); - - this._startIfPossible(); - - return this; + return this._addListener(new TickerListener(fn, context, priority)); } /** - * Calls {@link module:eventemitter3.EventEmitter#once} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. + * Add a handler for the tick event which is only execute once. * * @param {Function} fn - The listener function to be added for one update * @param {Function} [context] - The listener context + * @param {number} [priority=PIXI.UPDATE_PRIORITY.NORMAL] - The priority for emitting * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - addOnce(fn, context) + addOnce(fn, context, priority = UPDATE_PRIORITY.NORMAL) { - this._emitter.once(TICK, fn, context); + return this._addListener(new TickerListener(fn, context, priority, true)); + } + + /** + * Internally adds the event handler so that it can be sorted by priority. + * Priority allows certain handler (user, AnimatedSprite, Interaction) to be run + * before the rendering. + * + * @private + * @param {TickerListener} listener - Current listener being added. + * @returns {PIXI.ticker.Ticker} This instance of a ticker + */ + _addListener(listener) + { + // For attaching to head + let current = this._head.next; + let previous = this._head; + + // Add the first item + if (!current) + { + listener.connect(previous); + } + else + { + // Go from highest to lowest priority + while (current) + { + if (listener.priority >= current.priority) + { + listener.connect(previous); + break; + } + previous = current; + current = current.next; + } + + // Not yet connected + if (!listener.previous) + { + listener.connect(previous); + } + } this._startIfPossible(); @@ -229,19 +265,33 @@ } /** - * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. - * It checks if the emitter has listeners for 'tick' event. - * If it does, then it cancels the animation frame. + * Removes any handlers matching the function and context parameters. + * If no handlers are left after removing, then it cancels the animation frame. * - * @param {Function} [fn] - The listener function to be removed + * @param {Function} fn - The listener function to be removed * @param {Function} [context] - The listener context to be removed * @returns {PIXI.ticker.Ticker} This instance of a ticker */ remove(fn, context) { - this._emitter.off(TICK, fn, context); + let listener = this._head.next; - if (!this._emitter.listeners(TICK, true)) + while (listener) + { + // We found a match, lets remove it + // no break to delete all possible matches + // incase a listener was added 2+ times + if (listener.match(fn, context)) + { + listener = listener.destroy(); + } + else + { + listener = listener.next; + } + } + + if (!this._head.next) { this._cancelIfNeeded(); } @@ -276,6 +326,25 @@ } /** + * Destroy the ticker and don't use after this. Calling + * this method removes all references to internal events. + */ + destroy() + { + this.stop(); + + let listener = this._head.next; + + while (listener) + { + listener = listener.destroy(true); + } + + this._head.destroy(); + this._head = null; + } + + /** * Triggers an update. An update entails setting the * current {@link PIXI.ticker.Ticker#elapsedMS}, * the current {@link PIXI.ticker.Ticker#deltaTime}, @@ -320,8 +389,22 @@ this.deltaTime = elapsedMS * settings.TARGET_FPMS * this.speed; + // Cache a local reference, in-case ticker is destroyed + // during the emit, we can still check for head.next + const head = this._head; + // Invoke listeners added to internal emitter - this._emitter.emit(TICK, this.deltaTime); + let listener = head.next; + + while (listener) + { + listener = listener.emit(this.deltaTime); + } + + if (!head.next) + { + this._cancelIfNeeded(); + } } else { diff --git a/src/core/ticker/TickerListener.js b/src/core/ticker/TickerListener.js new file mode 100644 index 0000000..2bedb34 --- /dev/null +++ b/src/core/ticker/TickerListener.js @@ -0,0 +1,158 @@ +/** + * Internal class for handling the priority sorting of ticker handlers. + * + * @private + * @class + * @memberof PIXI.ticker + */ +export default class TickerListener +{ + /** + * Constructor + * + * @param {Function} fn - The listener function to be added for one update + * @param {Function} [context=null] - The listener context + * @param {number} [priority=0] - The priority for emitting + * @param {boolean} [once=false] - If the handler should fire once + */ + constructor(fn, context = null, priority = 0, once = false) + { + /** + * The handler function to execute. + * @member {Function} + */ + this.fn = fn; + + /** + * The calling to execute. + * @member {Function} + */ + this.context = context; + + /** + * The current priority. + * @member {number} + */ + this.priority = priority; + + /** + * If this should only execute once. + * @member {boolean} + */ + this.once = once; + + /** + * The next item in chain. + * @member {TickerListener} + */ + this.next = null; + + /** + * The previous item in chain. + * @member {TickerListener} + */ + this.previous = null; + + /** + * `true` if this listener has been destroyed already. + * @member {boolean} + * @private + */ + this._destroyed = false; + } + + /** + * Simple compare function to figure out if a function and context match. + * + * @param {Function} fn - The listener function to be added for one update + * @param {Function} context - The listener context + * @return {boolean} `true` if the listener match the arguments + */ + match(fn, context) + { + context = context || null; + + return this.fn === fn && this.context === context; + } + + /** + * Emit by calling the current function. + * @param {number} deltaTime - time since the last emit. + * @return {TickerListener} Next ticker + */ + emit(deltaTime) + { + if (this.context) + { + this.fn.call(this.context, deltaTime); + } + else + { + this.fn(deltaTime); + } + + if (this.once) + { + this.destroy(); + } + + const redirect = this.next; + + // Soft-destroying should remove + // the next reference + if (this._destroyed) + { + this.next = null; + } + + return redirect; + } + + /** + * Connect to the list. + * @param {TickerListener} previous - Input node, previous listener + */ + connect(previous) + { + this.previous = previous; + if (previous.next) + { + previous.next.previous = this; + } + this.next = previous.next; + previous.next = this; + } + + /** + * Destroy and don't use after this. + * @param {boolean} [hard = false] `true` to remove the `next` reference, this + * is considered a hard destroy. Soft destroy maintains the next reference. + * @return {TickerListener} The listener to redirect while emitting or removing. + */ + destroy(hard = false) + { + this._destroyed = true; + this.fn = null; + this.context = null; + + // Disconnect, hook up next and previous + if (this.previous) + { + this.previous.next = this.next; + } + + if (this.next) + { + this.next.previous = this.previous; + } + + // Redirect to the next item + const redirect = this.previous; + + // Remove references + this.next = hard ? null : redirect; + this.previous = null; + + return redirect; + } +} diff --git a/src/core/ticker/index.js b/src/core/ticker/index.js index 4b3017c..c3020d4 100644 --- a/src/core/ticker/index.js +++ b/src/core/ticker/index.js @@ -45,8 +45,25 @@ const shared = new Ticker(); shared.autoStart = true; +shared.destroy = () => +{ + // protect destroying shared ticker + // this is used by other internal systems + // like AnimatedSprite and InteractionManager +}; /** + * This namespace contains an API for interacting with PIXI's internal global update loop. + * + * This ticker is used for rendering, {@link PIXI.extras.AnimatedSprite AnimatedSprite}, + * {@link PIXI.interaction.InteractionManager InteractionManager} and many other time-based PIXI systems. + * @example + * const ticker = new PIXI.ticker.Ticker(); + * ticker.stop(); + * ticker.add((deltaTime) => { + * // do something every frame + * }); + * ticker.start(); * @namespace PIXI.ticker */ export { shared, Ticker }; diff --git a/src/core/utils/index.js b/src/core/utils/index.js index be44750..7441f7b 100644 --- a/src/core/utils/index.js +++ b/src/core/utils/index.js @@ -9,6 +9,21 @@ let saidHello = false; /** + * Generalized convenience utilities for PIXI. + * @example + * // Extend PIXI's internal Event Emitter. + * class MyEmitter extends PIXI.utils.EventEmitter { + * constructor() { + * super(); + * console.log("Emitter created!"); + * } + * } + * + * // Get info on current device + * console.log(PIXI.utils.isMobile); + * + * // Convert hex color to string + * console.log(PIXI.utils.hex2string(0xff00ff)); // returns: "#ff00ff" * @namespace PIXI.utils */ export { diff --git a/src/deprecation.js b/src/deprecation.js index b9c73b3..33daeec 100644 --- a/src/deprecation.js +++ b/src/deprecation.js @@ -918,6 +918,35 @@ }); /** + * @method + * @name PIXI.prepare.BasePrepare#register + * @see PIXI.prepare.BasePrepare#registerFindHook + * @deprecated since version 4.4.2 + * @param {Function} [addHook] - Function call that takes two parameters: `item:*, queue:Array` + * function must return `true` if it was able to add item to the queue. + * @param {Function} [uploadHook] - Function call that takes two parameters: `prepare:CanvasPrepare, item:*` and + * function must return `true` if it was able to handle upload of item. + * @return {PIXI.BasePrepare} Instance of plugin for chaining. + */ +prepare.BasePrepare.prototype.register = function register(addHook, uploadHook) +{ + warn('renderer.plugins.prepare.register is now deprecated, ' + + 'please use renderer.plugins.prepare.registerFindHook & renderer.plugins.prepare.registerUploadHook'); + + if (addHook) + { + this.registerFindHook(addHook); + } + + if (uploadHook) + { + this.registerUploadHook(uploadHook); + } + + return this; +}; + +/** * The number of graphics or textures to upload to the GPU. * * @name PIXI.prepare.canvas.UPLOADS_PER_FRAME @@ -969,67 +998,73 @@ }, }); -Object.defineProperties(loaders.Resource.prototype, { - isJson: { - get() - { - warn('The isJson property is deprecated, please use `resource.type === Resource.TYPE.JSON`.'); +if (loaders.Loader) +{ + const Resource = loaders.Resource; + const Loader = loaders.Loader; - return this.type === loaders.Loader.Resource.TYPE.JSON; + Object.defineProperties(Resource.prototype, { + isJson: { + get() + { + warn('The isJson property is deprecated, please use `resource.type === Resource.TYPE.JSON`.'); + + return this.type === Resource.TYPE.JSON; + }, }, - }, - isXml: { - get() - { - warn('The isXml property is deprecated, please use `resource.type === Resource.TYPE.XML`.'); + isXml: { + get() + { + warn('The isXml property is deprecated, please use `resource.type === Resource.TYPE.XML`.'); - return this.type === loaders.Loader.Resource.TYPE.XML; + return this.type === Resource.TYPE.XML; + }, }, - }, - isImage: { - get() - { - warn('The isImage property is deprecated, please use `resource.type === Resource.TYPE.IMAGE`.'); + isImage: { + get() + { + warn('The isImage property is deprecated, please use `resource.type === Resource.TYPE.IMAGE`.'); - return this.type === loaders.Loader.Resource.TYPE.IMAGE; + return this.type === Resource.TYPE.IMAGE; + }, }, - }, - isAudio: { - get() - { - warn('The isAudio property is deprecated, please use `resource.type === Resource.TYPE.AUDIO`.'); + isAudio: { + get() + { + warn('The isAudio property is deprecated, please use `resource.type === Resource.TYPE.AUDIO`.'); - return this.type === loaders.Loader.Resource.TYPE.AUDIO; + return this.type === Resource.TYPE.AUDIO; + }, }, - }, - isVideo: { - get() - { - warn('The isVideo property is deprecated, please use `resource.type === Resource.TYPE.VIDEO`.'); + isVideo: { + get() + { + warn('The isVideo property is deprecated, please use `resource.type === Resource.TYPE.VIDEO`.'); - return this.type === loaders.Loader.Resource.TYPE.VIDEO; + return this.type === Resource.TYPE.VIDEO; + }, }, - }, -}); + }); -Object.defineProperties(loaders.Loader.prototype, { - before: { - get() - { - warn('The before() method is deprecated, please use pre().'); + Object.defineProperties(Loader.prototype, { + before: { + get() + { + warn('The before() method is deprecated, please use pre().'); - return this.pre; + return this.pre; + }, }, - }, - after: { - get() - { - warn('The after() method is deprecated, please use use().'); + after: { + get() + { + warn('The after() method is deprecated, please use use().'); - return this.use; + return this.use; + }, }, - }, -}); + }); +} /** * @name PIXI.interaction.interactiveTarget#defaultCursor diff --git a/src/extract/canvas/CanvasExtract.js b/src/extract/canvas/CanvasExtract.js index 941ca05..3ac5aee 100644 --- a/src/extract/canvas/CanvasExtract.js +++ b/src/extract/canvas/CanvasExtract.js @@ -8,7 +8,7 @@ * An instance of this class is automatically created by default, and can be found at renderer.plugins.extract * * @class - * @memberof PIXI + * @memberof PIXI.extract */ export default class CanvasExtract { @@ -21,9 +21,9 @@ /** * Collection of methods for extracting data (image, pixels, etc.) from a display object or render texture * - * @member {PIXI.CanvasExtract} extract + * @member {PIXI.extract.CanvasExtract} extract * @memberof PIXI.CanvasRenderer# - * @see PIXI.CanvasExtract + * @see PIXI.extract.CanvasExtract */ renderer.extract = this; } diff --git a/src/extract/index.js b/src/extract/index.js index 0f2170d..e9478c1 100644 --- a/src/extract/index.js +++ b/src/extract/index.js @@ -1,2 +1,22 @@ +/** + * This namespace provides renderer-specific plugins for exporting content from a renderer. + * For instance, these plugins can be used for saving an Image, Canvas element or for exporting the raw image data (pixels). + * + * Do not instantiate these plugins directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. + * @example + * // Create a new app (will auto-add extract plugin to renderer) + * const app = new PIXI.Application(); + * + * // Draw a red circle + * const graphics = new PIXI.Graphics() + * .beginFill(0xFF0000) + * .drawCircle(0, 0, 50); + * + * // Render the graphics as an HTMLImageElement + * const image = app.renderer.plugins.extract.image(graphics); + * document.body.appendChild(image); + * @namespace PIXI.extract + */ export { default as webgl } from './webgl/WebGLExtract'; export { default as canvas } from './canvas/CanvasExtract'; diff --git a/src/extract/webgl/WebGLExtract.js b/src/extract/webgl/WebGLExtract.js index 60228bb..1c3dd30 100644 --- a/src/extract/webgl/WebGLExtract.js +++ b/src/extract/webgl/WebGLExtract.js @@ -9,7 +9,7 @@ * An instance of this class is automatically created by default, and can be found at renderer.plugins.extract * * @class - * @memberof PIXI + * @memberof PIXI.extract */ export default class WebGLExtract { @@ -22,9 +22,9 @@ /** * Collection of methods for extracting data (image, pixels, etc.) from a display object or render texture * - * @member {PIXI.WebGLExtract} extract + * @member {PIXI.extract.WebGLExtract} extract * @memberof PIXI.WebGLRenderer# - * @see PIXI.WebGLExtract + * @see PIXI.extract.WebGLExtract */ renderer.extract = this; } diff --git a/src/extras/AnimatedSprite.js b/src/extras/AnimatedSprite.js index 13b804f..5297778 100644 --- a/src/extras/AnimatedSprite.js +++ b/src/extras/AnimatedSprite.js @@ -137,7 +137,7 @@ this.playing = true; if (this._autoUpdate) { - core.ticker.shared.add(this.update, this); + core.ticker.shared.add(this.update, this, core.UPDATE_PRIORITY.HIGH); } } diff --git a/src/extras/TextureTransform.js b/src/extras/TextureTransform.js index 78e6e40..16536d2 100644 --- a/src/extras/TextureTransform.js +++ b/src/extras/TextureTransform.js @@ -65,8 +65,36 @@ } /** + * Multiplies uvs array to transform + * @param {Float32Array} uvs mesh uvs + * @param {Float32Array} [out=uvs] output + * @returns {Float32Array} output + */ + multiplyUvs(uvs, out) + { + if (out === undefined) + { + out = uvs; + } + + const mat = this.mapCoord; + + for (let i = 0; i < uvs.length; i += 2) + { + const x = uvs[i]; + const y = uvs[i + 1]; + + out[i] = (x * mat.a) + (y * mat.c) + mat.tx; + out[i + 1] = (x * mat.b) + (y * mat.d) + mat.ty; + } + + return out; + } + + /** * updates matrices if texture was changed * @param {boolean} forceUpdate if true, matrices will be updated any case + * @returns {boolean} whether or not it was updated */ update(forceUpdate) { @@ -74,13 +102,13 @@ if (!tex || !tex.valid) { - return; + return false; } if (!forceUpdate && this._lastTextureID === tex._updateID) { - return; + return false; } this._lastTextureID = tex._updateID; @@ -110,5 +138,7 @@ frame[3] = (tex._frame.y + tex._frame.height - margin + offset) / texBase.height; this.uClampOffset[0] = offset / texBase.realWidth; this.uClampOffset[1] = offset / texBase.realHeight; + + return true; } } diff --git a/src/extras/TilingSprite.js b/src/extras/TilingSprite.js index 1f6014f..a3f027f 100644 --- a/src/extras/TilingSprite.js +++ b/src/extras/TilingSprite.js @@ -180,9 +180,9 @@ const transform = this.worldTransform; const resolution = renderer.resolution; const baseTexture = texture.baseTexture; - const baseTextureResolution = texture.baseTexture.resolution; - const modX = (this.tilePosition.x / this.tileScale.x) % texture._frame.width; - const modY = (this.tilePosition.y / this.tileScale.y) % texture._frame.height; + const baseTextureResolution = baseTexture.resolution; + const modX = ((this.tilePosition.x / this.tileScale.x) % texture._frame.width) * baseTextureResolution; + const modY = ((this.tilePosition.y / this.tileScale.y) % texture._frame.height) * baseTextureResolution; // create a nice shiny pattern! // TODO this needs to be refreshed if texture changes.. diff --git a/src/extras/cacheAsBitmap.js b/src/extras/cacheAsBitmap.js index 5dd5640..aea06a8 100644 --- a/src/extras/cacheAsBitmap.js +++ b/src/extras/cacheAsBitmap.js @@ -227,7 +227,16 @@ this.transform._parentID = -1; // restore the transform of the cached sprite to avoid the nasty flicker.. - this.updateTransform(); + if (!this.parent) + { + this.parent = renderer._tempDisplayObjectParent; + this.updateTransform(); + this.parent = null; + } + else + { + this.updateTransform(); + } // map the hit test.. this.containsPoint = cachedSprite.containsPoint.bind(cachedSprite); @@ -314,7 +323,17 @@ cachedSprite._bounds = this._bounds; cachedSprite.alpha = cacheAlpha; - this.updateTransform(); + if (!this.parent) + { + this.parent = renderer._tempDisplayObjectParent; + this.updateTransform(); + this.parent = null; + } + else + { + this.updateTransform(); + } + this.updateTransform = this.displayObjectUpdateTransform; this._cacheData.sprite = cachedSprite; @@ -358,9 +377,12 @@ * Destroys the cached object. * * @private + * @param {object|boolean} [options] - Options parameter. A boolean will act as if all options + * have been set to that value. + * Used when destroying containers, see the Container.destroy method. */ -DisplayObject.prototype._cacheAsBitmapDestroy = function _cacheAsBitmapDestroy() +DisplayObject.prototype._cacheAsBitmapDestroy = function _cacheAsBitmapDestroy(options) { this.cacheAsBitmap = false; - this.destroy(); + this.destroy(options); }; diff --git a/src/extras/index.js b/src/extras/index.js index bbd8bbf..a34d7ec 100644 --- a/src/extras/index.js +++ b/src/extras/index.js @@ -1,7 +1,7 @@ /** + * Additional PIXI DisplayObjects for animation, tiling and bitmap text. * @namespace PIXI.extras */ -export { default as TextureTransform } from './TextureTransform'; export { default as AnimatedSprite } from './AnimatedSprite'; export { default as TilingSprite } from './TilingSprite'; export { default as TilingSpriteRenderer } from './webgl/TilingSpriteRenderer'; diff --git a/src/extras/webgl/TilingSpriteRenderer.js b/src/extras/webgl/TilingSpriteRenderer.js index aeec172..eee3b5c 100644 --- a/src/extras/webgl/TilingSpriteRenderer.js +++ b/src/extras/webgl/TilingSpriteRenderer.js @@ -11,7 +11,7 @@ * WebGL renderer plugin for tiling sprites * * @class - * @memberof PIXI + * @memberof PIXI.extras * @extends PIXI.ObjectRenderer */ export default class TilingSpriteRenderer extends core.ObjectRenderer @@ -118,7 +118,7 @@ tempMat.invert(); if (isSimple) { - tempMat.append(uv.mapCoord); + tempMat.prepend(uv.mapCoord); } else { diff --git a/src/filters/displacement/DisplacementFilter.js b/src/filters/displacement/DisplacementFilter.js index d5355c3..b5d030c 100644 --- a/src/filters/displacement/DisplacementFilter.js +++ b/src/filters/displacement/DisplacementFilter.js @@ -35,7 +35,7 @@ this.maskSprite = sprite; this.maskMatrix = maskMatrix; - this.uniforms.mapSampler = sprite.texture; + this.uniforms.mapSampler = sprite._texture; this.uniforms.filterMatrix = maskMatrix; this.uniforms.scale = { x: 1, y: 1 }; diff --git a/src/filters/index.js b/src/filters/index.js index f3e8862..7ceb50a 100644 --- a/src/filters/index.js +++ b/src/filters/index.js @@ -1,4 +1,21 @@ /** + * This namespace contains WebGL-only display filters that can be applied + * to DisplayObjects using the {@link PIXI.DisplayObject#filters filters} property. + * @example + * // Create a new application + * const app = new PIXI.Application(); + * + * // Draw a green rectangle + * const rect = new PIXI.Graphics() + * .beginFill(0x00ff00) + * .drawRect(40, 40, 200, 200); + * + * // Add a blur filter + * rect.filters = [new PIXI.filters.BlurFilter()]; + * + * // Display rectangle + * app.stage.addChild(rect); + * document.body.appendChild(app.view); * @namespace PIXI.filters */ export { default as FXAAFilter } from './fxaa/FXAAFilter'; diff --git a/src/index.js b/src/index.js index c553427..77da490 100644 --- a/src/index.js +++ b/src/index.js @@ -20,6 +20,14 @@ import { utils } from './core'; utils.mixins.performMixins(); +/** + * Alias for {@link PIXI.loaders.shared}. + * @name loader + * @memberof PIXI + * @type {PIXI.loader.Loader} + */ +const loader = loaders.shared || null; + export { accessibility, extract, @@ -30,18 +38,8 @@ mesh, particles, prepare, + loader, }; -/** - * A premade instance of the loader that can be used to load resources. - * - * @name loader - * @memberof PIXI - * @property {PIXI.loaders.Loader} - */ -const loader = loaders && loaders.Loader ? new loaders.Loader() : null; // check is there in case user excludes loader lib - -export { loader }; - // Always export pixi globally. global.PIXI = exports; // eslint-disable-line diff --git a/src/interaction/InteractionData.js b/src/interaction/InteractionData.js index 02d53a3..449f8cc 100644 --- a/src/interaction/InteractionData.js +++ b/src/interaction/InteractionData.js @@ -30,7 +30,10 @@ /** * When passed to an event handler, this will be the original DOM Event that was captured * - * @member {Event} + * @see https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent + * @see https://developer.mozilla.org/en-US/docs/Web/API/TouchEvent + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent + * @member {MouseEvent|TouchEvent|PointerEvent} */ this.originalEvent = null; } diff --git a/src/interaction/InteractionManager.js b/src/interaction/InteractionManager.js index 8baf13a..2d75c7c 100644 --- a/src/interaction/InteractionManager.js +++ b/src/interaction/InteractionManager.js @@ -13,6 +13,14 @@ const MOUSE_POINTER_ID = 'MOUSE'; +// helpers for hitTest() - only used inside hitTest() +const hitTestEvent = { + target: null, + data: { + global: null, + }, +}; + /** * The interaction manager deals with mouse, touch and pointer events. Any DisplayObject can be interactive * if its interactive parameter is set to true @@ -245,67 +253,60 @@ this.setTargetElement(this.renderer.view, this.renderer.resolution); /** - * Fired when a pointer device button (usually a mouse button) is pressed on the display + * Fired when a pointer device button (usually a mouse left-button) is pressed on the display * object. * - * @event mousedown - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mousedown + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device secondary button (usually a mouse right-button) is pressed * on the display object. * - * @event rightdown - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightdown + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** - * Fired when a pointer device button (usually a mouse button) is released over the display + * Fired when a pointer device button (usually a mouse left-button) is released over the display * object. * - * @event mouseup - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseup + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device secondary button (usually a mouse right-button) is released * over the display object. * - * @event rightup - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightup + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** - * Fired when a pointer device button (usually a mouse button) is pressed and released on + * Fired when a pointer device button (usually a mouse left-button) is pressed and released on * the display object. * - * @event click - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#click + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device secondary button (usually a mouse right-button) is pressed * and released on the display object. * - * @event rightclick - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightclick + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** - * Fired when a pointer device button (usually a mouse button) is released outside the + * Fired when a pointer device button (usually a mouse left-button) is released outside the * display object that initially registered a * [mousedown]{@link PIXI.interaction.InteractionManager#event:mousedown}. * - * @event mouseupoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** @@ -313,146 +314,362 @@ * outside the display object that initially registered a * [rightdown]{@link PIXI.interaction.InteractionManager#event:rightdown}. * - * @event rightupoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device (usually a mouse) is moved while over the display object * - * @event mousemove - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mousemove + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device (usually a mouse) is moved onto the display object * - * @event mouseover - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseover + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device (usually a mouse) is moved off the display object * - * @event mouseout - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseout + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is pressed on the display object. * - * @event pointerdown - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerdown + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is released over the display object. * - * @event pointerup - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerup + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when the operating system cancels a pointer event * - * @event pointercancel - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointercancel + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is pressed and released on the display object. * - * @event pointertap - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointertap + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is released outside the display object that initially * registered a [pointerdown]{@link PIXI.interaction.InteractionManager#event:pointerdown}. * - * @event pointerupoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device is moved while over the display object * - * @event pointermove - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointermove + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device is moved onto the display object * - * @event pointerover - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerover + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device is moved off the display object * - * @event pointerout - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerout + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is placed on the display object. * - * @event touchstart - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchstart + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is removed from the display object. * - * @event touchend - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchend + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when the operating system cancels a touch * - * @event touchcancel - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchcancel + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is placed and removed from the display object. * - * @event tap - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#tap + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is removed outside of the display object that initially * registered a [touchstart]{@link PIXI.interaction.InteractionManager#event:touchstart}. * - * @event touchendoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchendoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is moved along the display object. * - * @event touchmove - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchmove + * @param {PIXI.interaction.InteractionData} event - Interaction data */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is pressed on the display. + * object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mousedown + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is pressed + * on the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightdown + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is released over the display + * object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseup + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is released + * over the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightup + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is pressed and released on + * the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#click + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is pressed + * and released on the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightclick + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is released outside the + * display object that initially registered a + * [mousedown]{@link PIXI.DisplayObject#event:mousedown}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is released + * outside the display object that initially registered a + * [rightdown]{@link PIXI.DisplayObject#event:rightdown}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device (usually a mouse) is moved while over the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mousemove + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device (usually a mouse) is moved onto the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseover + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device (usually a mouse) is moved off the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseout + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is pressed on the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerdown + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is released over the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerup + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when the operating system cancels a pointer event. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointercancel + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is pressed and released on the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointertap + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is released outside the display object that initially + * registered a [pointerdown]{@link PIXI.DisplayObject#event:pointerdown}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device is moved while over the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointermove + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device is moved onto the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerover + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device is moved off the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerout + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is placed on the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchstart + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is removed from the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchend + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when the operating system cancels a touch. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchcancel + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is placed and removed from the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#tap + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is removed outside of the display object that initially + * registered a [touchstart]{@link PIXI.DisplayObject#event:touchstart}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchendoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is moved along the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchmove + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + } + + /** + * Hit tests a point against the display tree, returning the first interactive object that is hit. + * + * @param {PIXI.Point} globalPoint - A point to hit test with, in global space. + * @param {PIXI.Container} [root] - The root display object to start from. If omitted, defaults + * to the last rendered root of the associated renderer. + * @return {PIXI.DisplayObject} The hit display object, if any. + */ + hitTest(globalPoint, root) + { + // clear the target for our hit test + hitTestEvent.target = null; + // assign the global point + hitTestEvent.data.global = globalPoint; + // ensure safety of the root + if (!root) + { + root = this.renderer._lastObjectRendered; + } + // run the hit test + this.processInteractive(hitTestEvent, root, null, true); + // return our found object - it'll be null if we didn't hit anything + + return hitTestEvent.target; } /** @@ -487,7 +704,7 @@ return; } - core.ticker.shared.add(this.update, this); + core.ticker.shared.add(this.update, this, core.UPDATE_PRIORITY.INTERACTION); if (window.navigator.msPointerEnabled) { @@ -804,8 +1021,6 @@ } } - let keepHitTestingAfterChildren = hitTest; - // ** FREE TIP **! If an object is not interactive or has no buttons in it // (such as a game scene!) set interactiveChildren to false for that displayObject. // This will allow pixi to completely ignore and bypass checking the displayObjects children. @@ -818,7 +1033,9 @@ const child = children[i]; // time to get recursive.. if this function will return if something is hit.. - if (this.processInteractive(interactionEvent, child, func, hitTest, interactiveParent)) + const childHit = this.processInteractive(interactionEvent, child, func, hitTest, interactiveParent); + + if (childHit) { // its a good idea to check if a child has lost its parent. // this means it has been removed whilst looping so its best @@ -827,8 +1044,6 @@ continue; } - hit = true; - // we no longer need to hit test any more objects in this container as we we // now know the parent has been hit interactiveParent = false; @@ -838,36 +1053,41 @@ // This means we no longer need to hit test anything else. We still need to run // through all objects, but we don't need to perform any hit tests. - keepHitTestingAfterChildren = false; - - if (child.interactive) + if (childHit) { - hitTest = false; + if (interactionEvent.target) + { + hitTest = false; + } + hit = true; } - - // we can break now as we have hit an object. } } } - hitTest = keepHitTestingAfterChildren; - // no point running this if the item is not interactive or does not have an interactive parent. if (interactive) { // if we are hit testing (as in we have no hit any objects yet) // We also don't need to worry about hit testing if once of the displayObjects children - // has already been hit! - if (hitTest && !hit) + // has already been hit - but only if it was interactive, otherwise we need to keep + // looking for an interactive child, just in case we hit one + if (hitTest && !interactionEvent.target) { if (displayObject.hitArea) { displayObject.worldTransform.applyInverse(point, this._tempPoint); - hit = displayObject.hitArea.contains(this._tempPoint.x, this._tempPoint.y); + if (displayObject.hitArea.contains(this._tempPoint.x, this._tempPoint.y)) + { + hit = true; + } } else if (displayObject.containsPoint) { - hit = displayObject.containsPoint(point); + if (displayObject.containsPoint(point)) + { + hit = true; + } } } @@ -878,7 +1098,10 @@ interactionEvent.target = displayObject; } - func(interactionEvent, displayObject, hit); + if (func) + { + func(interactionEvent, displayObject, !!hit); + } } } @@ -914,7 +1137,7 @@ { const event = events[i]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -998,7 +1221,7 @@ { const event = events[i]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1098,7 +1321,7 @@ const test = isRightButton ? flags.RIGHT_DOWN : flags.LEFT_DOWN; - const isDown = trackingData !== undefined && (trackingData.flags | test); + const isDown = trackingData !== undefined && (trackingData.flags & test); if (hit) { @@ -1118,11 +1341,11 @@ { if (isRightButton) { - trackingData.rightDown = hit; + trackingData.rightDown = false; } else { - trackingData.leftDown = hit; + trackingData.leftDown = false; } } } @@ -1180,7 +1403,7 @@ { const event = events[i]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1255,7 +1478,7 @@ this.setCursorMode(null); } - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1268,6 +1491,12 @@ { this.emit('mouseout', interactionEvent); } + else + { + // we can get touchleave events after touchend, so we want to make sure we don't + // introduce memory leaks + this.releaseInteractionDataForPointerId(interactionData.identifier); + } } /** @@ -1344,7 +1573,7 @@ // Only mouse and pointer can call onPointerOver, so events will always be length 1 const event = events[0]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1366,12 +1595,14 @@ * Get InteractionData for a given pointerId. Store that data as well * * @private - * @param {number} pointerId - Identifier from a pointer event + * @param {PointerEvent} event - Normalized pointer event, output from normalizeToPointerData * @return {InteractionData} - Interaction data for the given pointer identifier */ - getInteractionDataForPointerId(pointerId) + getInteractionDataForPointerId(event) { - if (pointerId === MOUSE_POINTER_ID) + const pointerId = event.pointerId; + + if (pointerId === MOUSE_POINTER_ID || event.pointerType === 'mouse') { return this.mouse; } diff --git a/src/interaction/InteractionTrackingData.js b/src/interaction/InteractionTrackingData.js index 6a1f69e..8493b63 100644 --- a/src/interaction/InteractionTrackingData.js +++ b/src/interaction/InteractionTrackingData.js @@ -35,8 +35,10 @@ } /** + * Unique pointer id of the event + * * @readonly - * @type {number} Unique pointer id of the event + * @member {number} */ get pointerId() { diff --git a/src/interaction/index.js b/src/interaction/index.js index 56c5b09..72f7d71 100644 --- a/src/interaction/index.js +++ b/src/interaction/index.js @@ -1,4 +1,8 @@ /** + * This namespace contains a renderer plugin for handling mouse, pointer, and touch events. + * + * Do not instantiate this plugin directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. * @namespace PIXI.interaction */ export { default as InteractionData } from './InteractionData'; diff --git a/src/interaction/interactiveTarget.js b/src/interaction/interactiveTarget.js index d252fba..cebac40 100644 --- a/src/interaction/interactiveTarget.js +++ b/src/interaction/interactiveTarget.js @@ -2,7 +2,7 @@ * Default property values of interactive objects * Used by {@link PIXI.interaction.InteractionManager} to automatically give all DisplayObjects these properties * - * @mixin + * @private * @name interactiveTarget * @memberof PIXI.interaction * @example @@ -14,18 +14,28 @@ * ); */ export default { + /** - * Determines if the displayObject be clicked/touched + * Enable interaction events for the DisplayObject. Touch, pointer and mouse + * events will not be emitted unless `interactive` is set to `true`. * - * @inner {boolean} + * @example + * const sprite = new PIXI.Sprite(texture); + * sprite.interactive = true; + * sprite.on('tap', (event) => { + * //handle event + * }); + * @member {boolean} + * @memberof PIXI.DisplayObject# */ interactive: false, /** * Determines if the children to the displayObject can be clicked/touched - * Setting this to false allows pixi to bypass a recursive hitTest function + * Setting this to false allows pixi to bypass a recursive `hitTest` function * - * @inner {boolean} + * @member {boolean} + * @memberof PIXI.Container# */ interactiveChildren: true, @@ -33,7 +43,12 @@ * Interaction shape. Children will be hit first, then this shape will be checked. * Setting this will cause this shape to be checked in hit tests rather than the displayObject's bounds. * - * @inner {PIXI.Rectangle|PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.RoundedRectangle} + * @example + * const sprite = new PIXI.Sprite(texture); + * sprite.interactive = true; + * sprite.hitArea = new PIXI.Rectangle(0, 0, 100, 100); + * @member {PIXI.Rectangle|PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.RoundedRectangle} + * @memberof PIXI.DisplayObject# */ hitArea: null, @@ -41,8 +56,12 @@ * If enabled, the mouse cursor use the pointer behavior when hovered over the displayObject if it is interactive * Setting this changes the 'cursor' property to `'pointer'`. * + * @example + * const sprite = new PIXI.Sprite(texture); + * sprite.interactive = true; + * sprite.buttonMode = true; * @member {boolean} - * @memberof PIXI.interaction.interactiveTarget# + * @memberof PIXI.DisplayObject# */ get buttonMode() { @@ -64,9 +83,14 @@ * This defines what cursor mode is used when the mouse cursor * is hovered over the displayObject. * + * @example + * const sprite = new PIXI.Sprite(texture); + * sprite.interactive = true; + * sprite.cursor = 'wait'; * @see https://developer.mozilla.org/en/docs/Web/CSS/cursor * - * @inner {string} + * @member {string} + * @memberof PIXI.DisplayObject# */ cursor: null, @@ -74,7 +98,7 @@ * Internal set of all active pointers, by identifier * * @member {Map} - * @memberof PIXI.interaction.interactiveTarget# + * @memberof PIXI.DisplayObject# * @private */ get trackedPointers() @@ -87,7 +111,8 @@ /** * Map of all tracked pointers, by identifier. Use trackedPointers to access. * - * @private {Map} + * @private + * @type {Map} */ _trackedPointers: undefined, }; diff --git a/src/loaders/index.js b/src/loaders/index.js index 5189793..daa1fc4 100644 --- a/src/loaders/index.js +++ b/src/loaders/index.js @@ -1,9 +1,22 @@ +import Application from '../core/Application'; +import Loader from './loader'; + /** + * This namespace contains APIs which extends the {@link https://github.com/englercj/resource-loader resource-loader} module + * for loading assets, data, and other resources dynamically. + * @example + * const loader = new PIXI.loaders.Loader(); + * loader.add('bunny', 'data/bunny.png') + * .add('spaceship', 'assets/spritesheet.json'); + * loader.load((loader, resources) => { + * // resources.bunny + * // resources.spaceship + * }); * @namespace PIXI.loaders */ -export { default as Loader } from './loader'; +export { Loader }; export { default as bitmapFontParser, parse as parseBitmapFontData } from './bitmapFontParser'; -export { default as spritesheetParser } from './spritesheetParser'; +export { default as spritesheetParser, getResourcePath } from './spritesheetParser'; export { default as textureParser } from './textureParser'; /** @@ -13,3 +26,55 @@ * @memberof PIXI.loaders */ export { Resource } from 'resource-loader'; + +/** + * A premade instance of the loader that can be used to load resources. + * @name shared + * @memberof PIXI.loaders + * @type {PIXI.loaders.Loader} + */ +const shared = new Loader(); + +shared.destroy = () => +{ + // protect destroying shared loader +}; + +export { shared }; + +// Mixin the loader construction +const AppPrototype = Application.prototype; + +AppPrototype._loader = null; + +/** + * Loader instance to help with asset loading. + * @name PIXI.Application#loader + * @type {PIXI.loaders.Loader} + */ +Object.defineProperty(AppPrototype, 'loader', { + get() + { + if (!this._loader) + { + const sharedLoader = this._options.sharedLoader; + + this._loader = sharedLoader ? shared : new Loader(); + } + + return this._loader; + }, +}); + +// Override the destroy function +// making sure to destroy the current Loader +AppPrototype._parentDestroy = AppPrototype.destroy; +AppPrototype.destroy = function destroy() +{ + if (this._loader) + { + this._loader.destroy(); + this._loader = null; + } + this._parentDestroy(); +}; diff --git a/src/loaders/loader.js b/src/loaders/loader.js index 767a411..59b8000 100644 --- a/src/loaders/loader.js +++ b/src/loaders/loader.js @@ -90,6 +90,15 @@ { Loader._pixiMiddleware.push(fn); } + + /** + * Destroy the loader, removes references. + */ + destroy() + { + this.removeAllListeners(); + this.reset(); + } } // Copy EE3 prototype (mixin) diff --git a/src/loaders/spritesheetParser.js b/src/loaders/spritesheetParser.js index e57dfb2..eda9584 100644 --- a/src/loaders/spritesheetParser.js +++ b/src/loaders/spritesheetParser.js @@ -1,12 +1,11 @@ import { Resource } from 'resource-loader'; -import path from 'path'; +import url from 'url'; import { Spritesheet } from '../core'; export default function () { return function spritesheetParser(resource, next) { - let resourcePath; const imageResourceName = `${resource.name}_image`; // skip if no data, its not json, it isn't spritesheet data, or the image resource already exists @@ -28,15 +27,7 @@ parentResource: resource, }; - // Prepend url path unless the resource image is a data url - if (resource.isDataUrl) - { - resourcePath = resource.data.meta.image; - } - else - { - resourcePath = `${path.dirname(resource.url.replace(this.baseUrl, ''))}/${resource.data.meta.image}`; - } + const resourcePath = getResourcePath(resource, this.baseUrl); // load the image for this sheet this.add(imageResourceName, resourcePath, loadOptions, function onImageLoad(res) @@ -56,3 +47,14 @@ }); }; } + +export function getResourcePath(resource, baseUrl) +{ + // Prepend url path unless the resource image is a data url + if (resource.isDataUrl) + { + return resource.data.meta.image; + } + + return url.resolve(resource.url.replace(baseUrl, ''), resource.data.meta.image); +} diff --git a/src/mesh/canvas/CanvasMeshRenderer.js b/src/mesh/canvas/CanvasMeshRenderer.js index 297205f..f7899e1 100644 --- a/src/mesh/canvas/CanvasMeshRenderer.js +++ b/src/mesh/canvas/CanvasMeshRenderer.js @@ -134,12 +134,33 @@ const textureWidth = base.width; const textureHeight = base.height; - const u0 = uvs[index0] * base.width; - const u1 = uvs[index1] * base.width; - const u2 = uvs[index2] * base.width; - const v0 = uvs[index0 + 1] * base.height; - const v1 = uvs[index1 + 1] * base.height; - const v2 = uvs[index2 + 1] * base.height; + let u0; + let u1; + let u2; + let v0; + let v1; + let v2; + + if (mesh.uploadUvTransform) + { + const ut = mesh._uvTransform.mapCoord; + + u0 = ((uvs[index0] * ut.a) + (uvs[index0 + 1] * ut.c) + ut.tx) * base.width; + u1 = ((uvs[index1] * ut.a) + (uvs[index1 + 1] * ut.c) + ut.tx) * base.width; + u2 = ((uvs[index2] * ut.a) + (uvs[index2 + 1] * ut.c) + ut.tx) * base.width; + v0 = ((uvs[index0] * ut.b) + (uvs[index0 + 1] * ut.d) + ut.ty) * base.height; + v1 = ((uvs[index1] * ut.b) + (uvs[index1 + 1] * ut.d) + ut.ty) * base.height; + v2 = ((uvs[index2] * ut.b) + (uvs[index2 + 1] * ut.d) + ut.ty) * base.height; + } + else + { + u0 = uvs[index0] * base.width; + u1 = uvs[index1] * base.width; + u2 = uvs[index2] * base.width; + v0 = uvs[index0 + 1] * base.height; + v1 = uvs[index1 + 1] * base.height; + v2 = uvs[index2 + 1] * base.height; + } let x0 = vertices[index0]; let x1 = vertices[index1]; diff --git a/src/mesh/webgl/MeshRenderer.js b/src/mesh/webgl/MeshRenderer.js index 78d24c3..ab99fcd 100644 --- a/src/mesh/webgl/MeshRenderer.js +++ b/src/mesh/webgl/MeshRenderer.js @@ -2,6 +2,8 @@ const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; +const matrixIdentity = core.Matrix.IDENTITY; + /** * WebGL renderer plugin for tiling sprites * @@ -64,6 +66,7 @@ mesh.geometry.glVertexArrayObjects[this.CONTEXT_UID].draw(mesh.drawMode, mesh.size, mesh.start, mesh.geometry.instanceCount); } + /** * draws mesh * @param {PIXI.mesh.RawMesh} mesh mesh instance diff --git a/src/mesh/webgl/mesh.vert b/src/mesh/webgl/mesh.vert index a337aef..acc096c 100644 --- a/src/mesh/webgl/mesh.vert +++ b/src/mesh/webgl/mesh.vert @@ -1,8 +1,9 @@ attribute vec2 aVertexPosition; attribute vec2 aTextureCoord; -uniform mat3 translationMatrix; uniform mat3 projectionMatrix; +uniform mat3 translationMatrix; +uniform mat3 uTransform; varying vec2 vTextureCoord; @@ -10,5 +11,5 @@ { gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - vTextureCoord = aTextureCoord; + vTextureCoord = (uTransform * vec3(aTextureCoord, 1.0)).xy; } diff --git a/src/particles/ParticleContainer.js b/src/particles/ParticleContainer.js index 67796cf..63f7791 100644 --- a/src/particles/ParticleContainer.js +++ b/src/particles/ParticleContainer.js @@ -313,10 +313,10 @@ frame.y * resolution, frame.width * resolution, frame.height * resolution, - positionX * resolution, - positionY * resolution, - finalWidth * resolution, - finalHeight * resolution + positionX * renderer.resolution, + positionY * renderer.resolution, + finalWidth * renderer.resolution, + finalHeight * renderer.resolution ); } } diff --git a/src/prepare/BasePrepare.js b/src/prepare/BasePrepare.js index 09eb5e5..e45c4dc 100644 --- a/src/prepare/BasePrepare.js +++ b/src/prepare/BasePrepare.js @@ -18,6 +18,18 @@ * basic queuing functionality and is extended by {@link PIXI.prepare.WebGLPrepare} and {@link PIXI.prepare.CanvasPrepare} * to provide preparation capabilities specific to their respective renderers. * + * @example + * // Create a sprite + * const sprite = new PIXI.Sprite.fromImage('something.png'); + * + * // Load object into GPU + * app.renderer.plugins.prepare.upload(sprite, () => { + * + * //Texture(s) has been uploaded to GPU + * app.stage.addChild(sprite); + * + * }) + * * @abstract * @class * @memberof PIXI.prepare @@ -100,8 +112,16 @@ this.prepareItems(); }; - this.register(findText, drawText); - this.register(findTextStyle, calculateTextStyle); + // hooks to find the correct texture + this.registerFindHook(findText); + this.registerFindHook(findTextStyle); + this.registerFindHook(findMultipleBaseTextures); + this.registerFindHook(findBaseTexture); + this.registerFindHook(findTexture); + + // upload hooks + this.registerUploadHook(drawText); + this.registerUploadHook(calculateTextStyle); } /** @@ -138,7 +158,7 @@ if (!this.ticking) { this.ticking = true; - SharedTicker.addOnce(this.tick, this); + SharedTicker.addOnce(this.tick, this, core.UPDATE_PRIORITY.UTILITY); } } else if (done) @@ -172,13 +192,16 @@ const item = this.queue[0]; let uploaded = false; - for (let i = 0, len = this.uploadHooks.length; i < len; i++) + if (item && !item._destroyed) { - if (this.uploadHooks[i](this.uploadHookHelper, item)) + for (let i = 0, len = this.uploadHooks.length; i < len; i++) { - this.queue.shift(); - uploaded = true; - break; + if (this.uploadHooks[i](this.uploadHookHelper, item)) + { + this.queue.shift(); + uploaded = true; + break; + } } } @@ -205,26 +228,36 @@ else { // if we are not finished, on the next rAF do this again - SharedTicker.addOnce(this.tick, this); + SharedTicker.addOnce(this.tick, this, core.UPDATE_PRIORITY.UTILITY); } } /** - * Adds hooks for finding and uploading items. + * Adds hooks for finding items. * - * @param {Function} [addHook] - Function call that takes two parameters: `item:*, queue:Array` - function must return `true` if it was able to add item to the queue. - * @param {Function} [uploadHook] - Function call that takes two parameters: `prepare:CanvasPrepare, item:*` and - * function must return `true` if it was able to handle upload of item. - * @return {PIXI.CanvasPrepare} Instance of plugin for chaining. + * @param {Function} addHook - Function call that takes two parameters: `item:*, queue:Array` + * function must return `true` if it was able to add item to the queue. + * @return {PIXI.BasePrepare} Instance of plugin for chaining. */ - register(addHook, uploadHook) + registerFindHook(addHook) { if (addHook) { this.addHooks.push(addHook); } + return this; + } + + /** + * Adds hooks for uploading items. + * + * @param {Function} uploadHook - Function call that takes two parameters: `prepare:CanvasPrepare, item:*` and + * function must return `true` if it was able to handle upload of item. + * @return {PIXI.BasePrepare} Instance of plugin for chaining. + */ + registerUploadHook(uploadHook) + { if (uploadHook) { this.uploadHooks.push(uploadHook); @@ -287,6 +320,88 @@ } /** + * Built-in hook to find multiple textures from objects like AnimatedSprites. + * + * @private + * @param {PIXI.DisplayObject} item - Display object to check + * @param {Array<*>} queue - Collection of items to upload + * @return {boolean} if a PIXI.Texture object was found. + */ +function findMultipleBaseTextures(item, queue) +{ + let result = false; + + // Objects with mutliple textures + if (item && item._textures && item._textures.length) + { + for (let i = 0; i < item._textures.length; i++) + { + if (item._textures[i] instanceof core.Texture) + { + const baseTexture = item._textures[i].baseTexture; + + if (queue.indexOf(baseTexture) === -1) + { + queue.push(baseTexture); + result = true; + } + } + } + } + + return result; +} + +/** + * Built-in hook to find BaseTextures from Sprites. + * + * @private + * @param {PIXI.DisplayObject} item - Display object to check + * @param {Array<*>} queue - Collection of items to upload + * @return {boolean} if a PIXI.Texture object was found. + */ +function findBaseTexture(item, queue) +{ + // Objects with textures, like Sprites/Text + if (item instanceof core.BaseTexture) + { + if (queue.indexOf(item) === -1) + { + queue.push(item); + } + + return true; + } + + return false; +} + +/** + * Built-in hook to find textures from objects. + * + * @private + * @param {PIXI.DisplayObject} item - Display object to check + * @param {Array<*>} queue - Collection of items to upload + * @return {boolean} if a PIXI.Texture object was found. + */ +function findTexture(item, queue) +{ + if (item._texture && item._texture instanceof core.Texture) + { + const texture = item._texture.baseTexture; + + if (queue.indexOf(texture) === -1) + { + queue.push(texture); + } + + return true; + } + + return false; +} + +/** * Built-in hook to draw PIXI.Text to its texture. * * @private diff --git a/src/prepare/canvas/CanvasPrepare.js b/src/prepare/canvas/CanvasPrepare.js index 0224bb5..09c207a 100644 --- a/src/prepare/canvas/CanvasPrepare.js +++ b/src/prepare/canvas/CanvasPrepare.js @@ -43,7 +43,7 @@ this.ctx = this.canvas.getContext('2d'); // Add textures to upload - this.register(findBaseTextures, uploadBaseTextures); + this.registerUploadHook(uploadBaseTextures); } /** @@ -89,39 +89,4 @@ return false; } -/** - * Built-in hook to find textures from Sprites. - * - * @private - * @param {PIXI.DisplayObject} item -Display object to check - * @param {Array<*>} queue - Collection of items to upload - * @return {boolean} if a PIXI.Texture object was found. - */ -function findBaseTextures(item, queue) -{ - // Objects with textures, like Sprites/Text - if (item instanceof core.BaseTexture) - { - if (queue.indexOf(item) === -1) - { - queue.push(item); - } - - return true; - } - else if (item._texture && item._texture instanceof core.Texture) - { - const texture = item._texture.baseTexture; - - if (queue.indexOf(texture) === -1) - { - queue.push(texture); - } - - return true; - } - - return false; -} - core.CanvasRenderer.registerPlugin('prepare', CanvasPrepare); diff --git a/.travis.yml b/.travis.yml index 3df8b22..14de334 100644 --- a/.travis.yml +++ b/.travis.yml @@ -110,3 +110,43 @@ on: all_branches: true condition: $TRAVIS_TAG + # Deploy config for latest release + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: dist + upload-dir: "release" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: docs + upload-dir: "release/docs" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: coverage + upload-dir: "release/coverage" + on: + all_branches: true + condition: $TRAVIS_TAG \ No newline at end of file diff --git a/README.md b/README.md index d26fadd..3b85f85 100644 --- a/README.md +++ b/README.md @@ -36,22 +36,14 @@ - Wiki: Other misc tutorials and resources are [on the Wiki](https://github.com/pixijs/pixi.js/wiki/Resources). ### Community ### -- Forums: Check out the [forum] (http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow] (http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. +- Forums: Check out the [forum](http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow](http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. - Inspiration: Check out the [gallery](http://www.pixijs.com/gallery) to see some of the amazing things people have created! - Chat: You can join us on [Gitter](https://gitter.im/pixijs/pixi.js) To chat about Pixi. We also now have a Slack channel. If you would like to join it please Send me an email (mat@goodboydigital.com) and I will invite you in. ### Setup ### -It's easy to get started with Pixi.js! Simply grab the pre-built versions from here: - -Release Branch - Nice and stable Pixi.js -- Unminified: [http://pixijs.download/release/pixi.js] -- Minified: [http://pixijs.download/release/pixi.min.js] - -Develop Branch - The bleeding edge version of Pixi.js -- Unminified: [http://pixijs.download/dev/pixi.js] -- Minified: [http://pixijs.download/dev/pixi.min.js] +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. @@ -70,10 +62,10 @@ #### CDN Install (via cdnjs) ```html - + ``` -_Note: `4.2.2` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ +_Note: `4.4.3` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ ### Demos ### @@ -130,7 +122,7 @@ // and the root stage PIXI.Container. var app = new PIXI.Application(); -// The application will create a canvas element for you that you +// The application will create a canvas element for you that you // can then insert into the DOM. document.body.appendChild(app.view); @@ -150,7 +142,7 @@ // Add the bunny to the scene we are building. app.stage.addChild(bunny); - + // Listen for frame updates app.ticker.add(function() { // each frame we spin the bunny around a bit diff --git a/bower.json b/bower.json deleted file mode 100644 index 41b668c..0000000 --- a/bower.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "pixi.js", - "main": "dist/pixi.min.js", - "ignore": [ - "**/.*", - "docs", - "node_modules", - "bower_components", - "test", - "scripts", - "bower.json", - "inch.json" - ], - "dependencies": { - }, - "devDependencies": { - } -} diff --git a/package.json b/package.json index d35cbb2..308469f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.3.5", + "version": "4.4.4", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ @@ -40,11 +40,11 @@ "lib": "babel src --out-dir lib -s", "predocs": "rimraf docs/**", "docs": "jsdoc -c scripts/jsdoc.conf.json -R README.md", - "publish:patch": "npm version patch --no-git-tag-version && npm publish", - "publish:minor": "npm version minor --no-git-tag-version && npm publish", - "publish:major": "npm version major --no-git-tag-version && npm publish", + "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", - "postpublish": "node scripts/release.js" + "postpublish": "git push && git push --tags" }, "files": [ "dist/", @@ -72,7 +72,6 @@ "electron": "^1.4.15", "eslint": "^3.5.0", "floss": "^2.0.1", - "gh-pages": "^0.11.0", "jaguarjs-jsdoc": "^1.0.1", "js-md5": "^0.4.1", "jsdoc": "^3.4.2", diff --git a/scripts/jsdoc.conf.json b/scripts/jsdoc.conf.json index 553e61c..3f52180 100644 --- a/scripts/jsdoc.conf.json +++ b/scripts/jsdoc.conf.json @@ -43,7 +43,7 @@ }, "markdown" : { "parser" : "gfm", - "hardwrap" : true + "hardwrap" : false }, "opts": { "encoding" : "utf8", diff --git a/scripts/release.js b/scripts/release.js deleted file mode 100644 index 156620c..0000000 --- a/scripts/release.js +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env node -'use strict'; - -// Publish script to push releases of the bin files -// the normally are gitignored -const ghpages = require('gh-pages'); -const path = require('path'); -const packageInfo = require(path.join(__dirname, '..', 'package.json')); - -const options = { - src: [ - 'dist/**/*', - 'lib/**/*', - 'src/**/*', - 'scripts/*', - 'scripts/renders/*', - 'test/**/*', - '*.json', - '*.md', - 'LICENSE', - '.eslintrc', - '.editorconfig', - '.travis.yml', - '.babelrc', - ], - dotfiles: true, - branch: 'release', - message: packageInfo.version, - logger: console.log.bind(console), -}; - -ghpages.publish(process.cwd(), options, (err) => -{ - if (err) - { - console.log(err); - process.exit(1); - - return; - } - - process.exit(0); -}); diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index f16bcb1..220427a 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -21,7 +21,7 @@ const DIV_HOOK_ZINDEX = 2; /** - * The Accessibility manager reacreates the ability to tab and and have content read by screen + * The Accessibility manager recreates the ability to tab and and have content read by screen * readers. This is very important as it can possibly help people with disabilities access pixi * content. * diff --git a/src/accessibility/index.js b/src/accessibility/index.js index 568cfb1..0ae108f 100644 --- a/src/accessibility/index.js +++ b/src/accessibility/index.js @@ -1,4 +1,9 @@ /** + * This namespace contains a renderer plugin for interaction accessibility for end-users + * with physical impairments which require screen-renders, keyboard navigation, etc. + * + * Do not instantiate this plugin directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. * @namespace PIXI.accessibility */ export { default as accessibleTarget } from './accessibleTarget'; diff --git a/src/core/Application.js b/src/core/Application.js index 5340578..2fcc729 100644 --- a/src/core/Application.js +++ b/src/core/Application.js @@ -1,6 +1,8 @@ import { autoDetectRenderer } from './autoDetectRenderer'; import Container from './display/Container'; import { shared, Ticker } from './ticker'; +import settings from './settings'; +import { UPDATE_PRIORITY } from './const'; /** * Convenience class to create a new PIXI application. @@ -22,28 +24,52 @@ */ export default class Application { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experience unexplained flickering try setting this to true. - * @param {boolean} [useSharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedLoader=false] - `true` to use PIXI.loaders.shared, `false` to create new Loader. */ - constructor(width, height, options, noWebGL, useSharedTicker = false) + constructor(options, arg2, arg3, arg4, arg5) { + // Support for constructor(width, height, options, noWebGL, useSharedTicker) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + forceCanvas: !!arg4, + sharedTicker: !!arg5, + }, arg3); + } + + /** + * The default options, so we mixin functionality later. + * @member {object} + * @protected + */ + this._options = options = Object.assign({ + sharedTicker: false, + forceCanvas: false, + sharedLoader: false, + }, options); + /** * WebGL renderer if available, otherwise CanvasRenderer * @member {PIXI.WebGLRenderer|PIXI.CanvasRenderer} */ - this.renderer = autoDetectRenderer(width, height, options, noWebGL); + this.renderer = autoDetectRenderer(options); /** * The root display container that's rendered. @@ -63,7 +89,7 @@ * @member {PIXI.ticker.Ticker} * @default PIXI.ticker.shared */ - this.ticker = useSharedTicker ? shared : new Ticker(); + this.ticker = options.sharedTicker ? shared : new Ticker(); // Start the rendering this.start(); @@ -78,7 +104,7 @@ this._ticker = ticker; if (ticker) { - ticker.add(this.render, this); + ticker.add(this.render, this, UPDATE_PRIORITY.LOW); } } get ticker() // eslint-disable-line require-jsdoc @@ -136,13 +162,18 @@ */ destroy(removeView) { - this.stop(); + const oldTicker = this._ticker; + this.ticker = null; + oldTicker.destroy(); + this.stage.destroy(); this.stage = null; this.renderer.destroy(removeView); this.renderer = null; + + this._options = null; } } diff --git a/src/core/autoDetectRenderer.js b/src/core/autoDetectRenderer.js index 8960425..9ec0364 100644 --- a/src/core/autoDetectRenderer.js +++ b/src/core/autoDetectRenderer.js @@ -2,6 +2,7 @@ import CanvasRenderer from './renderers/canvas/CanvasRenderer'; import WebGLRenderer from './renderers/webgl/WebGLRenderer'; +// eslint-disable-next-line valid-jsdoc /** * This helper function will automatically detect which renderer you should be using. * WebGL is the preferred renderer as it is a lot faster. If webGL is not supported by @@ -9,24 +10,32 @@ * * @memberof PIXI * @function autoDetectRenderer - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @return {PIXI.WebGLRenderer|PIXI.CanvasRenderer} Returns WebGL renderer if available, otherwise CanvasRenderer */ -export function autoDetectRenderer(width = 800, height = 600, options, noWebGL) +export function autoDetectRenderer(options, arg1, arg2, arg3) { - if (!noWebGL && utils.isWebGLSupported()) + // Backward-compatible support for noWebGL option + let forceCanvas = options && options.forceCanvas; + + if (arg3 !== undefined) { - return new WebGLRenderer(width, height, options); + forceCanvas = arg3; } - return new CanvasRenderer(width, height, options); + if (!forceCanvas && utils.isWebGLSupported()) + { + return new WebGLRenderer(options, arg1, arg2); + } + + return new CanvasRenderer(options, arg1, arg2); } diff --git a/src/core/const.js b/src/core/const.js index b9e99ae..2c68dee 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -341,3 +341,27 @@ LINEAR_VERTICAL: 0, LINEAR_HORIZONTAL: 1, }; + +/** + * Represents the update priorities used by internal PIXI classes when registered with + * the {@link PIXI.ticker.Ticker} object. Higher priority items are updated first and lower + * priority items, such as render, should go later. + * + * @static + * @constant + * @name UPDATE_PRIORITY + * @memberof PIXI + * @type {object} + * @property {number} INTERACTION=50 Highest priority, used for {@link PIXI.interaction.InteractionManager} + * @property {number} HIGH=25 High priority updating, {@link PIXI.VideoBaseTexture} and {@link PIXI.extras.AnimatedSprite} + * @property {number} NORMAL=0 Default priority for ticker events, see {@link PIXI.ticker.Ticker#add}. + * @property {number} LOW=-25 Low priority used for {@link PIXI.Application} rendering. + * @property {number} UTILITY=-50 Lowest priority used for {@link PIXI.prepare.BasePrepare} utility. + */ +export const UPDATE_PRIORITY = { + INTERACTION: 50, + HIGH: 25, + NORMAL: 0, + LOW: -25, + UTILITY: -50, +}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 4e6c77d..decb42a 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -13,7 +13,6 @@ * * @class * @extends EventEmitter - * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ export default class DisplayObject extends EventEmitter @@ -113,6 +112,29 @@ * @private */ this._mask = null; + + /** + * If the object has been destroyed via destroy(). If true, it should not be used. + * + * @member {boolean} + * @private + * @readonly + */ + this._destroyed = false; + + /** + * Fired when this DisplayObject is added to a Container. + * + * @event PIXI.DisplayObject#added + * @param {PIXI.Container} container - The container added to. + */ + + /** + * Fired when this DisplayObject is removed from a Container. + * + * @event PIXI.DisplayObject#removed + * @param {PIXI.Container} container - The container removed from. + */ } /** @@ -403,6 +425,8 @@ this.interactive = false; this.interactiveChildren = false; + + this._destroyed = true; } /** diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index aafdfe2..f787c4a 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -792,8 +792,8 @@ sprite.worldAlpha = this.worldAlpha * sprite.alpha; sprite.blendMode = this.blendMode; - sprite.texture._frame.width = rect.width; - sprite.texture._frame.height = rect.height; + sprite._texture._frame.width = rect.width; + sprite._texture._frame.height = rect.height; sprite.transform.worldTransform = this.transform.worldTransform; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index a24c775..b9f192d 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -19,11 +19,12 @@ */ export default class SystemRenderer extends EventEmitter { + // eslint-disable-next-line valid-jsdoc /** * @param {string} system - The name of the system this renderer is for. - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -37,27 +38,31 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(system, screenWidth, screenHeight, options) + constructor(system, options, arg2, arg3) { super(); sayHello(system); - // prepare options - if (options) + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') { - for (const i in settings.RENDER_OPTIONS) - { - if (typeof options[i] === 'undefined') - { - options[i] = settings.RENDER_OPTIONS[i]; - } - } + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); } - else - { - options = settings.RENDER_OPTIONS; - } + + // Add the default render options + options = Object.assign({}, settings.RENDER_OPTIONS, options); + + /** + * The supplied constructor options. + * + * @member {Object} + * @readOnly + */ + this.options = options; /** * The type of the renderer. @@ -75,7 +80,7 @@ * * @member {PIXI.Rectangle} */ - this.screen = new Rectangle(0, 0, screenWidth || 800, screenHeight || 600); + this.screen = new Rectangle(0, 0, options.width, options.height); /** * The canvas element that everything is drawn to @@ -279,6 +284,8 @@ this.blendModes = null; + this.options = null; + this.preserveDrawingBuffer = false; this.clearBeforeRender = false; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index e84e49c..691caf0 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -17,10 +17,11 @@ */ export default class CanvasRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -34,9 +35,9 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('Canvas', screenWidth, screenHeight, options); + super('Canvas', options, arg2, arg3); this.type = RENDERER_TYPE.CANVAS; @@ -96,7 +97,19 @@ this.context = null; this.renderingToScreen = false; - this.resize(screenWidth, screenHeight); + this.resize(this.options.width, this.options.height); + + /** + * Fired after rendering finishes. + * + * @event PIXI.CanvasRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.CanvasRenderer#prerender + */ } /** @@ -299,4 +312,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.CanvasRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.CanvasExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.CanvasPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.CanvasRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index e1cf7cd..f6fe123 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -31,11 +31,12 @@ */ export default class WebGLRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** * - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -55,9 +56,9 @@ * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('WebGL', screenWidth, screenHeight, options); + super('WebGL', options, arg2, arg3); /** * The type of this renderer as a standardised const @@ -89,12 +90,6 @@ }; - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ this._backgroundColorRgba[3] = this.transparent ? 0 : 1; this.globalUniforms = new UniformGroup({ @@ -117,6 +112,13 @@ this.initPlugins(); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ if(options.context) { this.context.initFromContext(options.context); @@ -169,6 +171,25 @@ } return this; + + /** + * Fired after rendering finishes. + * + * @event PIXI.WebGLRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.WebGLRenderer#prerender + */ + + /** + * Fired when the WebGL context is set. + * + * @event PIXI.WebGLRenderer#context + * @param {WebGLRenderingContext} gl - WebGL context. + */ } /** @@ -295,4 +316,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.WebGLRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.WebGLRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(WebGLRenderer); diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index 4eb6128..fa3d883 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -47,6 +47,14 @@ * @member {boolean} */ this.enabled = true; + + /** + * If enabled, pixi will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; } /** diff --git a/src/core/renderers/webgl/systems/ContextSystem.js b/src/core/renderers/webgl/systems/ContextSystem.js index ff958c7..8df85ea 100644 --- a/src/core/renderers/webgl/systems/ContextSystem.js +++ b/src/core/renderers/webgl/systems/ContextSystem.js @@ -75,7 +75,8 @@ */ createContext(canvas, options) { - var gl = canvas.getContext('webgl', options) || + var gl = //canvas.getContext('webgl2', options) || + canvas.getContext('webgl', options) || canvas.getContext('experimental-webgl', options); if (!gl) diff --git a/src/core/renderers/webgl/systems/FilterSystem.js b/src/core/renderers/webgl/systems/FilterSystem.js index f47335d..37e28d2 100644 --- a/src/core/renderers/webgl/systems/FilterSystem.js +++ b/src/core/renderers/webgl/systems/FilterSystem.js @@ -111,7 +111,7 @@ // TODO we should fit the rect around the transform.. } - else + else if (filters[0].autoFit) { sourceFrame.fit(filterData.stack[0].destinationFrame); } diff --git a/src/core/renderers/webgl/systems/shader/shader/mapType.js b/src/core/renderers/webgl/systems/shader/shader/mapType.js index 581dace..917538a 100644 --- a/src/core/renderers/webgl/systems/shader/shader/mapType.js +++ b/src/core/renderers/webgl/systems/shader/shader/mapType.js @@ -15,7 +15,7 @@ } } - return GL_TABLE[type]; + return GL_TABLE[type]; }; var GL_TABLE = null; diff --git a/src/core/settings.js b/src/core/settings.js index 18c5877..38ef45c 100644 --- a/src/core/settings.js +++ b/src/core/settings.js @@ -2,6 +2,15 @@ import canUploadSameBuffer from './utils/canUploadSameBuffer'; /** + * User's customizable globals for overriding the default PIXI settings, such + * as a renderer's default resolution, framerate, float percision, etc. + * @example + * // Use the native window resolution as the default resolution + * // will support high-density displays when rendering + * PIXI.settings.RESOLUTION = window.devicePixelRatio. + * + * // Disable interpolation when scaling, will make texture be pixelated + * PIXI.settings.SCALE_MODE = PIXI.SCALE_MODES.NEAREST; * @namespace PIXI.settings */ export default { @@ -101,6 +110,9 @@ * @property {boolean} clearBeforeRender=true * @property {boolean} preserveDrawingBuffer=false * @property {boolean} roundPixels=false + * @property {number} width=800 + * @property {number} height=600 + * @property {boolean} legacy=false */ RENDER_OPTIONS: { view: null, @@ -112,6 +124,9 @@ clearBeforeRender: true, preserveDrawingBuffer: false, roundPixels: false, + width: 800, + height: 600, + legacy: false, }, /** diff --git a/src/core/shader/Program.js b/src/core/shader/Program.js index c0b538b..07b4398 100644 --- a/src/core/shader/Program.js +++ b/src/core/shader/Program.js @@ -1,6 +1,6 @@ import extractUniformsFromSrc from './extractUniformsFromSrc'; import generateUniformsSync from './generateUniformsSync'; -import glCore from 'pixi-gl-core'; +import shaderUtils from '../renderers/webgl/systems/shader/shader'; import { ProgramCache } from '../utils'; import getTestContext from '../utils/getTestContext'; @@ -64,14 +64,17 @@ } else { - vertexSrc = glCore.shader.setPrecision(vertexSrc, 'mediump'); - fragmentSrc = glCore.shader.setPrecision(fragmentSrc, 'mediump'); + vertexSrc = shaderUtils.setPrecision(vertexSrc, 'mediump'); + fragmentSrc = shaderUtils.setPrecision(fragmentSrc, 'mediump'); - const program = glCore.shader.compileProgram(gl, vertexSrc, fragmentSrc); + const program = shaderUtils.compileProgram(gl, vertexSrc, fragmentSrc); + console.log("<<<<<<>>><<"); this.attributeData = this.getAttributeData(program, gl); this.uniformData = this.getUniformData(program, gl); + console.log(this.uniformData); + //gl.deleteProgram(program); } } @@ -95,13 +98,13 @@ for (let i = 0; i < totalAttributes; i++) { const attribData = gl.getActiveAttrib(program, i); - const type = glCore.shader.mapType(gl, attribData.type); + const type = shaderUtils.mapType(gl, attribData.type); /*eslint-disable */ const data = { type: type, name: attribData.name, - size: glCore.shader.mapSize(type), + size: shaderUtils.mapSize(type), location: 0, }; /*eslint-enable */ @@ -137,13 +140,14 @@ // TODO expose this as a prop? // const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); - const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); + //const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); for (let i = 0; i < totalUniforms; i++) { const uniformData = gl.getActiveUniform(program, i); const name = uniformData.name.replace(/\[.*?\]/, ''); - const type = glCore.shader.mapType(gl, uniformData.type); + const type = shaderUtils.mapType(gl, uniformData.type); + console.log('mapping ' + uniformData.type + ' to ' + type); // if (!name.match(maskRegex)) { @@ -151,7 +155,7 @@ uniforms[name] = { type: type, size: uniformData.size, - value: glCore.shader.defaultValue(type, uniformData.size), + value: shaderUtils.defaultValue(type, uniformData.size), }; /*eslint-enable */ } diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index e387e99..b57d095 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -147,12 +147,12 @@ // so if _width is 0 then width was not set.. if (this._width) { - this.scale.x = sign(this.scale.x) * this._width / this.texture.orig.width; + this.scale.x = sign(this.scale.x) * this._width / this._texture.orig.width; } if (this._height) { - this.scale.y = sign(this.scale.y) * this._height / this.texture.orig.height; + this.scale.y = sign(this.scale.y) * this._height / this._texture.orig.height; } } diff --git a/src/core/sprites/canvas/CanvasTinter.js b/src/core/sprites/canvas/CanvasTinter.js index 27e5e0c..fd9de5d 100644 --- a/src/core/sprites/canvas/CanvasTinter.js +++ b/src/core/sprites/canvas/CanvasTinter.js @@ -18,7 +18,7 @@ */ getTintedTexture: (sprite, color) => { - const texture = sprite.texture; + const texture = sprite._texture; color = CanvasTinter.roundColor(color); diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 5a4edda..8b9b797 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -344,6 +344,7 @@ const texture = this._texture; const style = this._style; + const padding = style.trim ? 0 : style.padding; texture.baseTexture.valid = true; texture.baseTexture.resolution = this.resolution; @@ -353,11 +354,11 @@ texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - texture.trim.x = -style.padding; - texture.trim.y = -style.padding; + texture.trim.x = -padding; + texture.trim.y = -padding; - texture.orig.width = texture._frame.width - (style.padding * 2); - texture.orig.height = texture._frame.height - (style.padding * 2); + texture.orig.width = texture._frame.width - (padding * 2); + texture.orig.height = texture._frame.height - (padding * 2); // call sprite onTextureUpdate to update scale if _width or _height were set this._onTextureUpdate(); diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index 853da3d..8a9c34b 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -114,6 +114,38 @@ this.cacheId = null; this.validate(); + + /** + * Fired when a not-immediately-available source finishes loading. + * + * @protected + * @event PIXI.BaseTexture#loaded + * @param {PIXI.BaseTexture} baseTexture - Resource loaded. + */ + + /** + * Fired when a not-immediately-available source fails to load. + * + * @protected + * @event PIXI.BaseTexture#error + * @param {PIXI.BaseTexture} baseTexture - Resource errored. + */ + + /** + * Fired when BaseTexture is updated. + * + * @protected + * @event PIXI.BaseTexture#update + * @param {PIXI.BaseTexture} baseTexture - Instance of texture being updated. + */ + + /** + * Fired when BaseTexture is destroyed. + * + * @protected + * @event PIXI.BaseTexture#dispose + * @param {PIXI.BaseTexture} baseTexture - Instance of texture being destroyed. + */ } setResource(resource) diff --git a/src/core/textures/Spritesheet.js b/src/core/textures/Spritesheet.js index d469c62..9b24f27 100644 --- a/src/core/textures/Spritesheet.js +++ b/src/core/textures/Spritesheet.js @@ -208,6 +208,7 @@ // lets also add the frame to pixi's global cache for fromFrame and fromImage functions TextureCache[i] = this.textures[i]; + this.textures[i].textureCacheId = i; } frameIndex++; diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 0a2c379..2213087 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -147,9 +147,9 @@ /** * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. * - * @event update - * @memberof PIXI.Texture# + * @event PIXI.Texture#update * @protected + * @param {PIXI.Texture} texture - Instance of texture being updated. */ this._updateID = 0; @@ -159,6 +159,15 @@ * @type {Object} */ this.transform = null; + + /** + * The id under which this Texture has been added to the texture cache. This is + * automatically set in certain cases, but may not always be accurate, particularly if + * the texture is in the cache under multiple ids. + * + * @member {string} + */ + this.textureCacheId = null; } /** @@ -225,11 +234,17 @@ this._uvs = null; this.trim = null; this.orig = null; + this.textureCacheId = null; this.valid = false; - this.off('dispose', this.dispose, this); - this.off('update', this.update, this); + for (const prop in TextureCache) + { + if (TextureCache[prop] === this) + { + delete TextureCache[prop]; + } + } } /** @@ -315,14 +330,15 @@ */ static fromLoader(source, imageUrl, name) { - // console.log('added from loader...') + // console.log('added from loader...') const resource = new ImageResource(source);//.from(imageUrl, crossorigin);// document.createElement('img'); - // console.log('base resource ' + resource.width); + // console.log('base resource ' + resource.width); const baseTexture = new BaseTexture(resource, settings.SCALE_MODE, getResolutionOfUrl(imageUrl)); + /// console.log('base width ' + baseTexture.width); const texture = new Texture(baseTexture); @@ -335,6 +351,7 @@ // lets also add the frame to pixi's global cache for fromFrame and fromImage fucntions BaseTextureCache[name] = baseTexture; TextureCache[name] = texture; + texture.textureCacheId = name; // also add references by url if they are different. if (name !== imageUrl) @@ -355,6 +372,10 @@ */ static addTextureToCache(texture, id) { + if (!texture.textureCacheId) + { + texture.textureCacheId = id; + } TextureCache[id] = texture; } @@ -495,6 +516,7 @@ */ Texture.EMPTY = new Texture(new BaseTexture()); removeAllHandlers(Texture.EMPTY); +removeAllHandlers(Texture.EMPTY.baseTexture); /** * A white texture of 10x10 size, used for graphics and other things @@ -505,3 +527,4 @@ */ Texture.WHITE = createWhiteTexture(); removeAllHandlers(Texture.WHITE); +removeAllHandlers(Texture.WHITE.baseTexture); diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index 59eb9fd..a9b9f51 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -1,6 +1,7 @@ import BaseTexture from './BaseTexture'; import { uid, BaseTextureCache } from '../utils'; -import * as ticker from '../ticker'; +import { shared } from '../ticker'; +import { UPDATE_PRIORITY } from '../const'; /** * A texture of a [playing] Video. @@ -125,7 +126,7 @@ if (!this._isAutoUpdating && this.autoUpdate) { - ticker.shared.add(this.update, this); + shared.add(this.update, this, UPDATE_PRIORITY.HIGH); this._isAutoUpdating = true; } } @@ -139,7 +140,7 @@ { if (this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); this._isAutoUpdating = false; } } @@ -187,7 +188,7 @@ { if (this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); } if (this.source && this.source._pixiId) @@ -281,12 +282,12 @@ if (!this._autoUpdate && this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); this._isAutoUpdating = false; } else if (this._autoUpdate && !this._isAutoUpdating) { - ticker.shared.add(this.update, this); + shared.add(this.update, this, UPDATE_PRIORITY.HIGH); this._isAutoUpdating = true; } } diff --git a/src/core/ticker/Ticker.js b/src/core/ticker/Ticker.js index 17a1517..40df138 100644 --- a/src/core/ticker/Ticker.js +++ b/src/core/ticker/Ticker.js @@ -1,12 +1,10 @@ import settings from '../settings'; -import EventEmitter from 'eventemitter3'; - -// Internal event used by composed emitter -const TICK = 'tick'; +import { UPDATE_PRIORITY } from '../const'; +import TickerListener from './TickerListener'; /** * A Ticker class that runs an update loop that other objects listen to. - * This class is composed around an EventEmitter object to add listeners + * This class is composed around listeners * meant for execution on the next requested animation frame. * Animation frames are requested only when necessary, * e.g. When the ticker is started and the emitter has listeners. @@ -22,10 +20,11 @@ constructor() { /** - * Internal emitter used to fire 'tick' event + * The first listener. All new listeners added are chained on this. * @private + * @type {TickerListener} */ - this._emitter = new EventEmitter(); + this._head = new TickerListener(null, null, Infinity); /** * Internal current frame request ID @@ -131,7 +130,7 @@ // Invoke listeners now this.update(time); // Listener side effects may have modified ticker state. - if (this.started && this._requestId === null && this._emitter.listeners(TICK, true)) + if (this.started && this._requestId === null && this._head.next) { this._requestId = requestAnimationFrame(this._tick); } @@ -148,7 +147,7 @@ */ _requestIfNeeded() { - if (this._requestId === null && this._emitter.listeners(TICK, true)) + if (this._requestId === null && this._head.next) { // ensure callbacks get correct delta this.lastTime = performance.now(); @@ -193,35 +192,72 @@ } /** - * Calls {@link module:eventemitter3.EventEmitter#on} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. + * Register a handler for tick events. Calls continuously unless + * it is removed or the ticker is stopped. * * @param {Function} fn - The listener function to be added for updates * @param {Function} [context] - The listener context + * @param {number} [priority=PIXI.UPDATE_PRIORITY.NORMAL] - The priority for emitting * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - add(fn, context) + add(fn, context, priority = UPDATE_PRIORITY.NORMAL) { - this._emitter.on(TICK, fn, context); - - this._startIfPossible(); - - return this; + return this._addListener(new TickerListener(fn, context, priority)); } /** - * Calls {@link module:eventemitter3.EventEmitter#once} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. + * Add a handler for the tick event which is only execute once. * * @param {Function} fn - The listener function to be added for one update * @param {Function} [context] - The listener context + * @param {number} [priority=PIXI.UPDATE_PRIORITY.NORMAL] - The priority for emitting * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - addOnce(fn, context) + addOnce(fn, context, priority = UPDATE_PRIORITY.NORMAL) { - this._emitter.once(TICK, fn, context); + return this._addListener(new TickerListener(fn, context, priority, true)); + } + + /** + * Internally adds the event handler so that it can be sorted by priority. + * Priority allows certain handler (user, AnimatedSprite, Interaction) to be run + * before the rendering. + * + * @private + * @param {TickerListener} listener - Current listener being added. + * @returns {PIXI.ticker.Ticker} This instance of a ticker + */ + _addListener(listener) + { + // For attaching to head + let current = this._head.next; + let previous = this._head; + + // Add the first item + if (!current) + { + listener.connect(previous); + } + else + { + // Go from highest to lowest priority + while (current) + { + if (listener.priority >= current.priority) + { + listener.connect(previous); + break; + } + previous = current; + current = current.next; + } + + // Not yet connected + if (!listener.previous) + { + listener.connect(previous); + } + } this._startIfPossible(); @@ -229,19 +265,33 @@ } /** - * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. - * It checks if the emitter has listeners for 'tick' event. - * If it does, then it cancels the animation frame. + * Removes any handlers matching the function and context parameters. + * If no handlers are left after removing, then it cancels the animation frame. * - * @param {Function} [fn] - The listener function to be removed + * @param {Function} fn - The listener function to be removed * @param {Function} [context] - The listener context to be removed * @returns {PIXI.ticker.Ticker} This instance of a ticker */ remove(fn, context) { - this._emitter.off(TICK, fn, context); + let listener = this._head.next; - if (!this._emitter.listeners(TICK, true)) + while (listener) + { + // We found a match, lets remove it + // no break to delete all possible matches + // incase a listener was added 2+ times + if (listener.match(fn, context)) + { + listener = listener.destroy(); + } + else + { + listener = listener.next; + } + } + + if (!this._head.next) { this._cancelIfNeeded(); } @@ -276,6 +326,25 @@ } /** + * Destroy the ticker and don't use after this. Calling + * this method removes all references to internal events. + */ + destroy() + { + this.stop(); + + let listener = this._head.next; + + while (listener) + { + listener = listener.destroy(true); + } + + this._head.destroy(); + this._head = null; + } + + /** * Triggers an update. An update entails setting the * current {@link PIXI.ticker.Ticker#elapsedMS}, * the current {@link PIXI.ticker.Ticker#deltaTime}, @@ -320,8 +389,22 @@ this.deltaTime = elapsedMS * settings.TARGET_FPMS * this.speed; + // Cache a local reference, in-case ticker is destroyed + // during the emit, we can still check for head.next + const head = this._head; + // Invoke listeners added to internal emitter - this._emitter.emit(TICK, this.deltaTime); + let listener = head.next; + + while (listener) + { + listener = listener.emit(this.deltaTime); + } + + if (!head.next) + { + this._cancelIfNeeded(); + } } else { diff --git a/src/core/ticker/TickerListener.js b/src/core/ticker/TickerListener.js new file mode 100644 index 0000000..2bedb34 --- /dev/null +++ b/src/core/ticker/TickerListener.js @@ -0,0 +1,158 @@ +/** + * Internal class for handling the priority sorting of ticker handlers. + * + * @private + * @class + * @memberof PIXI.ticker + */ +export default class TickerListener +{ + /** + * Constructor + * + * @param {Function} fn - The listener function to be added for one update + * @param {Function} [context=null] - The listener context + * @param {number} [priority=0] - The priority for emitting + * @param {boolean} [once=false] - If the handler should fire once + */ + constructor(fn, context = null, priority = 0, once = false) + { + /** + * The handler function to execute. + * @member {Function} + */ + this.fn = fn; + + /** + * The calling to execute. + * @member {Function} + */ + this.context = context; + + /** + * The current priority. + * @member {number} + */ + this.priority = priority; + + /** + * If this should only execute once. + * @member {boolean} + */ + this.once = once; + + /** + * The next item in chain. + * @member {TickerListener} + */ + this.next = null; + + /** + * The previous item in chain. + * @member {TickerListener} + */ + this.previous = null; + + /** + * `true` if this listener has been destroyed already. + * @member {boolean} + * @private + */ + this._destroyed = false; + } + + /** + * Simple compare function to figure out if a function and context match. + * + * @param {Function} fn - The listener function to be added for one update + * @param {Function} context - The listener context + * @return {boolean} `true` if the listener match the arguments + */ + match(fn, context) + { + context = context || null; + + return this.fn === fn && this.context === context; + } + + /** + * Emit by calling the current function. + * @param {number} deltaTime - time since the last emit. + * @return {TickerListener} Next ticker + */ + emit(deltaTime) + { + if (this.context) + { + this.fn.call(this.context, deltaTime); + } + else + { + this.fn(deltaTime); + } + + if (this.once) + { + this.destroy(); + } + + const redirect = this.next; + + // Soft-destroying should remove + // the next reference + if (this._destroyed) + { + this.next = null; + } + + return redirect; + } + + /** + * Connect to the list. + * @param {TickerListener} previous - Input node, previous listener + */ + connect(previous) + { + this.previous = previous; + if (previous.next) + { + previous.next.previous = this; + } + this.next = previous.next; + previous.next = this; + } + + /** + * Destroy and don't use after this. + * @param {boolean} [hard = false] `true` to remove the `next` reference, this + * is considered a hard destroy. Soft destroy maintains the next reference. + * @return {TickerListener} The listener to redirect while emitting or removing. + */ + destroy(hard = false) + { + this._destroyed = true; + this.fn = null; + this.context = null; + + // Disconnect, hook up next and previous + if (this.previous) + { + this.previous.next = this.next; + } + + if (this.next) + { + this.next.previous = this.previous; + } + + // Redirect to the next item + const redirect = this.previous; + + // Remove references + this.next = hard ? null : redirect; + this.previous = null; + + return redirect; + } +} diff --git a/src/core/ticker/index.js b/src/core/ticker/index.js index 4b3017c..c3020d4 100644 --- a/src/core/ticker/index.js +++ b/src/core/ticker/index.js @@ -45,8 +45,25 @@ const shared = new Ticker(); shared.autoStart = true; +shared.destroy = () => +{ + // protect destroying shared ticker + // this is used by other internal systems + // like AnimatedSprite and InteractionManager +}; /** + * This namespace contains an API for interacting with PIXI's internal global update loop. + * + * This ticker is used for rendering, {@link PIXI.extras.AnimatedSprite AnimatedSprite}, + * {@link PIXI.interaction.InteractionManager InteractionManager} and many other time-based PIXI systems. + * @example + * const ticker = new PIXI.ticker.Ticker(); + * ticker.stop(); + * ticker.add((deltaTime) => { + * // do something every frame + * }); + * ticker.start(); * @namespace PIXI.ticker */ export { shared, Ticker }; diff --git a/src/core/utils/index.js b/src/core/utils/index.js index be44750..7441f7b 100644 --- a/src/core/utils/index.js +++ b/src/core/utils/index.js @@ -9,6 +9,21 @@ let saidHello = false; /** + * Generalized convenience utilities for PIXI. + * @example + * // Extend PIXI's internal Event Emitter. + * class MyEmitter extends PIXI.utils.EventEmitter { + * constructor() { + * super(); + * console.log("Emitter created!"); + * } + * } + * + * // Get info on current device + * console.log(PIXI.utils.isMobile); + * + * // Convert hex color to string + * console.log(PIXI.utils.hex2string(0xff00ff)); // returns: "#ff00ff" * @namespace PIXI.utils */ export { diff --git a/src/deprecation.js b/src/deprecation.js index b9c73b3..33daeec 100644 --- a/src/deprecation.js +++ b/src/deprecation.js @@ -918,6 +918,35 @@ }); /** + * @method + * @name PIXI.prepare.BasePrepare#register + * @see PIXI.prepare.BasePrepare#registerFindHook + * @deprecated since version 4.4.2 + * @param {Function} [addHook] - Function call that takes two parameters: `item:*, queue:Array` + * function must return `true` if it was able to add item to the queue. + * @param {Function} [uploadHook] - Function call that takes two parameters: `prepare:CanvasPrepare, item:*` and + * function must return `true` if it was able to handle upload of item. + * @return {PIXI.BasePrepare} Instance of plugin for chaining. + */ +prepare.BasePrepare.prototype.register = function register(addHook, uploadHook) +{ + warn('renderer.plugins.prepare.register is now deprecated, ' + + 'please use renderer.plugins.prepare.registerFindHook & renderer.plugins.prepare.registerUploadHook'); + + if (addHook) + { + this.registerFindHook(addHook); + } + + if (uploadHook) + { + this.registerUploadHook(uploadHook); + } + + return this; +}; + +/** * The number of graphics or textures to upload to the GPU. * * @name PIXI.prepare.canvas.UPLOADS_PER_FRAME @@ -969,67 +998,73 @@ }, }); -Object.defineProperties(loaders.Resource.prototype, { - isJson: { - get() - { - warn('The isJson property is deprecated, please use `resource.type === Resource.TYPE.JSON`.'); +if (loaders.Loader) +{ + const Resource = loaders.Resource; + const Loader = loaders.Loader; - return this.type === loaders.Loader.Resource.TYPE.JSON; + Object.defineProperties(Resource.prototype, { + isJson: { + get() + { + warn('The isJson property is deprecated, please use `resource.type === Resource.TYPE.JSON`.'); + + return this.type === Resource.TYPE.JSON; + }, }, - }, - isXml: { - get() - { - warn('The isXml property is deprecated, please use `resource.type === Resource.TYPE.XML`.'); + isXml: { + get() + { + warn('The isXml property is deprecated, please use `resource.type === Resource.TYPE.XML`.'); - return this.type === loaders.Loader.Resource.TYPE.XML; + return this.type === Resource.TYPE.XML; + }, }, - }, - isImage: { - get() - { - warn('The isImage property is deprecated, please use `resource.type === Resource.TYPE.IMAGE`.'); + isImage: { + get() + { + warn('The isImage property is deprecated, please use `resource.type === Resource.TYPE.IMAGE`.'); - return this.type === loaders.Loader.Resource.TYPE.IMAGE; + return this.type === Resource.TYPE.IMAGE; + }, }, - }, - isAudio: { - get() - { - warn('The isAudio property is deprecated, please use `resource.type === Resource.TYPE.AUDIO`.'); + isAudio: { + get() + { + warn('The isAudio property is deprecated, please use `resource.type === Resource.TYPE.AUDIO`.'); - return this.type === loaders.Loader.Resource.TYPE.AUDIO; + return this.type === Resource.TYPE.AUDIO; + }, }, - }, - isVideo: { - get() - { - warn('The isVideo property is deprecated, please use `resource.type === Resource.TYPE.VIDEO`.'); + isVideo: { + get() + { + warn('The isVideo property is deprecated, please use `resource.type === Resource.TYPE.VIDEO`.'); - return this.type === loaders.Loader.Resource.TYPE.VIDEO; + return this.type === Resource.TYPE.VIDEO; + }, }, - }, -}); + }); -Object.defineProperties(loaders.Loader.prototype, { - before: { - get() - { - warn('The before() method is deprecated, please use pre().'); + Object.defineProperties(Loader.prototype, { + before: { + get() + { + warn('The before() method is deprecated, please use pre().'); - return this.pre; + return this.pre; + }, }, - }, - after: { - get() - { - warn('The after() method is deprecated, please use use().'); + after: { + get() + { + warn('The after() method is deprecated, please use use().'); - return this.use; + return this.use; + }, }, - }, -}); + }); +} /** * @name PIXI.interaction.interactiveTarget#defaultCursor diff --git a/src/extract/canvas/CanvasExtract.js b/src/extract/canvas/CanvasExtract.js index 941ca05..3ac5aee 100644 --- a/src/extract/canvas/CanvasExtract.js +++ b/src/extract/canvas/CanvasExtract.js @@ -8,7 +8,7 @@ * An instance of this class is automatically created by default, and can be found at renderer.plugins.extract * * @class - * @memberof PIXI + * @memberof PIXI.extract */ export default class CanvasExtract { @@ -21,9 +21,9 @@ /** * Collection of methods for extracting data (image, pixels, etc.) from a display object or render texture * - * @member {PIXI.CanvasExtract} extract + * @member {PIXI.extract.CanvasExtract} extract * @memberof PIXI.CanvasRenderer# - * @see PIXI.CanvasExtract + * @see PIXI.extract.CanvasExtract */ renderer.extract = this; } diff --git a/src/extract/index.js b/src/extract/index.js index 0f2170d..e9478c1 100644 --- a/src/extract/index.js +++ b/src/extract/index.js @@ -1,2 +1,22 @@ +/** + * This namespace provides renderer-specific plugins for exporting content from a renderer. + * For instance, these plugins can be used for saving an Image, Canvas element or for exporting the raw image data (pixels). + * + * Do not instantiate these plugins directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. + * @example + * // Create a new app (will auto-add extract plugin to renderer) + * const app = new PIXI.Application(); + * + * // Draw a red circle + * const graphics = new PIXI.Graphics() + * .beginFill(0xFF0000) + * .drawCircle(0, 0, 50); + * + * // Render the graphics as an HTMLImageElement + * const image = app.renderer.plugins.extract.image(graphics); + * document.body.appendChild(image); + * @namespace PIXI.extract + */ export { default as webgl } from './webgl/WebGLExtract'; export { default as canvas } from './canvas/CanvasExtract'; diff --git a/src/extract/webgl/WebGLExtract.js b/src/extract/webgl/WebGLExtract.js index 60228bb..1c3dd30 100644 --- a/src/extract/webgl/WebGLExtract.js +++ b/src/extract/webgl/WebGLExtract.js @@ -9,7 +9,7 @@ * An instance of this class is automatically created by default, and can be found at renderer.plugins.extract * * @class - * @memberof PIXI + * @memberof PIXI.extract */ export default class WebGLExtract { @@ -22,9 +22,9 @@ /** * Collection of methods for extracting data (image, pixels, etc.) from a display object or render texture * - * @member {PIXI.WebGLExtract} extract + * @member {PIXI.extract.WebGLExtract} extract * @memberof PIXI.WebGLRenderer# - * @see PIXI.WebGLExtract + * @see PIXI.extract.WebGLExtract */ renderer.extract = this; } diff --git a/src/extras/AnimatedSprite.js b/src/extras/AnimatedSprite.js index 13b804f..5297778 100644 --- a/src/extras/AnimatedSprite.js +++ b/src/extras/AnimatedSprite.js @@ -137,7 +137,7 @@ this.playing = true; if (this._autoUpdate) { - core.ticker.shared.add(this.update, this); + core.ticker.shared.add(this.update, this, core.UPDATE_PRIORITY.HIGH); } } diff --git a/src/extras/TextureTransform.js b/src/extras/TextureTransform.js index 78e6e40..16536d2 100644 --- a/src/extras/TextureTransform.js +++ b/src/extras/TextureTransform.js @@ -65,8 +65,36 @@ } /** + * Multiplies uvs array to transform + * @param {Float32Array} uvs mesh uvs + * @param {Float32Array} [out=uvs] output + * @returns {Float32Array} output + */ + multiplyUvs(uvs, out) + { + if (out === undefined) + { + out = uvs; + } + + const mat = this.mapCoord; + + for (let i = 0; i < uvs.length; i += 2) + { + const x = uvs[i]; + const y = uvs[i + 1]; + + out[i] = (x * mat.a) + (y * mat.c) + mat.tx; + out[i + 1] = (x * mat.b) + (y * mat.d) + mat.ty; + } + + return out; + } + + /** * updates matrices if texture was changed * @param {boolean} forceUpdate if true, matrices will be updated any case + * @returns {boolean} whether or not it was updated */ update(forceUpdate) { @@ -74,13 +102,13 @@ if (!tex || !tex.valid) { - return; + return false; } if (!forceUpdate && this._lastTextureID === tex._updateID) { - return; + return false; } this._lastTextureID = tex._updateID; @@ -110,5 +138,7 @@ frame[3] = (tex._frame.y + tex._frame.height - margin + offset) / texBase.height; this.uClampOffset[0] = offset / texBase.realWidth; this.uClampOffset[1] = offset / texBase.realHeight; + + return true; } } diff --git a/src/extras/TilingSprite.js b/src/extras/TilingSprite.js index 1f6014f..a3f027f 100644 --- a/src/extras/TilingSprite.js +++ b/src/extras/TilingSprite.js @@ -180,9 +180,9 @@ const transform = this.worldTransform; const resolution = renderer.resolution; const baseTexture = texture.baseTexture; - const baseTextureResolution = texture.baseTexture.resolution; - const modX = (this.tilePosition.x / this.tileScale.x) % texture._frame.width; - const modY = (this.tilePosition.y / this.tileScale.y) % texture._frame.height; + const baseTextureResolution = baseTexture.resolution; + const modX = ((this.tilePosition.x / this.tileScale.x) % texture._frame.width) * baseTextureResolution; + const modY = ((this.tilePosition.y / this.tileScale.y) % texture._frame.height) * baseTextureResolution; // create a nice shiny pattern! // TODO this needs to be refreshed if texture changes.. diff --git a/src/extras/cacheAsBitmap.js b/src/extras/cacheAsBitmap.js index 5dd5640..aea06a8 100644 --- a/src/extras/cacheAsBitmap.js +++ b/src/extras/cacheAsBitmap.js @@ -227,7 +227,16 @@ this.transform._parentID = -1; // restore the transform of the cached sprite to avoid the nasty flicker.. - this.updateTransform(); + if (!this.parent) + { + this.parent = renderer._tempDisplayObjectParent; + this.updateTransform(); + this.parent = null; + } + else + { + this.updateTransform(); + } // map the hit test.. this.containsPoint = cachedSprite.containsPoint.bind(cachedSprite); @@ -314,7 +323,17 @@ cachedSprite._bounds = this._bounds; cachedSprite.alpha = cacheAlpha; - this.updateTransform(); + if (!this.parent) + { + this.parent = renderer._tempDisplayObjectParent; + this.updateTransform(); + this.parent = null; + } + else + { + this.updateTransform(); + } + this.updateTransform = this.displayObjectUpdateTransform; this._cacheData.sprite = cachedSprite; @@ -358,9 +377,12 @@ * Destroys the cached object. * * @private + * @param {object|boolean} [options] - Options parameter. A boolean will act as if all options + * have been set to that value. + * Used when destroying containers, see the Container.destroy method. */ -DisplayObject.prototype._cacheAsBitmapDestroy = function _cacheAsBitmapDestroy() +DisplayObject.prototype._cacheAsBitmapDestroy = function _cacheAsBitmapDestroy(options) { this.cacheAsBitmap = false; - this.destroy(); + this.destroy(options); }; diff --git a/src/extras/index.js b/src/extras/index.js index bbd8bbf..a34d7ec 100644 --- a/src/extras/index.js +++ b/src/extras/index.js @@ -1,7 +1,7 @@ /** + * Additional PIXI DisplayObjects for animation, tiling and bitmap text. * @namespace PIXI.extras */ -export { default as TextureTransform } from './TextureTransform'; export { default as AnimatedSprite } from './AnimatedSprite'; export { default as TilingSprite } from './TilingSprite'; export { default as TilingSpriteRenderer } from './webgl/TilingSpriteRenderer'; diff --git a/src/extras/webgl/TilingSpriteRenderer.js b/src/extras/webgl/TilingSpriteRenderer.js index aeec172..eee3b5c 100644 --- a/src/extras/webgl/TilingSpriteRenderer.js +++ b/src/extras/webgl/TilingSpriteRenderer.js @@ -11,7 +11,7 @@ * WebGL renderer plugin for tiling sprites * * @class - * @memberof PIXI + * @memberof PIXI.extras * @extends PIXI.ObjectRenderer */ export default class TilingSpriteRenderer extends core.ObjectRenderer @@ -118,7 +118,7 @@ tempMat.invert(); if (isSimple) { - tempMat.append(uv.mapCoord); + tempMat.prepend(uv.mapCoord); } else { diff --git a/src/filters/displacement/DisplacementFilter.js b/src/filters/displacement/DisplacementFilter.js index d5355c3..b5d030c 100644 --- a/src/filters/displacement/DisplacementFilter.js +++ b/src/filters/displacement/DisplacementFilter.js @@ -35,7 +35,7 @@ this.maskSprite = sprite; this.maskMatrix = maskMatrix; - this.uniforms.mapSampler = sprite.texture; + this.uniforms.mapSampler = sprite._texture; this.uniforms.filterMatrix = maskMatrix; this.uniforms.scale = { x: 1, y: 1 }; diff --git a/src/filters/index.js b/src/filters/index.js index f3e8862..7ceb50a 100644 --- a/src/filters/index.js +++ b/src/filters/index.js @@ -1,4 +1,21 @@ /** + * This namespace contains WebGL-only display filters that can be applied + * to DisplayObjects using the {@link PIXI.DisplayObject#filters filters} property. + * @example + * // Create a new application + * const app = new PIXI.Application(); + * + * // Draw a green rectangle + * const rect = new PIXI.Graphics() + * .beginFill(0x00ff00) + * .drawRect(40, 40, 200, 200); + * + * // Add a blur filter + * rect.filters = [new PIXI.filters.BlurFilter()]; + * + * // Display rectangle + * app.stage.addChild(rect); + * document.body.appendChild(app.view); * @namespace PIXI.filters */ export { default as FXAAFilter } from './fxaa/FXAAFilter'; diff --git a/src/index.js b/src/index.js index c553427..77da490 100644 --- a/src/index.js +++ b/src/index.js @@ -20,6 +20,14 @@ import { utils } from './core'; utils.mixins.performMixins(); +/** + * Alias for {@link PIXI.loaders.shared}. + * @name loader + * @memberof PIXI + * @type {PIXI.loader.Loader} + */ +const loader = loaders.shared || null; + export { accessibility, extract, @@ -30,18 +38,8 @@ mesh, particles, prepare, + loader, }; -/** - * A premade instance of the loader that can be used to load resources. - * - * @name loader - * @memberof PIXI - * @property {PIXI.loaders.Loader} - */ -const loader = loaders && loaders.Loader ? new loaders.Loader() : null; // check is there in case user excludes loader lib - -export { loader }; - // Always export pixi globally. global.PIXI = exports; // eslint-disable-line diff --git a/src/interaction/InteractionData.js b/src/interaction/InteractionData.js index 02d53a3..449f8cc 100644 --- a/src/interaction/InteractionData.js +++ b/src/interaction/InteractionData.js @@ -30,7 +30,10 @@ /** * When passed to an event handler, this will be the original DOM Event that was captured * - * @member {Event} + * @see https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent + * @see https://developer.mozilla.org/en-US/docs/Web/API/TouchEvent + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent + * @member {MouseEvent|TouchEvent|PointerEvent} */ this.originalEvent = null; } diff --git a/src/interaction/InteractionManager.js b/src/interaction/InteractionManager.js index 8baf13a..2d75c7c 100644 --- a/src/interaction/InteractionManager.js +++ b/src/interaction/InteractionManager.js @@ -13,6 +13,14 @@ const MOUSE_POINTER_ID = 'MOUSE'; +// helpers for hitTest() - only used inside hitTest() +const hitTestEvent = { + target: null, + data: { + global: null, + }, +}; + /** * The interaction manager deals with mouse, touch and pointer events. Any DisplayObject can be interactive * if its interactive parameter is set to true @@ -245,67 +253,60 @@ this.setTargetElement(this.renderer.view, this.renderer.resolution); /** - * Fired when a pointer device button (usually a mouse button) is pressed on the display + * Fired when a pointer device button (usually a mouse left-button) is pressed on the display * object. * - * @event mousedown - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mousedown + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device secondary button (usually a mouse right-button) is pressed * on the display object. * - * @event rightdown - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightdown + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** - * Fired when a pointer device button (usually a mouse button) is released over the display + * Fired when a pointer device button (usually a mouse left-button) is released over the display * object. * - * @event mouseup - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseup + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device secondary button (usually a mouse right-button) is released * over the display object. * - * @event rightup - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightup + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** - * Fired when a pointer device button (usually a mouse button) is pressed and released on + * Fired when a pointer device button (usually a mouse left-button) is pressed and released on * the display object. * - * @event click - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#click + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device secondary button (usually a mouse right-button) is pressed * and released on the display object. * - * @event rightclick - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightclick + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** - * Fired when a pointer device button (usually a mouse button) is released outside the + * Fired when a pointer device button (usually a mouse left-button) is released outside the * display object that initially registered a * [mousedown]{@link PIXI.interaction.InteractionManager#event:mousedown}. * - * @event mouseupoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** @@ -313,146 +314,362 @@ * outside the display object that initially registered a * [rightdown]{@link PIXI.interaction.InteractionManager#event:rightdown}. * - * @event rightupoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device (usually a mouse) is moved while over the display object * - * @event mousemove - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mousemove + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device (usually a mouse) is moved onto the display object * - * @event mouseover - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseover + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device (usually a mouse) is moved off the display object * - * @event mouseout - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseout + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is pressed on the display object. * - * @event pointerdown - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerdown + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is released over the display object. * - * @event pointerup - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerup + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when the operating system cancels a pointer event * - * @event pointercancel - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointercancel + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is pressed and released on the display object. * - * @event pointertap - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointertap + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is released outside the display object that initially * registered a [pointerdown]{@link PIXI.interaction.InteractionManager#event:pointerdown}. * - * @event pointerupoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device is moved while over the display object * - * @event pointermove - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointermove + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device is moved onto the display object * - * @event pointerover - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerover + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device is moved off the display object * - * @event pointerout - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerout + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is placed on the display object. * - * @event touchstart - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchstart + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is removed from the display object. * - * @event touchend - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchend + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when the operating system cancels a touch * - * @event touchcancel - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchcancel + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is placed and removed from the display object. * - * @event tap - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#tap + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is removed outside of the display object that initially * registered a [touchstart]{@link PIXI.interaction.InteractionManager#event:touchstart}. * - * @event touchendoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchendoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is moved along the display object. * - * @event touchmove - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchmove + * @param {PIXI.interaction.InteractionData} event - Interaction data */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is pressed on the display. + * object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mousedown + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is pressed + * on the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightdown + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is released over the display + * object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseup + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is released + * over the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightup + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is pressed and released on + * the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#click + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is pressed + * and released on the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightclick + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is released outside the + * display object that initially registered a + * [mousedown]{@link PIXI.DisplayObject#event:mousedown}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is released + * outside the display object that initially registered a + * [rightdown]{@link PIXI.DisplayObject#event:rightdown}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device (usually a mouse) is moved while over the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mousemove + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device (usually a mouse) is moved onto the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseover + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device (usually a mouse) is moved off the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseout + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is pressed on the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerdown + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is released over the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerup + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when the operating system cancels a pointer event. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointercancel + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is pressed and released on the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointertap + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is released outside the display object that initially + * registered a [pointerdown]{@link PIXI.DisplayObject#event:pointerdown}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device is moved while over the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointermove + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device is moved onto the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerover + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device is moved off the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerout + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is placed on the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchstart + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is removed from the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchend + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when the operating system cancels a touch. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchcancel + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is placed and removed from the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#tap + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is removed outside of the display object that initially + * registered a [touchstart]{@link PIXI.DisplayObject#event:touchstart}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchendoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is moved along the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchmove + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + } + + /** + * Hit tests a point against the display tree, returning the first interactive object that is hit. + * + * @param {PIXI.Point} globalPoint - A point to hit test with, in global space. + * @param {PIXI.Container} [root] - The root display object to start from. If omitted, defaults + * to the last rendered root of the associated renderer. + * @return {PIXI.DisplayObject} The hit display object, if any. + */ + hitTest(globalPoint, root) + { + // clear the target for our hit test + hitTestEvent.target = null; + // assign the global point + hitTestEvent.data.global = globalPoint; + // ensure safety of the root + if (!root) + { + root = this.renderer._lastObjectRendered; + } + // run the hit test + this.processInteractive(hitTestEvent, root, null, true); + // return our found object - it'll be null if we didn't hit anything + + return hitTestEvent.target; } /** @@ -487,7 +704,7 @@ return; } - core.ticker.shared.add(this.update, this); + core.ticker.shared.add(this.update, this, core.UPDATE_PRIORITY.INTERACTION); if (window.navigator.msPointerEnabled) { @@ -804,8 +1021,6 @@ } } - let keepHitTestingAfterChildren = hitTest; - // ** FREE TIP **! If an object is not interactive or has no buttons in it // (such as a game scene!) set interactiveChildren to false for that displayObject. // This will allow pixi to completely ignore and bypass checking the displayObjects children. @@ -818,7 +1033,9 @@ const child = children[i]; // time to get recursive.. if this function will return if something is hit.. - if (this.processInteractive(interactionEvent, child, func, hitTest, interactiveParent)) + const childHit = this.processInteractive(interactionEvent, child, func, hitTest, interactiveParent); + + if (childHit) { // its a good idea to check if a child has lost its parent. // this means it has been removed whilst looping so its best @@ -827,8 +1044,6 @@ continue; } - hit = true; - // we no longer need to hit test any more objects in this container as we we // now know the parent has been hit interactiveParent = false; @@ -838,36 +1053,41 @@ // This means we no longer need to hit test anything else. We still need to run // through all objects, but we don't need to perform any hit tests. - keepHitTestingAfterChildren = false; - - if (child.interactive) + if (childHit) { - hitTest = false; + if (interactionEvent.target) + { + hitTest = false; + } + hit = true; } - - // we can break now as we have hit an object. } } } - hitTest = keepHitTestingAfterChildren; - // no point running this if the item is not interactive or does not have an interactive parent. if (interactive) { // if we are hit testing (as in we have no hit any objects yet) // We also don't need to worry about hit testing if once of the displayObjects children - // has already been hit! - if (hitTest && !hit) + // has already been hit - but only if it was interactive, otherwise we need to keep + // looking for an interactive child, just in case we hit one + if (hitTest && !interactionEvent.target) { if (displayObject.hitArea) { displayObject.worldTransform.applyInverse(point, this._tempPoint); - hit = displayObject.hitArea.contains(this._tempPoint.x, this._tempPoint.y); + if (displayObject.hitArea.contains(this._tempPoint.x, this._tempPoint.y)) + { + hit = true; + } } else if (displayObject.containsPoint) { - hit = displayObject.containsPoint(point); + if (displayObject.containsPoint(point)) + { + hit = true; + } } } @@ -878,7 +1098,10 @@ interactionEvent.target = displayObject; } - func(interactionEvent, displayObject, hit); + if (func) + { + func(interactionEvent, displayObject, !!hit); + } } } @@ -914,7 +1137,7 @@ { const event = events[i]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -998,7 +1221,7 @@ { const event = events[i]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1098,7 +1321,7 @@ const test = isRightButton ? flags.RIGHT_DOWN : flags.LEFT_DOWN; - const isDown = trackingData !== undefined && (trackingData.flags | test); + const isDown = trackingData !== undefined && (trackingData.flags & test); if (hit) { @@ -1118,11 +1341,11 @@ { if (isRightButton) { - trackingData.rightDown = hit; + trackingData.rightDown = false; } else { - trackingData.leftDown = hit; + trackingData.leftDown = false; } } } @@ -1180,7 +1403,7 @@ { const event = events[i]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1255,7 +1478,7 @@ this.setCursorMode(null); } - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1268,6 +1491,12 @@ { this.emit('mouseout', interactionEvent); } + else + { + // we can get touchleave events after touchend, so we want to make sure we don't + // introduce memory leaks + this.releaseInteractionDataForPointerId(interactionData.identifier); + } } /** @@ -1344,7 +1573,7 @@ // Only mouse and pointer can call onPointerOver, so events will always be length 1 const event = events[0]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1366,12 +1595,14 @@ * Get InteractionData for a given pointerId. Store that data as well * * @private - * @param {number} pointerId - Identifier from a pointer event + * @param {PointerEvent} event - Normalized pointer event, output from normalizeToPointerData * @return {InteractionData} - Interaction data for the given pointer identifier */ - getInteractionDataForPointerId(pointerId) + getInteractionDataForPointerId(event) { - if (pointerId === MOUSE_POINTER_ID) + const pointerId = event.pointerId; + + if (pointerId === MOUSE_POINTER_ID || event.pointerType === 'mouse') { return this.mouse; } diff --git a/src/interaction/InteractionTrackingData.js b/src/interaction/InteractionTrackingData.js index 6a1f69e..8493b63 100644 --- a/src/interaction/InteractionTrackingData.js +++ b/src/interaction/InteractionTrackingData.js @@ -35,8 +35,10 @@ } /** + * Unique pointer id of the event + * * @readonly - * @type {number} Unique pointer id of the event + * @member {number} */ get pointerId() { diff --git a/src/interaction/index.js b/src/interaction/index.js index 56c5b09..72f7d71 100644 --- a/src/interaction/index.js +++ b/src/interaction/index.js @@ -1,4 +1,8 @@ /** + * This namespace contains a renderer plugin for handling mouse, pointer, and touch events. + * + * Do not instantiate this plugin directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. * @namespace PIXI.interaction */ export { default as InteractionData } from './InteractionData'; diff --git a/src/interaction/interactiveTarget.js b/src/interaction/interactiveTarget.js index d252fba..cebac40 100644 --- a/src/interaction/interactiveTarget.js +++ b/src/interaction/interactiveTarget.js @@ -2,7 +2,7 @@ * Default property values of interactive objects * Used by {@link PIXI.interaction.InteractionManager} to automatically give all DisplayObjects these properties * - * @mixin + * @private * @name interactiveTarget * @memberof PIXI.interaction * @example @@ -14,18 +14,28 @@ * ); */ export default { + /** - * Determines if the displayObject be clicked/touched + * Enable interaction events for the DisplayObject. Touch, pointer and mouse + * events will not be emitted unless `interactive` is set to `true`. * - * @inner {boolean} + * @example + * const sprite = new PIXI.Sprite(texture); + * sprite.interactive = true; + * sprite.on('tap', (event) => { + * //handle event + * }); + * @member {boolean} + * @memberof PIXI.DisplayObject# */ interactive: false, /** * Determines if the children to the displayObject can be clicked/touched - * Setting this to false allows pixi to bypass a recursive hitTest function + * Setting this to false allows pixi to bypass a recursive `hitTest` function * - * @inner {boolean} + * @member {boolean} + * @memberof PIXI.Container# */ interactiveChildren: true, @@ -33,7 +43,12 @@ * Interaction shape. Children will be hit first, then this shape will be checked. * Setting this will cause this shape to be checked in hit tests rather than the displayObject's bounds. * - * @inner {PIXI.Rectangle|PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.RoundedRectangle} + * @example + * const sprite = new PIXI.Sprite(texture); + * sprite.interactive = true; + * sprite.hitArea = new PIXI.Rectangle(0, 0, 100, 100); + * @member {PIXI.Rectangle|PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.RoundedRectangle} + * @memberof PIXI.DisplayObject# */ hitArea: null, @@ -41,8 +56,12 @@ * If enabled, the mouse cursor use the pointer behavior when hovered over the displayObject if it is interactive * Setting this changes the 'cursor' property to `'pointer'`. * + * @example + * const sprite = new PIXI.Sprite(texture); + * sprite.interactive = true; + * sprite.buttonMode = true; * @member {boolean} - * @memberof PIXI.interaction.interactiveTarget# + * @memberof PIXI.DisplayObject# */ get buttonMode() { @@ -64,9 +83,14 @@ * This defines what cursor mode is used when the mouse cursor * is hovered over the displayObject. * + * @example + * const sprite = new PIXI.Sprite(texture); + * sprite.interactive = true; + * sprite.cursor = 'wait'; * @see https://developer.mozilla.org/en/docs/Web/CSS/cursor * - * @inner {string} + * @member {string} + * @memberof PIXI.DisplayObject# */ cursor: null, @@ -74,7 +98,7 @@ * Internal set of all active pointers, by identifier * * @member {Map} - * @memberof PIXI.interaction.interactiveTarget# + * @memberof PIXI.DisplayObject# * @private */ get trackedPointers() @@ -87,7 +111,8 @@ /** * Map of all tracked pointers, by identifier. Use trackedPointers to access. * - * @private {Map} + * @private + * @type {Map} */ _trackedPointers: undefined, }; diff --git a/src/loaders/index.js b/src/loaders/index.js index 5189793..daa1fc4 100644 --- a/src/loaders/index.js +++ b/src/loaders/index.js @@ -1,9 +1,22 @@ +import Application from '../core/Application'; +import Loader from './loader'; + /** + * This namespace contains APIs which extends the {@link https://github.com/englercj/resource-loader resource-loader} module + * for loading assets, data, and other resources dynamically. + * @example + * const loader = new PIXI.loaders.Loader(); + * loader.add('bunny', 'data/bunny.png') + * .add('spaceship', 'assets/spritesheet.json'); + * loader.load((loader, resources) => { + * // resources.bunny + * // resources.spaceship + * }); * @namespace PIXI.loaders */ -export { default as Loader } from './loader'; +export { Loader }; export { default as bitmapFontParser, parse as parseBitmapFontData } from './bitmapFontParser'; -export { default as spritesheetParser } from './spritesheetParser'; +export { default as spritesheetParser, getResourcePath } from './spritesheetParser'; export { default as textureParser } from './textureParser'; /** @@ -13,3 +26,55 @@ * @memberof PIXI.loaders */ export { Resource } from 'resource-loader'; + +/** + * A premade instance of the loader that can be used to load resources. + * @name shared + * @memberof PIXI.loaders + * @type {PIXI.loaders.Loader} + */ +const shared = new Loader(); + +shared.destroy = () => +{ + // protect destroying shared loader +}; + +export { shared }; + +// Mixin the loader construction +const AppPrototype = Application.prototype; + +AppPrototype._loader = null; + +/** + * Loader instance to help with asset loading. + * @name PIXI.Application#loader + * @type {PIXI.loaders.Loader} + */ +Object.defineProperty(AppPrototype, 'loader', { + get() + { + if (!this._loader) + { + const sharedLoader = this._options.sharedLoader; + + this._loader = sharedLoader ? shared : new Loader(); + } + + return this._loader; + }, +}); + +// Override the destroy function +// making sure to destroy the current Loader +AppPrototype._parentDestroy = AppPrototype.destroy; +AppPrototype.destroy = function destroy() +{ + if (this._loader) + { + this._loader.destroy(); + this._loader = null; + } + this._parentDestroy(); +}; diff --git a/src/loaders/loader.js b/src/loaders/loader.js index 767a411..59b8000 100644 --- a/src/loaders/loader.js +++ b/src/loaders/loader.js @@ -90,6 +90,15 @@ { Loader._pixiMiddleware.push(fn); } + + /** + * Destroy the loader, removes references. + */ + destroy() + { + this.removeAllListeners(); + this.reset(); + } } // Copy EE3 prototype (mixin) diff --git a/src/loaders/spritesheetParser.js b/src/loaders/spritesheetParser.js index e57dfb2..eda9584 100644 --- a/src/loaders/spritesheetParser.js +++ b/src/loaders/spritesheetParser.js @@ -1,12 +1,11 @@ import { Resource } from 'resource-loader'; -import path from 'path'; +import url from 'url'; import { Spritesheet } from '../core'; export default function () { return function spritesheetParser(resource, next) { - let resourcePath; const imageResourceName = `${resource.name}_image`; // skip if no data, its not json, it isn't spritesheet data, or the image resource already exists @@ -28,15 +27,7 @@ parentResource: resource, }; - // Prepend url path unless the resource image is a data url - if (resource.isDataUrl) - { - resourcePath = resource.data.meta.image; - } - else - { - resourcePath = `${path.dirname(resource.url.replace(this.baseUrl, ''))}/${resource.data.meta.image}`; - } + const resourcePath = getResourcePath(resource, this.baseUrl); // load the image for this sheet this.add(imageResourceName, resourcePath, loadOptions, function onImageLoad(res) @@ -56,3 +47,14 @@ }); }; } + +export function getResourcePath(resource, baseUrl) +{ + // Prepend url path unless the resource image is a data url + if (resource.isDataUrl) + { + return resource.data.meta.image; + } + + return url.resolve(resource.url.replace(baseUrl, ''), resource.data.meta.image); +} diff --git a/src/mesh/canvas/CanvasMeshRenderer.js b/src/mesh/canvas/CanvasMeshRenderer.js index 297205f..f7899e1 100644 --- a/src/mesh/canvas/CanvasMeshRenderer.js +++ b/src/mesh/canvas/CanvasMeshRenderer.js @@ -134,12 +134,33 @@ const textureWidth = base.width; const textureHeight = base.height; - const u0 = uvs[index0] * base.width; - const u1 = uvs[index1] * base.width; - const u2 = uvs[index2] * base.width; - const v0 = uvs[index0 + 1] * base.height; - const v1 = uvs[index1 + 1] * base.height; - const v2 = uvs[index2 + 1] * base.height; + let u0; + let u1; + let u2; + let v0; + let v1; + let v2; + + if (mesh.uploadUvTransform) + { + const ut = mesh._uvTransform.mapCoord; + + u0 = ((uvs[index0] * ut.a) + (uvs[index0 + 1] * ut.c) + ut.tx) * base.width; + u1 = ((uvs[index1] * ut.a) + (uvs[index1 + 1] * ut.c) + ut.tx) * base.width; + u2 = ((uvs[index2] * ut.a) + (uvs[index2 + 1] * ut.c) + ut.tx) * base.width; + v0 = ((uvs[index0] * ut.b) + (uvs[index0 + 1] * ut.d) + ut.ty) * base.height; + v1 = ((uvs[index1] * ut.b) + (uvs[index1 + 1] * ut.d) + ut.ty) * base.height; + v2 = ((uvs[index2] * ut.b) + (uvs[index2 + 1] * ut.d) + ut.ty) * base.height; + } + else + { + u0 = uvs[index0] * base.width; + u1 = uvs[index1] * base.width; + u2 = uvs[index2] * base.width; + v0 = uvs[index0 + 1] * base.height; + v1 = uvs[index1 + 1] * base.height; + v2 = uvs[index2 + 1] * base.height; + } let x0 = vertices[index0]; let x1 = vertices[index1]; diff --git a/src/mesh/webgl/MeshRenderer.js b/src/mesh/webgl/MeshRenderer.js index 78d24c3..ab99fcd 100644 --- a/src/mesh/webgl/MeshRenderer.js +++ b/src/mesh/webgl/MeshRenderer.js @@ -2,6 +2,8 @@ const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; +const matrixIdentity = core.Matrix.IDENTITY; + /** * WebGL renderer plugin for tiling sprites * @@ -64,6 +66,7 @@ mesh.geometry.glVertexArrayObjects[this.CONTEXT_UID].draw(mesh.drawMode, mesh.size, mesh.start, mesh.geometry.instanceCount); } + /** * draws mesh * @param {PIXI.mesh.RawMesh} mesh mesh instance diff --git a/src/mesh/webgl/mesh.vert b/src/mesh/webgl/mesh.vert index a337aef..acc096c 100644 --- a/src/mesh/webgl/mesh.vert +++ b/src/mesh/webgl/mesh.vert @@ -1,8 +1,9 @@ attribute vec2 aVertexPosition; attribute vec2 aTextureCoord; -uniform mat3 translationMatrix; uniform mat3 projectionMatrix; +uniform mat3 translationMatrix; +uniform mat3 uTransform; varying vec2 vTextureCoord; @@ -10,5 +11,5 @@ { gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - vTextureCoord = aTextureCoord; + vTextureCoord = (uTransform * vec3(aTextureCoord, 1.0)).xy; } diff --git a/src/particles/ParticleContainer.js b/src/particles/ParticleContainer.js index 67796cf..63f7791 100644 --- a/src/particles/ParticleContainer.js +++ b/src/particles/ParticleContainer.js @@ -313,10 +313,10 @@ frame.y * resolution, frame.width * resolution, frame.height * resolution, - positionX * resolution, - positionY * resolution, - finalWidth * resolution, - finalHeight * resolution + positionX * renderer.resolution, + positionY * renderer.resolution, + finalWidth * renderer.resolution, + finalHeight * renderer.resolution ); } } diff --git a/src/prepare/BasePrepare.js b/src/prepare/BasePrepare.js index 09eb5e5..e45c4dc 100644 --- a/src/prepare/BasePrepare.js +++ b/src/prepare/BasePrepare.js @@ -18,6 +18,18 @@ * basic queuing functionality and is extended by {@link PIXI.prepare.WebGLPrepare} and {@link PIXI.prepare.CanvasPrepare} * to provide preparation capabilities specific to their respective renderers. * + * @example + * // Create a sprite + * const sprite = new PIXI.Sprite.fromImage('something.png'); + * + * // Load object into GPU + * app.renderer.plugins.prepare.upload(sprite, () => { + * + * //Texture(s) has been uploaded to GPU + * app.stage.addChild(sprite); + * + * }) + * * @abstract * @class * @memberof PIXI.prepare @@ -100,8 +112,16 @@ this.prepareItems(); }; - this.register(findText, drawText); - this.register(findTextStyle, calculateTextStyle); + // hooks to find the correct texture + this.registerFindHook(findText); + this.registerFindHook(findTextStyle); + this.registerFindHook(findMultipleBaseTextures); + this.registerFindHook(findBaseTexture); + this.registerFindHook(findTexture); + + // upload hooks + this.registerUploadHook(drawText); + this.registerUploadHook(calculateTextStyle); } /** @@ -138,7 +158,7 @@ if (!this.ticking) { this.ticking = true; - SharedTicker.addOnce(this.tick, this); + SharedTicker.addOnce(this.tick, this, core.UPDATE_PRIORITY.UTILITY); } } else if (done) @@ -172,13 +192,16 @@ const item = this.queue[0]; let uploaded = false; - for (let i = 0, len = this.uploadHooks.length; i < len; i++) + if (item && !item._destroyed) { - if (this.uploadHooks[i](this.uploadHookHelper, item)) + for (let i = 0, len = this.uploadHooks.length; i < len; i++) { - this.queue.shift(); - uploaded = true; - break; + if (this.uploadHooks[i](this.uploadHookHelper, item)) + { + this.queue.shift(); + uploaded = true; + break; + } } } @@ -205,26 +228,36 @@ else { // if we are not finished, on the next rAF do this again - SharedTicker.addOnce(this.tick, this); + SharedTicker.addOnce(this.tick, this, core.UPDATE_PRIORITY.UTILITY); } } /** - * Adds hooks for finding and uploading items. + * Adds hooks for finding items. * - * @param {Function} [addHook] - Function call that takes two parameters: `item:*, queue:Array` - function must return `true` if it was able to add item to the queue. - * @param {Function} [uploadHook] - Function call that takes two parameters: `prepare:CanvasPrepare, item:*` and - * function must return `true` if it was able to handle upload of item. - * @return {PIXI.CanvasPrepare} Instance of plugin for chaining. + * @param {Function} addHook - Function call that takes two parameters: `item:*, queue:Array` + * function must return `true` if it was able to add item to the queue. + * @return {PIXI.BasePrepare} Instance of plugin for chaining. */ - register(addHook, uploadHook) + registerFindHook(addHook) { if (addHook) { this.addHooks.push(addHook); } + return this; + } + + /** + * Adds hooks for uploading items. + * + * @param {Function} uploadHook - Function call that takes two parameters: `prepare:CanvasPrepare, item:*` and + * function must return `true` if it was able to handle upload of item. + * @return {PIXI.BasePrepare} Instance of plugin for chaining. + */ + registerUploadHook(uploadHook) + { if (uploadHook) { this.uploadHooks.push(uploadHook); @@ -287,6 +320,88 @@ } /** + * Built-in hook to find multiple textures from objects like AnimatedSprites. + * + * @private + * @param {PIXI.DisplayObject} item - Display object to check + * @param {Array<*>} queue - Collection of items to upload + * @return {boolean} if a PIXI.Texture object was found. + */ +function findMultipleBaseTextures(item, queue) +{ + let result = false; + + // Objects with mutliple textures + if (item && item._textures && item._textures.length) + { + for (let i = 0; i < item._textures.length; i++) + { + if (item._textures[i] instanceof core.Texture) + { + const baseTexture = item._textures[i].baseTexture; + + if (queue.indexOf(baseTexture) === -1) + { + queue.push(baseTexture); + result = true; + } + } + } + } + + return result; +} + +/** + * Built-in hook to find BaseTextures from Sprites. + * + * @private + * @param {PIXI.DisplayObject} item - Display object to check + * @param {Array<*>} queue - Collection of items to upload + * @return {boolean} if a PIXI.Texture object was found. + */ +function findBaseTexture(item, queue) +{ + // Objects with textures, like Sprites/Text + if (item instanceof core.BaseTexture) + { + if (queue.indexOf(item) === -1) + { + queue.push(item); + } + + return true; + } + + return false; +} + +/** + * Built-in hook to find textures from objects. + * + * @private + * @param {PIXI.DisplayObject} item - Display object to check + * @param {Array<*>} queue - Collection of items to upload + * @return {boolean} if a PIXI.Texture object was found. + */ +function findTexture(item, queue) +{ + if (item._texture && item._texture instanceof core.Texture) + { + const texture = item._texture.baseTexture; + + if (queue.indexOf(texture) === -1) + { + queue.push(texture); + } + + return true; + } + + return false; +} + +/** * Built-in hook to draw PIXI.Text to its texture. * * @private diff --git a/src/prepare/canvas/CanvasPrepare.js b/src/prepare/canvas/CanvasPrepare.js index 0224bb5..09c207a 100644 --- a/src/prepare/canvas/CanvasPrepare.js +++ b/src/prepare/canvas/CanvasPrepare.js @@ -43,7 +43,7 @@ this.ctx = this.canvas.getContext('2d'); // Add textures to upload - this.register(findBaseTextures, uploadBaseTextures); + this.registerUploadHook(uploadBaseTextures); } /** @@ -89,39 +89,4 @@ return false; } -/** - * Built-in hook to find textures from Sprites. - * - * @private - * @param {PIXI.DisplayObject} item -Display object to check - * @param {Array<*>} queue - Collection of items to upload - * @return {boolean} if a PIXI.Texture object was found. - */ -function findBaseTextures(item, queue) -{ - // Objects with textures, like Sprites/Text - if (item instanceof core.BaseTexture) - { - if (queue.indexOf(item) === -1) - { - queue.push(item); - } - - return true; - } - else if (item._texture && item._texture instanceof core.Texture) - { - const texture = item._texture.baseTexture; - - if (queue.indexOf(texture) === -1) - { - queue.push(texture); - } - - return true; - } - - return false; -} - core.CanvasRenderer.registerPlugin('prepare', CanvasPrepare); diff --git a/src/prepare/index.js b/src/prepare/index.js index f559c45..e7491ec 100644 --- a/src/prepare/index.js +++ b/src/prepare/index.js @@ -1,4 +1,29 @@ /** + * The prepare namespace provides renderer-specific plugins for pre-rendering DisplayObjects. These plugins are useful for + * asynchronously preparing assets, textures, graphics waiting to be displayed. + * + * Do not instantiate these plugins directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. + * @example + * // Create a new application + * const app = new PIXI.Application(); + * document.body.appendChild(app.view); + * + * // Don't start rendering right away + * app.stop(); + * + * // create a display object + * const rect = new PIXI.Graphics() + * .beginFill(0x00ff00) + * .drawRect(40, 40, 200, 200); + * + * // Add to the stage + * app.stage.addChild(rect); + * + * // Don't start rendering until the graphic is uploaded to the GPU + * app.renderer.plugins.prepare.upload(app.stage, () => { + * app.start(); + * }); * @namespace PIXI.prepare */ export { default as webgl } from './webgl/WebGLPrepare'; diff --git a/.travis.yml b/.travis.yml index 3df8b22..14de334 100644 --- a/.travis.yml +++ b/.travis.yml @@ -110,3 +110,43 @@ on: all_branches: true condition: $TRAVIS_TAG + # Deploy config for latest release + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: dist + upload-dir: "release" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: docs + upload-dir: "release/docs" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: coverage + upload-dir: "release/coverage" + on: + all_branches: true + condition: $TRAVIS_TAG \ No newline at end of file diff --git a/README.md b/README.md index d26fadd..3b85f85 100644 --- a/README.md +++ b/README.md @@ -36,22 +36,14 @@ - Wiki: Other misc tutorials and resources are [on the Wiki](https://github.com/pixijs/pixi.js/wiki/Resources). ### Community ### -- Forums: Check out the [forum] (http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow] (http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. +- Forums: Check out the [forum](http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow](http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. - Inspiration: Check out the [gallery](http://www.pixijs.com/gallery) to see some of the amazing things people have created! - Chat: You can join us on [Gitter](https://gitter.im/pixijs/pixi.js) To chat about Pixi. We also now have a Slack channel. If you would like to join it please Send me an email (mat@goodboydigital.com) and I will invite you in. ### Setup ### -It's easy to get started with Pixi.js! Simply grab the pre-built versions from here: - -Release Branch - Nice and stable Pixi.js -- Unminified: [http://pixijs.download/release/pixi.js] -- Minified: [http://pixijs.download/release/pixi.min.js] - -Develop Branch - The bleeding edge version of Pixi.js -- Unminified: [http://pixijs.download/dev/pixi.js] -- Minified: [http://pixijs.download/dev/pixi.min.js] +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. @@ -70,10 +62,10 @@ #### CDN Install (via cdnjs) ```html - + ``` -_Note: `4.2.2` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ +_Note: `4.4.3` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ ### Demos ### @@ -130,7 +122,7 @@ // and the root stage PIXI.Container. var app = new PIXI.Application(); -// The application will create a canvas element for you that you +// The application will create a canvas element for you that you // can then insert into the DOM. document.body.appendChild(app.view); @@ -150,7 +142,7 @@ // Add the bunny to the scene we are building. app.stage.addChild(bunny); - + // Listen for frame updates app.ticker.add(function() { // each frame we spin the bunny around a bit diff --git a/bower.json b/bower.json deleted file mode 100644 index 41b668c..0000000 --- a/bower.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "pixi.js", - "main": "dist/pixi.min.js", - "ignore": [ - "**/.*", - "docs", - "node_modules", - "bower_components", - "test", - "scripts", - "bower.json", - "inch.json" - ], - "dependencies": { - }, - "devDependencies": { - } -} diff --git a/package.json b/package.json index d35cbb2..308469f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.3.5", + "version": "4.4.4", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ @@ -40,11 +40,11 @@ "lib": "babel src --out-dir lib -s", "predocs": "rimraf docs/**", "docs": "jsdoc -c scripts/jsdoc.conf.json -R README.md", - "publish:patch": "npm version patch --no-git-tag-version && npm publish", - "publish:minor": "npm version minor --no-git-tag-version && npm publish", - "publish:major": "npm version major --no-git-tag-version && npm publish", + "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", - "postpublish": "node scripts/release.js" + "postpublish": "git push && git push --tags" }, "files": [ "dist/", @@ -72,7 +72,6 @@ "electron": "^1.4.15", "eslint": "^3.5.0", "floss": "^2.0.1", - "gh-pages": "^0.11.0", "jaguarjs-jsdoc": "^1.0.1", "js-md5": "^0.4.1", "jsdoc": "^3.4.2", diff --git a/scripts/jsdoc.conf.json b/scripts/jsdoc.conf.json index 553e61c..3f52180 100644 --- a/scripts/jsdoc.conf.json +++ b/scripts/jsdoc.conf.json @@ -43,7 +43,7 @@ }, "markdown" : { "parser" : "gfm", - "hardwrap" : true + "hardwrap" : false }, "opts": { "encoding" : "utf8", diff --git a/scripts/release.js b/scripts/release.js deleted file mode 100644 index 156620c..0000000 --- a/scripts/release.js +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env node -'use strict'; - -// Publish script to push releases of the bin files -// the normally are gitignored -const ghpages = require('gh-pages'); -const path = require('path'); -const packageInfo = require(path.join(__dirname, '..', 'package.json')); - -const options = { - src: [ - 'dist/**/*', - 'lib/**/*', - 'src/**/*', - 'scripts/*', - 'scripts/renders/*', - 'test/**/*', - '*.json', - '*.md', - 'LICENSE', - '.eslintrc', - '.editorconfig', - '.travis.yml', - '.babelrc', - ], - dotfiles: true, - branch: 'release', - message: packageInfo.version, - logger: console.log.bind(console), -}; - -ghpages.publish(process.cwd(), options, (err) => -{ - if (err) - { - console.log(err); - process.exit(1); - - return; - } - - process.exit(0); -}); diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index f16bcb1..220427a 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -21,7 +21,7 @@ const DIV_HOOK_ZINDEX = 2; /** - * The Accessibility manager reacreates the ability to tab and and have content read by screen + * The Accessibility manager recreates the ability to tab and and have content read by screen * readers. This is very important as it can possibly help people with disabilities access pixi * content. * diff --git a/src/accessibility/index.js b/src/accessibility/index.js index 568cfb1..0ae108f 100644 --- a/src/accessibility/index.js +++ b/src/accessibility/index.js @@ -1,4 +1,9 @@ /** + * This namespace contains a renderer plugin for interaction accessibility for end-users + * with physical impairments which require screen-renders, keyboard navigation, etc. + * + * Do not instantiate this plugin directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. * @namespace PIXI.accessibility */ export { default as accessibleTarget } from './accessibleTarget'; diff --git a/src/core/Application.js b/src/core/Application.js index 5340578..2fcc729 100644 --- a/src/core/Application.js +++ b/src/core/Application.js @@ -1,6 +1,8 @@ import { autoDetectRenderer } from './autoDetectRenderer'; import Container from './display/Container'; import { shared, Ticker } from './ticker'; +import settings from './settings'; +import { UPDATE_PRIORITY } from './const'; /** * Convenience class to create a new PIXI application. @@ -22,28 +24,52 @@ */ export default class Application { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experience unexplained flickering try setting this to true. - * @param {boolean} [useSharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedLoader=false] - `true` to use PIXI.loaders.shared, `false` to create new Loader. */ - constructor(width, height, options, noWebGL, useSharedTicker = false) + constructor(options, arg2, arg3, arg4, arg5) { + // Support for constructor(width, height, options, noWebGL, useSharedTicker) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + forceCanvas: !!arg4, + sharedTicker: !!arg5, + }, arg3); + } + + /** + * The default options, so we mixin functionality later. + * @member {object} + * @protected + */ + this._options = options = Object.assign({ + sharedTicker: false, + forceCanvas: false, + sharedLoader: false, + }, options); + /** * WebGL renderer if available, otherwise CanvasRenderer * @member {PIXI.WebGLRenderer|PIXI.CanvasRenderer} */ - this.renderer = autoDetectRenderer(width, height, options, noWebGL); + this.renderer = autoDetectRenderer(options); /** * The root display container that's rendered. @@ -63,7 +89,7 @@ * @member {PIXI.ticker.Ticker} * @default PIXI.ticker.shared */ - this.ticker = useSharedTicker ? shared : new Ticker(); + this.ticker = options.sharedTicker ? shared : new Ticker(); // Start the rendering this.start(); @@ -78,7 +104,7 @@ this._ticker = ticker; if (ticker) { - ticker.add(this.render, this); + ticker.add(this.render, this, UPDATE_PRIORITY.LOW); } } get ticker() // eslint-disable-line require-jsdoc @@ -136,13 +162,18 @@ */ destroy(removeView) { - this.stop(); + const oldTicker = this._ticker; + this.ticker = null; + oldTicker.destroy(); + this.stage.destroy(); this.stage = null; this.renderer.destroy(removeView); this.renderer = null; + + this._options = null; } } diff --git a/src/core/autoDetectRenderer.js b/src/core/autoDetectRenderer.js index 8960425..9ec0364 100644 --- a/src/core/autoDetectRenderer.js +++ b/src/core/autoDetectRenderer.js @@ -2,6 +2,7 @@ import CanvasRenderer from './renderers/canvas/CanvasRenderer'; import WebGLRenderer from './renderers/webgl/WebGLRenderer'; +// eslint-disable-next-line valid-jsdoc /** * This helper function will automatically detect which renderer you should be using. * WebGL is the preferred renderer as it is a lot faster. If webGL is not supported by @@ -9,24 +10,32 @@ * * @memberof PIXI * @function autoDetectRenderer - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @return {PIXI.WebGLRenderer|PIXI.CanvasRenderer} Returns WebGL renderer if available, otherwise CanvasRenderer */ -export function autoDetectRenderer(width = 800, height = 600, options, noWebGL) +export function autoDetectRenderer(options, arg1, arg2, arg3) { - if (!noWebGL && utils.isWebGLSupported()) + // Backward-compatible support for noWebGL option + let forceCanvas = options && options.forceCanvas; + + if (arg3 !== undefined) { - return new WebGLRenderer(width, height, options); + forceCanvas = arg3; } - return new CanvasRenderer(width, height, options); + if (!forceCanvas && utils.isWebGLSupported()) + { + return new WebGLRenderer(options, arg1, arg2); + } + + return new CanvasRenderer(options, arg1, arg2); } diff --git a/src/core/const.js b/src/core/const.js index b9e99ae..2c68dee 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -341,3 +341,27 @@ LINEAR_VERTICAL: 0, LINEAR_HORIZONTAL: 1, }; + +/** + * Represents the update priorities used by internal PIXI classes when registered with + * the {@link PIXI.ticker.Ticker} object. Higher priority items are updated first and lower + * priority items, such as render, should go later. + * + * @static + * @constant + * @name UPDATE_PRIORITY + * @memberof PIXI + * @type {object} + * @property {number} INTERACTION=50 Highest priority, used for {@link PIXI.interaction.InteractionManager} + * @property {number} HIGH=25 High priority updating, {@link PIXI.VideoBaseTexture} and {@link PIXI.extras.AnimatedSprite} + * @property {number} NORMAL=0 Default priority for ticker events, see {@link PIXI.ticker.Ticker#add}. + * @property {number} LOW=-25 Low priority used for {@link PIXI.Application} rendering. + * @property {number} UTILITY=-50 Lowest priority used for {@link PIXI.prepare.BasePrepare} utility. + */ +export const UPDATE_PRIORITY = { + INTERACTION: 50, + HIGH: 25, + NORMAL: 0, + LOW: -25, + UTILITY: -50, +}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 4e6c77d..decb42a 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -13,7 +13,6 @@ * * @class * @extends EventEmitter - * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ export default class DisplayObject extends EventEmitter @@ -113,6 +112,29 @@ * @private */ this._mask = null; + + /** + * If the object has been destroyed via destroy(). If true, it should not be used. + * + * @member {boolean} + * @private + * @readonly + */ + this._destroyed = false; + + /** + * Fired when this DisplayObject is added to a Container. + * + * @event PIXI.DisplayObject#added + * @param {PIXI.Container} container - The container added to. + */ + + /** + * Fired when this DisplayObject is removed from a Container. + * + * @event PIXI.DisplayObject#removed + * @param {PIXI.Container} container - The container removed from. + */ } /** @@ -403,6 +425,8 @@ this.interactive = false; this.interactiveChildren = false; + + this._destroyed = true; } /** diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index aafdfe2..f787c4a 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -792,8 +792,8 @@ sprite.worldAlpha = this.worldAlpha * sprite.alpha; sprite.blendMode = this.blendMode; - sprite.texture._frame.width = rect.width; - sprite.texture._frame.height = rect.height; + sprite._texture._frame.width = rect.width; + sprite._texture._frame.height = rect.height; sprite.transform.worldTransform = this.transform.worldTransform; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index a24c775..b9f192d 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -19,11 +19,12 @@ */ export default class SystemRenderer extends EventEmitter { + // eslint-disable-next-line valid-jsdoc /** * @param {string} system - The name of the system this renderer is for. - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -37,27 +38,31 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(system, screenWidth, screenHeight, options) + constructor(system, options, arg2, arg3) { super(); sayHello(system); - // prepare options - if (options) + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') { - for (const i in settings.RENDER_OPTIONS) - { - if (typeof options[i] === 'undefined') - { - options[i] = settings.RENDER_OPTIONS[i]; - } - } + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); } - else - { - options = settings.RENDER_OPTIONS; - } + + // Add the default render options + options = Object.assign({}, settings.RENDER_OPTIONS, options); + + /** + * The supplied constructor options. + * + * @member {Object} + * @readOnly + */ + this.options = options; /** * The type of the renderer. @@ -75,7 +80,7 @@ * * @member {PIXI.Rectangle} */ - this.screen = new Rectangle(0, 0, screenWidth || 800, screenHeight || 600); + this.screen = new Rectangle(0, 0, options.width, options.height); /** * The canvas element that everything is drawn to @@ -279,6 +284,8 @@ this.blendModes = null; + this.options = null; + this.preserveDrawingBuffer = false; this.clearBeforeRender = false; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index e84e49c..691caf0 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -17,10 +17,11 @@ */ export default class CanvasRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -34,9 +35,9 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('Canvas', screenWidth, screenHeight, options); + super('Canvas', options, arg2, arg3); this.type = RENDERER_TYPE.CANVAS; @@ -96,7 +97,19 @@ this.context = null; this.renderingToScreen = false; - this.resize(screenWidth, screenHeight); + this.resize(this.options.width, this.options.height); + + /** + * Fired after rendering finishes. + * + * @event PIXI.CanvasRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.CanvasRenderer#prerender + */ } /** @@ -299,4 +312,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.CanvasRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.CanvasExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.CanvasPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.CanvasRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index e1cf7cd..f6fe123 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -31,11 +31,12 @@ */ export default class WebGLRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** * - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -55,9 +56,9 @@ * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('WebGL', screenWidth, screenHeight, options); + super('WebGL', options, arg2, arg3); /** * The type of this renderer as a standardised const @@ -89,12 +90,6 @@ }; - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ this._backgroundColorRgba[3] = this.transparent ? 0 : 1; this.globalUniforms = new UniformGroup({ @@ -117,6 +112,13 @@ this.initPlugins(); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ if(options.context) { this.context.initFromContext(options.context); @@ -169,6 +171,25 @@ } return this; + + /** + * Fired after rendering finishes. + * + * @event PIXI.WebGLRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.WebGLRenderer#prerender + */ + + /** + * Fired when the WebGL context is set. + * + * @event PIXI.WebGLRenderer#context + * @param {WebGLRenderingContext} gl - WebGL context. + */ } /** @@ -295,4 +316,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.WebGLRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.WebGLRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(WebGLRenderer); diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index 4eb6128..fa3d883 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -47,6 +47,14 @@ * @member {boolean} */ this.enabled = true; + + /** + * If enabled, pixi will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; } /** diff --git a/src/core/renderers/webgl/systems/ContextSystem.js b/src/core/renderers/webgl/systems/ContextSystem.js index ff958c7..8df85ea 100644 --- a/src/core/renderers/webgl/systems/ContextSystem.js +++ b/src/core/renderers/webgl/systems/ContextSystem.js @@ -75,7 +75,8 @@ */ createContext(canvas, options) { - var gl = canvas.getContext('webgl', options) || + var gl = //canvas.getContext('webgl2', options) || + canvas.getContext('webgl', options) || canvas.getContext('experimental-webgl', options); if (!gl) diff --git a/src/core/renderers/webgl/systems/FilterSystem.js b/src/core/renderers/webgl/systems/FilterSystem.js index f47335d..37e28d2 100644 --- a/src/core/renderers/webgl/systems/FilterSystem.js +++ b/src/core/renderers/webgl/systems/FilterSystem.js @@ -111,7 +111,7 @@ // TODO we should fit the rect around the transform.. } - else + else if (filters[0].autoFit) { sourceFrame.fit(filterData.stack[0].destinationFrame); } diff --git a/src/core/renderers/webgl/systems/shader/shader/mapType.js b/src/core/renderers/webgl/systems/shader/shader/mapType.js index 581dace..917538a 100644 --- a/src/core/renderers/webgl/systems/shader/shader/mapType.js +++ b/src/core/renderers/webgl/systems/shader/shader/mapType.js @@ -15,7 +15,7 @@ } } - return GL_TABLE[type]; + return GL_TABLE[type]; }; var GL_TABLE = null; diff --git a/src/core/settings.js b/src/core/settings.js index 18c5877..38ef45c 100644 --- a/src/core/settings.js +++ b/src/core/settings.js @@ -2,6 +2,15 @@ import canUploadSameBuffer from './utils/canUploadSameBuffer'; /** + * User's customizable globals for overriding the default PIXI settings, such + * as a renderer's default resolution, framerate, float percision, etc. + * @example + * // Use the native window resolution as the default resolution + * // will support high-density displays when rendering + * PIXI.settings.RESOLUTION = window.devicePixelRatio. + * + * // Disable interpolation when scaling, will make texture be pixelated + * PIXI.settings.SCALE_MODE = PIXI.SCALE_MODES.NEAREST; * @namespace PIXI.settings */ export default { @@ -101,6 +110,9 @@ * @property {boolean} clearBeforeRender=true * @property {boolean} preserveDrawingBuffer=false * @property {boolean} roundPixels=false + * @property {number} width=800 + * @property {number} height=600 + * @property {boolean} legacy=false */ RENDER_OPTIONS: { view: null, @@ -112,6 +124,9 @@ clearBeforeRender: true, preserveDrawingBuffer: false, roundPixels: false, + width: 800, + height: 600, + legacy: false, }, /** diff --git a/src/core/shader/Program.js b/src/core/shader/Program.js index c0b538b..07b4398 100644 --- a/src/core/shader/Program.js +++ b/src/core/shader/Program.js @@ -1,6 +1,6 @@ import extractUniformsFromSrc from './extractUniformsFromSrc'; import generateUniformsSync from './generateUniformsSync'; -import glCore from 'pixi-gl-core'; +import shaderUtils from '../renderers/webgl/systems/shader/shader'; import { ProgramCache } from '../utils'; import getTestContext from '../utils/getTestContext'; @@ -64,14 +64,17 @@ } else { - vertexSrc = glCore.shader.setPrecision(vertexSrc, 'mediump'); - fragmentSrc = glCore.shader.setPrecision(fragmentSrc, 'mediump'); + vertexSrc = shaderUtils.setPrecision(vertexSrc, 'mediump'); + fragmentSrc = shaderUtils.setPrecision(fragmentSrc, 'mediump'); - const program = glCore.shader.compileProgram(gl, vertexSrc, fragmentSrc); + const program = shaderUtils.compileProgram(gl, vertexSrc, fragmentSrc); + console.log("<<<<<<>>><<"); this.attributeData = this.getAttributeData(program, gl); this.uniformData = this.getUniformData(program, gl); + console.log(this.uniformData); + //gl.deleteProgram(program); } } @@ -95,13 +98,13 @@ for (let i = 0; i < totalAttributes; i++) { const attribData = gl.getActiveAttrib(program, i); - const type = glCore.shader.mapType(gl, attribData.type); + const type = shaderUtils.mapType(gl, attribData.type); /*eslint-disable */ const data = { type: type, name: attribData.name, - size: glCore.shader.mapSize(type), + size: shaderUtils.mapSize(type), location: 0, }; /*eslint-enable */ @@ -137,13 +140,14 @@ // TODO expose this as a prop? // const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); - const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); + //const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); for (let i = 0; i < totalUniforms; i++) { const uniformData = gl.getActiveUniform(program, i); const name = uniformData.name.replace(/\[.*?\]/, ''); - const type = glCore.shader.mapType(gl, uniformData.type); + const type = shaderUtils.mapType(gl, uniformData.type); + console.log('mapping ' + uniformData.type + ' to ' + type); // if (!name.match(maskRegex)) { @@ -151,7 +155,7 @@ uniforms[name] = { type: type, size: uniformData.size, - value: glCore.shader.defaultValue(type, uniformData.size), + value: shaderUtils.defaultValue(type, uniformData.size), }; /*eslint-enable */ } diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index e387e99..b57d095 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -147,12 +147,12 @@ // so if _width is 0 then width was not set.. if (this._width) { - this.scale.x = sign(this.scale.x) * this._width / this.texture.orig.width; + this.scale.x = sign(this.scale.x) * this._width / this._texture.orig.width; } if (this._height) { - this.scale.y = sign(this.scale.y) * this._height / this.texture.orig.height; + this.scale.y = sign(this.scale.y) * this._height / this._texture.orig.height; } } diff --git a/src/core/sprites/canvas/CanvasTinter.js b/src/core/sprites/canvas/CanvasTinter.js index 27e5e0c..fd9de5d 100644 --- a/src/core/sprites/canvas/CanvasTinter.js +++ b/src/core/sprites/canvas/CanvasTinter.js @@ -18,7 +18,7 @@ */ getTintedTexture: (sprite, color) => { - const texture = sprite.texture; + const texture = sprite._texture; color = CanvasTinter.roundColor(color); diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 5a4edda..8b9b797 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -344,6 +344,7 @@ const texture = this._texture; const style = this._style; + const padding = style.trim ? 0 : style.padding; texture.baseTexture.valid = true; texture.baseTexture.resolution = this.resolution; @@ -353,11 +354,11 @@ texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - texture.trim.x = -style.padding; - texture.trim.y = -style.padding; + texture.trim.x = -padding; + texture.trim.y = -padding; - texture.orig.width = texture._frame.width - (style.padding * 2); - texture.orig.height = texture._frame.height - (style.padding * 2); + texture.orig.width = texture._frame.width - (padding * 2); + texture.orig.height = texture._frame.height - (padding * 2); // call sprite onTextureUpdate to update scale if _width or _height were set this._onTextureUpdate(); diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index 853da3d..8a9c34b 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -114,6 +114,38 @@ this.cacheId = null; this.validate(); + + /** + * Fired when a not-immediately-available source finishes loading. + * + * @protected + * @event PIXI.BaseTexture#loaded + * @param {PIXI.BaseTexture} baseTexture - Resource loaded. + */ + + /** + * Fired when a not-immediately-available source fails to load. + * + * @protected + * @event PIXI.BaseTexture#error + * @param {PIXI.BaseTexture} baseTexture - Resource errored. + */ + + /** + * Fired when BaseTexture is updated. + * + * @protected + * @event PIXI.BaseTexture#update + * @param {PIXI.BaseTexture} baseTexture - Instance of texture being updated. + */ + + /** + * Fired when BaseTexture is destroyed. + * + * @protected + * @event PIXI.BaseTexture#dispose + * @param {PIXI.BaseTexture} baseTexture - Instance of texture being destroyed. + */ } setResource(resource) diff --git a/src/core/textures/Spritesheet.js b/src/core/textures/Spritesheet.js index d469c62..9b24f27 100644 --- a/src/core/textures/Spritesheet.js +++ b/src/core/textures/Spritesheet.js @@ -208,6 +208,7 @@ // lets also add the frame to pixi's global cache for fromFrame and fromImage functions TextureCache[i] = this.textures[i]; + this.textures[i].textureCacheId = i; } frameIndex++; diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 0a2c379..2213087 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -147,9 +147,9 @@ /** * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. * - * @event update - * @memberof PIXI.Texture# + * @event PIXI.Texture#update * @protected + * @param {PIXI.Texture} texture - Instance of texture being updated. */ this._updateID = 0; @@ -159,6 +159,15 @@ * @type {Object} */ this.transform = null; + + /** + * The id under which this Texture has been added to the texture cache. This is + * automatically set in certain cases, but may not always be accurate, particularly if + * the texture is in the cache under multiple ids. + * + * @member {string} + */ + this.textureCacheId = null; } /** @@ -225,11 +234,17 @@ this._uvs = null; this.trim = null; this.orig = null; + this.textureCacheId = null; this.valid = false; - this.off('dispose', this.dispose, this); - this.off('update', this.update, this); + for (const prop in TextureCache) + { + if (TextureCache[prop] === this) + { + delete TextureCache[prop]; + } + } } /** @@ -315,14 +330,15 @@ */ static fromLoader(source, imageUrl, name) { - // console.log('added from loader...') + // console.log('added from loader...') const resource = new ImageResource(source);//.from(imageUrl, crossorigin);// document.createElement('img'); - // console.log('base resource ' + resource.width); + // console.log('base resource ' + resource.width); const baseTexture = new BaseTexture(resource, settings.SCALE_MODE, getResolutionOfUrl(imageUrl)); + /// console.log('base width ' + baseTexture.width); const texture = new Texture(baseTexture); @@ -335,6 +351,7 @@ // lets also add the frame to pixi's global cache for fromFrame and fromImage fucntions BaseTextureCache[name] = baseTexture; TextureCache[name] = texture; + texture.textureCacheId = name; // also add references by url if they are different. if (name !== imageUrl) @@ -355,6 +372,10 @@ */ static addTextureToCache(texture, id) { + if (!texture.textureCacheId) + { + texture.textureCacheId = id; + } TextureCache[id] = texture; } @@ -495,6 +516,7 @@ */ Texture.EMPTY = new Texture(new BaseTexture()); removeAllHandlers(Texture.EMPTY); +removeAllHandlers(Texture.EMPTY.baseTexture); /** * A white texture of 10x10 size, used for graphics and other things @@ -505,3 +527,4 @@ */ Texture.WHITE = createWhiteTexture(); removeAllHandlers(Texture.WHITE); +removeAllHandlers(Texture.WHITE.baseTexture); diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index 59eb9fd..a9b9f51 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -1,6 +1,7 @@ import BaseTexture from './BaseTexture'; import { uid, BaseTextureCache } from '../utils'; -import * as ticker from '../ticker'; +import { shared } from '../ticker'; +import { UPDATE_PRIORITY } from '../const'; /** * A texture of a [playing] Video. @@ -125,7 +126,7 @@ if (!this._isAutoUpdating && this.autoUpdate) { - ticker.shared.add(this.update, this); + shared.add(this.update, this, UPDATE_PRIORITY.HIGH); this._isAutoUpdating = true; } } @@ -139,7 +140,7 @@ { if (this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); this._isAutoUpdating = false; } } @@ -187,7 +188,7 @@ { if (this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); } if (this.source && this.source._pixiId) @@ -281,12 +282,12 @@ if (!this._autoUpdate && this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); this._isAutoUpdating = false; } else if (this._autoUpdate && !this._isAutoUpdating) { - ticker.shared.add(this.update, this); + shared.add(this.update, this, UPDATE_PRIORITY.HIGH); this._isAutoUpdating = true; } } diff --git a/src/core/ticker/Ticker.js b/src/core/ticker/Ticker.js index 17a1517..40df138 100644 --- a/src/core/ticker/Ticker.js +++ b/src/core/ticker/Ticker.js @@ -1,12 +1,10 @@ import settings from '../settings'; -import EventEmitter from 'eventemitter3'; - -// Internal event used by composed emitter -const TICK = 'tick'; +import { UPDATE_PRIORITY } from '../const'; +import TickerListener from './TickerListener'; /** * A Ticker class that runs an update loop that other objects listen to. - * This class is composed around an EventEmitter object to add listeners + * This class is composed around listeners * meant for execution on the next requested animation frame. * Animation frames are requested only when necessary, * e.g. When the ticker is started and the emitter has listeners. @@ -22,10 +20,11 @@ constructor() { /** - * Internal emitter used to fire 'tick' event + * The first listener. All new listeners added are chained on this. * @private + * @type {TickerListener} */ - this._emitter = new EventEmitter(); + this._head = new TickerListener(null, null, Infinity); /** * Internal current frame request ID @@ -131,7 +130,7 @@ // Invoke listeners now this.update(time); // Listener side effects may have modified ticker state. - if (this.started && this._requestId === null && this._emitter.listeners(TICK, true)) + if (this.started && this._requestId === null && this._head.next) { this._requestId = requestAnimationFrame(this._tick); } @@ -148,7 +147,7 @@ */ _requestIfNeeded() { - if (this._requestId === null && this._emitter.listeners(TICK, true)) + if (this._requestId === null && this._head.next) { // ensure callbacks get correct delta this.lastTime = performance.now(); @@ -193,35 +192,72 @@ } /** - * Calls {@link module:eventemitter3.EventEmitter#on} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. + * Register a handler for tick events. Calls continuously unless + * it is removed or the ticker is stopped. * * @param {Function} fn - The listener function to be added for updates * @param {Function} [context] - The listener context + * @param {number} [priority=PIXI.UPDATE_PRIORITY.NORMAL] - The priority for emitting * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - add(fn, context) + add(fn, context, priority = UPDATE_PRIORITY.NORMAL) { - this._emitter.on(TICK, fn, context); - - this._startIfPossible(); - - return this; + return this._addListener(new TickerListener(fn, context, priority)); } /** - * Calls {@link module:eventemitter3.EventEmitter#once} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. + * Add a handler for the tick event which is only execute once. * * @param {Function} fn - The listener function to be added for one update * @param {Function} [context] - The listener context + * @param {number} [priority=PIXI.UPDATE_PRIORITY.NORMAL] - The priority for emitting * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - addOnce(fn, context) + addOnce(fn, context, priority = UPDATE_PRIORITY.NORMAL) { - this._emitter.once(TICK, fn, context); + return this._addListener(new TickerListener(fn, context, priority, true)); + } + + /** + * Internally adds the event handler so that it can be sorted by priority. + * Priority allows certain handler (user, AnimatedSprite, Interaction) to be run + * before the rendering. + * + * @private + * @param {TickerListener} listener - Current listener being added. + * @returns {PIXI.ticker.Ticker} This instance of a ticker + */ + _addListener(listener) + { + // For attaching to head + let current = this._head.next; + let previous = this._head; + + // Add the first item + if (!current) + { + listener.connect(previous); + } + else + { + // Go from highest to lowest priority + while (current) + { + if (listener.priority >= current.priority) + { + listener.connect(previous); + break; + } + previous = current; + current = current.next; + } + + // Not yet connected + if (!listener.previous) + { + listener.connect(previous); + } + } this._startIfPossible(); @@ -229,19 +265,33 @@ } /** - * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. - * It checks if the emitter has listeners for 'tick' event. - * If it does, then it cancels the animation frame. + * Removes any handlers matching the function and context parameters. + * If no handlers are left after removing, then it cancels the animation frame. * - * @param {Function} [fn] - The listener function to be removed + * @param {Function} fn - The listener function to be removed * @param {Function} [context] - The listener context to be removed * @returns {PIXI.ticker.Ticker} This instance of a ticker */ remove(fn, context) { - this._emitter.off(TICK, fn, context); + let listener = this._head.next; - if (!this._emitter.listeners(TICK, true)) + while (listener) + { + // We found a match, lets remove it + // no break to delete all possible matches + // incase a listener was added 2+ times + if (listener.match(fn, context)) + { + listener = listener.destroy(); + } + else + { + listener = listener.next; + } + } + + if (!this._head.next) { this._cancelIfNeeded(); } @@ -276,6 +326,25 @@ } /** + * Destroy the ticker and don't use after this. Calling + * this method removes all references to internal events. + */ + destroy() + { + this.stop(); + + let listener = this._head.next; + + while (listener) + { + listener = listener.destroy(true); + } + + this._head.destroy(); + this._head = null; + } + + /** * Triggers an update. An update entails setting the * current {@link PIXI.ticker.Ticker#elapsedMS}, * the current {@link PIXI.ticker.Ticker#deltaTime}, @@ -320,8 +389,22 @@ this.deltaTime = elapsedMS * settings.TARGET_FPMS * this.speed; + // Cache a local reference, in-case ticker is destroyed + // during the emit, we can still check for head.next + const head = this._head; + // Invoke listeners added to internal emitter - this._emitter.emit(TICK, this.deltaTime); + let listener = head.next; + + while (listener) + { + listener = listener.emit(this.deltaTime); + } + + if (!head.next) + { + this._cancelIfNeeded(); + } } else { diff --git a/src/core/ticker/TickerListener.js b/src/core/ticker/TickerListener.js new file mode 100644 index 0000000..2bedb34 --- /dev/null +++ b/src/core/ticker/TickerListener.js @@ -0,0 +1,158 @@ +/** + * Internal class for handling the priority sorting of ticker handlers. + * + * @private + * @class + * @memberof PIXI.ticker + */ +export default class TickerListener +{ + /** + * Constructor + * + * @param {Function} fn - The listener function to be added for one update + * @param {Function} [context=null] - The listener context + * @param {number} [priority=0] - The priority for emitting + * @param {boolean} [once=false] - If the handler should fire once + */ + constructor(fn, context = null, priority = 0, once = false) + { + /** + * The handler function to execute. + * @member {Function} + */ + this.fn = fn; + + /** + * The calling to execute. + * @member {Function} + */ + this.context = context; + + /** + * The current priority. + * @member {number} + */ + this.priority = priority; + + /** + * If this should only execute once. + * @member {boolean} + */ + this.once = once; + + /** + * The next item in chain. + * @member {TickerListener} + */ + this.next = null; + + /** + * The previous item in chain. + * @member {TickerListener} + */ + this.previous = null; + + /** + * `true` if this listener has been destroyed already. + * @member {boolean} + * @private + */ + this._destroyed = false; + } + + /** + * Simple compare function to figure out if a function and context match. + * + * @param {Function} fn - The listener function to be added for one update + * @param {Function} context - The listener context + * @return {boolean} `true` if the listener match the arguments + */ + match(fn, context) + { + context = context || null; + + return this.fn === fn && this.context === context; + } + + /** + * Emit by calling the current function. + * @param {number} deltaTime - time since the last emit. + * @return {TickerListener} Next ticker + */ + emit(deltaTime) + { + if (this.context) + { + this.fn.call(this.context, deltaTime); + } + else + { + this.fn(deltaTime); + } + + if (this.once) + { + this.destroy(); + } + + const redirect = this.next; + + // Soft-destroying should remove + // the next reference + if (this._destroyed) + { + this.next = null; + } + + return redirect; + } + + /** + * Connect to the list. + * @param {TickerListener} previous - Input node, previous listener + */ + connect(previous) + { + this.previous = previous; + if (previous.next) + { + previous.next.previous = this; + } + this.next = previous.next; + previous.next = this; + } + + /** + * Destroy and don't use after this. + * @param {boolean} [hard = false] `true` to remove the `next` reference, this + * is considered a hard destroy. Soft destroy maintains the next reference. + * @return {TickerListener} The listener to redirect while emitting or removing. + */ + destroy(hard = false) + { + this._destroyed = true; + this.fn = null; + this.context = null; + + // Disconnect, hook up next and previous + if (this.previous) + { + this.previous.next = this.next; + } + + if (this.next) + { + this.next.previous = this.previous; + } + + // Redirect to the next item + const redirect = this.previous; + + // Remove references + this.next = hard ? null : redirect; + this.previous = null; + + return redirect; + } +} diff --git a/src/core/ticker/index.js b/src/core/ticker/index.js index 4b3017c..c3020d4 100644 --- a/src/core/ticker/index.js +++ b/src/core/ticker/index.js @@ -45,8 +45,25 @@ const shared = new Ticker(); shared.autoStart = true; +shared.destroy = () => +{ + // protect destroying shared ticker + // this is used by other internal systems + // like AnimatedSprite and InteractionManager +}; /** + * This namespace contains an API for interacting with PIXI's internal global update loop. + * + * This ticker is used for rendering, {@link PIXI.extras.AnimatedSprite AnimatedSprite}, + * {@link PIXI.interaction.InteractionManager InteractionManager} and many other time-based PIXI systems. + * @example + * const ticker = new PIXI.ticker.Ticker(); + * ticker.stop(); + * ticker.add((deltaTime) => { + * // do something every frame + * }); + * ticker.start(); * @namespace PIXI.ticker */ export { shared, Ticker }; diff --git a/src/core/utils/index.js b/src/core/utils/index.js index be44750..7441f7b 100644 --- a/src/core/utils/index.js +++ b/src/core/utils/index.js @@ -9,6 +9,21 @@ let saidHello = false; /** + * Generalized convenience utilities for PIXI. + * @example + * // Extend PIXI's internal Event Emitter. + * class MyEmitter extends PIXI.utils.EventEmitter { + * constructor() { + * super(); + * console.log("Emitter created!"); + * } + * } + * + * // Get info on current device + * console.log(PIXI.utils.isMobile); + * + * // Convert hex color to string + * console.log(PIXI.utils.hex2string(0xff00ff)); // returns: "#ff00ff" * @namespace PIXI.utils */ export { diff --git a/src/deprecation.js b/src/deprecation.js index b9c73b3..33daeec 100644 --- a/src/deprecation.js +++ b/src/deprecation.js @@ -918,6 +918,35 @@ }); /** + * @method + * @name PIXI.prepare.BasePrepare#register + * @see PIXI.prepare.BasePrepare#registerFindHook + * @deprecated since version 4.4.2 + * @param {Function} [addHook] - Function call that takes two parameters: `item:*, queue:Array` + * function must return `true` if it was able to add item to the queue. + * @param {Function} [uploadHook] - Function call that takes two parameters: `prepare:CanvasPrepare, item:*` and + * function must return `true` if it was able to handle upload of item. + * @return {PIXI.BasePrepare} Instance of plugin for chaining. + */ +prepare.BasePrepare.prototype.register = function register(addHook, uploadHook) +{ + warn('renderer.plugins.prepare.register is now deprecated, ' + + 'please use renderer.plugins.prepare.registerFindHook & renderer.plugins.prepare.registerUploadHook'); + + if (addHook) + { + this.registerFindHook(addHook); + } + + if (uploadHook) + { + this.registerUploadHook(uploadHook); + } + + return this; +}; + +/** * The number of graphics or textures to upload to the GPU. * * @name PIXI.prepare.canvas.UPLOADS_PER_FRAME @@ -969,67 +998,73 @@ }, }); -Object.defineProperties(loaders.Resource.prototype, { - isJson: { - get() - { - warn('The isJson property is deprecated, please use `resource.type === Resource.TYPE.JSON`.'); +if (loaders.Loader) +{ + const Resource = loaders.Resource; + const Loader = loaders.Loader; - return this.type === loaders.Loader.Resource.TYPE.JSON; + Object.defineProperties(Resource.prototype, { + isJson: { + get() + { + warn('The isJson property is deprecated, please use `resource.type === Resource.TYPE.JSON`.'); + + return this.type === Resource.TYPE.JSON; + }, }, - }, - isXml: { - get() - { - warn('The isXml property is deprecated, please use `resource.type === Resource.TYPE.XML`.'); + isXml: { + get() + { + warn('The isXml property is deprecated, please use `resource.type === Resource.TYPE.XML`.'); - return this.type === loaders.Loader.Resource.TYPE.XML; + return this.type === Resource.TYPE.XML; + }, }, - }, - isImage: { - get() - { - warn('The isImage property is deprecated, please use `resource.type === Resource.TYPE.IMAGE`.'); + isImage: { + get() + { + warn('The isImage property is deprecated, please use `resource.type === Resource.TYPE.IMAGE`.'); - return this.type === loaders.Loader.Resource.TYPE.IMAGE; + return this.type === Resource.TYPE.IMAGE; + }, }, - }, - isAudio: { - get() - { - warn('The isAudio property is deprecated, please use `resource.type === Resource.TYPE.AUDIO`.'); + isAudio: { + get() + { + warn('The isAudio property is deprecated, please use `resource.type === Resource.TYPE.AUDIO`.'); - return this.type === loaders.Loader.Resource.TYPE.AUDIO; + return this.type === Resource.TYPE.AUDIO; + }, }, - }, - isVideo: { - get() - { - warn('The isVideo property is deprecated, please use `resource.type === Resource.TYPE.VIDEO`.'); + isVideo: { + get() + { + warn('The isVideo property is deprecated, please use `resource.type === Resource.TYPE.VIDEO`.'); - return this.type === loaders.Loader.Resource.TYPE.VIDEO; + return this.type === Resource.TYPE.VIDEO; + }, }, - }, -}); + }); -Object.defineProperties(loaders.Loader.prototype, { - before: { - get() - { - warn('The before() method is deprecated, please use pre().'); + Object.defineProperties(Loader.prototype, { + before: { + get() + { + warn('The before() method is deprecated, please use pre().'); - return this.pre; + return this.pre; + }, }, - }, - after: { - get() - { - warn('The after() method is deprecated, please use use().'); + after: { + get() + { + warn('The after() method is deprecated, please use use().'); - return this.use; + return this.use; + }, }, - }, -}); + }); +} /** * @name PIXI.interaction.interactiveTarget#defaultCursor diff --git a/src/extract/canvas/CanvasExtract.js b/src/extract/canvas/CanvasExtract.js index 941ca05..3ac5aee 100644 --- a/src/extract/canvas/CanvasExtract.js +++ b/src/extract/canvas/CanvasExtract.js @@ -8,7 +8,7 @@ * An instance of this class is automatically created by default, and can be found at renderer.plugins.extract * * @class - * @memberof PIXI + * @memberof PIXI.extract */ export default class CanvasExtract { @@ -21,9 +21,9 @@ /** * Collection of methods for extracting data (image, pixels, etc.) from a display object or render texture * - * @member {PIXI.CanvasExtract} extract + * @member {PIXI.extract.CanvasExtract} extract * @memberof PIXI.CanvasRenderer# - * @see PIXI.CanvasExtract + * @see PIXI.extract.CanvasExtract */ renderer.extract = this; } diff --git a/src/extract/index.js b/src/extract/index.js index 0f2170d..e9478c1 100644 --- a/src/extract/index.js +++ b/src/extract/index.js @@ -1,2 +1,22 @@ +/** + * This namespace provides renderer-specific plugins for exporting content from a renderer. + * For instance, these plugins can be used for saving an Image, Canvas element or for exporting the raw image data (pixels). + * + * Do not instantiate these plugins directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. + * @example + * // Create a new app (will auto-add extract plugin to renderer) + * const app = new PIXI.Application(); + * + * // Draw a red circle + * const graphics = new PIXI.Graphics() + * .beginFill(0xFF0000) + * .drawCircle(0, 0, 50); + * + * // Render the graphics as an HTMLImageElement + * const image = app.renderer.plugins.extract.image(graphics); + * document.body.appendChild(image); + * @namespace PIXI.extract + */ export { default as webgl } from './webgl/WebGLExtract'; export { default as canvas } from './canvas/CanvasExtract'; diff --git a/src/extract/webgl/WebGLExtract.js b/src/extract/webgl/WebGLExtract.js index 60228bb..1c3dd30 100644 --- a/src/extract/webgl/WebGLExtract.js +++ b/src/extract/webgl/WebGLExtract.js @@ -9,7 +9,7 @@ * An instance of this class is automatically created by default, and can be found at renderer.plugins.extract * * @class - * @memberof PIXI + * @memberof PIXI.extract */ export default class WebGLExtract { @@ -22,9 +22,9 @@ /** * Collection of methods for extracting data (image, pixels, etc.) from a display object or render texture * - * @member {PIXI.WebGLExtract} extract + * @member {PIXI.extract.WebGLExtract} extract * @memberof PIXI.WebGLRenderer# - * @see PIXI.WebGLExtract + * @see PIXI.extract.WebGLExtract */ renderer.extract = this; } diff --git a/src/extras/AnimatedSprite.js b/src/extras/AnimatedSprite.js index 13b804f..5297778 100644 --- a/src/extras/AnimatedSprite.js +++ b/src/extras/AnimatedSprite.js @@ -137,7 +137,7 @@ this.playing = true; if (this._autoUpdate) { - core.ticker.shared.add(this.update, this); + core.ticker.shared.add(this.update, this, core.UPDATE_PRIORITY.HIGH); } } diff --git a/src/extras/TextureTransform.js b/src/extras/TextureTransform.js index 78e6e40..16536d2 100644 --- a/src/extras/TextureTransform.js +++ b/src/extras/TextureTransform.js @@ -65,8 +65,36 @@ } /** + * Multiplies uvs array to transform + * @param {Float32Array} uvs mesh uvs + * @param {Float32Array} [out=uvs] output + * @returns {Float32Array} output + */ + multiplyUvs(uvs, out) + { + if (out === undefined) + { + out = uvs; + } + + const mat = this.mapCoord; + + for (let i = 0; i < uvs.length; i += 2) + { + const x = uvs[i]; + const y = uvs[i + 1]; + + out[i] = (x * mat.a) + (y * mat.c) + mat.tx; + out[i + 1] = (x * mat.b) + (y * mat.d) + mat.ty; + } + + return out; + } + + /** * updates matrices if texture was changed * @param {boolean} forceUpdate if true, matrices will be updated any case + * @returns {boolean} whether or not it was updated */ update(forceUpdate) { @@ -74,13 +102,13 @@ if (!tex || !tex.valid) { - return; + return false; } if (!forceUpdate && this._lastTextureID === tex._updateID) { - return; + return false; } this._lastTextureID = tex._updateID; @@ -110,5 +138,7 @@ frame[3] = (tex._frame.y + tex._frame.height - margin + offset) / texBase.height; this.uClampOffset[0] = offset / texBase.realWidth; this.uClampOffset[1] = offset / texBase.realHeight; + + return true; } } diff --git a/src/extras/TilingSprite.js b/src/extras/TilingSprite.js index 1f6014f..a3f027f 100644 --- a/src/extras/TilingSprite.js +++ b/src/extras/TilingSprite.js @@ -180,9 +180,9 @@ const transform = this.worldTransform; const resolution = renderer.resolution; const baseTexture = texture.baseTexture; - const baseTextureResolution = texture.baseTexture.resolution; - const modX = (this.tilePosition.x / this.tileScale.x) % texture._frame.width; - const modY = (this.tilePosition.y / this.tileScale.y) % texture._frame.height; + const baseTextureResolution = baseTexture.resolution; + const modX = ((this.tilePosition.x / this.tileScale.x) % texture._frame.width) * baseTextureResolution; + const modY = ((this.tilePosition.y / this.tileScale.y) % texture._frame.height) * baseTextureResolution; // create a nice shiny pattern! // TODO this needs to be refreshed if texture changes.. diff --git a/src/extras/cacheAsBitmap.js b/src/extras/cacheAsBitmap.js index 5dd5640..aea06a8 100644 --- a/src/extras/cacheAsBitmap.js +++ b/src/extras/cacheAsBitmap.js @@ -227,7 +227,16 @@ this.transform._parentID = -1; // restore the transform of the cached sprite to avoid the nasty flicker.. - this.updateTransform(); + if (!this.parent) + { + this.parent = renderer._tempDisplayObjectParent; + this.updateTransform(); + this.parent = null; + } + else + { + this.updateTransform(); + } // map the hit test.. this.containsPoint = cachedSprite.containsPoint.bind(cachedSprite); @@ -314,7 +323,17 @@ cachedSprite._bounds = this._bounds; cachedSprite.alpha = cacheAlpha; - this.updateTransform(); + if (!this.parent) + { + this.parent = renderer._tempDisplayObjectParent; + this.updateTransform(); + this.parent = null; + } + else + { + this.updateTransform(); + } + this.updateTransform = this.displayObjectUpdateTransform; this._cacheData.sprite = cachedSprite; @@ -358,9 +377,12 @@ * Destroys the cached object. * * @private + * @param {object|boolean} [options] - Options parameter. A boolean will act as if all options + * have been set to that value. + * Used when destroying containers, see the Container.destroy method. */ -DisplayObject.prototype._cacheAsBitmapDestroy = function _cacheAsBitmapDestroy() +DisplayObject.prototype._cacheAsBitmapDestroy = function _cacheAsBitmapDestroy(options) { this.cacheAsBitmap = false; - this.destroy(); + this.destroy(options); }; diff --git a/src/extras/index.js b/src/extras/index.js index bbd8bbf..a34d7ec 100644 --- a/src/extras/index.js +++ b/src/extras/index.js @@ -1,7 +1,7 @@ /** + * Additional PIXI DisplayObjects for animation, tiling and bitmap text. * @namespace PIXI.extras */ -export { default as TextureTransform } from './TextureTransform'; export { default as AnimatedSprite } from './AnimatedSprite'; export { default as TilingSprite } from './TilingSprite'; export { default as TilingSpriteRenderer } from './webgl/TilingSpriteRenderer'; diff --git a/src/extras/webgl/TilingSpriteRenderer.js b/src/extras/webgl/TilingSpriteRenderer.js index aeec172..eee3b5c 100644 --- a/src/extras/webgl/TilingSpriteRenderer.js +++ b/src/extras/webgl/TilingSpriteRenderer.js @@ -11,7 +11,7 @@ * WebGL renderer plugin for tiling sprites * * @class - * @memberof PIXI + * @memberof PIXI.extras * @extends PIXI.ObjectRenderer */ export default class TilingSpriteRenderer extends core.ObjectRenderer @@ -118,7 +118,7 @@ tempMat.invert(); if (isSimple) { - tempMat.append(uv.mapCoord); + tempMat.prepend(uv.mapCoord); } else { diff --git a/src/filters/displacement/DisplacementFilter.js b/src/filters/displacement/DisplacementFilter.js index d5355c3..b5d030c 100644 --- a/src/filters/displacement/DisplacementFilter.js +++ b/src/filters/displacement/DisplacementFilter.js @@ -35,7 +35,7 @@ this.maskSprite = sprite; this.maskMatrix = maskMatrix; - this.uniforms.mapSampler = sprite.texture; + this.uniforms.mapSampler = sprite._texture; this.uniforms.filterMatrix = maskMatrix; this.uniforms.scale = { x: 1, y: 1 }; diff --git a/src/filters/index.js b/src/filters/index.js index f3e8862..7ceb50a 100644 --- a/src/filters/index.js +++ b/src/filters/index.js @@ -1,4 +1,21 @@ /** + * This namespace contains WebGL-only display filters that can be applied + * to DisplayObjects using the {@link PIXI.DisplayObject#filters filters} property. + * @example + * // Create a new application + * const app = new PIXI.Application(); + * + * // Draw a green rectangle + * const rect = new PIXI.Graphics() + * .beginFill(0x00ff00) + * .drawRect(40, 40, 200, 200); + * + * // Add a blur filter + * rect.filters = [new PIXI.filters.BlurFilter()]; + * + * // Display rectangle + * app.stage.addChild(rect); + * document.body.appendChild(app.view); * @namespace PIXI.filters */ export { default as FXAAFilter } from './fxaa/FXAAFilter'; diff --git a/src/index.js b/src/index.js index c553427..77da490 100644 --- a/src/index.js +++ b/src/index.js @@ -20,6 +20,14 @@ import { utils } from './core'; utils.mixins.performMixins(); +/** + * Alias for {@link PIXI.loaders.shared}. + * @name loader + * @memberof PIXI + * @type {PIXI.loader.Loader} + */ +const loader = loaders.shared || null; + export { accessibility, extract, @@ -30,18 +38,8 @@ mesh, particles, prepare, + loader, }; -/** - * A premade instance of the loader that can be used to load resources. - * - * @name loader - * @memberof PIXI - * @property {PIXI.loaders.Loader} - */ -const loader = loaders && loaders.Loader ? new loaders.Loader() : null; // check is there in case user excludes loader lib - -export { loader }; - // Always export pixi globally. global.PIXI = exports; // eslint-disable-line diff --git a/src/interaction/InteractionData.js b/src/interaction/InteractionData.js index 02d53a3..449f8cc 100644 --- a/src/interaction/InteractionData.js +++ b/src/interaction/InteractionData.js @@ -30,7 +30,10 @@ /** * When passed to an event handler, this will be the original DOM Event that was captured * - * @member {Event} + * @see https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent + * @see https://developer.mozilla.org/en-US/docs/Web/API/TouchEvent + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent + * @member {MouseEvent|TouchEvent|PointerEvent} */ this.originalEvent = null; } diff --git a/src/interaction/InteractionManager.js b/src/interaction/InteractionManager.js index 8baf13a..2d75c7c 100644 --- a/src/interaction/InteractionManager.js +++ b/src/interaction/InteractionManager.js @@ -13,6 +13,14 @@ const MOUSE_POINTER_ID = 'MOUSE'; +// helpers for hitTest() - only used inside hitTest() +const hitTestEvent = { + target: null, + data: { + global: null, + }, +}; + /** * The interaction manager deals with mouse, touch and pointer events. Any DisplayObject can be interactive * if its interactive parameter is set to true @@ -245,67 +253,60 @@ this.setTargetElement(this.renderer.view, this.renderer.resolution); /** - * Fired when a pointer device button (usually a mouse button) is pressed on the display + * Fired when a pointer device button (usually a mouse left-button) is pressed on the display * object. * - * @event mousedown - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mousedown + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device secondary button (usually a mouse right-button) is pressed * on the display object. * - * @event rightdown - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightdown + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** - * Fired when a pointer device button (usually a mouse button) is released over the display + * Fired when a pointer device button (usually a mouse left-button) is released over the display * object. * - * @event mouseup - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseup + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device secondary button (usually a mouse right-button) is released * over the display object. * - * @event rightup - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightup + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** - * Fired when a pointer device button (usually a mouse button) is pressed and released on + * Fired when a pointer device button (usually a mouse left-button) is pressed and released on * the display object. * - * @event click - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#click + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device secondary button (usually a mouse right-button) is pressed * and released on the display object. * - * @event rightclick - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightclick + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** - * Fired when a pointer device button (usually a mouse button) is released outside the + * Fired when a pointer device button (usually a mouse left-button) is released outside the * display object that initially registered a * [mousedown]{@link PIXI.interaction.InteractionManager#event:mousedown}. * - * @event mouseupoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** @@ -313,146 +314,362 @@ * outside the display object that initially registered a * [rightdown]{@link PIXI.interaction.InteractionManager#event:rightdown}. * - * @event rightupoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device (usually a mouse) is moved while over the display object * - * @event mousemove - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mousemove + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device (usually a mouse) is moved onto the display object * - * @event mouseover - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseover + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device (usually a mouse) is moved off the display object * - * @event mouseout - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseout + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is pressed on the display object. * - * @event pointerdown - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerdown + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is released over the display object. * - * @event pointerup - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerup + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when the operating system cancels a pointer event * - * @event pointercancel - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointercancel + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is pressed and released on the display object. * - * @event pointertap - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointertap + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is released outside the display object that initially * registered a [pointerdown]{@link PIXI.interaction.InteractionManager#event:pointerdown}. * - * @event pointerupoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device is moved while over the display object * - * @event pointermove - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointermove + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device is moved onto the display object * - * @event pointerover - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerover + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device is moved off the display object * - * @event pointerout - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerout + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is placed on the display object. * - * @event touchstart - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchstart + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is removed from the display object. * - * @event touchend - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchend + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when the operating system cancels a touch * - * @event touchcancel - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchcancel + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is placed and removed from the display object. * - * @event tap - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#tap + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is removed outside of the display object that initially * registered a [touchstart]{@link PIXI.interaction.InteractionManager#event:touchstart}. * - * @event touchendoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchendoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is moved along the display object. * - * @event touchmove - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchmove + * @param {PIXI.interaction.InteractionData} event - Interaction data */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is pressed on the display. + * object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mousedown + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is pressed + * on the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightdown + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is released over the display + * object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseup + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is released + * over the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightup + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is pressed and released on + * the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#click + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is pressed + * and released on the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightclick + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is released outside the + * display object that initially registered a + * [mousedown]{@link PIXI.DisplayObject#event:mousedown}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is released + * outside the display object that initially registered a + * [rightdown]{@link PIXI.DisplayObject#event:rightdown}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device (usually a mouse) is moved while over the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mousemove + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device (usually a mouse) is moved onto the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseover + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device (usually a mouse) is moved off the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseout + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is pressed on the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerdown + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is released over the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerup + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when the operating system cancels a pointer event. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointercancel + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is pressed and released on the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointertap + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is released outside the display object that initially + * registered a [pointerdown]{@link PIXI.DisplayObject#event:pointerdown}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device is moved while over the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointermove + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device is moved onto the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerover + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device is moved off the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerout + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is placed on the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchstart + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is removed from the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchend + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when the operating system cancels a touch. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchcancel + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is placed and removed from the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#tap + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is removed outside of the display object that initially + * registered a [touchstart]{@link PIXI.DisplayObject#event:touchstart}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchendoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is moved along the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchmove + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + } + + /** + * Hit tests a point against the display tree, returning the first interactive object that is hit. + * + * @param {PIXI.Point} globalPoint - A point to hit test with, in global space. + * @param {PIXI.Container} [root] - The root display object to start from. If omitted, defaults + * to the last rendered root of the associated renderer. + * @return {PIXI.DisplayObject} The hit display object, if any. + */ + hitTest(globalPoint, root) + { + // clear the target for our hit test + hitTestEvent.target = null; + // assign the global point + hitTestEvent.data.global = globalPoint; + // ensure safety of the root + if (!root) + { + root = this.renderer._lastObjectRendered; + } + // run the hit test + this.processInteractive(hitTestEvent, root, null, true); + // return our found object - it'll be null if we didn't hit anything + + return hitTestEvent.target; } /** @@ -487,7 +704,7 @@ return; } - core.ticker.shared.add(this.update, this); + core.ticker.shared.add(this.update, this, core.UPDATE_PRIORITY.INTERACTION); if (window.navigator.msPointerEnabled) { @@ -804,8 +1021,6 @@ } } - let keepHitTestingAfterChildren = hitTest; - // ** FREE TIP **! If an object is not interactive or has no buttons in it // (such as a game scene!) set interactiveChildren to false for that displayObject. // This will allow pixi to completely ignore and bypass checking the displayObjects children. @@ -818,7 +1033,9 @@ const child = children[i]; // time to get recursive.. if this function will return if something is hit.. - if (this.processInteractive(interactionEvent, child, func, hitTest, interactiveParent)) + const childHit = this.processInteractive(interactionEvent, child, func, hitTest, interactiveParent); + + if (childHit) { // its a good idea to check if a child has lost its parent. // this means it has been removed whilst looping so its best @@ -827,8 +1044,6 @@ continue; } - hit = true; - // we no longer need to hit test any more objects in this container as we we // now know the parent has been hit interactiveParent = false; @@ -838,36 +1053,41 @@ // This means we no longer need to hit test anything else. We still need to run // through all objects, but we don't need to perform any hit tests. - keepHitTestingAfterChildren = false; - - if (child.interactive) + if (childHit) { - hitTest = false; + if (interactionEvent.target) + { + hitTest = false; + } + hit = true; } - - // we can break now as we have hit an object. } } } - hitTest = keepHitTestingAfterChildren; - // no point running this if the item is not interactive or does not have an interactive parent. if (interactive) { // if we are hit testing (as in we have no hit any objects yet) // We also don't need to worry about hit testing if once of the displayObjects children - // has already been hit! - if (hitTest && !hit) + // has already been hit - but only if it was interactive, otherwise we need to keep + // looking for an interactive child, just in case we hit one + if (hitTest && !interactionEvent.target) { if (displayObject.hitArea) { displayObject.worldTransform.applyInverse(point, this._tempPoint); - hit = displayObject.hitArea.contains(this._tempPoint.x, this._tempPoint.y); + if (displayObject.hitArea.contains(this._tempPoint.x, this._tempPoint.y)) + { + hit = true; + } } else if (displayObject.containsPoint) { - hit = displayObject.containsPoint(point); + if (displayObject.containsPoint(point)) + { + hit = true; + } } } @@ -878,7 +1098,10 @@ interactionEvent.target = displayObject; } - func(interactionEvent, displayObject, hit); + if (func) + { + func(interactionEvent, displayObject, !!hit); + } } } @@ -914,7 +1137,7 @@ { const event = events[i]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -998,7 +1221,7 @@ { const event = events[i]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1098,7 +1321,7 @@ const test = isRightButton ? flags.RIGHT_DOWN : flags.LEFT_DOWN; - const isDown = trackingData !== undefined && (trackingData.flags | test); + const isDown = trackingData !== undefined && (trackingData.flags & test); if (hit) { @@ -1118,11 +1341,11 @@ { if (isRightButton) { - trackingData.rightDown = hit; + trackingData.rightDown = false; } else { - trackingData.leftDown = hit; + trackingData.leftDown = false; } } } @@ -1180,7 +1403,7 @@ { const event = events[i]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1255,7 +1478,7 @@ this.setCursorMode(null); } - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1268,6 +1491,12 @@ { this.emit('mouseout', interactionEvent); } + else + { + // we can get touchleave events after touchend, so we want to make sure we don't + // introduce memory leaks + this.releaseInteractionDataForPointerId(interactionData.identifier); + } } /** @@ -1344,7 +1573,7 @@ // Only mouse and pointer can call onPointerOver, so events will always be length 1 const event = events[0]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1366,12 +1595,14 @@ * Get InteractionData for a given pointerId. Store that data as well * * @private - * @param {number} pointerId - Identifier from a pointer event + * @param {PointerEvent} event - Normalized pointer event, output from normalizeToPointerData * @return {InteractionData} - Interaction data for the given pointer identifier */ - getInteractionDataForPointerId(pointerId) + getInteractionDataForPointerId(event) { - if (pointerId === MOUSE_POINTER_ID) + const pointerId = event.pointerId; + + if (pointerId === MOUSE_POINTER_ID || event.pointerType === 'mouse') { return this.mouse; } diff --git a/src/interaction/InteractionTrackingData.js b/src/interaction/InteractionTrackingData.js index 6a1f69e..8493b63 100644 --- a/src/interaction/InteractionTrackingData.js +++ b/src/interaction/InteractionTrackingData.js @@ -35,8 +35,10 @@ } /** + * Unique pointer id of the event + * * @readonly - * @type {number} Unique pointer id of the event + * @member {number} */ get pointerId() { diff --git a/src/interaction/index.js b/src/interaction/index.js index 56c5b09..72f7d71 100644 --- a/src/interaction/index.js +++ b/src/interaction/index.js @@ -1,4 +1,8 @@ /** + * This namespace contains a renderer plugin for handling mouse, pointer, and touch events. + * + * Do not instantiate this plugin directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. * @namespace PIXI.interaction */ export { default as InteractionData } from './InteractionData'; diff --git a/src/interaction/interactiveTarget.js b/src/interaction/interactiveTarget.js index d252fba..cebac40 100644 --- a/src/interaction/interactiveTarget.js +++ b/src/interaction/interactiveTarget.js @@ -2,7 +2,7 @@ * Default property values of interactive objects * Used by {@link PIXI.interaction.InteractionManager} to automatically give all DisplayObjects these properties * - * @mixin + * @private * @name interactiveTarget * @memberof PIXI.interaction * @example @@ -14,18 +14,28 @@ * ); */ export default { + /** - * Determines if the displayObject be clicked/touched + * Enable interaction events for the DisplayObject. Touch, pointer and mouse + * events will not be emitted unless `interactive` is set to `true`. * - * @inner {boolean} + * @example + * const sprite = new PIXI.Sprite(texture); + * sprite.interactive = true; + * sprite.on('tap', (event) => { + * //handle event + * }); + * @member {boolean} + * @memberof PIXI.DisplayObject# */ interactive: false, /** * Determines if the children to the displayObject can be clicked/touched - * Setting this to false allows pixi to bypass a recursive hitTest function + * Setting this to false allows pixi to bypass a recursive `hitTest` function * - * @inner {boolean} + * @member {boolean} + * @memberof PIXI.Container# */ interactiveChildren: true, @@ -33,7 +43,12 @@ * Interaction shape. Children will be hit first, then this shape will be checked. * Setting this will cause this shape to be checked in hit tests rather than the displayObject's bounds. * - * @inner {PIXI.Rectangle|PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.RoundedRectangle} + * @example + * const sprite = new PIXI.Sprite(texture); + * sprite.interactive = true; + * sprite.hitArea = new PIXI.Rectangle(0, 0, 100, 100); + * @member {PIXI.Rectangle|PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.RoundedRectangle} + * @memberof PIXI.DisplayObject# */ hitArea: null, @@ -41,8 +56,12 @@ * If enabled, the mouse cursor use the pointer behavior when hovered over the displayObject if it is interactive * Setting this changes the 'cursor' property to `'pointer'`. * + * @example + * const sprite = new PIXI.Sprite(texture); + * sprite.interactive = true; + * sprite.buttonMode = true; * @member {boolean} - * @memberof PIXI.interaction.interactiveTarget# + * @memberof PIXI.DisplayObject# */ get buttonMode() { @@ -64,9 +83,14 @@ * This defines what cursor mode is used when the mouse cursor * is hovered over the displayObject. * + * @example + * const sprite = new PIXI.Sprite(texture); + * sprite.interactive = true; + * sprite.cursor = 'wait'; * @see https://developer.mozilla.org/en/docs/Web/CSS/cursor * - * @inner {string} + * @member {string} + * @memberof PIXI.DisplayObject# */ cursor: null, @@ -74,7 +98,7 @@ * Internal set of all active pointers, by identifier * * @member {Map} - * @memberof PIXI.interaction.interactiveTarget# + * @memberof PIXI.DisplayObject# * @private */ get trackedPointers() @@ -87,7 +111,8 @@ /** * Map of all tracked pointers, by identifier. Use trackedPointers to access. * - * @private {Map} + * @private + * @type {Map} */ _trackedPointers: undefined, }; diff --git a/src/loaders/index.js b/src/loaders/index.js index 5189793..daa1fc4 100644 --- a/src/loaders/index.js +++ b/src/loaders/index.js @@ -1,9 +1,22 @@ +import Application from '../core/Application'; +import Loader from './loader'; + /** + * This namespace contains APIs which extends the {@link https://github.com/englercj/resource-loader resource-loader} module + * for loading assets, data, and other resources dynamically. + * @example + * const loader = new PIXI.loaders.Loader(); + * loader.add('bunny', 'data/bunny.png') + * .add('spaceship', 'assets/spritesheet.json'); + * loader.load((loader, resources) => { + * // resources.bunny + * // resources.spaceship + * }); * @namespace PIXI.loaders */ -export { default as Loader } from './loader'; +export { Loader }; export { default as bitmapFontParser, parse as parseBitmapFontData } from './bitmapFontParser'; -export { default as spritesheetParser } from './spritesheetParser'; +export { default as spritesheetParser, getResourcePath } from './spritesheetParser'; export { default as textureParser } from './textureParser'; /** @@ -13,3 +26,55 @@ * @memberof PIXI.loaders */ export { Resource } from 'resource-loader'; + +/** + * A premade instance of the loader that can be used to load resources. + * @name shared + * @memberof PIXI.loaders + * @type {PIXI.loaders.Loader} + */ +const shared = new Loader(); + +shared.destroy = () => +{ + // protect destroying shared loader +}; + +export { shared }; + +// Mixin the loader construction +const AppPrototype = Application.prototype; + +AppPrototype._loader = null; + +/** + * Loader instance to help with asset loading. + * @name PIXI.Application#loader + * @type {PIXI.loaders.Loader} + */ +Object.defineProperty(AppPrototype, 'loader', { + get() + { + if (!this._loader) + { + const sharedLoader = this._options.sharedLoader; + + this._loader = sharedLoader ? shared : new Loader(); + } + + return this._loader; + }, +}); + +// Override the destroy function +// making sure to destroy the current Loader +AppPrototype._parentDestroy = AppPrototype.destroy; +AppPrototype.destroy = function destroy() +{ + if (this._loader) + { + this._loader.destroy(); + this._loader = null; + } + this._parentDestroy(); +}; diff --git a/src/loaders/loader.js b/src/loaders/loader.js index 767a411..59b8000 100644 --- a/src/loaders/loader.js +++ b/src/loaders/loader.js @@ -90,6 +90,15 @@ { Loader._pixiMiddleware.push(fn); } + + /** + * Destroy the loader, removes references. + */ + destroy() + { + this.removeAllListeners(); + this.reset(); + } } // Copy EE3 prototype (mixin) diff --git a/src/loaders/spritesheetParser.js b/src/loaders/spritesheetParser.js index e57dfb2..eda9584 100644 --- a/src/loaders/spritesheetParser.js +++ b/src/loaders/spritesheetParser.js @@ -1,12 +1,11 @@ import { Resource } from 'resource-loader'; -import path from 'path'; +import url from 'url'; import { Spritesheet } from '../core'; export default function () { return function spritesheetParser(resource, next) { - let resourcePath; const imageResourceName = `${resource.name}_image`; // skip if no data, its not json, it isn't spritesheet data, or the image resource already exists @@ -28,15 +27,7 @@ parentResource: resource, }; - // Prepend url path unless the resource image is a data url - if (resource.isDataUrl) - { - resourcePath = resource.data.meta.image; - } - else - { - resourcePath = `${path.dirname(resource.url.replace(this.baseUrl, ''))}/${resource.data.meta.image}`; - } + const resourcePath = getResourcePath(resource, this.baseUrl); // load the image for this sheet this.add(imageResourceName, resourcePath, loadOptions, function onImageLoad(res) @@ -56,3 +47,14 @@ }); }; } + +export function getResourcePath(resource, baseUrl) +{ + // Prepend url path unless the resource image is a data url + if (resource.isDataUrl) + { + return resource.data.meta.image; + } + + return url.resolve(resource.url.replace(baseUrl, ''), resource.data.meta.image); +} diff --git a/src/mesh/canvas/CanvasMeshRenderer.js b/src/mesh/canvas/CanvasMeshRenderer.js index 297205f..f7899e1 100644 --- a/src/mesh/canvas/CanvasMeshRenderer.js +++ b/src/mesh/canvas/CanvasMeshRenderer.js @@ -134,12 +134,33 @@ const textureWidth = base.width; const textureHeight = base.height; - const u0 = uvs[index0] * base.width; - const u1 = uvs[index1] * base.width; - const u2 = uvs[index2] * base.width; - const v0 = uvs[index0 + 1] * base.height; - const v1 = uvs[index1 + 1] * base.height; - const v2 = uvs[index2 + 1] * base.height; + let u0; + let u1; + let u2; + let v0; + let v1; + let v2; + + if (mesh.uploadUvTransform) + { + const ut = mesh._uvTransform.mapCoord; + + u0 = ((uvs[index0] * ut.a) + (uvs[index0 + 1] * ut.c) + ut.tx) * base.width; + u1 = ((uvs[index1] * ut.a) + (uvs[index1 + 1] * ut.c) + ut.tx) * base.width; + u2 = ((uvs[index2] * ut.a) + (uvs[index2 + 1] * ut.c) + ut.tx) * base.width; + v0 = ((uvs[index0] * ut.b) + (uvs[index0 + 1] * ut.d) + ut.ty) * base.height; + v1 = ((uvs[index1] * ut.b) + (uvs[index1 + 1] * ut.d) + ut.ty) * base.height; + v2 = ((uvs[index2] * ut.b) + (uvs[index2 + 1] * ut.d) + ut.ty) * base.height; + } + else + { + u0 = uvs[index0] * base.width; + u1 = uvs[index1] * base.width; + u2 = uvs[index2] * base.width; + v0 = uvs[index0 + 1] * base.height; + v1 = uvs[index1 + 1] * base.height; + v2 = uvs[index2 + 1] * base.height; + } let x0 = vertices[index0]; let x1 = vertices[index1]; diff --git a/src/mesh/webgl/MeshRenderer.js b/src/mesh/webgl/MeshRenderer.js index 78d24c3..ab99fcd 100644 --- a/src/mesh/webgl/MeshRenderer.js +++ b/src/mesh/webgl/MeshRenderer.js @@ -2,6 +2,8 @@ const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; +const matrixIdentity = core.Matrix.IDENTITY; + /** * WebGL renderer plugin for tiling sprites * @@ -64,6 +66,7 @@ mesh.geometry.glVertexArrayObjects[this.CONTEXT_UID].draw(mesh.drawMode, mesh.size, mesh.start, mesh.geometry.instanceCount); } + /** * draws mesh * @param {PIXI.mesh.RawMesh} mesh mesh instance diff --git a/src/mesh/webgl/mesh.vert b/src/mesh/webgl/mesh.vert index a337aef..acc096c 100644 --- a/src/mesh/webgl/mesh.vert +++ b/src/mesh/webgl/mesh.vert @@ -1,8 +1,9 @@ attribute vec2 aVertexPosition; attribute vec2 aTextureCoord; -uniform mat3 translationMatrix; uniform mat3 projectionMatrix; +uniform mat3 translationMatrix; +uniform mat3 uTransform; varying vec2 vTextureCoord; @@ -10,5 +11,5 @@ { gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - vTextureCoord = aTextureCoord; + vTextureCoord = (uTransform * vec3(aTextureCoord, 1.0)).xy; } diff --git a/src/particles/ParticleContainer.js b/src/particles/ParticleContainer.js index 67796cf..63f7791 100644 --- a/src/particles/ParticleContainer.js +++ b/src/particles/ParticleContainer.js @@ -313,10 +313,10 @@ frame.y * resolution, frame.width * resolution, frame.height * resolution, - positionX * resolution, - positionY * resolution, - finalWidth * resolution, - finalHeight * resolution + positionX * renderer.resolution, + positionY * renderer.resolution, + finalWidth * renderer.resolution, + finalHeight * renderer.resolution ); } } diff --git a/src/prepare/BasePrepare.js b/src/prepare/BasePrepare.js index 09eb5e5..e45c4dc 100644 --- a/src/prepare/BasePrepare.js +++ b/src/prepare/BasePrepare.js @@ -18,6 +18,18 @@ * basic queuing functionality and is extended by {@link PIXI.prepare.WebGLPrepare} and {@link PIXI.prepare.CanvasPrepare} * to provide preparation capabilities specific to their respective renderers. * + * @example + * // Create a sprite + * const sprite = new PIXI.Sprite.fromImage('something.png'); + * + * // Load object into GPU + * app.renderer.plugins.prepare.upload(sprite, () => { + * + * //Texture(s) has been uploaded to GPU + * app.stage.addChild(sprite); + * + * }) + * * @abstract * @class * @memberof PIXI.prepare @@ -100,8 +112,16 @@ this.prepareItems(); }; - this.register(findText, drawText); - this.register(findTextStyle, calculateTextStyle); + // hooks to find the correct texture + this.registerFindHook(findText); + this.registerFindHook(findTextStyle); + this.registerFindHook(findMultipleBaseTextures); + this.registerFindHook(findBaseTexture); + this.registerFindHook(findTexture); + + // upload hooks + this.registerUploadHook(drawText); + this.registerUploadHook(calculateTextStyle); } /** @@ -138,7 +158,7 @@ if (!this.ticking) { this.ticking = true; - SharedTicker.addOnce(this.tick, this); + SharedTicker.addOnce(this.tick, this, core.UPDATE_PRIORITY.UTILITY); } } else if (done) @@ -172,13 +192,16 @@ const item = this.queue[0]; let uploaded = false; - for (let i = 0, len = this.uploadHooks.length; i < len; i++) + if (item && !item._destroyed) { - if (this.uploadHooks[i](this.uploadHookHelper, item)) + for (let i = 0, len = this.uploadHooks.length; i < len; i++) { - this.queue.shift(); - uploaded = true; - break; + if (this.uploadHooks[i](this.uploadHookHelper, item)) + { + this.queue.shift(); + uploaded = true; + break; + } } } @@ -205,26 +228,36 @@ else { // if we are not finished, on the next rAF do this again - SharedTicker.addOnce(this.tick, this); + SharedTicker.addOnce(this.tick, this, core.UPDATE_PRIORITY.UTILITY); } } /** - * Adds hooks for finding and uploading items. + * Adds hooks for finding items. * - * @param {Function} [addHook] - Function call that takes two parameters: `item:*, queue:Array` - function must return `true` if it was able to add item to the queue. - * @param {Function} [uploadHook] - Function call that takes two parameters: `prepare:CanvasPrepare, item:*` and - * function must return `true` if it was able to handle upload of item. - * @return {PIXI.CanvasPrepare} Instance of plugin for chaining. + * @param {Function} addHook - Function call that takes two parameters: `item:*, queue:Array` + * function must return `true` if it was able to add item to the queue. + * @return {PIXI.BasePrepare} Instance of plugin for chaining. */ - register(addHook, uploadHook) + registerFindHook(addHook) { if (addHook) { this.addHooks.push(addHook); } + return this; + } + + /** + * Adds hooks for uploading items. + * + * @param {Function} uploadHook - Function call that takes two parameters: `prepare:CanvasPrepare, item:*` and + * function must return `true` if it was able to handle upload of item. + * @return {PIXI.BasePrepare} Instance of plugin for chaining. + */ + registerUploadHook(uploadHook) + { if (uploadHook) { this.uploadHooks.push(uploadHook); @@ -287,6 +320,88 @@ } /** + * Built-in hook to find multiple textures from objects like AnimatedSprites. + * + * @private + * @param {PIXI.DisplayObject} item - Display object to check + * @param {Array<*>} queue - Collection of items to upload + * @return {boolean} if a PIXI.Texture object was found. + */ +function findMultipleBaseTextures(item, queue) +{ + let result = false; + + // Objects with mutliple textures + if (item && item._textures && item._textures.length) + { + for (let i = 0; i < item._textures.length; i++) + { + if (item._textures[i] instanceof core.Texture) + { + const baseTexture = item._textures[i].baseTexture; + + if (queue.indexOf(baseTexture) === -1) + { + queue.push(baseTexture); + result = true; + } + } + } + } + + return result; +} + +/** + * Built-in hook to find BaseTextures from Sprites. + * + * @private + * @param {PIXI.DisplayObject} item - Display object to check + * @param {Array<*>} queue - Collection of items to upload + * @return {boolean} if a PIXI.Texture object was found. + */ +function findBaseTexture(item, queue) +{ + // Objects with textures, like Sprites/Text + if (item instanceof core.BaseTexture) + { + if (queue.indexOf(item) === -1) + { + queue.push(item); + } + + return true; + } + + return false; +} + +/** + * Built-in hook to find textures from objects. + * + * @private + * @param {PIXI.DisplayObject} item - Display object to check + * @param {Array<*>} queue - Collection of items to upload + * @return {boolean} if a PIXI.Texture object was found. + */ +function findTexture(item, queue) +{ + if (item._texture && item._texture instanceof core.Texture) + { + const texture = item._texture.baseTexture; + + if (queue.indexOf(texture) === -1) + { + queue.push(texture); + } + + return true; + } + + return false; +} + +/** * Built-in hook to draw PIXI.Text to its texture. * * @private diff --git a/src/prepare/canvas/CanvasPrepare.js b/src/prepare/canvas/CanvasPrepare.js index 0224bb5..09c207a 100644 --- a/src/prepare/canvas/CanvasPrepare.js +++ b/src/prepare/canvas/CanvasPrepare.js @@ -43,7 +43,7 @@ this.ctx = this.canvas.getContext('2d'); // Add textures to upload - this.register(findBaseTextures, uploadBaseTextures); + this.registerUploadHook(uploadBaseTextures); } /** @@ -89,39 +89,4 @@ return false; } -/** - * Built-in hook to find textures from Sprites. - * - * @private - * @param {PIXI.DisplayObject} item -Display object to check - * @param {Array<*>} queue - Collection of items to upload - * @return {boolean} if a PIXI.Texture object was found. - */ -function findBaseTextures(item, queue) -{ - // Objects with textures, like Sprites/Text - if (item instanceof core.BaseTexture) - { - if (queue.indexOf(item) === -1) - { - queue.push(item); - } - - return true; - } - else if (item._texture && item._texture instanceof core.Texture) - { - const texture = item._texture.baseTexture; - - if (queue.indexOf(texture) === -1) - { - queue.push(texture); - } - - return true; - } - - return false; -} - core.CanvasRenderer.registerPlugin('prepare', CanvasPrepare); diff --git a/src/prepare/index.js b/src/prepare/index.js index f559c45..e7491ec 100644 --- a/src/prepare/index.js +++ b/src/prepare/index.js @@ -1,4 +1,29 @@ /** + * The prepare namespace provides renderer-specific plugins for pre-rendering DisplayObjects. These plugins are useful for + * asynchronously preparing assets, textures, graphics waiting to be displayed. + * + * Do not instantiate these plugins directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. + * @example + * // Create a new application + * const app = new PIXI.Application(); + * document.body.appendChild(app.view); + * + * // Don't start rendering right away + * app.stop(); + * + * // create a display object + * const rect = new PIXI.Graphics() + * .beginFill(0x00ff00) + * .drawRect(40, 40, 200, 200); + * + * // Add to the stage + * app.stage.addChild(rect); + * + * // Don't start rendering until the graphic is uploaded to the GPU + * app.renderer.plugins.prepare.upload(app.stage, () => { + * app.start(); + * }); * @namespace PIXI.prepare */ export { default as webgl } from './webgl/WebGLPrepare'; diff --git a/src/prepare/webgl/WebGLPrepare.js b/src/prepare/webgl/WebGLPrepare.js index eb7023f..9df3c3f 100644 --- a/src/prepare/webgl/WebGLPrepare.js +++ b/src/prepare/webgl/WebGLPrepare.js @@ -22,12 +22,11 @@ this.uploadHookHelper = this.renderer; // Add textures and graphics to upload - this.register(findBaseTextures, uploadBaseTextures) - .register(findGraphics, uploadGraphics); + this.registerFindHook(findGraphics); + this.registerUploadHook(uploadBaseTextures); + this.registerUploadHook(uploadGraphics); } - } - /** * Built-in hook to upload PIXI.Texture objects to the GPU. * @@ -80,41 +79,6 @@ } /** - * Built-in hook to find textures from Sprites. - * - * @private - * @param {PIXI.DisplayObject} item - Display object to check - * @param {Array<*>} queue - Collection of items to upload - * @return {boolean} if a PIXI.Texture object was found. - */ -function findBaseTextures(item, queue) -{ - // Objects with textures, like Sprites/Text - if (item instanceof core.BaseTexture) - { - if (queue.indexOf(item) === -1) - { - queue.push(item); - } - - return true; - } - else if (item._texture && item._texture instanceof core.Texture) - { - const texture = item._texture.baseTexture; - - if (queue.indexOf(texture) === -1) - { - queue.push(texture); - } - - return true; - } - - return false; -} - -/** * Built-in hook to find graphics. * * @private diff --git a/.travis.yml b/.travis.yml index 3df8b22..14de334 100644 --- a/.travis.yml +++ b/.travis.yml @@ -110,3 +110,43 @@ on: all_branches: true condition: $TRAVIS_TAG + # Deploy config for latest release + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: dist + upload-dir: "release" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: docs + upload-dir: "release/docs" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: coverage + upload-dir: "release/coverage" + on: + all_branches: true + condition: $TRAVIS_TAG \ No newline at end of file diff --git a/README.md b/README.md index d26fadd..3b85f85 100644 --- a/README.md +++ b/README.md @@ -36,22 +36,14 @@ - Wiki: Other misc tutorials and resources are [on the Wiki](https://github.com/pixijs/pixi.js/wiki/Resources). ### Community ### -- Forums: Check out the [forum] (http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow] (http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. +- Forums: Check out the [forum](http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow](http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. - Inspiration: Check out the [gallery](http://www.pixijs.com/gallery) to see some of the amazing things people have created! - Chat: You can join us on [Gitter](https://gitter.im/pixijs/pixi.js) To chat about Pixi. We also now have a Slack channel. If you would like to join it please Send me an email (mat@goodboydigital.com) and I will invite you in. ### Setup ### -It's easy to get started with Pixi.js! Simply grab the pre-built versions from here: - -Release Branch - Nice and stable Pixi.js -- Unminified: [http://pixijs.download/release/pixi.js] -- Minified: [http://pixijs.download/release/pixi.min.js] - -Develop Branch - The bleeding edge version of Pixi.js -- Unminified: [http://pixijs.download/dev/pixi.js] -- Minified: [http://pixijs.download/dev/pixi.min.js] +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. @@ -70,10 +62,10 @@ #### CDN Install (via cdnjs) ```html - + ``` -_Note: `4.2.2` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ +_Note: `4.4.3` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ ### Demos ### @@ -130,7 +122,7 @@ // and the root stage PIXI.Container. var app = new PIXI.Application(); -// The application will create a canvas element for you that you +// The application will create a canvas element for you that you // can then insert into the DOM. document.body.appendChild(app.view); @@ -150,7 +142,7 @@ // Add the bunny to the scene we are building. app.stage.addChild(bunny); - + // Listen for frame updates app.ticker.add(function() { // each frame we spin the bunny around a bit diff --git a/bower.json b/bower.json deleted file mode 100644 index 41b668c..0000000 --- a/bower.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "pixi.js", - "main": "dist/pixi.min.js", - "ignore": [ - "**/.*", - "docs", - "node_modules", - "bower_components", - "test", - "scripts", - "bower.json", - "inch.json" - ], - "dependencies": { - }, - "devDependencies": { - } -} diff --git a/package.json b/package.json index d35cbb2..308469f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.3.5", + "version": "4.4.4", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ @@ -40,11 +40,11 @@ "lib": "babel src --out-dir lib -s", "predocs": "rimraf docs/**", "docs": "jsdoc -c scripts/jsdoc.conf.json -R README.md", - "publish:patch": "npm version patch --no-git-tag-version && npm publish", - "publish:minor": "npm version minor --no-git-tag-version && npm publish", - "publish:major": "npm version major --no-git-tag-version && npm publish", + "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", - "postpublish": "node scripts/release.js" + "postpublish": "git push && git push --tags" }, "files": [ "dist/", @@ -72,7 +72,6 @@ "electron": "^1.4.15", "eslint": "^3.5.0", "floss": "^2.0.1", - "gh-pages": "^0.11.0", "jaguarjs-jsdoc": "^1.0.1", "js-md5": "^0.4.1", "jsdoc": "^3.4.2", diff --git a/scripts/jsdoc.conf.json b/scripts/jsdoc.conf.json index 553e61c..3f52180 100644 --- a/scripts/jsdoc.conf.json +++ b/scripts/jsdoc.conf.json @@ -43,7 +43,7 @@ }, "markdown" : { "parser" : "gfm", - "hardwrap" : true + "hardwrap" : false }, "opts": { "encoding" : "utf8", diff --git a/scripts/release.js b/scripts/release.js deleted file mode 100644 index 156620c..0000000 --- a/scripts/release.js +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env node -'use strict'; - -// Publish script to push releases of the bin files -// the normally are gitignored -const ghpages = require('gh-pages'); -const path = require('path'); -const packageInfo = require(path.join(__dirname, '..', 'package.json')); - -const options = { - src: [ - 'dist/**/*', - 'lib/**/*', - 'src/**/*', - 'scripts/*', - 'scripts/renders/*', - 'test/**/*', - '*.json', - '*.md', - 'LICENSE', - '.eslintrc', - '.editorconfig', - '.travis.yml', - '.babelrc', - ], - dotfiles: true, - branch: 'release', - message: packageInfo.version, - logger: console.log.bind(console), -}; - -ghpages.publish(process.cwd(), options, (err) => -{ - if (err) - { - console.log(err); - process.exit(1); - - return; - } - - process.exit(0); -}); diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index f16bcb1..220427a 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -21,7 +21,7 @@ const DIV_HOOK_ZINDEX = 2; /** - * The Accessibility manager reacreates the ability to tab and and have content read by screen + * The Accessibility manager recreates the ability to tab and and have content read by screen * readers. This is very important as it can possibly help people with disabilities access pixi * content. * diff --git a/src/accessibility/index.js b/src/accessibility/index.js index 568cfb1..0ae108f 100644 --- a/src/accessibility/index.js +++ b/src/accessibility/index.js @@ -1,4 +1,9 @@ /** + * This namespace contains a renderer plugin for interaction accessibility for end-users + * with physical impairments which require screen-renders, keyboard navigation, etc. + * + * Do not instantiate this plugin directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. * @namespace PIXI.accessibility */ export { default as accessibleTarget } from './accessibleTarget'; diff --git a/src/core/Application.js b/src/core/Application.js index 5340578..2fcc729 100644 --- a/src/core/Application.js +++ b/src/core/Application.js @@ -1,6 +1,8 @@ import { autoDetectRenderer } from './autoDetectRenderer'; import Container from './display/Container'; import { shared, Ticker } from './ticker'; +import settings from './settings'; +import { UPDATE_PRIORITY } from './const'; /** * Convenience class to create a new PIXI application. @@ -22,28 +24,52 @@ */ export default class Application { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experience unexplained flickering try setting this to true. - * @param {boolean} [useSharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedLoader=false] - `true` to use PIXI.loaders.shared, `false` to create new Loader. */ - constructor(width, height, options, noWebGL, useSharedTicker = false) + constructor(options, arg2, arg3, arg4, arg5) { + // Support for constructor(width, height, options, noWebGL, useSharedTicker) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + forceCanvas: !!arg4, + sharedTicker: !!arg5, + }, arg3); + } + + /** + * The default options, so we mixin functionality later. + * @member {object} + * @protected + */ + this._options = options = Object.assign({ + sharedTicker: false, + forceCanvas: false, + sharedLoader: false, + }, options); + /** * WebGL renderer if available, otherwise CanvasRenderer * @member {PIXI.WebGLRenderer|PIXI.CanvasRenderer} */ - this.renderer = autoDetectRenderer(width, height, options, noWebGL); + this.renderer = autoDetectRenderer(options); /** * The root display container that's rendered. @@ -63,7 +89,7 @@ * @member {PIXI.ticker.Ticker} * @default PIXI.ticker.shared */ - this.ticker = useSharedTicker ? shared : new Ticker(); + this.ticker = options.sharedTicker ? shared : new Ticker(); // Start the rendering this.start(); @@ -78,7 +104,7 @@ this._ticker = ticker; if (ticker) { - ticker.add(this.render, this); + ticker.add(this.render, this, UPDATE_PRIORITY.LOW); } } get ticker() // eslint-disable-line require-jsdoc @@ -136,13 +162,18 @@ */ destroy(removeView) { - this.stop(); + const oldTicker = this._ticker; + this.ticker = null; + oldTicker.destroy(); + this.stage.destroy(); this.stage = null; this.renderer.destroy(removeView); this.renderer = null; + + this._options = null; } } diff --git a/src/core/autoDetectRenderer.js b/src/core/autoDetectRenderer.js index 8960425..9ec0364 100644 --- a/src/core/autoDetectRenderer.js +++ b/src/core/autoDetectRenderer.js @@ -2,6 +2,7 @@ import CanvasRenderer from './renderers/canvas/CanvasRenderer'; import WebGLRenderer from './renderers/webgl/WebGLRenderer'; +// eslint-disable-next-line valid-jsdoc /** * This helper function will automatically detect which renderer you should be using. * WebGL is the preferred renderer as it is a lot faster. If webGL is not supported by @@ -9,24 +10,32 @@ * * @memberof PIXI * @function autoDetectRenderer - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @return {PIXI.WebGLRenderer|PIXI.CanvasRenderer} Returns WebGL renderer if available, otherwise CanvasRenderer */ -export function autoDetectRenderer(width = 800, height = 600, options, noWebGL) +export function autoDetectRenderer(options, arg1, arg2, arg3) { - if (!noWebGL && utils.isWebGLSupported()) + // Backward-compatible support for noWebGL option + let forceCanvas = options && options.forceCanvas; + + if (arg3 !== undefined) { - return new WebGLRenderer(width, height, options); + forceCanvas = arg3; } - return new CanvasRenderer(width, height, options); + if (!forceCanvas && utils.isWebGLSupported()) + { + return new WebGLRenderer(options, arg1, arg2); + } + + return new CanvasRenderer(options, arg1, arg2); } diff --git a/src/core/const.js b/src/core/const.js index b9e99ae..2c68dee 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -341,3 +341,27 @@ LINEAR_VERTICAL: 0, LINEAR_HORIZONTAL: 1, }; + +/** + * Represents the update priorities used by internal PIXI classes when registered with + * the {@link PIXI.ticker.Ticker} object. Higher priority items are updated first and lower + * priority items, such as render, should go later. + * + * @static + * @constant + * @name UPDATE_PRIORITY + * @memberof PIXI + * @type {object} + * @property {number} INTERACTION=50 Highest priority, used for {@link PIXI.interaction.InteractionManager} + * @property {number} HIGH=25 High priority updating, {@link PIXI.VideoBaseTexture} and {@link PIXI.extras.AnimatedSprite} + * @property {number} NORMAL=0 Default priority for ticker events, see {@link PIXI.ticker.Ticker#add}. + * @property {number} LOW=-25 Low priority used for {@link PIXI.Application} rendering. + * @property {number} UTILITY=-50 Lowest priority used for {@link PIXI.prepare.BasePrepare} utility. + */ +export const UPDATE_PRIORITY = { + INTERACTION: 50, + HIGH: 25, + NORMAL: 0, + LOW: -25, + UTILITY: -50, +}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 4e6c77d..decb42a 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -13,7 +13,6 @@ * * @class * @extends EventEmitter - * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ export default class DisplayObject extends EventEmitter @@ -113,6 +112,29 @@ * @private */ this._mask = null; + + /** + * If the object has been destroyed via destroy(). If true, it should not be used. + * + * @member {boolean} + * @private + * @readonly + */ + this._destroyed = false; + + /** + * Fired when this DisplayObject is added to a Container. + * + * @event PIXI.DisplayObject#added + * @param {PIXI.Container} container - The container added to. + */ + + /** + * Fired when this DisplayObject is removed from a Container. + * + * @event PIXI.DisplayObject#removed + * @param {PIXI.Container} container - The container removed from. + */ } /** @@ -403,6 +425,8 @@ this.interactive = false; this.interactiveChildren = false; + + this._destroyed = true; } /** diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index aafdfe2..f787c4a 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -792,8 +792,8 @@ sprite.worldAlpha = this.worldAlpha * sprite.alpha; sprite.blendMode = this.blendMode; - sprite.texture._frame.width = rect.width; - sprite.texture._frame.height = rect.height; + sprite._texture._frame.width = rect.width; + sprite._texture._frame.height = rect.height; sprite.transform.worldTransform = this.transform.worldTransform; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index a24c775..b9f192d 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -19,11 +19,12 @@ */ export default class SystemRenderer extends EventEmitter { + // eslint-disable-next-line valid-jsdoc /** * @param {string} system - The name of the system this renderer is for. - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -37,27 +38,31 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(system, screenWidth, screenHeight, options) + constructor(system, options, arg2, arg3) { super(); sayHello(system); - // prepare options - if (options) + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') { - for (const i in settings.RENDER_OPTIONS) - { - if (typeof options[i] === 'undefined') - { - options[i] = settings.RENDER_OPTIONS[i]; - } - } + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); } - else - { - options = settings.RENDER_OPTIONS; - } + + // Add the default render options + options = Object.assign({}, settings.RENDER_OPTIONS, options); + + /** + * The supplied constructor options. + * + * @member {Object} + * @readOnly + */ + this.options = options; /** * The type of the renderer. @@ -75,7 +80,7 @@ * * @member {PIXI.Rectangle} */ - this.screen = new Rectangle(0, 0, screenWidth || 800, screenHeight || 600); + this.screen = new Rectangle(0, 0, options.width, options.height); /** * The canvas element that everything is drawn to @@ -279,6 +284,8 @@ this.blendModes = null; + this.options = null; + this.preserveDrawingBuffer = false; this.clearBeforeRender = false; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index e84e49c..691caf0 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -17,10 +17,11 @@ */ export default class CanvasRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -34,9 +35,9 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('Canvas', screenWidth, screenHeight, options); + super('Canvas', options, arg2, arg3); this.type = RENDERER_TYPE.CANVAS; @@ -96,7 +97,19 @@ this.context = null; this.renderingToScreen = false; - this.resize(screenWidth, screenHeight); + this.resize(this.options.width, this.options.height); + + /** + * Fired after rendering finishes. + * + * @event PIXI.CanvasRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.CanvasRenderer#prerender + */ } /** @@ -299,4 +312,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.CanvasRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.CanvasExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.CanvasPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.CanvasRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index e1cf7cd..f6fe123 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -31,11 +31,12 @@ */ export default class WebGLRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** * - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -55,9 +56,9 @@ * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('WebGL', screenWidth, screenHeight, options); + super('WebGL', options, arg2, arg3); /** * The type of this renderer as a standardised const @@ -89,12 +90,6 @@ }; - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ this._backgroundColorRgba[3] = this.transparent ? 0 : 1; this.globalUniforms = new UniformGroup({ @@ -117,6 +112,13 @@ this.initPlugins(); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ if(options.context) { this.context.initFromContext(options.context); @@ -169,6 +171,25 @@ } return this; + + /** + * Fired after rendering finishes. + * + * @event PIXI.WebGLRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.WebGLRenderer#prerender + */ + + /** + * Fired when the WebGL context is set. + * + * @event PIXI.WebGLRenderer#context + * @param {WebGLRenderingContext} gl - WebGL context. + */ } /** @@ -295,4 +316,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.WebGLRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.WebGLRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(WebGLRenderer); diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index 4eb6128..fa3d883 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -47,6 +47,14 @@ * @member {boolean} */ this.enabled = true; + + /** + * If enabled, pixi will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; } /** diff --git a/src/core/renderers/webgl/systems/ContextSystem.js b/src/core/renderers/webgl/systems/ContextSystem.js index ff958c7..8df85ea 100644 --- a/src/core/renderers/webgl/systems/ContextSystem.js +++ b/src/core/renderers/webgl/systems/ContextSystem.js @@ -75,7 +75,8 @@ */ createContext(canvas, options) { - var gl = canvas.getContext('webgl', options) || + var gl = //canvas.getContext('webgl2', options) || + canvas.getContext('webgl', options) || canvas.getContext('experimental-webgl', options); if (!gl) diff --git a/src/core/renderers/webgl/systems/FilterSystem.js b/src/core/renderers/webgl/systems/FilterSystem.js index f47335d..37e28d2 100644 --- a/src/core/renderers/webgl/systems/FilterSystem.js +++ b/src/core/renderers/webgl/systems/FilterSystem.js @@ -111,7 +111,7 @@ // TODO we should fit the rect around the transform.. } - else + else if (filters[0].autoFit) { sourceFrame.fit(filterData.stack[0].destinationFrame); } diff --git a/src/core/renderers/webgl/systems/shader/shader/mapType.js b/src/core/renderers/webgl/systems/shader/shader/mapType.js index 581dace..917538a 100644 --- a/src/core/renderers/webgl/systems/shader/shader/mapType.js +++ b/src/core/renderers/webgl/systems/shader/shader/mapType.js @@ -15,7 +15,7 @@ } } - return GL_TABLE[type]; + return GL_TABLE[type]; }; var GL_TABLE = null; diff --git a/src/core/settings.js b/src/core/settings.js index 18c5877..38ef45c 100644 --- a/src/core/settings.js +++ b/src/core/settings.js @@ -2,6 +2,15 @@ import canUploadSameBuffer from './utils/canUploadSameBuffer'; /** + * User's customizable globals for overriding the default PIXI settings, such + * as a renderer's default resolution, framerate, float percision, etc. + * @example + * // Use the native window resolution as the default resolution + * // will support high-density displays when rendering + * PIXI.settings.RESOLUTION = window.devicePixelRatio. + * + * // Disable interpolation when scaling, will make texture be pixelated + * PIXI.settings.SCALE_MODE = PIXI.SCALE_MODES.NEAREST; * @namespace PIXI.settings */ export default { @@ -101,6 +110,9 @@ * @property {boolean} clearBeforeRender=true * @property {boolean} preserveDrawingBuffer=false * @property {boolean} roundPixels=false + * @property {number} width=800 + * @property {number} height=600 + * @property {boolean} legacy=false */ RENDER_OPTIONS: { view: null, @@ -112,6 +124,9 @@ clearBeforeRender: true, preserveDrawingBuffer: false, roundPixels: false, + width: 800, + height: 600, + legacy: false, }, /** diff --git a/src/core/shader/Program.js b/src/core/shader/Program.js index c0b538b..07b4398 100644 --- a/src/core/shader/Program.js +++ b/src/core/shader/Program.js @@ -1,6 +1,6 @@ import extractUniformsFromSrc from './extractUniformsFromSrc'; import generateUniformsSync from './generateUniformsSync'; -import glCore from 'pixi-gl-core'; +import shaderUtils from '../renderers/webgl/systems/shader/shader'; import { ProgramCache } from '../utils'; import getTestContext from '../utils/getTestContext'; @@ -64,14 +64,17 @@ } else { - vertexSrc = glCore.shader.setPrecision(vertexSrc, 'mediump'); - fragmentSrc = glCore.shader.setPrecision(fragmentSrc, 'mediump'); + vertexSrc = shaderUtils.setPrecision(vertexSrc, 'mediump'); + fragmentSrc = shaderUtils.setPrecision(fragmentSrc, 'mediump'); - const program = glCore.shader.compileProgram(gl, vertexSrc, fragmentSrc); + const program = shaderUtils.compileProgram(gl, vertexSrc, fragmentSrc); + console.log("<<<<<<>>><<"); this.attributeData = this.getAttributeData(program, gl); this.uniformData = this.getUniformData(program, gl); + console.log(this.uniformData); + //gl.deleteProgram(program); } } @@ -95,13 +98,13 @@ for (let i = 0; i < totalAttributes; i++) { const attribData = gl.getActiveAttrib(program, i); - const type = glCore.shader.mapType(gl, attribData.type); + const type = shaderUtils.mapType(gl, attribData.type); /*eslint-disable */ const data = { type: type, name: attribData.name, - size: glCore.shader.mapSize(type), + size: shaderUtils.mapSize(type), location: 0, }; /*eslint-enable */ @@ -137,13 +140,14 @@ // TODO expose this as a prop? // const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); - const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); + //const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); for (let i = 0; i < totalUniforms; i++) { const uniformData = gl.getActiveUniform(program, i); const name = uniformData.name.replace(/\[.*?\]/, ''); - const type = glCore.shader.mapType(gl, uniformData.type); + const type = shaderUtils.mapType(gl, uniformData.type); + console.log('mapping ' + uniformData.type + ' to ' + type); // if (!name.match(maskRegex)) { @@ -151,7 +155,7 @@ uniforms[name] = { type: type, size: uniformData.size, - value: glCore.shader.defaultValue(type, uniformData.size), + value: shaderUtils.defaultValue(type, uniformData.size), }; /*eslint-enable */ } diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index e387e99..b57d095 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -147,12 +147,12 @@ // so if _width is 0 then width was not set.. if (this._width) { - this.scale.x = sign(this.scale.x) * this._width / this.texture.orig.width; + this.scale.x = sign(this.scale.x) * this._width / this._texture.orig.width; } if (this._height) { - this.scale.y = sign(this.scale.y) * this._height / this.texture.orig.height; + this.scale.y = sign(this.scale.y) * this._height / this._texture.orig.height; } } diff --git a/src/core/sprites/canvas/CanvasTinter.js b/src/core/sprites/canvas/CanvasTinter.js index 27e5e0c..fd9de5d 100644 --- a/src/core/sprites/canvas/CanvasTinter.js +++ b/src/core/sprites/canvas/CanvasTinter.js @@ -18,7 +18,7 @@ */ getTintedTexture: (sprite, color) => { - const texture = sprite.texture; + const texture = sprite._texture; color = CanvasTinter.roundColor(color); diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 5a4edda..8b9b797 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -344,6 +344,7 @@ const texture = this._texture; const style = this._style; + const padding = style.trim ? 0 : style.padding; texture.baseTexture.valid = true; texture.baseTexture.resolution = this.resolution; @@ -353,11 +354,11 @@ texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - texture.trim.x = -style.padding; - texture.trim.y = -style.padding; + texture.trim.x = -padding; + texture.trim.y = -padding; - texture.orig.width = texture._frame.width - (style.padding * 2); - texture.orig.height = texture._frame.height - (style.padding * 2); + texture.orig.width = texture._frame.width - (padding * 2); + texture.orig.height = texture._frame.height - (padding * 2); // call sprite onTextureUpdate to update scale if _width or _height were set this._onTextureUpdate(); diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index 853da3d..8a9c34b 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -114,6 +114,38 @@ this.cacheId = null; this.validate(); + + /** + * Fired when a not-immediately-available source finishes loading. + * + * @protected + * @event PIXI.BaseTexture#loaded + * @param {PIXI.BaseTexture} baseTexture - Resource loaded. + */ + + /** + * Fired when a not-immediately-available source fails to load. + * + * @protected + * @event PIXI.BaseTexture#error + * @param {PIXI.BaseTexture} baseTexture - Resource errored. + */ + + /** + * Fired when BaseTexture is updated. + * + * @protected + * @event PIXI.BaseTexture#update + * @param {PIXI.BaseTexture} baseTexture - Instance of texture being updated. + */ + + /** + * Fired when BaseTexture is destroyed. + * + * @protected + * @event PIXI.BaseTexture#dispose + * @param {PIXI.BaseTexture} baseTexture - Instance of texture being destroyed. + */ } setResource(resource) diff --git a/src/core/textures/Spritesheet.js b/src/core/textures/Spritesheet.js index d469c62..9b24f27 100644 --- a/src/core/textures/Spritesheet.js +++ b/src/core/textures/Spritesheet.js @@ -208,6 +208,7 @@ // lets also add the frame to pixi's global cache for fromFrame and fromImage functions TextureCache[i] = this.textures[i]; + this.textures[i].textureCacheId = i; } frameIndex++; diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 0a2c379..2213087 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -147,9 +147,9 @@ /** * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. * - * @event update - * @memberof PIXI.Texture# + * @event PIXI.Texture#update * @protected + * @param {PIXI.Texture} texture - Instance of texture being updated. */ this._updateID = 0; @@ -159,6 +159,15 @@ * @type {Object} */ this.transform = null; + + /** + * The id under which this Texture has been added to the texture cache. This is + * automatically set in certain cases, but may not always be accurate, particularly if + * the texture is in the cache under multiple ids. + * + * @member {string} + */ + this.textureCacheId = null; } /** @@ -225,11 +234,17 @@ this._uvs = null; this.trim = null; this.orig = null; + this.textureCacheId = null; this.valid = false; - this.off('dispose', this.dispose, this); - this.off('update', this.update, this); + for (const prop in TextureCache) + { + if (TextureCache[prop] === this) + { + delete TextureCache[prop]; + } + } } /** @@ -315,14 +330,15 @@ */ static fromLoader(source, imageUrl, name) { - // console.log('added from loader...') + // console.log('added from loader...') const resource = new ImageResource(source);//.from(imageUrl, crossorigin);// document.createElement('img'); - // console.log('base resource ' + resource.width); + // console.log('base resource ' + resource.width); const baseTexture = new BaseTexture(resource, settings.SCALE_MODE, getResolutionOfUrl(imageUrl)); + /// console.log('base width ' + baseTexture.width); const texture = new Texture(baseTexture); @@ -335,6 +351,7 @@ // lets also add the frame to pixi's global cache for fromFrame and fromImage fucntions BaseTextureCache[name] = baseTexture; TextureCache[name] = texture; + texture.textureCacheId = name; // also add references by url if they are different. if (name !== imageUrl) @@ -355,6 +372,10 @@ */ static addTextureToCache(texture, id) { + if (!texture.textureCacheId) + { + texture.textureCacheId = id; + } TextureCache[id] = texture; } @@ -495,6 +516,7 @@ */ Texture.EMPTY = new Texture(new BaseTexture()); removeAllHandlers(Texture.EMPTY); +removeAllHandlers(Texture.EMPTY.baseTexture); /** * A white texture of 10x10 size, used for graphics and other things @@ -505,3 +527,4 @@ */ Texture.WHITE = createWhiteTexture(); removeAllHandlers(Texture.WHITE); +removeAllHandlers(Texture.WHITE.baseTexture); diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index 59eb9fd..a9b9f51 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -1,6 +1,7 @@ import BaseTexture from './BaseTexture'; import { uid, BaseTextureCache } from '../utils'; -import * as ticker from '../ticker'; +import { shared } from '../ticker'; +import { UPDATE_PRIORITY } from '../const'; /** * A texture of a [playing] Video. @@ -125,7 +126,7 @@ if (!this._isAutoUpdating && this.autoUpdate) { - ticker.shared.add(this.update, this); + shared.add(this.update, this, UPDATE_PRIORITY.HIGH); this._isAutoUpdating = true; } } @@ -139,7 +140,7 @@ { if (this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); this._isAutoUpdating = false; } } @@ -187,7 +188,7 @@ { if (this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); } if (this.source && this.source._pixiId) @@ -281,12 +282,12 @@ if (!this._autoUpdate && this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); this._isAutoUpdating = false; } else if (this._autoUpdate && !this._isAutoUpdating) { - ticker.shared.add(this.update, this); + shared.add(this.update, this, UPDATE_PRIORITY.HIGH); this._isAutoUpdating = true; } } diff --git a/src/core/ticker/Ticker.js b/src/core/ticker/Ticker.js index 17a1517..40df138 100644 --- a/src/core/ticker/Ticker.js +++ b/src/core/ticker/Ticker.js @@ -1,12 +1,10 @@ import settings from '../settings'; -import EventEmitter from 'eventemitter3'; - -// Internal event used by composed emitter -const TICK = 'tick'; +import { UPDATE_PRIORITY } from '../const'; +import TickerListener from './TickerListener'; /** * A Ticker class that runs an update loop that other objects listen to. - * This class is composed around an EventEmitter object to add listeners + * This class is composed around listeners * meant for execution on the next requested animation frame. * Animation frames are requested only when necessary, * e.g. When the ticker is started and the emitter has listeners. @@ -22,10 +20,11 @@ constructor() { /** - * Internal emitter used to fire 'tick' event + * The first listener. All new listeners added are chained on this. * @private + * @type {TickerListener} */ - this._emitter = new EventEmitter(); + this._head = new TickerListener(null, null, Infinity); /** * Internal current frame request ID @@ -131,7 +130,7 @@ // Invoke listeners now this.update(time); // Listener side effects may have modified ticker state. - if (this.started && this._requestId === null && this._emitter.listeners(TICK, true)) + if (this.started && this._requestId === null && this._head.next) { this._requestId = requestAnimationFrame(this._tick); } @@ -148,7 +147,7 @@ */ _requestIfNeeded() { - if (this._requestId === null && this._emitter.listeners(TICK, true)) + if (this._requestId === null && this._head.next) { // ensure callbacks get correct delta this.lastTime = performance.now(); @@ -193,35 +192,72 @@ } /** - * Calls {@link module:eventemitter3.EventEmitter#on} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. + * Register a handler for tick events. Calls continuously unless + * it is removed or the ticker is stopped. * * @param {Function} fn - The listener function to be added for updates * @param {Function} [context] - The listener context + * @param {number} [priority=PIXI.UPDATE_PRIORITY.NORMAL] - The priority for emitting * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - add(fn, context) + add(fn, context, priority = UPDATE_PRIORITY.NORMAL) { - this._emitter.on(TICK, fn, context); - - this._startIfPossible(); - - return this; + return this._addListener(new TickerListener(fn, context, priority)); } /** - * Calls {@link module:eventemitter3.EventEmitter#once} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. + * Add a handler for the tick event which is only execute once. * * @param {Function} fn - The listener function to be added for one update * @param {Function} [context] - The listener context + * @param {number} [priority=PIXI.UPDATE_PRIORITY.NORMAL] - The priority for emitting * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - addOnce(fn, context) + addOnce(fn, context, priority = UPDATE_PRIORITY.NORMAL) { - this._emitter.once(TICK, fn, context); + return this._addListener(new TickerListener(fn, context, priority, true)); + } + + /** + * Internally adds the event handler so that it can be sorted by priority. + * Priority allows certain handler (user, AnimatedSprite, Interaction) to be run + * before the rendering. + * + * @private + * @param {TickerListener} listener - Current listener being added. + * @returns {PIXI.ticker.Ticker} This instance of a ticker + */ + _addListener(listener) + { + // For attaching to head + let current = this._head.next; + let previous = this._head; + + // Add the first item + if (!current) + { + listener.connect(previous); + } + else + { + // Go from highest to lowest priority + while (current) + { + if (listener.priority >= current.priority) + { + listener.connect(previous); + break; + } + previous = current; + current = current.next; + } + + // Not yet connected + if (!listener.previous) + { + listener.connect(previous); + } + } this._startIfPossible(); @@ -229,19 +265,33 @@ } /** - * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. - * It checks if the emitter has listeners for 'tick' event. - * If it does, then it cancels the animation frame. + * Removes any handlers matching the function and context parameters. + * If no handlers are left after removing, then it cancels the animation frame. * - * @param {Function} [fn] - The listener function to be removed + * @param {Function} fn - The listener function to be removed * @param {Function} [context] - The listener context to be removed * @returns {PIXI.ticker.Ticker} This instance of a ticker */ remove(fn, context) { - this._emitter.off(TICK, fn, context); + let listener = this._head.next; - if (!this._emitter.listeners(TICK, true)) + while (listener) + { + // We found a match, lets remove it + // no break to delete all possible matches + // incase a listener was added 2+ times + if (listener.match(fn, context)) + { + listener = listener.destroy(); + } + else + { + listener = listener.next; + } + } + + if (!this._head.next) { this._cancelIfNeeded(); } @@ -276,6 +326,25 @@ } /** + * Destroy the ticker and don't use after this. Calling + * this method removes all references to internal events. + */ + destroy() + { + this.stop(); + + let listener = this._head.next; + + while (listener) + { + listener = listener.destroy(true); + } + + this._head.destroy(); + this._head = null; + } + + /** * Triggers an update. An update entails setting the * current {@link PIXI.ticker.Ticker#elapsedMS}, * the current {@link PIXI.ticker.Ticker#deltaTime}, @@ -320,8 +389,22 @@ this.deltaTime = elapsedMS * settings.TARGET_FPMS * this.speed; + // Cache a local reference, in-case ticker is destroyed + // during the emit, we can still check for head.next + const head = this._head; + // Invoke listeners added to internal emitter - this._emitter.emit(TICK, this.deltaTime); + let listener = head.next; + + while (listener) + { + listener = listener.emit(this.deltaTime); + } + + if (!head.next) + { + this._cancelIfNeeded(); + } } else { diff --git a/src/core/ticker/TickerListener.js b/src/core/ticker/TickerListener.js new file mode 100644 index 0000000..2bedb34 --- /dev/null +++ b/src/core/ticker/TickerListener.js @@ -0,0 +1,158 @@ +/** + * Internal class for handling the priority sorting of ticker handlers. + * + * @private + * @class + * @memberof PIXI.ticker + */ +export default class TickerListener +{ + /** + * Constructor + * + * @param {Function} fn - The listener function to be added for one update + * @param {Function} [context=null] - The listener context + * @param {number} [priority=0] - The priority for emitting + * @param {boolean} [once=false] - If the handler should fire once + */ + constructor(fn, context = null, priority = 0, once = false) + { + /** + * The handler function to execute. + * @member {Function} + */ + this.fn = fn; + + /** + * The calling to execute. + * @member {Function} + */ + this.context = context; + + /** + * The current priority. + * @member {number} + */ + this.priority = priority; + + /** + * If this should only execute once. + * @member {boolean} + */ + this.once = once; + + /** + * The next item in chain. + * @member {TickerListener} + */ + this.next = null; + + /** + * The previous item in chain. + * @member {TickerListener} + */ + this.previous = null; + + /** + * `true` if this listener has been destroyed already. + * @member {boolean} + * @private + */ + this._destroyed = false; + } + + /** + * Simple compare function to figure out if a function and context match. + * + * @param {Function} fn - The listener function to be added for one update + * @param {Function} context - The listener context + * @return {boolean} `true` if the listener match the arguments + */ + match(fn, context) + { + context = context || null; + + return this.fn === fn && this.context === context; + } + + /** + * Emit by calling the current function. + * @param {number} deltaTime - time since the last emit. + * @return {TickerListener} Next ticker + */ + emit(deltaTime) + { + if (this.context) + { + this.fn.call(this.context, deltaTime); + } + else + { + this.fn(deltaTime); + } + + if (this.once) + { + this.destroy(); + } + + const redirect = this.next; + + // Soft-destroying should remove + // the next reference + if (this._destroyed) + { + this.next = null; + } + + return redirect; + } + + /** + * Connect to the list. + * @param {TickerListener} previous - Input node, previous listener + */ + connect(previous) + { + this.previous = previous; + if (previous.next) + { + previous.next.previous = this; + } + this.next = previous.next; + previous.next = this; + } + + /** + * Destroy and don't use after this. + * @param {boolean} [hard = false] `true` to remove the `next` reference, this + * is considered a hard destroy. Soft destroy maintains the next reference. + * @return {TickerListener} The listener to redirect while emitting or removing. + */ + destroy(hard = false) + { + this._destroyed = true; + this.fn = null; + this.context = null; + + // Disconnect, hook up next and previous + if (this.previous) + { + this.previous.next = this.next; + } + + if (this.next) + { + this.next.previous = this.previous; + } + + // Redirect to the next item + const redirect = this.previous; + + // Remove references + this.next = hard ? null : redirect; + this.previous = null; + + return redirect; + } +} diff --git a/src/core/ticker/index.js b/src/core/ticker/index.js index 4b3017c..c3020d4 100644 --- a/src/core/ticker/index.js +++ b/src/core/ticker/index.js @@ -45,8 +45,25 @@ const shared = new Ticker(); shared.autoStart = true; +shared.destroy = () => +{ + // protect destroying shared ticker + // this is used by other internal systems + // like AnimatedSprite and InteractionManager +}; /** + * This namespace contains an API for interacting with PIXI's internal global update loop. + * + * This ticker is used for rendering, {@link PIXI.extras.AnimatedSprite AnimatedSprite}, + * {@link PIXI.interaction.InteractionManager InteractionManager} and many other time-based PIXI systems. + * @example + * const ticker = new PIXI.ticker.Ticker(); + * ticker.stop(); + * ticker.add((deltaTime) => { + * // do something every frame + * }); + * ticker.start(); * @namespace PIXI.ticker */ export { shared, Ticker }; diff --git a/src/core/utils/index.js b/src/core/utils/index.js index be44750..7441f7b 100644 --- a/src/core/utils/index.js +++ b/src/core/utils/index.js @@ -9,6 +9,21 @@ let saidHello = false; /** + * Generalized convenience utilities for PIXI. + * @example + * // Extend PIXI's internal Event Emitter. + * class MyEmitter extends PIXI.utils.EventEmitter { + * constructor() { + * super(); + * console.log("Emitter created!"); + * } + * } + * + * // Get info on current device + * console.log(PIXI.utils.isMobile); + * + * // Convert hex color to string + * console.log(PIXI.utils.hex2string(0xff00ff)); // returns: "#ff00ff" * @namespace PIXI.utils */ export { diff --git a/src/deprecation.js b/src/deprecation.js index b9c73b3..33daeec 100644 --- a/src/deprecation.js +++ b/src/deprecation.js @@ -918,6 +918,35 @@ }); /** + * @method + * @name PIXI.prepare.BasePrepare#register + * @see PIXI.prepare.BasePrepare#registerFindHook + * @deprecated since version 4.4.2 + * @param {Function} [addHook] - Function call that takes two parameters: `item:*, queue:Array` + * function must return `true` if it was able to add item to the queue. + * @param {Function} [uploadHook] - Function call that takes two parameters: `prepare:CanvasPrepare, item:*` and + * function must return `true` if it was able to handle upload of item. + * @return {PIXI.BasePrepare} Instance of plugin for chaining. + */ +prepare.BasePrepare.prototype.register = function register(addHook, uploadHook) +{ + warn('renderer.plugins.prepare.register is now deprecated, ' + + 'please use renderer.plugins.prepare.registerFindHook & renderer.plugins.prepare.registerUploadHook'); + + if (addHook) + { + this.registerFindHook(addHook); + } + + if (uploadHook) + { + this.registerUploadHook(uploadHook); + } + + return this; +}; + +/** * The number of graphics or textures to upload to the GPU. * * @name PIXI.prepare.canvas.UPLOADS_PER_FRAME @@ -969,67 +998,73 @@ }, }); -Object.defineProperties(loaders.Resource.prototype, { - isJson: { - get() - { - warn('The isJson property is deprecated, please use `resource.type === Resource.TYPE.JSON`.'); +if (loaders.Loader) +{ + const Resource = loaders.Resource; + const Loader = loaders.Loader; - return this.type === loaders.Loader.Resource.TYPE.JSON; + Object.defineProperties(Resource.prototype, { + isJson: { + get() + { + warn('The isJson property is deprecated, please use `resource.type === Resource.TYPE.JSON`.'); + + return this.type === Resource.TYPE.JSON; + }, }, - }, - isXml: { - get() - { - warn('The isXml property is deprecated, please use `resource.type === Resource.TYPE.XML`.'); + isXml: { + get() + { + warn('The isXml property is deprecated, please use `resource.type === Resource.TYPE.XML`.'); - return this.type === loaders.Loader.Resource.TYPE.XML; + return this.type === Resource.TYPE.XML; + }, }, - }, - isImage: { - get() - { - warn('The isImage property is deprecated, please use `resource.type === Resource.TYPE.IMAGE`.'); + isImage: { + get() + { + warn('The isImage property is deprecated, please use `resource.type === Resource.TYPE.IMAGE`.'); - return this.type === loaders.Loader.Resource.TYPE.IMAGE; + return this.type === Resource.TYPE.IMAGE; + }, }, - }, - isAudio: { - get() - { - warn('The isAudio property is deprecated, please use `resource.type === Resource.TYPE.AUDIO`.'); + isAudio: { + get() + { + warn('The isAudio property is deprecated, please use `resource.type === Resource.TYPE.AUDIO`.'); - return this.type === loaders.Loader.Resource.TYPE.AUDIO; + return this.type === Resource.TYPE.AUDIO; + }, }, - }, - isVideo: { - get() - { - warn('The isVideo property is deprecated, please use `resource.type === Resource.TYPE.VIDEO`.'); + isVideo: { + get() + { + warn('The isVideo property is deprecated, please use `resource.type === Resource.TYPE.VIDEO`.'); - return this.type === loaders.Loader.Resource.TYPE.VIDEO; + return this.type === Resource.TYPE.VIDEO; + }, }, - }, -}); + }); -Object.defineProperties(loaders.Loader.prototype, { - before: { - get() - { - warn('The before() method is deprecated, please use pre().'); + Object.defineProperties(Loader.prototype, { + before: { + get() + { + warn('The before() method is deprecated, please use pre().'); - return this.pre; + return this.pre; + }, }, - }, - after: { - get() - { - warn('The after() method is deprecated, please use use().'); + after: { + get() + { + warn('The after() method is deprecated, please use use().'); - return this.use; + return this.use; + }, }, - }, -}); + }); +} /** * @name PIXI.interaction.interactiveTarget#defaultCursor diff --git a/src/extract/canvas/CanvasExtract.js b/src/extract/canvas/CanvasExtract.js index 941ca05..3ac5aee 100644 --- a/src/extract/canvas/CanvasExtract.js +++ b/src/extract/canvas/CanvasExtract.js @@ -8,7 +8,7 @@ * An instance of this class is automatically created by default, and can be found at renderer.plugins.extract * * @class - * @memberof PIXI + * @memberof PIXI.extract */ export default class CanvasExtract { @@ -21,9 +21,9 @@ /** * Collection of methods for extracting data (image, pixels, etc.) from a display object or render texture * - * @member {PIXI.CanvasExtract} extract + * @member {PIXI.extract.CanvasExtract} extract * @memberof PIXI.CanvasRenderer# - * @see PIXI.CanvasExtract + * @see PIXI.extract.CanvasExtract */ renderer.extract = this; } diff --git a/src/extract/index.js b/src/extract/index.js index 0f2170d..e9478c1 100644 --- a/src/extract/index.js +++ b/src/extract/index.js @@ -1,2 +1,22 @@ +/** + * This namespace provides renderer-specific plugins for exporting content from a renderer. + * For instance, these plugins can be used for saving an Image, Canvas element or for exporting the raw image data (pixels). + * + * Do not instantiate these plugins directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. + * @example + * // Create a new app (will auto-add extract plugin to renderer) + * const app = new PIXI.Application(); + * + * // Draw a red circle + * const graphics = new PIXI.Graphics() + * .beginFill(0xFF0000) + * .drawCircle(0, 0, 50); + * + * // Render the graphics as an HTMLImageElement + * const image = app.renderer.plugins.extract.image(graphics); + * document.body.appendChild(image); + * @namespace PIXI.extract + */ export { default as webgl } from './webgl/WebGLExtract'; export { default as canvas } from './canvas/CanvasExtract'; diff --git a/src/extract/webgl/WebGLExtract.js b/src/extract/webgl/WebGLExtract.js index 60228bb..1c3dd30 100644 --- a/src/extract/webgl/WebGLExtract.js +++ b/src/extract/webgl/WebGLExtract.js @@ -9,7 +9,7 @@ * An instance of this class is automatically created by default, and can be found at renderer.plugins.extract * * @class - * @memberof PIXI + * @memberof PIXI.extract */ export default class WebGLExtract { @@ -22,9 +22,9 @@ /** * Collection of methods for extracting data (image, pixels, etc.) from a display object or render texture * - * @member {PIXI.WebGLExtract} extract + * @member {PIXI.extract.WebGLExtract} extract * @memberof PIXI.WebGLRenderer# - * @see PIXI.WebGLExtract + * @see PIXI.extract.WebGLExtract */ renderer.extract = this; } diff --git a/src/extras/AnimatedSprite.js b/src/extras/AnimatedSprite.js index 13b804f..5297778 100644 --- a/src/extras/AnimatedSprite.js +++ b/src/extras/AnimatedSprite.js @@ -137,7 +137,7 @@ this.playing = true; if (this._autoUpdate) { - core.ticker.shared.add(this.update, this); + core.ticker.shared.add(this.update, this, core.UPDATE_PRIORITY.HIGH); } } diff --git a/src/extras/TextureTransform.js b/src/extras/TextureTransform.js index 78e6e40..16536d2 100644 --- a/src/extras/TextureTransform.js +++ b/src/extras/TextureTransform.js @@ -65,8 +65,36 @@ } /** + * Multiplies uvs array to transform + * @param {Float32Array} uvs mesh uvs + * @param {Float32Array} [out=uvs] output + * @returns {Float32Array} output + */ + multiplyUvs(uvs, out) + { + if (out === undefined) + { + out = uvs; + } + + const mat = this.mapCoord; + + for (let i = 0; i < uvs.length; i += 2) + { + const x = uvs[i]; + const y = uvs[i + 1]; + + out[i] = (x * mat.a) + (y * mat.c) + mat.tx; + out[i + 1] = (x * mat.b) + (y * mat.d) + mat.ty; + } + + return out; + } + + /** * updates matrices if texture was changed * @param {boolean} forceUpdate if true, matrices will be updated any case + * @returns {boolean} whether or not it was updated */ update(forceUpdate) { @@ -74,13 +102,13 @@ if (!tex || !tex.valid) { - return; + return false; } if (!forceUpdate && this._lastTextureID === tex._updateID) { - return; + return false; } this._lastTextureID = tex._updateID; @@ -110,5 +138,7 @@ frame[3] = (tex._frame.y + tex._frame.height - margin + offset) / texBase.height; this.uClampOffset[0] = offset / texBase.realWidth; this.uClampOffset[1] = offset / texBase.realHeight; + + return true; } } diff --git a/src/extras/TilingSprite.js b/src/extras/TilingSprite.js index 1f6014f..a3f027f 100644 --- a/src/extras/TilingSprite.js +++ b/src/extras/TilingSprite.js @@ -180,9 +180,9 @@ const transform = this.worldTransform; const resolution = renderer.resolution; const baseTexture = texture.baseTexture; - const baseTextureResolution = texture.baseTexture.resolution; - const modX = (this.tilePosition.x / this.tileScale.x) % texture._frame.width; - const modY = (this.tilePosition.y / this.tileScale.y) % texture._frame.height; + const baseTextureResolution = baseTexture.resolution; + const modX = ((this.tilePosition.x / this.tileScale.x) % texture._frame.width) * baseTextureResolution; + const modY = ((this.tilePosition.y / this.tileScale.y) % texture._frame.height) * baseTextureResolution; // create a nice shiny pattern! // TODO this needs to be refreshed if texture changes.. diff --git a/src/extras/cacheAsBitmap.js b/src/extras/cacheAsBitmap.js index 5dd5640..aea06a8 100644 --- a/src/extras/cacheAsBitmap.js +++ b/src/extras/cacheAsBitmap.js @@ -227,7 +227,16 @@ this.transform._parentID = -1; // restore the transform of the cached sprite to avoid the nasty flicker.. - this.updateTransform(); + if (!this.parent) + { + this.parent = renderer._tempDisplayObjectParent; + this.updateTransform(); + this.parent = null; + } + else + { + this.updateTransform(); + } // map the hit test.. this.containsPoint = cachedSprite.containsPoint.bind(cachedSprite); @@ -314,7 +323,17 @@ cachedSprite._bounds = this._bounds; cachedSprite.alpha = cacheAlpha; - this.updateTransform(); + if (!this.parent) + { + this.parent = renderer._tempDisplayObjectParent; + this.updateTransform(); + this.parent = null; + } + else + { + this.updateTransform(); + } + this.updateTransform = this.displayObjectUpdateTransform; this._cacheData.sprite = cachedSprite; @@ -358,9 +377,12 @@ * Destroys the cached object. * * @private + * @param {object|boolean} [options] - Options parameter. A boolean will act as if all options + * have been set to that value. + * Used when destroying containers, see the Container.destroy method. */ -DisplayObject.prototype._cacheAsBitmapDestroy = function _cacheAsBitmapDestroy() +DisplayObject.prototype._cacheAsBitmapDestroy = function _cacheAsBitmapDestroy(options) { this.cacheAsBitmap = false; - this.destroy(); + this.destroy(options); }; diff --git a/src/extras/index.js b/src/extras/index.js index bbd8bbf..a34d7ec 100644 --- a/src/extras/index.js +++ b/src/extras/index.js @@ -1,7 +1,7 @@ /** + * Additional PIXI DisplayObjects for animation, tiling and bitmap text. * @namespace PIXI.extras */ -export { default as TextureTransform } from './TextureTransform'; export { default as AnimatedSprite } from './AnimatedSprite'; export { default as TilingSprite } from './TilingSprite'; export { default as TilingSpriteRenderer } from './webgl/TilingSpriteRenderer'; diff --git a/src/extras/webgl/TilingSpriteRenderer.js b/src/extras/webgl/TilingSpriteRenderer.js index aeec172..eee3b5c 100644 --- a/src/extras/webgl/TilingSpriteRenderer.js +++ b/src/extras/webgl/TilingSpriteRenderer.js @@ -11,7 +11,7 @@ * WebGL renderer plugin for tiling sprites * * @class - * @memberof PIXI + * @memberof PIXI.extras * @extends PIXI.ObjectRenderer */ export default class TilingSpriteRenderer extends core.ObjectRenderer @@ -118,7 +118,7 @@ tempMat.invert(); if (isSimple) { - tempMat.append(uv.mapCoord); + tempMat.prepend(uv.mapCoord); } else { diff --git a/src/filters/displacement/DisplacementFilter.js b/src/filters/displacement/DisplacementFilter.js index d5355c3..b5d030c 100644 --- a/src/filters/displacement/DisplacementFilter.js +++ b/src/filters/displacement/DisplacementFilter.js @@ -35,7 +35,7 @@ this.maskSprite = sprite; this.maskMatrix = maskMatrix; - this.uniforms.mapSampler = sprite.texture; + this.uniforms.mapSampler = sprite._texture; this.uniforms.filterMatrix = maskMatrix; this.uniforms.scale = { x: 1, y: 1 }; diff --git a/src/filters/index.js b/src/filters/index.js index f3e8862..7ceb50a 100644 --- a/src/filters/index.js +++ b/src/filters/index.js @@ -1,4 +1,21 @@ /** + * This namespace contains WebGL-only display filters that can be applied + * to DisplayObjects using the {@link PIXI.DisplayObject#filters filters} property. + * @example + * // Create a new application + * const app = new PIXI.Application(); + * + * // Draw a green rectangle + * const rect = new PIXI.Graphics() + * .beginFill(0x00ff00) + * .drawRect(40, 40, 200, 200); + * + * // Add a blur filter + * rect.filters = [new PIXI.filters.BlurFilter()]; + * + * // Display rectangle + * app.stage.addChild(rect); + * document.body.appendChild(app.view); * @namespace PIXI.filters */ export { default as FXAAFilter } from './fxaa/FXAAFilter'; diff --git a/src/index.js b/src/index.js index c553427..77da490 100644 --- a/src/index.js +++ b/src/index.js @@ -20,6 +20,14 @@ import { utils } from './core'; utils.mixins.performMixins(); +/** + * Alias for {@link PIXI.loaders.shared}. + * @name loader + * @memberof PIXI + * @type {PIXI.loader.Loader} + */ +const loader = loaders.shared || null; + export { accessibility, extract, @@ -30,18 +38,8 @@ mesh, particles, prepare, + loader, }; -/** - * A premade instance of the loader that can be used to load resources. - * - * @name loader - * @memberof PIXI - * @property {PIXI.loaders.Loader} - */ -const loader = loaders && loaders.Loader ? new loaders.Loader() : null; // check is there in case user excludes loader lib - -export { loader }; - // Always export pixi globally. global.PIXI = exports; // eslint-disable-line diff --git a/src/interaction/InteractionData.js b/src/interaction/InteractionData.js index 02d53a3..449f8cc 100644 --- a/src/interaction/InteractionData.js +++ b/src/interaction/InteractionData.js @@ -30,7 +30,10 @@ /** * When passed to an event handler, this will be the original DOM Event that was captured * - * @member {Event} + * @see https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent + * @see https://developer.mozilla.org/en-US/docs/Web/API/TouchEvent + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent + * @member {MouseEvent|TouchEvent|PointerEvent} */ this.originalEvent = null; } diff --git a/src/interaction/InteractionManager.js b/src/interaction/InteractionManager.js index 8baf13a..2d75c7c 100644 --- a/src/interaction/InteractionManager.js +++ b/src/interaction/InteractionManager.js @@ -13,6 +13,14 @@ const MOUSE_POINTER_ID = 'MOUSE'; +// helpers for hitTest() - only used inside hitTest() +const hitTestEvent = { + target: null, + data: { + global: null, + }, +}; + /** * The interaction manager deals with mouse, touch and pointer events. Any DisplayObject can be interactive * if its interactive parameter is set to true @@ -245,67 +253,60 @@ this.setTargetElement(this.renderer.view, this.renderer.resolution); /** - * Fired when a pointer device button (usually a mouse button) is pressed on the display + * Fired when a pointer device button (usually a mouse left-button) is pressed on the display * object. * - * @event mousedown - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mousedown + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device secondary button (usually a mouse right-button) is pressed * on the display object. * - * @event rightdown - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightdown + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** - * Fired when a pointer device button (usually a mouse button) is released over the display + * Fired when a pointer device button (usually a mouse left-button) is released over the display * object. * - * @event mouseup - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseup + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device secondary button (usually a mouse right-button) is released * over the display object. * - * @event rightup - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightup + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** - * Fired when a pointer device button (usually a mouse button) is pressed and released on + * Fired when a pointer device button (usually a mouse left-button) is pressed and released on * the display object. * - * @event click - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#click + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device secondary button (usually a mouse right-button) is pressed * and released on the display object. * - * @event rightclick - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightclick + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** - * Fired when a pointer device button (usually a mouse button) is released outside the + * Fired when a pointer device button (usually a mouse left-button) is released outside the * display object that initially registered a * [mousedown]{@link PIXI.interaction.InteractionManager#event:mousedown}. * - * @event mouseupoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** @@ -313,146 +314,362 @@ * outside the display object that initially registered a * [rightdown]{@link PIXI.interaction.InteractionManager#event:rightdown}. * - * @event rightupoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device (usually a mouse) is moved while over the display object * - * @event mousemove - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mousemove + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device (usually a mouse) is moved onto the display object * - * @event mouseover - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseover + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device (usually a mouse) is moved off the display object * - * @event mouseout - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseout + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is pressed on the display object. * - * @event pointerdown - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerdown + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is released over the display object. * - * @event pointerup - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerup + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when the operating system cancels a pointer event * - * @event pointercancel - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointercancel + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is pressed and released on the display object. * - * @event pointertap - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointertap + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is released outside the display object that initially * registered a [pointerdown]{@link PIXI.interaction.InteractionManager#event:pointerdown}. * - * @event pointerupoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device is moved while over the display object * - * @event pointermove - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointermove + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device is moved onto the display object * - * @event pointerover - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerover + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device is moved off the display object * - * @event pointerout - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerout + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is placed on the display object. * - * @event touchstart - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchstart + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is removed from the display object. * - * @event touchend - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchend + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when the operating system cancels a touch * - * @event touchcancel - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchcancel + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is placed and removed from the display object. * - * @event tap - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#tap + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is removed outside of the display object that initially * registered a [touchstart]{@link PIXI.interaction.InteractionManager#event:touchstart}. * - * @event touchendoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchendoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is moved along the display object. * - * @event touchmove - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchmove + * @param {PIXI.interaction.InteractionData} event - Interaction data */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is pressed on the display. + * object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mousedown + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is pressed + * on the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightdown + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is released over the display + * object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseup + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is released + * over the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightup + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is pressed and released on + * the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#click + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is pressed + * and released on the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightclick + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is released outside the + * display object that initially registered a + * [mousedown]{@link PIXI.DisplayObject#event:mousedown}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is released + * outside the display object that initially registered a + * [rightdown]{@link PIXI.DisplayObject#event:rightdown}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device (usually a mouse) is moved while over the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mousemove + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device (usually a mouse) is moved onto the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseover + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device (usually a mouse) is moved off the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseout + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is pressed on the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerdown + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is released over the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerup + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when the operating system cancels a pointer event. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointercancel + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is pressed and released on the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointertap + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is released outside the display object that initially + * registered a [pointerdown]{@link PIXI.DisplayObject#event:pointerdown}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device is moved while over the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointermove + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device is moved onto the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerover + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device is moved off the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerout + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is placed on the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchstart + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is removed from the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchend + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when the operating system cancels a touch. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchcancel + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is placed and removed from the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#tap + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is removed outside of the display object that initially + * registered a [touchstart]{@link PIXI.DisplayObject#event:touchstart}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchendoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is moved along the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchmove + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + } + + /** + * Hit tests a point against the display tree, returning the first interactive object that is hit. + * + * @param {PIXI.Point} globalPoint - A point to hit test with, in global space. + * @param {PIXI.Container} [root] - The root display object to start from. If omitted, defaults + * to the last rendered root of the associated renderer. + * @return {PIXI.DisplayObject} The hit display object, if any. + */ + hitTest(globalPoint, root) + { + // clear the target for our hit test + hitTestEvent.target = null; + // assign the global point + hitTestEvent.data.global = globalPoint; + // ensure safety of the root + if (!root) + { + root = this.renderer._lastObjectRendered; + } + // run the hit test + this.processInteractive(hitTestEvent, root, null, true); + // return our found object - it'll be null if we didn't hit anything + + return hitTestEvent.target; } /** @@ -487,7 +704,7 @@ return; } - core.ticker.shared.add(this.update, this); + core.ticker.shared.add(this.update, this, core.UPDATE_PRIORITY.INTERACTION); if (window.navigator.msPointerEnabled) { @@ -804,8 +1021,6 @@ } } - let keepHitTestingAfterChildren = hitTest; - // ** FREE TIP **! If an object is not interactive or has no buttons in it // (such as a game scene!) set interactiveChildren to false for that displayObject. // This will allow pixi to completely ignore and bypass checking the displayObjects children. @@ -818,7 +1033,9 @@ const child = children[i]; // time to get recursive.. if this function will return if something is hit.. - if (this.processInteractive(interactionEvent, child, func, hitTest, interactiveParent)) + const childHit = this.processInteractive(interactionEvent, child, func, hitTest, interactiveParent); + + if (childHit) { // its a good idea to check if a child has lost its parent. // this means it has been removed whilst looping so its best @@ -827,8 +1044,6 @@ continue; } - hit = true; - // we no longer need to hit test any more objects in this container as we we // now know the parent has been hit interactiveParent = false; @@ -838,36 +1053,41 @@ // This means we no longer need to hit test anything else. We still need to run // through all objects, but we don't need to perform any hit tests. - keepHitTestingAfterChildren = false; - - if (child.interactive) + if (childHit) { - hitTest = false; + if (interactionEvent.target) + { + hitTest = false; + } + hit = true; } - - // we can break now as we have hit an object. } } } - hitTest = keepHitTestingAfterChildren; - // no point running this if the item is not interactive or does not have an interactive parent. if (interactive) { // if we are hit testing (as in we have no hit any objects yet) // We also don't need to worry about hit testing if once of the displayObjects children - // has already been hit! - if (hitTest && !hit) + // has already been hit - but only if it was interactive, otherwise we need to keep + // looking for an interactive child, just in case we hit one + if (hitTest && !interactionEvent.target) { if (displayObject.hitArea) { displayObject.worldTransform.applyInverse(point, this._tempPoint); - hit = displayObject.hitArea.contains(this._tempPoint.x, this._tempPoint.y); + if (displayObject.hitArea.contains(this._tempPoint.x, this._tempPoint.y)) + { + hit = true; + } } else if (displayObject.containsPoint) { - hit = displayObject.containsPoint(point); + if (displayObject.containsPoint(point)) + { + hit = true; + } } } @@ -878,7 +1098,10 @@ interactionEvent.target = displayObject; } - func(interactionEvent, displayObject, hit); + if (func) + { + func(interactionEvent, displayObject, !!hit); + } } } @@ -914,7 +1137,7 @@ { const event = events[i]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -998,7 +1221,7 @@ { const event = events[i]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1098,7 +1321,7 @@ const test = isRightButton ? flags.RIGHT_DOWN : flags.LEFT_DOWN; - const isDown = trackingData !== undefined && (trackingData.flags | test); + const isDown = trackingData !== undefined && (trackingData.flags & test); if (hit) { @@ -1118,11 +1341,11 @@ { if (isRightButton) { - trackingData.rightDown = hit; + trackingData.rightDown = false; } else { - trackingData.leftDown = hit; + trackingData.leftDown = false; } } } @@ -1180,7 +1403,7 @@ { const event = events[i]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1255,7 +1478,7 @@ this.setCursorMode(null); } - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1268,6 +1491,12 @@ { this.emit('mouseout', interactionEvent); } + else + { + // we can get touchleave events after touchend, so we want to make sure we don't + // introduce memory leaks + this.releaseInteractionDataForPointerId(interactionData.identifier); + } } /** @@ -1344,7 +1573,7 @@ // Only mouse and pointer can call onPointerOver, so events will always be length 1 const event = events[0]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1366,12 +1595,14 @@ * Get InteractionData for a given pointerId. Store that data as well * * @private - * @param {number} pointerId - Identifier from a pointer event + * @param {PointerEvent} event - Normalized pointer event, output from normalizeToPointerData * @return {InteractionData} - Interaction data for the given pointer identifier */ - getInteractionDataForPointerId(pointerId) + getInteractionDataForPointerId(event) { - if (pointerId === MOUSE_POINTER_ID) + const pointerId = event.pointerId; + + if (pointerId === MOUSE_POINTER_ID || event.pointerType === 'mouse') { return this.mouse; } diff --git a/src/interaction/InteractionTrackingData.js b/src/interaction/InteractionTrackingData.js index 6a1f69e..8493b63 100644 --- a/src/interaction/InteractionTrackingData.js +++ b/src/interaction/InteractionTrackingData.js @@ -35,8 +35,10 @@ } /** + * Unique pointer id of the event + * * @readonly - * @type {number} Unique pointer id of the event + * @member {number} */ get pointerId() { diff --git a/src/interaction/index.js b/src/interaction/index.js index 56c5b09..72f7d71 100644 --- a/src/interaction/index.js +++ b/src/interaction/index.js @@ -1,4 +1,8 @@ /** + * This namespace contains a renderer plugin for handling mouse, pointer, and touch events. + * + * Do not instantiate this plugin directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. * @namespace PIXI.interaction */ export { default as InteractionData } from './InteractionData'; diff --git a/src/interaction/interactiveTarget.js b/src/interaction/interactiveTarget.js index d252fba..cebac40 100644 --- a/src/interaction/interactiveTarget.js +++ b/src/interaction/interactiveTarget.js @@ -2,7 +2,7 @@ * Default property values of interactive objects * Used by {@link PIXI.interaction.InteractionManager} to automatically give all DisplayObjects these properties * - * @mixin + * @private * @name interactiveTarget * @memberof PIXI.interaction * @example @@ -14,18 +14,28 @@ * ); */ export default { + /** - * Determines if the displayObject be clicked/touched + * Enable interaction events for the DisplayObject. Touch, pointer and mouse + * events will not be emitted unless `interactive` is set to `true`. * - * @inner {boolean} + * @example + * const sprite = new PIXI.Sprite(texture); + * sprite.interactive = true; + * sprite.on('tap', (event) => { + * //handle event + * }); + * @member {boolean} + * @memberof PIXI.DisplayObject# */ interactive: false, /** * Determines if the children to the displayObject can be clicked/touched - * Setting this to false allows pixi to bypass a recursive hitTest function + * Setting this to false allows pixi to bypass a recursive `hitTest` function * - * @inner {boolean} + * @member {boolean} + * @memberof PIXI.Container# */ interactiveChildren: true, @@ -33,7 +43,12 @@ * Interaction shape. Children will be hit first, then this shape will be checked. * Setting this will cause this shape to be checked in hit tests rather than the displayObject's bounds. * - * @inner {PIXI.Rectangle|PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.RoundedRectangle} + * @example + * const sprite = new PIXI.Sprite(texture); + * sprite.interactive = true; + * sprite.hitArea = new PIXI.Rectangle(0, 0, 100, 100); + * @member {PIXI.Rectangle|PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.RoundedRectangle} + * @memberof PIXI.DisplayObject# */ hitArea: null, @@ -41,8 +56,12 @@ * If enabled, the mouse cursor use the pointer behavior when hovered over the displayObject if it is interactive * Setting this changes the 'cursor' property to `'pointer'`. * + * @example + * const sprite = new PIXI.Sprite(texture); + * sprite.interactive = true; + * sprite.buttonMode = true; * @member {boolean} - * @memberof PIXI.interaction.interactiveTarget# + * @memberof PIXI.DisplayObject# */ get buttonMode() { @@ -64,9 +83,14 @@ * This defines what cursor mode is used when the mouse cursor * is hovered over the displayObject. * + * @example + * const sprite = new PIXI.Sprite(texture); + * sprite.interactive = true; + * sprite.cursor = 'wait'; * @see https://developer.mozilla.org/en/docs/Web/CSS/cursor * - * @inner {string} + * @member {string} + * @memberof PIXI.DisplayObject# */ cursor: null, @@ -74,7 +98,7 @@ * Internal set of all active pointers, by identifier * * @member {Map} - * @memberof PIXI.interaction.interactiveTarget# + * @memberof PIXI.DisplayObject# * @private */ get trackedPointers() @@ -87,7 +111,8 @@ /** * Map of all tracked pointers, by identifier. Use trackedPointers to access. * - * @private {Map} + * @private + * @type {Map} */ _trackedPointers: undefined, }; diff --git a/src/loaders/index.js b/src/loaders/index.js index 5189793..daa1fc4 100644 --- a/src/loaders/index.js +++ b/src/loaders/index.js @@ -1,9 +1,22 @@ +import Application from '../core/Application'; +import Loader from './loader'; + /** + * This namespace contains APIs which extends the {@link https://github.com/englercj/resource-loader resource-loader} module + * for loading assets, data, and other resources dynamically. + * @example + * const loader = new PIXI.loaders.Loader(); + * loader.add('bunny', 'data/bunny.png') + * .add('spaceship', 'assets/spritesheet.json'); + * loader.load((loader, resources) => { + * // resources.bunny + * // resources.spaceship + * }); * @namespace PIXI.loaders */ -export { default as Loader } from './loader'; +export { Loader }; export { default as bitmapFontParser, parse as parseBitmapFontData } from './bitmapFontParser'; -export { default as spritesheetParser } from './spritesheetParser'; +export { default as spritesheetParser, getResourcePath } from './spritesheetParser'; export { default as textureParser } from './textureParser'; /** @@ -13,3 +26,55 @@ * @memberof PIXI.loaders */ export { Resource } from 'resource-loader'; + +/** + * A premade instance of the loader that can be used to load resources. + * @name shared + * @memberof PIXI.loaders + * @type {PIXI.loaders.Loader} + */ +const shared = new Loader(); + +shared.destroy = () => +{ + // protect destroying shared loader +}; + +export { shared }; + +// Mixin the loader construction +const AppPrototype = Application.prototype; + +AppPrototype._loader = null; + +/** + * Loader instance to help with asset loading. + * @name PIXI.Application#loader + * @type {PIXI.loaders.Loader} + */ +Object.defineProperty(AppPrototype, 'loader', { + get() + { + if (!this._loader) + { + const sharedLoader = this._options.sharedLoader; + + this._loader = sharedLoader ? shared : new Loader(); + } + + return this._loader; + }, +}); + +// Override the destroy function +// making sure to destroy the current Loader +AppPrototype._parentDestroy = AppPrototype.destroy; +AppPrototype.destroy = function destroy() +{ + if (this._loader) + { + this._loader.destroy(); + this._loader = null; + } + this._parentDestroy(); +}; diff --git a/src/loaders/loader.js b/src/loaders/loader.js index 767a411..59b8000 100644 --- a/src/loaders/loader.js +++ b/src/loaders/loader.js @@ -90,6 +90,15 @@ { Loader._pixiMiddleware.push(fn); } + + /** + * Destroy the loader, removes references. + */ + destroy() + { + this.removeAllListeners(); + this.reset(); + } } // Copy EE3 prototype (mixin) diff --git a/src/loaders/spritesheetParser.js b/src/loaders/spritesheetParser.js index e57dfb2..eda9584 100644 --- a/src/loaders/spritesheetParser.js +++ b/src/loaders/spritesheetParser.js @@ -1,12 +1,11 @@ import { Resource } from 'resource-loader'; -import path from 'path'; +import url from 'url'; import { Spritesheet } from '../core'; export default function () { return function spritesheetParser(resource, next) { - let resourcePath; const imageResourceName = `${resource.name}_image`; // skip if no data, its not json, it isn't spritesheet data, or the image resource already exists @@ -28,15 +27,7 @@ parentResource: resource, }; - // Prepend url path unless the resource image is a data url - if (resource.isDataUrl) - { - resourcePath = resource.data.meta.image; - } - else - { - resourcePath = `${path.dirname(resource.url.replace(this.baseUrl, ''))}/${resource.data.meta.image}`; - } + const resourcePath = getResourcePath(resource, this.baseUrl); // load the image for this sheet this.add(imageResourceName, resourcePath, loadOptions, function onImageLoad(res) @@ -56,3 +47,14 @@ }); }; } + +export function getResourcePath(resource, baseUrl) +{ + // Prepend url path unless the resource image is a data url + if (resource.isDataUrl) + { + return resource.data.meta.image; + } + + return url.resolve(resource.url.replace(baseUrl, ''), resource.data.meta.image); +} diff --git a/src/mesh/canvas/CanvasMeshRenderer.js b/src/mesh/canvas/CanvasMeshRenderer.js index 297205f..f7899e1 100644 --- a/src/mesh/canvas/CanvasMeshRenderer.js +++ b/src/mesh/canvas/CanvasMeshRenderer.js @@ -134,12 +134,33 @@ const textureWidth = base.width; const textureHeight = base.height; - const u0 = uvs[index0] * base.width; - const u1 = uvs[index1] * base.width; - const u2 = uvs[index2] * base.width; - const v0 = uvs[index0 + 1] * base.height; - const v1 = uvs[index1 + 1] * base.height; - const v2 = uvs[index2 + 1] * base.height; + let u0; + let u1; + let u2; + let v0; + let v1; + let v2; + + if (mesh.uploadUvTransform) + { + const ut = mesh._uvTransform.mapCoord; + + u0 = ((uvs[index0] * ut.a) + (uvs[index0 + 1] * ut.c) + ut.tx) * base.width; + u1 = ((uvs[index1] * ut.a) + (uvs[index1 + 1] * ut.c) + ut.tx) * base.width; + u2 = ((uvs[index2] * ut.a) + (uvs[index2 + 1] * ut.c) + ut.tx) * base.width; + v0 = ((uvs[index0] * ut.b) + (uvs[index0 + 1] * ut.d) + ut.ty) * base.height; + v1 = ((uvs[index1] * ut.b) + (uvs[index1 + 1] * ut.d) + ut.ty) * base.height; + v2 = ((uvs[index2] * ut.b) + (uvs[index2 + 1] * ut.d) + ut.ty) * base.height; + } + else + { + u0 = uvs[index0] * base.width; + u1 = uvs[index1] * base.width; + u2 = uvs[index2] * base.width; + v0 = uvs[index0 + 1] * base.height; + v1 = uvs[index1 + 1] * base.height; + v2 = uvs[index2 + 1] * base.height; + } let x0 = vertices[index0]; let x1 = vertices[index1]; diff --git a/src/mesh/webgl/MeshRenderer.js b/src/mesh/webgl/MeshRenderer.js index 78d24c3..ab99fcd 100644 --- a/src/mesh/webgl/MeshRenderer.js +++ b/src/mesh/webgl/MeshRenderer.js @@ -2,6 +2,8 @@ const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; +const matrixIdentity = core.Matrix.IDENTITY; + /** * WebGL renderer plugin for tiling sprites * @@ -64,6 +66,7 @@ mesh.geometry.glVertexArrayObjects[this.CONTEXT_UID].draw(mesh.drawMode, mesh.size, mesh.start, mesh.geometry.instanceCount); } + /** * draws mesh * @param {PIXI.mesh.RawMesh} mesh mesh instance diff --git a/src/mesh/webgl/mesh.vert b/src/mesh/webgl/mesh.vert index a337aef..acc096c 100644 --- a/src/mesh/webgl/mesh.vert +++ b/src/mesh/webgl/mesh.vert @@ -1,8 +1,9 @@ attribute vec2 aVertexPosition; attribute vec2 aTextureCoord; -uniform mat3 translationMatrix; uniform mat3 projectionMatrix; +uniform mat3 translationMatrix; +uniform mat3 uTransform; varying vec2 vTextureCoord; @@ -10,5 +11,5 @@ { gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - vTextureCoord = aTextureCoord; + vTextureCoord = (uTransform * vec3(aTextureCoord, 1.0)).xy; } diff --git a/src/particles/ParticleContainer.js b/src/particles/ParticleContainer.js index 67796cf..63f7791 100644 --- a/src/particles/ParticleContainer.js +++ b/src/particles/ParticleContainer.js @@ -313,10 +313,10 @@ frame.y * resolution, frame.width * resolution, frame.height * resolution, - positionX * resolution, - positionY * resolution, - finalWidth * resolution, - finalHeight * resolution + positionX * renderer.resolution, + positionY * renderer.resolution, + finalWidth * renderer.resolution, + finalHeight * renderer.resolution ); } } diff --git a/src/prepare/BasePrepare.js b/src/prepare/BasePrepare.js index 09eb5e5..e45c4dc 100644 --- a/src/prepare/BasePrepare.js +++ b/src/prepare/BasePrepare.js @@ -18,6 +18,18 @@ * basic queuing functionality and is extended by {@link PIXI.prepare.WebGLPrepare} and {@link PIXI.prepare.CanvasPrepare} * to provide preparation capabilities specific to their respective renderers. * + * @example + * // Create a sprite + * const sprite = new PIXI.Sprite.fromImage('something.png'); + * + * // Load object into GPU + * app.renderer.plugins.prepare.upload(sprite, () => { + * + * //Texture(s) has been uploaded to GPU + * app.stage.addChild(sprite); + * + * }) + * * @abstract * @class * @memberof PIXI.prepare @@ -100,8 +112,16 @@ this.prepareItems(); }; - this.register(findText, drawText); - this.register(findTextStyle, calculateTextStyle); + // hooks to find the correct texture + this.registerFindHook(findText); + this.registerFindHook(findTextStyle); + this.registerFindHook(findMultipleBaseTextures); + this.registerFindHook(findBaseTexture); + this.registerFindHook(findTexture); + + // upload hooks + this.registerUploadHook(drawText); + this.registerUploadHook(calculateTextStyle); } /** @@ -138,7 +158,7 @@ if (!this.ticking) { this.ticking = true; - SharedTicker.addOnce(this.tick, this); + SharedTicker.addOnce(this.tick, this, core.UPDATE_PRIORITY.UTILITY); } } else if (done) @@ -172,13 +192,16 @@ const item = this.queue[0]; let uploaded = false; - for (let i = 0, len = this.uploadHooks.length; i < len; i++) + if (item && !item._destroyed) { - if (this.uploadHooks[i](this.uploadHookHelper, item)) + for (let i = 0, len = this.uploadHooks.length; i < len; i++) { - this.queue.shift(); - uploaded = true; - break; + if (this.uploadHooks[i](this.uploadHookHelper, item)) + { + this.queue.shift(); + uploaded = true; + break; + } } } @@ -205,26 +228,36 @@ else { // if we are not finished, on the next rAF do this again - SharedTicker.addOnce(this.tick, this); + SharedTicker.addOnce(this.tick, this, core.UPDATE_PRIORITY.UTILITY); } } /** - * Adds hooks for finding and uploading items. + * Adds hooks for finding items. * - * @param {Function} [addHook] - Function call that takes two parameters: `item:*, queue:Array` - function must return `true` if it was able to add item to the queue. - * @param {Function} [uploadHook] - Function call that takes two parameters: `prepare:CanvasPrepare, item:*` and - * function must return `true` if it was able to handle upload of item. - * @return {PIXI.CanvasPrepare} Instance of plugin for chaining. + * @param {Function} addHook - Function call that takes two parameters: `item:*, queue:Array` + * function must return `true` if it was able to add item to the queue. + * @return {PIXI.BasePrepare} Instance of plugin for chaining. */ - register(addHook, uploadHook) + registerFindHook(addHook) { if (addHook) { this.addHooks.push(addHook); } + return this; + } + + /** + * Adds hooks for uploading items. + * + * @param {Function} uploadHook - Function call that takes two parameters: `prepare:CanvasPrepare, item:*` and + * function must return `true` if it was able to handle upload of item. + * @return {PIXI.BasePrepare} Instance of plugin for chaining. + */ + registerUploadHook(uploadHook) + { if (uploadHook) { this.uploadHooks.push(uploadHook); @@ -287,6 +320,88 @@ } /** + * Built-in hook to find multiple textures from objects like AnimatedSprites. + * + * @private + * @param {PIXI.DisplayObject} item - Display object to check + * @param {Array<*>} queue - Collection of items to upload + * @return {boolean} if a PIXI.Texture object was found. + */ +function findMultipleBaseTextures(item, queue) +{ + let result = false; + + // Objects with mutliple textures + if (item && item._textures && item._textures.length) + { + for (let i = 0; i < item._textures.length; i++) + { + if (item._textures[i] instanceof core.Texture) + { + const baseTexture = item._textures[i].baseTexture; + + if (queue.indexOf(baseTexture) === -1) + { + queue.push(baseTexture); + result = true; + } + } + } + } + + return result; +} + +/** + * Built-in hook to find BaseTextures from Sprites. + * + * @private + * @param {PIXI.DisplayObject} item - Display object to check + * @param {Array<*>} queue - Collection of items to upload + * @return {boolean} if a PIXI.Texture object was found. + */ +function findBaseTexture(item, queue) +{ + // Objects with textures, like Sprites/Text + if (item instanceof core.BaseTexture) + { + if (queue.indexOf(item) === -1) + { + queue.push(item); + } + + return true; + } + + return false; +} + +/** + * Built-in hook to find textures from objects. + * + * @private + * @param {PIXI.DisplayObject} item - Display object to check + * @param {Array<*>} queue - Collection of items to upload + * @return {boolean} if a PIXI.Texture object was found. + */ +function findTexture(item, queue) +{ + if (item._texture && item._texture instanceof core.Texture) + { + const texture = item._texture.baseTexture; + + if (queue.indexOf(texture) === -1) + { + queue.push(texture); + } + + return true; + } + + return false; +} + +/** * Built-in hook to draw PIXI.Text to its texture. * * @private diff --git a/src/prepare/canvas/CanvasPrepare.js b/src/prepare/canvas/CanvasPrepare.js index 0224bb5..09c207a 100644 --- a/src/prepare/canvas/CanvasPrepare.js +++ b/src/prepare/canvas/CanvasPrepare.js @@ -43,7 +43,7 @@ this.ctx = this.canvas.getContext('2d'); // Add textures to upload - this.register(findBaseTextures, uploadBaseTextures); + this.registerUploadHook(uploadBaseTextures); } /** @@ -89,39 +89,4 @@ return false; } -/** - * Built-in hook to find textures from Sprites. - * - * @private - * @param {PIXI.DisplayObject} item -Display object to check - * @param {Array<*>} queue - Collection of items to upload - * @return {boolean} if a PIXI.Texture object was found. - */ -function findBaseTextures(item, queue) -{ - // Objects with textures, like Sprites/Text - if (item instanceof core.BaseTexture) - { - if (queue.indexOf(item) === -1) - { - queue.push(item); - } - - return true; - } - else if (item._texture && item._texture instanceof core.Texture) - { - const texture = item._texture.baseTexture; - - if (queue.indexOf(texture) === -1) - { - queue.push(texture); - } - - return true; - } - - return false; -} - core.CanvasRenderer.registerPlugin('prepare', CanvasPrepare); diff --git a/src/prepare/index.js b/src/prepare/index.js index f559c45..e7491ec 100644 --- a/src/prepare/index.js +++ b/src/prepare/index.js @@ -1,4 +1,29 @@ /** + * The prepare namespace provides renderer-specific plugins for pre-rendering DisplayObjects. These plugins are useful for + * asynchronously preparing assets, textures, graphics waiting to be displayed. + * + * Do not instantiate these plugins directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. + * @example + * // Create a new application + * const app = new PIXI.Application(); + * document.body.appendChild(app.view); + * + * // Don't start rendering right away + * app.stop(); + * + * // create a display object + * const rect = new PIXI.Graphics() + * .beginFill(0x00ff00) + * .drawRect(40, 40, 200, 200); + * + * // Add to the stage + * app.stage.addChild(rect); + * + * // Don't start rendering until the graphic is uploaded to the GPU + * app.renderer.plugins.prepare.upload(app.stage, () => { + * app.start(); + * }); * @namespace PIXI.prepare */ export { default as webgl } from './webgl/WebGLPrepare'; diff --git a/src/prepare/webgl/WebGLPrepare.js b/src/prepare/webgl/WebGLPrepare.js index eb7023f..9df3c3f 100644 --- a/src/prepare/webgl/WebGLPrepare.js +++ b/src/prepare/webgl/WebGLPrepare.js @@ -22,12 +22,11 @@ this.uploadHookHelper = this.renderer; // Add textures and graphics to upload - this.register(findBaseTextures, uploadBaseTextures) - .register(findGraphics, uploadGraphics); + this.registerFindHook(findGraphics); + this.registerUploadHook(uploadBaseTextures); + this.registerUploadHook(uploadGraphics); } - } - /** * Built-in hook to upload PIXI.Texture objects to the GPU. * @@ -80,41 +79,6 @@ } /** - * Built-in hook to find textures from Sprites. - * - * @private - * @param {PIXI.DisplayObject} item - Display object to check - * @param {Array<*>} queue - Collection of items to upload - * @return {boolean} if a PIXI.Texture object was found. - */ -function findBaseTextures(item, queue) -{ - // Objects with textures, like Sprites/Text - if (item instanceof core.BaseTexture) - { - if (queue.indexOf(item) === -1) - { - queue.push(item); - } - - return true; - } - else if (item._texture && item._texture instanceof core.Texture) - { - const texture = item._texture.baseTexture; - - if (queue.indexOf(texture) === -1) - { - queue.push(texture); - } - - return true; - } - - return false; -} - -/** * Built-in hook to find graphics. * * @private diff --git a/test/.eslintrc.json b/test/.eslintrc.json index e03220d..ac12110 100644 --- a/test/.eslintrc.json +++ b/test/.eslintrc.json @@ -12,7 +12,8 @@ "sinon": false, "expect": false, "assert": false, - "PIXI": false + "PIXI": false, + "PointerEvent": true }, "rules": { "func-names": 0, diff --git a/.travis.yml b/.travis.yml index 3df8b22..14de334 100644 --- a/.travis.yml +++ b/.travis.yml @@ -110,3 +110,43 @@ on: all_branches: true condition: $TRAVIS_TAG + # Deploy config for latest release + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: dist + upload-dir: "release" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: docs + upload-dir: "release/docs" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: coverage + upload-dir: "release/coverage" + on: + all_branches: true + condition: $TRAVIS_TAG \ No newline at end of file diff --git a/README.md b/README.md index d26fadd..3b85f85 100644 --- a/README.md +++ b/README.md @@ -36,22 +36,14 @@ - Wiki: Other misc tutorials and resources are [on the Wiki](https://github.com/pixijs/pixi.js/wiki/Resources). ### Community ### -- Forums: Check out the [forum] (http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow] (http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. +- Forums: Check out the [forum](http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow](http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. - Inspiration: Check out the [gallery](http://www.pixijs.com/gallery) to see some of the amazing things people have created! - Chat: You can join us on [Gitter](https://gitter.im/pixijs/pixi.js) To chat about Pixi. We also now have a Slack channel. If you would like to join it please Send me an email (mat@goodboydigital.com) and I will invite you in. ### Setup ### -It's easy to get started with Pixi.js! Simply grab the pre-built versions from here: - -Release Branch - Nice and stable Pixi.js -- Unminified: [http://pixijs.download/release/pixi.js] -- Minified: [http://pixijs.download/release/pixi.min.js] - -Develop Branch - The bleeding edge version of Pixi.js -- Unminified: [http://pixijs.download/dev/pixi.js] -- Minified: [http://pixijs.download/dev/pixi.min.js] +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. @@ -70,10 +62,10 @@ #### CDN Install (via cdnjs) ```html - + ``` -_Note: `4.2.2` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ +_Note: `4.4.3` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ ### Demos ### @@ -130,7 +122,7 @@ // and the root stage PIXI.Container. var app = new PIXI.Application(); -// The application will create a canvas element for you that you +// The application will create a canvas element for you that you // can then insert into the DOM. document.body.appendChild(app.view); @@ -150,7 +142,7 @@ // Add the bunny to the scene we are building. app.stage.addChild(bunny); - + // Listen for frame updates app.ticker.add(function() { // each frame we spin the bunny around a bit diff --git a/bower.json b/bower.json deleted file mode 100644 index 41b668c..0000000 --- a/bower.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "pixi.js", - "main": "dist/pixi.min.js", - "ignore": [ - "**/.*", - "docs", - "node_modules", - "bower_components", - "test", - "scripts", - "bower.json", - "inch.json" - ], - "dependencies": { - }, - "devDependencies": { - } -} diff --git a/package.json b/package.json index d35cbb2..308469f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.3.5", + "version": "4.4.4", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ @@ -40,11 +40,11 @@ "lib": "babel src --out-dir lib -s", "predocs": "rimraf docs/**", "docs": "jsdoc -c scripts/jsdoc.conf.json -R README.md", - "publish:patch": "npm version patch --no-git-tag-version && npm publish", - "publish:minor": "npm version minor --no-git-tag-version && npm publish", - "publish:major": "npm version major --no-git-tag-version && npm publish", + "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", - "postpublish": "node scripts/release.js" + "postpublish": "git push && git push --tags" }, "files": [ "dist/", @@ -72,7 +72,6 @@ "electron": "^1.4.15", "eslint": "^3.5.0", "floss": "^2.0.1", - "gh-pages": "^0.11.0", "jaguarjs-jsdoc": "^1.0.1", "js-md5": "^0.4.1", "jsdoc": "^3.4.2", diff --git a/scripts/jsdoc.conf.json b/scripts/jsdoc.conf.json index 553e61c..3f52180 100644 --- a/scripts/jsdoc.conf.json +++ b/scripts/jsdoc.conf.json @@ -43,7 +43,7 @@ }, "markdown" : { "parser" : "gfm", - "hardwrap" : true + "hardwrap" : false }, "opts": { "encoding" : "utf8", diff --git a/scripts/release.js b/scripts/release.js deleted file mode 100644 index 156620c..0000000 --- a/scripts/release.js +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env node -'use strict'; - -// Publish script to push releases of the bin files -// the normally are gitignored -const ghpages = require('gh-pages'); -const path = require('path'); -const packageInfo = require(path.join(__dirname, '..', 'package.json')); - -const options = { - src: [ - 'dist/**/*', - 'lib/**/*', - 'src/**/*', - 'scripts/*', - 'scripts/renders/*', - 'test/**/*', - '*.json', - '*.md', - 'LICENSE', - '.eslintrc', - '.editorconfig', - '.travis.yml', - '.babelrc', - ], - dotfiles: true, - branch: 'release', - message: packageInfo.version, - logger: console.log.bind(console), -}; - -ghpages.publish(process.cwd(), options, (err) => -{ - if (err) - { - console.log(err); - process.exit(1); - - return; - } - - process.exit(0); -}); diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index f16bcb1..220427a 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -21,7 +21,7 @@ const DIV_HOOK_ZINDEX = 2; /** - * The Accessibility manager reacreates the ability to tab and and have content read by screen + * The Accessibility manager recreates the ability to tab and and have content read by screen * readers. This is very important as it can possibly help people with disabilities access pixi * content. * diff --git a/src/accessibility/index.js b/src/accessibility/index.js index 568cfb1..0ae108f 100644 --- a/src/accessibility/index.js +++ b/src/accessibility/index.js @@ -1,4 +1,9 @@ /** + * This namespace contains a renderer plugin for interaction accessibility for end-users + * with physical impairments which require screen-renders, keyboard navigation, etc. + * + * Do not instantiate this plugin directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. * @namespace PIXI.accessibility */ export { default as accessibleTarget } from './accessibleTarget'; diff --git a/src/core/Application.js b/src/core/Application.js index 5340578..2fcc729 100644 --- a/src/core/Application.js +++ b/src/core/Application.js @@ -1,6 +1,8 @@ import { autoDetectRenderer } from './autoDetectRenderer'; import Container from './display/Container'; import { shared, Ticker } from './ticker'; +import settings from './settings'; +import { UPDATE_PRIORITY } from './const'; /** * Convenience class to create a new PIXI application. @@ -22,28 +24,52 @@ */ export default class Application { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experience unexplained flickering try setting this to true. - * @param {boolean} [useSharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedLoader=false] - `true` to use PIXI.loaders.shared, `false` to create new Loader. */ - constructor(width, height, options, noWebGL, useSharedTicker = false) + constructor(options, arg2, arg3, arg4, arg5) { + // Support for constructor(width, height, options, noWebGL, useSharedTicker) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + forceCanvas: !!arg4, + sharedTicker: !!arg5, + }, arg3); + } + + /** + * The default options, so we mixin functionality later. + * @member {object} + * @protected + */ + this._options = options = Object.assign({ + sharedTicker: false, + forceCanvas: false, + sharedLoader: false, + }, options); + /** * WebGL renderer if available, otherwise CanvasRenderer * @member {PIXI.WebGLRenderer|PIXI.CanvasRenderer} */ - this.renderer = autoDetectRenderer(width, height, options, noWebGL); + this.renderer = autoDetectRenderer(options); /** * The root display container that's rendered. @@ -63,7 +89,7 @@ * @member {PIXI.ticker.Ticker} * @default PIXI.ticker.shared */ - this.ticker = useSharedTicker ? shared : new Ticker(); + this.ticker = options.sharedTicker ? shared : new Ticker(); // Start the rendering this.start(); @@ -78,7 +104,7 @@ this._ticker = ticker; if (ticker) { - ticker.add(this.render, this); + ticker.add(this.render, this, UPDATE_PRIORITY.LOW); } } get ticker() // eslint-disable-line require-jsdoc @@ -136,13 +162,18 @@ */ destroy(removeView) { - this.stop(); + const oldTicker = this._ticker; + this.ticker = null; + oldTicker.destroy(); + this.stage.destroy(); this.stage = null; this.renderer.destroy(removeView); this.renderer = null; + + this._options = null; } } diff --git a/src/core/autoDetectRenderer.js b/src/core/autoDetectRenderer.js index 8960425..9ec0364 100644 --- a/src/core/autoDetectRenderer.js +++ b/src/core/autoDetectRenderer.js @@ -2,6 +2,7 @@ import CanvasRenderer from './renderers/canvas/CanvasRenderer'; import WebGLRenderer from './renderers/webgl/WebGLRenderer'; +// eslint-disable-next-line valid-jsdoc /** * This helper function will automatically detect which renderer you should be using. * WebGL is the preferred renderer as it is a lot faster. If webGL is not supported by @@ -9,24 +10,32 @@ * * @memberof PIXI * @function autoDetectRenderer - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @return {PIXI.WebGLRenderer|PIXI.CanvasRenderer} Returns WebGL renderer if available, otherwise CanvasRenderer */ -export function autoDetectRenderer(width = 800, height = 600, options, noWebGL) +export function autoDetectRenderer(options, arg1, arg2, arg3) { - if (!noWebGL && utils.isWebGLSupported()) + // Backward-compatible support for noWebGL option + let forceCanvas = options && options.forceCanvas; + + if (arg3 !== undefined) { - return new WebGLRenderer(width, height, options); + forceCanvas = arg3; } - return new CanvasRenderer(width, height, options); + if (!forceCanvas && utils.isWebGLSupported()) + { + return new WebGLRenderer(options, arg1, arg2); + } + + return new CanvasRenderer(options, arg1, arg2); } diff --git a/src/core/const.js b/src/core/const.js index b9e99ae..2c68dee 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -341,3 +341,27 @@ LINEAR_VERTICAL: 0, LINEAR_HORIZONTAL: 1, }; + +/** + * Represents the update priorities used by internal PIXI classes when registered with + * the {@link PIXI.ticker.Ticker} object. Higher priority items are updated first and lower + * priority items, such as render, should go later. + * + * @static + * @constant + * @name UPDATE_PRIORITY + * @memberof PIXI + * @type {object} + * @property {number} INTERACTION=50 Highest priority, used for {@link PIXI.interaction.InteractionManager} + * @property {number} HIGH=25 High priority updating, {@link PIXI.VideoBaseTexture} and {@link PIXI.extras.AnimatedSprite} + * @property {number} NORMAL=0 Default priority for ticker events, see {@link PIXI.ticker.Ticker#add}. + * @property {number} LOW=-25 Low priority used for {@link PIXI.Application} rendering. + * @property {number} UTILITY=-50 Lowest priority used for {@link PIXI.prepare.BasePrepare} utility. + */ +export const UPDATE_PRIORITY = { + INTERACTION: 50, + HIGH: 25, + NORMAL: 0, + LOW: -25, + UTILITY: -50, +}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 4e6c77d..decb42a 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -13,7 +13,6 @@ * * @class * @extends EventEmitter - * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ export default class DisplayObject extends EventEmitter @@ -113,6 +112,29 @@ * @private */ this._mask = null; + + /** + * If the object has been destroyed via destroy(). If true, it should not be used. + * + * @member {boolean} + * @private + * @readonly + */ + this._destroyed = false; + + /** + * Fired when this DisplayObject is added to a Container. + * + * @event PIXI.DisplayObject#added + * @param {PIXI.Container} container - The container added to. + */ + + /** + * Fired when this DisplayObject is removed from a Container. + * + * @event PIXI.DisplayObject#removed + * @param {PIXI.Container} container - The container removed from. + */ } /** @@ -403,6 +425,8 @@ this.interactive = false; this.interactiveChildren = false; + + this._destroyed = true; } /** diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index aafdfe2..f787c4a 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -792,8 +792,8 @@ sprite.worldAlpha = this.worldAlpha * sprite.alpha; sprite.blendMode = this.blendMode; - sprite.texture._frame.width = rect.width; - sprite.texture._frame.height = rect.height; + sprite._texture._frame.width = rect.width; + sprite._texture._frame.height = rect.height; sprite.transform.worldTransform = this.transform.worldTransform; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index a24c775..b9f192d 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -19,11 +19,12 @@ */ export default class SystemRenderer extends EventEmitter { + // eslint-disable-next-line valid-jsdoc /** * @param {string} system - The name of the system this renderer is for. - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -37,27 +38,31 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(system, screenWidth, screenHeight, options) + constructor(system, options, arg2, arg3) { super(); sayHello(system); - // prepare options - if (options) + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') { - for (const i in settings.RENDER_OPTIONS) - { - if (typeof options[i] === 'undefined') - { - options[i] = settings.RENDER_OPTIONS[i]; - } - } + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); } - else - { - options = settings.RENDER_OPTIONS; - } + + // Add the default render options + options = Object.assign({}, settings.RENDER_OPTIONS, options); + + /** + * The supplied constructor options. + * + * @member {Object} + * @readOnly + */ + this.options = options; /** * The type of the renderer. @@ -75,7 +80,7 @@ * * @member {PIXI.Rectangle} */ - this.screen = new Rectangle(0, 0, screenWidth || 800, screenHeight || 600); + this.screen = new Rectangle(0, 0, options.width, options.height); /** * The canvas element that everything is drawn to @@ -279,6 +284,8 @@ this.blendModes = null; + this.options = null; + this.preserveDrawingBuffer = false; this.clearBeforeRender = false; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index e84e49c..691caf0 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -17,10 +17,11 @@ */ export default class CanvasRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -34,9 +35,9 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('Canvas', screenWidth, screenHeight, options); + super('Canvas', options, arg2, arg3); this.type = RENDERER_TYPE.CANVAS; @@ -96,7 +97,19 @@ this.context = null; this.renderingToScreen = false; - this.resize(screenWidth, screenHeight); + this.resize(this.options.width, this.options.height); + + /** + * Fired after rendering finishes. + * + * @event PIXI.CanvasRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.CanvasRenderer#prerender + */ } /** @@ -299,4 +312,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.CanvasRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.CanvasExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.CanvasPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.CanvasRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index e1cf7cd..f6fe123 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -31,11 +31,12 @@ */ export default class WebGLRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** * - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -55,9 +56,9 @@ * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('WebGL', screenWidth, screenHeight, options); + super('WebGL', options, arg2, arg3); /** * The type of this renderer as a standardised const @@ -89,12 +90,6 @@ }; - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ this._backgroundColorRgba[3] = this.transparent ? 0 : 1; this.globalUniforms = new UniformGroup({ @@ -117,6 +112,13 @@ this.initPlugins(); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ if(options.context) { this.context.initFromContext(options.context); @@ -169,6 +171,25 @@ } return this; + + /** + * Fired after rendering finishes. + * + * @event PIXI.WebGLRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.WebGLRenderer#prerender + */ + + /** + * Fired when the WebGL context is set. + * + * @event PIXI.WebGLRenderer#context + * @param {WebGLRenderingContext} gl - WebGL context. + */ } /** @@ -295,4 +316,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.WebGLRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.WebGLRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(WebGLRenderer); diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index 4eb6128..fa3d883 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -47,6 +47,14 @@ * @member {boolean} */ this.enabled = true; + + /** + * If enabled, pixi will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; } /** diff --git a/src/core/renderers/webgl/systems/ContextSystem.js b/src/core/renderers/webgl/systems/ContextSystem.js index ff958c7..8df85ea 100644 --- a/src/core/renderers/webgl/systems/ContextSystem.js +++ b/src/core/renderers/webgl/systems/ContextSystem.js @@ -75,7 +75,8 @@ */ createContext(canvas, options) { - var gl = canvas.getContext('webgl', options) || + var gl = //canvas.getContext('webgl2', options) || + canvas.getContext('webgl', options) || canvas.getContext('experimental-webgl', options); if (!gl) diff --git a/src/core/renderers/webgl/systems/FilterSystem.js b/src/core/renderers/webgl/systems/FilterSystem.js index f47335d..37e28d2 100644 --- a/src/core/renderers/webgl/systems/FilterSystem.js +++ b/src/core/renderers/webgl/systems/FilterSystem.js @@ -111,7 +111,7 @@ // TODO we should fit the rect around the transform.. } - else + else if (filters[0].autoFit) { sourceFrame.fit(filterData.stack[0].destinationFrame); } diff --git a/src/core/renderers/webgl/systems/shader/shader/mapType.js b/src/core/renderers/webgl/systems/shader/shader/mapType.js index 581dace..917538a 100644 --- a/src/core/renderers/webgl/systems/shader/shader/mapType.js +++ b/src/core/renderers/webgl/systems/shader/shader/mapType.js @@ -15,7 +15,7 @@ } } - return GL_TABLE[type]; + return GL_TABLE[type]; }; var GL_TABLE = null; diff --git a/src/core/settings.js b/src/core/settings.js index 18c5877..38ef45c 100644 --- a/src/core/settings.js +++ b/src/core/settings.js @@ -2,6 +2,15 @@ import canUploadSameBuffer from './utils/canUploadSameBuffer'; /** + * User's customizable globals for overriding the default PIXI settings, such + * as a renderer's default resolution, framerate, float percision, etc. + * @example + * // Use the native window resolution as the default resolution + * // will support high-density displays when rendering + * PIXI.settings.RESOLUTION = window.devicePixelRatio. + * + * // Disable interpolation when scaling, will make texture be pixelated + * PIXI.settings.SCALE_MODE = PIXI.SCALE_MODES.NEAREST; * @namespace PIXI.settings */ export default { @@ -101,6 +110,9 @@ * @property {boolean} clearBeforeRender=true * @property {boolean} preserveDrawingBuffer=false * @property {boolean} roundPixels=false + * @property {number} width=800 + * @property {number} height=600 + * @property {boolean} legacy=false */ RENDER_OPTIONS: { view: null, @@ -112,6 +124,9 @@ clearBeforeRender: true, preserveDrawingBuffer: false, roundPixels: false, + width: 800, + height: 600, + legacy: false, }, /** diff --git a/src/core/shader/Program.js b/src/core/shader/Program.js index c0b538b..07b4398 100644 --- a/src/core/shader/Program.js +++ b/src/core/shader/Program.js @@ -1,6 +1,6 @@ import extractUniformsFromSrc from './extractUniformsFromSrc'; import generateUniformsSync from './generateUniformsSync'; -import glCore from 'pixi-gl-core'; +import shaderUtils from '../renderers/webgl/systems/shader/shader'; import { ProgramCache } from '../utils'; import getTestContext from '../utils/getTestContext'; @@ -64,14 +64,17 @@ } else { - vertexSrc = glCore.shader.setPrecision(vertexSrc, 'mediump'); - fragmentSrc = glCore.shader.setPrecision(fragmentSrc, 'mediump'); + vertexSrc = shaderUtils.setPrecision(vertexSrc, 'mediump'); + fragmentSrc = shaderUtils.setPrecision(fragmentSrc, 'mediump'); - const program = glCore.shader.compileProgram(gl, vertexSrc, fragmentSrc); + const program = shaderUtils.compileProgram(gl, vertexSrc, fragmentSrc); + console.log("<<<<<<>>><<"); this.attributeData = this.getAttributeData(program, gl); this.uniformData = this.getUniformData(program, gl); + console.log(this.uniformData); + //gl.deleteProgram(program); } } @@ -95,13 +98,13 @@ for (let i = 0; i < totalAttributes; i++) { const attribData = gl.getActiveAttrib(program, i); - const type = glCore.shader.mapType(gl, attribData.type); + const type = shaderUtils.mapType(gl, attribData.type); /*eslint-disable */ const data = { type: type, name: attribData.name, - size: glCore.shader.mapSize(type), + size: shaderUtils.mapSize(type), location: 0, }; /*eslint-enable */ @@ -137,13 +140,14 @@ // TODO expose this as a prop? // const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); - const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); + //const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); for (let i = 0; i < totalUniforms; i++) { const uniformData = gl.getActiveUniform(program, i); const name = uniformData.name.replace(/\[.*?\]/, ''); - const type = glCore.shader.mapType(gl, uniformData.type); + const type = shaderUtils.mapType(gl, uniformData.type); + console.log('mapping ' + uniformData.type + ' to ' + type); // if (!name.match(maskRegex)) { @@ -151,7 +155,7 @@ uniforms[name] = { type: type, size: uniformData.size, - value: glCore.shader.defaultValue(type, uniformData.size), + value: shaderUtils.defaultValue(type, uniformData.size), }; /*eslint-enable */ } diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index e387e99..b57d095 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -147,12 +147,12 @@ // so if _width is 0 then width was not set.. if (this._width) { - this.scale.x = sign(this.scale.x) * this._width / this.texture.orig.width; + this.scale.x = sign(this.scale.x) * this._width / this._texture.orig.width; } if (this._height) { - this.scale.y = sign(this.scale.y) * this._height / this.texture.orig.height; + this.scale.y = sign(this.scale.y) * this._height / this._texture.orig.height; } } diff --git a/src/core/sprites/canvas/CanvasTinter.js b/src/core/sprites/canvas/CanvasTinter.js index 27e5e0c..fd9de5d 100644 --- a/src/core/sprites/canvas/CanvasTinter.js +++ b/src/core/sprites/canvas/CanvasTinter.js @@ -18,7 +18,7 @@ */ getTintedTexture: (sprite, color) => { - const texture = sprite.texture; + const texture = sprite._texture; color = CanvasTinter.roundColor(color); diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 5a4edda..8b9b797 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -344,6 +344,7 @@ const texture = this._texture; const style = this._style; + const padding = style.trim ? 0 : style.padding; texture.baseTexture.valid = true; texture.baseTexture.resolution = this.resolution; @@ -353,11 +354,11 @@ texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - texture.trim.x = -style.padding; - texture.trim.y = -style.padding; + texture.trim.x = -padding; + texture.trim.y = -padding; - texture.orig.width = texture._frame.width - (style.padding * 2); - texture.orig.height = texture._frame.height - (style.padding * 2); + texture.orig.width = texture._frame.width - (padding * 2); + texture.orig.height = texture._frame.height - (padding * 2); // call sprite onTextureUpdate to update scale if _width or _height were set this._onTextureUpdate(); diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index 853da3d..8a9c34b 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -114,6 +114,38 @@ this.cacheId = null; this.validate(); + + /** + * Fired when a not-immediately-available source finishes loading. + * + * @protected + * @event PIXI.BaseTexture#loaded + * @param {PIXI.BaseTexture} baseTexture - Resource loaded. + */ + + /** + * Fired when a not-immediately-available source fails to load. + * + * @protected + * @event PIXI.BaseTexture#error + * @param {PIXI.BaseTexture} baseTexture - Resource errored. + */ + + /** + * Fired when BaseTexture is updated. + * + * @protected + * @event PIXI.BaseTexture#update + * @param {PIXI.BaseTexture} baseTexture - Instance of texture being updated. + */ + + /** + * Fired when BaseTexture is destroyed. + * + * @protected + * @event PIXI.BaseTexture#dispose + * @param {PIXI.BaseTexture} baseTexture - Instance of texture being destroyed. + */ } setResource(resource) diff --git a/src/core/textures/Spritesheet.js b/src/core/textures/Spritesheet.js index d469c62..9b24f27 100644 --- a/src/core/textures/Spritesheet.js +++ b/src/core/textures/Spritesheet.js @@ -208,6 +208,7 @@ // lets also add the frame to pixi's global cache for fromFrame and fromImage functions TextureCache[i] = this.textures[i]; + this.textures[i].textureCacheId = i; } frameIndex++; diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 0a2c379..2213087 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -147,9 +147,9 @@ /** * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. * - * @event update - * @memberof PIXI.Texture# + * @event PIXI.Texture#update * @protected + * @param {PIXI.Texture} texture - Instance of texture being updated. */ this._updateID = 0; @@ -159,6 +159,15 @@ * @type {Object} */ this.transform = null; + + /** + * The id under which this Texture has been added to the texture cache. This is + * automatically set in certain cases, but may not always be accurate, particularly if + * the texture is in the cache under multiple ids. + * + * @member {string} + */ + this.textureCacheId = null; } /** @@ -225,11 +234,17 @@ this._uvs = null; this.trim = null; this.orig = null; + this.textureCacheId = null; this.valid = false; - this.off('dispose', this.dispose, this); - this.off('update', this.update, this); + for (const prop in TextureCache) + { + if (TextureCache[prop] === this) + { + delete TextureCache[prop]; + } + } } /** @@ -315,14 +330,15 @@ */ static fromLoader(source, imageUrl, name) { - // console.log('added from loader...') + // console.log('added from loader...') const resource = new ImageResource(source);//.from(imageUrl, crossorigin);// document.createElement('img'); - // console.log('base resource ' + resource.width); + // console.log('base resource ' + resource.width); const baseTexture = new BaseTexture(resource, settings.SCALE_MODE, getResolutionOfUrl(imageUrl)); + /// console.log('base width ' + baseTexture.width); const texture = new Texture(baseTexture); @@ -335,6 +351,7 @@ // lets also add the frame to pixi's global cache for fromFrame and fromImage fucntions BaseTextureCache[name] = baseTexture; TextureCache[name] = texture; + texture.textureCacheId = name; // also add references by url if they are different. if (name !== imageUrl) @@ -355,6 +372,10 @@ */ static addTextureToCache(texture, id) { + if (!texture.textureCacheId) + { + texture.textureCacheId = id; + } TextureCache[id] = texture; } @@ -495,6 +516,7 @@ */ Texture.EMPTY = new Texture(new BaseTexture()); removeAllHandlers(Texture.EMPTY); +removeAllHandlers(Texture.EMPTY.baseTexture); /** * A white texture of 10x10 size, used for graphics and other things @@ -505,3 +527,4 @@ */ Texture.WHITE = createWhiteTexture(); removeAllHandlers(Texture.WHITE); +removeAllHandlers(Texture.WHITE.baseTexture); diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index 59eb9fd..a9b9f51 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -1,6 +1,7 @@ import BaseTexture from './BaseTexture'; import { uid, BaseTextureCache } from '../utils'; -import * as ticker from '../ticker'; +import { shared } from '../ticker'; +import { UPDATE_PRIORITY } from '../const'; /** * A texture of a [playing] Video. @@ -125,7 +126,7 @@ if (!this._isAutoUpdating && this.autoUpdate) { - ticker.shared.add(this.update, this); + shared.add(this.update, this, UPDATE_PRIORITY.HIGH); this._isAutoUpdating = true; } } @@ -139,7 +140,7 @@ { if (this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); this._isAutoUpdating = false; } } @@ -187,7 +188,7 @@ { if (this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); } if (this.source && this.source._pixiId) @@ -281,12 +282,12 @@ if (!this._autoUpdate && this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); this._isAutoUpdating = false; } else if (this._autoUpdate && !this._isAutoUpdating) { - ticker.shared.add(this.update, this); + shared.add(this.update, this, UPDATE_PRIORITY.HIGH); this._isAutoUpdating = true; } } diff --git a/src/core/ticker/Ticker.js b/src/core/ticker/Ticker.js index 17a1517..40df138 100644 --- a/src/core/ticker/Ticker.js +++ b/src/core/ticker/Ticker.js @@ -1,12 +1,10 @@ import settings from '../settings'; -import EventEmitter from 'eventemitter3'; - -// Internal event used by composed emitter -const TICK = 'tick'; +import { UPDATE_PRIORITY } from '../const'; +import TickerListener from './TickerListener'; /** * A Ticker class that runs an update loop that other objects listen to. - * This class is composed around an EventEmitter object to add listeners + * This class is composed around listeners * meant for execution on the next requested animation frame. * Animation frames are requested only when necessary, * e.g. When the ticker is started and the emitter has listeners. @@ -22,10 +20,11 @@ constructor() { /** - * Internal emitter used to fire 'tick' event + * The first listener. All new listeners added are chained on this. * @private + * @type {TickerListener} */ - this._emitter = new EventEmitter(); + this._head = new TickerListener(null, null, Infinity); /** * Internal current frame request ID @@ -131,7 +130,7 @@ // Invoke listeners now this.update(time); // Listener side effects may have modified ticker state. - if (this.started && this._requestId === null && this._emitter.listeners(TICK, true)) + if (this.started && this._requestId === null && this._head.next) { this._requestId = requestAnimationFrame(this._tick); } @@ -148,7 +147,7 @@ */ _requestIfNeeded() { - if (this._requestId === null && this._emitter.listeners(TICK, true)) + if (this._requestId === null && this._head.next) { // ensure callbacks get correct delta this.lastTime = performance.now(); @@ -193,35 +192,72 @@ } /** - * Calls {@link module:eventemitter3.EventEmitter#on} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. + * Register a handler for tick events. Calls continuously unless + * it is removed or the ticker is stopped. * * @param {Function} fn - The listener function to be added for updates * @param {Function} [context] - The listener context + * @param {number} [priority=PIXI.UPDATE_PRIORITY.NORMAL] - The priority for emitting * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - add(fn, context) + add(fn, context, priority = UPDATE_PRIORITY.NORMAL) { - this._emitter.on(TICK, fn, context); - - this._startIfPossible(); - - return this; + return this._addListener(new TickerListener(fn, context, priority)); } /** - * Calls {@link module:eventemitter3.EventEmitter#once} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. + * Add a handler for the tick event which is only execute once. * * @param {Function} fn - The listener function to be added for one update * @param {Function} [context] - The listener context + * @param {number} [priority=PIXI.UPDATE_PRIORITY.NORMAL] - The priority for emitting * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - addOnce(fn, context) + addOnce(fn, context, priority = UPDATE_PRIORITY.NORMAL) { - this._emitter.once(TICK, fn, context); + return this._addListener(new TickerListener(fn, context, priority, true)); + } + + /** + * Internally adds the event handler so that it can be sorted by priority. + * Priority allows certain handler (user, AnimatedSprite, Interaction) to be run + * before the rendering. + * + * @private + * @param {TickerListener} listener - Current listener being added. + * @returns {PIXI.ticker.Ticker} This instance of a ticker + */ + _addListener(listener) + { + // For attaching to head + let current = this._head.next; + let previous = this._head; + + // Add the first item + if (!current) + { + listener.connect(previous); + } + else + { + // Go from highest to lowest priority + while (current) + { + if (listener.priority >= current.priority) + { + listener.connect(previous); + break; + } + previous = current; + current = current.next; + } + + // Not yet connected + if (!listener.previous) + { + listener.connect(previous); + } + } this._startIfPossible(); @@ -229,19 +265,33 @@ } /** - * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. - * It checks if the emitter has listeners for 'tick' event. - * If it does, then it cancels the animation frame. + * Removes any handlers matching the function and context parameters. + * If no handlers are left after removing, then it cancels the animation frame. * - * @param {Function} [fn] - The listener function to be removed + * @param {Function} fn - The listener function to be removed * @param {Function} [context] - The listener context to be removed * @returns {PIXI.ticker.Ticker} This instance of a ticker */ remove(fn, context) { - this._emitter.off(TICK, fn, context); + let listener = this._head.next; - if (!this._emitter.listeners(TICK, true)) + while (listener) + { + // We found a match, lets remove it + // no break to delete all possible matches + // incase a listener was added 2+ times + if (listener.match(fn, context)) + { + listener = listener.destroy(); + } + else + { + listener = listener.next; + } + } + + if (!this._head.next) { this._cancelIfNeeded(); } @@ -276,6 +326,25 @@ } /** + * Destroy the ticker and don't use after this. Calling + * this method removes all references to internal events. + */ + destroy() + { + this.stop(); + + let listener = this._head.next; + + while (listener) + { + listener = listener.destroy(true); + } + + this._head.destroy(); + this._head = null; + } + + /** * Triggers an update. An update entails setting the * current {@link PIXI.ticker.Ticker#elapsedMS}, * the current {@link PIXI.ticker.Ticker#deltaTime}, @@ -320,8 +389,22 @@ this.deltaTime = elapsedMS * settings.TARGET_FPMS * this.speed; + // Cache a local reference, in-case ticker is destroyed + // during the emit, we can still check for head.next + const head = this._head; + // Invoke listeners added to internal emitter - this._emitter.emit(TICK, this.deltaTime); + let listener = head.next; + + while (listener) + { + listener = listener.emit(this.deltaTime); + } + + if (!head.next) + { + this._cancelIfNeeded(); + } } else { diff --git a/src/core/ticker/TickerListener.js b/src/core/ticker/TickerListener.js new file mode 100644 index 0000000..2bedb34 --- /dev/null +++ b/src/core/ticker/TickerListener.js @@ -0,0 +1,158 @@ +/** + * Internal class for handling the priority sorting of ticker handlers. + * + * @private + * @class + * @memberof PIXI.ticker + */ +export default class TickerListener +{ + /** + * Constructor + * + * @param {Function} fn - The listener function to be added for one update + * @param {Function} [context=null] - The listener context + * @param {number} [priority=0] - The priority for emitting + * @param {boolean} [once=false] - If the handler should fire once + */ + constructor(fn, context = null, priority = 0, once = false) + { + /** + * The handler function to execute. + * @member {Function} + */ + this.fn = fn; + + /** + * The calling to execute. + * @member {Function} + */ + this.context = context; + + /** + * The current priority. + * @member {number} + */ + this.priority = priority; + + /** + * If this should only execute once. + * @member {boolean} + */ + this.once = once; + + /** + * The next item in chain. + * @member {TickerListener} + */ + this.next = null; + + /** + * The previous item in chain. + * @member {TickerListener} + */ + this.previous = null; + + /** + * `true` if this listener has been destroyed already. + * @member {boolean} + * @private + */ + this._destroyed = false; + } + + /** + * Simple compare function to figure out if a function and context match. + * + * @param {Function} fn - The listener function to be added for one update + * @param {Function} context - The listener context + * @return {boolean} `true` if the listener match the arguments + */ + match(fn, context) + { + context = context || null; + + return this.fn === fn && this.context === context; + } + + /** + * Emit by calling the current function. + * @param {number} deltaTime - time since the last emit. + * @return {TickerListener} Next ticker + */ + emit(deltaTime) + { + if (this.context) + { + this.fn.call(this.context, deltaTime); + } + else + { + this.fn(deltaTime); + } + + if (this.once) + { + this.destroy(); + } + + const redirect = this.next; + + // Soft-destroying should remove + // the next reference + if (this._destroyed) + { + this.next = null; + } + + return redirect; + } + + /** + * Connect to the list. + * @param {TickerListener} previous - Input node, previous listener + */ + connect(previous) + { + this.previous = previous; + if (previous.next) + { + previous.next.previous = this; + } + this.next = previous.next; + previous.next = this; + } + + /** + * Destroy and don't use after this. + * @param {boolean} [hard = false] `true` to remove the `next` reference, this + * is considered a hard destroy. Soft destroy maintains the next reference. + * @return {TickerListener} The listener to redirect while emitting or removing. + */ + destroy(hard = false) + { + this._destroyed = true; + this.fn = null; + this.context = null; + + // Disconnect, hook up next and previous + if (this.previous) + { + this.previous.next = this.next; + } + + if (this.next) + { + this.next.previous = this.previous; + } + + // Redirect to the next item + const redirect = this.previous; + + // Remove references + this.next = hard ? null : redirect; + this.previous = null; + + return redirect; + } +} diff --git a/src/core/ticker/index.js b/src/core/ticker/index.js index 4b3017c..c3020d4 100644 --- a/src/core/ticker/index.js +++ b/src/core/ticker/index.js @@ -45,8 +45,25 @@ const shared = new Ticker(); shared.autoStart = true; +shared.destroy = () => +{ + // protect destroying shared ticker + // this is used by other internal systems + // like AnimatedSprite and InteractionManager +}; /** + * This namespace contains an API for interacting with PIXI's internal global update loop. + * + * This ticker is used for rendering, {@link PIXI.extras.AnimatedSprite AnimatedSprite}, + * {@link PIXI.interaction.InteractionManager InteractionManager} and many other time-based PIXI systems. + * @example + * const ticker = new PIXI.ticker.Ticker(); + * ticker.stop(); + * ticker.add((deltaTime) => { + * // do something every frame + * }); + * ticker.start(); * @namespace PIXI.ticker */ export { shared, Ticker }; diff --git a/src/core/utils/index.js b/src/core/utils/index.js index be44750..7441f7b 100644 --- a/src/core/utils/index.js +++ b/src/core/utils/index.js @@ -9,6 +9,21 @@ let saidHello = false; /** + * Generalized convenience utilities for PIXI. + * @example + * // Extend PIXI's internal Event Emitter. + * class MyEmitter extends PIXI.utils.EventEmitter { + * constructor() { + * super(); + * console.log("Emitter created!"); + * } + * } + * + * // Get info on current device + * console.log(PIXI.utils.isMobile); + * + * // Convert hex color to string + * console.log(PIXI.utils.hex2string(0xff00ff)); // returns: "#ff00ff" * @namespace PIXI.utils */ export { diff --git a/src/deprecation.js b/src/deprecation.js index b9c73b3..33daeec 100644 --- a/src/deprecation.js +++ b/src/deprecation.js @@ -918,6 +918,35 @@ }); /** + * @method + * @name PIXI.prepare.BasePrepare#register + * @see PIXI.prepare.BasePrepare#registerFindHook + * @deprecated since version 4.4.2 + * @param {Function} [addHook] - Function call that takes two parameters: `item:*, queue:Array` + * function must return `true` if it was able to add item to the queue. + * @param {Function} [uploadHook] - Function call that takes two parameters: `prepare:CanvasPrepare, item:*` and + * function must return `true` if it was able to handle upload of item. + * @return {PIXI.BasePrepare} Instance of plugin for chaining. + */ +prepare.BasePrepare.prototype.register = function register(addHook, uploadHook) +{ + warn('renderer.plugins.prepare.register is now deprecated, ' + + 'please use renderer.plugins.prepare.registerFindHook & renderer.plugins.prepare.registerUploadHook'); + + if (addHook) + { + this.registerFindHook(addHook); + } + + if (uploadHook) + { + this.registerUploadHook(uploadHook); + } + + return this; +}; + +/** * The number of graphics or textures to upload to the GPU. * * @name PIXI.prepare.canvas.UPLOADS_PER_FRAME @@ -969,67 +998,73 @@ }, }); -Object.defineProperties(loaders.Resource.prototype, { - isJson: { - get() - { - warn('The isJson property is deprecated, please use `resource.type === Resource.TYPE.JSON`.'); +if (loaders.Loader) +{ + const Resource = loaders.Resource; + const Loader = loaders.Loader; - return this.type === loaders.Loader.Resource.TYPE.JSON; + Object.defineProperties(Resource.prototype, { + isJson: { + get() + { + warn('The isJson property is deprecated, please use `resource.type === Resource.TYPE.JSON`.'); + + return this.type === Resource.TYPE.JSON; + }, }, - }, - isXml: { - get() - { - warn('The isXml property is deprecated, please use `resource.type === Resource.TYPE.XML`.'); + isXml: { + get() + { + warn('The isXml property is deprecated, please use `resource.type === Resource.TYPE.XML`.'); - return this.type === loaders.Loader.Resource.TYPE.XML; + return this.type === Resource.TYPE.XML; + }, }, - }, - isImage: { - get() - { - warn('The isImage property is deprecated, please use `resource.type === Resource.TYPE.IMAGE`.'); + isImage: { + get() + { + warn('The isImage property is deprecated, please use `resource.type === Resource.TYPE.IMAGE`.'); - return this.type === loaders.Loader.Resource.TYPE.IMAGE; + return this.type === Resource.TYPE.IMAGE; + }, }, - }, - isAudio: { - get() - { - warn('The isAudio property is deprecated, please use `resource.type === Resource.TYPE.AUDIO`.'); + isAudio: { + get() + { + warn('The isAudio property is deprecated, please use `resource.type === Resource.TYPE.AUDIO`.'); - return this.type === loaders.Loader.Resource.TYPE.AUDIO; + return this.type === Resource.TYPE.AUDIO; + }, }, - }, - isVideo: { - get() - { - warn('The isVideo property is deprecated, please use `resource.type === Resource.TYPE.VIDEO`.'); + isVideo: { + get() + { + warn('The isVideo property is deprecated, please use `resource.type === Resource.TYPE.VIDEO`.'); - return this.type === loaders.Loader.Resource.TYPE.VIDEO; + return this.type === Resource.TYPE.VIDEO; + }, }, - }, -}); + }); -Object.defineProperties(loaders.Loader.prototype, { - before: { - get() - { - warn('The before() method is deprecated, please use pre().'); + Object.defineProperties(Loader.prototype, { + before: { + get() + { + warn('The before() method is deprecated, please use pre().'); - return this.pre; + return this.pre; + }, }, - }, - after: { - get() - { - warn('The after() method is deprecated, please use use().'); + after: { + get() + { + warn('The after() method is deprecated, please use use().'); - return this.use; + return this.use; + }, }, - }, -}); + }); +} /** * @name PIXI.interaction.interactiveTarget#defaultCursor diff --git a/src/extract/canvas/CanvasExtract.js b/src/extract/canvas/CanvasExtract.js index 941ca05..3ac5aee 100644 --- a/src/extract/canvas/CanvasExtract.js +++ b/src/extract/canvas/CanvasExtract.js @@ -8,7 +8,7 @@ * An instance of this class is automatically created by default, and can be found at renderer.plugins.extract * * @class - * @memberof PIXI + * @memberof PIXI.extract */ export default class CanvasExtract { @@ -21,9 +21,9 @@ /** * Collection of methods for extracting data (image, pixels, etc.) from a display object or render texture * - * @member {PIXI.CanvasExtract} extract + * @member {PIXI.extract.CanvasExtract} extract * @memberof PIXI.CanvasRenderer# - * @see PIXI.CanvasExtract + * @see PIXI.extract.CanvasExtract */ renderer.extract = this; } diff --git a/src/extract/index.js b/src/extract/index.js index 0f2170d..e9478c1 100644 --- a/src/extract/index.js +++ b/src/extract/index.js @@ -1,2 +1,22 @@ +/** + * This namespace provides renderer-specific plugins for exporting content from a renderer. + * For instance, these plugins can be used for saving an Image, Canvas element or for exporting the raw image data (pixels). + * + * Do not instantiate these plugins directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. + * @example + * // Create a new app (will auto-add extract plugin to renderer) + * const app = new PIXI.Application(); + * + * // Draw a red circle + * const graphics = new PIXI.Graphics() + * .beginFill(0xFF0000) + * .drawCircle(0, 0, 50); + * + * // Render the graphics as an HTMLImageElement + * const image = app.renderer.plugins.extract.image(graphics); + * document.body.appendChild(image); + * @namespace PIXI.extract + */ export { default as webgl } from './webgl/WebGLExtract'; export { default as canvas } from './canvas/CanvasExtract'; diff --git a/src/extract/webgl/WebGLExtract.js b/src/extract/webgl/WebGLExtract.js index 60228bb..1c3dd30 100644 --- a/src/extract/webgl/WebGLExtract.js +++ b/src/extract/webgl/WebGLExtract.js @@ -9,7 +9,7 @@ * An instance of this class is automatically created by default, and can be found at renderer.plugins.extract * * @class - * @memberof PIXI + * @memberof PIXI.extract */ export default class WebGLExtract { @@ -22,9 +22,9 @@ /** * Collection of methods for extracting data (image, pixels, etc.) from a display object or render texture * - * @member {PIXI.WebGLExtract} extract + * @member {PIXI.extract.WebGLExtract} extract * @memberof PIXI.WebGLRenderer# - * @see PIXI.WebGLExtract + * @see PIXI.extract.WebGLExtract */ renderer.extract = this; } diff --git a/src/extras/AnimatedSprite.js b/src/extras/AnimatedSprite.js index 13b804f..5297778 100644 --- a/src/extras/AnimatedSprite.js +++ b/src/extras/AnimatedSprite.js @@ -137,7 +137,7 @@ this.playing = true; if (this._autoUpdate) { - core.ticker.shared.add(this.update, this); + core.ticker.shared.add(this.update, this, core.UPDATE_PRIORITY.HIGH); } } diff --git a/src/extras/TextureTransform.js b/src/extras/TextureTransform.js index 78e6e40..16536d2 100644 --- a/src/extras/TextureTransform.js +++ b/src/extras/TextureTransform.js @@ -65,8 +65,36 @@ } /** + * Multiplies uvs array to transform + * @param {Float32Array} uvs mesh uvs + * @param {Float32Array} [out=uvs] output + * @returns {Float32Array} output + */ + multiplyUvs(uvs, out) + { + if (out === undefined) + { + out = uvs; + } + + const mat = this.mapCoord; + + for (let i = 0; i < uvs.length; i += 2) + { + const x = uvs[i]; + const y = uvs[i + 1]; + + out[i] = (x * mat.a) + (y * mat.c) + mat.tx; + out[i + 1] = (x * mat.b) + (y * mat.d) + mat.ty; + } + + return out; + } + + /** * updates matrices if texture was changed * @param {boolean} forceUpdate if true, matrices will be updated any case + * @returns {boolean} whether or not it was updated */ update(forceUpdate) { @@ -74,13 +102,13 @@ if (!tex || !tex.valid) { - return; + return false; } if (!forceUpdate && this._lastTextureID === tex._updateID) { - return; + return false; } this._lastTextureID = tex._updateID; @@ -110,5 +138,7 @@ frame[3] = (tex._frame.y + tex._frame.height - margin + offset) / texBase.height; this.uClampOffset[0] = offset / texBase.realWidth; this.uClampOffset[1] = offset / texBase.realHeight; + + return true; } } diff --git a/src/extras/TilingSprite.js b/src/extras/TilingSprite.js index 1f6014f..a3f027f 100644 --- a/src/extras/TilingSprite.js +++ b/src/extras/TilingSprite.js @@ -180,9 +180,9 @@ const transform = this.worldTransform; const resolution = renderer.resolution; const baseTexture = texture.baseTexture; - const baseTextureResolution = texture.baseTexture.resolution; - const modX = (this.tilePosition.x / this.tileScale.x) % texture._frame.width; - const modY = (this.tilePosition.y / this.tileScale.y) % texture._frame.height; + const baseTextureResolution = baseTexture.resolution; + const modX = ((this.tilePosition.x / this.tileScale.x) % texture._frame.width) * baseTextureResolution; + const modY = ((this.tilePosition.y / this.tileScale.y) % texture._frame.height) * baseTextureResolution; // create a nice shiny pattern! // TODO this needs to be refreshed if texture changes.. diff --git a/src/extras/cacheAsBitmap.js b/src/extras/cacheAsBitmap.js index 5dd5640..aea06a8 100644 --- a/src/extras/cacheAsBitmap.js +++ b/src/extras/cacheAsBitmap.js @@ -227,7 +227,16 @@ this.transform._parentID = -1; // restore the transform of the cached sprite to avoid the nasty flicker.. - this.updateTransform(); + if (!this.parent) + { + this.parent = renderer._tempDisplayObjectParent; + this.updateTransform(); + this.parent = null; + } + else + { + this.updateTransform(); + } // map the hit test.. this.containsPoint = cachedSprite.containsPoint.bind(cachedSprite); @@ -314,7 +323,17 @@ cachedSprite._bounds = this._bounds; cachedSprite.alpha = cacheAlpha; - this.updateTransform(); + if (!this.parent) + { + this.parent = renderer._tempDisplayObjectParent; + this.updateTransform(); + this.parent = null; + } + else + { + this.updateTransform(); + } + this.updateTransform = this.displayObjectUpdateTransform; this._cacheData.sprite = cachedSprite; @@ -358,9 +377,12 @@ * Destroys the cached object. * * @private + * @param {object|boolean} [options] - Options parameter. A boolean will act as if all options + * have been set to that value. + * Used when destroying containers, see the Container.destroy method. */ -DisplayObject.prototype._cacheAsBitmapDestroy = function _cacheAsBitmapDestroy() +DisplayObject.prototype._cacheAsBitmapDestroy = function _cacheAsBitmapDestroy(options) { this.cacheAsBitmap = false; - this.destroy(); + this.destroy(options); }; diff --git a/src/extras/index.js b/src/extras/index.js index bbd8bbf..a34d7ec 100644 --- a/src/extras/index.js +++ b/src/extras/index.js @@ -1,7 +1,7 @@ /** + * Additional PIXI DisplayObjects for animation, tiling and bitmap text. * @namespace PIXI.extras */ -export { default as TextureTransform } from './TextureTransform'; export { default as AnimatedSprite } from './AnimatedSprite'; export { default as TilingSprite } from './TilingSprite'; export { default as TilingSpriteRenderer } from './webgl/TilingSpriteRenderer'; diff --git a/src/extras/webgl/TilingSpriteRenderer.js b/src/extras/webgl/TilingSpriteRenderer.js index aeec172..eee3b5c 100644 --- a/src/extras/webgl/TilingSpriteRenderer.js +++ b/src/extras/webgl/TilingSpriteRenderer.js @@ -11,7 +11,7 @@ * WebGL renderer plugin for tiling sprites * * @class - * @memberof PIXI + * @memberof PIXI.extras * @extends PIXI.ObjectRenderer */ export default class TilingSpriteRenderer extends core.ObjectRenderer @@ -118,7 +118,7 @@ tempMat.invert(); if (isSimple) { - tempMat.append(uv.mapCoord); + tempMat.prepend(uv.mapCoord); } else { diff --git a/src/filters/displacement/DisplacementFilter.js b/src/filters/displacement/DisplacementFilter.js index d5355c3..b5d030c 100644 --- a/src/filters/displacement/DisplacementFilter.js +++ b/src/filters/displacement/DisplacementFilter.js @@ -35,7 +35,7 @@ this.maskSprite = sprite; this.maskMatrix = maskMatrix; - this.uniforms.mapSampler = sprite.texture; + this.uniforms.mapSampler = sprite._texture; this.uniforms.filterMatrix = maskMatrix; this.uniforms.scale = { x: 1, y: 1 }; diff --git a/src/filters/index.js b/src/filters/index.js index f3e8862..7ceb50a 100644 --- a/src/filters/index.js +++ b/src/filters/index.js @@ -1,4 +1,21 @@ /** + * This namespace contains WebGL-only display filters that can be applied + * to DisplayObjects using the {@link PIXI.DisplayObject#filters filters} property. + * @example + * // Create a new application + * const app = new PIXI.Application(); + * + * // Draw a green rectangle + * const rect = new PIXI.Graphics() + * .beginFill(0x00ff00) + * .drawRect(40, 40, 200, 200); + * + * // Add a blur filter + * rect.filters = [new PIXI.filters.BlurFilter()]; + * + * // Display rectangle + * app.stage.addChild(rect); + * document.body.appendChild(app.view); * @namespace PIXI.filters */ export { default as FXAAFilter } from './fxaa/FXAAFilter'; diff --git a/src/index.js b/src/index.js index c553427..77da490 100644 --- a/src/index.js +++ b/src/index.js @@ -20,6 +20,14 @@ import { utils } from './core'; utils.mixins.performMixins(); +/** + * Alias for {@link PIXI.loaders.shared}. + * @name loader + * @memberof PIXI + * @type {PIXI.loader.Loader} + */ +const loader = loaders.shared || null; + export { accessibility, extract, @@ -30,18 +38,8 @@ mesh, particles, prepare, + loader, }; -/** - * A premade instance of the loader that can be used to load resources. - * - * @name loader - * @memberof PIXI - * @property {PIXI.loaders.Loader} - */ -const loader = loaders && loaders.Loader ? new loaders.Loader() : null; // check is there in case user excludes loader lib - -export { loader }; - // Always export pixi globally. global.PIXI = exports; // eslint-disable-line diff --git a/src/interaction/InteractionData.js b/src/interaction/InteractionData.js index 02d53a3..449f8cc 100644 --- a/src/interaction/InteractionData.js +++ b/src/interaction/InteractionData.js @@ -30,7 +30,10 @@ /** * When passed to an event handler, this will be the original DOM Event that was captured * - * @member {Event} + * @see https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent + * @see https://developer.mozilla.org/en-US/docs/Web/API/TouchEvent + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent + * @member {MouseEvent|TouchEvent|PointerEvent} */ this.originalEvent = null; } diff --git a/src/interaction/InteractionManager.js b/src/interaction/InteractionManager.js index 8baf13a..2d75c7c 100644 --- a/src/interaction/InteractionManager.js +++ b/src/interaction/InteractionManager.js @@ -13,6 +13,14 @@ const MOUSE_POINTER_ID = 'MOUSE'; +// helpers for hitTest() - only used inside hitTest() +const hitTestEvent = { + target: null, + data: { + global: null, + }, +}; + /** * The interaction manager deals with mouse, touch and pointer events. Any DisplayObject can be interactive * if its interactive parameter is set to true @@ -245,67 +253,60 @@ this.setTargetElement(this.renderer.view, this.renderer.resolution); /** - * Fired when a pointer device button (usually a mouse button) is pressed on the display + * Fired when a pointer device button (usually a mouse left-button) is pressed on the display * object. * - * @event mousedown - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mousedown + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device secondary button (usually a mouse right-button) is pressed * on the display object. * - * @event rightdown - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightdown + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** - * Fired when a pointer device button (usually a mouse button) is released over the display + * Fired when a pointer device button (usually a mouse left-button) is released over the display * object. * - * @event mouseup - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseup + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device secondary button (usually a mouse right-button) is released * over the display object. * - * @event rightup - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightup + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** - * Fired when a pointer device button (usually a mouse button) is pressed and released on + * Fired when a pointer device button (usually a mouse left-button) is pressed and released on * the display object. * - * @event click - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#click + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device secondary button (usually a mouse right-button) is pressed * and released on the display object. * - * @event rightclick - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightclick + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** - * Fired when a pointer device button (usually a mouse button) is released outside the + * Fired when a pointer device button (usually a mouse left-button) is released outside the * display object that initially registered a * [mousedown]{@link PIXI.interaction.InteractionManager#event:mousedown}. * - * @event mouseupoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** @@ -313,146 +314,362 @@ * outside the display object that initially registered a * [rightdown]{@link PIXI.interaction.InteractionManager#event:rightdown}. * - * @event rightupoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device (usually a mouse) is moved while over the display object * - * @event mousemove - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mousemove + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device (usually a mouse) is moved onto the display object * - * @event mouseover - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseover + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device (usually a mouse) is moved off the display object * - * @event mouseout - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseout + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is pressed on the display object. * - * @event pointerdown - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerdown + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is released over the display object. * - * @event pointerup - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerup + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when the operating system cancels a pointer event * - * @event pointercancel - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointercancel + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is pressed and released on the display object. * - * @event pointertap - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointertap + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is released outside the display object that initially * registered a [pointerdown]{@link PIXI.interaction.InteractionManager#event:pointerdown}. * - * @event pointerupoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device is moved while over the display object * - * @event pointermove - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointermove + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device is moved onto the display object * - * @event pointerover - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerover + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device is moved off the display object * - * @event pointerout - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerout + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is placed on the display object. * - * @event touchstart - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchstart + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is removed from the display object. * - * @event touchend - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchend + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when the operating system cancels a touch * - * @event touchcancel - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchcancel + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is placed and removed from the display object. * - * @event tap - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#tap + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is removed outside of the display object that initially * registered a [touchstart]{@link PIXI.interaction.InteractionManager#event:touchstart}. * - * @event touchendoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchendoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is moved along the display object. * - * @event touchmove - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchmove + * @param {PIXI.interaction.InteractionData} event - Interaction data */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is pressed on the display. + * object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mousedown + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is pressed + * on the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightdown + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is released over the display + * object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseup + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is released + * over the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightup + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is pressed and released on + * the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#click + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is pressed + * and released on the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightclick + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is released outside the + * display object that initially registered a + * [mousedown]{@link PIXI.DisplayObject#event:mousedown}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is released + * outside the display object that initially registered a + * [rightdown]{@link PIXI.DisplayObject#event:rightdown}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device (usually a mouse) is moved while over the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mousemove + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device (usually a mouse) is moved onto the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseover + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device (usually a mouse) is moved off the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseout + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is pressed on the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerdown + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is released over the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerup + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when the operating system cancels a pointer event. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointercancel + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is pressed and released on the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointertap + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is released outside the display object that initially + * registered a [pointerdown]{@link PIXI.DisplayObject#event:pointerdown}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device is moved while over the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointermove + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device is moved onto the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerover + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device is moved off the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerout + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is placed on the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchstart + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is removed from the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchend + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when the operating system cancels a touch. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchcancel + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is placed and removed from the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#tap + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is removed outside of the display object that initially + * registered a [touchstart]{@link PIXI.DisplayObject#event:touchstart}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchendoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is moved along the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchmove + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + } + + /** + * Hit tests a point against the display tree, returning the first interactive object that is hit. + * + * @param {PIXI.Point} globalPoint - A point to hit test with, in global space. + * @param {PIXI.Container} [root] - The root display object to start from. If omitted, defaults + * to the last rendered root of the associated renderer. + * @return {PIXI.DisplayObject} The hit display object, if any. + */ + hitTest(globalPoint, root) + { + // clear the target for our hit test + hitTestEvent.target = null; + // assign the global point + hitTestEvent.data.global = globalPoint; + // ensure safety of the root + if (!root) + { + root = this.renderer._lastObjectRendered; + } + // run the hit test + this.processInteractive(hitTestEvent, root, null, true); + // return our found object - it'll be null if we didn't hit anything + + return hitTestEvent.target; } /** @@ -487,7 +704,7 @@ return; } - core.ticker.shared.add(this.update, this); + core.ticker.shared.add(this.update, this, core.UPDATE_PRIORITY.INTERACTION); if (window.navigator.msPointerEnabled) { @@ -804,8 +1021,6 @@ } } - let keepHitTestingAfterChildren = hitTest; - // ** FREE TIP **! If an object is not interactive or has no buttons in it // (such as a game scene!) set interactiveChildren to false for that displayObject. // This will allow pixi to completely ignore and bypass checking the displayObjects children. @@ -818,7 +1033,9 @@ const child = children[i]; // time to get recursive.. if this function will return if something is hit.. - if (this.processInteractive(interactionEvent, child, func, hitTest, interactiveParent)) + const childHit = this.processInteractive(interactionEvent, child, func, hitTest, interactiveParent); + + if (childHit) { // its a good idea to check if a child has lost its parent. // this means it has been removed whilst looping so its best @@ -827,8 +1044,6 @@ continue; } - hit = true; - // we no longer need to hit test any more objects in this container as we we // now know the parent has been hit interactiveParent = false; @@ -838,36 +1053,41 @@ // This means we no longer need to hit test anything else. We still need to run // through all objects, but we don't need to perform any hit tests. - keepHitTestingAfterChildren = false; - - if (child.interactive) + if (childHit) { - hitTest = false; + if (interactionEvent.target) + { + hitTest = false; + } + hit = true; } - - // we can break now as we have hit an object. } } } - hitTest = keepHitTestingAfterChildren; - // no point running this if the item is not interactive or does not have an interactive parent. if (interactive) { // if we are hit testing (as in we have no hit any objects yet) // We also don't need to worry about hit testing if once of the displayObjects children - // has already been hit! - if (hitTest && !hit) + // has already been hit - but only if it was interactive, otherwise we need to keep + // looking for an interactive child, just in case we hit one + if (hitTest && !interactionEvent.target) { if (displayObject.hitArea) { displayObject.worldTransform.applyInverse(point, this._tempPoint); - hit = displayObject.hitArea.contains(this._tempPoint.x, this._tempPoint.y); + if (displayObject.hitArea.contains(this._tempPoint.x, this._tempPoint.y)) + { + hit = true; + } } else if (displayObject.containsPoint) { - hit = displayObject.containsPoint(point); + if (displayObject.containsPoint(point)) + { + hit = true; + } } } @@ -878,7 +1098,10 @@ interactionEvent.target = displayObject; } - func(interactionEvent, displayObject, hit); + if (func) + { + func(interactionEvent, displayObject, !!hit); + } } } @@ -914,7 +1137,7 @@ { const event = events[i]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -998,7 +1221,7 @@ { const event = events[i]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1098,7 +1321,7 @@ const test = isRightButton ? flags.RIGHT_DOWN : flags.LEFT_DOWN; - const isDown = trackingData !== undefined && (trackingData.flags | test); + const isDown = trackingData !== undefined && (trackingData.flags & test); if (hit) { @@ -1118,11 +1341,11 @@ { if (isRightButton) { - trackingData.rightDown = hit; + trackingData.rightDown = false; } else { - trackingData.leftDown = hit; + trackingData.leftDown = false; } } } @@ -1180,7 +1403,7 @@ { const event = events[i]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1255,7 +1478,7 @@ this.setCursorMode(null); } - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1268,6 +1491,12 @@ { this.emit('mouseout', interactionEvent); } + else + { + // we can get touchleave events after touchend, so we want to make sure we don't + // introduce memory leaks + this.releaseInteractionDataForPointerId(interactionData.identifier); + } } /** @@ -1344,7 +1573,7 @@ // Only mouse and pointer can call onPointerOver, so events will always be length 1 const event = events[0]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1366,12 +1595,14 @@ * Get InteractionData for a given pointerId. Store that data as well * * @private - * @param {number} pointerId - Identifier from a pointer event + * @param {PointerEvent} event - Normalized pointer event, output from normalizeToPointerData * @return {InteractionData} - Interaction data for the given pointer identifier */ - getInteractionDataForPointerId(pointerId) + getInteractionDataForPointerId(event) { - if (pointerId === MOUSE_POINTER_ID) + const pointerId = event.pointerId; + + if (pointerId === MOUSE_POINTER_ID || event.pointerType === 'mouse') { return this.mouse; } diff --git a/src/interaction/InteractionTrackingData.js b/src/interaction/InteractionTrackingData.js index 6a1f69e..8493b63 100644 --- a/src/interaction/InteractionTrackingData.js +++ b/src/interaction/InteractionTrackingData.js @@ -35,8 +35,10 @@ } /** + * Unique pointer id of the event + * * @readonly - * @type {number} Unique pointer id of the event + * @member {number} */ get pointerId() { diff --git a/src/interaction/index.js b/src/interaction/index.js index 56c5b09..72f7d71 100644 --- a/src/interaction/index.js +++ b/src/interaction/index.js @@ -1,4 +1,8 @@ /** + * This namespace contains a renderer plugin for handling mouse, pointer, and touch events. + * + * Do not instantiate this plugin directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. * @namespace PIXI.interaction */ export { default as InteractionData } from './InteractionData'; diff --git a/src/interaction/interactiveTarget.js b/src/interaction/interactiveTarget.js index d252fba..cebac40 100644 --- a/src/interaction/interactiveTarget.js +++ b/src/interaction/interactiveTarget.js @@ -2,7 +2,7 @@ * Default property values of interactive objects * Used by {@link PIXI.interaction.InteractionManager} to automatically give all DisplayObjects these properties * - * @mixin + * @private * @name interactiveTarget * @memberof PIXI.interaction * @example @@ -14,18 +14,28 @@ * ); */ export default { + /** - * Determines if the displayObject be clicked/touched + * Enable interaction events for the DisplayObject. Touch, pointer and mouse + * events will not be emitted unless `interactive` is set to `true`. * - * @inner {boolean} + * @example + * const sprite = new PIXI.Sprite(texture); + * sprite.interactive = true; + * sprite.on('tap', (event) => { + * //handle event + * }); + * @member {boolean} + * @memberof PIXI.DisplayObject# */ interactive: false, /** * Determines if the children to the displayObject can be clicked/touched - * Setting this to false allows pixi to bypass a recursive hitTest function + * Setting this to false allows pixi to bypass a recursive `hitTest` function * - * @inner {boolean} + * @member {boolean} + * @memberof PIXI.Container# */ interactiveChildren: true, @@ -33,7 +43,12 @@ * Interaction shape. Children will be hit first, then this shape will be checked. * Setting this will cause this shape to be checked in hit tests rather than the displayObject's bounds. * - * @inner {PIXI.Rectangle|PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.RoundedRectangle} + * @example + * const sprite = new PIXI.Sprite(texture); + * sprite.interactive = true; + * sprite.hitArea = new PIXI.Rectangle(0, 0, 100, 100); + * @member {PIXI.Rectangle|PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.RoundedRectangle} + * @memberof PIXI.DisplayObject# */ hitArea: null, @@ -41,8 +56,12 @@ * If enabled, the mouse cursor use the pointer behavior when hovered over the displayObject if it is interactive * Setting this changes the 'cursor' property to `'pointer'`. * + * @example + * const sprite = new PIXI.Sprite(texture); + * sprite.interactive = true; + * sprite.buttonMode = true; * @member {boolean} - * @memberof PIXI.interaction.interactiveTarget# + * @memberof PIXI.DisplayObject# */ get buttonMode() { @@ -64,9 +83,14 @@ * This defines what cursor mode is used when the mouse cursor * is hovered over the displayObject. * + * @example + * const sprite = new PIXI.Sprite(texture); + * sprite.interactive = true; + * sprite.cursor = 'wait'; * @see https://developer.mozilla.org/en/docs/Web/CSS/cursor * - * @inner {string} + * @member {string} + * @memberof PIXI.DisplayObject# */ cursor: null, @@ -74,7 +98,7 @@ * Internal set of all active pointers, by identifier * * @member {Map} - * @memberof PIXI.interaction.interactiveTarget# + * @memberof PIXI.DisplayObject# * @private */ get trackedPointers() @@ -87,7 +111,8 @@ /** * Map of all tracked pointers, by identifier. Use trackedPointers to access. * - * @private {Map} + * @private + * @type {Map} */ _trackedPointers: undefined, }; diff --git a/src/loaders/index.js b/src/loaders/index.js index 5189793..daa1fc4 100644 --- a/src/loaders/index.js +++ b/src/loaders/index.js @@ -1,9 +1,22 @@ +import Application from '../core/Application'; +import Loader from './loader'; + /** + * This namespace contains APIs which extends the {@link https://github.com/englercj/resource-loader resource-loader} module + * for loading assets, data, and other resources dynamically. + * @example + * const loader = new PIXI.loaders.Loader(); + * loader.add('bunny', 'data/bunny.png') + * .add('spaceship', 'assets/spritesheet.json'); + * loader.load((loader, resources) => { + * // resources.bunny + * // resources.spaceship + * }); * @namespace PIXI.loaders */ -export { default as Loader } from './loader'; +export { Loader }; export { default as bitmapFontParser, parse as parseBitmapFontData } from './bitmapFontParser'; -export { default as spritesheetParser } from './spritesheetParser'; +export { default as spritesheetParser, getResourcePath } from './spritesheetParser'; export { default as textureParser } from './textureParser'; /** @@ -13,3 +26,55 @@ * @memberof PIXI.loaders */ export { Resource } from 'resource-loader'; + +/** + * A premade instance of the loader that can be used to load resources. + * @name shared + * @memberof PIXI.loaders + * @type {PIXI.loaders.Loader} + */ +const shared = new Loader(); + +shared.destroy = () => +{ + // protect destroying shared loader +}; + +export { shared }; + +// Mixin the loader construction +const AppPrototype = Application.prototype; + +AppPrototype._loader = null; + +/** + * Loader instance to help with asset loading. + * @name PIXI.Application#loader + * @type {PIXI.loaders.Loader} + */ +Object.defineProperty(AppPrototype, 'loader', { + get() + { + if (!this._loader) + { + const sharedLoader = this._options.sharedLoader; + + this._loader = sharedLoader ? shared : new Loader(); + } + + return this._loader; + }, +}); + +// Override the destroy function +// making sure to destroy the current Loader +AppPrototype._parentDestroy = AppPrototype.destroy; +AppPrototype.destroy = function destroy() +{ + if (this._loader) + { + this._loader.destroy(); + this._loader = null; + } + this._parentDestroy(); +}; diff --git a/src/loaders/loader.js b/src/loaders/loader.js index 767a411..59b8000 100644 --- a/src/loaders/loader.js +++ b/src/loaders/loader.js @@ -90,6 +90,15 @@ { Loader._pixiMiddleware.push(fn); } + + /** + * Destroy the loader, removes references. + */ + destroy() + { + this.removeAllListeners(); + this.reset(); + } } // Copy EE3 prototype (mixin) diff --git a/src/loaders/spritesheetParser.js b/src/loaders/spritesheetParser.js index e57dfb2..eda9584 100644 --- a/src/loaders/spritesheetParser.js +++ b/src/loaders/spritesheetParser.js @@ -1,12 +1,11 @@ import { Resource } from 'resource-loader'; -import path from 'path'; +import url from 'url'; import { Spritesheet } from '../core'; export default function () { return function spritesheetParser(resource, next) { - let resourcePath; const imageResourceName = `${resource.name}_image`; // skip if no data, its not json, it isn't spritesheet data, or the image resource already exists @@ -28,15 +27,7 @@ parentResource: resource, }; - // Prepend url path unless the resource image is a data url - if (resource.isDataUrl) - { - resourcePath = resource.data.meta.image; - } - else - { - resourcePath = `${path.dirname(resource.url.replace(this.baseUrl, ''))}/${resource.data.meta.image}`; - } + const resourcePath = getResourcePath(resource, this.baseUrl); // load the image for this sheet this.add(imageResourceName, resourcePath, loadOptions, function onImageLoad(res) @@ -56,3 +47,14 @@ }); }; } + +export function getResourcePath(resource, baseUrl) +{ + // Prepend url path unless the resource image is a data url + if (resource.isDataUrl) + { + return resource.data.meta.image; + } + + return url.resolve(resource.url.replace(baseUrl, ''), resource.data.meta.image); +} diff --git a/src/mesh/canvas/CanvasMeshRenderer.js b/src/mesh/canvas/CanvasMeshRenderer.js index 297205f..f7899e1 100644 --- a/src/mesh/canvas/CanvasMeshRenderer.js +++ b/src/mesh/canvas/CanvasMeshRenderer.js @@ -134,12 +134,33 @@ const textureWidth = base.width; const textureHeight = base.height; - const u0 = uvs[index0] * base.width; - const u1 = uvs[index1] * base.width; - const u2 = uvs[index2] * base.width; - const v0 = uvs[index0 + 1] * base.height; - const v1 = uvs[index1 + 1] * base.height; - const v2 = uvs[index2 + 1] * base.height; + let u0; + let u1; + let u2; + let v0; + let v1; + let v2; + + if (mesh.uploadUvTransform) + { + const ut = mesh._uvTransform.mapCoord; + + u0 = ((uvs[index0] * ut.a) + (uvs[index0 + 1] * ut.c) + ut.tx) * base.width; + u1 = ((uvs[index1] * ut.a) + (uvs[index1 + 1] * ut.c) + ut.tx) * base.width; + u2 = ((uvs[index2] * ut.a) + (uvs[index2 + 1] * ut.c) + ut.tx) * base.width; + v0 = ((uvs[index0] * ut.b) + (uvs[index0 + 1] * ut.d) + ut.ty) * base.height; + v1 = ((uvs[index1] * ut.b) + (uvs[index1 + 1] * ut.d) + ut.ty) * base.height; + v2 = ((uvs[index2] * ut.b) + (uvs[index2 + 1] * ut.d) + ut.ty) * base.height; + } + else + { + u0 = uvs[index0] * base.width; + u1 = uvs[index1] * base.width; + u2 = uvs[index2] * base.width; + v0 = uvs[index0 + 1] * base.height; + v1 = uvs[index1 + 1] * base.height; + v2 = uvs[index2 + 1] * base.height; + } let x0 = vertices[index0]; let x1 = vertices[index1]; diff --git a/src/mesh/webgl/MeshRenderer.js b/src/mesh/webgl/MeshRenderer.js index 78d24c3..ab99fcd 100644 --- a/src/mesh/webgl/MeshRenderer.js +++ b/src/mesh/webgl/MeshRenderer.js @@ -2,6 +2,8 @@ const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; +const matrixIdentity = core.Matrix.IDENTITY; + /** * WebGL renderer plugin for tiling sprites * @@ -64,6 +66,7 @@ mesh.geometry.glVertexArrayObjects[this.CONTEXT_UID].draw(mesh.drawMode, mesh.size, mesh.start, mesh.geometry.instanceCount); } + /** * draws mesh * @param {PIXI.mesh.RawMesh} mesh mesh instance diff --git a/src/mesh/webgl/mesh.vert b/src/mesh/webgl/mesh.vert index a337aef..acc096c 100644 --- a/src/mesh/webgl/mesh.vert +++ b/src/mesh/webgl/mesh.vert @@ -1,8 +1,9 @@ attribute vec2 aVertexPosition; attribute vec2 aTextureCoord; -uniform mat3 translationMatrix; uniform mat3 projectionMatrix; +uniform mat3 translationMatrix; +uniform mat3 uTransform; varying vec2 vTextureCoord; @@ -10,5 +11,5 @@ { gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - vTextureCoord = aTextureCoord; + vTextureCoord = (uTransform * vec3(aTextureCoord, 1.0)).xy; } diff --git a/src/particles/ParticleContainer.js b/src/particles/ParticleContainer.js index 67796cf..63f7791 100644 --- a/src/particles/ParticleContainer.js +++ b/src/particles/ParticleContainer.js @@ -313,10 +313,10 @@ frame.y * resolution, frame.width * resolution, frame.height * resolution, - positionX * resolution, - positionY * resolution, - finalWidth * resolution, - finalHeight * resolution + positionX * renderer.resolution, + positionY * renderer.resolution, + finalWidth * renderer.resolution, + finalHeight * renderer.resolution ); } } diff --git a/src/prepare/BasePrepare.js b/src/prepare/BasePrepare.js index 09eb5e5..e45c4dc 100644 --- a/src/prepare/BasePrepare.js +++ b/src/prepare/BasePrepare.js @@ -18,6 +18,18 @@ * basic queuing functionality and is extended by {@link PIXI.prepare.WebGLPrepare} and {@link PIXI.prepare.CanvasPrepare} * to provide preparation capabilities specific to their respective renderers. * + * @example + * // Create a sprite + * const sprite = new PIXI.Sprite.fromImage('something.png'); + * + * // Load object into GPU + * app.renderer.plugins.prepare.upload(sprite, () => { + * + * //Texture(s) has been uploaded to GPU + * app.stage.addChild(sprite); + * + * }) + * * @abstract * @class * @memberof PIXI.prepare @@ -100,8 +112,16 @@ this.prepareItems(); }; - this.register(findText, drawText); - this.register(findTextStyle, calculateTextStyle); + // hooks to find the correct texture + this.registerFindHook(findText); + this.registerFindHook(findTextStyle); + this.registerFindHook(findMultipleBaseTextures); + this.registerFindHook(findBaseTexture); + this.registerFindHook(findTexture); + + // upload hooks + this.registerUploadHook(drawText); + this.registerUploadHook(calculateTextStyle); } /** @@ -138,7 +158,7 @@ if (!this.ticking) { this.ticking = true; - SharedTicker.addOnce(this.tick, this); + SharedTicker.addOnce(this.tick, this, core.UPDATE_PRIORITY.UTILITY); } } else if (done) @@ -172,13 +192,16 @@ const item = this.queue[0]; let uploaded = false; - for (let i = 0, len = this.uploadHooks.length; i < len; i++) + if (item && !item._destroyed) { - if (this.uploadHooks[i](this.uploadHookHelper, item)) + for (let i = 0, len = this.uploadHooks.length; i < len; i++) { - this.queue.shift(); - uploaded = true; - break; + if (this.uploadHooks[i](this.uploadHookHelper, item)) + { + this.queue.shift(); + uploaded = true; + break; + } } } @@ -205,26 +228,36 @@ else { // if we are not finished, on the next rAF do this again - SharedTicker.addOnce(this.tick, this); + SharedTicker.addOnce(this.tick, this, core.UPDATE_PRIORITY.UTILITY); } } /** - * Adds hooks for finding and uploading items. + * Adds hooks for finding items. * - * @param {Function} [addHook] - Function call that takes two parameters: `item:*, queue:Array` - function must return `true` if it was able to add item to the queue. - * @param {Function} [uploadHook] - Function call that takes two parameters: `prepare:CanvasPrepare, item:*` and - * function must return `true` if it was able to handle upload of item. - * @return {PIXI.CanvasPrepare} Instance of plugin for chaining. + * @param {Function} addHook - Function call that takes two parameters: `item:*, queue:Array` + * function must return `true` if it was able to add item to the queue. + * @return {PIXI.BasePrepare} Instance of plugin for chaining. */ - register(addHook, uploadHook) + registerFindHook(addHook) { if (addHook) { this.addHooks.push(addHook); } + return this; + } + + /** + * Adds hooks for uploading items. + * + * @param {Function} uploadHook - Function call that takes two parameters: `prepare:CanvasPrepare, item:*` and + * function must return `true` if it was able to handle upload of item. + * @return {PIXI.BasePrepare} Instance of plugin for chaining. + */ + registerUploadHook(uploadHook) + { if (uploadHook) { this.uploadHooks.push(uploadHook); @@ -287,6 +320,88 @@ } /** + * Built-in hook to find multiple textures from objects like AnimatedSprites. + * + * @private + * @param {PIXI.DisplayObject} item - Display object to check + * @param {Array<*>} queue - Collection of items to upload + * @return {boolean} if a PIXI.Texture object was found. + */ +function findMultipleBaseTextures(item, queue) +{ + let result = false; + + // Objects with mutliple textures + if (item && item._textures && item._textures.length) + { + for (let i = 0; i < item._textures.length; i++) + { + if (item._textures[i] instanceof core.Texture) + { + const baseTexture = item._textures[i].baseTexture; + + if (queue.indexOf(baseTexture) === -1) + { + queue.push(baseTexture); + result = true; + } + } + } + } + + return result; +} + +/** + * Built-in hook to find BaseTextures from Sprites. + * + * @private + * @param {PIXI.DisplayObject} item - Display object to check + * @param {Array<*>} queue - Collection of items to upload + * @return {boolean} if a PIXI.Texture object was found. + */ +function findBaseTexture(item, queue) +{ + // Objects with textures, like Sprites/Text + if (item instanceof core.BaseTexture) + { + if (queue.indexOf(item) === -1) + { + queue.push(item); + } + + return true; + } + + return false; +} + +/** + * Built-in hook to find textures from objects. + * + * @private + * @param {PIXI.DisplayObject} item - Display object to check + * @param {Array<*>} queue - Collection of items to upload + * @return {boolean} if a PIXI.Texture object was found. + */ +function findTexture(item, queue) +{ + if (item._texture && item._texture instanceof core.Texture) + { + const texture = item._texture.baseTexture; + + if (queue.indexOf(texture) === -1) + { + queue.push(texture); + } + + return true; + } + + return false; +} + +/** * Built-in hook to draw PIXI.Text to its texture. * * @private diff --git a/src/prepare/canvas/CanvasPrepare.js b/src/prepare/canvas/CanvasPrepare.js index 0224bb5..09c207a 100644 --- a/src/prepare/canvas/CanvasPrepare.js +++ b/src/prepare/canvas/CanvasPrepare.js @@ -43,7 +43,7 @@ this.ctx = this.canvas.getContext('2d'); // Add textures to upload - this.register(findBaseTextures, uploadBaseTextures); + this.registerUploadHook(uploadBaseTextures); } /** @@ -89,39 +89,4 @@ return false; } -/** - * Built-in hook to find textures from Sprites. - * - * @private - * @param {PIXI.DisplayObject} item -Display object to check - * @param {Array<*>} queue - Collection of items to upload - * @return {boolean} if a PIXI.Texture object was found. - */ -function findBaseTextures(item, queue) -{ - // Objects with textures, like Sprites/Text - if (item instanceof core.BaseTexture) - { - if (queue.indexOf(item) === -1) - { - queue.push(item); - } - - return true; - } - else if (item._texture && item._texture instanceof core.Texture) - { - const texture = item._texture.baseTexture; - - if (queue.indexOf(texture) === -1) - { - queue.push(texture); - } - - return true; - } - - return false; -} - core.CanvasRenderer.registerPlugin('prepare', CanvasPrepare); diff --git a/src/prepare/index.js b/src/prepare/index.js index f559c45..e7491ec 100644 --- a/src/prepare/index.js +++ b/src/prepare/index.js @@ -1,4 +1,29 @@ /** + * The prepare namespace provides renderer-specific plugins for pre-rendering DisplayObjects. These plugins are useful for + * asynchronously preparing assets, textures, graphics waiting to be displayed. + * + * Do not instantiate these plugins directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. + * @example + * // Create a new application + * const app = new PIXI.Application(); + * document.body.appendChild(app.view); + * + * // Don't start rendering right away + * app.stop(); + * + * // create a display object + * const rect = new PIXI.Graphics() + * .beginFill(0x00ff00) + * .drawRect(40, 40, 200, 200); + * + * // Add to the stage + * app.stage.addChild(rect); + * + * // Don't start rendering until the graphic is uploaded to the GPU + * app.renderer.plugins.prepare.upload(app.stage, () => { + * app.start(); + * }); * @namespace PIXI.prepare */ export { default as webgl } from './webgl/WebGLPrepare'; diff --git a/src/prepare/webgl/WebGLPrepare.js b/src/prepare/webgl/WebGLPrepare.js index eb7023f..9df3c3f 100644 --- a/src/prepare/webgl/WebGLPrepare.js +++ b/src/prepare/webgl/WebGLPrepare.js @@ -22,12 +22,11 @@ this.uploadHookHelper = this.renderer; // Add textures and graphics to upload - this.register(findBaseTextures, uploadBaseTextures) - .register(findGraphics, uploadGraphics); + this.registerFindHook(findGraphics); + this.registerUploadHook(uploadBaseTextures); + this.registerUploadHook(uploadGraphics); } - } - /** * Built-in hook to upload PIXI.Texture objects to the GPU. * @@ -80,41 +79,6 @@ } /** - * Built-in hook to find textures from Sprites. - * - * @private - * @param {PIXI.DisplayObject} item - Display object to check - * @param {Array<*>} queue - Collection of items to upload - * @return {boolean} if a PIXI.Texture object was found. - */ -function findBaseTextures(item, queue) -{ - // Objects with textures, like Sprites/Text - if (item instanceof core.BaseTexture) - { - if (queue.indexOf(item) === -1) - { - queue.push(item); - } - - return true; - } - else if (item._texture && item._texture instanceof core.Texture) - { - const texture = item._texture.baseTexture; - - if (queue.indexOf(texture) === -1) - { - queue.push(texture); - } - - return true; - } - - return false; -} - -/** * Built-in hook to find graphics. * * @private diff --git a/test/.eslintrc.json b/test/.eslintrc.json index e03220d..ac12110 100644 --- a/test/.eslintrc.json +++ b/test/.eslintrc.json @@ -12,7 +12,8 @@ "sinon": false, "expect": false, "assert": false, - "PIXI": false + "PIXI": false, + "PointerEvent": true }, "rules": { "func-names": 0, diff --git a/test/core/BaseTexture.js b/test/core/BaseTexture.js index 8b93478..bb65068 100644 --- a/test/core/BaseTexture.js +++ b/test/core/BaseTexture.js @@ -14,4 +14,28 @@ expect(baseTexture.imageType).to.be.equals('png'); }); }); + + it('should remove Canvas BaseTexture from cache on destroy', function () + { + const canvas = document.createElement('canvas'); + const texture = PIXI.BaseTexture.fromCanvas(canvas); + const _pixiId = canvas._pixiId; + + expect(PIXI.utils.BaseTextureCache[_pixiId]).to.equal(texture); + texture.destroy(); + expect(PIXI.utils.BaseTextureCache[_pixiId]).to.equal(undefined); + }); + + it('should remove Image BaseTexture from cache on destroy', function () + { + const URL = 'foo.png'; + const NAME = 'bar'; + const image = new Image(); + + const texture = PIXI.Texture.fromLoader(image, URL, NAME); + + expect(PIXI.utils.BaseTextureCache[NAME]).to.equal(texture.baseTexture); + texture.destroy(true); + expect(PIXI.utils.BaseTextureCache[NAME]).to.equal(undefined); + }); }); diff --git a/.travis.yml b/.travis.yml index 3df8b22..14de334 100644 --- a/.travis.yml +++ b/.travis.yml @@ -110,3 +110,43 @@ on: all_branches: true condition: $TRAVIS_TAG + # Deploy config for latest release + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: dist + upload-dir: "release" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: docs + upload-dir: "release/docs" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: coverage + upload-dir: "release/coverage" + on: + all_branches: true + condition: $TRAVIS_TAG \ No newline at end of file diff --git a/README.md b/README.md index d26fadd..3b85f85 100644 --- a/README.md +++ b/README.md @@ -36,22 +36,14 @@ - Wiki: Other misc tutorials and resources are [on the Wiki](https://github.com/pixijs/pixi.js/wiki/Resources). ### Community ### -- Forums: Check out the [forum] (http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow] (http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. +- Forums: Check out the [forum](http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow](http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. - Inspiration: Check out the [gallery](http://www.pixijs.com/gallery) to see some of the amazing things people have created! - Chat: You can join us on [Gitter](https://gitter.im/pixijs/pixi.js) To chat about Pixi. We also now have a Slack channel. If you would like to join it please Send me an email (mat@goodboydigital.com) and I will invite you in. ### Setup ### -It's easy to get started with Pixi.js! Simply grab the pre-built versions from here: - -Release Branch - Nice and stable Pixi.js -- Unminified: [http://pixijs.download/release/pixi.js] -- Minified: [http://pixijs.download/release/pixi.min.js] - -Develop Branch - The bleeding edge version of Pixi.js -- Unminified: [http://pixijs.download/dev/pixi.js] -- Minified: [http://pixijs.download/dev/pixi.min.js] +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. @@ -70,10 +62,10 @@ #### CDN Install (via cdnjs) ```html - + ``` -_Note: `4.2.2` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ +_Note: `4.4.3` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ ### Demos ### @@ -130,7 +122,7 @@ // and the root stage PIXI.Container. var app = new PIXI.Application(); -// The application will create a canvas element for you that you +// The application will create a canvas element for you that you // can then insert into the DOM. document.body.appendChild(app.view); @@ -150,7 +142,7 @@ // Add the bunny to the scene we are building. app.stage.addChild(bunny); - + // Listen for frame updates app.ticker.add(function() { // each frame we spin the bunny around a bit diff --git a/bower.json b/bower.json deleted file mode 100644 index 41b668c..0000000 --- a/bower.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "pixi.js", - "main": "dist/pixi.min.js", - "ignore": [ - "**/.*", - "docs", - "node_modules", - "bower_components", - "test", - "scripts", - "bower.json", - "inch.json" - ], - "dependencies": { - }, - "devDependencies": { - } -} diff --git a/package.json b/package.json index d35cbb2..308469f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.3.5", + "version": "4.4.4", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ @@ -40,11 +40,11 @@ "lib": "babel src --out-dir lib -s", "predocs": "rimraf docs/**", "docs": "jsdoc -c scripts/jsdoc.conf.json -R README.md", - "publish:patch": "npm version patch --no-git-tag-version && npm publish", - "publish:minor": "npm version minor --no-git-tag-version && npm publish", - "publish:major": "npm version major --no-git-tag-version && npm publish", + "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", - "postpublish": "node scripts/release.js" + "postpublish": "git push && git push --tags" }, "files": [ "dist/", @@ -72,7 +72,6 @@ "electron": "^1.4.15", "eslint": "^3.5.0", "floss": "^2.0.1", - "gh-pages": "^0.11.0", "jaguarjs-jsdoc": "^1.0.1", "js-md5": "^0.4.1", "jsdoc": "^3.4.2", diff --git a/scripts/jsdoc.conf.json b/scripts/jsdoc.conf.json index 553e61c..3f52180 100644 --- a/scripts/jsdoc.conf.json +++ b/scripts/jsdoc.conf.json @@ -43,7 +43,7 @@ }, "markdown" : { "parser" : "gfm", - "hardwrap" : true + "hardwrap" : false }, "opts": { "encoding" : "utf8", diff --git a/scripts/release.js b/scripts/release.js deleted file mode 100644 index 156620c..0000000 --- a/scripts/release.js +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env node -'use strict'; - -// Publish script to push releases of the bin files -// the normally are gitignored -const ghpages = require('gh-pages'); -const path = require('path'); -const packageInfo = require(path.join(__dirname, '..', 'package.json')); - -const options = { - src: [ - 'dist/**/*', - 'lib/**/*', - 'src/**/*', - 'scripts/*', - 'scripts/renders/*', - 'test/**/*', - '*.json', - '*.md', - 'LICENSE', - '.eslintrc', - '.editorconfig', - '.travis.yml', - '.babelrc', - ], - dotfiles: true, - branch: 'release', - message: packageInfo.version, - logger: console.log.bind(console), -}; - -ghpages.publish(process.cwd(), options, (err) => -{ - if (err) - { - console.log(err); - process.exit(1); - - return; - } - - process.exit(0); -}); diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index f16bcb1..220427a 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -21,7 +21,7 @@ const DIV_HOOK_ZINDEX = 2; /** - * The Accessibility manager reacreates the ability to tab and and have content read by screen + * The Accessibility manager recreates the ability to tab and and have content read by screen * readers. This is very important as it can possibly help people with disabilities access pixi * content. * diff --git a/src/accessibility/index.js b/src/accessibility/index.js index 568cfb1..0ae108f 100644 --- a/src/accessibility/index.js +++ b/src/accessibility/index.js @@ -1,4 +1,9 @@ /** + * This namespace contains a renderer plugin for interaction accessibility for end-users + * with physical impairments which require screen-renders, keyboard navigation, etc. + * + * Do not instantiate this plugin directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. * @namespace PIXI.accessibility */ export { default as accessibleTarget } from './accessibleTarget'; diff --git a/src/core/Application.js b/src/core/Application.js index 5340578..2fcc729 100644 --- a/src/core/Application.js +++ b/src/core/Application.js @@ -1,6 +1,8 @@ import { autoDetectRenderer } from './autoDetectRenderer'; import Container from './display/Container'; import { shared, Ticker } from './ticker'; +import settings from './settings'; +import { UPDATE_PRIORITY } from './const'; /** * Convenience class to create a new PIXI application. @@ -22,28 +24,52 @@ */ export default class Application { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experience unexplained flickering try setting this to true. - * @param {boolean} [useSharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedLoader=false] - `true` to use PIXI.loaders.shared, `false` to create new Loader. */ - constructor(width, height, options, noWebGL, useSharedTicker = false) + constructor(options, arg2, arg3, arg4, arg5) { + // Support for constructor(width, height, options, noWebGL, useSharedTicker) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + forceCanvas: !!arg4, + sharedTicker: !!arg5, + }, arg3); + } + + /** + * The default options, so we mixin functionality later. + * @member {object} + * @protected + */ + this._options = options = Object.assign({ + sharedTicker: false, + forceCanvas: false, + sharedLoader: false, + }, options); + /** * WebGL renderer if available, otherwise CanvasRenderer * @member {PIXI.WebGLRenderer|PIXI.CanvasRenderer} */ - this.renderer = autoDetectRenderer(width, height, options, noWebGL); + this.renderer = autoDetectRenderer(options); /** * The root display container that's rendered. @@ -63,7 +89,7 @@ * @member {PIXI.ticker.Ticker} * @default PIXI.ticker.shared */ - this.ticker = useSharedTicker ? shared : new Ticker(); + this.ticker = options.sharedTicker ? shared : new Ticker(); // Start the rendering this.start(); @@ -78,7 +104,7 @@ this._ticker = ticker; if (ticker) { - ticker.add(this.render, this); + ticker.add(this.render, this, UPDATE_PRIORITY.LOW); } } get ticker() // eslint-disable-line require-jsdoc @@ -136,13 +162,18 @@ */ destroy(removeView) { - this.stop(); + const oldTicker = this._ticker; + this.ticker = null; + oldTicker.destroy(); + this.stage.destroy(); this.stage = null; this.renderer.destroy(removeView); this.renderer = null; + + this._options = null; } } diff --git a/src/core/autoDetectRenderer.js b/src/core/autoDetectRenderer.js index 8960425..9ec0364 100644 --- a/src/core/autoDetectRenderer.js +++ b/src/core/autoDetectRenderer.js @@ -2,6 +2,7 @@ import CanvasRenderer from './renderers/canvas/CanvasRenderer'; import WebGLRenderer from './renderers/webgl/WebGLRenderer'; +// eslint-disable-next-line valid-jsdoc /** * This helper function will automatically detect which renderer you should be using. * WebGL is the preferred renderer as it is a lot faster. If webGL is not supported by @@ -9,24 +10,32 @@ * * @memberof PIXI * @function autoDetectRenderer - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @return {PIXI.WebGLRenderer|PIXI.CanvasRenderer} Returns WebGL renderer if available, otherwise CanvasRenderer */ -export function autoDetectRenderer(width = 800, height = 600, options, noWebGL) +export function autoDetectRenderer(options, arg1, arg2, arg3) { - if (!noWebGL && utils.isWebGLSupported()) + // Backward-compatible support for noWebGL option + let forceCanvas = options && options.forceCanvas; + + if (arg3 !== undefined) { - return new WebGLRenderer(width, height, options); + forceCanvas = arg3; } - return new CanvasRenderer(width, height, options); + if (!forceCanvas && utils.isWebGLSupported()) + { + return new WebGLRenderer(options, arg1, arg2); + } + + return new CanvasRenderer(options, arg1, arg2); } diff --git a/src/core/const.js b/src/core/const.js index b9e99ae..2c68dee 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -341,3 +341,27 @@ LINEAR_VERTICAL: 0, LINEAR_HORIZONTAL: 1, }; + +/** + * Represents the update priorities used by internal PIXI classes when registered with + * the {@link PIXI.ticker.Ticker} object. Higher priority items are updated first and lower + * priority items, such as render, should go later. + * + * @static + * @constant + * @name UPDATE_PRIORITY + * @memberof PIXI + * @type {object} + * @property {number} INTERACTION=50 Highest priority, used for {@link PIXI.interaction.InteractionManager} + * @property {number} HIGH=25 High priority updating, {@link PIXI.VideoBaseTexture} and {@link PIXI.extras.AnimatedSprite} + * @property {number} NORMAL=0 Default priority for ticker events, see {@link PIXI.ticker.Ticker#add}. + * @property {number} LOW=-25 Low priority used for {@link PIXI.Application} rendering. + * @property {number} UTILITY=-50 Lowest priority used for {@link PIXI.prepare.BasePrepare} utility. + */ +export const UPDATE_PRIORITY = { + INTERACTION: 50, + HIGH: 25, + NORMAL: 0, + LOW: -25, + UTILITY: -50, +}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 4e6c77d..decb42a 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -13,7 +13,6 @@ * * @class * @extends EventEmitter - * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ export default class DisplayObject extends EventEmitter @@ -113,6 +112,29 @@ * @private */ this._mask = null; + + /** + * If the object has been destroyed via destroy(). If true, it should not be used. + * + * @member {boolean} + * @private + * @readonly + */ + this._destroyed = false; + + /** + * Fired when this DisplayObject is added to a Container. + * + * @event PIXI.DisplayObject#added + * @param {PIXI.Container} container - The container added to. + */ + + /** + * Fired when this DisplayObject is removed from a Container. + * + * @event PIXI.DisplayObject#removed + * @param {PIXI.Container} container - The container removed from. + */ } /** @@ -403,6 +425,8 @@ this.interactive = false; this.interactiveChildren = false; + + this._destroyed = true; } /** diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index aafdfe2..f787c4a 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -792,8 +792,8 @@ sprite.worldAlpha = this.worldAlpha * sprite.alpha; sprite.blendMode = this.blendMode; - sprite.texture._frame.width = rect.width; - sprite.texture._frame.height = rect.height; + sprite._texture._frame.width = rect.width; + sprite._texture._frame.height = rect.height; sprite.transform.worldTransform = this.transform.worldTransform; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index a24c775..b9f192d 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -19,11 +19,12 @@ */ export default class SystemRenderer extends EventEmitter { + // eslint-disable-next-line valid-jsdoc /** * @param {string} system - The name of the system this renderer is for. - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -37,27 +38,31 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(system, screenWidth, screenHeight, options) + constructor(system, options, arg2, arg3) { super(); sayHello(system); - // prepare options - if (options) + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') { - for (const i in settings.RENDER_OPTIONS) - { - if (typeof options[i] === 'undefined') - { - options[i] = settings.RENDER_OPTIONS[i]; - } - } + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); } - else - { - options = settings.RENDER_OPTIONS; - } + + // Add the default render options + options = Object.assign({}, settings.RENDER_OPTIONS, options); + + /** + * The supplied constructor options. + * + * @member {Object} + * @readOnly + */ + this.options = options; /** * The type of the renderer. @@ -75,7 +80,7 @@ * * @member {PIXI.Rectangle} */ - this.screen = new Rectangle(0, 0, screenWidth || 800, screenHeight || 600); + this.screen = new Rectangle(0, 0, options.width, options.height); /** * The canvas element that everything is drawn to @@ -279,6 +284,8 @@ this.blendModes = null; + this.options = null; + this.preserveDrawingBuffer = false; this.clearBeforeRender = false; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index e84e49c..691caf0 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -17,10 +17,11 @@ */ export default class CanvasRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -34,9 +35,9 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('Canvas', screenWidth, screenHeight, options); + super('Canvas', options, arg2, arg3); this.type = RENDERER_TYPE.CANVAS; @@ -96,7 +97,19 @@ this.context = null; this.renderingToScreen = false; - this.resize(screenWidth, screenHeight); + this.resize(this.options.width, this.options.height); + + /** + * Fired after rendering finishes. + * + * @event PIXI.CanvasRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.CanvasRenderer#prerender + */ } /** @@ -299,4 +312,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.CanvasRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.CanvasExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.CanvasPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.CanvasRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index e1cf7cd..f6fe123 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -31,11 +31,12 @@ */ export default class WebGLRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** * - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -55,9 +56,9 @@ * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('WebGL', screenWidth, screenHeight, options); + super('WebGL', options, arg2, arg3); /** * The type of this renderer as a standardised const @@ -89,12 +90,6 @@ }; - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ this._backgroundColorRgba[3] = this.transparent ? 0 : 1; this.globalUniforms = new UniformGroup({ @@ -117,6 +112,13 @@ this.initPlugins(); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ if(options.context) { this.context.initFromContext(options.context); @@ -169,6 +171,25 @@ } return this; + + /** + * Fired after rendering finishes. + * + * @event PIXI.WebGLRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.WebGLRenderer#prerender + */ + + /** + * Fired when the WebGL context is set. + * + * @event PIXI.WebGLRenderer#context + * @param {WebGLRenderingContext} gl - WebGL context. + */ } /** @@ -295,4 +316,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.WebGLRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.WebGLRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(WebGLRenderer); diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index 4eb6128..fa3d883 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -47,6 +47,14 @@ * @member {boolean} */ this.enabled = true; + + /** + * If enabled, pixi will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; } /** diff --git a/src/core/renderers/webgl/systems/ContextSystem.js b/src/core/renderers/webgl/systems/ContextSystem.js index ff958c7..8df85ea 100644 --- a/src/core/renderers/webgl/systems/ContextSystem.js +++ b/src/core/renderers/webgl/systems/ContextSystem.js @@ -75,7 +75,8 @@ */ createContext(canvas, options) { - var gl = canvas.getContext('webgl', options) || + var gl = //canvas.getContext('webgl2', options) || + canvas.getContext('webgl', options) || canvas.getContext('experimental-webgl', options); if (!gl) diff --git a/src/core/renderers/webgl/systems/FilterSystem.js b/src/core/renderers/webgl/systems/FilterSystem.js index f47335d..37e28d2 100644 --- a/src/core/renderers/webgl/systems/FilterSystem.js +++ b/src/core/renderers/webgl/systems/FilterSystem.js @@ -111,7 +111,7 @@ // TODO we should fit the rect around the transform.. } - else + else if (filters[0].autoFit) { sourceFrame.fit(filterData.stack[0].destinationFrame); } diff --git a/src/core/renderers/webgl/systems/shader/shader/mapType.js b/src/core/renderers/webgl/systems/shader/shader/mapType.js index 581dace..917538a 100644 --- a/src/core/renderers/webgl/systems/shader/shader/mapType.js +++ b/src/core/renderers/webgl/systems/shader/shader/mapType.js @@ -15,7 +15,7 @@ } } - return GL_TABLE[type]; + return GL_TABLE[type]; }; var GL_TABLE = null; diff --git a/src/core/settings.js b/src/core/settings.js index 18c5877..38ef45c 100644 --- a/src/core/settings.js +++ b/src/core/settings.js @@ -2,6 +2,15 @@ import canUploadSameBuffer from './utils/canUploadSameBuffer'; /** + * User's customizable globals for overriding the default PIXI settings, such + * as a renderer's default resolution, framerate, float percision, etc. + * @example + * // Use the native window resolution as the default resolution + * // will support high-density displays when rendering + * PIXI.settings.RESOLUTION = window.devicePixelRatio. + * + * // Disable interpolation when scaling, will make texture be pixelated + * PIXI.settings.SCALE_MODE = PIXI.SCALE_MODES.NEAREST; * @namespace PIXI.settings */ export default { @@ -101,6 +110,9 @@ * @property {boolean} clearBeforeRender=true * @property {boolean} preserveDrawingBuffer=false * @property {boolean} roundPixels=false + * @property {number} width=800 + * @property {number} height=600 + * @property {boolean} legacy=false */ RENDER_OPTIONS: { view: null, @@ -112,6 +124,9 @@ clearBeforeRender: true, preserveDrawingBuffer: false, roundPixels: false, + width: 800, + height: 600, + legacy: false, }, /** diff --git a/src/core/shader/Program.js b/src/core/shader/Program.js index c0b538b..07b4398 100644 --- a/src/core/shader/Program.js +++ b/src/core/shader/Program.js @@ -1,6 +1,6 @@ import extractUniformsFromSrc from './extractUniformsFromSrc'; import generateUniformsSync from './generateUniformsSync'; -import glCore from 'pixi-gl-core'; +import shaderUtils from '../renderers/webgl/systems/shader/shader'; import { ProgramCache } from '../utils'; import getTestContext from '../utils/getTestContext'; @@ -64,14 +64,17 @@ } else { - vertexSrc = glCore.shader.setPrecision(vertexSrc, 'mediump'); - fragmentSrc = glCore.shader.setPrecision(fragmentSrc, 'mediump'); + vertexSrc = shaderUtils.setPrecision(vertexSrc, 'mediump'); + fragmentSrc = shaderUtils.setPrecision(fragmentSrc, 'mediump'); - const program = glCore.shader.compileProgram(gl, vertexSrc, fragmentSrc); + const program = shaderUtils.compileProgram(gl, vertexSrc, fragmentSrc); + console.log("<<<<<<>>><<"); this.attributeData = this.getAttributeData(program, gl); this.uniformData = this.getUniformData(program, gl); + console.log(this.uniformData); + //gl.deleteProgram(program); } } @@ -95,13 +98,13 @@ for (let i = 0; i < totalAttributes; i++) { const attribData = gl.getActiveAttrib(program, i); - const type = glCore.shader.mapType(gl, attribData.type); + const type = shaderUtils.mapType(gl, attribData.type); /*eslint-disable */ const data = { type: type, name: attribData.name, - size: glCore.shader.mapSize(type), + size: shaderUtils.mapSize(type), location: 0, }; /*eslint-enable */ @@ -137,13 +140,14 @@ // TODO expose this as a prop? // const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); - const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); + //const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); for (let i = 0; i < totalUniforms; i++) { const uniformData = gl.getActiveUniform(program, i); const name = uniformData.name.replace(/\[.*?\]/, ''); - const type = glCore.shader.mapType(gl, uniformData.type); + const type = shaderUtils.mapType(gl, uniformData.type); + console.log('mapping ' + uniformData.type + ' to ' + type); // if (!name.match(maskRegex)) { @@ -151,7 +155,7 @@ uniforms[name] = { type: type, size: uniformData.size, - value: glCore.shader.defaultValue(type, uniformData.size), + value: shaderUtils.defaultValue(type, uniformData.size), }; /*eslint-enable */ } diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index e387e99..b57d095 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -147,12 +147,12 @@ // so if _width is 0 then width was not set.. if (this._width) { - this.scale.x = sign(this.scale.x) * this._width / this.texture.orig.width; + this.scale.x = sign(this.scale.x) * this._width / this._texture.orig.width; } if (this._height) { - this.scale.y = sign(this.scale.y) * this._height / this.texture.orig.height; + this.scale.y = sign(this.scale.y) * this._height / this._texture.orig.height; } } diff --git a/src/core/sprites/canvas/CanvasTinter.js b/src/core/sprites/canvas/CanvasTinter.js index 27e5e0c..fd9de5d 100644 --- a/src/core/sprites/canvas/CanvasTinter.js +++ b/src/core/sprites/canvas/CanvasTinter.js @@ -18,7 +18,7 @@ */ getTintedTexture: (sprite, color) => { - const texture = sprite.texture; + const texture = sprite._texture; color = CanvasTinter.roundColor(color); diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 5a4edda..8b9b797 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -344,6 +344,7 @@ const texture = this._texture; const style = this._style; + const padding = style.trim ? 0 : style.padding; texture.baseTexture.valid = true; texture.baseTexture.resolution = this.resolution; @@ -353,11 +354,11 @@ texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - texture.trim.x = -style.padding; - texture.trim.y = -style.padding; + texture.trim.x = -padding; + texture.trim.y = -padding; - texture.orig.width = texture._frame.width - (style.padding * 2); - texture.orig.height = texture._frame.height - (style.padding * 2); + texture.orig.width = texture._frame.width - (padding * 2); + texture.orig.height = texture._frame.height - (padding * 2); // call sprite onTextureUpdate to update scale if _width or _height were set this._onTextureUpdate(); diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index 853da3d..8a9c34b 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -114,6 +114,38 @@ this.cacheId = null; this.validate(); + + /** + * Fired when a not-immediately-available source finishes loading. + * + * @protected + * @event PIXI.BaseTexture#loaded + * @param {PIXI.BaseTexture} baseTexture - Resource loaded. + */ + + /** + * Fired when a not-immediately-available source fails to load. + * + * @protected + * @event PIXI.BaseTexture#error + * @param {PIXI.BaseTexture} baseTexture - Resource errored. + */ + + /** + * Fired when BaseTexture is updated. + * + * @protected + * @event PIXI.BaseTexture#update + * @param {PIXI.BaseTexture} baseTexture - Instance of texture being updated. + */ + + /** + * Fired when BaseTexture is destroyed. + * + * @protected + * @event PIXI.BaseTexture#dispose + * @param {PIXI.BaseTexture} baseTexture - Instance of texture being destroyed. + */ } setResource(resource) diff --git a/src/core/textures/Spritesheet.js b/src/core/textures/Spritesheet.js index d469c62..9b24f27 100644 --- a/src/core/textures/Spritesheet.js +++ b/src/core/textures/Spritesheet.js @@ -208,6 +208,7 @@ // lets also add the frame to pixi's global cache for fromFrame and fromImage functions TextureCache[i] = this.textures[i]; + this.textures[i].textureCacheId = i; } frameIndex++; diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 0a2c379..2213087 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -147,9 +147,9 @@ /** * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. * - * @event update - * @memberof PIXI.Texture# + * @event PIXI.Texture#update * @protected + * @param {PIXI.Texture} texture - Instance of texture being updated. */ this._updateID = 0; @@ -159,6 +159,15 @@ * @type {Object} */ this.transform = null; + + /** + * The id under which this Texture has been added to the texture cache. This is + * automatically set in certain cases, but may not always be accurate, particularly if + * the texture is in the cache under multiple ids. + * + * @member {string} + */ + this.textureCacheId = null; } /** @@ -225,11 +234,17 @@ this._uvs = null; this.trim = null; this.orig = null; + this.textureCacheId = null; this.valid = false; - this.off('dispose', this.dispose, this); - this.off('update', this.update, this); + for (const prop in TextureCache) + { + if (TextureCache[prop] === this) + { + delete TextureCache[prop]; + } + } } /** @@ -315,14 +330,15 @@ */ static fromLoader(source, imageUrl, name) { - // console.log('added from loader...') + // console.log('added from loader...') const resource = new ImageResource(source);//.from(imageUrl, crossorigin);// document.createElement('img'); - // console.log('base resource ' + resource.width); + // console.log('base resource ' + resource.width); const baseTexture = new BaseTexture(resource, settings.SCALE_MODE, getResolutionOfUrl(imageUrl)); + /// console.log('base width ' + baseTexture.width); const texture = new Texture(baseTexture); @@ -335,6 +351,7 @@ // lets also add the frame to pixi's global cache for fromFrame and fromImage fucntions BaseTextureCache[name] = baseTexture; TextureCache[name] = texture; + texture.textureCacheId = name; // also add references by url if they are different. if (name !== imageUrl) @@ -355,6 +372,10 @@ */ static addTextureToCache(texture, id) { + if (!texture.textureCacheId) + { + texture.textureCacheId = id; + } TextureCache[id] = texture; } @@ -495,6 +516,7 @@ */ Texture.EMPTY = new Texture(new BaseTexture()); removeAllHandlers(Texture.EMPTY); +removeAllHandlers(Texture.EMPTY.baseTexture); /** * A white texture of 10x10 size, used for graphics and other things @@ -505,3 +527,4 @@ */ Texture.WHITE = createWhiteTexture(); removeAllHandlers(Texture.WHITE); +removeAllHandlers(Texture.WHITE.baseTexture); diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index 59eb9fd..a9b9f51 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -1,6 +1,7 @@ import BaseTexture from './BaseTexture'; import { uid, BaseTextureCache } from '../utils'; -import * as ticker from '../ticker'; +import { shared } from '../ticker'; +import { UPDATE_PRIORITY } from '../const'; /** * A texture of a [playing] Video. @@ -125,7 +126,7 @@ if (!this._isAutoUpdating && this.autoUpdate) { - ticker.shared.add(this.update, this); + shared.add(this.update, this, UPDATE_PRIORITY.HIGH); this._isAutoUpdating = true; } } @@ -139,7 +140,7 @@ { if (this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); this._isAutoUpdating = false; } } @@ -187,7 +188,7 @@ { if (this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); } if (this.source && this.source._pixiId) @@ -281,12 +282,12 @@ if (!this._autoUpdate && this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); this._isAutoUpdating = false; } else if (this._autoUpdate && !this._isAutoUpdating) { - ticker.shared.add(this.update, this); + shared.add(this.update, this, UPDATE_PRIORITY.HIGH); this._isAutoUpdating = true; } } diff --git a/src/core/ticker/Ticker.js b/src/core/ticker/Ticker.js index 17a1517..40df138 100644 --- a/src/core/ticker/Ticker.js +++ b/src/core/ticker/Ticker.js @@ -1,12 +1,10 @@ import settings from '../settings'; -import EventEmitter from 'eventemitter3'; - -// Internal event used by composed emitter -const TICK = 'tick'; +import { UPDATE_PRIORITY } from '../const'; +import TickerListener from './TickerListener'; /** * A Ticker class that runs an update loop that other objects listen to. - * This class is composed around an EventEmitter object to add listeners + * This class is composed around listeners * meant for execution on the next requested animation frame. * Animation frames are requested only when necessary, * e.g. When the ticker is started and the emitter has listeners. @@ -22,10 +20,11 @@ constructor() { /** - * Internal emitter used to fire 'tick' event + * The first listener. All new listeners added are chained on this. * @private + * @type {TickerListener} */ - this._emitter = new EventEmitter(); + this._head = new TickerListener(null, null, Infinity); /** * Internal current frame request ID @@ -131,7 +130,7 @@ // Invoke listeners now this.update(time); // Listener side effects may have modified ticker state. - if (this.started && this._requestId === null && this._emitter.listeners(TICK, true)) + if (this.started && this._requestId === null && this._head.next) { this._requestId = requestAnimationFrame(this._tick); } @@ -148,7 +147,7 @@ */ _requestIfNeeded() { - if (this._requestId === null && this._emitter.listeners(TICK, true)) + if (this._requestId === null && this._head.next) { // ensure callbacks get correct delta this.lastTime = performance.now(); @@ -193,35 +192,72 @@ } /** - * Calls {@link module:eventemitter3.EventEmitter#on} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. + * Register a handler for tick events. Calls continuously unless + * it is removed or the ticker is stopped. * * @param {Function} fn - The listener function to be added for updates * @param {Function} [context] - The listener context + * @param {number} [priority=PIXI.UPDATE_PRIORITY.NORMAL] - The priority for emitting * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - add(fn, context) + add(fn, context, priority = UPDATE_PRIORITY.NORMAL) { - this._emitter.on(TICK, fn, context); - - this._startIfPossible(); - - return this; + return this._addListener(new TickerListener(fn, context, priority)); } /** - * Calls {@link module:eventemitter3.EventEmitter#once} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. + * Add a handler for the tick event which is only execute once. * * @param {Function} fn - The listener function to be added for one update * @param {Function} [context] - The listener context + * @param {number} [priority=PIXI.UPDATE_PRIORITY.NORMAL] - The priority for emitting * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - addOnce(fn, context) + addOnce(fn, context, priority = UPDATE_PRIORITY.NORMAL) { - this._emitter.once(TICK, fn, context); + return this._addListener(new TickerListener(fn, context, priority, true)); + } + + /** + * Internally adds the event handler so that it can be sorted by priority. + * Priority allows certain handler (user, AnimatedSprite, Interaction) to be run + * before the rendering. + * + * @private + * @param {TickerListener} listener - Current listener being added. + * @returns {PIXI.ticker.Ticker} This instance of a ticker + */ + _addListener(listener) + { + // For attaching to head + let current = this._head.next; + let previous = this._head; + + // Add the first item + if (!current) + { + listener.connect(previous); + } + else + { + // Go from highest to lowest priority + while (current) + { + if (listener.priority >= current.priority) + { + listener.connect(previous); + break; + } + previous = current; + current = current.next; + } + + // Not yet connected + if (!listener.previous) + { + listener.connect(previous); + } + } this._startIfPossible(); @@ -229,19 +265,33 @@ } /** - * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. - * It checks if the emitter has listeners for 'tick' event. - * If it does, then it cancels the animation frame. + * Removes any handlers matching the function and context parameters. + * If no handlers are left after removing, then it cancels the animation frame. * - * @param {Function} [fn] - The listener function to be removed + * @param {Function} fn - The listener function to be removed * @param {Function} [context] - The listener context to be removed * @returns {PIXI.ticker.Ticker} This instance of a ticker */ remove(fn, context) { - this._emitter.off(TICK, fn, context); + let listener = this._head.next; - if (!this._emitter.listeners(TICK, true)) + while (listener) + { + // We found a match, lets remove it + // no break to delete all possible matches + // incase a listener was added 2+ times + if (listener.match(fn, context)) + { + listener = listener.destroy(); + } + else + { + listener = listener.next; + } + } + + if (!this._head.next) { this._cancelIfNeeded(); } @@ -276,6 +326,25 @@ } /** + * Destroy the ticker and don't use after this. Calling + * this method removes all references to internal events. + */ + destroy() + { + this.stop(); + + let listener = this._head.next; + + while (listener) + { + listener = listener.destroy(true); + } + + this._head.destroy(); + this._head = null; + } + + /** * Triggers an update. An update entails setting the * current {@link PIXI.ticker.Ticker#elapsedMS}, * the current {@link PIXI.ticker.Ticker#deltaTime}, @@ -320,8 +389,22 @@ this.deltaTime = elapsedMS * settings.TARGET_FPMS * this.speed; + // Cache a local reference, in-case ticker is destroyed + // during the emit, we can still check for head.next + const head = this._head; + // Invoke listeners added to internal emitter - this._emitter.emit(TICK, this.deltaTime); + let listener = head.next; + + while (listener) + { + listener = listener.emit(this.deltaTime); + } + + if (!head.next) + { + this._cancelIfNeeded(); + } } else { diff --git a/src/core/ticker/TickerListener.js b/src/core/ticker/TickerListener.js new file mode 100644 index 0000000..2bedb34 --- /dev/null +++ b/src/core/ticker/TickerListener.js @@ -0,0 +1,158 @@ +/** + * Internal class for handling the priority sorting of ticker handlers. + * + * @private + * @class + * @memberof PIXI.ticker + */ +export default class TickerListener +{ + /** + * Constructor + * + * @param {Function} fn - The listener function to be added for one update + * @param {Function} [context=null] - The listener context + * @param {number} [priority=0] - The priority for emitting + * @param {boolean} [once=false] - If the handler should fire once + */ + constructor(fn, context = null, priority = 0, once = false) + { + /** + * The handler function to execute. + * @member {Function} + */ + this.fn = fn; + + /** + * The calling to execute. + * @member {Function} + */ + this.context = context; + + /** + * The current priority. + * @member {number} + */ + this.priority = priority; + + /** + * If this should only execute once. + * @member {boolean} + */ + this.once = once; + + /** + * The next item in chain. + * @member {TickerListener} + */ + this.next = null; + + /** + * The previous item in chain. + * @member {TickerListener} + */ + this.previous = null; + + /** + * `true` if this listener has been destroyed already. + * @member {boolean} + * @private + */ + this._destroyed = false; + } + + /** + * Simple compare function to figure out if a function and context match. + * + * @param {Function} fn - The listener function to be added for one update + * @param {Function} context - The listener context + * @return {boolean} `true` if the listener match the arguments + */ + match(fn, context) + { + context = context || null; + + return this.fn === fn && this.context === context; + } + + /** + * Emit by calling the current function. + * @param {number} deltaTime - time since the last emit. + * @return {TickerListener} Next ticker + */ + emit(deltaTime) + { + if (this.context) + { + this.fn.call(this.context, deltaTime); + } + else + { + this.fn(deltaTime); + } + + if (this.once) + { + this.destroy(); + } + + const redirect = this.next; + + // Soft-destroying should remove + // the next reference + if (this._destroyed) + { + this.next = null; + } + + return redirect; + } + + /** + * Connect to the list. + * @param {TickerListener} previous - Input node, previous listener + */ + connect(previous) + { + this.previous = previous; + if (previous.next) + { + previous.next.previous = this; + } + this.next = previous.next; + previous.next = this; + } + + /** + * Destroy and don't use after this. + * @param {boolean} [hard = false] `true` to remove the `next` reference, this + * is considered a hard destroy. Soft destroy maintains the next reference. + * @return {TickerListener} The listener to redirect while emitting or removing. + */ + destroy(hard = false) + { + this._destroyed = true; + this.fn = null; + this.context = null; + + // Disconnect, hook up next and previous + if (this.previous) + { + this.previous.next = this.next; + } + + if (this.next) + { + this.next.previous = this.previous; + } + + // Redirect to the next item + const redirect = this.previous; + + // Remove references + this.next = hard ? null : redirect; + this.previous = null; + + return redirect; + } +} diff --git a/src/core/ticker/index.js b/src/core/ticker/index.js index 4b3017c..c3020d4 100644 --- a/src/core/ticker/index.js +++ b/src/core/ticker/index.js @@ -45,8 +45,25 @@ const shared = new Ticker(); shared.autoStart = true; +shared.destroy = () => +{ + // protect destroying shared ticker + // this is used by other internal systems + // like AnimatedSprite and InteractionManager +}; /** + * This namespace contains an API for interacting with PIXI's internal global update loop. + * + * This ticker is used for rendering, {@link PIXI.extras.AnimatedSprite AnimatedSprite}, + * {@link PIXI.interaction.InteractionManager InteractionManager} and many other time-based PIXI systems. + * @example + * const ticker = new PIXI.ticker.Ticker(); + * ticker.stop(); + * ticker.add((deltaTime) => { + * // do something every frame + * }); + * ticker.start(); * @namespace PIXI.ticker */ export { shared, Ticker }; diff --git a/src/core/utils/index.js b/src/core/utils/index.js index be44750..7441f7b 100644 --- a/src/core/utils/index.js +++ b/src/core/utils/index.js @@ -9,6 +9,21 @@ let saidHello = false; /** + * Generalized convenience utilities for PIXI. + * @example + * // Extend PIXI's internal Event Emitter. + * class MyEmitter extends PIXI.utils.EventEmitter { + * constructor() { + * super(); + * console.log("Emitter created!"); + * } + * } + * + * // Get info on current device + * console.log(PIXI.utils.isMobile); + * + * // Convert hex color to string + * console.log(PIXI.utils.hex2string(0xff00ff)); // returns: "#ff00ff" * @namespace PIXI.utils */ export { diff --git a/src/deprecation.js b/src/deprecation.js index b9c73b3..33daeec 100644 --- a/src/deprecation.js +++ b/src/deprecation.js @@ -918,6 +918,35 @@ }); /** + * @method + * @name PIXI.prepare.BasePrepare#register + * @see PIXI.prepare.BasePrepare#registerFindHook + * @deprecated since version 4.4.2 + * @param {Function} [addHook] - Function call that takes two parameters: `item:*, queue:Array` + * function must return `true` if it was able to add item to the queue. + * @param {Function} [uploadHook] - Function call that takes two parameters: `prepare:CanvasPrepare, item:*` and + * function must return `true` if it was able to handle upload of item. + * @return {PIXI.BasePrepare} Instance of plugin for chaining. + */ +prepare.BasePrepare.prototype.register = function register(addHook, uploadHook) +{ + warn('renderer.plugins.prepare.register is now deprecated, ' + + 'please use renderer.plugins.prepare.registerFindHook & renderer.plugins.prepare.registerUploadHook'); + + if (addHook) + { + this.registerFindHook(addHook); + } + + if (uploadHook) + { + this.registerUploadHook(uploadHook); + } + + return this; +}; + +/** * The number of graphics or textures to upload to the GPU. * * @name PIXI.prepare.canvas.UPLOADS_PER_FRAME @@ -969,67 +998,73 @@ }, }); -Object.defineProperties(loaders.Resource.prototype, { - isJson: { - get() - { - warn('The isJson property is deprecated, please use `resource.type === Resource.TYPE.JSON`.'); +if (loaders.Loader) +{ + const Resource = loaders.Resource; + const Loader = loaders.Loader; - return this.type === loaders.Loader.Resource.TYPE.JSON; + Object.defineProperties(Resource.prototype, { + isJson: { + get() + { + warn('The isJson property is deprecated, please use `resource.type === Resource.TYPE.JSON`.'); + + return this.type === Resource.TYPE.JSON; + }, }, - }, - isXml: { - get() - { - warn('The isXml property is deprecated, please use `resource.type === Resource.TYPE.XML`.'); + isXml: { + get() + { + warn('The isXml property is deprecated, please use `resource.type === Resource.TYPE.XML`.'); - return this.type === loaders.Loader.Resource.TYPE.XML; + return this.type === Resource.TYPE.XML; + }, }, - }, - isImage: { - get() - { - warn('The isImage property is deprecated, please use `resource.type === Resource.TYPE.IMAGE`.'); + isImage: { + get() + { + warn('The isImage property is deprecated, please use `resource.type === Resource.TYPE.IMAGE`.'); - return this.type === loaders.Loader.Resource.TYPE.IMAGE; + return this.type === Resource.TYPE.IMAGE; + }, }, - }, - isAudio: { - get() - { - warn('The isAudio property is deprecated, please use `resource.type === Resource.TYPE.AUDIO`.'); + isAudio: { + get() + { + warn('The isAudio property is deprecated, please use `resource.type === Resource.TYPE.AUDIO`.'); - return this.type === loaders.Loader.Resource.TYPE.AUDIO; + return this.type === Resource.TYPE.AUDIO; + }, }, - }, - isVideo: { - get() - { - warn('The isVideo property is deprecated, please use `resource.type === Resource.TYPE.VIDEO`.'); + isVideo: { + get() + { + warn('The isVideo property is deprecated, please use `resource.type === Resource.TYPE.VIDEO`.'); - return this.type === loaders.Loader.Resource.TYPE.VIDEO; + return this.type === Resource.TYPE.VIDEO; + }, }, - }, -}); + }); -Object.defineProperties(loaders.Loader.prototype, { - before: { - get() - { - warn('The before() method is deprecated, please use pre().'); + Object.defineProperties(Loader.prototype, { + before: { + get() + { + warn('The before() method is deprecated, please use pre().'); - return this.pre; + return this.pre; + }, }, - }, - after: { - get() - { - warn('The after() method is deprecated, please use use().'); + after: { + get() + { + warn('The after() method is deprecated, please use use().'); - return this.use; + return this.use; + }, }, - }, -}); + }); +} /** * @name PIXI.interaction.interactiveTarget#defaultCursor diff --git a/src/extract/canvas/CanvasExtract.js b/src/extract/canvas/CanvasExtract.js index 941ca05..3ac5aee 100644 --- a/src/extract/canvas/CanvasExtract.js +++ b/src/extract/canvas/CanvasExtract.js @@ -8,7 +8,7 @@ * An instance of this class is automatically created by default, and can be found at renderer.plugins.extract * * @class - * @memberof PIXI + * @memberof PIXI.extract */ export default class CanvasExtract { @@ -21,9 +21,9 @@ /** * Collection of methods for extracting data (image, pixels, etc.) from a display object or render texture * - * @member {PIXI.CanvasExtract} extract + * @member {PIXI.extract.CanvasExtract} extract * @memberof PIXI.CanvasRenderer# - * @see PIXI.CanvasExtract + * @see PIXI.extract.CanvasExtract */ renderer.extract = this; } diff --git a/src/extract/index.js b/src/extract/index.js index 0f2170d..e9478c1 100644 --- a/src/extract/index.js +++ b/src/extract/index.js @@ -1,2 +1,22 @@ +/** + * This namespace provides renderer-specific plugins for exporting content from a renderer. + * For instance, these plugins can be used for saving an Image, Canvas element or for exporting the raw image data (pixels). + * + * Do not instantiate these plugins directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. + * @example + * // Create a new app (will auto-add extract plugin to renderer) + * const app = new PIXI.Application(); + * + * // Draw a red circle + * const graphics = new PIXI.Graphics() + * .beginFill(0xFF0000) + * .drawCircle(0, 0, 50); + * + * // Render the graphics as an HTMLImageElement + * const image = app.renderer.plugins.extract.image(graphics); + * document.body.appendChild(image); + * @namespace PIXI.extract + */ export { default as webgl } from './webgl/WebGLExtract'; export { default as canvas } from './canvas/CanvasExtract'; diff --git a/src/extract/webgl/WebGLExtract.js b/src/extract/webgl/WebGLExtract.js index 60228bb..1c3dd30 100644 --- a/src/extract/webgl/WebGLExtract.js +++ b/src/extract/webgl/WebGLExtract.js @@ -9,7 +9,7 @@ * An instance of this class is automatically created by default, and can be found at renderer.plugins.extract * * @class - * @memberof PIXI + * @memberof PIXI.extract */ export default class WebGLExtract { @@ -22,9 +22,9 @@ /** * Collection of methods for extracting data (image, pixels, etc.) from a display object or render texture * - * @member {PIXI.WebGLExtract} extract + * @member {PIXI.extract.WebGLExtract} extract * @memberof PIXI.WebGLRenderer# - * @see PIXI.WebGLExtract + * @see PIXI.extract.WebGLExtract */ renderer.extract = this; } diff --git a/src/extras/AnimatedSprite.js b/src/extras/AnimatedSprite.js index 13b804f..5297778 100644 --- a/src/extras/AnimatedSprite.js +++ b/src/extras/AnimatedSprite.js @@ -137,7 +137,7 @@ this.playing = true; if (this._autoUpdate) { - core.ticker.shared.add(this.update, this); + core.ticker.shared.add(this.update, this, core.UPDATE_PRIORITY.HIGH); } } diff --git a/src/extras/TextureTransform.js b/src/extras/TextureTransform.js index 78e6e40..16536d2 100644 --- a/src/extras/TextureTransform.js +++ b/src/extras/TextureTransform.js @@ -65,8 +65,36 @@ } /** + * Multiplies uvs array to transform + * @param {Float32Array} uvs mesh uvs + * @param {Float32Array} [out=uvs] output + * @returns {Float32Array} output + */ + multiplyUvs(uvs, out) + { + if (out === undefined) + { + out = uvs; + } + + const mat = this.mapCoord; + + for (let i = 0; i < uvs.length; i += 2) + { + const x = uvs[i]; + const y = uvs[i + 1]; + + out[i] = (x * mat.a) + (y * mat.c) + mat.tx; + out[i + 1] = (x * mat.b) + (y * mat.d) + mat.ty; + } + + return out; + } + + /** * updates matrices if texture was changed * @param {boolean} forceUpdate if true, matrices will be updated any case + * @returns {boolean} whether or not it was updated */ update(forceUpdate) { @@ -74,13 +102,13 @@ if (!tex || !tex.valid) { - return; + return false; } if (!forceUpdate && this._lastTextureID === tex._updateID) { - return; + return false; } this._lastTextureID = tex._updateID; @@ -110,5 +138,7 @@ frame[3] = (tex._frame.y + tex._frame.height - margin + offset) / texBase.height; this.uClampOffset[0] = offset / texBase.realWidth; this.uClampOffset[1] = offset / texBase.realHeight; + + return true; } } diff --git a/src/extras/TilingSprite.js b/src/extras/TilingSprite.js index 1f6014f..a3f027f 100644 --- a/src/extras/TilingSprite.js +++ b/src/extras/TilingSprite.js @@ -180,9 +180,9 @@ const transform = this.worldTransform; const resolution = renderer.resolution; const baseTexture = texture.baseTexture; - const baseTextureResolution = texture.baseTexture.resolution; - const modX = (this.tilePosition.x / this.tileScale.x) % texture._frame.width; - const modY = (this.tilePosition.y / this.tileScale.y) % texture._frame.height; + const baseTextureResolution = baseTexture.resolution; + const modX = ((this.tilePosition.x / this.tileScale.x) % texture._frame.width) * baseTextureResolution; + const modY = ((this.tilePosition.y / this.tileScale.y) % texture._frame.height) * baseTextureResolution; // create a nice shiny pattern! // TODO this needs to be refreshed if texture changes.. diff --git a/src/extras/cacheAsBitmap.js b/src/extras/cacheAsBitmap.js index 5dd5640..aea06a8 100644 --- a/src/extras/cacheAsBitmap.js +++ b/src/extras/cacheAsBitmap.js @@ -227,7 +227,16 @@ this.transform._parentID = -1; // restore the transform of the cached sprite to avoid the nasty flicker.. - this.updateTransform(); + if (!this.parent) + { + this.parent = renderer._tempDisplayObjectParent; + this.updateTransform(); + this.parent = null; + } + else + { + this.updateTransform(); + } // map the hit test.. this.containsPoint = cachedSprite.containsPoint.bind(cachedSprite); @@ -314,7 +323,17 @@ cachedSprite._bounds = this._bounds; cachedSprite.alpha = cacheAlpha; - this.updateTransform(); + if (!this.parent) + { + this.parent = renderer._tempDisplayObjectParent; + this.updateTransform(); + this.parent = null; + } + else + { + this.updateTransform(); + } + this.updateTransform = this.displayObjectUpdateTransform; this._cacheData.sprite = cachedSprite; @@ -358,9 +377,12 @@ * Destroys the cached object. * * @private + * @param {object|boolean} [options] - Options parameter. A boolean will act as if all options + * have been set to that value. + * Used when destroying containers, see the Container.destroy method. */ -DisplayObject.prototype._cacheAsBitmapDestroy = function _cacheAsBitmapDestroy() +DisplayObject.prototype._cacheAsBitmapDestroy = function _cacheAsBitmapDestroy(options) { this.cacheAsBitmap = false; - this.destroy(); + this.destroy(options); }; diff --git a/src/extras/index.js b/src/extras/index.js index bbd8bbf..a34d7ec 100644 --- a/src/extras/index.js +++ b/src/extras/index.js @@ -1,7 +1,7 @@ /** + * Additional PIXI DisplayObjects for animation, tiling and bitmap text. * @namespace PIXI.extras */ -export { default as TextureTransform } from './TextureTransform'; export { default as AnimatedSprite } from './AnimatedSprite'; export { default as TilingSprite } from './TilingSprite'; export { default as TilingSpriteRenderer } from './webgl/TilingSpriteRenderer'; diff --git a/src/extras/webgl/TilingSpriteRenderer.js b/src/extras/webgl/TilingSpriteRenderer.js index aeec172..eee3b5c 100644 --- a/src/extras/webgl/TilingSpriteRenderer.js +++ b/src/extras/webgl/TilingSpriteRenderer.js @@ -11,7 +11,7 @@ * WebGL renderer plugin for tiling sprites * * @class - * @memberof PIXI + * @memberof PIXI.extras * @extends PIXI.ObjectRenderer */ export default class TilingSpriteRenderer extends core.ObjectRenderer @@ -118,7 +118,7 @@ tempMat.invert(); if (isSimple) { - tempMat.append(uv.mapCoord); + tempMat.prepend(uv.mapCoord); } else { diff --git a/src/filters/displacement/DisplacementFilter.js b/src/filters/displacement/DisplacementFilter.js index d5355c3..b5d030c 100644 --- a/src/filters/displacement/DisplacementFilter.js +++ b/src/filters/displacement/DisplacementFilter.js @@ -35,7 +35,7 @@ this.maskSprite = sprite; this.maskMatrix = maskMatrix; - this.uniforms.mapSampler = sprite.texture; + this.uniforms.mapSampler = sprite._texture; this.uniforms.filterMatrix = maskMatrix; this.uniforms.scale = { x: 1, y: 1 }; diff --git a/src/filters/index.js b/src/filters/index.js index f3e8862..7ceb50a 100644 --- a/src/filters/index.js +++ b/src/filters/index.js @@ -1,4 +1,21 @@ /** + * This namespace contains WebGL-only display filters that can be applied + * to DisplayObjects using the {@link PIXI.DisplayObject#filters filters} property. + * @example + * // Create a new application + * const app = new PIXI.Application(); + * + * // Draw a green rectangle + * const rect = new PIXI.Graphics() + * .beginFill(0x00ff00) + * .drawRect(40, 40, 200, 200); + * + * // Add a blur filter + * rect.filters = [new PIXI.filters.BlurFilter()]; + * + * // Display rectangle + * app.stage.addChild(rect); + * document.body.appendChild(app.view); * @namespace PIXI.filters */ export { default as FXAAFilter } from './fxaa/FXAAFilter'; diff --git a/src/index.js b/src/index.js index c553427..77da490 100644 --- a/src/index.js +++ b/src/index.js @@ -20,6 +20,14 @@ import { utils } from './core'; utils.mixins.performMixins(); +/** + * Alias for {@link PIXI.loaders.shared}. + * @name loader + * @memberof PIXI + * @type {PIXI.loader.Loader} + */ +const loader = loaders.shared || null; + export { accessibility, extract, @@ -30,18 +38,8 @@ mesh, particles, prepare, + loader, }; -/** - * A premade instance of the loader that can be used to load resources. - * - * @name loader - * @memberof PIXI - * @property {PIXI.loaders.Loader} - */ -const loader = loaders && loaders.Loader ? new loaders.Loader() : null; // check is there in case user excludes loader lib - -export { loader }; - // Always export pixi globally. global.PIXI = exports; // eslint-disable-line diff --git a/src/interaction/InteractionData.js b/src/interaction/InteractionData.js index 02d53a3..449f8cc 100644 --- a/src/interaction/InteractionData.js +++ b/src/interaction/InteractionData.js @@ -30,7 +30,10 @@ /** * When passed to an event handler, this will be the original DOM Event that was captured * - * @member {Event} + * @see https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent + * @see https://developer.mozilla.org/en-US/docs/Web/API/TouchEvent + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent + * @member {MouseEvent|TouchEvent|PointerEvent} */ this.originalEvent = null; } diff --git a/src/interaction/InteractionManager.js b/src/interaction/InteractionManager.js index 8baf13a..2d75c7c 100644 --- a/src/interaction/InteractionManager.js +++ b/src/interaction/InteractionManager.js @@ -13,6 +13,14 @@ const MOUSE_POINTER_ID = 'MOUSE'; +// helpers for hitTest() - only used inside hitTest() +const hitTestEvent = { + target: null, + data: { + global: null, + }, +}; + /** * The interaction manager deals with mouse, touch and pointer events. Any DisplayObject can be interactive * if its interactive parameter is set to true @@ -245,67 +253,60 @@ this.setTargetElement(this.renderer.view, this.renderer.resolution); /** - * Fired when a pointer device button (usually a mouse button) is pressed on the display + * Fired when a pointer device button (usually a mouse left-button) is pressed on the display * object. * - * @event mousedown - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mousedown + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device secondary button (usually a mouse right-button) is pressed * on the display object. * - * @event rightdown - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightdown + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** - * Fired when a pointer device button (usually a mouse button) is released over the display + * Fired when a pointer device button (usually a mouse left-button) is released over the display * object. * - * @event mouseup - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseup + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device secondary button (usually a mouse right-button) is released * over the display object. * - * @event rightup - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightup + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** - * Fired when a pointer device button (usually a mouse button) is pressed and released on + * Fired when a pointer device button (usually a mouse left-button) is pressed and released on * the display object. * - * @event click - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#click + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device secondary button (usually a mouse right-button) is pressed * and released on the display object. * - * @event rightclick - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightclick + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** - * Fired when a pointer device button (usually a mouse button) is released outside the + * Fired when a pointer device button (usually a mouse left-button) is released outside the * display object that initially registered a * [mousedown]{@link PIXI.interaction.InteractionManager#event:mousedown}. * - * @event mouseupoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** @@ -313,146 +314,362 @@ * outside the display object that initially registered a * [rightdown]{@link PIXI.interaction.InteractionManager#event:rightdown}. * - * @event rightupoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device (usually a mouse) is moved while over the display object * - * @event mousemove - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mousemove + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device (usually a mouse) is moved onto the display object * - * @event mouseover - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseover + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device (usually a mouse) is moved off the display object * - * @event mouseout - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseout + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is pressed on the display object. * - * @event pointerdown - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerdown + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is released over the display object. * - * @event pointerup - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerup + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when the operating system cancels a pointer event * - * @event pointercancel - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointercancel + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is pressed and released on the display object. * - * @event pointertap - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointertap + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is released outside the display object that initially * registered a [pointerdown]{@link PIXI.interaction.InteractionManager#event:pointerdown}. * - * @event pointerupoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device is moved while over the display object * - * @event pointermove - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointermove + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device is moved onto the display object * - * @event pointerover - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerover + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device is moved off the display object * - * @event pointerout - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerout + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is placed on the display object. * - * @event touchstart - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchstart + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is removed from the display object. * - * @event touchend - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchend + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when the operating system cancels a touch * - * @event touchcancel - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchcancel + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is placed and removed from the display object. * - * @event tap - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#tap + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is removed outside of the display object that initially * registered a [touchstart]{@link PIXI.interaction.InteractionManager#event:touchstart}. * - * @event touchendoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchendoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is moved along the display object. * - * @event touchmove - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchmove + * @param {PIXI.interaction.InteractionData} event - Interaction data */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is pressed on the display. + * object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mousedown + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is pressed + * on the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightdown + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is released over the display + * object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseup + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is released + * over the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightup + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is pressed and released on + * the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#click + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is pressed + * and released on the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightclick + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is released outside the + * display object that initially registered a + * [mousedown]{@link PIXI.DisplayObject#event:mousedown}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is released + * outside the display object that initially registered a + * [rightdown]{@link PIXI.DisplayObject#event:rightdown}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device (usually a mouse) is moved while over the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mousemove + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device (usually a mouse) is moved onto the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseover + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device (usually a mouse) is moved off the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseout + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is pressed on the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerdown + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is released over the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerup + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when the operating system cancels a pointer event. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointercancel + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is pressed and released on the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointertap + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is released outside the display object that initially + * registered a [pointerdown]{@link PIXI.DisplayObject#event:pointerdown}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device is moved while over the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointermove + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device is moved onto the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerover + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device is moved off the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerout + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is placed on the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchstart + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is removed from the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchend + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when the operating system cancels a touch. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchcancel + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is placed and removed from the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#tap + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is removed outside of the display object that initially + * registered a [touchstart]{@link PIXI.DisplayObject#event:touchstart}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchendoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is moved along the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchmove + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + } + + /** + * Hit tests a point against the display tree, returning the first interactive object that is hit. + * + * @param {PIXI.Point} globalPoint - A point to hit test with, in global space. + * @param {PIXI.Container} [root] - The root display object to start from. If omitted, defaults + * to the last rendered root of the associated renderer. + * @return {PIXI.DisplayObject} The hit display object, if any. + */ + hitTest(globalPoint, root) + { + // clear the target for our hit test + hitTestEvent.target = null; + // assign the global point + hitTestEvent.data.global = globalPoint; + // ensure safety of the root + if (!root) + { + root = this.renderer._lastObjectRendered; + } + // run the hit test + this.processInteractive(hitTestEvent, root, null, true); + // return our found object - it'll be null if we didn't hit anything + + return hitTestEvent.target; } /** @@ -487,7 +704,7 @@ return; } - core.ticker.shared.add(this.update, this); + core.ticker.shared.add(this.update, this, core.UPDATE_PRIORITY.INTERACTION); if (window.navigator.msPointerEnabled) { @@ -804,8 +1021,6 @@ } } - let keepHitTestingAfterChildren = hitTest; - // ** FREE TIP **! If an object is not interactive or has no buttons in it // (such as a game scene!) set interactiveChildren to false for that displayObject. // This will allow pixi to completely ignore and bypass checking the displayObjects children. @@ -818,7 +1033,9 @@ const child = children[i]; // time to get recursive.. if this function will return if something is hit.. - if (this.processInteractive(interactionEvent, child, func, hitTest, interactiveParent)) + const childHit = this.processInteractive(interactionEvent, child, func, hitTest, interactiveParent); + + if (childHit) { // its a good idea to check if a child has lost its parent. // this means it has been removed whilst looping so its best @@ -827,8 +1044,6 @@ continue; } - hit = true; - // we no longer need to hit test any more objects in this container as we we // now know the parent has been hit interactiveParent = false; @@ -838,36 +1053,41 @@ // This means we no longer need to hit test anything else. We still need to run // through all objects, but we don't need to perform any hit tests. - keepHitTestingAfterChildren = false; - - if (child.interactive) + if (childHit) { - hitTest = false; + if (interactionEvent.target) + { + hitTest = false; + } + hit = true; } - - // we can break now as we have hit an object. } } } - hitTest = keepHitTestingAfterChildren; - // no point running this if the item is not interactive or does not have an interactive parent. if (interactive) { // if we are hit testing (as in we have no hit any objects yet) // We also don't need to worry about hit testing if once of the displayObjects children - // has already been hit! - if (hitTest && !hit) + // has already been hit - but only if it was interactive, otherwise we need to keep + // looking for an interactive child, just in case we hit one + if (hitTest && !interactionEvent.target) { if (displayObject.hitArea) { displayObject.worldTransform.applyInverse(point, this._tempPoint); - hit = displayObject.hitArea.contains(this._tempPoint.x, this._tempPoint.y); + if (displayObject.hitArea.contains(this._tempPoint.x, this._tempPoint.y)) + { + hit = true; + } } else if (displayObject.containsPoint) { - hit = displayObject.containsPoint(point); + if (displayObject.containsPoint(point)) + { + hit = true; + } } } @@ -878,7 +1098,10 @@ interactionEvent.target = displayObject; } - func(interactionEvent, displayObject, hit); + if (func) + { + func(interactionEvent, displayObject, !!hit); + } } } @@ -914,7 +1137,7 @@ { const event = events[i]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -998,7 +1221,7 @@ { const event = events[i]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1098,7 +1321,7 @@ const test = isRightButton ? flags.RIGHT_DOWN : flags.LEFT_DOWN; - const isDown = trackingData !== undefined && (trackingData.flags | test); + const isDown = trackingData !== undefined && (trackingData.flags & test); if (hit) { @@ -1118,11 +1341,11 @@ { if (isRightButton) { - trackingData.rightDown = hit; + trackingData.rightDown = false; } else { - trackingData.leftDown = hit; + trackingData.leftDown = false; } } } @@ -1180,7 +1403,7 @@ { const event = events[i]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1255,7 +1478,7 @@ this.setCursorMode(null); } - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1268,6 +1491,12 @@ { this.emit('mouseout', interactionEvent); } + else + { + // we can get touchleave events after touchend, so we want to make sure we don't + // introduce memory leaks + this.releaseInteractionDataForPointerId(interactionData.identifier); + } } /** @@ -1344,7 +1573,7 @@ // Only mouse and pointer can call onPointerOver, so events will always be length 1 const event = events[0]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1366,12 +1595,14 @@ * Get InteractionData for a given pointerId. Store that data as well * * @private - * @param {number} pointerId - Identifier from a pointer event + * @param {PointerEvent} event - Normalized pointer event, output from normalizeToPointerData * @return {InteractionData} - Interaction data for the given pointer identifier */ - getInteractionDataForPointerId(pointerId) + getInteractionDataForPointerId(event) { - if (pointerId === MOUSE_POINTER_ID) + const pointerId = event.pointerId; + + if (pointerId === MOUSE_POINTER_ID || event.pointerType === 'mouse') { return this.mouse; } diff --git a/src/interaction/InteractionTrackingData.js b/src/interaction/InteractionTrackingData.js index 6a1f69e..8493b63 100644 --- a/src/interaction/InteractionTrackingData.js +++ b/src/interaction/InteractionTrackingData.js @@ -35,8 +35,10 @@ } /** + * Unique pointer id of the event + * * @readonly - * @type {number} Unique pointer id of the event + * @member {number} */ get pointerId() { diff --git a/src/interaction/index.js b/src/interaction/index.js index 56c5b09..72f7d71 100644 --- a/src/interaction/index.js +++ b/src/interaction/index.js @@ -1,4 +1,8 @@ /** + * This namespace contains a renderer plugin for handling mouse, pointer, and touch events. + * + * Do not instantiate this plugin directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. * @namespace PIXI.interaction */ export { default as InteractionData } from './InteractionData'; diff --git a/src/interaction/interactiveTarget.js b/src/interaction/interactiveTarget.js index d252fba..cebac40 100644 --- a/src/interaction/interactiveTarget.js +++ b/src/interaction/interactiveTarget.js @@ -2,7 +2,7 @@ * Default property values of interactive objects * Used by {@link PIXI.interaction.InteractionManager} to automatically give all DisplayObjects these properties * - * @mixin + * @private * @name interactiveTarget * @memberof PIXI.interaction * @example @@ -14,18 +14,28 @@ * ); */ export default { + /** - * Determines if the displayObject be clicked/touched + * Enable interaction events for the DisplayObject. Touch, pointer and mouse + * events will not be emitted unless `interactive` is set to `true`. * - * @inner {boolean} + * @example + * const sprite = new PIXI.Sprite(texture); + * sprite.interactive = true; + * sprite.on('tap', (event) => { + * //handle event + * }); + * @member {boolean} + * @memberof PIXI.DisplayObject# */ interactive: false, /** * Determines if the children to the displayObject can be clicked/touched - * Setting this to false allows pixi to bypass a recursive hitTest function + * Setting this to false allows pixi to bypass a recursive `hitTest` function * - * @inner {boolean} + * @member {boolean} + * @memberof PIXI.Container# */ interactiveChildren: true, @@ -33,7 +43,12 @@ * Interaction shape. Children will be hit first, then this shape will be checked. * Setting this will cause this shape to be checked in hit tests rather than the displayObject's bounds. * - * @inner {PIXI.Rectangle|PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.RoundedRectangle} + * @example + * const sprite = new PIXI.Sprite(texture); + * sprite.interactive = true; + * sprite.hitArea = new PIXI.Rectangle(0, 0, 100, 100); + * @member {PIXI.Rectangle|PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.RoundedRectangle} + * @memberof PIXI.DisplayObject# */ hitArea: null, @@ -41,8 +56,12 @@ * If enabled, the mouse cursor use the pointer behavior when hovered over the displayObject if it is interactive * Setting this changes the 'cursor' property to `'pointer'`. * + * @example + * const sprite = new PIXI.Sprite(texture); + * sprite.interactive = true; + * sprite.buttonMode = true; * @member {boolean} - * @memberof PIXI.interaction.interactiveTarget# + * @memberof PIXI.DisplayObject# */ get buttonMode() { @@ -64,9 +83,14 @@ * This defines what cursor mode is used when the mouse cursor * is hovered over the displayObject. * + * @example + * const sprite = new PIXI.Sprite(texture); + * sprite.interactive = true; + * sprite.cursor = 'wait'; * @see https://developer.mozilla.org/en/docs/Web/CSS/cursor * - * @inner {string} + * @member {string} + * @memberof PIXI.DisplayObject# */ cursor: null, @@ -74,7 +98,7 @@ * Internal set of all active pointers, by identifier * * @member {Map} - * @memberof PIXI.interaction.interactiveTarget# + * @memberof PIXI.DisplayObject# * @private */ get trackedPointers() @@ -87,7 +111,8 @@ /** * Map of all tracked pointers, by identifier. Use trackedPointers to access. * - * @private {Map} + * @private + * @type {Map} */ _trackedPointers: undefined, }; diff --git a/src/loaders/index.js b/src/loaders/index.js index 5189793..daa1fc4 100644 --- a/src/loaders/index.js +++ b/src/loaders/index.js @@ -1,9 +1,22 @@ +import Application from '../core/Application'; +import Loader from './loader'; + /** + * This namespace contains APIs which extends the {@link https://github.com/englercj/resource-loader resource-loader} module + * for loading assets, data, and other resources dynamically. + * @example + * const loader = new PIXI.loaders.Loader(); + * loader.add('bunny', 'data/bunny.png') + * .add('spaceship', 'assets/spritesheet.json'); + * loader.load((loader, resources) => { + * // resources.bunny + * // resources.spaceship + * }); * @namespace PIXI.loaders */ -export { default as Loader } from './loader'; +export { Loader }; export { default as bitmapFontParser, parse as parseBitmapFontData } from './bitmapFontParser'; -export { default as spritesheetParser } from './spritesheetParser'; +export { default as spritesheetParser, getResourcePath } from './spritesheetParser'; export { default as textureParser } from './textureParser'; /** @@ -13,3 +26,55 @@ * @memberof PIXI.loaders */ export { Resource } from 'resource-loader'; + +/** + * A premade instance of the loader that can be used to load resources. + * @name shared + * @memberof PIXI.loaders + * @type {PIXI.loaders.Loader} + */ +const shared = new Loader(); + +shared.destroy = () => +{ + // protect destroying shared loader +}; + +export { shared }; + +// Mixin the loader construction +const AppPrototype = Application.prototype; + +AppPrototype._loader = null; + +/** + * Loader instance to help with asset loading. + * @name PIXI.Application#loader + * @type {PIXI.loaders.Loader} + */ +Object.defineProperty(AppPrototype, 'loader', { + get() + { + if (!this._loader) + { + const sharedLoader = this._options.sharedLoader; + + this._loader = sharedLoader ? shared : new Loader(); + } + + return this._loader; + }, +}); + +// Override the destroy function +// making sure to destroy the current Loader +AppPrototype._parentDestroy = AppPrototype.destroy; +AppPrototype.destroy = function destroy() +{ + if (this._loader) + { + this._loader.destroy(); + this._loader = null; + } + this._parentDestroy(); +}; diff --git a/src/loaders/loader.js b/src/loaders/loader.js index 767a411..59b8000 100644 --- a/src/loaders/loader.js +++ b/src/loaders/loader.js @@ -90,6 +90,15 @@ { Loader._pixiMiddleware.push(fn); } + + /** + * Destroy the loader, removes references. + */ + destroy() + { + this.removeAllListeners(); + this.reset(); + } } // Copy EE3 prototype (mixin) diff --git a/src/loaders/spritesheetParser.js b/src/loaders/spritesheetParser.js index e57dfb2..eda9584 100644 --- a/src/loaders/spritesheetParser.js +++ b/src/loaders/spritesheetParser.js @@ -1,12 +1,11 @@ import { Resource } from 'resource-loader'; -import path from 'path'; +import url from 'url'; import { Spritesheet } from '../core'; export default function () { return function spritesheetParser(resource, next) { - let resourcePath; const imageResourceName = `${resource.name}_image`; // skip if no data, its not json, it isn't spritesheet data, or the image resource already exists @@ -28,15 +27,7 @@ parentResource: resource, }; - // Prepend url path unless the resource image is a data url - if (resource.isDataUrl) - { - resourcePath = resource.data.meta.image; - } - else - { - resourcePath = `${path.dirname(resource.url.replace(this.baseUrl, ''))}/${resource.data.meta.image}`; - } + const resourcePath = getResourcePath(resource, this.baseUrl); // load the image for this sheet this.add(imageResourceName, resourcePath, loadOptions, function onImageLoad(res) @@ -56,3 +47,14 @@ }); }; } + +export function getResourcePath(resource, baseUrl) +{ + // Prepend url path unless the resource image is a data url + if (resource.isDataUrl) + { + return resource.data.meta.image; + } + + return url.resolve(resource.url.replace(baseUrl, ''), resource.data.meta.image); +} diff --git a/src/mesh/canvas/CanvasMeshRenderer.js b/src/mesh/canvas/CanvasMeshRenderer.js index 297205f..f7899e1 100644 --- a/src/mesh/canvas/CanvasMeshRenderer.js +++ b/src/mesh/canvas/CanvasMeshRenderer.js @@ -134,12 +134,33 @@ const textureWidth = base.width; const textureHeight = base.height; - const u0 = uvs[index0] * base.width; - const u1 = uvs[index1] * base.width; - const u2 = uvs[index2] * base.width; - const v0 = uvs[index0 + 1] * base.height; - const v1 = uvs[index1 + 1] * base.height; - const v2 = uvs[index2 + 1] * base.height; + let u0; + let u1; + let u2; + let v0; + let v1; + let v2; + + if (mesh.uploadUvTransform) + { + const ut = mesh._uvTransform.mapCoord; + + u0 = ((uvs[index0] * ut.a) + (uvs[index0 + 1] * ut.c) + ut.tx) * base.width; + u1 = ((uvs[index1] * ut.a) + (uvs[index1 + 1] * ut.c) + ut.tx) * base.width; + u2 = ((uvs[index2] * ut.a) + (uvs[index2 + 1] * ut.c) + ut.tx) * base.width; + v0 = ((uvs[index0] * ut.b) + (uvs[index0 + 1] * ut.d) + ut.ty) * base.height; + v1 = ((uvs[index1] * ut.b) + (uvs[index1 + 1] * ut.d) + ut.ty) * base.height; + v2 = ((uvs[index2] * ut.b) + (uvs[index2 + 1] * ut.d) + ut.ty) * base.height; + } + else + { + u0 = uvs[index0] * base.width; + u1 = uvs[index1] * base.width; + u2 = uvs[index2] * base.width; + v0 = uvs[index0 + 1] * base.height; + v1 = uvs[index1 + 1] * base.height; + v2 = uvs[index2 + 1] * base.height; + } let x0 = vertices[index0]; let x1 = vertices[index1]; diff --git a/src/mesh/webgl/MeshRenderer.js b/src/mesh/webgl/MeshRenderer.js index 78d24c3..ab99fcd 100644 --- a/src/mesh/webgl/MeshRenderer.js +++ b/src/mesh/webgl/MeshRenderer.js @@ -2,6 +2,8 @@ const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; +const matrixIdentity = core.Matrix.IDENTITY; + /** * WebGL renderer plugin for tiling sprites * @@ -64,6 +66,7 @@ mesh.geometry.glVertexArrayObjects[this.CONTEXT_UID].draw(mesh.drawMode, mesh.size, mesh.start, mesh.geometry.instanceCount); } + /** * draws mesh * @param {PIXI.mesh.RawMesh} mesh mesh instance diff --git a/src/mesh/webgl/mesh.vert b/src/mesh/webgl/mesh.vert index a337aef..acc096c 100644 --- a/src/mesh/webgl/mesh.vert +++ b/src/mesh/webgl/mesh.vert @@ -1,8 +1,9 @@ attribute vec2 aVertexPosition; attribute vec2 aTextureCoord; -uniform mat3 translationMatrix; uniform mat3 projectionMatrix; +uniform mat3 translationMatrix; +uniform mat3 uTransform; varying vec2 vTextureCoord; @@ -10,5 +11,5 @@ { gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - vTextureCoord = aTextureCoord; + vTextureCoord = (uTransform * vec3(aTextureCoord, 1.0)).xy; } diff --git a/src/particles/ParticleContainer.js b/src/particles/ParticleContainer.js index 67796cf..63f7791 100644 --- a/src/particles/ParticleContainer.js +++ b/src/particles/ParticleContainer.js @@ -313,10 +313,10 @@ frame.y * resolution, frame.width * resolution, frame.height * resolution, - positionX * resolution, - positionY * resolution, - finalWidth * resolution, - finalHeight * resolution + positionX * renderer.resolution, + positionY * renderer.resolution, + finalWidth * renderer.resolution, + finalHeight * renderer.resolution ); } } diff --git a/src/prepare/BasePrepare.js b/src/prepare/BasePrepare.js index 09eb5e5..e45c4dc 100644 --- a/src/prepare/BasePrepare.js +++ b/src/prepare/BasePrepare.js @@ -18,6 +18,18 @@ * basic queuing functionality and is extended by {@link PIXI.prepare.WebGLPrepare} and {@link PIXI.prepare.CanvasPrepare} * to provide preparation capabilities specific to their respective renderers. * + * @example + * // Create a sprite + * const sprite = new PIXI.Sprite.fromImage('something.png'); + * + * // Load object into GPU + * app.renderer.plugins.prepare.upload(sprite, () => { + * + * //Texture(s) has been uploaded to GPU + * app.stage.addChild(sprite); + * + * }) + * * @abstract * @class * @memberof PIXI.prepare @@ -100,8 +112,16 @@ this.prepareItems(); }; - this.register(findText, drawText); - this.register(findTextStyle, calculateTextStyle); + // hooks to find the correct texture + this.registerFindHook(findText); + this.registerFindHook(findTextStyle); + this.registerFindHook(findMultipleBaseTextures); + this.registerFindHook(findBaseTexture); + this.registerFindHook(findTexture); + + // upload hooks + this.registerUploadHook(drawText); + this.registerUploadHook(calculateTextStyle); } /** @@ -138,7 +158,7 @@ if (!this.ticking) { this.ticking = true; - SharedTicker.addOnce(this.tick, this); + SharedTicker.addOnce(this.tick, this, core.UPDATE_PRIORITY.UTILITY); } } else if (done) @@ -172,13 +192,16 @@ const item = this.queue[0]; let uploaded = false; - for (let i = 0, len = this.uploadHooks.length; i < len; i++) + if (item && !item._destroyed) { - if (this.uploadHooks[i](this.uploadHookHelper, item)) + for (let i = 0, len = this.uploadHooks.length; i < len; i++) { - this.queue.shift(); - uploaded = true; - break; + if (this.uploadHooks[i](this.uploadHookHelper, item)) + { + this.queue.shift(); + uploaded = true; + break; + } } } @@ -205,26 +228,36 @@ else { // if we are not finished, on the next rAF do this again - SharedTicker.addOnce(this.tick, this); + SharedTicker.addOnce(this.tick, this, core.UPDATE_PRIORITY.UTILITY); } } /** - * Adds hooks for finding and uploading items. + * Adds hooks for finding items. * - * @param {Function} [addHook] - Function call that takes two parameters: `item:*, queue:Array` - function must return `true` if it was able to add item to the queue. - * @param {Function} [uploadHook] - Function call that takes two parameters: `prepare:CanvasPrepare, item:*` and - * function must return `true` if it was able to handle upload of item. - * @return {PIXI.CanvasPrepare} Instance of plugin for chaining. + * @param {Function} addHook - Function call that takes two parameters: `item:*, queue:Array` + * function must return `true` if it was able to add item to the queue. + * @return {PIXI.BasePrepare} Instance of plugin for chaining. */ - register(addHook, uploadHook) + registerFindHook(addHook) { if (addHook) { this.addHooks.push(addHook); } + return this; + } + + /** + * Adds hooks for uploading items. + * + * @param {Function} uploadHook - Function call that takes two parameters: `prepare:CanvasPrepare, item:*` and + * function must return `true` if it was able to handle upload of item. + * @return {PIXI.BasePrepare} Instance of plugin for chaining. + */ + registerUploadHook(uploadHook) + { if (uploadHook) { this.uploadHooks.push(uploadHook); @@ -287,6 +320,88 @@ } /** + * Built-in hook to find multiple textures from objects like AnimatedSprites. + * + * @private + * @param {PIXI.DisplayObject} item - Display object to check + * @param {Array<*>} queue - Collection of items to upload + * @return {boolean} if a PIXI.Texture object was found. + */ +function findMultipleBaseTextures(item, queue) +{ + let result = false; + + // Objects with mutliple textures + if (item && item._textures && item._textures.length) + { + for (let i = 0; i < item._textures.length; i++) + { + if (item._textures[i] instanceof core.Texture) + { + const baseTexture = item._textures[i].baseTexture; + + if (queue.indexOf(baseTexture) === -1) + { + queue.push(baseTexture); + result = true; + } + } + } + } + + return result; +} + +/** + * Built-in hook to find BaseTextures from Sprites. + * + * @private + * @param {PIXI.DisplayObject} item - Display object to check + * @param {Array<*>} queue - Collection of items to upload + * @return {boolean} if a PIXI.Texture object was found. + */ +function findBaseTexture(item, queue) +{ + // Objects with textures, like Sprites/Text + if (item instanceof core.BaseTexture) + { + if (queue.indexOf(item) === -1) + { + queue.push(item); + } + + return true; + } + + return false; +} + +/** + * Built-in hook to find textures from objects. + * + * @private + * @param {PIXI.DisplayObject} item - Display object to check + * @param {Array<*>} queue - Collection of items to upload + * @return {boolean} if a PIXI.Texture object was found. + */ +function findTexture(item, queue) +{ + if (item._texture && item._texture instanceof core.Texture) + { + const texture = item._texture.baseTexture; + + if (queue.indexOf(texture) === -1) + { + queue.push(texture); + } + + return true; + } + + return false; +} + +/** * Built-in hook to draw PIXI.Text to its texture. * * @private diff --git a/src/prepare/canvas/CanvasPrepare.js b/src/prepare/canvas/CanvasPrepare.js index 0224bb5..09c207a 100644 --- a/src/prepare/canvas/CanvasPrepare.js +++ b/src/prepare/canvas/CanvasPrepare.js @@ -43,7 +43,7 @@ this.ctx = this.canvas.getContext('2d'); // Add textures to upload - this.register(findBaseTextures, uploadBaseTextures); + this.registerUploadHook(uploadBaseTextures); } /** @@ -89,39 +89,4 @@ return false; } -/** - * Built-in hook to find textures from Sprites. - * - * @private - * @param {PIXI.DisplayObject} item -Display object to check - * @param {Array<*>} queue - Collection of items to upload - * @return {boolean} if a PIXI.Texture object was found. - */ -function findBaseTextures(item, queue) -{ - // Objects with textures, like Sprites/Text - if (item instanceof core.BaseTexture) - { - if (queue.indexOf(item) === -1) - { - queue.push(item); - } - - return true; - } - else if (item._texture && item._texture instanceof core.Texture) - { - const texture = item._texture.baseTexture; - - if (queue.indexOf(texture) === -1) - { - queue.push(texture); - } - - return true; - } - - return false; -} - core.CanvasRenderer.registerPlugin('prepare', CanvasPrepare); diff --git a/src/prepare/index.js b/src/prepare/index.js index f559c45..e7491ec 100644 --- a/src/prepare/index.js +++ b/src/prepare/index.js @@ -1,4 +1,29 @@ /** + * The prepare namespace provides renderer-specific plugins for pre-rendering DisplayObjects. These plugins are useful for + * asynchronously preparing assets, textures, graphics waiting to be displayed. + * + * Do not instantiate these plugins directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. + * @example + * // Create a new application + * const app = new PIXI.Application(); + * document.body.appendChild(app.view); + * + * // Don't start rendering right away + * app.stop(); + * + * // create a display object + * const rect = new PIXI.Graphics() + * .beginFill(0x00ff00) + * .drawRect(40, 40, 200, 200); + * + * // Add to the stage + * app.stage.addChild(rect); + * + * // Don't start rendering until the graphic is uploaded to the GPU + * app.renderer.plugins.prepare.upload(app.stage, () => { + * app.start(); + * }); * @namespace PIXI.prepare */ export { default as webgl } from './webgl/WebGLPrepare'; diff --git a/src/prepare/webgl/WebGLPrepare.js b/src/prepare/webgl/WebGLPrepare.js index eb7023f..9df3c3f 100644 --- a/src/prepare/webgl/WebGLPrepare.js +++ b/src/prepare/webgl/WebGLPrepare.js @@ -22,12 +22,11 @@ this.uploadHookHelper = this.renderer; // Add textures and graphics to upload - this.register(findBaseTextures, uploadBaseTextures) - .register(findGraphics, uploadGraphics); + this.registerFindHook(findGraphics); + this.registerUploadHook(uploadBaseTextures); + this.registerUploadHook(uploadGraphics); } - } - /** * Built-in hook to upload PIXI.Texture objects to the GPU. * @@ -80,41 +79,6 @@ } /** - * Built-in hook to find textures from Sprites. - * - * @private - * @param {PIXI.DisplayObject} item - Display object to check - * @param {Array<*>} queue - Collection of items to upload - * @return {boolean} if a PIXI.Texture object was found. - */ -function findBaseTextures(item, queue) -{ - // Objects with textures, like Sprites/Text - if (item instanceof core.BaseTexture) - { - if (queue.indexOf(item) === -1) - { - queue.push(item); - } - - return true; - } - else if (item._texture && item._texture instanceof core.Texture) - { - const texture = item._texture.baseTexture; - - if (queue.indexOf(texture) === -1) - { - queue.push(texture); - } - - return true; - } - - return false; -} - -/** * Built-in hook to find graphics. * * @private diff --git a/test/.eslintrc.json b/test/.eslintrc.json index e03220d..ac12110 100644 --- a/test/.eslintrc.json +++ b/test/.eslintrc.json @@ -12,7 +12,8 @@ "sinon": false, "expect": false, "assert": false, - "PIXI": false + "PIXI": false, + "PointerEvent": true }, "rules": { "func-names": 0, diff --git a/test/core/BaseTexture.js b/test/core/BaseTexture.js index 8b93478..bb65068 100644 --- a/test/core/BaseTexture.js +++ b/test/core/BaseTexture.js @@ -14,4 +14,28 @@ expect(baseTexture.imageType).to.be.equals('png'); }); }); + + it('should remove Canvas BaseTexture from cache on destroy', function () + { + const canvas = document.createElement('canvas'); + const texture = PIXI.BaseTexture.fromCanvas(canvas); + const _pixiId = canvas._pixiId; + + expect(PIXI.utils.BaseTextureCache[_pixiId]).to.equal(texture); + texture.destroy(); + expect(PIXI.utils.BaseTextureCache[_pixiId]).to.equal(undefined); + }); + + it('should remove Image BaseTexture from cache on destroy', function () + { + const URL = 'foo.png'; + const NAME = 'bar'; + const image = new Image(); + + const texture = PIXI.Texture.fromLoader(image, URL, NAME); + + expect(PIXI.utils.BaseTextureCache[NAME]).to.equal(texture.baseTexture); + texture.destroy(true); + expect(PIXI.utils.BaseTextureCache[NAME]).to.equal(undefined); + }); }); diff --git a/test/core/Circle.js b/test/core/Circle.js index 183f98f..f621992 100644 --- a/test/core/Circle.js +++ b/test/core/Circle.js @@ -53,6 +53,10 @@ expect(circ1.contains(10, 16)).to.be.false; expect(circ1.contains(11, 15)).to.be.false; expect(circ1.contains(0, 0)).to.be.false; + + const circ2 = new PIXI.Circle(10, 10, 0); + + expect(circ2.contains(10, 10)).to.be.false; }); it('should return framing rectangle', function () diff --git a/.travis.yml b/.travis.yml index 3df8b22..14de334 100644 --- a/.travis.yml +++ b/.travis.yml @@ -110,3 +110,43 @@ on: all_branches: true condition: $TRAVIS_TAG + # Deploy config for latest release + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: dist + upload-dir: "release" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: docs + upload-dir: "release/docs" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: coverage + upload-dir: "release/coverage" + on: + all_branches: true + condition: $TRAVIS_TAG \ No newline at end of file diff --git a/README.md b/README.md index d26fadd..3b85f85 100644 --- a/README.md +++ b/README.md @@ -36,22 +36,14 @@ - Wiki: Other misc tutorials and resources are [on the Wiki](https://github.com/pixijs/pixi.js/wiki/Resources). ### Community ### -- Forums: Check out the [forum] (http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow] (http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. +- Forums: Check out the [forum](http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow](http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. - Inspiration: Check out the [gallery](http://www.pixijs.com/gallery) to see some of the amazing things people have created! - Chat: You can join us on [Gitter](https://gitter.im/pixijs/pixi.js) To chat about Pixi. We also now have a Slack channel. If you would like to join it please Send me an email (mat@goodboydigital.com) and I will invite you in. ### Setup ### -It's easy to get started with Pixi.js! Simply grab the pre-built versions from here: - -Release Branch - Nice and stable Pixi.js -- Unminified: [http://pixijs.download/release/pixi.js] -- Minified: [http://pixijs.download/release/pixi.min.js] - -Develop Branch - The bleeding edge version of Pixi.js -- Unminified: [http://pixijs.download/dev/pixi.js] -- Minified: [http://pixijs.download/dev/pixi.min.js] +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. @@ -70,10 +62,10 @@ #### CDN Install (via cdnjs) ```html - + ``` -_Note: `4.2.2` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ +_Note: `4.4.3` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ ### Demos ### @@ -130,7 +122,7 @@ // and the root stage PIXI.Container. var app = new PIXI.Application(); -// The application will create a canvas element for you that you +// The application will create a canvas element for you that you // can then insert into the DOM. document.body.appendChild(app.view); @@ -150,7 +142,7 @@ // Add the bunny to the scene we are building. app.stage.addChild(bunny); - + // Listen for frame updates app.ticker.add(function() { // each frame we spin the bunny around a bit diff --git a/bower.json b/bower.json deleted file mode 100644 index 41b668c..0000000 --- a/bower.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "pixi.js", - "main": "dist/pixi.min.js", - "ignore": [ - "**/.*", - "docs", - "node_modules", - "bower_components", - "test", - "scripts", - "bower.json", - "inch.json" - ], - "dependencies": { - }, - "devDependencies": { - } -} diff --git a/package.json b/package.json index d35cbb2..308469f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.3.5", + "version": "4.4.4", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ @@ -40,11 +40,11 @@ "lib": "babel src --out-dir lib -s", "predocs": "rimraf docs/**", "docs": "jsdoc -c scripts/jsdoc.conf.json -R README.md", - "publish:patch": "npm version patch --no-git-tag-version && npm publish", - "publish:minor": "npm version minor --no-git-tag-version && npm publish", - "publish:major": "npm version major --no-git-tag-version && npm publish", + "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", - "postpublish": "node scripts/release.js" + "postpublish": "git push && git push --tags" }, "files": [ "dist/", @@ -72,7 +72,6 @@ "electron": "^1.4.15", "eslint": "^3.5.0", "floss": "^2.0.1", - "gh-pages": "^0.11.0", "jaguarjs-jsdoc": "^1.0.1", "js-md5": "^0.4.1", "jsdoc": "^3.4.2", diff --git a/scripts/jsdoc.conf.json b/scripts/jsdoc.conf.json index 553e61c..3f52180 100644 --- a/scripts/jsdoc.conf.json +++ b/scripts/jsdoc.conf.json @@ -43,7 +43,7 @@ }, "markdown" : { "parser" : "gfm", - "hardwrap" : true + "hardwrap" : false }, "opts": { "encoding" : "utf8", diff --git a/scripts/release.js b/scripts/release.js deleted file mode 100644 index 156620c..0000000 --- a/scripts/release.js +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env node -'use strict'; - -// Publish script to push releases of the bin files -// the normally are gitignored -const ghpages = require('gh-pages'); -const path = require('path'); -const packageInfo = require(path.join(__dirname, '..', 'package.json')); - -const options = { - src: [ - 'dist/**/*', - 'lib/**/*', - 'src/**/*', - 'scripts/*', - 'scripts/renders/*', - 'test/**/*', - '*.json', - '*.md', - 'LICENSE', - '.eslintrc', - '.editorconfig', - '.travis.yml', - '.babelrc', - ], - dotfiles: true, - branch: 'release', - message: packageInfo.version, - logger: console.log.bind(console), -}; - -ghpages.publish(process.cwd(), options, (err) => -{ - if (err) - { - console.log(err); - process.exit(1); - - return; - } - - process.exit(0); -}); diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index f16bcb1..220427a 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -21,7 +21,7 @@ const DIV_HOOK_ZINDEX = 2; /** - * The Accessibility manager reacreates the ability to tab and and have content read by screen + * The Accessibility manager recreates the ability to tab and and have content read by screen * readers. This is very important as it can possibly help people with disabilities access pixi * content. * diff --git a/src/accessibility/index.js b/src/accessibility/index.js index 568cfb1..0ae108f 100644 --- a/src/accessibility/index.js +++ b/src/accessibility/index.js @@ -1,4 +1,9 @@ /** + * This namespace contains a renderer plugin for interaction accessibility for end-users + * with physical impairments which require screen-renders, keyboard navigation, etc. + * + * Do not instantiate this plugin directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. * @namespace PIXI.accessibility */ export { default as accessibleTarget } from './accessibleTarget'; diff --git a/src/core/Application.js b/src/core/Application.js index 5340578..2fcc729 100644 --- a/src/core/Application.js +++ b/src/core/Application.js @@ -1,6 +1,8 @@ import { autoDetectRenderer } from './autoDetectRenderer'; import Container from './display/Container'; import { shared, Ticker } from './ticker'; +import settings from './settings'; +import { UPDATE_PRIORITY } from './const'; /** * Convenience class to create a new PIXI application. @@ -22,28 +24,52 @@ */ export default class Application { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experience unexplained flickering try setting this to true. - * @param {boolean} [useSharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedLoader=false] - `true` to use PIXI.loaders.shared, `false` to create new Loader. */ - constructor(width, height, options, noWebGL, useSharedTicker = false) + constructor(options, arg2, arg3, arg4, arg5) { + // Support for constructor(width, height, options, noWebGL, useSharedTicker) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + forceCanvas: !!arg4, + sharedTicker: !!arg5, + }, arg3); + } + + /** + * The default options, so we mixin functionality later. + * @member {object} + * @protected + */ + this._options = options = Object.assign({ + sharedTicker: false, + forceCanvas: false, + sharedLoader: false, + }, options); + /** * WebGL renderer if available, otherwise CanvasRenderer * @member {PIXI.WebGLRenderer|PIXI.CanvasRenderer} */ - this.renderer = autoDetectRenderer(width, height, options, noWebGL); + this.renderer = autoDetectRenderer(options); /** * The root display container that's rendered. @@ -63,7 +89,7 @@ * @member {PIXI.ticker.Ticker} * @default PIXI.ticker.shared */ - this.ticker = useSharedTicker ? shared : new Ticker(); + this.ticker = options.sharedTicker ? shared : new Ticker(); // Start the rendering this.start(); @@ -78,7 +104,7 @@ this._ticker = ticker; if (ticker) { - ticker.add(this.render, this); + ticker.add(this.render, this, UPDATE_PRIORITY.LOW); } } get ticker() // eslint-disable-line require-jsdoc @@ -136,13 +162,18 @@ */ destroy(removeView) { - this.stop(); + const oldTicker = this._ticker; + this.ticker = null; + oldTicker.destroy(); + this.stage.destroy(); this.stage = null; this.renderer.destroy(removeView); this.renderer = null; + + this._options = null; } } diff --git a/src/core/autoDetectRenderer.js b/src/core/autoDetectRenderer.js index 8960425..9ec0364 100644 --- a/src/core/autoDetectRenderer.js +++ b/src/core/autoDetectRenderer.js @@ -2,6 +2,7 @@ import CanvasRenderer from './renderers/canvas/CanvasRenderer'; import WebGLRenderer from './renderers/webgl/WebGLRenderer'; +// eslint-disable-next-line valid-jsdoc /** * This helper function will automatically detect which renderer you should be using. * WebGL is the preferred renderer as it is a lot faster. If webGL is not supported by @@ -9,24 +10,32 @@ * * @memberof PIXI * @function autoDetectRenderer - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @return {PIXI.WebGLRenderer|PIXI.CanvasRenderer} Returns WebGL renderer if available, otherwise CanvasRenderer */ -export function autoDetectRenderer(width = 800, height = 600, options, noWebGL) +export function autoDetectRenderer(options, arg1, arg2, arg3) { - if (!noWebGL && utils.isWebGLSupported()) + // Backward-compatible support for noWebGL option + let forceCanvas = options && options.forceCanvas; + + if (arg3 !== undefined) { - return new WebGLRenderer(width, height, options); + forceCanvas = arg3; } - return new CanvasRenderer(width, height, options); + if (!forceCanvas && utils.isWebGLSupported()) + { + return new WebGLRenderer(options, arg1, arg2); + } + + return new CanvasRenderer(options, arg1, arg2); } diff --git a/src/core/const.js b/src/core/const.js index b9e99ae..2c68dee 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -341,3 +341,27 @@ LINEAR_VERTICAL: 0, LINEAR_HORIZONTAL: 1, }; + +/** + * Represents the update priorities used by internal PIXI classes when registered with + * the {@link PIXI.ticker.Ticker} object. Higher priority items are updated first and lower + * priority items, such as render, should go later. + * + * @static + * @constant + * @name UPDATE_PRIORITY + * @memberof PIXI + * @type {object} + * @property {number} INTERACTION=50 Highest priority, used for {@link PIXI.interaction.InteractionManager} + * @property {number} HIGH=25 High priority updating, {@link PIXI.VideoBaseTexture} and {@link PIXI.extras.AnimatedSprite} + * @property {number} NORMAL=0 Default priority for ticker events, see {@link PIXI.ticker.Ticker#add}. + * @property {number} LOW=-25 Low priority used for {@link PIXI.Application} rendering. + * @property {number} UTILITY=-50 Lowest priority used for {@link PIXI.prepare.BasePrepare} utility. + */ +export const UPDATE_PRIORITY = { + INTERACTION: 50, + HIGH: 25, + NORMAL: 0, + LOW: -25, + UTILITY: -50, +}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 4e6c77d..decb42a 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -13,7 +13,6 @@ * * @class * @extends EventEmitter - * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ export default class DisplayObject extends EventEmitter @@ -113,6 +112,29 @@ * @private */ this._mask = null; + + /** + * If the object has been destroyed via destroy(). If true, it should not be used. + * + * @member {boolean} + * @private + * @readonly + */ + this._destroyed = false; + + /** + * Fired when this DisplayObject is added to a Container. + * + * @event PIXI.DisplayObject#added + * @param {PIXI.Container} container - The container added to. + */ + + /** + * Fired when this DisplayObject is removed from a Container. + * + * @event PIXI.DisplayObject#removed + * @param {PIXI.Container} container - The container removed from. + */ } /** @@ -403,6 +425,8 @@ this.interactive = false; this.interactiveChildren = false; + + this._destroyed = true; } /** diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index aafdfe2..f787c4a 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -792,8 +792,8 @@ sprite.worldAlpha = this.worldAlpha * sprite.alpha; sprite.blendMode = this.blendMode; - sprite.texture._frame.width = rect.width; - sprite.texture._frame.height = rect.height; + sprite._texture._frame.width = rect.width; + sprite._texture._frame.height = rect.height; sprite.transform.worldTransform = this.transform.worldTransform; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index a24c775..b9f192d 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -19,11 +19,12 @@ */ export default class SystemRenderer extends EventEmitter { + // eslint-disable-next-line valid-jsdoc /** * @param {string} system - The name of the system this renderer is for. - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -37,27 +38,31 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(system, screenWidth, screenHeight, options) + constructor(system, options, arg2, arg3) { super(); sayHello(system); - // prepare options - if (options) + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') { - for (const i in settings.RENDER_OPTIONS) - { - if (typeof options[i] === 'undefined') - { - options[i] = settings.RENDER_OPTIONS[i]; - } - } + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); } - else - { - options = settings.RENDER_OPTIONS; - } + + // Add the default render options + options = Object.assign({}, settings.RENDER_OPTIONS, options); + + /** + * The supplied constructor options. + * + * @member {Object} + * @readOnly + */ + this.options = options; /** * The type of the renderer. @@ -75,7 +80,7 @@ * * @member {PIXI.Rectangle} */ - this.screen = new Rectangle(0, 0, screenWidth || 800, screenHeight || 600); + this.screen = new Rectangle(0, 0, options.width, options.height); /** * The canvas element that everything is drawn to @@ -279,6 +284,8 @@ this.blendModes = null; + this.options = null; + this.preserveDrawingBuffer = false; this.clearBeforeRender = false; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index e84e49c..691caf0 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -17,10 +17,11 @@ */ export default class CanvasRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -34,9 +35,9 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('Canvas', screenWidth, screenHeight, options); + super('Canvas', options, arg2, arg3); this.type = RENDERER_TYPE.CANVAS; @@ -96,7 +97,19 @@ this.context = null; this.renderingToScreen = false; - this.resize(screenWidth, screenHeight); + this.resize(this.options.width, this.options.height); + + /** + * Fired after rendering finishes. + * + * @event PIXI.CanvasRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.CanvasRenderer#prerender + */ } /** @@ -299,4 +312,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.CanvasRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.CanvasExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.CanvasPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.CanvasRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index e1cf7cd..f6fe123 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -31,11 +31,12 @@ */ export default class WebGLRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** * - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -55,9 +56,9 @@ * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('WebGL', screenWidth, screenHeight, options); + super('WebGL', options, arg2, arg3); /** * The type of this renderer as a standardised const @@ -89,12 +90,6 @@ }; - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ this._backgroundColorRgba[3] = this.transparent ? 0 : 1; this.globalUniforms = new UniformGroup({ @@ -117,6 +112,13 @@ this.initPlugins(); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ if(options.context) { this.context.initFromContext(options.context); @@ -169,6 +171,25 @@ } return this; + + /** + * Fired after rendering finishes. + * + * @event PIXI.WebGLRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.WebGLRenderer#prerender + */ + + /** + * Fired when the WebGL context is set. + * + * @event PIXI.WebGLRenderer#context + * @param {WebGLRenderingContext} gl - WebGL context. + */ } /** @@ -295,4 +316,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.WebGLRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.WebGLRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(WebGLRenderer); diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index 4eb6128..fa3d883 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -47,6 +47,14 @@ * @member {boolean} */ this.enabled = true; + + /** + * If enabled, pixi will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; } /** diff --git a/src/core/renderers/webgl/systems/ContextSystem.js b/src/core/renderers/webgl/systems/ContextSystem.js index ff958c7..8df85ea 100644 --- a/src/core/renderers/webgl/systems/ContextSystem.js +++ b/src/core/renderers/webgl/systems/ContextSystem.js @@ -75,7 +75,8 @@ */ createContext(canvas, options) { - var gl = canvas.getContext('webgl', options) || + var gl = //canvas.getContext('webgl2', options) || + canvas.getContext('webgl', options) || canvas.getContext('experimental-webgl', options); if (!gl) diff --git a/src/core/renderers/webgl/systems/FilterSystem.js b/src/core/renderers/webgl/systems/FilterSystem.js index f47335d..37e28d2 100644 --- a/src/core/renderers/webgl/systems/FilterSystem.js +++ b/src/core/renderers/webgl/systems/FilterSystem.js @@ -111,7 +111,7 @@ // TODO we should fit the rect around the transform.. } - else + else if (filters[0].autoFit) { sourceFrame.fit(filterData.stack[0].destinationFrame); } diff --git a/src/core/renderers/webgl/systems/shader/shader/mapType.js b/src/core/renderers/webgl/systems/shader/shader/mapType.js index 581dace..917538a 100644 --- a/src/core/renderers/webgl/systems/shader/shader/mapType.js +++ b/src/core/renderers/webgl/systems/shader/shader/mapType.js @@ -15,7 +15,7 @@ } } - return GL_TABLE[type]; + return GL_TABLE[type]; }; var GL_TABLE = null; diff --git a/src/core/settings.js b/src/core/settings.js index 18c5877..38ef45c 100644 --- a/src/core/settings.js +++ b/src/core/settings.js @@ -2,6 +2,15 @@ import canUploadSameBuffer from './utils/canUploadSameBuffer'; /** + * User's customizable globals for overriding the default PIXI settings, such + * as a renderer's default resolution, framerate, float percision, etc. + * @example + * // Use the native window resolution as the default resolution + * // will support high-density displays when rendering + * PIXI.settings.RESOLUTION = window.devicePixelRatio. + * + * // Disable interpolation when scaling, will make texture be pixelated + * PIXI.settings.SCALE_MODE = PIXI.SCALE_MODES.NEAREST; * @namespace PIXI.settings */ export default { @@ -101,6 +110,9 @@ * @property {boolean} clearBeforeRender=true * @property {boolean} preserveDrawingBuffer=false * @property {boolean} roundPixels=false + * @property {number} width=800 + * @property {number} height=600 + * @property {boolean} legacy=false */ RENDER_OPTIONS: { view: null, @@ -112,6 +124,9 @@ clearBeforeRender: true, preserveDrawingBuffer: false, roundPixels: false, + width: 800, + height: 600, + legacy: false, }, /** diff --git a/src/core/shader/Program.js b/src/core/shader/Program.js index c0b538b..07b4398 100644 --- a/src/core/shader/Program.js +++ b/src/core/shader/Program.js @@ -1,6 +1,6 @@ import extractUniformsFromSrc from './extractUniformsFromSrc'; import generateUniformsSync from './generateUniformsSync'; -import glCore from 'pixi-gl-core'; +import shaderUtils from '../renderers/webgl/systems/shader/shader'; import { ProgramCache } from '../utils'; import getTestContext from '../utils/getTestContext'; @@ -64,14 +64,17 @@ } else { - vertexSrc = glCore.shader.setPrecision(vertexSrc, 'mediump'); - fragmentSrc = glCore.shader.setPrecision(fragmentSrc, 'mediump'); + vertexSrc = shaderUtils.setPrecision(vertexSrc, 'mediump'); + fragmentSrc = shaderUtils.setPrecision(fragmentSrc, 'mediump'); - const program = glCore.shader.compileProgram(gl, vertexSrc, fragmentSrc); + const program = shaderUtils.compileProgram(gl, vertexSrc, fragmentSrc); + console.log("<<<<<<>>><<"); this.attributeData = this.getAttributeData(program, gl); this.uniformData = this.getUniformData(program, gl); + console.log(this.uniformData); + //gl.deleteProgram(program); } } @@ -95,13 +98,13 @@ for (let i = 0; i < totalAttributes; i++) { const attribData = gl.getActiveAttrib(program, i); - const type = glCore.shader.mapType(gl, attribData.type); + const type = shaderUtils.mapType(gl, attribData.type); /*eslint-disable */ const data = { type: type, name: attribData.name, - size: glCore.shader.mapSize(type), + size: shaderUtils.mapSize(type), location: 0, }; /*eslint-enable */ @@ -137,13 +140,14 @@ // TODO expose this as a prop? // const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); - const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); + //const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); for (let i = 0; i < totalUniforms; i++) { const uniformData = gl.getActiveUniform(program, i); const name = uniformData.name.replace(/\[.*?\]/, ''); - const type = glCore.shader.mapType(gl, uniformData.type); + const type = shaderUtils.mapType(gl, uniformData.type); + console.log('mapping ' + uniformData.type + ' to ' + type); // if (!name.match(maskRegex)) { @@ -151,7 +155,7 @@ uniforms[name] = { type: type, size: uniformData.size, - value: glCore.shader.defaultValue(type, uniformData.size), + value: shaderUtils.defaultValue(type, uniformData.size), }; /*eslint-enable */ } diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index e387e99..b57d095 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -147,12 +147,12 @@ // so if _width is 0 then width was not set.. if (this._width) { - this.scale.x = sign(this.scale.x) * this._width / this.texture.orig.width; + this.scale.x = sign(this.scale.x) * this._width / this._texture.orig.width; } if (this._height) { - this.scale.y = sign(this.scale.y) * this._height / this.texture.orig.height; + this.scale.y = sign(this.scale.y) * this._height / this._texture.orig.height; } } diff --git a/src/core/sprites/canvas/CanvasTinter.js b/src/core/sprites/canvas/CanvasTinter.js index 27e5e0c..fd9de5d 100644 --- a/src/core/sprites/canvas/CanvasTinter.js +++ b/src/core/sprites/canvas/CanvasTinter.js @@ -18,7 +18,7 @@ */ getTintedTexture: (sprite, color) => { - const texture = sprite.texture; + const texture = sprite._texture; color = CanvasTinter.roundColor(color); diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 5a4edda..8b9b797 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -344,6 +344,7 @@ const texture = this._texture; const style = this._style; + const padding = style.trim ? 0 : style.padding; texture.baseTexture.valid = true; texture.baseTexture.resolution = this.resolution; @@ -353,11 +354,11 @@ texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - texture.trim.x = -style.padding; - texture.trim.y = -style.padding; + texture.trim.x = -padding; + texture.trim.y = -padding; - texture.orig.width = texture._frame.width - (style.padding * 2); - texture.orig.height = texture._frame.height - (style.padding * 2); + texture.orig.width = texture._frame.width - (padding * 2); + texture.orig.height = texture._frame.height - (padding * 2); // call sprite onTextureUpdate to update scale if _width or _height were set this._onTextureUpdate(); diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index 853da3d..8a9c34b 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -114,6 +114,38 @@ this.cacheId = null; this.validate(); + + /** + * Fired when a not-immediately-available source finishes loading. + * + * @protected + * @event PIXI.BaseTexture#loaded + * @param {PIXI.BaseTexture} baseTexture - Resource loaded. + */ + + /** + * Fired when a not-immediately-available source fails to load. + * + * @protected + * @event PIXI.BaseTexture#error + * @param {PIXI.BaseTexture} baseTexture - Resource errored. + */ + + /** + * Fired when BaseTexture is updated. + * + * @protected + * @event PIXI.BaseTexture#update + * @param {PIXI.BaseTexture} baseTexture - Instance of texture being updated. + */ + + /** + * Fired when BaseTexture is destroyed. + * + * @protected + * @event PIXI.BaseTexture#dispose + * @param {PIXI.BaseTexture} baseTexture - Instance of texture being destroyed. + */ } setResource(resource) diff --git a/src/core/textures/Spritesheet.js b/src/core/textures/Spritesheet.js index d469c62..9b24f27 100644 --- a/src/core/textures/Spritesheet.js +++ b/src/core/textures/Spritesheet.js @@ -208,6 +208,7 @@ // lets also add the frame to pixi's global cache for fromFrame and fromImage functions TextureCache[i] = this.textures[i]; + this.textures[i].textureCacheId = i; } frameIndex++; diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 0a2c379..2213087 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -147,9 +147,9 @@ /** * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. * - * @event update - * @memberof PIXI.Texture# + * @event PIXI.Texture#update * @protected + * @param {PIXI.Texture} texture - Instance of texture being updated. */ this._updateID = 0; @@ -159,6 +159,15 @@ * @type {Object} */ this.transform = null; + + /** + * The id under which this Texture has been added to the texture cache. This is + * automatically set in certain cases, but may not always be accurate, particularly if + * the texture is in the cache under multiple ids. + * + * @member {string} + */ + this.textureCacheId = null; } /** @@ -225,11 +234,17 @@ this._uvs = null; this.trim = null; this.orig = null; + this.textureCacheId = null; this.valid = false; - this.off('dispose', this.dispose, this); - this.off('update', this.update, this); + for (const prop in TextureCache) + { + if (TextureCache[prop] === this) + { + delete TextureCache[prop]; + } + } } /** @@ -315,14 +330,15 @@ */ static fromLoader(source, imageUrl, name) { - // console.log('added from loader...') + // console.log('added from loader...') const resource = new ImageResource(source);//.from(imageUrl, crossorigin);// document.createElement('img'); - // console.log('base resource ' + resource.width); + // console.log('base resource ' + resource.width); const baseTexture = new BaseTexture(resource, settings.SCALE_MODE, getResolutionOfUrl(imageUrl)); + /// console.log('base width ' + baseTexture.width); const texture = new Texture(baseTexture); @@ -335,6 +351,7 @@ // lets also add the frame to pixi's global cache for fromFrame and fromImage fucntions BaseTextureCache[name] = baseTexture; TextureCache[name] = texture; + texture.textureCacheId = name; // also add references by url if they are different. if (name !== imageUrl) @@ -355,6 +372,10 @@ */ static addTextureToCache(texture, id) { + if (!texture.textureCacheId) + { + texture.textureCacheId = id; + } TextureCache[id] = texture; } @@ -495,6 +516,7 @@ */ Texture.EMPTY = new Texture(new BaseTexture()); removeAllHandlers(Texture.EMPTY); +removeAllHandlers(Texture.EMPTY.baseTexture); /** * A white texture of 10x10 size, used for graphics and other things @@ -505,3 +527,4 @@ */ Texture.WHITE = createWhiteTexture(); removeAllHandlers(Texture.WHITE); +removeAllHandlers(Texture.WHITE.baseTexture); diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index 59eb9fd..a9b9f51 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -1,6 +1,7 @@ import BaseTexture from './BaseTexture'; import { uid, BaseTextureCache } from '../utils'; -import * as ticker from '../ticker'; +import { shared } from '../ticker'; +import { UPDATE_PRIORITY } from '../const'; /** * A texture of a [playing] Video. @@ -125,7 +126,7 @@ if (!this._isAutoUpdating && this.autoUpdate) { - ticker.shared.add(this.update, this); + shared.add(this.update, this, UPDATE_PRIORITY.HIGH); this._isAutoUpdating = true; } } @@ -139,7 +140,7 @@ { if (this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); this._isAutoUpdating = false; } } @@ -187,7 +188,7 @@ { if (this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); } if (this.source && this.source._pixiId) @@ -281,12 +282,12 @@ if (!this._autoUpdate && this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); this._isAutoUpdating = false; } else if (this._autoUpdate && !this._isAutoUpdating) { - ticker.shared.add(this.update, this); + shared.add(this.update, this, UPDATE_PRIORITY.HIGH); this._isAutoUpdating = true; } } diff --git a/src/core/ticker/Ticker.js b/src/core/ticker/Ticker.js index 17a1517..40df138 100644 --- a/src/core/ticker/Ticker.js +++ b/src/core/ticker/Ticker.js @@ -1,12 +1,10 @@ import settings from '../settings'; -import EventEmitter from 'eventemitter3'; - -// Internal event used by composed emitter -const TICK = 'tick'; +import { UPDATE_PRIORITY } from '../const'; +import TickerListener from './TickerListener'; /** * A Ticker class that runs an update loop that other objects listen to. - * This class is composed around an EventEmitter object to add listeners + * This class is composed around listeners * meant for execution on the next requested animation frame. * Animation frames are requested only when necessary, * e.g. When the ticker is started and the emitter has listeners. @@ -22,10 +20,11 @@ constructor() { /** - * Internal emitter used to fire 'tick' event + * The first listener. All new listeners added are chained on this. * @private + * @type {TickerListener} */ - this._emitter = new EventEmitter(); + this._head = new TickerListener(null, null, Infinity); /** * Internal current frame request ID @@ -131,7 +130,7 @@ // Invoke listeners now this.update(time); // Listener side effects may have modified ticker state. - if (this.started && this._requestId === null && this._emitter.listeners(TICK, true)) + if (this.started && this._requestId === null && this._head.next) { this._requestId = requestAnimationFrame(this._tick); } @@ -148,7 +147,7 @@ */ _requestIfNeeded() { - if (this._requestId === null && this._emitter.listeners(TICK, true)) + if (this._requestId === null && this._head.next) { // ensure callbacks get correct delta this.lastTime = performance.now(); @@ -193,35 +192,72 @@ } /** - * Calls {@link module:eventemitter3.EventEmitter#on} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. + * Register a handler for tick events. Calls continuously unless + * it is removed or the ticker is stopped. * * @param {Function} fn - The listener function to be added for updates * @param {Function} [context] - The listener context + * @param {number} [priority=PIXI.UPDATE_PRIORITY.NORMAL] - The priority for emitting * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - add(fn, context) + add(fn, context, priority = UPDATE_PRIORITY.NORMAL) { - this._emitter.on(TICK, fn, context); - - this._startIfPossible(); - - return this; + return this._addListener(new TickerListener(fn, context, priority)); } /** - * Calls {@link module:eventemitter3.EventEmitter#once} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. + * Add a handler for the tick event which is only execute once. * * @param {Function} fn - The listener function to be added for one update * @param {Function} [context] - The listener context + * @param {number} [priority=PIXI.UPDATE_PRIORITY.NORMAL] - The priority for emitting * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - addOnce(fn, context) + addOnce(fn, context, priority = UPDATE_PRIORITY.NORMAL) { - this._emitter.once(TICK, fn, context); + return this._addListener(new TickerListener(fn, context, priority, true)); + } + + /** + * Internally adds the event handler so that it can be sorted by priority. + * Priority allows certain handler (user, AnimatedSprite, Interaction) to be run + * before the rendering. + * + * @private + * @param {TickerListener} listener - Current listener being added. + * @returns {PIXI.ticker.Ticker} This instance of a ticker + */ + _addListener(listener) + { + // For attaching to head + let current = this._head.next; + let previous = this._head; + + // Add the first item + if (!current) + { + listener.connect(previous); + } + else + { + // Go from highest to lowest priority + while (current) + { + if (listener.priority >= current.priority) + { + listener.connect(previous); + break; + } + previous = current; + current = current.next; + } + + // Not yet connected + if (!listener.previous) + { + listener.connect(previous); + } + } this._startIfPossible(); @@ -229,19 +265,33 @@ } /** - * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. - * It checks if the emitter has listeners for 'tick' event. - * If it does, then it cancels the animation frame. + * Removes any handlers matching the function and context parameters. + * If no handlers are left after removing, then it cancels the animation frame. * - * @param {Function} [fn] - The listener function to be removed + * @param {Function} fn - The listener function to be removed * @param {Function} [context] - The listener context to be removed * @returns {PIXI.ticker.Ticker} This instance of a ticker */ remove(fn, context) { - this._emitter.off(TICK, fn, context); + let listener = this._head.next; - if (!this._emitter.listeners(TICK, true)) + while (listener) + { + // We found a match, lets remove it + // no break to delete all possible matches + // incase a listener was added 2+ times + if (listener.match(fn, context)) + { + listener = listener.destroy(); + } + else + { + listener = listener.next; + } + } + + if (!this._head.next) { this._cancelIfNeeded(); } @@ -276,6 +326,25 @@ } /** + * Destroy the ticker and don't use after this. Calling + * this method removes all references to internal events. + */ + destroy() + { + this.stop(); + + let listener = this._head.next; + + while (listener) + { + listener = listener.destroy(true); + } + + this._head.destroy(); + this._head = null; + } + + /** * Triggers an update. An update entails setting the * current {@link PIXI.ticker.Ticker#elapsedMS}, * the current {@link PIXI.ticker.Ticker#deltaTime}, @@ -320,8 +389,22 @@ this.deltaTime = elapsedMS * settings.TARGET_FPMS * this.speed; + // Cache a local reference, in-case ticker is destroyed + // during the emit, we can still check for head.next + const head = this._head; + // Invoke listeners added to internal emitter - this._emitter.emit(TICK, this.deltaTime); + let listener = head.next; + + while (listener) + { + listener = listener.emit(this.deltaTime); + } + + if (!head.next) + { + this._cancelIfNeeded(); + } } else { diff --git a/src/core/ticker/TickerListener.js b/src/core/ticker/TickerListener.js new file mode 100644 index 0000000..2bedb34 --- /dev/null +++ b/src/core/ticker/TickerListener.js @@ -0,0 +1,158 @@ +/** + * Internal class for handling the priority sorting of ticker handlers. + * + * @private + * @class + * @memberof PIXI.ticker + */ +export default class TickerListener +{ + /** + * Constructor + * + * @param {Function} fn - The listener function to be added for one update + * @param {Function} [context=null] - The listener context + * @param {number} [priority=0] - The priority for emitting + * @param {boolean} [once=false] - If the handler should fire once + */ + constructor(fn, context = null, priority = 0, once = false) + { + /** + * The handler function to execute. + * @member {Function} + */ + this.fn = fn; + + /** + * The calling to execute. + * @member {Function} + */ + this.context = context; + + /** + * The current priority. + * @member {number} + */ + this.priority = priority; + + /** + * If this should only execute once. + * @member {boolean} + */ + this.once = once; + + /** + * The next item in chain. + * @member {TickerListener} + */ + this.next = null; + + /** + * The previous item in chain. + * @member {TickerListener} + */ + this.previous = null; + + /** + * `true` if this listener has been destroyed already. + * @member {boolean} + * @private + */ + this._destroyed = false; + } + + /** + * Simple compare function to figure out if a function and context match. + * + * @param {Function} fn - The listener function to be added for one update + * @param {Function} context - The listener context + * @return {boolean} `true` if the listener match the arguments + */ + match(fn, context) + { + context = context || null; + + return this.fn === fn && this.context === context; + } + + /** + * Emit by calling the current function. + * @param {number} deltaTime - time since the last emit. + * @return {TickerListener} Next ticker + */ + emit(deltaTime) + { + if (this.context) + { + this.fn.call(this.context, deltaTime); + } + else + { + this.fn(deltaTime); + } + + if (this.once) + { + this.destroy(); + } + + const redirect = this.next; + + // Soft-destroying should remove + // the next reference + if (this._destroyed) + { + this.next = null; + } + + return redirect; + } + + /** + * Connect to the list. + * @param {TickerListener} previous - Input node, previous listener + */ + connect(previous) + { + this.previous = previous; + if (previous.next) + { + previous.next.previous = this; + } + this.next = previous.next; + previous.next = this; + } + + /** + * Destroy and don't use after this. + * @param {boolean} [hard = false] `true` to remove the `next` reference, this + * is considered a hard destroy. Soft destroy maintains the next reference. + * @return {TickerListener} The listener to redirect while emitting or removing. + */ + destroy(hard = false) + { + this._destroyed = true; + this.fn = null; + this.context = null; + + // Disconnect, hook up next and previous + if (this.previous) + { + this.previous.next = this.next; + } + + if (this.next) + { + this.next.previous = this.previous; + } + + // Redirect to the next item + const redirect = this.previous; + + // Remove references + this.next = hard ? null : redirect; + this.previous = null; + + return redirect; + } +} diff --git a/src/core/ticker/index.js b/src/core/ticker/index.js index 4b3017c..c3020d4 100644 --- a/src/core/ticker/index.js +++ b/src/core/ticker/index.js @@ -45,8 +45,25 @@ const shared = new Ticker(); shared.autoStart = true; +shared.destroy = () => +{ + // protect destroying shared ticker + // this is used by other internal systems + // like AnimatedSprite and InteractionManager +}; /** + * This namespace contains an API for interacting with PIXI's internal global update loop. + * + * This ticker is used for rendering, {@link PIXI.extras.AnimatedSprite AnimatedSprite}, + * {@link PIXI.interaction.InteractionManager InteractionManager} and many other time-based PIXI systems. + * @example + * const ticker = new PIXI.ticker.Ticker(); + * ticker.stop(); + * ticker.add((deltaTime) => { + * // do something every frame + * }); + * ticker.start(); * @namespace PIXI.ticker */ export { shared, Ticker }; diff --git a/src/core/utils/index.js b/src/core/utils/index.js index be44750..7441f7b 100644 --- a/src/core/utils/index.js +++ b/src/core/utils/index.js @@ -9,6 +9,21 @@ let saidHello = false; /** + * Generalized convenience utilities for PIXI. + * @example + * // Extend PIXI's internal Event Emitter. + * class MyEmitter extends PIXI.utils.EventEmitter { + * constructor() { + * super(); + * console.log("Emitter created!"); + * } + * } + * + * // Get info on current device + * console.log(PIXI.utils.isMobile); + * + * // Convert hex color to string + * console.log(PIXI.utils.hex2string(0xff00ff)); // returns: "#ff00ff" * @namespace PIXI.utils */ export { diff --git a/src/deprecation.js b/src/deprecation.js index b9c73b3..33daeec 100644 --- a/src/deprecation.js +++ b/src/deprecation.js @@ -918,6 +918,35 @@ }); /** + * @method + * @name PIXI.prepare.BasePrepare#register + * @see PIXI.prepare.BasePrepare#registerFindHook + * @deprecated since version 4.4.2 + * @param {Function} [addHook] - Function call that takes two parameters: `item:*, queue:Array` + * function must return `true` if it was able to add item to the queue. + * @param {Function} [uploadHook] - Function call that takes two parameters: `prepare:CanvasPrepare, item:*` and + * function must return `true` if it was able to handle upload of item. + * @return {PIXI.BasePrepare} Instance of plugin for chaining. + */ +prepare.BasePrepare.prototype.register = function register(addHook, uploadHook) +{ + warn('renderer.plugins.prepare.register is now deprecated, ' + + 'please use renderer.plugins.prepare.registerFindHook & renderer.plugins.prepare.registerUploadHook'); + + if (addHook) + { + this.registerFindHook(addHook); + } + + if (uploadHook) + { + this.registerUploadHook(uploadHook); + } + + return this; +}; + +/** * The number of graphics or textures to upload to the GPU. * * @name PIXI.prepare.canvas.UPLOADS_PER_FRAME @@ -969,67 +998,73 @@ }, }); -Object.defineProperties(loaders.Resource.prototype, { - isJson: { - get() - { - warn('The isJson property is deprecated, please use `resource.type === Resource.TYPE.JSON`.'); +if (loaders.Loader) +{ + const Resource = loaders.Resource; + const Loader = loaders.Loader; - return this.type === loaders.Loader.Resource.TYPE.JSON; + Object.defineProperties(Resource.prototype, { + isJson: { + get() + { + warn('The isJson property is deprecated, please use `resource.type === Resource.TYPE.JSON`.'); + + return this.type === Resource.TYPE.JSON; + }, }, - }, - isXml: { - get() - { - warn('The isXml property is deprecated, please use `resource.type === Resource.TYPE.XML`.'); + isXml: { + get() + { + warn('The isXml property is deprecated, please use `resource.type === Resource.TYPE.XML`.'); - return this.type === loaders.Loader.Resource.TYPE.XML; + return this.type === Resource.TYPE.XML; + }, }, - }, - isImage: { - get() - { - warn('The isImage property is deprecated, please use `resource.type === Resource.TYPE.IMAGE`.'); + isImage: { + get() + { + warn('The isImage property is deprecated, please use `resource.type === Resource.TYPE.IMAGE`.'); - return this.type === loaders.Loader.Resource.TYPE.IMAGE; + return this.type === Resource.TYPE.IMAGE; + }, }, - }, - isAudio: { - get() - { - warn('The isAudio property is deprecated, please use `resource.type === Resource.TYPE.AUDIO`.'); + isAudio: { + get() + { + warn('The isAudio property is deprecated, please use `resource.type === Resource.TYPE.AUDIO`.'); - return this.type === loaders.Loader.Resource.TYPE.AUDIO; + return this.type === Resource.TYPE.AUDIO; + }, }, - }, - isVideo: { - get() - { - warn('The isVideo property is deprecated, please use `resource.type === Resource.TYPE.VIDEO`.'); + isVideo: { + get() + { + warn('The isVideo property is deprecated, please use `resource.type === Resource.TYPE.VIDEO`.'); - return this.type === loaders.Loader.Resource.TYPE.VIDEO; + return this.type === Resource.TYPE.VIDEO; + }, }, - }, -}); + }); -Object.defineProperties(loaders.Loader.prototype, { - before: { - get() - { - warn('The before() method is deprecated, please use pre().'); + Object.defineProperties(Loader.prototype, { + before: { + get() + { + warn('The before() method is deprecated, please use pre().'); - return this.pre; + return this.pre; + }, }, - }, - after: { - get() - { - warn('The after() method is deprecated, please use use().'); + after: { + get() + { + warn('The after() method is deprecated, please use use().'); - return this.use; + return this.use; + }, }, - }, -}); + }); +} /** * @name PIXI.interaction.interactiveTarget#defaultCursor diff --git a/src/extract/canvas/CanvasExtract.js b/src/extract/canvas/CanvasExtract.js index 941ca05..3ac5aee 100644 --- a/src/extract/canvas/CanvasExtract.js +++ b/src/extract/canvas/CanvasExtract.js @@ -8,7 +8,7 @@ * An instance of this class is automatically created by default, and can be found at renderer.plugins.extract * * @class - * @memberof PIXI + * @memberof PIXI.extract */ export default class CanvasExtract { @@ -21,9 +21,9 @@ /** * Collection of methods for extracting data (image, pixels, etc.) from a display object or render texture * - * @member {PIXI.CanvasExtract} extract + * @member {PIXI.extract.CanvasExtract} extract * @memberof PIXI.CanvasRenderer# - * @see PIXI.CanvasExtract + * @see PIXI.extract.CanvasExtract */ renderer.extract = this; } diff --git a/src/extract/index.js b/src/extract/index.js index 0f2170d..e9478c1 100644 --- a/src/extract/index.js +++ b/src/extract/index.js @@ -1,2 +1,22 @@ +/** + * This namespace provides renderer-specific plugins for exporting content from a renderer. + * For instance, these plugins can be used for saving an Image, Canvas element or for exporting the raw image data (pixels). + * + * Do not instantiate these plugins directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. + * @example + * // Create a new app (will auto-add extract plugin to renderer) + * const app = new PIXI.Application(); + * + * // Draw a red circle + * const graphics = new PIXI.Graphics() + * .beginFill(0xFF0000) + * .drawCircle(0, 0, 50); + * + * // Render the graphics as an HTMLImageElement + * const image = app.renderer.plugins.extract.image(graphics); + * document.body.appendChild(image); + * @namespace PIXI.extract + */ export { default as webgl } from './webgl/WebGLExtract'; export { default as canvas } from './canvas/CanvasExtract'; diff --git a/src/extract/webgl/WebGLExtract.js b/src/extract/webgl/WebGLExtract.js index 60228bb..1c3dd30 100644 --- a/src/extract/webgl/WebGLExtract.js +++ b/src/extract/webgl/WebGLExtract.js @@ -9,7 +9,7 @@ * An instance of this class is automatically created by default, and can be found at renderer.plugins.extract * * @class - * @memberof PIXI + * @memberof PIXI.extract */ export default class WebGLExtract { @@ -22,9 +22,9 @@ /** * Collection of methods for extracting data (image, pixels, etc.) from a display object or render texture * - * @member {PIXI.WebGLExtract} extract + * @member {PIXI.extract.WebGLExtract} extract * @memberof PIXI.WebGLRenderer# - * @see PIXI.WebGLExtract + * @see PIXI.extract.WebGLExtract */ renderer.extract = this; } diff --git a/src/extras/AnimatedSprite.js b/src/extras/AnimatedSprite.js index 13b804f..5297778 100644 --- a/src/extras/AnimatedSprite.js +++ b/src/extras/AnimatedSprite.js @@ -137,7 +137,7 @@ this.playing = true; if (this._autoUpdate) { - core.ticker.shared.add(this.update, this); + core.ticker.shared.add(this.update, this, core.UPDATE_PRIORITY.HIGH); } } diff --git a/src/extras/TextureTransform.js b/src/extras/TextureTransform.js index 78e6e40..16536d2 100644 --- a/src/extras/TextureTransform.js +++ b/src/extras/TextureTransform.js @@ -65,8 +65,36 @@ } /** + * Multiplies uvs array to transform + * @param {Float32Array} uvs mesh uvs + * @param {Float32Array} [out=uvs] output + * @returns {Float32Array} output + */ + multiplyUvs(uvs, out) + { + if (out === undefined) + { + out = uvs; + } + + const mat = this.mapCoord; + + for (let i = 0; i < uvs.length; i += 2) + { + const x = uvs[i]; + const y = uvs[i + 1]; + + out[i] = (x * mat.a) + (y * mat.c) + mat.tx; + out[i + 1] = (x * mat.b) + (y * mat.d) + mat.ty; + } + + return out; + } + + /** * updates matrices if texture was changed * @param {boolean} forceUpdate if true, matrices will be updated any case + * @returns {boolean} whether or not it was updated */ update(forceUpdate) { @@ -74,13 +102,13 @@ if (!tex || !tex.valid) { - return; + return false; } if (!forceUpdate && this._lastTextureID === tex._updateID) { - return; + return false; } this._lastTextureID = tex._updateID; @@ -110,5 +138,7 @@ frame[3] = (tex._frame.y + tex._frame.height - margin + offset) / texBase.height; this.uClampOffset[0] = offset / texBase.realWidth; this.uClampOffset[1] = offset / texBase.realHeight; + + return true; } } diff --git a/src/extras/TilingSprite.js b/src/extras/TilingSprite.js index 1f6014f..a3f027f 100644 --- a/src/extras/TilingSprite.js +++ b/src/extras/TilingSprite.js @@ -180,9 +180,9 @@ const transform = this.worldTransform; const resolution = renderer.resolution; const baseTexture = texture.baseTexture; - const baseTextureResolution = texture.baseTexture.resolution; - const modX = (this.tilePosition.x / this.tileScale.x) % texture._frame.width; - const modY = (this.tilePosition.y / this.tileScale.y) % texture._frame.height; + const baseTextureResolution = baseTexture.resolution; + const modX = ((this.tilePosition.x / this.tileScale.x) % texture._frame.width) * baseTextureResolution; + const modY = ((this.tilePosition.y / this.tileScale.y) % texture._frame.height) * baseTextureResolution; // create a nice shiny pattern! // TODO this needs to be refreshed if texture changes.. diff --git a/src/extras/cacheAsBitmap.js b/src/extras/cacheAsBitmap.js index 5dd5640..aea06a8 100644 --- a/src/extras/cacheAsBitmap.js +++ b/src/extras/cacheAsBitmap.js @@ -227,7 +227,16 @@ this.transform._parentID = -1; // restore the transform of the cached sprite to avoid the nasty flicker.. - this.updateTransform(); + if (!this.parent) + { + this.parent = renderer._tempDisplayObjectParent; + this.updateTransform(); + this.parent = null; + } + else + { + this.updateTransform(); + } // map the hit test.. this.containsPoint = cachedSprite.containsPoint.bind(cachedSprite); @@ -314,7 +323,17 @@ cachedSprite._bounds = this._bounds; cachedSprite.alpha = cacheAlpha; - this.updateTransform(); + if (!this.parent) + { + this.parent = renderer._tempDisplayObjectParent; + this.updateTransform(); + this.parent = null; + } + else + { + this.updateTransform(); + } + this.updateTransform = this.displayObjectUpdateTransform; this._cacheData.sprite = cachedSprite; @@ -358,9 +377,12 @@ * Destroys the cached object. * * @private + * @param {object|boolean} [options] - Options parameter. A boolean will act as if all options + * have been set to that value. + * Used when destroying containers, see the Container.destroy method. */ -DisplayObject.prototype._cacheAsBitmapDestroy = function _cacheAsBitmapDestroy() +DisplayObject.prototype._cacheAsBitmapDestroy = function _cacheAsBitmapDestroy(options) { this.cacheAsBitmap = false; - this.destroy(); + this.destroy(options); }; diff --git a/src/extras/index.js b/src/extras/index.js index bbd8bbf..a34d7ec 100644 --- a/src/extras/index.js +++ b/src/extras/index.js @@ -1,7 +1,7 @@ /** + * Additional PIXI DisplayObjects for animation, tiling and bitmap text. * @namespace PIXI.extras */ -export { default as TextureTransform } from './TextureTransform'; export { default as AnimatedSprite } from './AnimatedSprite'; export { default as TilingSprite } from './TilingSprite'; export { default as TilingSpriteRenderer } from './webgl/TilingSpriteRenderer'; diff --git a/src/extras/webgl/TilingSpriteRenderer.js b/src/extras/webgl/TilingSpriteRenderer.js index aeec172..eee3b5c 100644 --- a/src/extras/webgl/TilingSpriteRenderer.js +++ b/src/extras/webgl/TilingSpriteRenderer.js @@ -11,7 +11,7 @@ * WebGL renderer plugin for tiling sprites * * @class - * @memberof PIXI + * @memberof PIXI.extras * @extends PIXI.ObjectRenderer */ export default class TilingSpriteRenderer extends core.ObjectRenderer @@ -118,7 +118,7 @@ tempMat.invert(); if (isSimple) { - tempMat.append(uv.mapCoord); + tempMat.prepend(uv.mapCoord); } else { diff --git a/src/filters/displacement/DisplacementFilter.js b/src/filters/displacement/DisplacementFilter.js index d5355c3..b5d030c 100644 --- a/src/filters/displacement/DisplacementFilter.js +++ b/src/filters/displacement/DisplacementFilter.js @@ -35,7 +35,7 @@ this.maskSprite = sprite; this.maskMatrix = maskMatrix; - this.uniforms.mapSampler = sprite.texture; + this.uniforms.mapSampler = sprite._texture; this.uniforms.filterMatrix = maskMatrix; this.uniforms.scale = { x: 1, y: 1 }; diff --git a/src/filters/index.js b/src/filters/index.js index f3e8862..7ceb50a 100644 --- a/src/filters/index.js +++ b/src/filters/index.js @@ -1,4 +1,21 @@ /** + * This namespace contains WebGL-only display filters that can be applied + * to DisplayObjects using the {@link PIXI.DisplayObject#filters filters} property. + * @example + * // Create a new application + * const app = new PIXI.Application(); + * + * // Draw a green rectangle + * const rect = new PIXI.Graphics() + * .beginFill(0x00ff00) + * .drawRect(40, 40, 200, 200); + * + * // Add a blur filter + * rect.filters = [new PIXI.filters.BlurFilter()]; + * + * // Display rectangle + * app.stage.addChild(rect); + * document.body.appendChild(app.view); * @namespace PIXI.filters */ export { default as FXAAFilter } from './fxaa/FXAAFilter'; diff --git a/src/index.js b/src/index.js index c553427..77da490 100644 --- a/src/index.js +++ b/src/index.js @@ -20,6 +20,14 @@ import { utils } from './core'; utils.mixins.performMixins(); +/** + * Alias for {@link PIXI.loaders.shared}. + * @name loader + * @memberof PIXI + * @type {PIXI.loader.Loader} + */ +const loader = loaders.shared || null; + export { accessibility, extract, @@ -30,18 +38,8 @@ mesh, particles, prepare, + loader, }; -/** - * A premade instance of the loader that can be used to load resources. - * - * @name loader - * @memberof PIXI - * @property {PIXI.loaders.Loader} - */ -const loader = loaders && loaders.Loader ? new loaders.Loader() : null; // check is there in case user excludes loader lib - -export { loader }; - // Always export pixi globally. global.PIXI = exports; // eslint-disable-line diff --git a/src/interaction/InteractionData.js b/src/interaction/InteractionData.js index 02d53a3..449f8cc 100644 --- a/src/interaction/InteractionData.js +++ b/src/interaction/InteractionData.js @@ -30,7 +30,10 @@ /** * When passed to an event handler, this will be the original DOM Event that was captured * - * @member {Event} + * @see https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent + * @see https://developer.mozilla.org/en-US/docs/Web/API/TouchEvent + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent + * @member {MouseEvent|TouchEvent|PointerEvent} */ this.originalEvent = null; } diff --git a/src/interaction/InteractionManager.js b/src/interaction/InteractionManager.js index 8baf13a..2d75c7c 100644 --- a/src/interaction/InteractionManager.js +++ b/src/interaction/InteractionManager.js @@ -13,6 +13,14 @@ const MOUSE_POINTER_ID = 'MOUSE'; +// helpers for hitTest() - only used inside hitTest() +const hitTestEvent = { + target: null, + data: { + global: null, + }, +}; + /** * The interaction manager deals with mouse, touch and pointer events. Any DisplayObject can be interactive * if its interactive parameter is set to true @@ -245,67 +253,60 @@ this.setTargetElement(this.renderer.view, this.renderer.resolution); /** - * Fired when a pointer device button (usually a mouse button) is pressed on the display + * Fired when a pointer device button (usually a mouse left-button) is pressed on the display * object. * - * @event mousedown - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mousedown + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device secondary button (usually a mouse right-button) is pressed * on the display object. * - * @event rightdown - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightdown + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** - * Fired when a pointer device button (usually a mouse button) is released over the display + * Fired when a pointer device button (usually a mouse left-button) is released over the display * object. * - * @event mouseup - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseup + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device secondary button (usually a mouse right-button) is released * over the display object. * - * @event rightup - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightup + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** - * Fired when a pointer device button (usually a mouse button) is pressed and released on + * Fired when a pointer device button (usually a mouse left-button) is pressed and released on * the display object. * - * @event click - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#click + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device secondary button (usually a mouse right-button) is pressed * and released on the display object. * - * @event rightclick - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightclick + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** - * Fired when a pointer device button (usually a mouse button) is released outside the + * Fired when a pointer device button (usually a mouse left-button) is released outside the * display object that initially registered a * [mousedown]{@link PIXI.interaction.InteractionManager#event:mousedown}. * - * @event mouseupoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** @@ -313,146 +314,362 @@ * outside the display object that initially registered a * [rightdown]{@link PIXI.interaction.InteractionManager#event:rightdown}. * - * @event rightupoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device (usually a mouse) is moved while over the display object * - * @event mousemove - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mousemove + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device (usually a mouse) is moved onto the display object * - * @event mouseover - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseover + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device (usually a mouse) is moved off the display object * - * @event mouseout - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseout + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is pressed on the display object. * - * @event pointerdown - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerdown + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is released over the display object. * - * @event pointerup - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerup + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when the operating system cancels a pointer event * - * @event pointercancel - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointercancel + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is pressed and released on the display object. * - * @event pointertap - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointertap + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is released outside the display object that initially * registered a [pointerdown]{@link PIXI.interaction.InteractionManager#event:pointerdown}. * - * @event pointerupoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device is moved while over the display object * - * @event pointermove - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointermove + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device is moved onto the display object * - * @event pointerover - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerover + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device is moved off the display object * - * @event pointerout - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerout + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is placed on the display object. * - * @event touchstart - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchstart + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is removed from the display object. * - * @event touchend - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchend + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when the operating system cancels a touch * - * @event touchcancel - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchcancel + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is placed and removed from the display object. * - * @event tap - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#tap + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is removed outside of the display object that initially * registered a [touchstart]{@link PIXI.interaction.InteractionManager#event:touchstart}. * - * @event touchendoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchendoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is moved along the display object. * - * @event touchmove - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchmove + * @param {PIXI.interaction.InteractionData} event - Interaction data */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is pressed on the display. + * object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mousedown + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is pressed + * on the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightdown + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is released over the display + * object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseup + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is released + * over the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightup + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is pressed and released on + * the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#click + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is pressed + * and released on the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightclick + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is released outside the + * display object that initially registered a + * [mousedown]{@link PIXI.DisplayObject#event:mousedown}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is released + * outside the display object that initially registered a + * [rightdown]{@link PIXI.DisplayObject#event:rightdown}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device (usually a mouse) is moved while over the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mousemove + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device (usually a mouse) is moved onto the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseover + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device (usually a mouse) is moved off the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseout + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is pressed on the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerdown + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is released over the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerup + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when the operating system cancels a pointer event. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointercancel + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is pressed and released on the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointertap + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is released outside the display object that initially + * registered a [pointerdown]{@link PIXI.DisplayObject#event:pointerdown}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device is moved while over the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointermove + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device is moved onto the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerover + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device is moved off the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerout + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is placed on the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchstart + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is removed from the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchend + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when the operating system cancels a touch. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchcancel + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is placed and removed from the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#tap + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is removed outside of the display object that initially + * registered a [touchstart]{@link PIXI.DisplayObject#event:touchstart}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchendoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is moved along the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchmove + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + } + + /** + * Hit tests a point against the display tree, returning the first interactive object that is hit. + * + * @param {PIXI.Point} globalPoint - A point to hit test with, in global space. + * @param {PIXI.Container} [root] - The root display object to start from. If omitted, defaults + * to the last rendered root of the associated renderer. + * @return {PIXI.DisplayObject} The hit display object, if any. + */ + hitTest(globalPoint, root) + { + // clear the target for our hit test + hitTestEvent.target = null; + // assign the global point + hitTestEvent.data.global = globalPoint; + // ensure safety of the root + if (!root) + { + root = this.renderer._lastObjectRendered; + } + // run the hit test + this.processInteractive(hitTestEvent, root, null, true); + // return our found object - it'll be null if we didn't hit anything + + return hitTestEvent.target; } /** @@ -487,7 +704,7 @@ return; } - core.ticker.shared.add(this.update, this); + core.ticker.shared.add(this.update, this, core.UPDATE_PRIORITY.INTERACTION); if (window.navigator.msPointerEnabled) { @@ -804,8 +1021,6 @@ } } - let keepHitTestingAfterChildren = hitTest; - // ** FREE TIP **! If an object is not interactive or has no buttons in it // (such as a game scene!) set interactiveChildren to false for that displayObject. // This will allow pixi to completely ignore and bypass checking the displayObjects children. @@ -818,7 +1033,9 @@ const child = children[i]; // time to get recursive.. if this function will return if something is hit.. - if (this.processInteractive(interactionEvent, child, func, hitTest, interactiveParent)) + const childHit = this.processInteractive(interactionEvent, child, func, hitTest, interactiveParent); + + if (childHit) { // its a good idea to check if a child has lost its parent. // this means it has been removed whilst looping so its best @@ -827,8 +1044,6 @@ continue; } - hit = true; - // we no longer need to hit test any more objects in this container as we we // now know the parent has been hit interactiveParent = false; @@ -838,36 +1053,41 @@ // This means we no longer need to hit test anything else. We still need to run // through all objects, but we don't need to perform any hit tests. - keepHitTestingAfterChildren = false; - - if (child.interactive) + if (childHit) { - hitTest = false; + if (interactionEvent.target) + { + hitTest = false; + } + hit = true; } - - // we can break now as we have hit an object. } } } - hitTest = keepHitTestingAfterChildren; - // no point running this if the item is not interactive or does not have an interactive parent. if (interactive) { // if we are hit testing (as in we have no hit any objects yet) // We also don't need to worry about hit testing if once of the displayObjects children - // has already been hit! - if (hitTest && !hit) + // has already been hit - but only if it was interactive, otherwise we need to keep + // looking for an interactive child, just in case we hit one + if (hitTest && !interactionEvent.target) { if (displayObject.hitArea) { displayObject.worldTransform.applyInverse(point, this._tempPoint); - hit = displayObject.hitArea.contains(this._tempPoint.x, this._tempPoint.y); + if (displayObject.hitArea.contains(this._tempPoint.x, this._tempPoint.y)) + { + hit = true; + } } else if (displayObject.containsPoint) { - hit = displayObject.containsPoint(point); + if (displayObject.containsPoint(point)) + { + hit = true; + } } } @@ -878,7 +1098,10 @@ interactionEvent.target = displayObject; } - func(interactionEvent, displayObject, hit); + if (func) + { + func(interactionEvent, displayObject, !!hit); + } } } @@ -914,7 +1137,7 @@ { const event = events[i]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -998,7 +1221,7 @@ { const event = events[i]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1098,7 +1321,7 @@ const test = isRightButton ? flags.RIGHT_DOWN : flags.LEFT_DOWN; - const isDown = trackingData !== undefined && (trackingData.flags | test); + const isDown = trackingData !== undefined && (trackingData.flags & test); if (hit) { @@ -1118,11 +1341,11 @@ { if (isRightButton) { - trackingData.rightDown = hit; + trackingData.rightDown = false; } else { - trackingData.leftDown = hit; + trackingData.leftDown = false; } } } @@ -1180,7 +1403,7 @@ { const event = events[i]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1255,7 +1478,7 @@ this.setCursorMode(null); } - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1268,6 +1491,12 @@ { this.emit('mouseout', interactionEvent); } + else + { + // we can get touchleave events after touchend, so we want to make sure we don't + // introduce memory leaks + this.releaseInteractionDataForPointerId(interactionData.identifier); + } } /** @@ -1344,7 +1573,7 @@ // Only mouse and pointer can call onPointerOver, so events will always be length 1 const event = events[0]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1366,12 +1595,14 @@ * Get InteractionData for a given pointerId. Store that data as well * * @private - * @param {number} pointerId - Identifier from a pointer event + * @param {PointerEvent} event - Normalized pointer event, output from normalizeToPointerData * @return {InteractionData} - Interaction data for the given pointer identifier */ - getInteractionDataForPointerId(pointerId) + getInteractionDataForPointerId(event) { - if (pointerId === MOUSE_POINTER_ID) + const pointerId = event.pointerId; + + if (pointerId === MOUSE_POINTER_ID || event.pointerType === 'mouse') { return this.mouse; } diff --git a/src/interaction/InteractionTrackingData.js b/src/interaction/InteractionTrackingData.js index 6a1f69e..8493b63 100644 --- a/src/interaction/InteractionTrackingData.js +++ b/src/interaction/InteractionTrackingData.js @@ -35,8 +35,10 @@ } /** + * Unique pointer id of the event + * * @readonly - * @type {number} Unique pointer id of the event + * @member {number} */ get pointerId() { diff --git a/src/interaction/index.js b/src/interaction/index.js index 56c5b09..72f7d71 100644 --- a/src/interaction/index.js +++ b/src/interaction/index.js @@ -1,4 +1,8 @@ /** + * This namespace contains a renderer plugin for handling mouse, pointer, and touch events. + * + * Do not instantiate this plugin directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. * @namespace PIXI.interaction */ export { default as InteractionData } from './InteractionData'; diff --git a/src/interaction/interactiveTarget.js b/src/interaction/interactiveTarget.js index d252fba..cebac40 100644 --- a/src/interaction/interactiveTarget.js +++ b/src/interaction/interactiveTarget.js @@ -2,7 +2,7 @@ * Default property values of interactive objects * Used by {@link PIXI.interaction.InteractionManager} to automatically give all DisplayObjects these properties * - * @mixin + * @private * @name interactiveTarget * @memberof PIXI.interaction * @example @@ -14,18 +14,28 @@ * ); */ export default { + /** - * Determines if the displayObject be clicked/touched + * Enable interaction events for the DisplayObject. Touch, pointer and mouse + * events will not be emitted unless `interactive` is set to `true`. * - * @inner {boolean} + * @example + * const sprite = new PIXI.Sprite(texture); + * sprite.interactive = true; + * sprite.on('tap', (event) => { + * //handle event + * }); + * @member {boolean} + * @memberof PIXI.DisplayObject# */ interactive: false, /** * Determines if the children to the displayObject can be clicked/touched - * Setting this to false allows pixi to bypass a recursive hitTest function + * Setting this to false allows pixi to bypass a recursive `hitTest` function * - * @inner {boolean} + * @member {boolean} + * @memberof PIXI.Container# */ interactiveChildren: true, @@ -33,7 +43,12 @@ * Interaction shape. Children will be hit first, then this shape will be checked. * Setting this will cause this shape to be checked in hit tests rather than the displayObject's bounds. * - * @inner {PIXI.Rectangle|PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.RoundedRectangle} + * @example + * const sprite = new PIXI.Sprite(texture); + * sprite.interactive = true; + * sprite.hitArea = new PIXI.Rectangle(0, 0, 100, 100); + * @member {PIXI.Rectangle|PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.RoundedRectangle} + * @memberof PIXI.DisplayObject# */ hitArea: null, @@ -41,8 +56,12 @@ * If enabled, the mouse cursor use the pointer behavior when hovered over the displayObject if it is interactive * Setting this changes the 'cursor' property to `'pointer'`. * + * @example + * const sprite = new PIXI.Sprite(texture); + * sprite.interactive = true; + * sprite.buttonMode = true; * @member {boolean} - * @memberof PIXI.interaction.interactiveTarget# + * @memberof PIXI.DisplayObject# */ get buttonMode() { @@ -64,9 +83,14 @@ * This defines what cursor mode is used when the mouse cursor * is hovered over the displayObject. * + * @example + * const sprite = new PIXI.Sprite(texture); + * sprite.interactive = true; + * sprite.cursor = 'wait'; * @see https://developer.mozilla.org/en/docs/Web/CSS/cursor * - * @inner {string} + * @member {string} + * @memberof PIXI.DisplayObject# */ cursor: null, @@ -74,7 +98,7 @@ * Internal set of all active pointers, by identifier * * @member {Map} - * @memberof PIXI.interaction.interactiveTarget# + * @memberof PIXI.DisplayObject# * @private */ get trackedPointers() @@ -87,7 +111,8 @@ /** * Map of all tracked pointers, by identifier. Use trackedPointers to access. * - * @private {Map} + * @private + * @type {Map} */ _trackedPointers: undefined, }; diff --git a/src/loaders/index.js b/src/loaders/index.js index 5189793..daa1fc4 100644 --- a/src/loaders/index.js +++ b/src/loaders/index.js @@ -1,9 +1,22 @@ +import Application from '../core/Application'; +import Loader from './loader'; + /** + * This namespace contains APIs which extends the {@link https://github.com/englercj/resource-loader resource-loader} module + * for loading assets, data, and other resources dynamically. + * @example + * const loader = new PIXI.loaders.Loader(); + * loader.add('bunny', 'data/bunny.png') + * .add('spaceship', 'assets/spritesheet.json'); + * loader.load((loader, resources) => { + * // resources.bunny + * // resources.spaceship + * }); * @namespace PIXI.loaders */ -export { default as Loader } from './loader'; +export { Loader }; export { default as bitmapFontParser, parse as parseBitmapFontData } from './bitmapFontParser'; -export { default as spritesheetParser } from './spritesheetParser'; +export { default as spritesheetParser, getResourcePath } from './spritesheetParser'; export { default as textureParser } from './textureParser'; /** @@ -13,3 +26,55 @@ * @memberof PIXI.loaders */ export { Resource } from 'resource-loader'; + +/** + * A premade instance of the loader that can be used to load resources. + * @name shared + * @memberof PIXI.loaders + * @type {PIXI.loaders.Loader} + */ +const shared = new Loader(); + +shared.destroy = () => +{ + // protect destroying shared loader +}; + +export { shared }; + +// Mixin the loader construction +const AppPrototype = Application.prototype; + +AppPrototype._loader = null; + +/** + * Loader instance to help with asset loading. + * @name PIXI.Application#loader + * @type {PIXI.loaders.Loader} + */ +Object.defineProperty(AppPrototype, 'loader', { + get() + { + if (!this._loader) + { + const sharedLoader = this._options.sharedLoader; + + this._loader = sharedLoader ? shared : new Loader(); + } + + return this._loader; + }, +}); + +// Override the destroy function +// making sure to destroy the current Loader +AppPrototype._parentDestroy = AppPrototype.destroy; +AppPrototype.destroy = function destroy() +{ + if (this._loader) + { + this._loader.destroy(); + this._loader = null; + } + this._parentDestroy(); +}; diff --git a/src/loaders/loader.js b/src/loaders/loader.js index 767a411..59b8000 100644 --- a/src/loaders/loader.js +++ b/src/loaders/loader.js @@ -90,6 +90,15 @@ { Loader._pixiMiddleware.push(fn); } + + /** + * Destroy the loader, removes references. + */ + destroy() + { + this.removeAllListeners(); + this.reset(); + } } // Copy EE3 prototype (mixin) diff --git a/src/loaders/spritesheetParser.js b/src/loaders/spritesheetParser.js index e57dfb2..eda9584 100644 --- a/src/loaders/spritesheetParser.js +++ b/src/loaders/spritesheetParser.js @@ -1,12 +1,11 @@ import { Resource } from 'resource-loader'; -import path from 'path'; +import url from 'url'; import { Spritesheet } from '../core'; export default function () { return function spritesheetParser(resource, next) { - let resourcePath; const imageResourceName = `${resource.name}_image`; // skip if no data, its not json, it isn't spritesheet data, or the image resource already exists @@ -28,15 +27,7 @@ parentResource: resource, }; - // Prepend url path unless the resource image is a data url - if (resource.isDataUrl) - { - resourcePath = resource.data.meta.image; - } - else - { - resourcePath = `${path.dirname(resource.url.replace(this.baseUrl, ''))}/${resource.data.meta.image}`; - } + const resourcePath = getResourcePath(resource, this.baseUrl); // load the image for this sheet this.add(imageResourceName, resourcePath, loadOptions, function onImageLoad(res) @@ -56,3 +47,14 @@ }); }; } + +export function getResourcePath(resource, baseUrl) +{ + // Prepend url path unless the resource image is a data url + if (resource.isDataUrl) + { + return resource.data.meta.image; + } + + return url.resolve(resource.url.replace(baseUrl, ''), resource.data.meta.image); +} diff --git a/src/mesh/canvas/CanvasMeshRenderer.js b/src/mesh/canvas/CanvasMeshRenderer.js index 297205f..f7899e1 100644 --- a/src/mesh/canvas/CanvasMeshRenderer.js +++ b/src/mesh/canvas/CanvasMeshRenderer.js @@ -134,12 +134,33 @@ const textureWidth = base.width; const textureHeight = base.height; - const u0 = uvs[index0] * base.width; - const u1 = uvs[index1] * base.width; - const u2 = uvs[index2] * base.width; - const v0 = uvs[index0 + 1] * base.height; - const v1 = uvs[index1 + 1] * base.height; - const v2 = uvs[index2 + 1] * base.height; + let u0; + let u1; + let u2; + let v0; + let v1; + let v2; + + if (mesh.uploadUvTransform) + { + const ut = mesh._uvTransform.mapCoord; + + u0 = ((uvs[index0] * ut.a) + (uvs[index0 + 1] * ut.c) + ut.tx) * base.width; + u1 = ((uvs[index1] * ut.a) + (uvs[index1 + 1] * ut.c) + ut.tx) * base.width; + u2 = ((uvs[index2] * ut.a) + (uvs[index2 + 1] * ut.c) + ut.tx) * base.width; + v0 = ((uvs[index0] * ut.b) + (uvs[index0 + 1] * ut.d) + ut.ty) * base.height; + v1 = ((uvs[index1] * ut.b) + (uvs[index1 + 1] * ut.d) + ut.ty) * base.height; + v2 = ((uvs[index2] * ut.b) + (uvs[index2 + 1] * ut.d) + ut.ty) * base.height; + } + else + { + u0 = uvs[index0] * base.width; + u1 = uvs[index1] * base.width; + u2 = uvs[index2] * base.width; + v0 = uvs[index0 + 1] * base.height; + v1 = uvs[index1 + 1] * base.height; + v2 = uvs[index2 + 1] * base.height; + } let x0 = vertices[index0]; let x1 = vertices[index1]; diff --git a/src/mesh/webgl/MeshRenderer.js b/src/mesh/webgl/MeshRenderer.js index 78d24c3..ab99fcd 100644 --- a/src/mesh/webgl/MeshRenderer.js +++ b/src/mesh/webgl/MeshRenderer.js @@ -2,6 +2,8 @@ const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; +const matrixIdentity = core.Matrix.IDENTITY; + /** * WebGL renderer plugin for tiling sprites * @@ -64,6 +66,7 @@ mesh.geometry.glVertexArrayObjects[this.CONTEXT_UID].draw(mesh.drawMode, mesh.size, mesh.start, mesh.geometry.instanceCount); } + /** * draws mesh * @param {PIXI.mesh.RawMesh} mesh mesh instance diff --git a/src/mesh/webgl/mesh.vert b/src/mesh/webgl/mesh.vert index a337aef..acc096c 100644 --- a/src/mesh/webgl/mesh.vert +++ b/src/mesh/webgl/mesh.vert @@ -1,8 +1,9 @@ attribute vec2 aVertexPosition; attribute vec2 aTextureCoord; -uniform mat3 translationMatrix; uniform mat3 projectionMatrix; +uniform mat3 translationMatrix; +uniform mat3 uTransform; varying vec2 vTextureCoord; @@ -10,5 +11,5 @@ { gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - vTextureCoord = aTextureCoord; + vTextureCoord = (uTransform * vec3(aTextureCoord, 1.0)).xy; } diff --git a/src/particles/ParticleContainer.js b/src/particles/ParticleContainer.js index 67796cf..63f7791 100644 --- a/src/particles/ParticleContainer.js +++ b/src/particles/ParticleContainer.js @@ -313,10 +313,10 @@ frame.y * resolution, frame.width * resolution, frame.height * resolution, - positionX * resolution, - positionY * resolution, - finalWidth * resolution, - finalHeight * resolution + positionX * renderer.resolution, + positionY * renderer.resolution, + finalWidth * renderer.resolution, + finalHeight * renderer.resolution ); } } diff --git a/src/prepare/BasePrepare.js b/src/prepare/BasePrepare.js index 09eb5e5..e45c4dc 100644 --- a/src/prepare/BasePrepare.js +++ b/src/prepare/BasePrepare.js @@ -18,6 +18,18 @@ * basic queuing functionality and is extended by {@link PIXI.prepare.WebGLPrepare} and {@link PIXI.prepare.CanvasPrepare} * to provide preparation capabilities specific to their respective renderers. * + * @example + * // Create a sprite + * const sprite = new PIXI.Sprite.fromImage('something.png'); + * + * // Load object into GPU + * app.renderer.plugins.prepare.upload(sprite, () => { + * + * //Texture(s) has been uploaded to GPU + * app.stage.addChild(sprite); + * + * }) + * * @abstract * @class * @memberof PIXI.prepare @@ -100,8 +112,16 @@ this.prepareItems(); }; - this.register(findText, drawText); - this.register(findTextStyle, calculateTextStyle); + // hooks to find the correct texture + this.registerFindHook(findText); + this.registerFindHook(findTextStyle); + this.registerFindHook(findMultipleBaseTextures); + this.registerFindHook(findBaseTexture); + this.registerFindHook(findTexture); + + // upload hooks + this.registerUploadHook(drawText); + this.registerUploadHook(calculateTextStyle); } /** @@ -138,7 +158,7 @@ if (!this.ticking) { this.ticking = true; - SharedTicker.addOnce(this.tick, this); + SharedTicker.addOnce(this.tick, this, core.UPDATE_PRIORITY.UTILITY); } } else if (done) @@ -172,13 +192,16 @@ const item = this.queue[0]; let uploaded = false; - for (let i = 0, len = this.uploadHooks.length; i < len; i++) + if (item && !item._destroyed) { - if (this.uploadHooks[i](this.uploadHookHelper, item)) + for (let i = 0, len = this.uploadHooks.length; i < len; i++) { - this.queue.shift(); - uploaded = true; - break; + if (this.uploadHooks[i](this.uploadHookHelper, item)) + { + this.queue.shift(); + uploaded = true; + break; + } } } @@ -205,26 +228,36 @@ else { // if we are not finished, on the next rAF do this again - SharedTicker.addOnce(this.tick, this); + SharedTicker.addOnce(this.tick, this, core.UPDATE_PRIORITY.UTILITY); } } /** - * Adds hooks for finding and uploading items. + * Adds hooks for finding items. * - * @param {Function} [addHook] - Function call that takes two parameters: `item:*, queue:Array` - function must return `true` if it was able to add item to the queue. - * @param {Function} [uploadHook] - Function call that takes two parameters: `prepare:CanvasPrepare, item:*` and - * function must return `true` if it was able to handle upload of item. - * @return {PIXI.CanvasPrepare} Instance of plugin for chaining. + * @param {Function} addHook - Function call that takes two parameters: `item:*, queue:Array` + * function must return `true` if it was able to add item to the queue. + * @return {PIXI.BasePrepare} Instance of plugin for chaining. */ - register(addHook, uploadHook) + registerFindHook(addHook) { if (addHook) { this.addHooks.push(addHook); } + return this; + } + + /** + * Adds hooks for uploading items. + * + * @param {Function} uploadHook - Function call that takes two parameters: `prepare:CanvasPrepare, item:*` and + * function must return `true` if it was able to handle upload of item. + * @return {PIXI.BasePrepare} Instance of plugin for chaining. + */ + registerUploadHook(uploadHook) + { if (uploadHook) { this.uploadHooks.push(uploadHook); @@ -287,6 +320,88 @@ } /** + * Built-in hook to find multiple textures from objects like AnimatedSprites. + * + * @private + * @param {PIXI.DisplayObject} item - Display object to check + * @param {Array<*>} queue - Collection of items to upload + * @return {boolean} if a PIXI.Texture object was found. + */ +function findMultipleBaseTextures(item, queue) +{ + let result = false; + + // Objects with mutliple textures + if (item && item._textures && item._textures.length) + { + for (let i = 0; i < item._textures.length; i++) + { + if (item._textures[i] instanceof core.Texture) + { + const baseTexture = item._textures[i].baseTexture; + + if (queue.indexOf(baseTexture) === -1) + { + queue.push(baseTexture); + result = true; + } + } + } + } + + return result; +} + +/** + * Built-in hook to find BaseTextures from Sprites. + * + * @private + * @param {PIXI.DisplayObject} item - Display object to check + * @param {Array<*>} queue - Collection of items to upload + * @return {boolean} if a PIXI.Texture object was found. + */ +function findBaseTexture(item, queue) +{ + // Objects with textures, like Sprites/Text + if (item instanceof core.BaseTexture) + { + if (queue.indexOf(item) === -1) + { + queue.push(item); + } + + return true; + } + + return false; +} + +/** + * Built-in hook to find textures from objects. + * + * @private + * @param {PIXI.DisplayObject} item - Display object to check + * @param {Array<*>} queue - Collection of items to upload + * @return {boolean} if a PIXI.Texture object was found. + */ +function findTexture(item, queue) +{ + if (item._texture && item._texture instanceof core.Texture) + { + const texture = item._texture.baseTexture; + + if (queue.indexOf(texture) === -1) + { + queue.push(texture); + } + + return true; + } + + return false; +} + +/** * Built-in hook to draw PIXI.Text to its texture. * * @private diff --git a/src/prepare/canvas/CanvasPrepare.js b/src/prepare/canvas/CanvasPrepare.js index 0224bb5..09c207a 100644 --- a/src/prepare/canvas/CanvasPrepare.js +++ b/src/prepare/canvas/CanvasPrepare.js @@ -43,7 +43,7 @@ this.ctx = this.canvas.getContext('2d'); // Add textures to upload - this.register(findBaseTextures, uploadBaseTextures); + this.registerUploadHook(uploadBaseTextures); } /** @@ -89,39 +89,4 @@ return false; } -/** - * Built-in hook to find textures from Sprites. - * - * @private - * @param {PIXI.DisplayObject} item -Display object to check - * @param {Array<*>} queue - Collection of items to upload - * @return {boolean} if a PIXI.Texture object was found. - */ -function findBaseTextures(item, queue) -{ - // Objects with textures, like Sprites/Text - if (item instanceof core.BaseTexture) - { - if (queue.indexOf(item) === -1) - { - queue.push(item); - } - - return true; - } - else if (item._texture && item._texture instanceof core.Texture) - { - const texture = item._texture.baseTexture; - - if (queue.indexOf(texture) === -1) - { - queue.push(texture); - } - - return true; - } - - return false; -} - core.CanvasRenderer.registerPlugin('prepare', CanvasPrepare); diff --git a/src/prepare/index.js b/src/prepare/index.js index f559c45..e7491ec 100644 --- a/src/prepare/index.js +++ b/src/prepare/index.js @@ -1,4 +1,29 @@ /** + * The prepare namespace provides renderer-specific plugins for pre-rendering DisplayObjects. These plugins are useful for + * asynchronously preparing assets, textures, graphics waiting to be displayed. + * + * Do not instantiate these plugins directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. + * @example + * // Create a new application + * const app = new PIXI.Application(); + * document.body.appendChild(app.view); + * + * // Don't start rendering right away + * app.stop(); + * + * // create a display object + * const rect = new PIXI.Graphics() + * .beginFill(0x00ff00) + * .drawRect(40, 40, 200, 200); + * + * // Add to the stage + * app.stage.addChild(rect); + * + * // Don't start rendering until the graphic is uploaded to the GPU + * app.renderer.plugins.prepare.upload(app.stage, () => { + * app.start(); + * }); * @namespace PIXI.prepare */ export { default as webgl } from './webgl/WebGLPrepare'; diff --git a/src/prepare/webgl/WebGLPrepare.js b/src/prepare/webgl/WebGLPrepare.js index eb7023f..9df3c3f 100644 --- a/src/prepare/webgl/WebGLPrepare.js +++ b/src/prepare/webgl/WebGLPrepare.js @@ -22,12 +22,11 @@ this.uploadHookHelper = this.renderer; // Add textures and graphics to upload - this.register(findBaseTextures, uploadBaseTextures) - .register(findGraphics, uploadGraphics); + this.registerFindHook(findGraphics); + this.registerUploadHook(uploadBaseTextures); + this.registerUploadHook(uploadGraphics); } - } - /** * Built-in hook to upload PIXI.Texture objects to the GPU. * @@ -80,41 +79,6 @@ } /** - * Built-in hook to find textures from Sprites. - * - * @private - * @param {PIXI.DisplayObject} item - Display object to check - * @param {Array<*>} queue - Collection of items to upload - * @return {boolean} if a PIXI.Texture object was found. - */ -function findBaseTextures(item, queue) -{ - // Objects with textures, like Sprites/Text - if (item instanceof core.BaseTexture) - { - if (queue.indexOf(item) === -1) - { - queue.push(item); - } - - return true; - } - else if (item._texture && item._texture instanceof core.Texture) - { - const texture = item._texture.baseTexture; - - if (queue.indexOf(texture) === -1) - { - queue.push(texture); - } - - return true; - } - - return false; -} - -/** * Built-in hook to find graphics. * * @private diff --git a/test/.eslintrc.json b/test/.eslintrc.json index e03220d..ac12110 100644 --- a/test/.eslintrc.json +++ b/test/.eslintrc.json @@ -12,7 +12,8 @@ "sinon": false, "expect": false, "assert": false, - "PIXI": false + "PIXI": false, + "PointerEvent": true }, "rules": { "func-names": 0, diff --git a/test/core/BaseTexture.js b/test/core/BaseTexture.js index 8b93478..bb65068 100644 --- a/test/core/BaseTexture.js +++ b/test/core/BaseTexture.js @@ -14,4 +14,28 @@ expect(baseTexture.imageType).to.be.equals('png'); }); }); + + it('should remove Canvas BaseTexture from cache on destroy', function () + { + const canvas = document.createElement('canvas'); + const texture = PIXI.BaseTexture.fromCanvas(canvas); + const _pixiId = canvas._pixiId; + + expect(PIXI.utils.BaseTextureCache[_pixiId]).to.equal(texture); + texture.destroy(); + expect(PIXI.utils.BaseTextureCache[_pixiId]).to.equal(undefined); + }); + + it('should remove Image BaseTexture from cache on destroy', function () + { + const URL = 'foo.png'; + const NAME = 'bar'; + const image = new Image(); + + const texture = PIXI.Texture.fromLoader(image, URL, NAME); + + expect(PIXI.utils.BaseTextureCache[NAME]).to.equal(texture.baseTexture); + texture.destroy(true); + expect(PIXI.utils.BaseTextureCache[NAME]).to.equal(undefined); + }); }); diff --git a/test/core/Circle.js b/test/core/Circle.js index 183f98f..f621992 100644 --- a/test/core/Circle.js +++ b/test/core/Circle.js @@ -53,6 +53,10 @@ expect(circ1.contains(10, 16)).to.be.false; expect(circ1.contains(11, 15)).to.be.false; expect(circ1.contains(0, 0)).to.be.false; + + const circ2 = new PIXI.Circle(10, 10, 0); + + expect(circ2.contains(10, 10)).to.be.false; }); it('should return framing rectangle', function () diff --git a/test/core/Container.js b/test/core/Container.js index d11be33..cefde54 100644 --- a/test/core/Container.js +++ b/test/core/Container.js @@ -2,30 +2,36 @@ function testAddChild(fn) { - fn(function (container, obj) + return function () { - container.addChild(obj); - }); - fn(function (container, obj) - { - container.addChildAt(obj); - }); + fn(function (container, obj) + { + container.addChild(obj); + }); + fn(function (container, obj) + { + container.addChildAt(obj); + }); + }; } function testRemoveChild(fn) { - fn(function (container, obj) + return function () { - container.removeChild(obj); - }); - fn(function (container, obj) - { - container.removeChildAt(container.children.indexOf(obj)); - }); - fn(function (container, obj) - { - container.removeChildren(container.children.indexOf(obj), container.children.indexOf(obj) + 1); - }); + fn(function (container, obj) + { + container.removeChild(obj); + }); + fn(function (container, obj) + { + container.removeChildAt(container.children.indexOf(obj)); + }); + fn(function (container, obj) + { + container.removeChildren(container.children.indexOf(obj), container.children.indexOf(obj) + 1); + }); + }; } describe('PIXI.Container', function () diff --git a/.travis.yml b/.travis.yml index 3df8b22..14de334 100644 --- a/.travis.yml +++ b/.travis.yml @@ -110,3 +110,43 @@ on: all_branches: true condition: $TRAVIS_TAG + # Deploy config for latest release + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: dist + upload-dir: "release" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: docs + upload-dir: "release/docs" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: coverage + upload-dir: "release/coverage" + on: + all_branches: true + condition: $TRAVIS_TAG \ No newline at end of file diff --git a/README.md b/README.md index d26fadd..3b85f85 100644 --- a/README.md +++ b/README.md @@ -36,22 +36,14 @@ - Wiki: Other misc tutorials and resources are [on the Wiki](https://github.com/pixijs/pixi.js/wiki/Resources). ### Community ### -- Forums: Check out the [forum] (http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow] (http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. +- Forums: Check out the [forum](http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow](http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. - Inspiration: Check out the [gallery](http://www.pixijs.com/gallery) to see some of the amazing things people have created! - Chat: You can join us on [Gitter](https://gitter.im/pixijs/pixi.js) To chat about Pixi. We also now have a Slack channel. If you would like to join it please Send me an email (mat@goodboydigital.com) and I will invite you in. ### Setup ### -It's easy to get started with Pixi.js! Simply grab the pre-built versions from here: - -Release Branch - Nice and stable Pixi.js -- Unminified: [http://pixijs.download/release/pixi.js] -- Minified: [http://pixijs.download/release/pixi.min.js] - -Develop Branch - The bleeding edge version of Pixi.js -- Unminified: [http://pixijs.download/dev/pixi.js] -- Minified: [http://pixijs.download/dev/pixi.min.js] +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. @@ -70,10 +62,10 @@ #### CDN Install (via cdnjs) ```html - + ``` -_Note: `4.2.2` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ +_Note: `4.4.3` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ ### Demos ### @@ -130,7 +122,7 @@ // and the root stage PIXI.Container. var app = new PIXI.Application(); -// The application will create a canvas element for you that you +// The application will create a canvas element for you that you // can then insert into the DOM. document.body.appendChild(app.view); @@ -150,7 +142,7 @@ // Add the bunny to the scene we are building. app.stage.addChild(bunny); - + // Listen for frame updates app.ticker.add(function() { // each frame we spin the bunny around a bit diff --git a/bower.json b/bower.json deleted file mode 100644 index 41b668c..0000000 --- a/bower.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "pixi.js", - "main": "dist/pixi.min.js", - "ignore": [ - "**/.*", - "docs", - "node_modules", - "bower_components", - "test", - "scripts", - "bower.json", - "inch.json" - ], - "dependencies": { - }, - "devDependencies": { - } -} diff --git a/package.json b/package.json index d35cbb2..308469f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.3.5", + "version": "4.4.4", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ @@ -40,11 +40,11 @@ "lib": "babel src --out-dir lib -s", "predocs": "rimraf docs/**", "docs": "jsdoc -c scripts/jsdoc.conf.json -R README.md", - "publish:patch": "npm version patch --no-git-tag-version && npm publish", - "publish:minor": "npm version minor --no-git-tag-version && npm publish", - "publish:major": "npm version major --no-git-tag-version && npm publish", + "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", - "postpublish": "node scripts/release.js" + "postpublish": "git push && git push --tags" }, "files": [ "dist/", @@ -72,7 +72,6 @@ "electron": "^1.4.15", "eslint": "^3.5.0", "floss": "^2.0.1", - "gh-pages": "^0.11.0", "jaguarjs-jsdoc": "^1.0.1", "js-md5": "^0.4.1", "jsdoc": "^3.4.2", diff --git a/scripts/jsdoc.conf.json b/scripts/jsdoc.conf.json index 553e61c..3f52180 100644 --- a/scripts/jsdoc.conf.json +++ b/scripts/jsdoc.conf.json @@ -43,7 +43,7 @@ }, "markdown" : { "parser" : "gfm", - "hardwrap" : true + "hardwrap" : false }, "opts": { "encoding" : "utf8", diff --git a/scripts/release.js b/scripts/release.js deleted file mode 100644 index 156620c..0000000 --- a/scripts/release.js +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env node -'use strict'; - -// Publish script to push releases of the bin files -// the normally are gitignored -const ghpages = require('gh-pages'); -const path = require('path'); -const packageInfo = require(path.join(__dirname, '..', 'package.json')); - -const options = { - src: [ - 'dist/**/*', - 'lib/**/*', - 'src/**/*', - 'scripts/*', - 'scripts/renders/*', - 'test/**/*', - '*.json', - '*.md', - 'LICENSE', - '.eslintrc', - '.editorconfig', - '.travis.yml', - '.babelrc', - ], - dotfiles: true, - branch: 'release', - message: packageInfo.version, - logger: console.log.bind(console), -}; - -ghpages.publish(process.cwd(), options, (err) => -{ - if (err) - { - console.log(err); - process.exit(1); - - return; - } - - process.exit(0); -}); diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index f16bcb1..220427a 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -21,7 +21,7 @@ const DIV_HOOK_ZINDEX = 2; /** - * The Accessibility manager reacreates the ability to tab and and have content read by screen + * The Accessibility manager recreates the ability to tab and and have content read by screen * readers. This is very important as it can possibly help people with disabilities access pixi * content. * diff --git a/src/accessibility/index.js b/src/accessibility/index.js index 568cfb1..0ae108f 100644 --- a/src/accessibility/index.js +++ b/src/accessibility/index.js @@ -1,4 +1,9 @@ /** + * This namespace contains a renderer plugin for interaction accessibility for end-users + * with physical impairments which require screen-renders, keyboard navigation, etc. + * + * Do not instantiate this plugin directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. * @namespace PIXI.accessibility */ export { default as accessibleTarget } from './accessibleTarget'; diff --git a/src/core/Application.js b/src/core/Application.js index 5340578..2fcc729 100644 --- a/src/core/Application.js +++ b/src/core/Application.js @@ -1,6 +1,8 @@ import { autoDetectRenderer } from './autoDetectRenderer'; import Container from './display/Container'; import { shared, Ticker } from './ticker'; +import settings from './settings'; +import { UPDATE_PRIORITY } from './const'; /** * Convenience class to create a new PIXI application. @@ -22,28 +24,52 @@ */ export default class Application { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experience unexplained flickering try setting this to true. - * @param {boolean} [useSharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedLoader=false] - `true` to use PIXI.loaders.shared, `false` to create new Loader. */ - constructor(width, height, options, noWebGL, useSharedTicker = false) + constructor(options, arg2, arg3, arg4, arg5) { + // Support for constructor(width, height, options, noWebGL, useSharedTicker) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + forceCanvas: !!arg4, + sharedTicker: !!arg5, + }, arg3); + } + + /** + * The default options, so we mixin functionality later. + * @member {object} + * @protected + */ + this._options = options = Object.assign({ + sharedTicker: false, + forceCanvas: false, + sharedLoader: false, + }, options); + /** * WebGL renderer if available, otherwise CanvasRenderer * @member {PIXI.WebGLRenderer|PIXI.CanvasRenderer} */ - this.renderer = autoDetectRenderer(width, height, options, noWebGL); + this.renderer = autoDetectRenderer(options); /** * The root display container that's rendered. @@ -63,7 +89,7 @@ * @member {PIXI.ticker.Ticker} * @default PIXI.ticker.shared */ - this.ticker = useSharedTicker ? shared : new Ticker(); + this.ticker = options.sharedTicker ? shared : new Ticker(); // Start the rendering this.start(); @@ -78,7 +104,7 @@ this._ticker = ticker; if (ticker) { - ticker.add(this.render, this); + ticker.add(this.render, this, UPDATE_PRIORITY.LOW); } } get ticker() // eslint-disable-line require-jsdoc @@ -136,13 +162,18 @@ */ destroy(removeView) { - this.stop(); + const oldTicker = this._ticker; + this.ticker = null; + oldTicker.destroy(); + this.stage.destroy(); this.stage = null; this.renderer.destroy(removeView); this.renderer = null; + + this._options = null; } } diff --git a/src/core/autoDetectRenderer.js b/src/core/autoDetectRenderer.js index 8960425..9ec0364 100644 --- a/src/core/autoDetectRenderer.js +++ b/src/core/autoDetectRenderer.js @@ -2,6 +2,7 @@ import CanvasRenderer from './renderers/canvas/CanvasRenderer'; import WebGLRenderer from './renderers/webgl/WebGLRenderer'; +// eslint-disable-next-line valid-jsdoc /** * This helper function will automatically detect which renderer you should be using. * WebGL is the preferred renderer as it is a lot faster. If webGL is not supported by @@ -9,24 +10,32 @@ * * @memberof PIXI * @function autoDetectRenderer - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @return {PIXI.WebGLRenderer|PIXI.CanvasRenderer} Returns WebGL renderer if available, otherwise CanvasRenderer */ -export function autoDetectRenderer(width = 800, height = 600, options, noWebGL) +export function autoDetectRenderer(options, arg1, arg2, arg3) { - if (!noWebGL && utils.isWebGLSupported()) + // Backward-compatible support for noWebGL option + let forceCanvas = options && options.forceCanvas; + + if (arg3 !== undefined) { - return new WebGLRenderer(width, height, options); + forceCanvas = arg3; } - return new CanvasRenderer(width, height, options); + if (!forceCanvas && utils.isWebGLSupported()) + { + return new WebGLRenderer(options, arg1, arg2); + } + + return new CanvasRenderer(options, arg1, arg2); } diff --git a/src/core/const.js b/src/core/const.js index b9e99ae..2c68dee 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -341,3 +341,27 @@ LINEAR_VERTICAL: 0, LINEAR_HORIZONTAL: 1, }; + +/** + * Represents the update priorities used by internal PIXI classes when registered with + * the {@link PIXI.ticker.Ticker} object. Higher priority items are updated first and lower + * priority items, such as render, should go later. + * + * @static + * @constant + * @name UPDATE_PRIORITY + * @memberof PIXI + * @type {object} + * @property {number} INTERACTION=50 Highest priority, used for {@link PIXI.interaction.InteractionManager} + * @property {number} HIGH=25 High priority updating, {@link PIXI.VideoBaseTexture} and {@link PIXI.extras.AnimatedSprite} + * @property {number} NORMAL=0 Default priority for ticker events, see {@link PIXI.ticker.Ticker#add}. + * @property {number} LOW=-25 Low priority used for {@link PIXI.Application} rendering. + * @property {number} UTILITY=-50 Lowest priority used for {@link PIXI.prepare.BasePrepare} utility. + */ +export const UPDATE_PRIORITY = { + INTERACTION: 50, + HIGH: 25, + NORMAL: 0, + LOW: -25, + UTILITY: -50, +}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 4e6c77d..decb42a 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -13,7 +13,6 @@ * * @class * @extends EventEmitter - * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ export default class DisplayObject extends EventEmitter @@ -113,6 +112,29 @@ * @private */ this._mask = null; + + /** + * If the object has been destroyed via destroy(). If true, it should not be used. + * + * @member {boolean} + * @private + * @readonly + */ + this._destroyed = false; + + /** + * Fired when this DisplayObject is added to a Container. + * + * @event PIXI.DisplayObject#added + * @param {PIXI.Container} container - The container added to. + */ + + /** + * Fired when this DisplayObject is removed from a Container. + * + * @event PIXI.DisplayObject#removed + * @param {PIXI.Container} container - The container removed from. + */ } /** @@ -403,6 +425,8 @@ this.interactive = false; this.interactiveChildren = false; + + this._destroyed = true; } /** diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index aafdfe2..f787c4a 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -792,8 +792,8 @@ sprite.worldAlpha = this.worldAlpha * sprite.alpha; sprite.blendMode = this.blendMode; - sprite.texture._frame.width = rect.width; - sprite.texture._frame.height = rect.height; + sprite._texture._frame.width = rect.width; + sprite._texture._frame.height = rect.height; sprite.transform.worldTransform = this.transform.worldTransform; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index a24c775..b9f192d 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -19,11 +19,12 @@ */ export default class SystemRenderer extends EventEmitter { + // eslint-disable-next-line valid-jsdoc /** * @param {string} system - The name of the system this renderer is for. - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -37,27 +38,31 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(system, screenWidth, screenHeight, options) + constructor(system, options, arg2, arg3) { super(); sayHello(system); - // prepare options - if (options) + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') { - for (const i in settings.RENDER_OPTIONS) - { - if (typeof options[i] === 'undefined') - { - options[i] = settings.RENDER_OPTIONS[i]; - } - } + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); } - else - { - options = settings.RENDER_OPTIONS; - } + + // Add the default render options + options = Object.assign({}, settings.RENDER_OPTIONS, options); + + /** + * The supplied constructor options. + * + * @member {Object} + * @readOnly + */ + this.options = options; /** * The type of the renderer. @@ -75,7 +80,7 @@ * * @member {PIXI.Rectangle} */ - this.screen = new Rectangle(0, 0, screenWidth || 800, screenHeight || 600); + this.screen = new Rectangle(0, 0, options.width, options.height); /** * The canvas element that everything is drawn to @@ -279,6 +284,8 @@ this.blendModes = null; + this.options = null; + this.preserveDrawingBuffer = false; this.clearBeforeRender = false; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index e84e49c..691caf0 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -17,10 +17,11 @@ */ export default class CanvasRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -34,9 +35,9 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('Canvas', screenWidth, screenHeight, options); + super('Canvas', options, arg2, arg3); this.type = RENDERER_TYPE.CANVAS; @@ -96,7 +97,19 @@ this.context = null; this.renderingToScreen = false; - this.resize(screenWidth, screenHeight); + this.resize(this.options.width, this.options.height); + + /** + * Fired after rendering finishes. + * + * @event PIXI.CanvasRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.CanvasRenderer#prerender + */ } /** @@ -299,4 +312,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.CanvasRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.CanvasExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.CanvasPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.CanvasRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index e1cf7cd..f6fe123 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -31,11 +31,12 @@ */ export default class WebGLRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** * - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -55,9 +56,9 @@ * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('WebGL', screenWidth, screenHeight, options); + super('WebGL', options, arg2, arg3); /** * The type of this renderer as a standardised const @@ -89,12 +90,6 @@ }; - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ this._backgroundColorRgba[3] = this.transparent ? 0 : 1; this.globalUniforms = new UniformGroup({ @@ -117,6 +112,13 @@ this.initPlugins(); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ if(options.context) { this.context.initFromContext(options.context); @@ -169,6 +171,25 @@ } return this; + + /** + * Fired after rendering finishes. + * + * @event PIXI.WebGLRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.WebGLRenderer#prerender + */ + + /** + * Fired when the WebGL context is set. + * + * @event PIXI.WebGLRenderer#context + * @param {WebGLRenderingContext} gl - WebGL context. + */ } /** @@ -295,4 +316,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.WebGLRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.WebGLRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(WebGLRenderer); diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index 4eb6128..fa3d883 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -47,6 +47,14 @@ * @member {boolean} */ this.enabled = true; + + /** + * If enabled, pixi will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; } /** diff --git a/src/core/renderers/webgl/systems/ContextSystem.js b/src/core/renderers/webgl/systems/ContextSystem.js index ff958c7..8df85ea 100644 --- a/src/core/renderers/webgl/systems/ContextSystem.js +++ b/src/core/renderers/webgl/systems/ContextSystem.js @@ -75,7 +75,8 @@ */ createContext(canvas, options) { - var gl = canvas.getContext('webgl', options) || + var gl = //canvas.getContext('webgl2', options) || + canvas.getContext('webgl', options) || canvas.getContext('experimental-webgl', options); if (!gl) diff --git a/src/core/renderers/webgl/systems/FilterSystem.js b/src/core/renderers/webgl/systems/FilterSystem.js index f47335d..37e28d2 100644 --- a/src/core/renderers/webgl/systems/FilterSystem.js +++ b/src/core/renderers/webgl/systems/FilterSystem.js @@ -111,7 +111,7 @@ // TODO we should fit the rect around the transform.. } - else + else if (filters[0].autoFit) { sourceFrame.fit(filterData.stack[0].destinationFrame); } diff --git a/src/core/renderers/webgl/systems/shader/shader/mapType.js b/src/core/renderers/webgl/systems/shader/shader/mapType.js index 581dace..917538a 100644 --- a/src/core/renderers/webgl/systems/shader/shader/mapType.js +++ b/src/core/renderers/webgl/systems/shader/shader/mapType.js @@ -15,7 +15,7 @@ } } - return GL_TABLE[type]; + return GL_TABLE[type]; }; var GL_TABLE = null; diff --git a/src/core/settings.js b/src/core/settings.js index 18c5877..38ef45c 100644 --- a/src/core/settings.js +++ b/src/core/settings.js @@ -2,6 +2,15 @@ import canUploadSameBuffer from './utils/canUploadSameBuffer'; /** + * User's customizable globals for overriding the default PIXI settings, such + * as a renderer's default resolution, framerate, float percision, etc. + * @example + * // Use the native window resolution as the default resolution + * // will support high-density displays when rendering + * PIXI.settings.RESOLUTION = window.devicePixelRatio. + * + * // Disable interpolation when scaling, will make texture be pixelated + * PIXI.settings.SCALE_MODE = PIXI.SCALE_MODES.NEAREST; * @namespace PIXI.settings */ export default { @@ -101,6 +110,9 @@ * @property {boolean} clearBeforeRender=true * @property {boolean} preserveDrawingBuffer=false * @property {boolean} roundPixels=false + * @property {number} width=800 + * @property {number} height=600 + * @property {boolean} legacy=false */ RENDER_OPTIONS: { view: null, @@ -112,6 +124,9 @@ clearBeforeRender: true, preserveDrawingBuffer: false, roundPixels: false, + width: 800, + height: 600, + legacy: false, }, /** diff --git a/src/core/shader/Program.js b/src/core/shader/Program.js index c0b538b..07b4398 100644 --- a/src/core/shader/Program.js +++ b/src/core/shader/Program.js @@ -1,6 +1,6 @@ import extractUniformsFromSrc from './extractUniformsFromSrc'; import generateUniformsSync from './generateUniformsSync'; -import glCore from 'pixi-gl-core'; +import shaderUtils from '../renderers/webgl/systems/shader/shader'; import { ProgramCache } from '../utils'; import getTestContext from '../utils/getTestContext'; @@ -64,14 +64,17 @@ } else { - vertexSrc = glCore.shader.setPrecision(vertexSrc, 'mediump'); - fragmentSrc = glCore.shader.setPrecision(fragmentSrc, 'mediump'); + vertexSrc = shaderUtils.setPrecision(vertexSrc, 'mediump'); + fragmentSrc = shaderUtils.setPrecision(fragmentSrc, 'mediump'); - const program = glCore.shader.compileProgram(gl, vertexSrc, fragmentSrc); + const program = shaderUtils.compileProgram(gl, vertexSrc, fragmentSrc); + console.log("<<<<<<>>><<"); this.attributeData = this.getAttributeData(program, gl); this.uniformData = this.getUniformData(program, gl); + console.log(this.uniformData); + //gl.deleteProgram(program); } } @@ -95,13 +98,13 @@ for (let i = 0; i < totalAttributes; i++) { const attribData = gl.getActiveAttrib(program, i); - const type = glCore.shader.mapType(gl, attribData.type); + const type = shaderUtils.mapType(gl, attribData.type); /*eslint-disable */ const data = { type: type, name: attribData.name, - size: glCore.shader.mapSize(type), + size: shaderUtils.mapSize(type), location: 0, }; /*eslint-enable */ @@ -137,13 +140,14 @@ // TODO expose this as a prop? // const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); - const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); + //const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); for (let i = 0; i < totalUniforms; i++) { const uniformData = gl.getActiveUniform(program, i); const name = uniformData.name.replace(/\[.*?\]/, ''); - const type = glCore.shader.mapType(gl, uniformData.type); + const type = shaderUtils.mapType(gl, uniformData.type); + console.log('mapping ' + uniformData.type + ' to ' + type); // if (!name.match(maskRegex)) { @@ -151,7 +155,7 @@ uniforms[name] = { type: type, size: uniformData.size, - value: glCore.shader.defaultValue(type, uniformData.size), + value: shaderUtils.defaultValue(type, uniformData.size), }; /*eslint-enable */ } diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index e387e99..b57d095 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -147,12 +147,12 @@ // so if _width is 0 then width was not set.. if (this._width) { - this.scale.x = sign(this.scale.x) * this._width / this.texture.orig.width; + this.scale.x = sign(this.scale.x) * this._width / this._texture.orig.width; } if (this._height) { - this.scale.y = sign(this.scale.y) * this._height / this.texture.orig.height; + this.scale.y = sign(this.scale.y) * this._height / this._texture.orig.height; } } diff --git a/src/core/sprites/canvas/CanvasTinter.js b/src/core/sprites/canvas/CanvasTinter.js index 27e5e0c..fd9de5d 100644 --- a/src/core/sprites/canvas/CanvasTinter.js +++ b/src/core/sprites/canvas/CanvasTinter.js @@ -18,7 +18,7 @@ */ getTintedTexture: (sprite, color) => { - const texture = sprite.texture; + const texture = sprite._texture; color = CanvasTinter.roundColor(color); diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 5a4edda..8b9b797 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -344,6 +344,7 @@ const texture = this._texture; const style = this._style; + const padding = style.trim ? 0 : style.padding; texture.baseTexture.valid = true; texture.baseTexture.resolution = this.resolution; @@ -353,11 +354,11 @@ texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - texture.trim.x = -style.padding; - texture.trim.y = -style.padding; + texture.trim.x = -padding; + texture.trim.y = -padding; - texture.orig.width = texture._frame.width - (style.padding * 2); - texture.orig.height = texture._frame.height - (style.padding * 2); + texture.orig.width = texture._frame.width - (padding * 2); + texture.orig.height = texture._frame.height - (padding * 2); // call sprite onTextureUpdate to update scale if _width or _height were set this._onTextureUpdate(); diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index 853da3d..8a9c34b 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -114,6 +114,38 @@ this.cacheId = null; this.validate(); + + /** + * Fired when a not-immediately-available source finishes loading. + * + * @protected + * @event PIXI.BaseTexture#loaded + * @param {PIXI.BaseTexture} baseTexture - Resource loaded. + */ + + /** + * Fired when a not-immediately-available source fails to load. + * + * @protected + * @event PIXI.BaseTexture#error + * @param {PIXI.BaseTexture} baseTexture - Resource errored. + */ + + /** + * Fired when BaseTexture is updated. + * + * @protected + * @event PIXI.BaseTexture#update + * @param {PIXI.BaseTexture} baseTexture - Instance of texture being updated. + */ + + /** + * Fired when BaseTexture is destroyed. + * + * @protected + * @event PIXI.BaseTexture#dispose + * @param {PIXI.BaseTexture} baseTexture - Instance of texture being destroyed. + */ } setResource(resource) diff --git a/src/core/textures/Spritesheet.js b/src/core/textures/Spritesheet.js index d469c62..9b24f27 100644 --- a/src/core/textures/Spritesheet.js +++ b/src/core/textures/Spritesheet.js @@ -208,6 +208,7 @@ // lets also add the frame to pixi's global cache for fromFrame and fromImage functions TextureCache[i] = this.textures[i]; + this.textures[i].textureCacheId = i; } frameIndex++; diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 0a2c379..2213087 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -147,9 +147,9 @@ /** * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. * - * @event update - * @memberof PIXI.Texture# + * @event PIXI.Texture#update * @protected + * @param {PIXI.Texture} texture - Instance of texture being updated. */ this._updateID = 0; @@ -159,6 +159,15 @@ * @type {Object} */ this.transform = null; + + /** + * The id under which this Texture has been added to the texture cache. This is + * automatically set in certain cases, but may not always be accurate, particularly if + * the texture is in the cache under multiple ids. + * + * @member {string} + */ + this.textureCacheId = null; } /** @@ -225,11 +234,17 @@ this._uvs = null; this.trim = null; this.orig = null; + this.textureCacheId = null; this.valid = false; - this.off('dispose', this.dispose, this); - this.off('update', this.update, this); + for (const prop in TextureCache) + { + if (TextureCache[prop] === this) + { + delete TextureCache[prop]; + } + } } /** @@ -315,14 +330,15 @@ */ static fromLoader(source, imageUrl, name) { - // console.log('added from loader...') + // console.log('added from loader...') const resource = new ImageResource(source);//.from(imageUrl, crossorigin);// document.createElement('img'); - // console.log('base resource ' + resource.width); + // console.log('base resource ' + resource.width); const baseTexture = new BaseTexture(resource, settings.SCALE_MODE, getResolutionOfUrl(imageUrl)); + /// console.log('base width ' + baseTexture.width); const texture = new Texture(baseTexture); @@ -335,6 +351,7 @@ // lets also add the frame to pixi's global cache for fromFrame and fromImage fucntions BaseTextureCache[name] = baseTexture; TextureCache[name] = texture; + texture.textureCacheId = name; // also add references by url if they are different. if (name !== imageUrl) @@ -355,6 +372,10 @@ */ static addTextureToCache(texture, id) { + if (!texture.textureCacheId) + { + texture.textureCacheId = id; + } TextureCache[id] = texture; } @@ -495,6 +516,7 @@ */ Texture.EMPTY = new Texture(new BaseTexture()); removeAllHandlers(Texture.EMPTY); +removeAllHandlers(Texture.EMPTY.baseTexture); /** * A white texture of 10x10 size, used for graphics and other things @@ -505,3 +527,4 @@ */ Texture.WHITE = createWhiteTexture(); removeAllHandlers(Texture.WHITE); +removeAllHandlers(Texture.WHITE.baseTexture); diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index 59eb9fd..a9b9f51 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -1,6 +1,7 @@ import BaseTexture from './BaseTexture'; import { uid, BaseTextureCache } from '../utils'; -import * as ticker from '../ticker'; +import { shared } from '../ticker'; +import { UPDATE_PRIORITY } from '../const'; /** * A texture of a [playing] Video. @@ -125,7 +126,7 @@ if (!this._isAutoUpdating && this.autoUpdate) { - ticker.shared.add(this.update, this); + shared.add(this.update, this, UPDATE_PRIORITY.HIGH); this._isAutoUpdating = true; } } @@ -139,7 +140,7 @@ { if (this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); this._isAutoUpdating = false; } } @@ -187,7 +188,7 @@ { if (this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); } if (this.source && this.source._pixiId) @@ -281,12 +282,12 @@ if (!this._autoUpdate && this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); this._isAutoUpdating = false; } else if (this._autoUpdate && !this._isAutoUpdating) { - ticker.shared.add(this.update, this); + shared.add(this.update, this, UPDATE_PRIORITY.HIGH); this._isAutoUpdating = true; } } diff --git a/src/core/ticker/Ticker.js b/src/core/ticker/Ticker.js index 17a1517..40df138 100644 --- a/src/core/ticker/Ticker.js +++ b/src/core/ticker/Ticker.js @@ -1,12 +1,10 @@ import settings from '../settings'; -import EventEmitter from 'eventemitter3'; - -// Internal event used by composed emitter -const TICK = 'tick'; +import { UPDATE_PRIORITY } from '../const'; +import TickerListener from './TickerListener'; /** * A Ticker class that runs an update loop that other objects listen to. - * This class is composed around an EventEmitter object to add listeners + * This class is composed around listeners * meant for execution on the next requested animation frame. * Animation frames are requested only when necessary, * e.g. When the ticker is started and the emitter has listeners. @@ -22,10 +20,11 @@ constructor() { /** - * Internal emitter used to fire 'tick' event + * The first listener. All new listeners added are chained on this. * @private + * @type {TickerListener} */ - this._emitter = new EventEmitter(); + this._head = new TickerListener(null, null, Infinity); /** * Internal current frame request ID @@ -131,7 +130,7 @@ // Invoke listeners now this.update(time); // Listener side effects may have modified ticker state. - if (this.started && this._requestId === null && this._emitter.listeners(TICK, true)) + if (this.started && this._requestId === null && this._head.next) { this._requestId = requestAnimationFrame(this._tick); } @@ -148,7 +147,7 @@ */ _requestIfNeeded() { - if (this._requestId === null && this._emitter.listeners(TICK, true)) + if (this._requestId === null && this._head.next) { // ensure callbacks get correct delta this.lastTime = performance.now(); @@ -193,35 +192,72 @@ } /** - * Calls {@link module:eventemitter3.EventEmitter#on} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. + * Register a handler for tick events. Calls continuously unless + * it is removed or the ticker is stopped. * * @param {Function} fn - The listener function to be added for updates * @param {Function} [context] - The listener context + * @param {number} [priority=PIXI.UPDATE_PRIORITY.NORMAL] - The priority for emitting * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - add(fn, context) + add(fn, context, priority = UPDATE_PRIORITY.NORMAL) { - this._emitter.on(TICK, fn, context); - - this._startIfPossible(); - - return this; + return this._addListener(new TickerListener(fn, context, priority)); } /** - * Calls {@link module:eventemitter3.EventEmitter#once} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. + * Add a handler for the tick event which is only execute once. * * @param {Function} fn - The listener function to be added for one update * @param {Function} [context] - The listener context + * @param {number} [priority=PIXI.UPDATE_PRIORITY.NORMAL] - The priority for emitting * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - addOnce(fn, context) + addOnce(fn, context, priority = UPDATE_PRIORITY.NORMAL) { - this._emitter.once(TICK, fn, context); + return this._addListener(new TickerListener(fn, context, priority, true)); + } + + /** + * Internally adds the event handler so that it can be sorted by priority. + * Priority allows certain handler (user, AnimatedSprite, Interaction) to be run + * before the rendering. + * + * @private + * @param {TickerListener} listener - Current listener being added. + * @returns {PIXI.ticker.Ticker} This instance of a ticker + */ + _addListener(listener) + { + // For attaching to head + let current = this._head.next; + let previous = this._head; + + // Add the first item + if (!current) + { + listener.connect(previous); + } + else + { + // Go from highest to lowest priority + while (current) + { + if (listener.priority >= current.priority) + { + listener.connect(previous); + break; + } + previous = current; + current = current.next; + } + + // Not yet connected + if (!listener.previous) + { + listener.connect(previous); + } + } this._startIfPossible(); @@ -229,19 +265,33 @@ } /** - * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. - * It checks if the emitter has listeners for 'tick' event. - * If it does, then it cancels the animation frame. + * Removes any handlers matching the function and context parameters. + * If no handlers are left after removing, then it cancels the animation frame. * - * @param {Function} [fn] - The listener function to be removed + * @param {Function} fn - The listener function to be removed * @param {Function} [context] - The listener context to be removed * @returns {PIXI.ticker.Ticker} This instance of a ticker */ remove(fn, context) { - this._emitter.off(TICK, fn, context); + let listener = this._head.next; - if (!this._emitter.listeners(TICK, true)) + while (listener) + { + // We found a match, lets remove it + // no break to delete all possible matches + // incase a listener was added 2+ times + if (listener.match(fn, context)) + { + listener = listener.destroy(); + } + else + { + listener = listener.next; + } + } + + if (!this._head.next) { this._cancelIfNeeded(); } @@ -276,6 +326,25 @@ } /** + * Destroy the ticker and don't use after this. Calling + * this method removes all references to internal events. + */ + destroy() + { + this.stop(); + + let listener = this._head.next; + + while (listener) + { + listener = listener.destroy(true); + } + + this._head.destroy(); + this._head = null; + } + + /** * Triggers an update. An update entails setting the * current {@link PIXI.ticker.Ticker#elapsedMS}, * the current {@link PIXI.ticker.Ticker#deltaTime}, @@ -320,8 +389,22 @@ this.deltaTime = elapsedMS * settings.TARGET_FPMS * this.speed; + // Cache a local reference, in-case ticker is destroyed + // during the emit, we can still check for head.next + const head = this._head; + // Invoke listeners added to internal emitter - this._emitter.emit(TICK, this.deltaTime); + let listener = head.next; + + while (listener) + { + listener = listener.emit(this.deltaTime); + } + + if (!head.next) + { + this._cancelIfNeeded(); + } } else { diff --git a/src/core/ticker/TickerListener.js b/src/core/ticker/TickerListener.js new file mode 100644 index 0000000..2bedb34 --- /dev/null +++ b/src/core/ticker/TickerListener.js @@ -0,0 +1,158 @@ +/** + * Internal class for handling the priority sorting of ticker handlers. + * + * @private + * @class + * @memberof PIXI.ticker + */ +export default class TickerListener +{ + /** + * Constructor + * + * @param {Function} fn - The listener function to be added for one update + * @param {Function} [context=null] - The listener context + * @param {number} [priority=0] - The priority for emitting + * @param {boolean} [once=false] - If the handler should fire once + */ + constructor(fn, context = null, priority = 0, once = false) + { + /** + * The handler function to execute. + * @member {Function} + */ + this.fn = fn; + + /** + * The calling to execute. + * @member {Function} + */ + this.context = context; + + /** + * The current priority. + * @member {number} + */ + this.priority = priority; + + /** + * If this should only execute once. + * @member {boolean} + */ + this.once = once; + + /** + * The next item in chain. + * @member {TickerListener} + */ + this.next = null; + + /** + * The previous item in chain. + * @member {TickerListener} + */ + this.previous = null; + + /** + * `true` if this listener has been destroyed already. + * @member {boolean} + * @private + */ + this._destroyed = false; + } + + /** + * Simple compare function to figure out if a function and context match. + * + * @param {Function} fn - The listener function to be added for one update + * @param {Function} context - The listener context + * @return {boolean} `true` if the listener match the arguments + */ + match(fn, context) + { + context = context || null; + + return this.fn === fn && this.context === context; + } + + /** + * Emit by calling the current function. + * @param {number} deltaTime - time since the last emit. + * @return {TickerListener} Next ticker + */ + emit(deltaTime) + { + if (this.context) + { + this.fn.call(this.context, deltaTime); + } + else + { + this.fn(deltaTime); + } + + if (this.once) + { + this.destroy(); + } + + const redirect = this.next; + + // Soft-destroying should remove + // the next reference + if (this._destroyed) + { + this.next = null; + } + + return redirect; + } + + /** + * Connect to the list. + * @param {TickerListener} previous - Input node, previous listener + */ + connect(previous) + { + this.previous = previous; + if (previous.next) + { + previous.next.previous = this; + } + this.next = previous.next; + previous.next = this; + } + + /** + * Destroy and don't use after this. + * @param {boolean} [hard = false] `true` to remove the `next` reference, this + * is considered a hard destroy. Soft destroy maintains the next reference. + * @return {TickerListener} The listener to redirect while emitting or removing. + */ + destroy(hard = false) + { + this._destroyed = true; + this.fn = null; + this.context = null; + + // Disconnect, hook up next and previous + if (this.previous) + { + this.previous.next = this.next; + } + + if (this.next) + { + this.next.previous = this.previous; + } + + // Redirect to the next item + const redirect = this.previous; + + // Remove references + this.next = hard ? null : redirect; + this.previous = null; + + return redirect; + } +} diff --git a/src/core/ticker/index.js b/src/core/ticker/index.js index 4b3017c..c3020d4 100644 --- a/src/core/ticker/index.js +++ b/src/core/ticker/index.js @@ -45,8 +45,25 @@ const shared = new Ticker(); shared.autoStart = true; +shared.destroy = () => +{ + // protect destroying shared ticker + // this is used by other internal systems + // like AnimatedSprite and InteractionManager +}; /** + * This namespace contains an API for interacting with PIXI's internal global update loop. + * + * This ticker is used for rendering, {@link PIXI.extras.AnimatedSprite AnimatedSprite}, + * {@link PIXI.interaction.InteractionManager InteractionManager} and many other time-based PIXI systems. + * @example + * const ticker = new PIXI.ticker.Ticker(); + * ticker.stop(); + * ticker.add((deltaTime) => { + * // do something every frame + * }); + * ticker.start(); * @namespace PIXI.ticker */ export { shared, Ticker }; diff --git a/src/core/utils/index.js b/src/core/utils/index.js index be44750..7441f7b 100644 --- a/src/core/utils/index.js +++ b/src/core/utils/index.js @@ -9,6 +9,21 @@ let saidHello = false; /** + * Generalized convenience utilities for PIXI. + * @example + * // Extend PIXI's internal Event Emitter. + * class MyEmitter extends PIXI.utils.EventEmitter { + * constructor() { + * super(); + * console.log("Emitter created!"); + * } + * } + * + * // Get info on current device + * console.log(PIXI.utils.isMobile); + * + * // Convert hex color to string + * console.log(PIXI.utils.hex2string(0xff00ff)); // returns: "#ff00ff" * @namespace PIXI.utils */ export { diff --git a/src/deprecation.js b/src/deprecation.js index b9c73b3..33daeec 100644 --- a/src/deprecation.js +++ b/src/deprecation.js @@ -918,6 +918,35 @@ }); /** + * @method + * @name PIXI.prepare.BasePrepare#register + * @see PIXI.prepare.BasePrepare#registerFindHook + * @deprecated since version 4.4.2 + * @param {Function} [addHook] - Function call that takes two parameters: `item:*, queue:Array` + * function must return `true` if it was able to add item to the queue. + * @param {Function} [uploadHook] - Function call that takes two parameters: `prepare:CanvasPrepare, item:*` and + * function must return `true` if it was able to handle upload of item. + * @return {PIXI.BasePrepare} Instance of plugin for chaining. + */ +prepare.BasePrepare.prototype.register = function register(addHook, uploadHook) +{ + warn('renderer.plugins.prepare.register is now deprecated, ' + + 'please use renderer.plugins.prepare.registerFindHook & renderer.plugins.prepare.registerUploadHook'); + + if (addHook) + { + this.registerFindHook(addHook); + } + + if (uploadHook) + { + this.registerUploadHook(uploadHook); + } + + return this; +}; + +/** * The number of graphics or textures to upload to the GPU. * * @name PIXI.prepare.canvas.UPLOADS_PER_FRAME @@ -969,67 +998,73 @@ }, }); -Object.defineProperties(loaders.Resource.prototype, { - isJson: { - get() - { - warn('The isJson property is deprecated, please use `resource.type === Resource.TYPE.JSON`.'); +if (loaders.Loader) +{ + const Resource = loaders.Resource; + const Loader = loaders.Loader; - return this.type === loaders.Loader.Resource.TYPE.JSON; + Object.defineProperties(Resource.prototype, { + isJson: { + get() + { + warn('The isJson property is deprecated, please use `resource.type === Resource.TYPE.JSON`.'); + + return this.type === Resource.TYPE.JSON; + }, }, - }, - isXml: { - get() - { - warn('The isXml property is deprecated, please use `resource.type === Resource.TYPE.XML`.'); + isXml: { + get() + { + warn('The isXml property is deprecated, please use `resource.type === Resource.TYPE.XML`.'); - return this.type === loaders.Loader.Resource.TYPE.XML; + return this.type === Resource.TYPE.XML; + }, }, - }, - isImage: { - get() - { - warn('The isImage property is deprecated, please use `resource.type === Resource.TYPE.IMAGE`.'); + isImage: { + get() + { + warn('The isImage property is deprecated, please use `resource.type === Resource.TYPE.IMAGE`.'); - return this.type === loaders.Loader.Resource.TYPE.IMAGE; + return this.type === Resource.TYPE.IMAGE; + }, }, - }, - isAudio: { - get() - { - warn('The isAudio property is deprecated, please use `resource.type === Resource.TYPE.AUDIO`.'); + isAudio: { + get() + { + warn('The isAudio property is deprecated, please use `resource.type === Resource.TYPE.AUDIO`.'); - return this.type === loaders.Loader.Resource.TYPE.AUDIO; + return this.type === Resource.TYPE.AUDIO; + }, }, - }, - isVideo: { - get() - { - warn('The isVideo property is deprecated, please use `resource.type === Resource.TYPE.VIDEO`.'); + isVideo: { + get() + { + warn('The isVideo property is deprecated, please use `resource.type === Resource.TYPE.VIDEO`.'); - return this.type === loaders.Loader.Resource.TYPE.VIDEO; + return this.type === Resource.TYPE.VIDEO; + }, }, - }, -}); + }); -Object.defineProperties(loaders.Loader.prototype, { - before: { - get() - { - warn('The before() method is deprecated, please use pre().'); + Object.defineProperties(Loader.prototype, { + before: { + get() + { + warn('The before() method is deprecated, please use pre().'); - return this.pre; + return this.pre; + }, }, - }, - after: { - get() - { - warn('The after() method is deprecated, please use use().'); + after: { + get() + { + warn('The after() method is deprecated, please use use().'); - return this.use; + return this.use; + }, }, - }, -}); + }); +} /** * @name PIXI.interaction.interactiveTarget#defaultCursor diff --git a/src/extract/canvas/CanvasExtract.js b/src/extract/canvas/CanvasExtract.js index 941ca05..3ac5aee 100644 --- a/src/extract/canvas/CanvasExtract.js +++ b/src/extract/canvas/CanvasExtract.js @@ -8,7 +8,7 @@ * An instance of this class is automatically created by default, and can be found at renderer.plugins.extract * * @class - * @memberof PIXI + * @memberof PIXI.extract */ export default class CanvasExtract { @@ -21,9 +21,9 @@ /** * Collection of methods for extracting data (image, pixels, etc.) from a display object or render texture * - * @member {PIXI.CanvasExtract} extract + * @member {PIXI.extract.CanvasExtract} extract * @memberof PIXI.CanvasRenderer# - * @see PIXI.CanvasExtract + * @see PIXI.extract.CanvasExtract */ renderer.extract = this; } diff --git a/src/extract/index.js b/src/extract/index.js index 0f2170d..e9478c1 100644 --- a/src/extract/index.js +++ b/src/extract/index.js @@ -1,2 +1,22 @@ +/** + * This namespace provides renderer-specific plugins for exporting content from a renderer. + * For instance, these plugins can be used for saving an Image, Canvas element or for exporting the raw image data (pixels). + * + * Do not instantiate these plugins directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. + * @example + * // Create a new app (will auto-add extract plugin to renderer) + * const app = new PIXI.Application(); + * + * // Draw a red circle + * const graphics = new PIXI.Graphics() + * .beginFill(0xFF0000) + * .drawCircle(0, 0, 50); + * + * // Render the graphics as an HTMLImageElement + * const image = app.renderer.plugins.extract.image(graphics); + * document.body.appendChild(image); + * @namespace PIXI.extract + */ export { default as webgl } from './webgl/WebGLExtract'; export { default as canvas } from './canvas/CanvasExtract'; diff --git a/src/extract/webgl/WebGLExtract.js b/src/extract/webgl/WebGLExtract.js index 60228bb..1c3dd30 100644 --- a/src/extract/webgl/WebGLExtract.js +++ b/src/extract/webgl/WebGLExtract.js @@ -9,7 +9,7 @@ * An instance of this class is automatically created by default, and can be found at renderer.plugins.extract * * @class - * @memberof PIXI + * @memberof PIXI.extract */ export default class WebGLExtract { @@ -22,9 +22,9 @@ /** * Collection of methods for extracting data (image, pixels, etc.) from a display object or render texture * - * @member {PIXI.WebGLExtract} extract + * @member {PIXI.extract.WebGLExtract} extract * @memberof PIXI.WebGLRenderer# - * @see PIXI.WebGLExtract + * @see PIXI.extract.WebGLExtract */ renderer.extract = this; } diff --git a/src/extras/AnimatedSprite.js b/src/extras/AnimatedSprite.js index 13b804f..5297778 100644 --- a/src/extras/AnimatedSprite.js +++ b/src/extras/AnimatedSprite.js @@ -137,7 +137,7 @@ this.playing = true; if (this._autoUpdate) { - core.ticker.shared.add(this.update, this); + core.ticker.shared.add(this.update, this, core.UPDATE_PRIORITY.HIGH); } } diff --git a/src/extras/TextureTransform.js b/src/extras/TextureTransform.js index 78e6e40..16536d2 100644 --- a/src/extras/TextureTransform.js +++ b/src/extras/TextureTransform.js @@ -65,8 +65,36 @@ } /** + * Multiplies uvs array to transform + * @param {Float32Array} uvs mesh uvs + * @param {Float32Array} [out=uvs] output + * @returns {Float32Array} output + */ + multiplyUvs(uvs, out) + { + if (out === undefined) + { + out = uvs; + } + + const mat = this.mapCoord; + + for (let i = 0; i < uvs.length; i += 2) + { + const x = uvs[i]; + const y = uvs[i + 1]; + + out[i] = (x * mat.a) + (y * mat.c) + mat.tx; + out[i + 1] = (x * mat.b) + (y * mat.d) + mat.ty; + } + + return out; + } + + /** * updates matrices if texture was changed * @param {boolean} forceUpdate if true, matrices will be updated any case + * @returns {boolean} whether or not it was updated */ update(forceUpdate) { @@ -74,13 +102,13 @@ if (!tex || !tex.valid) { - return; + return false; } if (!forceUpdate && this._lastTextureID === tex._updateID) { - return; + return false; } this._lastTextureID = tex._updateID; @@ -110,5 +138,7 @@ frame[3] = (tex._frame.y + tex._frame.height - margin + offset) / texBase.height; this.uClampOffset[0] = offset / texBase.realWidth; this.uClampOffset[1] = offset / texBase.realHeight; + + return true; } } diff --git a/src/extras/TilingSprite.js b/src/extras/TilingSprite.js index 1f6014f..a3f027f 100644 --- a/src/extras/TilingSprite.js +++ b/src/extras/TilingSprite.js @@ -180,9 +180,9 @@ const transform = this.worldTransform; const resolution = renderer.resolution; const baseTexture = texture.baseTexture; - const baseTextureResolution = texture.baseTexture.resolution; - const modX = (this.tilePosition.x / this.tileScale.x) % texture._frame.width; - const modY = (this.tilePosition.y / this.tileScale.y) % texture._frame.height; + const baseTextureResolution = baseTexture.resolution; + const modX = ((this.tilePosition.x / this.tileScale.x) % texture._frame.width) * baseTextureResolution; + const modY = ((this.tilePosition.y / this.tileScale.y) % texture._frame.height) * baseTextureResolution; // create a nice shiny pattern! // TODO this needs to be refreshed if texture changes.. diff --git a/src/extras/cacheAsBitmap.js b/src/extras/cacheAsBitmap.js index 5dd5640..aea06a8 100644 --- a/src/extras/cacheAsBitmap.js +++ b/src/extras/cacheAsBitmap.js @@ -227,7 +227,16 @@ this.transform._parentID = -1; // restore the transform of the cached sprite to avoid the nasty flicker.. - this.updateTransform(); + if (!this.parent) + { + this.parent = renderer._tempDisplayObjectParent; + this.updateTransform(); + this.parent = null; + } + else + { + this.updateTransform(); + } // map the hit test.. this.containsPoint = cachedSprite.containsPoint.bind(cachedSprite); @@ -314,7 +323,17 @@ cachedSprite._bounds = this._bounds; cachedSprite.alpha = cacheAlpha; - this.updateTransform(); + if (!this.parent) + { + this.parent = renderer._tempDisplayObjectParent; + this.updateTransform(); + this.parent = null; + } + else + { + this.updateTransform(); + } + this.updateTransform = this.displayObjectUpdateTransform; this._cacheData.sprite = cachedSprite; @@ -358,9 +377,12 @@ * Destroys the cached object. * * @private + * @param {object|boolean} [options] - Options parameter. A boolean will act as if all options + * have been set to that value. + * Used when destroying containers, see the Container.destroy method. */ -DisplayObject.prototype._cacheAsBitmapDestroy = function _cacheAsBitmapDestroy() +DisplayObject.prototype._cacheAsBitmapDestroy = function _cacheAsBitmapDestroy(options) { this.cacheAsBitmap = false; - this.destroy(); + this.destroy(options); }; diff --git a/src/extras/index.js b/src/extras/index.js index bbd8bbf..a34d7ec 100644 --- a/src/extras/index.js +++ b/src/extras/index.js @@ -1,7 +1,7 @@ /** + * Additional PIXI DisplayObjects for animation, tiling and bitmap text. * @namespace PIXI.extras */ -export { default as TextureTransform } from './TextureTransform'; export { default as AnimatedSprite } from './AnimatedSprite'; export { default as TilingSprite } from './TilingSprite'; export { default as TilingSpriteRenderer } from './webgl/TilingSpriteRenderer'; diff --git a/src/extras/webgl/TilingSpriteRenderer.js b/src/extras/webgl/TilingSpriteRenderer.js index aeec172..eee3b5c 100644 --- a/src/extras/webgl/TilingSpriteRenderer.js +++ b/src/extras/webgl/TilingSpriteRenderer.js @@ -11,7 +11,7 @@ * WebGL renderer plugin for tiling sprites * * @class - * @memberof PIXI + * @memberof PIXI.extras * @extends PIXI.ObjectRenderer */ export default class TilingSpriteRenderer extends core.ObjectRenderer @@ -118,7 +118,7 @@ tempMat.invert(); if (isSimple) { - tempMat.append(uv.mapCoord); + tempMat.prepend(uv.mapCoord); } else { diff --git a/src/filters/displacement/DisplacementFilter.js b/src/filters/displacement/DisplacementFilter.js index d5355c3..b5d030c 100644 --- a/src/filters/displacement/DisplacementFilter.js +++ b/src/filters/displacement/DisplacementFilter.js @@ -35,7 +35,7 @@ this.maskSprite = sprite; this.maskMatrix = maskMatrix; - this.uniforms.mapSampler = sprite.texture; + this.uniforms.mapSampler = sprite._texture; this.uniforms.filterMatrix = maskMatrix; this.uniforms.scale = { x: 1, y: 1 }; diff --git a/src/filters/index.js b/src/filters/index.js index f3e8862..7ceb50a 100644 --- a/src/filters/index.js +++ b/src/filters/index.js @@ -1,4 +1,21 @@ /** + * This namespace contains WebGL-only display filters that can be applied + * to DisplayObjects using the {@link PIXI.DisplayObject#filters filters} property. + * @example + * // Create a new application + * const app = new PIXI.Application(); + * + * // Draw a green rectangle + * const rect = new PIXI.Graphics() + * .beginFill(0x00ff00) + * .drawRect(40, 40, 200, 200); + * + * // Add a blur filter + * rect.filters = [new PIXI.filters.BlurFilter()]; + * + * // Display rectangle + * app.stage.addChild(rect); + * document.body.appendChild(app.view); * @namespace PIXI.filters */ export { default as FXAAFilter } from './fxaa/FXAAFilter'; diff --git a/src/index.js b/src/index.js index c553427..77da490 100644 --- a/src/index.js +++ b/src/index.js @@ -20,6 +20,14 @@ import { utils } from './core'; utils.mixins.performMixins(); +/** + * Alias for {@link PIXI.loaders.shared}. + * @name loader + * @memberof PIXI + * @type {PIXI.loader.Loader} + */ +const loader = loaders.shared || null; + export { accessibility, extract, @@ -30,18 +38,8 @@ mesh, particles, prepare, + loader, }; -/** - * A premade instance of the loader that can be used to load resources. - * - * @name loader - * @memberof PIXI - * @property {PIXI.loaders.Loader} - */ -const loader = loaders && loaders.Loader ? new loaders.Loader() : null; // check is there in case user excludes loader lib - -export { loader }; - // Always export pixi globally. global.PIXI = exports; // eslint-disable-line diff --git a/src/interaction/InteractionData.js b/src/interaction/InteractionData.js index 02d53a3..449f8cc 100644 --- a/src/interaction/InteractionData.js +++ b/src/interaction/InteractionData.js @@ -30,7 +30,10 @@ /** * When passed to an event handler, this will be the original DOM Event that was captured * - * @member {Event} + * @see https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent + * @see https://developer.mozilla.org/en-US/docs/Web/API/TouchEvent + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent + * @member {MouseEvent|TouchEvent|PointerEvent} */ this.originalEvent = null; } diff --git a/src/interaction/InteractionManager.js b/src/interaction/InteractionManager.js index 8baf13a..2d75c7c 100644 --- a/src/interaction/InteractionManager.js +++ b/src/interaction/InteractionManager.js @@ -13,6 +13,14 @@ const MOUSE_POINTER_ID = 'MOUSE'; +// helpers for hitTest() - only used inside hitTest() +const hitTestEvent = { + target: null, + data: { + global: null, + }, +}; + /** * The interaction manager deals with mouse, touch and pointer events. Any DisplayObject can be interactive * if its interactive parameter is set to true @@ -245,67 +253,60 @@ this.setTargetElement(this.renderer.view, this.renderer.resolution); /** - * Fired when a pointer device button (usually a mouse button) is pressed on the display + * Fired when a pointer device button (usually a mouse left-button) is pressed on the display * object. * - * @event mousedown - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mousedown + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device secondary button (usually a mouse right-button) is pressed * on the display object. * - * @event rightdown - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightdown + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** - * Fired when a pointer device button (usually a mouse button) is released over the display + * Fired when a pointer device button (usually a mouse left-button) is released over the display * object. * - * @event mouseup - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseup + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device secondary button (usually a mouse right-button) is released * over the display object. * - * @event rightup - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightup + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** - * Fired when a pointer device button (usually a mouse button) is pressed and released on + * Fired when a pointer device button (usually a mouse left-button) is pressed and released on * the display object. * - * @event click - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#click + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device secondary button (usually a mouse right-button) is pressed * and released on the display object. * - * @event rightclick - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightclick + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** - * Fired when a pointer device button (usually a mouse button) is released outside the + * Fired when a pointer device button (usually a mouse left-button) is released outside the * display object that initially registered a * [mousedown]{@link PIXI.interaction.InteractionManager#event:mousedown}. * - * @event mouseupoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** @@ -313,146 +314,362 @@ * outside the display object that initially registered a * [rightdown]{@link PIXI.interaction.InteractionManager#event:rightdown}. * - * @event rightupoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device (usually a mouse) is moved while over the display object * - * @event mousemove - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mousemove + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device (usually a mouse) is moved onto the display object * - * @event mouseover - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseover + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device (usually a mouse) is moved off the display object * - * @event mouseout - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseout + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is pressed on the display object. * - * @event pointerdown - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerdown + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is released over the display object. * - * @event pointerup - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerup + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when the operating system cancels a pointer event * - * @event pointercancel - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointercancel + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is pressed and released on the display object. * - * @event pointertap - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointertap + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is released outside the display object that initially * registered a [pointerdown]{@link PIXI.interaction.InteractionManager#event:pointerdown}. * - * @event pointerupoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device is moved while over the display object * - * @event pointermove - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointermove + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device is moved onto the display object * - * @event pointerover - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerover + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device is moved off the display object * - * @event pointerout - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerout + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is placed on the display object. * - * @event touchstart - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchstart + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is removed from the display object. * - * @event touchend - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchend + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when the operating system cancels a touch * - * @event touchcancel - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchcancel + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is placed and removed from the display object. * - * @event tap - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#tap + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is removed outside of the display object that initially * registered a [touchstart]{@link PIXI.interaction.InteractionManager#event:touchstart}. * - * @event touchendoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchendoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is moved along the display object. * - * @event touchmove - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchmove + * @param {PIXI.interaction.InteractionData} event - Interaction data */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is pressed on the display. + * object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mousedown + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is pressed + * on the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightdown + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is released over the display + * object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseup + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is released + * over the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightup + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is pressed and released on + * the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#click + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is pressed + * and released on the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightclick + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is released outside the + * display object that initially registered a + * [mousedown]{@link PIXI.DisplayObject#event:mousedown}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is released + * outside the display object that initially registered a + * [rightdown]{@link PIXI.DisplayObject#event:rightdown}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device (usually a mouse) is moved while over the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mousemove + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device (usually a mouse) is moved onto the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseover + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device (usually a mouse) is moved off the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseout + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is pressed on the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerdown + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is released over the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerup + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when the operating system cancels a pointer event. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointercancel + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is pressed and released on the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointertap + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is released outside the display object that initially + * registered a [pointerdown]{@link PIXI.DisplayObject#event:pointerdown}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device is moved while over the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointermove + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device is moved onto the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerover + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device is moved off the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerout + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is placed on the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchstart + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is removed from the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchend + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when the operating system cancels a touch. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchcancel + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is placed and removed from the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#tap + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is removed outside of the display object that initially + * registered a [touchstart]{@link PIXI.DisplayObject#event:touchstart}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchendoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is moved along the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchmove + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + } + + /** + * Hit tests a point against the display tree, returning the first interactive object that is hit. + * + * @param {PIXI.Point} globalPoint - A point to hit test with, in global space. + * @param {PIXI.Container} [root] - The root display object to start from. If omitted, defaults + * to the last rendered root of the associated renderer. + * @return {PIXI.DisplayObject} The hit display object, if any. + */ + hitTest(globalPoint, root) + { + // clear the target for our hit test + hitTestEvent.target = null; + // assign the global point + hitTestEvent.data.global = globalPoint; + // ensure safety of the root + if (!root) + { + root = this.renderer._lastObjectRendered; + } + // run the hit test + this.processInteractive(hitTestEvent, root, null, true); + // return our found object - it'll be null if we didn't hit anything + + return hitTestEvent.target; } /** @@ -487,7 +704,7 @@ return; } - core.ticker.shared.add(this.update, this); + core.ticker.shared.add(this.update, this, core.UPDATE_PRIORITY.INTERACTION); if (window.navigator.msPointerEnabled) { @@ -804,8 +1021,6 @@ } } - let keepHitTestingAfterChildren = hitTest; - // ** FREE TIP **! If an object is not interactive or has no buttons in it // (such as a game scene!) set interactiveChildren to false for that displayObject. // This will allow pixi to completely ignore and bypass checking the displayObjects children. @@ -818,7 +1033,9 @@ const child = children[i]; // time to get recursive.. if this function will return if something is hit.. - if (this.processInteractive(interactionEvent, child, func, hitTest, interactiveParent)) + const childHit = this.processInteractive(interactionEvent, child, func, hitTest, interactiveParent); + + if (childHit) { // its a good idea to check if a child has lost its parent. // this means it has been removed whilst looping so its best @@ -827,8 +1044,6 @@ continue; } - hit = true; - // we no longer need to hit test any more objects in this container as we we // now know the parent has been hit interactiveParent = false; @@ -838,36 +1053,41 @@ // This means we no longer need to hit test anything else. We still need to run // through all objects, but we don't need to perform any hit tests. - keepHitTestingAfterChildren = false; - - if (child.interactive) + if (childHit) { - hitTest = false; + if (interactionEvent.target) + { + hitTest = false; + } + hit = true; } - - // we can break now as we have hit an object. } } } - hitTest = keepHitTestingAfterChildren; - // no point running this if the item is not interactive or does not have an interactive parent. if (interactive) { // if we are hit testing (as in we have no hit any objects yet) // We also don't need to worry about hit testing if once of the displayObjects children - // has already been hit! - if (hitTest && !hit) + // has already been hit - but only if it was interactive, otherwise we need to keep + // looking for an interactive child, just in case we hit one + if (hitTest && !interactionEvent.target) { if (displayObject.hitArea) { displayObject.worldTransform.applyInverse(point, this._tempPoint); - hit = displayObject.hitArea.contains(this._tempPoint.x, this._tempPoint.y); + if (displayObject.hitArea.contains(this._tempPoint.x, this._tempPoint.y)) + { + hit = true; + } } else if (displayObject.containsPoint) { - hit = displayObject.containsPoint(point); + if (displayObject.containsPoint(point)) + { + hit = true; + } } } @@ -878,7 +1098,10 @@ interactionEvent.target = displayObject; } - func(interactionEvent, displayObject, hit); + if (func) + { + func(interactionEvent, displayObject, !!hit); + } } } @@ -914,7 +1137,7 @@ { const event = events[i]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -998,7 +1221,7 @@ { const event = events[i]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1098,7 +1321,7 @@ const test = isRightButton ? flags.RIGHT_DOWN : flags.LEFT_DOWN; - const isDown = trackingData !== undefined && (trackingData.flags | test); + const isDown = trackingData !== undefined && (trackingData.flags & test); if (hit) { @@ -1118,11 +1341,11 @@ { if (isRightButton) { - trackingData.rightDown = hit; + trackingData.rightDown = false; } else { - trackingData.leftDown = hit; + trackingData.leftDown = false; } } } @@ -1180,7 +1403,7 @@ { const event = events[i]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1255,7 +1478,7 @@ this.setCursorMode(null); } - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1268,6 +1491,12 @@ { this.emit('mouseout', interactionEvent); } + else + { + // we can get touchleave events after touchend, so we want to make sure we don't + // introduce memory leaks + this.releaseInteractionDataForPointerId(interactionData.identifier); + } } /** @@ -1344,7 +1573,7 @@ // Only mouse and pointer can call onPointerOver, so events will always be length 1 const event = events[0]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1366,12 +1595,14 @@ * Get InteractionData for a given pointerId. Store that data as well * * @private - * @param {number} pointerId - Identifier from a pointer event + * @param {PointerEvent} event - Normalized pointer event, output from normalizeToPointerData * @return {InteractionData} - Interaction data for the given pointer identifier */ - getInteractionDataForPointerId(pointerId) + getInteractionDataForPointerId(event) { - if (pointerId === MOUSE_POINTER_ID) + const pointerId = event.pointerId; + + if (pointerId === MOUSE_POINTER_ID || event.pointerType === 'mouse') { return this.mouse; } diff --git a/src/interaction/InteractionTrackingData.js b/src/interaction/InteractionTrackingData.js index 6a1f69e..8493b63 100644 --- a/src/interaction/InteractionTrackingData.js +++ b/src/interaction/InteractionTrackingData.js @@ -35,8 +35,10 @@ } /** + * Unique pointer id of the event + * * @readonly - * @type {number} Unique pointer id of the event + * @member {number} */ get pointerId() { diff --git a/src/interaction/index.js b/src/interaction/index.js index 56c5b09..72f7d71 100644 --- a/src/interaction/index.js +++ b/src/interaction/index.js @@ -1,4 +1,8 @@ /** + * This namespace contains a renderer plugin for handling mouse, pointer, and touch events. + * + * Do not instantiate this plugin directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. * @namespace PIXI.interaction */ export { default as InteractionData } from './InteractionData'; diff --git a/src/interaction/interactiveTarget.js b/src/interaction/interactiveTarget.js index d252fba..cebac40 100644 --- a/src/interaction/interactiveTarget.js +++ b/src/interaction/interactiveTarget.js @@ -2,7 +2,7 @@ * Default property values of interactive objects * Used by {@link PIXI.interaction.InteractionManager} to automatically give all DisplayObjects these properties * - * @mixin + * @private * @name interactiveTarget * @memberof PIXI.interaction * @example @@ -14,18 +14,28 @@ * ); */ export default { + /** - * Determines if the displayObject be clicked/touched + * Enable interaction events for the DisplayObject. Touch, pointer and mouse + * events will not be emitted unless `interactive` is set to `true`. * - * @inner {boolean} + * @example + * const sprite = new PIXI.Sprite(texture); + * sprite.interactive = true; + * sprite.on('tap', (event) => { + * //handle event + * }); + * @member {boolean} + * @memberof PIXI.DisplayObject# */ interactive: false, /** * Determines if the children to the displayObject can be clicked/touched - * Setting this to false allows pixi to bypass a recursive hitTest function + * Setting this to false allows pixi to bypass a recursive `hitTest` function * - * @inner {boolean} + * @member {boolean} + * @memberof PIXI.Container# */ interactiveChildren: true, @@ -33,7 +43,12 @@ * Interaction shape. Children will be hit first, then this shape will be checked. * Setting this will cause this shape to be checked in hit tests rather than the displayObject's bounds. * - * @inner {PIXI.Rectangle|PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.RoundedRectangle} + * @example + * const sprite = new PIXI.Sprite(texture); + * sprite.interactive = true; + * sprite.hitArea = new PIXI.Rectangle(0, 0, 100, 100); + * @member {PIXI.Rectangle|PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.RoundedRectangle} + * @memberof PIXI.DisplayObject# */ hitArea: null, @@ -41,8 +56,12 @@ * If enabled, the mouse cursor use the pointer behavior when hovered over the displayObject if it is interactive * Setting this changes the 'cursor' property to `'pointer'`. * + * @example + * const sprite = new PIXI.Sprite(texture); + * sprite.interactive = true; + * sprite.buttonMode = true; * @member {boolean} - * @memberof PIXI.interaction.interactiveTarget# + * @memberof PIXI.DisplayObject# */ get buttonMode() { @@ -64,9 +83,14 @@ * This defines what cursor mode is used when the mouse cursor * is hovered over the displayObject. * + * @example + * const sprite = new PIXI.Sprite(texture); + * sprite.interactive = true; + * sprite.cursor = 'wait'; * @see https://developer.mozilla.org/en/docs/Web/CSS/cursor * - * @inner {string} + * @member {string} + * @memberof PIXI.DisplayObject# */ cursor: null, @@ -74,7 +98,7 @@ * Internal set of all active pointers, by identifier * * @member {Map} - * @memberof PIXI.interaction.interactiveTarget# + * @memberof PIXI.DisplayObject# * @private */ get trackedPointers() @@ -87,7 +111,8 @@ /** * Map of all tracked pointers, by identifier. Use trackedPointers to access. * - * @private {Map} + * @private + * @type {Map} */ _trackedPointers: undefined, }; diff --git a/src/loaders/index.js b/src/loaders/index.js index 5189793..daa1fc4 100644 --- a/src/loaders/index.js +++ b/src/loaders/index.js @@ -1,9 +1,22 @@ +import Application from '../core/Application'; +import Loader from './loader'; + /** + * This namespace contains APIs which extends the {@link https://github.com/englercj/resource-loader resource-loader} module + * for loading assets, data, and other resources dynamically. + * @example + * const loader = new PIXI.loaders.Loader(); + * loader.add('bunny', 'data/bunny.png') + * .add('spaceship', 'assets/spritesheet.json'); + * loader.load((loader, resources) => { + * // resources.bunny + * // resources.spaceship + * }); * @namespace PIXI.loaders */ -export { default as Loader } from './loader'; +export { Loader }; export { default as bitmapFontParser, parse as parseBitmapFontData } from './bitmapFontParser'; -export { default as spritesheetParser } from './spritesheetParser'; +export { default as spritesheetParser, getResourcePath } from './spritesheetParser'; export { default as textureParser } from './textureParser'; /** @@ -13,3 +26,55 @@ * @memberof PIXI.loaders */ export { Resource } from 'resource-loader'; + +/** + * A premade instance of the loader that can be used to load resources. + * @name shared + * @memberof PIXI.loaders + * @type {PIXI.loaders.Loader} + */ +const shared = new Loader(); + +shared.destroy = () => +{ + // protect destroying shared loader +}; + +export { shared }; + +// Mixin the loader construction +const AppPrototype = Application.prototype; + +AppPrototype._loader = null; + +/** + * Loader instance to help with asset loading. + * @name PIXI.Application#loader + * @type {PIXI.loaders.Loader} + */ +Object.defineProperty(AppPrototype, 'loader', { + get() + { + if (!this._loader) + { + const sharedLoader = this._options.sharedLoader; + + this._loader = sharedLoader ? shared : new Loader(); + } + + return this._loader; + }, +}); + +// Override the destroy function +// making sure to destroy the current Loader +AppPrototype._parentDestroy = AppPrototype.destroy; +AppPrototype.destroy = function destroy() +{ + if (this._loader) + { + this._loader.destroy(); + this._loader = null; + } + this._parentDestroy(); +}; diff --git a/src/loaders/loader.js b/src/loaders/loader.js index 767a411..59b8000 100644 --- a/src/loaders/loader.js +++ b/src/loaders/loader.js @@ -90,6 +90,15 @@ { Loader._pixiMiddleware.push(fn); } + + /** + * Destroy the loader, removes references. + */ + destroy() + { + this.removeAllListeners(); + this.reset(); + } } // Copy EE3 prototype (mixin) diff --git a/src/loaders/spritesheetParser.js b/src/loaders/spritesheetParser.js index e57dfb2..eda9584 100644 --- a/src/loaders/spritesheetParser.js +++ b/src/loaders/spritesheetParser.js @@ -1,12 +1,11 @@ import { Resource } from 'resource-loader'; -import path from 'path'; +import url from 'url'; import { Spritesheet } from '../core'; export default function () { return function spritesheetParser(resource, next) { - let resourcePath; const imageResourceName = `${resource.name}_image`; // skip if no data, its not json, it isn't spritesheet data, or the image resource already exists @@ -28,15 +27,7 @@ parentResource: resource, }; - // Prepend url path unless the resource image is a data url - if (resource.isDataUrl) - { - resourcePath = resource.data.meta.image; - } - else - { - resourcePath = `${path.dirname(resource.url.replace(this.baseUrl, ''))}/${resource.data.meta.image}`; - } + const resourcePath = getResourcePath(resource, this.baseUrl); // load the image for this sheet this.add(imageResourceName, resourcePath, loadOptions, function onImageLoad(res) @@ -56,3 +47,14 @@ }); }; } + +export function getResourcePath(resource, baseUrl) +{ + // Prepend url path unless the resource image is a data url + if (resource.isDataUrl) + { + return resource.data.meta.image; + } + + return url.resolve(resource.url.replace(baseUrl, ''), resource.data.meta.image); +} diff --git a/src/mesh/canvas/CanvasMeshRenderer.js b/src/mesh/canvas/CanvasMeshRenderer.js index 297205f..f7899e1 100644 --- a/src/mesh/canvas/CanvasMeshRenderer.js +++ b/src/mesh/canvas/CanvasMeshRenderer.js @@ -134,12 +134,33 @@ const textureWidth = base.width; const textureHeight = base.height; - const u0 = uvs[index0] * base.width; - const u1 = uvs[index1] * base.width; - const u2 = uvs[index2] * base.width; - const v0 = uvs[index0 + 1] * base.height; - const v1 = uvs[index1 + 1] * base.height; - const v2 = uvs[index2 + 1] * base.height; + let u0; + let u1; + let u2; + let v0; + let v1; + let v2; + + if (mesh.uploadUvTransform) + { + const ut = mesh._uvTransform.mapCoord; + + u0 = ((uvs[index0] * ut.a) + (uvs[index0 + 1] * ut.c) + ut.tx) * base.width; + u1 = ((uvs[index1] * ut.a) + (uvs[index1 + 1] * ut.c) + ut.tx) * base.width; + u2 = ((uvs[index2] * ut.a) + (uvs[index2 + 1] * ut.c) + ut.tx) * base.width; + v0 = ((uvs[index0] * ut.b) + (uvs[index0 + 1] * ut.d) + ut.ty) * base.height; + v1 = ((uvs[index1] * ut.b) + (uvs[index1 + 1] * ut.d) + ut.ty) * base.height; + v2 = ((uvs[index2] * ut.b) + (uvs[index2 + 1] * ut.d) + ut.ty) * base.height; + } + else + { + u0 = uvs[index0] * base.width; + u1 = uvs[index1] * base.width; + u2 = uvs[index2] * base.width; + v0 = uvs[index0 + 1] * base.height; + v1 = uvs[index1 + 1] * base.height; + v2 = uvs[index2 + 1] * base.height; + } let x0 = vertices[index0]; let x1 = vertices[index1]; diff --git a/src/mesh/webgl/MeshRenderer.js b/src/mesh/webgl/MeshRenderer.js index 78d24c3..ab99fcd 100644 --- a/src/mesh/webgl/MeshRenderer.js +++ b/src/mesh/webgl/MeshRenderer.js @@ -2,6 +2,8 @@ const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; +const matrixIdentity = core.Matrix.IDENTITY; + /** * WebGL renderer plugin for tiling sprites * @@ -64,6 +66,7 @@ mesh.geometry.glVertexArrayObjects[this.CONTEXT_UID].draw(mesh.drawMode, mesh.size, mesh.start, mesh.geometry.instanceCount); } + /** * draws mesh * @param {PIXI.mesh.RawMesh} mesh mesh instance diff --git a/src/mesh/webgl/mesh.vert b/src/mesh/webgl/mesh.vert index a337aef..acc096c 100644 --- a/src/mesh/webgl/mesh.vert +++ b/src/mesh/webgl/mesh.vert @@ -1,8 +1,9 @@ attribute vec2 aVertexPosition; attribute vec2 aTextureCoord; -uniform mat3 translationMatrix; uniform mat3 projectionMatrix; +uniform mat3 translationMatrix; +uniform mat3 uTransform; varying vec2 vTextureCoord; @@ -10,5 +11,5 @@ { gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - vTextureCoord = aTextureCoord; + vTextureCoord = (uTransform * vec3(aTextureCoord, 1.0)).xy; } diff --git a/src/particles/ParticleContainer.js b/src/particles/ParticleContainer.js index 67796cf..63f7791 100644 --- a/src/particles/ParticleContainer.js +++ b/src/particles/ParticleContainer.js @@ -313,10 +313,10 @@ frame.y * resolution, frame.width * resolution, frame.height * resolution, - positionX * resolution, - positionY * resolution, - finalWidth * resolution, - finalHeight * resolution + positionX * renderer.resolution, + positionY * renderer.resolution, + finalWidth * renderer.resolution, + finalHeight * renderer.resolution ); } } diff --git a/src/prepare/BasePrepare.js b/src/prepare/BasePrepare.js index 09eb5e5..e45c4dc 100644 --- a/src/prepare/BasePrepare.js +++ b/src/prepare/BasePrepare.js @@ -18,6 +18,18 @@ * basic queuing functionality and is extended by {@link PIXI.prepare.WebGLPrepare} and {@link PIXI.prepare.CanvasPrepare} * to provide preparation capabilities specific to their respective renderers. * + * @example + * // Create a sprite + * const sprite = new PIXI.Sprite.fromImage('something.png'); + * + * // Load object into GPU + * app.renderer.plugins.prepare.upload(sprite, () => { + * + * //Texture(s) has been uploaded to GPU + * app.stage.addChild(sprite); + * + * }) + * * @abstract * @class * @memberof PIXI.prepare @@ -100,8 +112,16 @@ this.prepareItems(); }; - this.register(findText, drawText); - this.register(findTextStyle, calculateTextStyle); + // hooks to find the correct texture + this.registerFindHook(findText); + this.registerFindHook(findTextStyle); + this.registerFindHook(findMultipleBaseTextures); + this.registerFindHook(findBaseTexture); + this.registerFindHook(findTexture); + + // upload hooks + this.registerUploadHook(drawText); + this.registerUploadHook(calculateTextStyle); } /** @@ -138,7 +158,7 @@ if (!this.ticking) { this.ticking = true; - SharedTicker.addOnce(this.tick, this); + SharedTicker.addOnce(this.tick, this, core.UPDATE_PRIORITY.UTILITY); } } else if (done) @@ -172,13 +192,16 @@ const item = this.queue[0]; let uploaded = false; - for (let i = 0, len = this.uploadHooks.length; i < len; i++) + if (item && !item._destroyed) { - if (this.uploadHooks[i](this.uploadHookHelper, item)) + for (let i = 0, len = this.uploadHooks.length; i < len; i++) { - this.queue.shift(); - uploaded = true; - break; + if (this.uploadHooks[i](this.uploadHookHelper, item)) + { + this.queue.shift(); + uploaded = true; + break; + } } } @@ -205,26 +228,36 @@ else { // if we are not finished, on the next rAF do this again - SharedTicker.addOnce(this.tick, this); + SharedTicker.addOnce(this.tick, this, core.UPDATE_PRIORITY.UTILITY); } } /** - * Adds hooks for finding and uploading items. + * Adds hooks for finding items. * - * @param {Function} [addHook] - Function call that takes two parameters: `item:*, queue:Array` - function must return `true` if it was able to add item to the queue. - * @param {Function} [uploadHook] - Function call that takes two parameters: `prepare:CanvasPrepare, item:*` and - * function must return `true` if it was able to handle upload of item. - * @return {PIXI.CanvasPrepare} Instance of plugin for chaining. + * @param {Function} addHook - Function call that takes two parameters: `item:*, queue:Array` + * function must return `true` if it was able to add item to the queue. + * @return {PIXI.BasePrepare} Instance of plugin for chaining. */ - register(addHook, uploadHook) + registerFindHook(addHook) { if (addHook) { this.addHooks.push(addHook); } + return this; + } + + /** + * Adds hooks for uploading items. + * + * @param {Function} uploadHook - Function call that takes two parameters: `prepare:CanvasPrepare, item:*` and + * function must return `true` if it was able to handle upload of item. + * @return {PIXI.BasePrepare} Instance of plugin for chaining. + */ + registerUploadHook(uploadHook) + { if (uploadHook) { this.uploadHooks.push(uploadHook); @@ -287,6 +320,88 @@ } /** + * Built-in hook to find multiple textures from objects like AnimatedSprites. + * + * @private + * @param {PIXI.DisplayObject} item - Display object to check + * @param {Array<*>} queue - Collection of items to upload + * @return {boolean} if a PIXI.Texture object was found. + */ +function findMultipleBaseTextures(item, queue) +{ + let result = false; + + // Objects with mutliple textures + if (item && item._textures && item._textures.length) + { + for (let i = 0; i < item._textures.length; i++) + { + if (item._textures[i] instanceof core.Texture) + { + const baseTexture = item._textures[i].baseTexture; + + if (queue.indexOf(baseTexture) === -1) + { + queue.push(baseTexture); + result = true; + } + } + } + } + + return result; +} + +/** + * Built-in hook to find BaseTextures from Sprites. + * + * @private + * @param {PIXI.DisplayObject} item - Display object to check + * @param {Array<*>} queue - Collection of items to upload + * @return {boolean} if a PIXI.Texture object was found. + */ +function findBaseTexture(item, queue) +{ + // Objects with textures, like Sprites/Text + if (item instanceof core.BaseTexture) + { + if (queue.indexOf(item) === -1) + { + queue.push(item); + } + + return true; + } + + return false; +} + +/** + * Built-in hook to find textures from objects. + * + * @private + * @param {PIXI.DisplayObject} item - Display object to check + * @param {Array<*>} queue - Collection of items to upload + * @return {boolean} if a PIXI.Texture object was found. + */ +function findTexture(item, queue) +{ + if (item._texture && item._texture instanceof core.Texture) + { + const texture = item._texture.baseTexture; + + if (queue.indexOf(texture) === -1) + { + queue.push(texture); + } + + return true; + } + + return false; +} + +/** * Built-in hook to draw PIXI.Text to its texture. * * @private diff --git a/src/prepare/canvas/CanvasPrepare.js b/src/prepare/canvas/CanvasPrepare.js index 0224bb5..09c207a 100644 --- a/src/prepare/canvas/CanvasPrepare.js +++ b/src/prepare/canvas/CanvasPrepare.js @@ -43,7 +43,7 @@ this.ctx = this.canvas.getContext('2d'); // Add textures to upload - this.register(findBaseTextures, uploadBaseTextures); + this.registerUploadHook(uploadBaseTextures); } /** @@ -89,39 +89,4 @@ return false; } -/** - * Built-in hook to find textures from Sprites. - * - * @private - * @param {PIXI.DisplayObject} item -Display object to check - * @param {Array<*>} queue - Collection of items to upload - * @return {boolean} if a PIXI.Texture object was found. - */ -function findBaseTextures(item, queue) -{ - // Objects with textures, like Sprites/Text - if (item instanceof core.BaseTexture) - { - if (queue.indexOf(item) === -1) - { - queue.push(item); - } - - return true; - } - else if (item._texture && item._texture instanceof core.Texture) - { - const texture = item._texture.baseTexture; - - if (queue.indexOf(texture) === -1) - { - queue.push(texture); - } - - return true; - } - - return false; -} - core.CanvasRenderer.registerPlugin('prepare', CanvasPrepare); diff --git a/src/prepare/index.js b/src/prepare/index.js index f559c45..e7491ec 100644 --- a/src/prepare/index.js +++ b/src/prepare/index.js @@ -1,4 +1,29 @@ /** + * The prepare namespace provides renderer-specific plugins for pre-rendering DisplayObjects. These plugins are useful for + * asynchronously preparing assets, textures, graphics waiting to be displayed. + * + * Do not instantiate these plugins directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. + * @example + * // Create a new application + * const app = new PIXI.Application(); + * document.body.appendChild(app.view); + * + * // Don't start rendering right away + * app.stop(); + * + * // create a display object + * const rect = new PIXI.Graphics() + * .beginFill(0x00ff00) + * .drawRect(40, 40, 200, 200); + * + * // Add to the stage + * app.stage.addChild(rect); + * + * // Don't start rendering until the graphic is uploaded to the GPU + * app.renderer.plugins.prepare.upload(app.stage, () => { + * app.start(); + * }); * @namespace PIXI.prepare */ export { default as webgl } from './webgl/WebGLPrepare'; diff --git a/src/prepare/webgl/WebGLPrepare.js b/src/prepare/webgl/WebGLPrepare.js index eb7023f..9df3c3f 100644 --- a/src/prepare/webgl/WebGLPrepare.js +++ b/src/prepare/webgl/WebGLPrepare.js @@ -22,12 +22,11 @@ this.uploadHookHelper = this.renderer; // Add textures and graphics to upload - this.register(findBaseTextures, uploadBaseTextures) - .register(findGraphics, uploadGraphics); + this.registerFindHook(findGraphics); + this.registerUploadHook(uploadBaseTextures); + this.registerUploadHook(uploadGraphics); } - } - /** * Built-in hook to upload PIXI.Texture objects to the GPU. * @@ -80,41 +79,6 @@ } /** - * Built-in hook to find textures from Sprites. - * - * @private - * @param {PIXI.DisplayObject} item - Display object to check - * @param {Array<*>} queue - Collection of items to upload - * @return {boolean} if a PIXI.Texture object was found. - */ -function findBaseTextures(item, queue) -{ - // Objects with textures, like Sprites/Text - if (item instanceof core.BaseTexture) - { - if (queue.indexOf(item) === -1) - { - queue.push(item); - } - - return true; - } - else if (item._texture && item._texture instanceof core.Texture) - { - const texture = item._texture.baseTexture; - - if (queue.indexOf(texture) === -1) - { - queue.push(texture); - } - - return true; - } - - return false; -} - -/** * Built-in hook to find graphics. * * @private diff --git a/test/.eslintrc.json b/test/.eslintrc.json index e03220d..ac12110 100644 --- a/test/.eslintrc.json +++ b/test/.eslintrc.json @@ -12,7 +12,8 @@ "sinon": false, "expect": false, "assert": false, - "PIXI": false + "PIXI": false, + "PointerEvent": true }, "rules": { "func-names": 0, diff --git a/test/core/BaseTexture.js b/test/core/BaseTexture.js index 8b93478..bb65068 100644 --- a/test/core/BaseTexture.js +++ b/test/core/BaseTexture.js @@ -14,4 +14,28 @@ expect(baseTexture.imageType).to.be.equals('png'); }); }); + + it('should remove Canvas BaseTexture from cache on destroy', function () + { + const canvas = document.createElement('canvas'); + const texture = PIXI.BaseTexture.fromCanvas(canvas); + const _pixiId = canvas._pixiId; + + expect(PIXI.utils.BaseTextureCache[_pixiId]).to.equal(texture); + texture.destroy(); + expect(PIXI.utils.BaseTextureCache[_pixiId]).to.equal(undefined); + }); + + it('should remove Image BaseTexture from cache on destroy', function () + { + const URL = 'foo.png'; + const NAME = 'bar'; + const image = new Image(); + + const texture = PIXI.Texture.fromLoader(image, URL, NAME); + + expect(PIXI.utils.BaseTextureCache[NAME]).to.equal(texture.baseTexture); + texture.destroy(true); + expect(PIXI.utils.BaseTextureCache[NAME]).to.equal(undefined); + }); }); diff --git a/test/core/Circle.js b/test/core/Circle.js index 183f98f..f621992 100644 --- a/test/core/Circle.js +++ b/test/core/Circle.js @@ -53,6 +53,10 @@ expect(circ1.contains(10, 16)).to.be.false; expect(circ1.contains(11, 15)).to.be.false; expect(circ1.contains(0, 0)).to.be.false; + + const circ2 = new PIXI.Circle(10, 10, 0); + + expect(circ2.contains(10, 10)).to.be.false; }); it('should return framing rectangle', function () diff --git a/test/core/Container.js b/test/core/Container.js index d11be33..cefde54 100644 --- a/test/core/Container.js +++ b/test/core/Container.js @@ -2,30 +2,36 @@ function testAddChild(fn) { - fn(function (container, obj) + return function () { - container.addChild(obj); - }); - fn(function (container, obj) - { - container.addChildAt(obj); - }); + fn(function (container, obj) + { + container.addChild(obj); + }); + fn(function (container, obj) + { + container.addChildAt(obj); + }); + }; } function testRemoveChild(fn) { - fn(function (container, obj) + return function () { - container.removeChild(obj); - }); - fn(function (container, obj) - { - container.removeChildAt(container.children.indexOf(obj)); - }); - fn(function (container, obj) - { - container.removeChildren(container.children.indexOf(obj), container.children.indexOf(obj) + 1); - }); + fn(function (container, obj) + { + container.removeChild(obj); + }); + fn(function (container, obj) + { + container.removeChildAt(container.children.indexOf(obj)); + }); + fn(function (container, obj) + { + container.removeChildren(container.children.indexOf(obj), container.children.indexOf(obj) + 1); + }); + }; } describe('PIXI.Container', function () diff --git a/test/core/Ellipse.js b/test/core/Ellipse.js index f2e8234..3f47442 100644 --- a/test/core/Ellipse.js +++ b/test/core/Ellipse.js @@ -56,6 +56,10 @@ expect(ellipse1.contains(10, 16)).to.be.false; expect(ellipse1.contains(11, 15)).to.be.false; expect(ellipse1.contains(0, 0)).to.be.false; + + const ellipse2 = new PIXI.Ellipse(10, 10, 0, 0); + + expect(ellipse2.contains(10, 10)).to.be.false; }); it('should return framing rectangle', function () diff --git a/.travis.yml b/.travis.yml index 3df8b22..14de334 100644 --- a/.travis.yml +++ b/.travis.yml @@ -110,3 +110,43 @@ on: all_branches: true condition: $TRAVIS_TAG + # Deploy config for latest release + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: dist + upload-dir: "release" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: docs + upload-dir: "release/docs" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: coverage + upload-dir: "release/coverage" + on: + all_branches: true + condition: $TRAVIS_TAG \ No newline at end of file diff --git a/README.md b/README.md index d26fadd..3b85f85 100644 --- a/README.md +++ b/README.md @@ -36,22 +36,14 @@ - Wiki: Other misc tutorials and resources are [on the Wiki](https://github.com/pixijs/pixi.js/wiki/Resources). ### Community ### -- Forums: Check out the [forum] (http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow] (http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. +- Forums: Check out the [forum](http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow](http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. - Inspiration: Check out the [gallery](http://www.pixijs.com/gallery) to see some of the amazing things people have created! - Chat: You can join us on [Gitter](https://gitter.im/pixijs/pixi.js) To chat about Pixi. We also now have a Slack channel. If you would like to join it please Send me an email (mat@goodboydigital.com) and I will invite you in. ### Setup ### -It's easy to get started with Pixi.js! Simply grab the pre-built versions from here: - -Release Branch - Nice and stable Pixi.js -- Unminified: [http://pixijs.download/release/pixi.js] -- Minified: [http://pixijs.download/release/pixi.min.js] - -Develop Branch - The bleeding edge version of Pixi.js -- Unminified: [http://pixijs.download/dev/pixi.js] -- Minified: [http://pixijs.download/dev/pixi.min.js] +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. @@ -70,10 +62,10 @@ #### CDN Install (via cdnjs) ```html - + ``` -_Note: `4.2.2` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ +_Note: `4.4.3` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ ### Demos ### @@ -130,7 +122,7 @@ // and the root stage PIXI.Container. var app = new PIXI.Application(); -// The application will create a canvas element for you that you +// The application will create a canvas element for you that you // can then insert into the DOM. document.body.appendChild(app.view); @@ -150,7 +142,7 @@ // Add the bunny to the scene we are building. app.stage.addChild(bunny); - + // Listen for frame updates app.ticker.add(function() { // each frame we spin the bunny around a bit diff --git a/bower.json b/bower.json deleted file mode 100644 index 41b668c..0000000 --- a/bower.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "pixi.js", - "main": "dist/pixi.min.js", - "ignore": [ - "**/.*", - "docs", - "node_modules", - "bower_components", - "test", - "scripts", - "bower.json", - "inch.json" - ], - "dependencies": { - }, - "devDependencies": { - } -} diff --git a/package.json b/package.json index d35cbb2..308469f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.3.5", + "version": "4.4.4", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ @@ -40,11 +40,11 @@ "lib": "babel src --out-dir lib -s", "predocs": "rimraf docs/**", "docs": "jsdoc -c scripts/jsdoc.conf.json -R README.md", - "publish:patch": "npm version patch --no-git-tag-version && npm publish", - "publish:minor": "npm version minor --no-git-tag-version && npm publish", - "publish:major": "npm version major --no-git-tag-version && npm publish", + "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", - "postpublish": "node scripts/release.js" + "postpublish": "git push && git push --tags" }, "files": [ "dist/", @@ -72,7 +72,6 @@ "electron": "^1.4.15", "eslint": "^3.5.0", "floss": "^2.0.1", - "gh-pages": "^0.11.0", "jaguarjs-jsdoc": "^1.0.1", "js-md5": "^0.4.1", "jsdoc": "^3.4.2", diff --git a/scripts/jsdoc.conf.json b/scripts/jsdoc.conf.json index 553e61c..3f52180 100644 --- a/scripts/jsdoc.conf.json +++ b/scripts/jsdoc.conf.json @@ -43,7 +43,7 @@ }, "markdown" : { "parser" : "gfm", - "hardwrap" : true + "hardwrap" : false }, "opts": { "encoding" : "utf8", diff --git a/scripts/release.js b/scripts/release.js deleted file mode 100644 index 156620c..0000000 --- a/scripts/release.js +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env node -'use strict'; - -// Publish script to push releases of the bin files -// the normally are gitignored -const ghpages = require('gh-pages'); -const path = require('path'); -const packageInfo = require(path.join(__dirname, '..', 'package.json')); - -const options = { - src: [ - 'dist/**/*', - 'lib/**/*', - 'src/**/*', - 'scripts/*', - 'scripts/renders/*', - 'test/**/*', - '*.json', - '*.md', - 'LICENSE', - '.eslintrc', - '.editorconfig', - '.travis.yml', - '.babelrc', - ], - dotfiles: true, - branch: 'release', - message: packageInfo.version, - logger: console.log.bind(console), -}; - -ghpages.publish(process.cwd(), options, (err) => -{ - if (err) - { - console.log(err); - process.exit(1); - - return; - } - - process.exit(0); -}); diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index f16bcb1..220427a 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -21,7 +21,7 @@ const DIV_HOOK_ZINDEX = 2; /** - * The Accessibility manager reacreates the ability to tab and and have content read by screen + * The Accessibility manager recreates the ability to tab and and have content read by screen * readers. This is very important as it can possibly help people with disabilities access pixi * content. * diff --git a/src/accessibility/index.js b/src/accessibility/index.js index 568cfb1..0ae108f 100644 --- a/src/accessibility/index.js +++ b/src/accessibility/index.js @@ -1,4 +1,9 @@ /** + * This namespace contains a renderer plugin for interaction accessibility for end-users + * with physical impairments which require screen-renders, keyboard navigation, etc. + * + * Do not instantiate this plugin directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. * @namespace PIXI.accessibility */ export { default as accessibleTarget } from './accessibleTarget'; diff --git a/src/core/Application.js b/src/core/Application.js index 5340578..2fcc729 100644 --- a/src/core/Application.js +++ b/src/core/Application.js @@ -1,6 +1,8 @@ import { autoDetectRenderer } from './autoDetectRenderer'; import Container from './display/Container'; import { shared, Ticker } from './ticker'; +import settings from './settings'; +import { UPDATE_PRIORITY } from './const'; /** * Convenience class to create a new PIXI application. @@ -22,28 +24,52 @@ */ export default class Application { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experience unexplained flickering try setting this to true. - * @param {boolean} [useSharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedLoader=false] - `true` to use PIXI.loaders.shared, `false` to create new Loader. */ - constructor(width, height, options, noWebGL, useSharedTicker = false) + constructor(options, arg2, arg3, arg4, arg5) { + // Support for constructor(width, height, options, noWebGL, useSharedTicker) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + forceCanvas: !!arg4, + sharedTicker: !!arg5, + }, arg3); + } + + /** + * The default options, so we mixin functionality later. + * @member {object} + * @protected + */ + this._options = options = Object.assign({ + sharedTicker: false, + forceCanvas: false, + sharedLoader: false, + }, options); + /** * WebGL renderer if available, otherwise CanvasRenderer * @member {PIXI.WebGLRenderer|PIXI.CanvasRenderer} */ - this.renderer = autoDetectRenderer(width, height, options, noWebGL); + this.renderer = autoDetectRenderer(options); /** * The root display container that's rendered. @@ -63,7 +89,7 @@ * @member {PIXI.ticker.Ticker} * @default PIXI.ticker.shared */ - this.ticker = useSharedTicker ? shared : new Ticker(); + this.ticker = options.sharedTicker ? shared : new Ticker(); // Start the rendering this.start(); @@ -78,7 +104,7 @@ this._ticker = ticker; if (ticker) { - ticker.add(this.render, this); + ticker.add(this.render, this, UPDATE_PRIORITY.LOW); } } get ticker() // eslint-disable-line require-jsdoc @@ -136,13 +162,18 @@ */ destroy(removeView) { - this.stop(); + const oldTicker = this._ticker; + this.ticker = null; + oldTicker.destroy(); + this.stage.destroy(); this.stage = null; this.renderer.destroy(removeView); this.renderer = null; + + this._options = null; } } diff --git a/src/core/autoDetectRenderer.js b/src/core/autoDetectRenderer.js index 8960425..9ec0364 100644 --- a/src/core/autoDetectRenderer.js +++ b/src/core/autoDetectRenderer.js @@ -2,6 +2,7 @@ import CanvasRenderer from './renderers/canvas/CanvasRenderer'; import WebGLRenderer from './renderers/webgl/WebGLRenderer'; +// eslint-disable-next-line valid-jsdoc /** * This helper function will automatically detect which renderer you should be using. * WebGL is the preferred renderer as it is a lot faster. If webGL is not supported by @@ -9,24 +10,32 @@ * * @memberof PIXI * @function autoDetectRenderer - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @return {PIXI.WebGLRenderer|PIXI.CanvasRenderer} Returns WebGL renderer if available, otherwise CanvasRenderer */ -export function autoDetectRenderer(width = 800, height = 600, options, noWebGL) +export function autoDetectRenderer(options, arg1, arg2, arg3) { - if (!noWebGL && utils.isWebGLSupported()) + // Backward-compatible support for noWebGL option + let forceCanvas = options && options.forceCanvas; + + if (arg3 !== undefined) { - return new WebGLRenderer(width, height, options); + forceCanvas = arg3; } - return new CanvasRenderer(width, height, options); + if (!forceCanvas && utils.isWebGLSupported()) + { + return new WebGLRenderer(options, arg1, arg2); + } + + return new CanvasRenderer(options, arg1, arg2); } diff --git a/src/core/const.js b/src/core/const.js index b9e99ae..2c68dee 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -341,3 +341,27 @@ LINEAR_VERTICAL: 0, LINEAR_HORIZONTAL: 1, }; + +/** + * Represents the update priorities used by internal PIXI classes when registered with + * the {@link PIXI.ticker.Ticker} object. Higher priority items are updated first and lower + * priority items, such as render, should go later. + * + * @static + * @constant + * @name UPDATE_PRIORITY + * @memberof PIXI + * @type {object} + * @property {number} INTERACTION=50 Highest priority, used for {@link PIXI.interaction.InteractionManager} + * @property {number} HIGH=25 High priority updating, {@link PIXI.VideoBaseTexture} and {@link PIXI.extras.AnimatedSprite} + * @property {number} NORMAL=0 Default priority for ticker events, see {@link PIXI.ticker.Ticker#add}. + * @property {number} LOW=-25 Low priority used for {@link PIXI.Application} rendering. + * @property {number} UTILITY=-50 Lowest priority used for {@link PIXI.prepare.BasePrepare} utility. + */ +export const UPDATE_PRIORITY = { + INTERACTION: 50, + HIGH: 25, + NORMAL: 0, + LOW: -25, + UTILITY: -50, +}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 4e6c77d..decb42a 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -13,7 +13,6 @@ * * @class * @extends EventEmitter - * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ export default class DisplayObject extends EventEmitter @@ -113,6 +112,29 @@ * @private */ this._mask = null; + + /** + * If the object has been destroyed via destroy(). If true, it should not be used. + * + * @member {boolean} + * @private + * @readonly + */ + this._destroyed = false; + + /** + * Fired when this DisplayObject is added to a Container. + * + * @event PIXI.DisplayObject#added + * @param {PIXI.Container} container - The container added to. + */ + + /** + * Fired when this DisplayObject is removed from a Container. + * + * @event PIXI.DisplayObject#removed + * @param {PIXI.Container} container - The container removed from. + */ } /** @@ -403,6 +425,8 @@ this.interactive = false; this.interactiveChildren = false; + + this._destroyed = true; } /** diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index aafdfe2..f787c4a 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -792,8 +792,8 @@ sprite.worldAlpha = this.worldAlpha * sprite.alpha; sprite.blendMode = this.blendMode; - sprite.texture._frame.width = rect.width; - sprite.texture._frame.height = rect.height; + sprite._texture._frame.width = rect.width; + sprite._texture._frame.height = rect.height; sprite.transform.worldTransform = this.transform.worldTransform; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index a24c775..b9f192d 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -19,11 +19,12 @@ */ export default class SystemRenderer extends EventEmitter { + // eslint-disable-next-line valid-jsdoc /** * @param {string} system - The name of the system this renderer is for. - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -37,27 +38,31 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(system, screenWidth, screenHeight, options) + constructor(system, options, arg2, arg3) { super(); sayHello(system); - // prepare options - if (options) + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') { - for (const i in settings.RENDER_OPTIONS) - { - if (typeof options[i] === 'undefined') - { - options[i] = settings.RENDER_OPTIONS[i]; - } - } + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); } - else - { - options = settings.RENDER_OPTIONS; - } + + // Add the default render options + options = Object.assign({}, settings.RENDER_OPTIONS, options); + + /** + * The supplied constructor options. + * + * @member {Object} + * @readOnly + */ + this.options = options; /** * The type of the renderer. @@ -75,7 +80,7 @@ * * @member {PIXI.Rectangle} */ - this.screen = new Rectangle(0, 0, screenWidth || 800, screenHeight || 600); + this.screen = new Rectangle(0, 0, options.width, options.height); /** * The canvas element that everything is drawn to @@ -279,6 +284,8 @@ this.blendModes = null; + this.options = null; + this.preserveDrawingBuffer = false; this.clearBeforeRender = false; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index e84e49c..691caf0 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -17,10 +17,11 @@ */ export default class CanvasRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -34,9 +35,9 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('Canvas', screenWidth, screenHeight, options); + super('Canvas', options, arg2, arg3); this.type = RENDERER_TYPE.CANVAS; @@ -96,7 +97,19 @@ this.context = null; this.renderingToScreen = false; - this.resize(screenWidth, screenHeight); + this.resize(this.options.width, this.options.height); + + /** + * Fired after rendering finishes. + * + * @event PIXI.CanvasRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.CanvasRenderer#prerender + */ } /** @@ -299,4 +312,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.CanvasRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.CanvasExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.CanvasPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.CanvasRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index e1cf7cd..f6fe123 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -31,11 +31,12 @@ */ export default class WebGLRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** * - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -55,9 +56,9 @@ * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('WebGL', screenWidth, screenHeight, options); + super('WebGL', options, arg2, arg3); /** * The type of this renderer as a standardised const @@ -89,12 +90,6 @@ }; - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ this._backgroundColorRgba[3] = this.transparent ? 0 : 1; this.globalUniforms = new UniformGroup({ @@ -117,6 +112,13 @@ this.initPlugins(); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ if(options.context) { this.context.initFromContext(options.context); @@ -169,6 +171,25 @@ } return this; + + /** + * Fired after rendering finishes. + * + * @event PIXI.WebGLRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.WebGLRenderer#prerender + */ + + /** + * Fired when the WebGL context is set. + * + * @event PIXI.WebGLRenderer#context + * @param {WebGLRenderingContext} gl - WebGL context. + */ } /** @@ -295,4 +316,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.WebGLRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.WebGLRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(WebGLRenderer); diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index 4eb6128..fa3d883 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -47,6 +47,14 @@ * @member {boolean} */ this.enabled = true; + + /** + * If enabled, pixi will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; } /** diff --git a/src/core/renderers/webgl/systems/ContextSystem.js b/src/core/renderers/webgl/systems/ContextSystem.js index ff958c7..8df85ea 100644 --- a/src/core/renderers/webgl/systems/ContextSystem.js +++ b/src/core/renderers/webgl/systems/ContextSystem.js @@ -75,7 +75,8 @@ */ createContext(canvas, options) { - var gl = canvas.getContext('webgl', options) || + var gl = //canvas.getContext('webgl2', options) || + canvas.getContext('webgl', options) || canvas.getContext('experimental-webgl', options); if (!gl) diff --git a/src/core/renderers/webgl/systems/FilterSystem.js b/src/core/renderers/webgl/systems/FilterSystem.js index f47335d..37e28d2 100644 --- a/src/core/renderers/webgl/systems/FilterSystem.js +++ b/src/core/renderers/webgl/systems/FilterSystem.js @@ -111,7 +111,7 @@ // TODO we should fit the rect around the transform.. } - else + else if (filters[0].autoFit) { sourceFrame.fit(filterData.stack[0].destinationFrame); } diff --git a/src/core/renderers/webgl/systems/shader/shader/mapType.js b/src/core/renderers/webgl/systems/shader/shader/mapType.js index 581dace..917538a 100644 --- a/src/core/renderers/webgl/systems/shader/shader/mapType.js +++ b/src/core/renderers/webgl/systems/shader/shader/mapType.js @@ -15,7 +15,7 @@ } } - return GL_TABLE[type]; + return GL_TABLE[type]; }; var GL_TABLE = null; diff --git a/src/core/settings.js b/src/core/settings.js index 18c5877..38ef45c 100644 --- a/src/core/settings.js +++ b/src/core/settings.js @@ -2,6 +2,15 @@ import canUploadSameBuffer from './utils/canUploadSameBuffer'; /** + * User's customizable globals for overriding the default PIXI settings, such + * as a renderer's default resolution, framerate, float percision, etc. + * @example + * // Use the native window resolution as the default resolution + * // will support high-density displays when rendering + * PIXI.settings.RESOLUTION = window.devicePixelRatio. + * + * // Disable interpolation when scaling, will make texture be pixelated + * PIXI.settings.SCALE_MODE = PIXI.SCALE_MODES.NEAREST; * @namespace PIXI.settings */ export default { @@ -101,6 +110,9 @@ * @property {boolean} clearBeforeRender=true * @property {boolean} preserveDrawingBuffer=false * @property {boolean} roundPixels=false + * @property {number} width=800 + * @property {number} height=600 + * @property {boolean} legacy=false */ RENDER_OPTIONS: { view: null, @@ -112,6 +124,9 @@ clearBeforeRender: true, preserveDrawingBuffer: false, roundPixels: false, + width: 800, + height: 600, + legacy: false, }, /** diff --git a/src/core/shader/Program.js b/src/core/shader/Program.js index c0b538b..07b4398 100644 --- a/src/core/shader/Program.js +++ b/src/core/shader/Program.js @@ -1,6 +1,6 @@ import extractUniformsFromSrc from './extractUniformsFromSrc'; import generateUniformsSync from './generateUniformsSync'; -import glCore from 'pixi-gl-core'; +import shaderUtils from '../renderers/webgl/systems/shader/shader'; import { ProgramCache } from '../utils'; import getTestContext from '../utils/getTestContext'; @@ -64,14 +64,17 @@ } else { - vertexSrc = glCore.shader.setPrecision(vertexSrc, 'mediump'); - fragmentSrc = glCore.shader.setPrecision(fragmentSrc, 'mediump'); + vertexSrc = shaderUtils.setPrecision(vertexSrc, 'mediump'); + fragmentSrc = shaderUtils.setPrecision(fragmentSrc, 'mediump'); - const program = glCore.shader.compileProgram(gl, vertexSrc, fragmentSrc); + const program = shaderUtils.compileProgram(gl, vertexSrc, fragmentSrc); + console.log("<<<<<<>>><<"); this.attributeData = this.getAttributeData(program, gl); this.uniformData = this.getUniformData(program, gl); + console.log(this.uniformData); + //gl.deleteProgram(program); } } @@ -95,13 +98,13 @@ for (let i = 0; i < totalAttributes; i++) { const attribData = gl.getActiveAttrib(program, i); - const type = glCore.shader.mapType(gl, attribData.type); + const type = shaderUtils.mapType(gl, attribData.type); /*eslint-disable */ const data = { type: type, name: attribData.name, - size: glCore.shader.mapSize(type), + size: shaderUtils.mapSize(type), location: 0, }; /*eslint-enable */ @@ -137,13 +140,14 @@ // TODO expose this as a prop? // const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); - const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); + //const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); for (let i = 0; i < totalUniforms; i++) { const uniformData = gl.getActiveUniform(program, i); const name = uniformData.name.replace(/\[.*?\]/, ''); - const type = glCore.shader.mapType(gl, uniformData.type); + const type = shaderUtils.mapType(gl, uniformData.type); + console.log('mapping ' + uniformData.type + ' to ' + type); // if (!name.match(maskRegex)) { @@ -151,7 +155,7 @@ uniforms[name] = { type: type, size: uniformData.size, - value: glCore.shader.defaultValue(type, uniformData.size), + value: shaderUtils.defaultValue(type, uniformData.size), }; /*eslint-enable */ } diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index e387e99..b57d095 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -147,12 +147,12 @@ // so if _width is 0 then width was not set.. if (this._width) { - this.scale.x = sign(this.scale.x) * this._width / this.texture.orig.width; + this.scale.x = sign(this.scale.x) * this._width / this._texture.orig.width; } if (this._height) { - this.scale.y = sign(this.scale.y) * this._height / this.texture.orig.height; + this.scale.y = sign(this.scale.y) * this._height / this._texture.orig.height; } } diff --git a/src/core/sprites/canvas/CanvasTinter.js b/src/core/sprites/canvas/CanvasTinter.js index 27e5e0c..fd9de5d 100644 --- a/src/core/sprites/canvas/CanvasTinter.js +++ b/src/core/sprites/canvas/CanvasTinter.js @@ -18,7 +18,7 @@ */ getTintedTexture: (sprite, color) => { - const texture = sprite.texture; + const texture = sprite._texture; color = CanvasTinter.roundColor(color); diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 5a4edda..8b9b797 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -344,6 +344,7 @@ const texture = this._texture; const style = this._style; + const padding = style.trim ? 0 : style.padding; texture.baseTexture.valid = true; texture.baseTexture.resolution = this.resolution; @@ -353,11 +354,11 @@ texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - texture.trim.x = -style.padding; - texture.trim.y = -style.padding; + texture.trim.x = -padding; + texture.trim.y = -padding; - texture.orig.width = texture._frame.width - (style.padding * 2); - texture.orig.height = texture._frame.height - (style.padding * 2); + texture.orig.width = texture._frame.width - (padding * 2); + texture.orig.height = texture._frame.height - (padding * 2); // call sprite onTextureUpdate to update scale if _width or _height were set this._onTextureUpdate(); diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index 853da3d..8a9c34b 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -114,6 +114,38 @@ this.cacheId = null; this.validate(); + + /** + * Fired when a not-immediately-available source finishes loading. + * + * @protected + * @event PIXI.BaseTexture#loaded + * @param {PIXI.BaseTexture} baseTexture - Resource loaded. + */ + + /** + * Fired when a not-immediately-available source fails to load. + * + * @protected + * @event PIXI.BaseTexture#error + * @param {PIXI.BaseTexture} baseTexture - Resource errored. + */ + + /** + * Fired when BaseTexture is updated. + * + * @protected + * @event PIXI.BaseTexture#update + * @param {PIXI.BaseTexture} baseTexture - Instance of texture being updated. + */ + + /** + * Fired when BaseTexture is destroyed. + * + * @protected + * @event PIXI.BaseTexture#dispose + * @param {PIXI.BaseTexture} baseTexture - Instance of texture being destroyed. + */ } setResource(resource) diff --git a/src/core/textures/Spritesheet.js b/src/core/textures/Spritesheet.js index d469c62..9b24f27 100644 --- a/src/core/textures/Spritesheet.js +++ b/src/core/textures/Spritesheet.js @@ -208,6 +208,7 @@ // lets also add the frame to pixi's global cache for fromFrame and fromImage functions TextureCache[i] = this.textures[i]; + this.textures[i].textureCacheId = i; } frameIndex++; diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 0a2c379..2213087 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -147,9 +147,9 @@ /** * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. * - * @event update - * @memberof PIXI.Texture# + * @event PIXI.Texture#update * @protected + * @param {PIXI.Texture} texture - Instance of texture being updated. */ this._updateID = 0; @@ -159,6 +159,15 @@ * @type {Object} */ this.transform = null; + + /** + * The id under which this Texture has been added to the texture cache. This is + * automatically set in certain cases, but may not always be accurate, particularly if + * the texture is in the cache under multiple ids. + * + * @member {string} + */ + this.textureCacheId = null; } /** @@ -225,11 +234,17 @@ this._uvs = null; this.trim = null; this.orig = null; + this.textureCacheId = null; this.valid = false; - this.off('dispose', this.dispose, this); - this.off('update', this.update, this); + for (const prop in TextureCache) + { + if (TextureCache[prop] === this) + { + delete TextureCache[prop]; + } + } } /** @@ -315,14 +330,15 @@ */ static fromLoader(source, imageUrl, name) { - // console.log('added from loader...') + // console.log('added from loader...') const resource = new ImageResource(source);//.from(imageUrl, crossorigin);// document.createElement('img'); - // console.log('base resource ' + resource.width); + // console.log('base resource ' + resource.width); const baseTexture = new BaseTexture(resource, settings.SCALE_MODE, getResolutionOfUrl(imageUrl)); + /// console.log('base width ' + baseTexture.width); const texture = new Texture(baseTexture); @@ -335,6 +351,7 @@ // lets also add the frame to pixi's global cache for fromFrame and fromImage fucntions BaseTextureCache[name] = baseTexture; TextureCache[name] = texture; + texture.textureCacheId = name; // also add references by url if they are different. if (name !== imageUrl) @@ -355,6 +372,10 @@ */ static addTextureToCache(texture, id) { + if (!texture.textureCacheId) + { + texture.textureCacheId = id; + } TextureCache[id] = texture; } @@ -495,6 +516,7 @@ */ Texture.EMPTY = new Texture(new BaseTexture()); removeAllHandlers(Texture.EMPTY); +removeAllHandlers(Texture.EMPTY.baseTexture); /** * A white texture of 10x10 size, used for graphics and other things @@ -505,3 +527,4 @@ */ Texture.WHITE = createWhiteTexture(); removeAllHandlers(Texture.WHITE); +removeAllHandlers(Texture.WHITE.baseTexture); diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index 59eb9fd..a9b9f51 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -1,6 +1,7 @@ import BaseTexture from './BaseTexture'; import { uid, BaseTextureCache } from '../utils'; -import * as ticker from '../ticker'; +import { shared } from '../ticker'; +import { UPDATE_PRIORITY } from '../const'; /** * A texture of a [playing] Video. @@ -125,7 +126,7 @@ if (!this._isAutoUpdating && this.autoUpdate) { - ticker.shared.add(this.update, this); + shared.add(this.update, this, UPDATE_PRIORITY.HIGH); this._isAutoUpdating = true; } } @@ -139,7 +140,7 @@ { if (this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); this._isAutoUpdating = false; } } @@ -187,7 +188,7 @@ { if (this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); } if (this.source && this.source._pixiId) @@ -281,12 +282,12 @@ if (!this._autoUpdate && this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); this._isAutoUpdating = false; } else if (this._autoUpdate && !this._isAutoUpdating) { - ticker.shared.add(this.update, this); + shared.add(this.update, this, UPDATE_PRIORITY.HIGH); this._isAutoUpdating = true; } } diff --git a/src/core/ticker/Ticker.js b/src/core/ticker/Ticker.js index 17a1517..40df138 100644 --- a/src/core/ticker/Ticker.js +++ b/src/core/ticker/Ticker.js @@ -1,12 +1,10 @@ import settings from '../settings'; -import EventEmitter from 'eventemitter3'; - -// Internal event used by composed emitter -const TICK = 'tick'; +import { UPDATE_PRIORITY } from '../const'; +import TickerListener from './TickerListener'; /** * A Ticker class that runs an update loop that other objects listen to. - * This class is composed around an EventEmitter object to add listeners + * This class is composed around listeners * meant for execution on the next requested animation frame. * Animation frames are requested only when necessary, * e.g. When the ticker is started and the emitter has listeners. @@ -22,10 +20,11 @@ constructor() { /** - * Internal emitter used to fire 'tick' event + * The first listener. All new listeners added are chained on this. * @private + * @type {TickerListener} */ - this._emitter = new EventEmitter(); + this._head = new TickerListener(null, null, Infinity); /** * Internal current frame request ID @@ -131,7 +130,7 @@ // Invoke listeners now this.update(time); // Listener side effects may have modified ticker state. - if (this.started && this._requestId === null && this._emitter.listeners(TICK, true)) + if (this.started && this._requestId === null && this._head.next) { this._requestId = requestAnimationFrame(this._tick); } @@ -148,7 +147,7 @@ */ _requestIfNeeded() { - if (this._requestId === null && this._emitter.listeners(TICK, true)) + if (this._requestId === null && this._head.next) { // ensure callbacks get correct delta this.lastTime = performance.now(); @@ -193,35 +192,72 @@ } /** - * Calls {@link module:eventemitter3.EventEmitter#on} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. + * Register a handler for tick events. Calls continuously unless + * it is removed or the ticker is stopped. * * @param {Function} fn - The listener function to be added for updates * @param {Function} [context] - The listener context + * @param {number} [priority=PIXI.UPDATE_PRIORITY.NORMAL] - The priority for emitting * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - add(fn, context) + add(fn, context, priority = UPDATE_PRIORITY.NORMAL) { - this._emitter.on(TICK, fn, context); - - this._startIfPossible(); - - return this; + return this._addListener(new TickerListener(fn, context, priority)); } /** - * Calls {@link module:eventemitter3.EventEmitter#once} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. + * Add a handler for the tick event which is only execute once. * * @param {Function} fn - The listener function to be added for one update * @param {Function} [context] - The listener context + * @param {number} [priority=PIXI.UPDATE_PRIORITY.NORMAL] - The priority for emitting * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - addOnce(fn, context) + addOnce(fn, context, priority = UPDATE_PRIORITY.NORMAL) { - this._emitter.once(TICK, fn, context); + return this._addListener(new TickerListener(fn, context, priority, true)); + } + + /** + * Internally adds the event handler so that it can be sorted by priority. + * Priority allows certain handler (user, AnimatedSprite, Interaction) to be run + * before the rendering. + * + * @private + * @param {TickerListener} listener - Current listener being added. + * @returns {PIXI.ticker.Ticker} This instance of a ticker + */ + _addListener(listener) + { + // For attaching to head + let current = this._head.next; + let previous = this._head; + + // Add the first item + if (!current) + { + listener.connect(previous); + } + else + { + // Go from highest to lowest priority + while (current) + { + if (listener.priority >= current.priority) + { + listener.connect(previous); + break; + } + previous = current; + current = current.next; + } + + // Not yet connected + if (!listener.previous) + { + listener.connect(previous); + } + } this._startIfPossible(); @@ -229,19 +265,33 @@ } /** - * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. - * It checks if the emitter has listeners for 'tick' event. - * If it does, then it cancels the animation frame. + * Removes any handlers matching the function and context parameters. + * If no handlers are left after removing, then it cancels the animation frame. * - * @param {Function} [fn] - The listener function to be removed + * @param {Function} fn - The listener function to be removed * @param {Function} [context] - The listener context to be removed * @returns {PIXI.ticker.Ticker} This instance of a ticker */ remove(fn, context) { - this._emitter.off(TICK, fn, context); + let listener = this._head.next; - if (!this._emitter.listeners(TICK, true)) + while (listener) + { + // We found a match, lets remove it + // no break to delete all possible matches + // incase a listener was added 2+ times + if (listener.match(fn, context)) + { + listener = listener.destroy(); + } + else + { + listener = listener.next; + } + } + + if (!this._head.next) { this._cancelIfNeeded(); } @@ -276,6 +326,25 @@ } /** + * Destroy the ticker and don't use after this. Calling + * this method removes all references to internal events. + */ + destroy() + { + this.stop(); + + let listener = this._head.next; + + while (listener) + { + listener = listener.destroy(true); + } + + this._head.destroy(); + this._head = null; + } + + /** * Triggers an update. An update entails setting the * current {@link PIXI.ticker.Ticker#elapsedMS}, * the current {@link PIXI.ticker.Ticker#deltaTime}, @@ -320,8 +389,22 @@ this.deltaTime = elapsedMS * settings.TARGET_FPMS * this.speed; + // Cache a local reference, in-case ticker is destroyed + // during the emit, we can still check for head.next + const head = this._head; + // Invoke listeners added to internal emitter - this._emitter.emit(TICK, this.deltaTime); + let listener = head.next; + + while (listener) + { + listener = listener.emit(this.deltaTime); + } + + if (!head.next) + { + this._cancelIfNeeded(); + } } else { diff --git a/src/core/ticker/TickerListener.js b/src/core/ticker/TickerListener.js new file mode 100644 index 0000000..2bedb34 --- /dev/null +++ b/src/core/ticker/TickerListener.js @@ -0,0 +1,158 @@ +/** + * Internal class for handling the priority sorting of ticker handlers. + * + * @private + * @class + * @memberof PIXI.ticker + */ +export default class TickerListener +{ + /** + * Constructor + * + * @param {Function} fn - The listener function to be added for one update + * @param {Function} [context=null] - The listener context + * @param {number} [priority=0] - The priority for emitting + * @param {boolean} [once=false] - If the handler should fire once + */ + constructor(fn, context = null, priority = 0, once = false) + { + /** + * The handler function to execute. + * @member {Function} + */ + this.fn = fn; + + /** + * The calling to execute. + * @member {Function} + */ + this.context = context; + + /** + * The current priority. + * @member {number} + */ + this.priority = priority; + + /** + * If this should only execute once. + * @member {boolean} + */ + this.once = once; + + /** + * The next item in chain. + * @member {TickerListener} + */ + this.next = null; + + /** + * The previous item in chain. + * @member {TickerListener} + */ + this.previous = null; + + /** + * `true` if this listener has been destroyed already. + * @member {boolean} + * @private + */ + this._destroyed = false; + } + + /** + * Simple compare function to figure out if a function and context match. + * + * @param {Function} fn - The listener function to be added for one update + * @param {Function} context - The listener context + * @return {boolean} `true` if the listener match the arguments + */ + match(fn, context) + { + context = context || null; + + return this.fn === fn && this.context === context; + } + + /** + * Emit by calling the current function. + * @param {number} deltaTime - time since the last emit. + * @return {TickerListener} Next ticker + */ + emit(deltaTime) + { + if (this.context) + { + this.fn.call(this.context, deltaTime); + } + else + { + this.fn(deltaTime); + } + + if (this.once) + { + this.destroy(); + } + + const redirect = this.next; + + // Soft-destroying should remove + // the next reference + if (this._destroyed) + { + this.next = null; + } + + return redirect; + } + + /** + * Connect to the list. + * @param {TickerListener} previous - Input node, previous listener + */ + connect(previous) + { + this.previous = previous; + if (previous.next) + { + previous.next.previous = this; + } + this.next = previous.next; + previous.next = this; + } + + /** + * Destroy and don't use after this. + * @param {boolean} [hard = false] `true` to remove the `next` reference, this + * is considered a hard destroy. Soft destroy maintains the next reference. + * @return {TickerListener} The listener to redirect while emitting or removing. + */ + destroy(hard = false) + { + this._destroyed = true; + this.fn = null; + this.context = null; + + // Disconnect, hook up next and previous + if (this.previous) + { + this.previous.next = this.next; + } + + if (this.next) + { + this.next.previous = this.previous; + } + + // Redirect to the next item + const redirect = this.previous; + + // Remove references + this.next = hard ? null : redirect; + this.previous = null; + + return redirect; + } +} diff --git a/src/core/ticker/index.js b/src/core/ticker/index.js index 4b3017c..c3020d4 100644 --- a/src/core/ticker/index.js +++ b/src/core/ticker/index.js @@ -45,8 +45,25 @@ const shared = new Ticker(); shared.autoStart = true; +shared.destroy = () => +{ + // protect destroying shared ticker + // this is used by other internal systems + // like AnimatedSprite and InteractionManager +}; /** + * This namespace contains an API for interacting with PIXI's internal global update loop. + * + * This ticker is used for rendering, {@link PIXI.extras.AnimatedSprite AnimatedSprite}, + * {@link PIXI.interaction.InteractionManager InteractionManager} and many other time-based PIXI systems. + * @example + * const ticker = new PIXI.ticker.Ticker(); + * ticker.stop(); + * ticker.add((deltaTime) => { + * // do something every frame + * }); + * ticker.start(); * @namespace PIXI.ticker */ export { shared, Ticker }; diff --git a/src/core/utils/index.js b/src/core/utils/index.js index be44750..7441f7b 100644 --- a/src/core/utils/index.js +++ b/src/core/utils/index.js @@ -9,6 +9,21 @@ let saidHello = false; /** + * Generalized convenience utilities for PIXI. + * @example + * // Extend PIXI's internal Event Emitter. + * class MyEmitter extends PIXI.utils.EventEmitter { + * constructor() { + * super(); + * console.log("Emitter created!"); + * } + * } + * + * // Get info on current device + * console.log(PIXI.utils.isMobile); + * + * // Convert hex color to string + * console.log(PIXI.utils.hex2string(0xff00ff)); // returns: "#ff00ff" * @namespace PIXI.utils */ export { diff --git a/src/deprecation.js b/src/deprecation.js index b9c73b3..33daeec 100644 --- a/src/deprecation.js +++ b/src/deprecation.js @@ -918,6 +918,35 @@ }); /** + * @method + * @name PIXI.prepare.BasePrepare#register + * @see PIXI.prepare.BasePrepare#registerFindHook + * @deprecated since version 4.4.2 + * @param {Function} [addHook] - Function call that takes two parameters: `item:*, queue:Array` + * function must return `true` if it was able to add item to the queue. + * @param {Function} [uploadHook] - Function call that takes two parameters: `prepare:CanvasPrepare, item:*` and + * function must return `true` if it was able to handle upload of item. + * @return {PIXI.BasePrepare} Instance of plugin for chaining. + */ +prepare.BasePrepare.prototype.register = function register(addHook, uploadHook) +{ + warn('renderer.plugins.prepare.register is now deprecated, ' + + 'please use renderer.plugins.prepare.registerFindHook & renderer.plugins.prepare.registerUploadHook'); + + if (addHook) + { + this.registerFindHook(addHook); + } + + if (uploadHook) + { + this.registerUploadHook(uploadHook); + } + + return this; +}; + +/** * The number of graphics or textures to upload to the GPU. * * @name PIXI.prepare.canvas.UPLOADS_PER_FRAME @@ -969,67 +998,73 @@ }, }); -Object.defineProperties(loaders.Resource.prototype, { - isJson: { - get() - { - warn('The isJson property is deprecated, please use `resource.type === Resource.TYPE.JSON`.'); +if (loaders.Loader) +{ + const Resource = loaders.Resource; + const Loader = loaders.Loader; - return this.type === loaders.Loader.Resource.TYPE.JSON; + Object.defineProperties(Resource.prototype, { + isJson: { + get() + { + warn('The isJson property is deprecated, please use `resource.type === Resource.TYPE.JSON`.'); + + return this.type === Resource.TYPE.JSON; + }, }, - }, - isXml: { - get() - { - warn('The isXml property is deprecated, please use `resource.type === Resource.TYPE.XML`.'); + isXml: { + get() + { + warn('The isXml property is deprecated, please use `resource.type === Resource.TYPE.XML`.'); - return this.type === loaders.Loader.Resource.TYPE.XML; + return this.type === Resource.TYPE.XML; + }, }, - }, - isImage: { - get() - { - warn('The isImage property is deprecated, please use `resource.type === Resource.TYPE.IMAGE`.'); + isImage: { + get() + { + warn('The isImage property is deprecated, please use `resource.type === Resource.TYPE.IMAGE`.'); - return this.type === loaders.Loader.Resource.TYPE.IMAGE; + return this.type === Resource.TYPE.IMAGE; + }, }, - }, - isAudio: { - get() - { - warn('The isAudio property is deprecated, please use `resource.type === Resource.TYPE.AUDIO`.'); + isAudio: { + get() + { + warn('The isAudio property is deprecated, please use `resource.type === Resource.TYPE.AUDIO`.'); - return this.type === loaders.Loader.Resource.TYPE.AUDIO; + return this.type === Resource.TYPE.AUDIO; + }, }, - }, - isVideo: { - get() - { - warn('The isVideo property is deprecated, please use `resource.type === Resource.TYPE.VIDEO`.'); + isVideo: { + get() + { + warn('The isVideo property is deprecated, please use `resource.type === Resource.TYPE.VIDEO`.'); - return this.type === loaders.Loader.Resource.TYPE.VIDEO; + return this.type === Resource.TYPE.VIDEO; + }, }, - }, -}); + }); -Object.defineProperties(loaders.Loader.prototype, { - before: { - get() - { - warn('The before() method is deprecated, please use pre().'); + Object.defineProperties(Loader.prototype, { + before: { + get() + { + warn('The before() method is deprecated, please use pre().'); - return this.pre; + return this.pre; + }, }, - }, - after: { - get() - { - warn('The after() method is deprecated, please use use().'); + after: { + get() + { + warn('The after() method is deprecated, please use use().'); - return this.use; + return this.use; + }, }, - }, -}); + }); +} /** * @name PIXI.interaction.interactiveTarget#defaultCursor diff --git a/src/extract/canvas/CanvasExtract.js b/src/extract/canvas/CanvasExtract.js index 941ca05..3ac5aee 100644 --- a/src/extract/canvas/CanvasExtract.js +++ b/src/extract/canvas/CanvasExtract.js @@ -8,7 +8,7 @@ * An instance of this class is automatically created by default, and can be found at renderer.plugins.extract * * @class - * @memberof PIXI + * @memberof PIXI.extract */ export default class CanvasExtract { @@ -21,9 +21,9 @@ /** * Collection of methods for extracting data (image, pixels, etc.) from a display object or render texture * - * @member {PIXI.CanvasExtract} extract + * @member {PIXI.extract.CanvasExtract} extract * @memberof PIXI.CanvasRenderer# - * @see PIXI.CanvasExtract + * @see PIXI.extract.CanvasExtract */ renderer.extract = this; } diff --git a/src/extract/index.js b/src/extract/index.js index 0f2170d..e9478c1 100644 --- a/src/extract/index.js +++ b/src/extract/index.js @@ -1,2 +1,22 @@ +/** + * This namespace provides renderer-specific plugins for exporting content from a renderer. + * For instance, these plugins can be used for saving an Image, Canvas element or for exporting the raw image data (pixels). + * + * Do not instantiate these plugins directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. + * @example + * // Create a new app (will auto-add extract plugin to renderer) + * const app = new PIXI.Application(); + * + * // Draw a red circle + * const graphics = new PIXI.Graphics() + * .beginFill(0xFF0000) + * .drawCircle(0, 0, 50); + * + * // Render the graphics as an HTMLImageElement + * const image = app.renderer.plugins.extract.image(graphics); + * document.body.appendChild(image); + * @namespace PIXI.extract + */ export { default as webgl } from './webgl/WebGLExtract'; export { default as canvas } from './canvas/CanvasExtract'; diff --git a/src/extract/webgl/WebGLExtract.js b/src/extract/webgl/WebGLExtract.js index 60228bb..1c3dd30 100644 --- a/src/extract/webgl/WebGLExtract.js +++ b/src/extract/webgl/WebGLExtract.js @@ -9,7 +9,7 @@ * An instance of this class is automatically created by default, and can be found at renderer.plugins.extract * * @class - * @memberof PIXI + * @memberof PIXI.extract */ export default class WebGLExtract { @@ -22,9 +22,9 @@ /** * Collection of methods for extracting data (image, pixels, etc.) from a display object or render texture * - * @member {PIXI.WebGLExtract} extract + * @member {PIXI.extract.WebGLExtract} extract * @memberof PIXI.WebGLRenderer# - * @see PIXI.WebGLExtract + * @see PIXI.extract.WebGLExtract */ renderer.extract = this; } diff --git a/src/extras/AnimatedSprite.js b/src/extras/AnimatedSprite.js index 13b804f..5297778 100644 --- a/src/extras/AnimatedSprite.js +++ b/src/extras/AnimatedSprite.js @@ -137,7 +137,7 @@ this.playing = true; if (this._autoUpdate) { - core.ticker.shared.add(this.update, this); + core.ticker.shared.add(this.update, this, core.UPDATE_PRIORITY.HIGH); } } diff --git a/src/extras/TextureTransform.js b/src/extras/TextureTransform.js index 78e6e40..16536d2 100644 --- a/src/extras/TextureTransform.js +++ b/src/extras/TextureTransform.js @@ -65,8 +65,36 @@ } /** + * Multiplies uvs array to transform + * @param {Float32Array} uvs mesh uvs + * @param {Float32Array} [out=uvs] output + * @returns {Float32Array} output + */ + multiplyUvs(uvs, out) + { + if (out === undefined) + { + out = uvs; + } + + const mat = this.mapCoord; + + for (let i = 0; i < uvs.length; i += 2) + { + const x = uvs[i]; + const y = uvs[i + 1]; + + out[i] = (x * mat.a) + (y * mat.c) + mat.tx; + out[i + 1] = (x * mat.b) + (y * mat.d) + mat.ty; + } + + return out; + } + + /** * updates matrices if texture was changed * @param {boolean} forceUpdate if true, matrices will be updated any case + * @returns {boolean} whether or not it was updated */ update(forceUpdate) { @@ -74,13 +102,13 @@ if (!tex || !tex.valid) { - return; + return false; } if (!forceUpdate && this._lastTextureID === tex._updateID) { - return; + return false; } this._lastTextureID = tex._updateID; @@ -110,5 +138,7 @@ frame[3] = (tex._frame.y + tex._frame.height - margin + offset) / texBase.height; this.uClampOffset[0] = offset / texBase.realWidth; this.uClampOffset[1] = offset / texBase.realHeight; + + return true; } } diff --git a/src/extras/TilingSprite.js b/src/extras/TilingSprite.js index 1f6014f..a3f027f 100644 --- a/src/extras/TilingSprite.js +++ b/src/extras/TilingSprite.js @@ -180,9 +180,9 @@ const transform = this.worldTransform; const resolution = renderer.resolution; const baseTexture = texture.baseTexture; - const baseTextureResolution = texture.baseTexture.resolution; - const modX = (this.tilePosition.x / this.tileScale.x) % texture._frame.width; - const modY = (this.tilePosition.y / this.tileScale.y) % texture._frame.height; + const baseTextureResolution = baseTexture.resolution; + const modX = ((this.tilePosition.x / this.tileScale.x) % texture._frame.width) * baseTextureResolution; + const modY = ((this.tilePosition.y / this.tileScale.y) % texture._frame.height) * baseTextureResolution; // create a nice shiny pattern! // TODO this needs to be refreshed if texture changes.. diff --git a/src/extras/cacheAsBitmap.js b/src/extras/cacheAsBitmap.js index 5dd5640..aea06a8 100644 --- a/src/extras/cacheAsBitmap.js +++ b/src/extras/cacheAsBitmap.js @@ -227,7 +227,16 @@ this.transform._parentID = -1; // restore the transform of the cached sprite to avoid the nasty flicker.. - this.updateTransform(); + if (!this.parent) + { + this.parent = renderer._tempDisplayObjectParent; + this.updateTransform(); + this.parent = null; + } + else + { + this.updateTransform(); + } // map the hit test.. this.containsPoint = cachedSprite.containsPoint.bind(cachedSprite); @@ -314,7 +323,17 @@ cachedSprite._bounds = this._bounds; cachedSprite.alpha = cacheAlpha; - this.updateTransform(); + if (!this.parent) + { + this.parent = renderer._tempDisplayObjectParent; + this.updateTransform(); + this.parent = null; + } + else + { + this.updateTransform(); + } + this.updateTransform = this.displayObjectUpdateTransform; this._cacheData.sprite = cachedSprite; @@ -358,9 +377,12 @@ * Destroys the cached object. * * @private + * @param {object|boolean} [options] - Options parameter. A boolean will act as if all options + * have been set to that value. + * Used when destroying containers, see the Container.destroy method. */ -DisplayObject.prototype._cacheAsBitmapDestroy = function _cacheAsBitmapDestroy() +DisplayObject.prototype._cacheAsBitmapDestroy = function _cacheAsBitmapDestroy(options) { this.cacheAsBitmap = false; - this.destroy(); + this.destroy(options); }; diff --git a/src/extras/index.js b/src/extras/index.js index bbd8bbf..a34d7ec 100644 --- a/src/extras/index.js +++ b/src/extras/index.js @@ -1,7 +1,7 @@ /** + * Additional PIXI DisplayObjects for animation, tiling and bitmap text. * @namespace PIXI.extras */ -export { default as TextureTransform } from './TextureTransform'; export { default as AnimatedSprite } from './AnimatedSprite'; export { default as TilingSprite } from './TilingSprite'; export { default as TilingSpriteRenderer } from './webgl/TilingSpriteRenderer'; diff --git a/src/extras/webgl/TilingSpriteRenderer.js b/src/extras/webgl/TilingSpriteRenderer.js index aeec172..eee3b5c 100644 --- a/src/extras/webgl/TilingSpriteRenderer.js +++ b/src/extras/webgl/TilingSpriteRenderer.js @@ -11,7 +11,7 @@ * WebGL renderer plugin for tiling sprites * * @class - * @memberof PIXI + * @memberof PIXI.extras * @extends PIXI.ObjectRenderer */ export default class TilingSpriteRenderer extends core.ObjectRenderer @@ -118,7 +118,7 @@ tempMat.invert(); if (isSimple) { - tempMat.append(uv.mapCoord); + tempMat.prepend(uv.mapCoord); } else { diff --git a/src/filters/displacement/DisplacementFilter.js b/src/filters/displacement/DisplacementFilter.js index d5355c3..b5d030c 100644 --- a/src/filters/displacement/DisplacementFilter.js +++ b/src/filters/displacement/DisplacementFilter.js @@ -35,7 +35,7 @@ this.maskSprite = sprite; this.maskMatrix = maskMatrix; - this.uniforms.mapSampler = sprite.texture; + this.uniforms.mapSampler = sprite._texture; this.uniforms.filterMatrix = maskMatrix; this.uniforms.scale = { x: 1, y: 1 }; diff --git a/src/filters/index.js b/src/filters/index.js index f3e8862..7ceb50a 100644 --- a/src/filters/index.js +++ b/src/filters/index.js @@ -1,4 +1,21 @@ /** + * This namespace contains WebGL-only display filters that can be applied + * to DisplayObjects using the {@link PIXI.DisplayObject#filters filters} property. + * @example + * // Create a new application + * const app = new PIXI.Application(); + * + * // Draw a green rectangle + * const rect = new PIXI.Graphics() + * .beginFill(0x00ff00) + * .drawRect(40, 40, 200, 200); + * + * // Add a blur filter + * rect.filters = [new PIXI.filters.BlurFilter()]; + * + * // Display rectangle + * app.stage.addChild(rect); + * document.body.appendChild(app.view); * @namespace PIXI.filters */ export { default as FXAAFilter } from './fxaa/FXAAFilter'; diff --git a/src/index.js b/src/index.js index c553427..77da490 100644 --- a/src/index.js +++ b/src/index.js @@ -20,6 +20,14 @@ import { utils } from './core'; utils.mixins.performMixins(); +/** + * Alias for {@link PIXI.loaders.shared}. + * @name loader + * @memberof PIXI + * @type {PIXI.loader.Loader} + */ +const loader = loaders.shared || null; + export { accessibility, extract, @@ -30,18 +38,8 @@ mesh, particles, prepare, + loader, }; -/** - * A premade instance of the loader that can be used to load resources. - * - * @name loader - * @memberof PIXI - * @property {PIXI.loaders.Loader} - */ -const loader = loaders && loaders.Loader ? new loaders.Loader() : null; // check is there in case user excludes loader lib - -export { loader }; - // Always export pixi globally. global.PIXI = exports; // eslint-disable-line diff --git a/src/interaction/InteractionData.js b/src/interaction/InteractionData.js index 02d53a3..449f8cc 100644 --- a/src/interaction/InteractionData.js +++ b/src/interaction/InteractionData.js @@ -30,7 +30,10 @@ /** * When passed to an event handler, this will be the original DOM Event that was captured * - * @member {Event} + * @see https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent + * @see https://developer.mozilla.org/en-US/docs/Web/API/TouchEvent + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent + * @member {MouseEvent|TouchEvent|PointerEvent} */ this.originalEvent = null; } diff --git a/src/interaction/InteractionManager.js b/src/interaction/InteractionManager.js index 8baf13a..2d75c7c 100644 --- a/src/interaction/InteractionManager.js +++ b/src/interaction/InteractionManager.js @@ -13,6 +13,14 @@ const MOUSE_POINTER_ID = 'MOUSE'; +// helpers for hitTest() - only used inside hitTest() +const hitTestEvent = { + target: null, + data: { + global: null, + }, +}; + /** * The interaction manager deals with mouse, touch and pointer events. Any DisplayObject can be interactive * if its interactive parameter is set to true @@ -245,67 +253,60 @@ this.setTargetElement(this.renderer.view, this.renderer.resolution); /** - * Fired when a pointer device button (usually a mouse button) is pressed on the display + * Fired when a pointer device button (usually a mouse left-button) is pressed on the display * object. * - * @event mousedown - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mousedown + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device secondary button (usually a mouse right-button) is pressed * on the display object. * - * @event rightdown - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightdown + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** - * Fired when a pointer device button (usually a mouse button) is released over the display + * Fired when a pointer device button (usually a mouse left-button) is released over the display * object. * - * @event mouseup - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseup + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device secondary button (usually a mouse right-button) is released * over the display object. * - * @event rightup - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightup + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** - * Fired when a pointer device button (usually a mouse button) is pressed and released on + * Fired when a pointer device button (usually a mouse left-button) is pressed and released on * the display object. * - * @event click - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#click + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device secondary button (usually a mouse right-button) is pressed * and released on the display object. * - * @event rightclick - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightclick + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** - * Fired when a pointer device button (usually a mouse button) is released outside the + * Fired when a pointer device button (usually a mouse left-button) is released outside the * display object that initially registered a * [mousedown]{@link PIXI.interaction.InteractionManager#event:mousedown}. * - * @event mouseupoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** @@ -313,146 +314,362 @@ * outside the display object that initially registered a * [rightdown]{@link PIXI.interaction.InteractionManager#event:rightdown}. * - * @event rightupoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device (usually a mouse) is moved while over the display object * - * @event mousemove - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mousemove + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device (usually a mouse) is moved onto the display object * - * @event mouseover - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseover + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device (usually a mouse) is moved off the display object * - * @event mouseout - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseout + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is pressed on the display object. * - * @event pointerdown - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerdown + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is released over the display object. * - * @event pointerup - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerup + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when the operating system cancels a pointer event * - * @event pointercancel - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointercancel + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is pressed and released on the display object. * - * @event pointertap - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointertap + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is released outside the display object that initially * registered a [pointerdown]{@link PIXI.interaction.InteractionManager#event:pointerdown}. * - * @event pointerupoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device is moved while over the display object * - * @event pointermove - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointermove + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device is moved onto the display object * - * @event pointerover - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerover + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device is moved off the display object * - * @event pointerout - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerout + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is placed on the display object. * - * @event touchstart - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchstart + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is removed from the display object. * - * @event touchend - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchend + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when the operating system cancels a touch * - * @event touchcancel - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchcancel + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is placed and removed from the display object. * - * @event tap - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#tap + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is removed outside of the display object that initially * registered a [touchstart]{@link PIXI.interaction.InteractionManager#event:touchstart}. * - * @event touchendoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchendoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is moved along the display object. * - * @event touchmove - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchmove + * @param {PIXI.interaction.InteractionData} event - Interaction data */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is pressed on the display. + * object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mousedown + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is pressed + * on the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightdown + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is released over the display + * object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseup + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is released + * over the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightup + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is pressed and released on + * the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#click + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is pressed + * and released on the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightclick + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is released outside the + * display object that initially registered a + * [mousedown]{@link PIXI.DisplayObject#event:mousedown}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is released + * outside the display object that initially registered a + * [rightdown]{@link PIXI.DisplayObject#event:rightdown}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device (usually a mouse) is moved while over the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mousemove + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device (usually a mouse) is moved onto the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseover + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device (usually a mouse) is moved off the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseout + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is pressed on the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerdown + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is released over the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerup + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when the operating system cancels a pointer event. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointercancel + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is pressed and released on the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointertap + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is released outside the display object that initially + * registered a [pointerdown]{@link PIXI.DisplayObject#event:pointerdown}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device is moved while over the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointermove + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device is moved onto the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerover + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device is moved off the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerout + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is placed on the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchstart + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is removed from the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchend + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when the operating system cancels a touch. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchcancel + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is placed and removed from the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#tap + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is removed outside of the display object that initially + * registered a [touchstart]{@link PIXI.DisplayObject#event:touchstart}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchendoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is moved along the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchmove + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + } + + /** + * Hit tests a point against the display tree, returning the first interactive object that is hit. + * + * @param {PIXI.Point} globalPoint - A point to hit test with, in global space. + * @param {PIXI.Container} [root] - The root display object to start from. If omitted, defaults + * to the last rendered root of the associated renderer. + * @return {PIXI.DisplayObject} The hit display object, if any. + */ + hitTest(globalPoint, root) + { + // clear the target for our hit test + hitTestEvent.target = null; + // assign the global point + hitTestEvent.data.global = globalPoint; + // ensure safety of the root + if (!root) + { + root = this.renderer._lastObjectRendered; + } + // run the hit test + this.processInteractive(hitTestEvent, root, null, true); + // return our found object - it'll be null if we didn't hit anything + + return hitTestEvent.target; } /** @@ -487,7 +704,7 @@ return; } - core.ticker.shared.add(this.update, this); + core.ticker.shared.add(this.update, this, core.UPDATE_PRIORITY.INTERACTION); if (window.navigator.msPointerEnabled) { @@ -804,8 +1021,6 @@ } } - let keepHitTestingAfterChildren = hitTest; - // ** FREE TIP **! If an object is not interactive or has no buttons in it // (such as a game scene!) set interactiveChildren to false for that displayObject. // This will allow pixi to completely ignore and bypass checking the displayObjects children. @@ -818,7 +1033,9 @@ const child = children[i]; // time to get recursive.. if this function will return if something is hit.. - if (this.processInteractive(interactionEvent, child, func, hitTest, interactiveParent)) + const childHit = this.processInteractive(interactionEvent, child, func, hitTest, interactiveParent); + + if (childHit) { // its a good idea to check if a child has lost its parent. // this means it has been removed whilst looping so its best @@ -827,8 +1044,6 @@ continue; } - hit = true; - // we no longer need to hit test any more objects in this container as we we // now know the parent has been hit interactiveParent = false; @@ -838,36 +1053,41 @@ // This means we no longer need to hit test anything else. We still need to run // through all objects, but we don't need to perform any hit tests. - keepHitTestingAfterChildren = false; - - if (child.interactive) + if (childHit) { - hitTest = false; + if (interactionEvent.target) + { + hitTest = false; + } + hit = true; } - - // we can break now as we have hit an object. } } } - hitTest = keepHitTestingAfterChildren; - // no point running this if the item is not interactive or does not have an interactive parent. if (interactive) { // if we are hit testing (as in we have no hit any objects yet) // We also don't need to worry about hit testing if once of the displayObjects children - // has already been hit! - if (hitTest && !hit) + // has already been hit - but only if it was interactive, otherwise we need to keep + // looking for an interactive child, just in case we hit one + if (hitTest && !interactionEvent.target) { if (displayObject.hitArea) { displayObject.worldTransform.applyInverse(point, this._tempPoint); - hit = displayObject.hitArea.contains(this._tempPoint.x, this._tempPoint.y); + if (displayObject.hitArea.contains(this._tempPoint.x, this._tempPoint.y)) + { + hit = true; + } } else if (displayObject.containsPoint) { - hit = displayObject.containsPoint(point); + if (displayObject.containsPoint(point)) + { + hit = true; + } } } @@ -878,7 +1098,10 @@ interactionEvent.target = displayObject; } - func(interactionEvent, displayObject, hit); + if (func) + { + func(interactionEvent, displayObject, !!hit); + } } } @@ -914,7 +1137,7 @@ { const event = events[i]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -998,7 +1221,7 @@ { const event = events[i]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1098,7 +1321,7 @@ const test = isRightButton ? flags.RIGHT_DOWN : flags.LEFT_DOWN; - const isDown = trackingData !== undefined && (trackingData.flags | test); + const isDown = trackingData !== undefined && (trackingData.flags & test); if (hit) { @@ -1118,11 +1341,11 @@ { if (isRightButton) { - trackingData.rightDown = hit; + trackingData.rightDown = false; } else { - trackingData.leftDown = hit; + trackingData.leftDown = false; } } } @@ -1180,7 +1403,7 @@ { const event = events[i]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1255,7 +1478,7 @@ this.setCursorMode(null); } - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1268,6 +1491,12 @@ { this.emit('mouseout', interactionEvent); } + else + { + // we can get touchleave events after touchend, so we want to make sure we don't + // introduce memory leaks + this.releaseInteractionDataForPointerId(interactionData.identifier); + } } /** @@ -1344,7 +1573,7 @@ // Only mouse and pointer can call onPointerOver, so events will always be length 1 const event = events[0]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1366,12 +1595,14 @@ * Get InteractionData for a given pointerId. Store that data as well * * @private - * @param {number} pointerId - Identifier from a pointer event + * @param {PointerEvent} event - Normalized pointer event, output from normalizeToPointerData * @return {InteractionData} - Interaction data for the given pointer identifier */ - getInteractionDataForPointerId(pointerId) + getInteractionDataForPointerId(event) { - if (pointerId === MOUSE_POINTER_ID) + const pointerId = event.pointerId; + + if (pointerId === MOUSE_POINTER_ID || event.pointerType === 'mouse') { return this.mouse; } diff --git a/src/interaction/InteractionTrackingData.js b/src/interaction/InteractionTrackingData.js index 6a1f69e..8493b63 100644 --- a/src/interaction/InteractionTrackingData.js +++ b/src/interaction/InteractionTrackingData.js @@ -35,8 +35,10 @@ } /** + * Unique pointer id of the event + * * @readonly - * @type {number} Unique pointer id of the event + * @member {number} */ get pointerId() { diff --git a/src/interaction/index.js b/src/interaction/index.js index 56c5b09..72f7d71 100644 --- a/src/interaction/index.js +++ b/src/interaction/index.js @@ -1,4 +1,8 @@ /** + * This namespace contains a renderer plugin for handling mouse, pointer, and touch events. + * + * Do not instantiate this plugin directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. * @namespace PIXI.interaction */ export { default as InteractionData } from './InteractionData'; diff --git a/src/interaction/interactiveTarget.js b/src/interaction/interactiveTarget.js index d252fba..cebac40 100644 --- a/src/interaction/interactiveTarget.js +++ b/src/interaction/interactiveTarget.js @@ -2,7 +2,7 @@ * Default property values of interactive objects * Used by {@link PIXI.interaction.InteractionManager} to automatically give all DisplayObjects these properties * - * @mixin + * @private * @name interactiveTarget * @memberof PIXI.interaction * @example @@ -14,18 +14,28 @@ * ); */ export default { + /** - * Determines if the displayObject be clicked/touched + * Enable interaction events for the DisplayObject. Touch, pointer and mouse + * events will not be emitted unless `interactive` is set to `true`. * - * @inner {boolean} + * @example + * const sprite = new PIXI.Sprite(texture); + * sprite.interactive = true; + * sprite.on('tap', (event) => { + * //handle event + * }); + * @member {boolean} + * @memberof PIXI.DisplayObject# */ interactive: false, /** * Determines if the children to the displayObject can be clicked/touched - * Setting this to false allows pixi to bypass a recursive hitTest function + * Setting this to false allows pixi to bypass a recursive `hitTest` function * - * @inner {boolean} + * @member {boolean} + * @memberof PIXI.Container# */ interactiveChildren: true, @@ -33,7 +43,12 @@ * Interaction shape. Children will be hit first, then this shape will be checked. * Setting this will cause this shape to be checked in hit tests rather than the displayObject's bounds. * - * @inner {PIXI.Rectangle|PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.RoundedRectangle} + * @example + * const sprite = new PIXI.Sprite(texture); + * sprite.interactive = true; + * sprite.hitArea = new PIXI.Rectangle(0, 0, 100, 100); + * @member {PIXI.Rectangle|PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.RoundedRectangle} + * @memberof PIXI.DisplayObject# */ hitArea: null, @@ -41,8 +56,12 @@ * If enabled, the mouse cursor use the pointer behavior when hovered over the displayObject if it is interactive * Setting this changes the 'cursor' property to `'pointer'`. * + * @example + * const sprite = new PIXI.Sprite(texture); + * sprite.interactive = true; + * sprite.buttonMode = true; * @member {boolean} - * @memberof PIXI.interaction.interactiveTarget# + * @memberof PIXI.DisplayObject# */ get buttonMode() { @@ -64,9 +83,14 @@ * This defines what cursor mode is used when the mouse cursor * is hovered over the displayObject. * + * @example + * const sprite = new PIXI.Sprite(texture); + * sprite.interactive = true; + * sprite.cursor = 'wait'; * @see https://developer.mozilla.org/en/docs/Web/CSS/cursor * - * @inner {string} + * @member {string} + * @memberof PIXI.DisplayObject# */ cursor: null, @@ -74,7 +98,7 @@ * Internal set of all active pointers, by identifier * * @member {Map} - * @memberof PIXI.interaction.interactiveTarget# + * @memberof PIXI.DisplayObject# * @private */ get trackedPointers() @@ -87,7 +111,8 @@ /** * Map of all tracked pointers, by identifier. Use trackedPointers to access. * - * @private {Map} + * @private + * @type {Map} */ _trackedPointers: undefined, }; diff --git a/src/loaders/index.js b/src/loaders/index.js index 5189793..daa1fc4 100644 --- a/src/loaders/index.js +++ b/src/loaders/index.js @@ -1,9 +1,22 @@ +import Application from '../core/Application'; +import Loader from './loader'; + /** + * This namespace contains APIs which extends the {@link https://github.com/englercj/resource-loader resource-loader} module + * for loading assets, data, and other resources dynamically. + * @example + * const loader = new PIXI.loaders.Loader(); + * loader.add('bunny', 'data/bunny.png') + * .add('spaceship', 'assets/spritesheet.json'); + * loader.load((loader, resources) => { + * // resources.bunny + * // resources.spaceship + * }); * @namespace PIXI.loaders */ -export { default as Loader } from './loader'; +export { Loader }; export { default as bitmapFontParser, parse as parseBitmapFontData } from './bitmapFontParser'; -export { default as spritesheetParser } from './spritesheetParser'; +export { default as spritesheetParser, getResourcePath } from './spritesheetParser'; export { default as textureParser } from './textureParser'; /** @@ -13,3 +26,55 @@ * @memberof PIXI.loaders */ export { Resource } from 'resource-loader'; + +/** + * A premade instance of the loader that can be used to load resources. + * @name shared + * @memberof PIXI.loaders + * @type {PIXI.loaders.Loader} + */ +const shared = new Loader(); + +shared.destroy = () => +{ + // protect destroying shared loader +}; + +export { shared }; + +// Mixin the loader construction +const AppPrototype = Application.prototype; + +AppPrototype._loader = null; + +/** + * Loader instance to help with asset loading. + * @name PIXI.Application#loader + * @type {PIXI.loaders.Loader} + */ +Object.defineProperty(AppPrototype, 'loader', { + get() + { + if (!this._loader) + { + const sharedLoader = this._options.sharedLoader; + + this._loader = sharedLoader ? shared : new Loader(); + } + + return this._loader; + }, +}); + +// Override the destroy function +// making sure to destroy the current Loader +AppPrototype._parentDestroy = AppPrototype.destroy; +AppPrototype.destroy = function destroy() +{ + if (this._loader) + { + this._loader.destroy(); + this._loader = null; + } + this._parentDestroy(); +}; diff --git a/src/loaders/loader.js b/src/loaders/loader.js index 767a411..59b8000 100644 --- a/src/loaders/loader.js +++ b/src/loaders/loader.js @@ -90,6 +90,15 @@ { Loader._pixiMiddleware.push(fn); } + + /** + * Destroy the loader, removes references. + */ + destroy() + { + this.removeAllListeners(); + this.reset(); + } } // Copy EE3 prototype (mixin) diff --git a/src/loaders/spritesheetParser.js b/src/loaders/spritesheetParser.js index e57dfb2..eda9584 100644 --- a/src/loaders/spritesheetParser.js +++ b/src/loaders/spritesheetParser.js @@ -1,12 +1,11 @@ import { Resource } from 'resource-loader'; -import path from 'path'; +import url from 'url'; import { Spritesheet } from '../core'; export default function () { return function spritesheetParser(resource, next) { - let resourcePath; const imageResourceName = `${resource.name}_image`; // skip if no data, its not json, it isn't spritesheet data, or the image resource already exists @@ -28,15 +27,7 @@ parentResource: resource, }; - // Prepend url path unless the resource image is a data url - if (resource.isDataUrl) - { - resourcePath = resource.data.meta.image; - } - else - { - resourcePath = `${path.dirname(resource.url.replace(this.baseUrl, ''))}/${resource.data.meta.image}`; - } + const resourcePath = getResourcePath(resource, this.baseUrl); // load the image for this sheet this.add(imageResourceName, resourcePath, loadOptions, function onImageLoad(res) @@ -56,3 +47,14 @@ }); }; } + +export function getResourcePath(resource, baseUrl) +{ + // Prepend url path unless the resource image is a data url + if (resource.isDataUrl) + { + return resource.data.meta.image; + } + + return url.resolve(resource.url.replace(baseUrl, ''), resource.data.meta.image); +} diff --git a/src/mesh/canvas/CanvasMeshRenderer.js b/src/mesh/canvas/CanvasMeshRenderer.js index 297205f..f7899e1 100644 --- a/src/mesh/canvas/CanvasMeshRenderer.js +++ b/src/mesh/canvas/CanvasMeshRenderer.js @@ -134,12 +134,33 @@ const textureWidth = base.width; const textureHeight = base.height; - const u0 = uvs[index0] * base.width; - const u1 = uvs[index1] * base.width; - const u2 = uvs[index2] * base.width; - const v0 = uvs[index0 + 1] * base.height; - const v1 = uvs[index1 + 1] * base.height; - const v2 = uvs[index2 + 1] * base.height; + let u0; + let u1; + let u2; + let v0; + let v1; + let v2; + + if (mesh.uploadUvTransform) + { + const ut = mesh._uvTransform.mapCoord; + + u0 = ((uvs[index0] * ut.a) + (uvs[index0 + 1] * ut.c) + ut.tx) * base.width; + u1 = ((uvs[index1] * ut.a) + (uvs[index1 + 1] * ut.c) + ut.tx) * base.width; + u2 = ((uvs[index2] * ut.a) + (uvs[index2 + 1] * ut.c) + ut.tx) * base.width; + v0 = ((uvs[index0] * ut.b) + (uvs[index0 + 1] * ut.d) + ut.ty) * base.height; + v1 = ((uvs[index1] * ut.b) + (uvs[index1 + 1] * ut.d) + ut.ty) * base.height; + v2 = ((uvs[index2] * ut.b) + (uvs[index2 + 1] * ut.d) + ut.ty) * base.height; + } + else + { + u0 = uvs[index0] * base.width; + u1 = uvs[index1] * base.width; + u2 = uvs[index2] * base.width; + v0 = uvs[index0 + 1] * base.height; + v1 = uvs[index1 + 1] * base.height; + v2 = uvs[index2 + 1] * base.height; + } let x0 = vertices[index0]; let x1 = vertices[index1]; diff --git a/src/mesh/webgl/MeshRenderer.js b/src/mesh/webgl/MeshRenderer.js index 78d24c3..ab99fcd 100644 --- a/src/mesh/webgl/MeshRenderer.js +++ b/src/mesh/webgl/MeshRenderer.js @@ -2,6 +2,8 @@ const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; +const matrixIdentity = core.Matrix.IDENTITY; + /** * WebGL renderer plugin for tiling sprites * @@ -64,6 +66,7 @@ mesh.geometry.glVertexArrayObjects[this.CONTEXT_UID].draw(mesh.drawMode, mesh.size, mesh.start, mesh.geometry.instanceCount); } + /** * draws mesh * @param {PIXI.mesh.RawMesh} mesh mesh instance diff --git a/src/mesh/webgl/mesh.vert b/src/mesh/webgl/mesh.vert index a337aef..acc096c 100644 --- a/src/mesh/webgl/mesh.vert +++ b/src/mesh/webgl/mesh.vert @@ -1,8 +1,9 @@ attribute vec2 aVertexPosition; attribute vec2 aTextureCoord; -uniform mat3 translationMatrix; uniform mat3 projectionMatrix; +uniform mat3 translationMatrix; +uniform mat3 uTransform; varying vec2 vTextureCoord; @@ -10,5 +11,5 @@ { gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - vTextureCoord = aTextureCoord; + vTextureCoord = (uTransform * vec3(aTextureCoord, 1.0)).xy; } diff --git a/src/particles/ParticleContainer.js b/src/particles/ParticleContainer.js index 67796cf..63f7791 100644 --- a/src/particles/ParticleContainer.js +++ b/src/particles/ParticleContainer.js @@ -313,10 +313,10 @@ frame.y * resolution, frame.width * resolution, frame.height * resolution, - positionX * resolution, - positionY * resolution, - finalWidth * resolution, - finalHeight * resolution + positionX * renderer.resolution, + positionY * renderer.resolution, + finalWidth * renderer.resolution, + finalHeight * renderer.resolution ); } } diff --git a/src/prepare/BasePrepare.js b/src/prepare/BasePrepare.js index 09eb5e5..e45c4dc 100644 --- a/src/prepare/BasePrepare.js +++ b/src/prepare/BasePrepare.js @@ -18,6 +18,18 @@ * basic queuing functionality and is extended by {@link PIXI.prepare.WebGLPrepare} and {@link PIXI.prepare.CanvasPrepare} * to provide preparation capabilities specific to their respective renderers. * + * @example + * // Create a sprite + * const sprite = new PIXI.Sprite.fromImage('something.png'); + * + * // Load object into GPU + * app.renderer.plugins.prepare.upload(sprite, () => { + * + * //Texture(s) has been uploaded to GPU + * app.stage.addChild(sprite); + * + * }) + * * @abstract * @class * @memberof PIXI.prepare @@ -100,8 +112,16 @@ this.prepareItems(); }; - this.register(findText, drawText); - this.register(findTextStyle, calculateTextStyle); + // hooks to find the correct texture + this.registerFindHook(findText); + this.registerFindHook(findTextStyle); + this.registerFindHook(findMultipleBaseTextures); + this.registerFindHook(findBaseTexture); + this.registerFindHook(findTexture); + + // upload hooks + this.registerUploadHook(drawText); + this.registerUploadHook(calculateTextStyle); } /** @@ -138,7 +158,7 @@ if (!this.ticking) { this.ticking = true; - SharedTicker.addOnce(this.tick, this); + SharedTicker.addOnce(this.tick, this, core.UPDATE_PRIORITY.UTILITY); } } else if (done) @@ -172,13 +192,16 @@ const item = this.queue[0]; let uploaded = false; - for (let i = 0, len = this.uploadHooks.length; i < len; i++) + if (item && !item._destroyed) { - if (this.uploadHooks[i](this.uploadHookHelper, item)) + for (let i = 0, len = this.uploadHooks.length; i < len; i++) { - this.queue.shift(); - uploaded = true; - break; + if (this.uploadHooks[i](this.uploadHookHelper, item)) + { + this.queue.shift(); + uploaded = true; + break; + } } } @@ -205,26 +228,36 @@ else { // if we are not finished, on the next rAF do this again - SharedTicker.addOnce(this.tick, this); + SharedTicker.addOnce(this.tick, this, core.UPDATE_PRIORITY.UTILITY); } } /** - * Adds hooks for finding and uploading items. + * Adds hooks for finding items. * - * @param {Function} [addHook] - Function call that takes two parameters: `item:*, queue:Array` - function must return `true` if it was able to add item to the queue. - * @param {Function} [uploadHook] - Function call that takes two parameters: `prepare:CanvasPrepare, item:*` and - * function must return `true` if it was able to handle upload of item. - * @return {PIXI.CanvasPrepare} Instance of plugin for chaining. + * @param {Function} addHook - Function call that takes two parameters: `item:*, queue:Array` + * function must return `true` if it was able to add item to the queue. + * @return {PIXI.BasePrepare} Instance of plugin for chaining. */ - register(addHook, uploadHook) + registerFindHook(addHook) { if (addHook) { this.addHooks.push(addHook); } + return this; + } + + /** + * Adds hooks for uploading items. + * + * @param {Function} uploadHook - Function call that takes two parameters: `prepare:CanvasPrepare, item:*` and + * function must return `true` if it was able to handle upload of item. + * @return {PIXI.BasePrepare} Instance of plugin for chaining. + */ + registerUploadHook(uploadHook) + { if (uploadHook) { this.uploadHooks.push(uploadHook); @@ -287,6 +320,88 @@ } /** + * Built-in hook to find multiple textures from objects like AnimatedSprites. + * + * @private + * @param {PIXI.DisplayObject} item - Display object to check + * @param {Array<*>} queue - Collection of items to upload + * @return {boolean} if a PIXI.Texture object was found. + */ +function findMultipleBaseTextures(item, queue) +{ + let result = false; + + // Objects with mutliple textures + if (item && item._textures && item._textures.length) + { + for (let i = 0; i < item._textures.length; i++) + { + if (item._textures[i] instanceof core.Texture) + { + const baseTexture = item._textures[i].baseTexture; + + if (queue.indexOf(baseTexture) === -1) + { + queue.push(baseTexture); + result = true; + } + } + } + } + + return result; +} + +/** + * Built-in hook to find BaseTextures from Sprites. + * + * @private + * @param {PIXI.DisplayObject} item - Display object to check + * @param {Array<*>} queue - Collection of items to upload + * @return {boolean} if a PIXI.Texture object was found. + */ +function findBaseTexture(item, queue) +{ + // Objects with textures, like Sprites/Text + if (item instanceof core.BaseTexture) + { + if (queue.indexOf(item) === -1) + { + queue.push(item); + } + + return true; + } + + return false; +} + +/** + * Built-in hook to find textures from objects. + * + * @private + * @param {PIXI.DisplayObject} item - Display object to check + * @param {Array<*>} queue - Collection of items to upload + * @return {boolean} if a PIXI.Texture object was found. + */ +function findTexture(item, queue) +{ + if (item._texture && item._texture instanceof core.Texture) + { + const texture = item._texture.baseTexture; + + if (queue.indexOf(texture) === -1) + { + queue.push(texture); + } + + return true; + } + + return false; +} + +/** * Built-in hook to draw PIXI.Text to its texture. * * @private diff --git a/src/prepare/canvas/CanvasPrepare.js b/src/prepare/canvas/CanvasPrepare.js index 0224bb5..09c207a 100644 --- a/src/prepare/canvas/CanvasPrepare.js +++ b/src/prepare/canvas/CanvasPrepare.js @@ -43,7 +43,7 @@ this.ctx = this.canvas.getContext('2d'); // Add textures to upload - this.register(findBaseTextures, uploadBaseTextures); + this.registerUploadHook(uploadBaseTextures); } /** @@ -89,39 +89,4 @@ return false; } -/** - * Built-in hook to find textures from Sprites. - * - * @private - * @param {PIXI.DisplayObject} item -Display object to check - * @param {Array<*>} queue - Collection of items to upload - * @return {boolean} if a PIXI.Texture object was found. - */ -function findBaseTextures(item, queue) -{ - // Objects with textures, like Sprites/Text - if (item instanceof core.BaseTexture) - { - if (queue.indexOf(item) === -1) - { - queue.push(item); - } - - return true; - } - else if (item._texture && item._texture instanceof core.Texture) - { - const texture = item._texture.baseTexture; - - if (queue.indexOf(texture) === -1) - { - queue.push(texture); - } - - return true; - } - - return false; -} - core.CanvasRenderer.registerPlugin('prepare', CanvasPrepare); diff --git a/src/prepare/index.js b/src/prepare/index.js index f559c45..e7491ec 100644 --- a/src/prepare/index.js +++ b/src/prepare/index.js @@ -1,4 +1,29 @@ /** + * The prepare namespace provides renderer-specific plugins for pre-rendering DisplayObjects. These plugins are useful for + * asynchronously preparing assets, textures, graphics waiting to be displayed. + * + * Do not instantiate these plugins directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. + * @example + * // Create a new application + * const app = new PIXI.Application(); + * document.body.appendChild(app.view); + * + * // Don't start rendering right away + * app.stop(); + * + * // create a display object + * const rect = new PIXI.Graphics() + * .beginFill(0x00ff00) + * .drawRect(40, 40, 200, 200); + * + * // Add to the stage + * app.stage.addChild(rect); + * + * // Don't start rendering until the graphic is uploaded to the GPU + * app.renderer.plugins.prepare.upload(app.stage, () => { + * app.start(); + * }); * @namespace PIXI.prepare */ export { default as webgl } from './webgl/WebGLPrepare'; diff --git a/src/prepare/webgl/WebGLPrepare.js b/src/prepare/webgl/WebGLPrepare.js index eb7023f..9df3c3f 100644 --- a/src/prepare/webgl/WebGLPrepare.js +++ b/src/prepare/webgl/WebGLPrepare.js @@ -22,12 +22,11 @@ this.uploadHookHelper = this.renderer; // Add textures and graphics to upload - this.register(findBaseTextures, uploadBaseTextures) - .register(findGraphics, uploadGraphics); + this.registerFindHook(findGraphics); + this.registerUploadHook(uploadBaseTextures); + this.registerUploadHook(uploadGraphics); } - } - /** * Built-in hook to upload PIXI.Texture objects to the GPU. * @@ -80,41 +79,6 @@ } /** - * Built-in hook to find textures from Sprites. - * - * @private - * @param {PIXI.DisplayObject} item - Display object to check - * @param {Array<*>} queue - Collection of items to upload - * @return {boolean} if a PIXI.Texture object was found. - */ -function findBaseTextures(item, queue) -{ - // Objects with textures, like Sprites/Text - if (item instanceof core.BaseTexture) - { - if (queue.indexOf(item) === -1) - { - queue.push(item); - } - - return true; - } - else if (item._texture && item._texture instanceof core.Texture) - { - const texture = item._texture.baseTexture; - - if (queue.indexOf(texture) === -1) - { - queue.push(texture); - } - - return true; - } - - return false; -} - -/** * Built-in hook to find graphics. * * @private diff --git a/test/.eslintrc.json b/test/.eslintrc.json index e03220d..ac12110 100644 --- a/test/.eslintrc.json +++ b/test/.eslintrc.json @@ -12,7 +12,8 @@ "sinon": false, "expect": false, "assert": false, - "PIXI": false + "PIXI": false, + "PointerEvent": true }, "rules": { "func-names": 0, diff --git a/test/core/BaseTexture.js b/test/core/BaseTexture.js index 8b93478..bb65068 100644 --- a/test/core/BaseTexture.js +++ b/test/core/BaseTexture.js @@ -14,4 +14,28 @@ expect(baseTexture.imageType).to.be.equals('png'); }); }); + + it('should remove Canvas BaseTexture from cache on destroy', function () + { + const canvas = document.createElement('canvas'); + const texture = PIXI.BaseTexture.fromCanvas(canvas); + const _pixiId = canvas._pixiId; + + expect(PIXI.utils.BaseTextureCache[_pixiId]).to.equal(texture); + texture.destroy(); + expect(PIXI.utils.BaseTextureCache[_pixiId]).to.equal(undefined); + }); + + it('should remove Image BaseTexture from cache on destroy', function () + { + const URL = 'foo.png'; + const NAME = 'bar'; + const image = new Image(); + + const texture = PIXI.Texture.fromLoader(image, URL, NAME); + + expect(PIXI.utils.BaseTextureCache[NAME]).to.equal(texture.baseTexture); + texture.destroy(true); + expect(PIXI.utils.BaseTextureCache[NAME]).to.equal(undefined); + }); }); diff --git a/test/core/Circle.js b/test/core/Circle.js index 183f98f..f621992 100644 --- a/test/core/Circle.js +++ b/test/core/Circle.js @@ -53,6 +53,10 @@ expect(circ1.contains(10, 16)).to.be.false; expect(circ1.contains(11, 15)).to.be.false; expect(circ1.contains(0, 0)).to.be.false; + + const circ2 = new PIXI.Circle(10, 10, 0); + + expect(circ2.contains(10, 10)).to.be.false; }); it('should return framing rectangle', function () diff --git a/test/core/Container.js b/test/core/Container.js index d11be33..cefde54 100644 --- a/test/core/Container.js +++ b/test/core/Container.js @@ -2,30 +2,36 @@ function testAddChild(fn) { - fn(function (container, obj) + return function () { - container.addChild(obj); - }); - fn(function (container, obj) - { - container.addChildAt(obj); - }); + fn(function (container, obj) + { + container.addChild(obj); + }); + fn(function (container, obj) + { + container.addChildAt(obj); + }); + }; } function testRemoveChild(fn) { - fn(function (container, obj) + return function () { - container.removeChild(obj); - }); - fn(function (container, obj) - { - container.removeChildAt(container.children.indexOf(obj)); - }); - fn(function (container, obj) - { - container.removeChildren(container.children.indexOf(obj), container.children.indexOf(obj) + 1); - }); + fn(function (container, obj) + { + container.removeChild(obj); + }); + fn(function (container, obj) + { + container.removeChildAt(container.children.indexOf(obj)); + }); + fn(function (container, obj) + { + container.removeChildren(container.children.indexOf(obj), container.children.indexOf(obj) + 1); + }); + }; } describe('PIXI.Container', function () diff --git a/test/core/Ellipse.js b/test/core/Ellipse.js index f2e8234..3f47442 100644 --- a/test/core/Ellipse.js +++ b/test/core/Ellipse.js @@ -56,6 +56,10 @@ expect(ellipse1.contains(10, 16)).to.be.false; expect(ellipse1.contains(11, 15)).to.be.false; expect(ellipse1.contains(0, 0)).to.be.false; + + const ellipse2 = new PIXI.Ellipse(10, 10, 0, 0); + + expect(ellipse2.contains(10, 10)).to.be.false; }); it('should return framing rectangle', function () diff --git a/test/core/Matrix.js b/test/core/Matrix.js index 9d2694b..76bd57c 100644 --- a/test/core/Matrix.js +++ b/test/core/Matrix.js @@ -107,10 +107,60 @@ expect(m1.ty).to.equal(m2.ty); }); + it('should prepend matrix', function () + { + const m1 = new PIXI.Matrix(); + const m2 = new PIXI.Matrix(); + + m2.set(2, 3, 4, 5, 100, 200); + m1.prepend(m2); + + expect(m1.a).to.equal(m2.a); + expect(m1.b).to.equal(m2.b); + expect(m1.c).to.equal(m2.c); + expect(m1.d).to.equal(m2.d); + expect(m1.tx).to.equal(m2.tx); + expect(m1.ty).to.equal(m2.ty); + + const m3 = new PIXI.Matrix(); + const m4 = new PIXI.Matrix(); + + m3.prepend(m4); + + expect(m3.a).to.equal(m4.a); + expect(m3.b).to.equal(m4.b); + expect(m3.c).to.equal(m4.c); + expect(m3.d).to.equal(m4.d); + expect(m3.tx).to.equal(m4.tx); + expect(m3.ty).to.equal(m4.ty); + }); + it('should get IDENTITY and TEMP_MATRIX', function () { expect(PIXI.Matrix.IDENTITY instanceof PIXI.Matrix).to.be.true; expect(PIXI.Matrix.TEMP_MATRIX instanceof PIXI.Matrix).to.be.true; }); -}); + it('should reset matrix to default when identity() is called', function () + { + const matrix = new PIXI.Matrix(); + + matrix.set(2, 3, 4, 5, 100, 200); + + expect(matrix.a).to.equal(2); + expect(matrix.b).to.equal(3); + expect(matrix.c).to.equal(4); + expect(matrix.d).to.equal(5); + expect(matrix.tx).to.equal(100); + expect(matrix.ty).to.equal(200); + + matrix.identity(); + + expect(matrix.a).to.equal(1); + expect(matrix.b).to.equal(0); + expect(matrix.c).to.equal(0); + expect(matrix.d).to.equal(1); + expect(matrix.tx).to.equal(0); + expect(matrix.ty).to.equal(0); + }); +}); diff --git a/.travis.yml b/.travis.yml index 3df8b22..14de334 100644 --- a/.travis.yml +++ b/.travis.yml @@ -110,3 +110,43 @@ on: all_branches: true condition: $TRAVIS_TAG + # Deploy config for latest release + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: dist + upload-dir: "release" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: docs + upload-dir: "release/docs" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: coverage + upload-dir: "release/coverage" + on: + all_branches: true + condition: $TRAVIS_TAG \ No newline at end of file diff --git a/README.md b/README.md index d26fadd..3b85f85 100644 --- a/README.md +++ b/README.md @@ -36,22 +36,14 @@ - Wiki: Other misc tutorials and resources are [on the Wiki](https://github.com/pixijs/pixi.js/wiki/Resources). ### Community ### -- Forums: Check out the [forum] (http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow] (http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. +- Forums: Check out the [forum](http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow](http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. - Inspiration: Check out the [gallery](http://www.pixijs.com/gallery) to see some of the amazing things people have created! - Chat: You can join us on [Gitter](https://gitter.im/pixijs/pixi.js) To chat about Pixi. We also now have a Slack channel. If you would like to join it please Send me an email (mat@goodboydigital.com) and I will invite you in. ### Setup ### -It's easy to get started with Pixi.js! Simply grab the pre-built versions from here: - -Release Branch - Nice and stable Pixi.js -- Unminified: [http://pixijs.download/release/pixi.js] -- Minified: [http://pixijs.download/release/pixi.min.js] - -Develop Branch - The bleeding edge version of Pixi.js -- Unminified: [http://pixijs.download/dev/pixi.js] -- Minified: [http://pixijs.download/dev/pixi.min.js] +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. @@ -70,10 +62,10 @@ #### CDN Install (via cdnjs) ```html - + ``` -_Note: `4.2.2` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ +_Note: `4.4.3` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ ### Demos ### @@ -130,7 +122,7 @@ // and the root stage PIXI.Container. var app = new PIXI.Application(); -// The application will create a canvas element for you that you +// The application will create a canvas element for you that you // can then insert into the DOM. document.body.appendChild(app.view); @@ -150,7 +142,7 @@ // Add the bunny to the scene we are building. app.stage.addChild(bunny); - + // Listen for frame updates app.ticker.add(function() { // each frame we spin the bunny around a bit diff --git a/bower.json b/bower.json deleted file mode 100644 index 41b668c..0000000 --- a/bower.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "pixi.js", - "main": "dist/pixi.min.js", - "ignore": [ - "**/.*", - "docs", - "node_modules", - "bower_components", - "test", - "scripts", - "bower.json", - "inch.json" - ], - "dependencies": { - }, - "devDependencies": { - } -} diff --git a/package.json b/package.json index d35cbb2..308469f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.3.5", + "version": "4.4.4", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ @@ -40,11 +40,11 @@ "lib": "babel src --out-dir lib -s", "predocs": "rimraf docs/**", "docs": "jsdoc -c scripts/jsdoc.conf.json -R README.md", - "publish:patch": "npm version patch --no-git-tag-version && npm publish", - "publish:minor": "npm version minor --no-git-tag-version && npm publish", - "publish:major": "npm version major --no-git-tag-version && npm publish", + "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", - "postpublish": "node scripts/release.js" + "postpublish": "git push && git push --tags" }, "files": [ "dist/", @@ -72,7 +72,6 @@ "electron": "^1.4.15", "eslint": "^3.5.0", "floss": "^2.0.1", - "gh-pages": "^0.11.0", "jaguarjs-jsdoc": "^1.0.1", "js-md5": "^0.4.1", "jsdoc": "^3.4.2", diff --git a/scripts/jsdoc.conf.json b/scripts/jsdoc.conf.json index 553e61c..3f52180 100644 --- a/scripts/jsdoc.conf.json +++ b/scripts/jsdoc.conf.json @@ -43,7 +43,7 @@ }, "markdown" : { "parser" : "gfm", - "hardwrap" : true + "hardwrap" : false }, "opts": { "encoding" : "utf8", diff --git a/scripts/release.js b/scripts/release.js deleted file mode 100644 index 156620c..0000000 --- a/scripts/release.js +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env node -'use strict'; - -// Publish script to push releases of the bin files -// the normally are gitignored -const ghpages = require('gh-pages'); -const path = require('path'); -const packageInfo = require(path.join(__dirname, '..', 'package.json')); - -const options = { - src: [ - 'dist/**/*', - 'lib/**/*', - 'src/**/*', - 'scripts/*', - 'scripts/renders/*', - 'test/**/*', - '*.json', - '*.md', - 'LICENSE', - '.eslintrc', - '.editorconfig', - '.travis.yml', - '.babelrc', - ], - dotfiles: true, - branch: 'release', - message: packageInfo.version, - logger: console.log.bind(console), -}; - -ghpages.publish(process.cwd(), options, (err) => -{ - if (err) - { - console.log(err); - process.exit(1); - - return; - } - - process.exit(0); -}); diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index f16bcb1..220427a 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -21,7 +21,7 @@ const DIV_HOOK_ZINDEX = 2; /** - * The Accessibility manager reacreates the ability to tab and and have content read by screen + * The Accessibility manager recreates the ability to tab and and have content read by screen * readers. This is very important as it can possibly help people with disabilities access pixi * content. * diff --git a/src/accessibility/index.js b/src/accessibility/index.js index 568cfb1..0ae108f 100644 --- a/src/accessibility/index.js +++ b/src/accessibility/index.js @@ -1,4 +1,9 @@ /** + * This namespace contains a renderer plugin for interaction accessibility for end-users + * with physical impairments which require screen-renders, keyboard navigation, etc. + * + * Do not instantiate this plugin directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. * @namespace PIXI.accessibility */ export { default as accessibleTarget } from './accessibleTarget'; diff --git a/src/core/Application.js b/src/core/Application.js index 5340578..2fcc729 100644 --- a/src/core/Application.js +++ b/src/core/Application.js @@ -1,6 +1,8 @@ import { autoDetectRenderer } from './autoDetectRenderer'; import Container from './display/Container'; import { shared, Ticker } from './ticker'; +import settings from './settings'; +import { UPDATE_PRIORITY } from './const'; /** * Convenience class to create a new PIXI application. @@ -22,28 +24,52 @@ */ export default class Application { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experience unexplained flickering try setting this to true. - * @param {boolean} [useSharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedLoader=false] - `true` to use PIXI.loaders.shared, `false` to create new Loader. */ - constructor(width, height, options, noWebGL, useSharedTicker = false) + constructor(options, arg2, arg3, arg4, arg5) { + // Support for constructor(width, height, options, noWebGL, useSharedTicker) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + forceCanvas: !!arg4, + sharedTicker: !!arg5, + }, arg3); + } + + /** + * The default options, so we mixin functionality later. + * @member {object} + * @protected + */ + this._options = options = Object.assign({ + sharedTicker: false, + forceCanvas: false, + sharedLoader: false, + }, options); + /** * WebGL renderer if available, otherwise CanvasRenderer * @member {PIXI.WebGLRenderer|PIXI.CanvasRenderer} */ - this.renderer = autoDetectRenderer(width, height, options, noWebGL); + this.renderer = autoDetectRenderer(options); /** * The root display container that's rendered. @@ -63,7 +89,7 @@ * @member {PIXI.ticker.Ticker} * @default PIXI.ticker.shared */ - this.ticker = useSharedTicker ? shared : new Ticker(); + this.ticker = options.sharedTicker ? shared : new Ticker(); // Start the rendering this.start(); @@ -78,7 +104,7 @@ this._ticker = ticker; if (ticker) { - ticker.add(this.render, this); + ticker.add(this.render, this, UPDATE_PRIORITY.LOW); } } get ticker() // eslint-disable-line require-jsdoc @@ -136,13 +162,18 @@ */ destroy(removeView) { - this.stop(); + const oldTicker = this._ticker; + this.ticker = null; + oldTicker.destroy(); + this.stage.destroy(); this.stage = null; this.renderer.destroy(removeView); this.renderer = null; + + this._options = null; } } diff --git a/src/core/autoDetectRenderer.js b/src/core/autoDetectRenderer.js index 8960425..9ec0364 100644 --- a/src/core/autoDetectRenderer.js +++ b/src/core/autoDetectRenderer.js @@ -2,6 +2,7 @@ import CanvasRenderer from './renderers/canvas/CanvasRenderer'; import WebGLRenderer from './renderers/webgl/WebGLRenderer'; +// eslint-disable-next-line valid-jsdoc /** * This helper function will automatically detect which renderer you should be using. * WebGL is the preferred renderer as it is a lot faster. If webGL is not supported by @@ -9,24 +10,32 @@ * * @memberof PIXI * @function autoDetectRenderer - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @return {PIXI.WebGLRenderer|PIXI.CanvasRenderer} Returns WebGL renderer if available, otherwise CanvasRenderer */ -export function autoDetectRenderer(width = 800, height = 600, options, noWebGL) +export function autoDetectRenderer(options, arg1, arg2, arg3) { - if (!noWebGL && utils.isWebGLSupported()) + // Backward-compatible support for noWebGL option + let forceCanvas = options && options.forceCanvas; + + if (arg3 !== undefined) { - return new WebGLRenderer(width, height, options); + forceCanvas = arg3; } - return new CanvasRenderer(width, height, options); + if (!forceCanvas && utils.isWebGLSupported()) + { + return new WebGLRenderer(options, arg1, arg2); + } + + return new CanvasRenderer(options, arg1, arg2); } diff --git a/src/core/const.js b/src/core/const.js index b9e99ae..2c68dee 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -341,3 +341,27 @@ LINEAR_VERTICAL: 0, LINEAR_HORIZONTAL: 1, }; + +/** + * Represents the update priorities used by internal PIXI classes when registered with + * the {@link PIXI.ticker.Ticker} object. Higher priority items are updated first and lower + * priority items, such as render, should go later. + * + * @static + * @constant + * @name UPDATE_PRIORITY + * @memberof PIXI + * @type {object} + * @property {number} INTERACTION=50 Highest priority, used for {@link PIXI.interaction.InteractionManager} + * @property {number} HIGH=25 High priority updating, {@link PIXI.VideoBaseTexture} and {@link PIXI.extras.AnimatedSprite} + * @property {number} NORMAL=0 Default priority for ticker events, see {@link PIXI.ticker.Ticker#add}. + * @property {number} LOW=-25 Low priority used for {@link PIXI.Application} rendering. + * @property {number} UTILITY=-50 Lowest priority used for {@link PIXI.prepare.BasePrepare} utility. + */ +export const UPDATE_PRIORITY = { + INTERACTION: 50, + HIGH: 25, + NORMAL: 0, + LOW: -25, + UTILITY: -50, +}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 4e6c77d..decb42a 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -13,7 +13,6 @@ * * @class * @extends EventEmitter - * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ export default class DisplayObject extends EventEmitter @@ -113,6 +112,29 @@ * @private */ this._mask = null; + + /** + * If the object has been destroyed via destroy(). If true, it should not be used. + * + * @member {boolean} + * @private + * @readonly + */ + this._destroyed = false; + + /** + * Fired when this DisplayObject is added to a Container. + * + * @event PIXI.DisplayObject#added + * @param {PIXI.Container} container - The container added to. + */ + + /** + * Fired when this DisplayObject is removed from a Container. + * + * @event PIXI.DisplayObject#removed + * @param {PIXI.Container} container - The container removed from. + */ } /** @@ -403,6 +425,8 @@ this.interactive = false; this.interactiveChildren = false; + + this._destroyed = true; } /** diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index aafdfe2..f787c4a 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -792,8 +792,8 @@ sprite.worldAlpha = this.worldAlpha * sprite.alpha; sprite.blendMode = this.blendMode; - sprite.texture._frame.width = rect.width; - sprite.texture._frame.height = rect.height; + sprite._texture._frame.width = rect.width; + sprite._texture._frame.height = rect.height; sprite.transform.worldTransform = this.transform.worldTransform; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index a24c775..b9f192d 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -19,11 +19,12 @@ */ export default class SystemRenderer extends EventEmitter { + // eslint-disable-next-line valid-jsdoc /** * @param {string} system - The name of the system this renderer is for. - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -37,27 +38,31 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(system, screenWidth, screenHeight, options) + constructor(system, options, arg2, arg3) { super(); sayHello(system); - // prepare options - if (options) + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') { - for (const i in settings.RENDER_OPTIONS) - { - if (typeof options[i] === 'undefined') - { - options[i] = settings.RENDER_OPTIONS[i]; - } - } + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); } - else - { - options = settings.RENDER_OPTIONS; - } + + // Add the default render options + options = Object.assign({}, settings.RENDER_OPTIONS, options); + + /** + * The supplied constructor options. + * + * @member {Object} + * @readOnly + */ + this.options = options; /** * The type of the renderer. @@ -75,7 +80,7 @@ * * @member {PIXI.Rectangle} */ - this.screen = new Rectangle(0, 0, screenWidth || 800, screenHeight || 600); + this.screen = new Rectangle(0, 0, options.width, options.height); /** * The canvas element that everything is drawn to @@ -279,6 +284,8 @@ this.blendModes = null; + this.options = null; + this.preserveDrawingBuffer = false; this.clearBeforeRender = false; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index e84e49c..691caf0 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -17,10 +17,11 @@ */ export default class CanvasRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -34,9 +35,9 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('Canvas', screenWidth, screenHeight, options); + super('Canvas', options, arg2, arg3); this.type = RENDERER_TYPE.CANVAS; @@ -96,7 +97,19 @@ this.context = null; this.renderingToScreen = false; - this.resize(screenWidth, screenHeight); + this.resize(this.options.width, this.options.height); + + /** + * Fired after rendering finishes. + * + * @event PIXI.CanvasRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.CanvasRenderer#prerender + */ } /** @@ -299,4 +312,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.CanvasRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.CanvasExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.CanvasPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.CanvasRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index e1cf7cd..f6fe123 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -31,11 +31,12 @@ */ export default class WebGLRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** * - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -55,9 +56,9 @@ * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('WebGL', screenWidth, screenHeight, options); + super('WebGL', options, arg2, arg3); /** * The type of this renderer as a standardised const @@ -89,12 +90,6 @@ }; - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ this._backgroundColorRgba[3] = this.transparent ? 0 : 1; this.globalUniforms = new UniformGroup({ @@ -117,6 +112,13 @@ this.initPlugins(); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ if(options.context) { this.context.initFromContext(options.context); @@ -169,6 +171,25 @@ } return this; + + /** + * Fired after rendering finishes. + * + * @event PIXI.WebGLRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.WebGLRenderer#prerender + */ + + /** + * Fired when the WebGL context is set. + * + * @event PIXI.WebGLRenderer#context + * @param {WebGLRenderingContext} gl - WebGL context. + */ } /** @@ -295,4 +316,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.WebGLRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.WebGLRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(WebGLRenderer); diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index 4eb6128..fa3d883 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -47,6 +47,14 @@ * @member {boolean} */ this.enabled = true; + + /** + * If enabled, pixi will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; } /** diff --git a/src/core/renderers/webgl/systems/ContextSystem.js b/src/core/renderers/webgl/systems/ContextSystem.js index ff958c7..8df85ea 100644 --- a/src/core/renderers/webgl/systems/ContextSystem.js +++ b/src/core/renderers/webgl/systems/ContextSystem.js @@ -75,7 +75,8 @@ */ createContext(canvas, options) { - var gl = canvas.getContext('webgl', options) || + var gl = //canvas.getContext('webgl2', options) || + canvas.getContext('webgl', options) || canvas.getContext('experimental-webgl', options); if (!gl) diff --git a/src/core/renderers/webgl/systems/FilterSystem.js b/src/core/renderers/webgl/systems/FilterSystem.js index f47335d..37e28d2 100644 --- a/src/core/renderers/webgl/systems/FilterSystem.js +++ b/src/core/renderers/webgl/systems/FilterSystem.js @@ -111,7 +111,7 @@ // TODO we should fit the rect around the transform.. } - else + else if (filters[0].autoFit) { sourceFrame.fit(filterData.stack[0].destinationFrame); } diff --git a/src/core/renderers/webgl/systems/shader/shader/mapType.js b/src/core/renderers/webgl/systems/shader/shader/mapType.js index 581dace..917538a 100644 --- a/src/core/renderers/webgl/systems/shader/shader/mapType.js +++ b/src/core/renderers/webgl/systems/shader/shader/mapType.js @@ -15,7 +15,7 @@ } } - return GL_TABLE[type]; + return GL_TABLE[type]; }; var GL_TABLE = null; diff --git a/src/core/settings.js b/src/core/settings.js index 18c5877..38ef45c 100644 --- a/src/core/settings.js +++ b/src/core/settings.js @@ -2,6 +2,15 @@ import canUploadSameBuffer from './utils/canUploadSameBuffer'; /** + * User's customizable globals for overriding the default PIXI settings, such + * as a renderer's default resolution, framerate, float percision, etc. + * @example + * // Use the native window resolution as the default resolution + * // will support high-density displays when rendering + * PIXI.settings.RESOLUTION = window.devicePixelRatio. + * + * // Disable interpolation when scaling, will make texture be pixelated + * PIXI.settings.SCALE_MODE = PIXI.SCALE_MODES.NEAREST; * @namespace PIXI.settings */ export default { @@ -101,6 +110,9 @@ * @property {boolean} clearBeforeRender=true * @property {boolean} preserveDrawingBuffer=false * @property {boolean} roundPixels=false + * @property {number} width=800 + * @property {number} height=600 + * @property {boolean} legacy=false */ RENDER_OPTIONS: { view: null, @@ -112,6 +124,9 @@ clearBeforeRender: true, preserveDrawingBuffer: false, roundPixels: false, + width: 800, + height: 600, + legacy: false, }, /** diff --git a/src/core/shader/Program.js b/src/core/shader/Program.js index c0b538b..07b4398 100644 --- a/src/core/shader/Program.js +++ b/src/core/shader/Program.js @@ -1,6 +1,6 @@ import extractUniformsFromSrc from './extractUniformsFromSrc'; import generateUniformsSync from './generateUniformsSync'; -import glCore from 'pixi-gl-core'; +import shaderUtils from '../renderers/webgl/systems/shader/shader'; import { ProgramCache } from '../utils'; import getTestContext from '../utils/getTestContext'; @@ -64,14 +64,17 @@ } else { - vertexSrc = glCore.shader.setPrecision(vertexSrc, 'mediump'); - fragmentSrc = glCore.shader.setPrecision(fragmentSrc, 'mediump'); + vertexSrc = shaderUtils.setPrecision(vertexSrc, 'mediump'); + fragmentSrc = shaderUtils.setPrecision(fragmentSrc, 'mediump'); - const program = glCore.shader.compileProgram(gl, vertexSrc, fragmentSrc); + const program = shaderUtils.compileProgram(gl, vertexSrc, fragmentSrc); + console.log("<<<<<<>>><<"); this.attributeData = this.getAttributeData(program, gl); this.uniformData = this.getUniformData(program, gl); + console.log(this.uniformData); + //gl.deleteProgram(program); } } @@ -95,13 +98,13 @@ for (let i = 0; i < totalAttributes; i++) { const attribData = gl.getActiveAttrib(program, i); - const type = glCore.shader.mapType(gl, attribData.type); + const type = shaderUtils.mapType(gl, attribData.type); /*eslint-disable */ const data = { type: type, name: attribData.name, - size: glCore.shader.mapSize(type), + size: shaderUtils.mapSize(type), location: 0, }; /*eslint-enable */ @@ -137,13 +140,14 @@ // TODO expose this as a prop? // const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); - const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); + //const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); for (let i = 0; i < totalUniforms; i++) { const uniformData = gl.getActiveUniform(program, i); const name = uniformData.name.replace(/\[.*?\]/, ''); - const type = glCore.shader.mapType(gl, uniformData.type); + const type = shaderUtils.mapType(gl, uniformData.type); + console.log('mapping ' + uniformData.type + ' to ' + type); // if (!name.match(maskRegex)) { @@ -151,7 +155,7 @@ uniforms[name] = { type: type, size: uniformData.size, - value: glCore.shader.defaultValue(type, uniformData.size), + value: shaderUtils.defaultValue(type, uniformData.size), }; /*eslint-enable */ } diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index e387e99..b57d095 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -147,12 +147,12 @@ // so if _width is 0 then width was not set.. if (this._width) { - this.scale.x = sign(this.scale.x) * this._width / this.texture.orig.width; + this.scale.x = sign(this.scale.x) * this._width / this._texture.orig.width; } if (this._height) { - this.scale.y = sign(this.scale.y) * this._height / this.texture.orig.height; + this.scale.y = sign(this.scale.y) * this._height / this._texture.orig.height; } } diff --git a/src/core/sprites/canvas/CanvasTinter.js b/src/core/sprites/canvas/CanvasTinter.js index 27e5e0c..fd9de5d 100644 --- a/src/core/sprites/canvas/CanvasTinter.js +++ b/src/core/sprites/canvas/CanvasTinter.js @@ -18,7 +18,7 @@ */ getTintedTexture: (sprite, color) => { - const texture = sprite.texture; + const texture = sprite._texture; color = CanvasTinter.roundColor(color); diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 5a4edda..8b9b797 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -344,6 +344,7 @@ const texture = this._texture; const style = this._style; + const padding = style.trim ? 0 : style.padding; texture.baseTexture.valid = true; texture.baseTexture.resolution = this.resolution; @@ -353,11 +354,11 @@ texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - texture.trim.x = -style.padding; - texture.trim.y = -style.padding; + texture.trim.x = -padding; + texture.trim.y = -padding; - texture.orig.width = texture._frame.width - (style.padding * 2); - texture.orig.height = texture._frame.height - (style.padding * 2); + texture.orig.width = texture._frame.width - (padding * 2); + texture.orig.height = texture._frame.height - (padding * 2); // call sprite onTextureUpdate to update scale if _width or _height were set this._onTextureUpdate(); diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index 853da3d..8a9c34b 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -114,6 +114,38 @@ this.cacheId = null; this.validate(); + + /** + * Fired when a not-immediately-available source finishes loading. + * + * @protected + * @event PIXI.BaseTexture#loaded + * @param {PIXI.BaseTexture} baseTexture - Resource loaded. + */ + + /** + * Fired when a not-immediately-available source fails to load. + * + * @protected + * @event PIXI.BaseTexture#error + * @param {PIXI.BaseTexture} baseTexture - Resource errored. + */ + + /** + * Fired when BaseTexture is updated. + * + * @protected + * @event PIXI.BaseTexture#update + * @param {PIXI.BaseTexture} baseTexture - Instance of texture being updated. + */ + + /** + * Fired when BaseTexture is destroyed. + * + * @protected + * @event PIXI.BaseTexture#dispose + * @param {PIXI.BaseTexture} baseTexture - Instance of texture being destroyed. + */ } setResource(resource) diff --git a/src/core/textures/Spritesheet.js b/src/core/textures/Spritesheet.js index d469c62..9b24f27 100644 --- a/src/core/textures/Spritesheet.js +++ b/src/core/textures/Spritesheet.js @@ -208,6 +208,7 @@ // lets also add the frame to pixi's global cache for fromFrame and fromImage functions TextureCache[i] = this.textures[i]; + this.textures[i].textureCacheId = i; } frameIndex++; diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 0a2c379..2213087 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -147,9 +147,9 @@ /** * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. * - * @event update - * @memberof PIXI.Texture# + * @event PIXI.Texture#update * @protected + * @param {PIXI.Texture} texture - Instance of texture being updated. */ this._updateID = 0; @@ -159,6 +159,15 @@ * @type {Object} */ this.transform = null; + + /** + * The id under which this Texture has been added to the texture cache. This is + * automatically set in certain cases, but may not always be accurate, particularly if + * the texture is in the cache under multiple ids. + * + * @member {string} + */ + this.textureCacheId = null; } /** @@ -225,11 +234,17 @@ this._uvs = null; this.trim = null; this.orig = null; + this.textureCacheId = null; this.valid = false; - this.off('dispose', this.dispose, this); - this.off('update', this.update, this); + for (const prop in TextureCache) + { + if (TextureCache[prop] === this) + { + delete TextureCache[prop]; + } + } } /** @@ -315,14 +330,15 @@ */ static fromLoader(source, imageUrl, name) { - // console.log('added from loader...') + // console.log('added from loader...') const resource = new ImageResource(source);//.from(imageUrl, crossorigin);// document.createElement('img'); - // console.log('base resource ' + resource.width); + // console.log('base resource ' + resource.width); const baseTexture = new BaseTexture(resource, settings.SCALE_MODE, getResolutionOfUrl(imageUrl)); + /// console.log('base width ' + baseTexture.width); const texture = new Texture(baseTexture); @@ -335,6 +351,7 @@ // lets also add the frame to pixi's global cache for fromFrame and fromImage fucntions BaseTextureCache[name] = baseTexture; TextureCache[name] = texture; + texture.textureCacheId = name; // also add references by url if they are different. if (name !== imageUrl) @@ -355,6 +372,10 @@ */ static addTextureToCache(texture, id) { + if (!texture.textureCacheId) + { + texture.textureCacheId = id; + } TextureCache[id] = texture; } @@ -495,6 +516,7 @@ */ Texture.EMPTY = new Texture(new BaseTexture()); removeAllHandlers(Texture.EMPTY); +removeAllHandlers(Texture.EMPTY.baseTexture); /** * A white texture of 10x10 size, used for graphics and other things @@ -505,3 +527,4 @@ */ Texture.WHITE = createWhiteTexture(); removeAllHandlers(Texture.WHITE); +removeAllHandlers(Texture.WHITE.baseTexture); diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index 59eb9fd..a9b9f51 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -1,6 +1,7 @@ import BaseTexture from './BaseTexture'; import { uid, BaseTextureCache } from '../utils'; -import * as ticker from '../ticker'; +import { shared } from '../ticker'; +import { UPDATE_PRIORITY } from '../const'; /** * A texture of a [playing] Video. @@ -125,7 +126,7 @@ if (!this._isAutoUpdating && this.autoUpdate) { - ticker.shared.add(this.update, this); + shared.add(this.update, this, UPDATE_PRIORITY.HIGH); this._isAutoUpdating = true; } } @@ -139,7 +140,7 @@ { if (this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); this._isAutoUpdating = false; } } @@ -187,7 +188,7 @@ { if (this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); } if (this.source && this.source._pixiId) @@ -281,12 +282,12 @@ if (!this._autoUpdate && this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); this._isAutoUpdating = false; } else if (this._autoUpdate && !this._isAutoUpdating) { - ticker.shared.add(this.update, this); + shared.add(this.update, this, UPDATE_PRIORITY.HIGH); this._isAutoUpdating = true; } } diff --git a/src/core/ticker/Ticker.js b/src/core/ticker/Ticker.js index 17a1517..40df138 100644 --- a/src/core/ticker/Ticker.js +++ b/src/core/ticker/Ticker.js @@ -1,12 +1,10 @@ import settings from '../settings'; -import EventEmitter from 'eventemitter3'; - -// Internal event used by composed emitter -const TICK = 'tick'; +import { UPDATE_PRIORITY } from '../const'; +import TickerListener from './TickerListener'; /** * A Ticker class that runs an update loop that other objects listen to. - * This class is composed around an EventEmitter object to add listeners + * This class is composed around listeners * meant for execution on the next requested animation frame. * Animation frames are requested only when necessary, * e.g. When the ticker is started and the emitter has listeners. @@ -22,10 +20,11 @@ constructor() { /** - * Internal emitter used to fire 'tick' event + * The first listener. All new listeners added are chained on this. * @private + * @type {TickerListener} */ - this._emitter = new EventEmitter(); + this._head = new TickerListener(null, null, Infinity); /** * Internal current frame request ID @@ -131,7 +130,7 @@ // Invoke listeners now this.update(time); // Listener side effects may have modified ticker state. - if (this.started && this._requestId === null && this._emitter.listeners(TICK, true)) + if (this.started && this._requestId === null && this._head.next) { this._requestId = requestAnimationFrame(this._tick); } @@ -148,7 +147,7 @@ */ _requestIfNeeded() { - if (this._requestId === null && this._emitter.listeners(TICK, true)) + if (this._requestId === null && this._head.next) { // ensure callbacks get correct delta this.lastTime = performance.now(); @@ -193,35 +192,72 @@ } /** - * Calls {@link module:eventemitter3.EventEmitter#on} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. + * Register a handler for tick events. Calls continuously unless + * it is removed or the ticker is stopped. * * @param {Function} fn - The listener function to be added for updates * @param {Function} [context] - The listener context + * @param {number} [priority=PIXI.UPDATE_PRIORITY.NORMAL] - The priority for emitting * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - add(fn, context) + add(fn, context, priority = UPDATE_PRIORITY.NORMAL) { - this._emitter.on(TICK, fn, context); - - this._startIfPossible(); - - return this; + return this._addListener(new TickerListener(fn, context, priority)); } /** - * Calls {@link module:eventemitter3.EventEmitter#once} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. + * Add a handler for the tick event which is only execute once. * * @param {Function} fn - The listener function to be added for one update * @param {Function} [context] - The listener context + * @param {number} [priority=PIXI.UPDATE_PRIORITY.NORMAL] - The priority for emitting * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - addOnce(fn, context) + addOnce(fn, context, priority = UPDATE_PRIORITY.NORMAL) { - this._emitter.once(TICK, fn, context); + return this._addListener(new TickerListener(fn, context, priority, true)); + } + + /** + * Internally adds the event handler so that it can be sorted by priority. + * Priority allows certain handler (user, AnimatedSprite, Interaction) to be run + * before the rendering. + * + * @private + * @param {TickerListener} listener - Current listener being added. + * @returns {PIXI.ticker.Ticker} This instance of a ticker + */ + _addListener(listener) + { + // For attaching to head + let current = this._head.next; + let previous = this._head; + + // Add the first item + if (!current) + { + listener.connect(previous); + } + else + { + // Go from highest to lowest priority + while (current) + { + if (listener.priority >= current.priority) + { + listener.connect(previous); + break; + } + previous = current; + current = current.next; + } + + // Not yet connected + if (!listener.previous) + { + listener.connect(previous); + } + } this._startIfPossible(); @@ -229,19 +265,33 @@ } /** - * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. - * It checks if the emitter has listeners for 'tick' event. - * If it does, then it cancels the animation frame. + * Removes any handlers matching the function and context parameters. + * If no handlers are left after removing, then it cancels the animation frame. * - * @param {Function} [fn] - The listener function to be removed + * @param {Function} fn - The listener function to be removed * @param {Function} [context] - The listener context to be removed * @returns {PIXI.ticker.Ticker} This instance of a ticker */ remove(fn, context) { - this._emitter.off(TICK, fn, context); + let listener = this._head.next; - if (!this._emitter.listeners(TICK, true)) + while (listener) + { + // We found a match, lets remove it + // no break to delete all possible matches + // incase a listener was added 2+ times + if (listener.match(fn, context)) + { + listener = listener.destroy(); + } + else + { + listener = listener.next; + } + } + + if (!this._head.next) { this._cancelIfNeeded(); } @@ -276,6 +326,25 @@ } /** + * Destroy the ticker and don't use after this. Calling + * this method removes all references to internal events. + */ + destroy() + { + this.stop(); + + let listener = this._head.next; + + while (listener) + { + listener = listener.destroy(true); + } + + this._head.destroy(); + this._head = null; + } + + /** * Triggers an update. An update entails setting the * current {@link PIXI.ticker.Ticker#elapsedMS}, * the current {@link PIXI.ticker.Ticker#deltaTime}, @@ -320,8 +389,22 @@ this.deltaTime = elapsedMS * settings.TARGET_FPMS * this.speed; + // Cache a local reference, in-case ticker is destroyed + // during the emit, we can still check for head.next + const head = this._head; + // Invoke listeners added to internal emitter - this._emitter.emit(TICK, this.deltaTime); + let listener = head.next; + + while (listener) + { + listener = listener.emit(this.deltaTime); + } + + if (!head.next) + { + this._cancelIfNeeded(); + } } else { diff --git a/src/core/ticker/TickerListener.js b/src/core/ticker/TickerListener.js new file mode 100644 index 0000000..2bedb34 --- /dev/null +++ b/src/core/ticker/TickerListener.js @@ -0,0 +1,158 @@ +/** + * Internal class for handling the priority sorting of ticker handlers. + * + * @private + * @class + * @memberof PIXI.ticker + */ +export default class TickerListener +{ + /** + * Constructor + * + * @param {Function} fn - The listener function to be added for one update + * @param {Function} [context=null] - The listener context + * @param {number} [priority=0] - The priority for emitting + * @param {boolean} [once=false] - If the handler should fire once + */ + constructor(fn, context = null, priority = 0, once = false) + { + /** + * The handler function to execute. + * @member {Function} + */ + this.fn = fn; + + /** + * The calling to execute. + * @member {Function} + */ + this.context = context; + + /** + * The current priority. + * @member {number} + */ + this.priority = priority; + + /** + * If this should only execute once. + * @member {boolean} + */ + this.once = once; + + /** + * The next item in chain. + * @member {TickerListener} + */ + this.next = null; + + /** + * The previous item in chain. + * @member {TickerListener} + */ + this.previous = null; + + /** + * `true` if this listener has been destroyed already. + * @member {boolean} + * @private + */ + this._destroyed = false; + } + + /** + * Simple compare function to figure out if a function and context match. + * + * @param {Function} fn - The listener function to be added for one update + * @param {Function} context - The listener context + * @return {boolean} `true` if the listener match the arguments + */ + match(fn, context) + { + context = context || null; + + return this.fn === fn && this.context === context; + } + + /** + * Emit by calling the current function. + * @param {number} deltaTime - time since the last emit. + * @return {TickerListener} Next ticker + */ + emit(deltaTime) + { + if (this.context) + { + this.fn.call(this.context, deltaTime); + } + else + { + this.fn(deltaTime); + } + + if (this.once) + { + this.destroy(); + } + + const redirect = this.next; + + // Soft-destroying should remove + // the next reference + if (this._destroyed) + { + this.next = null; + } + + return redirect; + } + + /** + * Connect to the list. + * @param {TickerListener} previous - Input node, previous listener + */ + connect(previous) + { + this.previous = previous; + if (previous.next) + { + previous.next.previous = this; + } + this.next = previous.next; + previous.next = this; + } + + /** + * Destroy and don't use after this. + * @param {boolean} [hard = false] `true` to remove the `next` reference, this + * is considered a hard destroy. Soft destroy maintains the next reference. + * @return {TickerListener} The listener to redirect while emitting or removing. + */ + destroy(hard = false) + { + this._destroyed = true; + this.fn = null; + this.context = null; + + // Disconnect, hook up next and previous + if (this.previous) + { + this.previous.next = this.next; + } + + if (this.next) + { + this.next.previous = this.previous; + } + + // Redirect to the next item + const redirect = this.previous; + + // Remove references + this.next = hard ? null : redirect; + this.previous = null; + + return redirect; + } +} diff --git a/src/core/ticker/index.js b/src/core/ticker/index.js index 4b3017c..c3020d4 100644 --- a/src/core/ticker/index.js +++ b/src/core/ticker/index.js @@ -45,8 +45,25 @@ const shared = new Ticker(); shared.autoStart = true; +shared.destroy = () => +{ + // protect destroying shared ticker + // this is used by other internal systems + // like AnimatedSprite and InteractionManager +}; /** + * This namespace contains an API for interacting with PIXI's internal global update loop. + * + * This ticker is used for rendering, {@link PIXI.extras.AnimatedSprite AnimatedSprite}, + * {@link PIXI.interaction.InteractionManager InteractionManager} and many other time-based PIXI systems. + * @example + * const ticker = new PIXI.ticker.Ticker(); + * ticker.stop(); + * ticker.add((deltaTime) => { + * // do something every frame + * }); + * ticker.start(); * @namespace PIXI.ticker */ export { shared, Ticker }; diff --git a/src/core/utils/index.js b/src/core/utils/index.js index be44750..7441f7b 100644 --- a/src/core/utils/index.js +++ b/src/core/utils/index.js @@ -9,6 +9,21 @@ let saidHello = false; /** + * Generalized convenience utilities for PIXI. + * @example + * // Extend PIXI's internal Event Emitter. + * class MyEmitter extends PIXI.utils.EventEmitter { + * constructor() { + * super(); + * console.log("Emitter created!"); + * } + * } + * + * // Get info on current device + * console.log(PIXI.utils.isMobile); + * + * // Convert hex color to string + * console.log(PIXI.utils.hex2string(0xff00ff)); // returns: "#ff00ff" * @namespace PIXI.utils */ export { diff --git a/src/deprecation.js b/src/deprecation.js index b9c73b3..33daeec 100644 --- a/src/deprecation.js +++ b/src/deprecation.js @@ -918,6 +918,35 @@ }); /** + * @method + * @name PIXI.prepare.BasePrepare#register + * @see PIXI.prepare.BasePrepare#registerFindHook + * @deprecated since version 4.4.2 + * @param {Function} [addHook] - Function call that takes two parameters: `item:*, queue:Array` + * function must return `true` if it was able to add item to the queue. + * @param {Function} [uploadHook] - Function call that takes two parameters: `prepare:CanvasPrepare, item:*` and + * function must return `true` if it was able to handle upload of item. + * @return {PIXI.BasePrepare} Instance of plugin for chaining. + */ +prepare.BasePrepare.prototype.register = function register(addHook, uploadHook) +{ + warn('renderer.plugins.prepare.register is now deprecated, ' + + 'please use renderer.plugins.prepare.registerFindHook & renderer.plugins.prepare.registerUploadHook'); + + if (addHook) + { + this.registerFindHook(addHook); + } + + if (uploadHook) + { + this.registerUploadHook(uploadHook); + } + + return this; +}; + +/** * The number of graphics or textures to upload to the GPU. * * @name PIXI.prepare.canvas.UPLOADS_PER_FRAME @@ -969,67 +998,73 @@ }, }); -Object.defineProperties(loaders.Resource.prototype, { - isJson: { - get() - { - warn('The isJson property is deprecated, please use `resource.type === Resource.TYPE.JSON`.'); +if (loaders.Loader) +{ + const Resource = loaders.Resource; + const Loader = loaders.Loader; - return this.type === loaders.Loader.Resource.TYPE.JSON; + Object.defineProperties(Resource.prototype, { + isJson: { + get() + { + warn('The isJson property is deprecated, please use `resource.type === Resource.TYPE.JSON`.'); + + return this.type === Resource.TYPE.JSON; + }, }, - }, - isXml: { - get() - { - warn('The isXml property is deprecated, please use `resource.type === Resource.TYPE.XML`.'); + isXml: { + get() + { + warn('The isXml property is deprecated, please use `resource.type === Resource.TYPE.XML`.'); - return this.type === loaders.Loader.Resource.TYPE.XML; + return this.type === Resource.TYPE.XML; + }, }, - }, - isImage: { - get() - { - warn('The isImage property is deprecated, please use `resource.type === Resource.TYPE.IMAGE`.'); + isImage: { + get() + { + warn('The isImage property is deprecated, please use `resource.type === Resource.TYPE.IMAGE`.'); - return this.type === loaders.Loader.Resource.TYPE.IMAGE; + return this.type === Resource.TYPE.IMAGE; + }, }, - }, - isAudio: { - get() - { - warn('The isAudio property is deprecated, please use `resource.type === Resource.TYPE.AUDIO`.'); + isAudio: { + get() + { + warn('The isAudio property is deprecated, please use `resource.type === Resource.TYPE.AUDIO`.'); - return this.type === loaders.Loader.Resource.TYPE.AUDIO; + return this.type === Resource.TYPE.AUDIO; + }, }, - }, - isVideo: { - get() - { - warn('The isVideo property is deprecated, please use `resource.type === Resource.TYPE.VIDEO`.'); + isVideo: { + get() + { + warn('The isVideo property is deprecated, please use `resource.type === Resource.TYPE.VIDEO`.'); - return this.type === loaders.Loader.Resource.TYPE.VIDEO; + return this.type === Resource.TYPE.VIDEO; + }, }, - }, -}); + }); -Object.defineProperties(loaders.Loader.prototype, { - before: { - get() - { - warn('The before() method is deprecated, please use pre().'); + Object.defineProperties(Loader.prototype, { + before: { + get() + { + warn('The before() method is deprecated, please use pre().'); - return this.pre; + return this.pre; + }, }, - }, - after: { - get() - { - warn('The after() method is deprecated, please use use().'); + after: { + get() + { + warn('The after() method is deprecated, please use use().'); - return this.use; + return this.use; + }, }, - }, -}); + }); +} /** * @name PIXI.interaction.interactiveTarget#defaultCursor diff --git a/src/extract/canvas/CanvasExtract.js b/src/extract/canvas/CanvasExtract.js index 941ca05..3ac5aee 100644 --- a/src/extract/canvas/CanvasExtract.js +++ b/src/extract/canvas/CanvasExtract.js @@ -8,7 +8,7 @@ * An instance of this class is automatically created by default, and can be found at renderer.plugins.extract * * @class - * @memberof PIXI + * @memberof PIXI.extract */ export default class CanvasExtract { @@ -21,9 +21,9 @@ /** * Collection of methods for extracting data (image, pixels, etc.) from a display object or render texture * - * @member {PIXI.CanvasExtract} extract + * @member {PIXI.extract.CanvasExtract} extract * @memberof PIXI.CanvasRenderer# - * @see PIXI.CanvasExtract + * @see PIXI.extract.CanvasExtract */ renderer.extract = this; } diff --git a/src/extract/index.js b/src/extract/index.js index 0f2170d..e9478c1 100644 --- a/src/extract/index.js +++ b/src/extract/index.js @@ -1,2 +1,22 @@ +/** + * This namespace provides renderer-specific plugins for exporting content from a renderer. + * For instance, these plugins can be used for saving an Image, Canvas element or for exporting the raw image data (pixels). + * + * Do not instantiate these plugins directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. + * @example + * // Create a new app (will auto-add extract plugin to renderer) + * const app = new PIXI.Application(); + * + * // Draw a red circle + * const graphics = new PIXI.Graphics() + * .beginFill(0xFF0000) + * .drawCircle(0, 0, 50); + * + * // Render the graphics as an HTMLImageElement + * const image = app.renderer.plugins.extract.image(graphics); + * document.body.appendChild(image); + * @namespace PIXI.extract + */ export { default as webgl } from './webgl/WebGLExtract'; export { default as canvas } from './canvas/CanvasExtract'; diff --git a/src/extract/webgl/WebGLExtract.js b/src/extract/webgl/WebGLExtract.js index 60228bb..1c3dd30 100644 --- a/src/extract/webgl/WebGLExtract.js +++ b/src/extract/webgl/WebGLExtract.js @@ -9,7 +9,7 @@ * An instance of this class is automatically created by default, and can be found at renderer.plugins.extract * * @class - * @memberof PIXI + * @memberof PIXI.extract */ export default class WebGLExtract { @@ -22,9 +22,9 @@ /** * Collection of methods for extracting data (image, pixels, etc.) from a display object or render texture * - * @member {PIXI.WebGLExtract} extract + * @member {PIXI.extract.WebGLExtract} extract * @memberof PIXI.WebGLRenderer# - * @see PIXI.WebGLExtract + * @see PIXI.extract.WebGLExtract */ renderer.extract = this; } diff --git a/src/extras/AnimatedSprite.js b/src/extras/AnimatedSprite.js index 13b804f..5297778 100644 --- a/src/extras/AnimatedSprite.js +++ b/src/extras/AnimatedSprite.js @@ -137,7 +137,7 @@ this.playing = true; if (this._autoUpdate) { - core.ticker.shared.add(this.update, this); + core.ticker.shared.add(this.update, this, core.UPDATE_PRIORITY.HIGH); } } diff --git a/src/extras/TextureTransform.js b/src/extras/TextureTransform.js index 78e6e40..16536d2 100644 --- a/src/extras/TextureTransform.js +++ b/src/extras/TextureTransform.js @@ -65,8 +65,36 @@ } /** + * Multiplies uvs array to transform + * @param {Float32Array} uvs mesh uvs + * @param {Float32Array} [out=uvs] output + * @returns {Float32Array} output + */ + multiplyUvs(uvs, out) + { + if (out === undefined) + { + out = uvs; + } + + const mat = this.mapCoord; + + for (let i = 0; i < uvs.length; i += 2) + { + const x = uvs[i]; + const y = uvs[i + 1]; + + out[i] = (x * mat.a) + (y * mat.c) + mat.tx; + out[i + 1] = (x * mat.b) + (y * mat.d) + mat.ty; + } + + return out; + } + + /** * updates matrices if texture was changed * @param {boolean} forceUpdate if true, matrices will be updated any case + * @returns {boolean} whether or not it was updated */ update(forceUpdate) { @@ -74,13 +102,13 @@ if (!tex || !tex.valid) { - return; + return false; } if (!forceUpdate && this._lastTextureID === tex._updateID) { - return; + return false; } this._lastTextureID = tex._updateID; @@ -110,5 +138,7 @@ frame[3] = (tex._frame.y + tex._frame.height - margin + offset) / texBase.height; this.uClampOffset[0] = offset / texBase.realWidth; this.uClampOffset[1] = offset / texBase.realHeight; + + return true; } } diff --git a/src/extras/TilingSprite.js b/src/extras/TilingSprite.js index 1f6014f..a3f027f 100644 --- a/src/extras/TilingSprite.js +++ b/src/extras/TilingSprite.js @@ -180,9 +180,9 @@ const transform = this.worldTransform; const resolution = renderer.resolution; const baseTexture = texture.baseTexture; - const baseTextureResolution = texture.baseTexture.resolution; - const modX = (this.tilePosition.x / this.tileScale.x) % texture._frame.width; - const modY = (this.tilePosition.y / this.tileScale.y) % texture._frame.height; + const baseTextureResolution = baseTexture.resolution; + const modX = ((this.tilePosition.x / this.tileScale.x) % texture._frame.width) * baseTextureResolution; + const modY = ((this.tilePosition.y / this.tileScale.y) % texture._frame.height) * baseTextureResolution; // create a nice shiny pattern! // TODO this needs to be refreshed if texture changes.. diff --git a/src/extras/cacheAsBitmap.js b/src/extras/cacheAsBitmap.js index 5dd5640..aea06a8 100644 --- a/src/extras/cacheAsBitmap.js +++ b/src/extras/cacheAsBitmap.js @@ -227,7 +227,16 @@ this.transform._parentID = -1; // restore the transform of the cached sprite to avoid the nasty flicker.. - this.updateTransform(); + if (!this.parent) + { + this.parent = renderer._tempDisplayObjectParent; + this.updateTransform(); + this.parent = null; + } + else + { + this.updateTransform(); + } // map the hit test.. this.containsPoint = cachedSprite.containsPoint.bind(cachedSprite); @@ -314,7 +323,17 @@ cachedSprite._bounds = this._bounds; cachedSprite.alpha = cacheAlpha; - this.updateTransform(); + if (!this.parent) + { + this.parent = renderer._tempDisplayObjectParent; + this.updateTransform(); + this.parent = null; + } + else + { + this.updateTransform(); + } + this.updateTransform = this.displayObjectUpdateTransform; this._cacheData.sprite = cachedSprite; @@ -358,9 +377,12 @@ * Destroys the cached object. * * @private + * @param {object|boolean} [options] - Options parameter. A boolean will act as if all options + * have been set to that value. + * Used when destroying containers, see the Container.destroy method. */ -DisplayObject.prototype._cacheAsBitmapDestroy = function _cacheAsBitmapDestroy() +DisplayObject.prototype._cacheAsBitmapDestroy = function _cacheAsBitmapDestroy(options) { this.cacheAsBitmap = false; - this.destroy(); + this.destroy(options); }; diff --git a/src/extras/index.js b/src/extras/index.js index bbd8bbf..a34d7ec 100644 --- a/src/extras/index.js +++ b/src/extras/index.js @@ -1,7 +1,7 @@ /** + * Additional PIXI DisplayObjects for animation, tiling and bitmap text. * @namespace PIXI.extras */ -export { default as TextureTransform } from './TextureTransform'; export { default as AnimatedSprite } from './AnimatedSprite'; export { default as TilingSprite } from './TilingSprite'; export { default as TilingSpriteRenderer } from './webgl/TilingSpriteRenderer'; diff --git a/src/extras/webgl/TilingSpriteRenderer.js b/src/extras/webgl/TilingSpriteRenderer.js index aeec172..eee3b5c 100644 --- a/src/extras/webgl/TilingSpriteRenderer.js +++ b/src/extras/webgl/TilingSpriteRenderer.js @@ -11,7 +11,7 @@ * WebGL renderer plugin for tiling sprites * * @class - * @memberof PIXI + * @memberof PIXI.extras * @extends PIXI.ObjectRenderer */ export default class TilingSpriteRenderer extends core.ObjectRenderer @@ -118,7 +118,7 @@ tempMat.invert(); if (isSimple) { - tempMat.append(uv.mapCoord); + tempMat.prepend(uv.mapCoord); } else { diff --git a/src/filters/displacement/DisplacementFilter.js b/src/filters/displacement/DisplacementFilter.js index d5355c3..b5d030c 100644 --- a/src/filters/displacement/DisplacementFilter.js +++ b/src/filters/displacement/DisplacementFilter.js @@ -35,7 +35,7 @@ this.maskSprite = sprite; this.maskMatrix = maskMatrix; - this.uniforms.mapSampler = sprite.texture; + this.uniforms.mapSampler = sprite._texture; this.uniforms.filterMatrix = maskMatrix; this.uniforms.scale = { x: 1, y: 1 }; diff --git a/src/filters/index.js b/src/filters/index.js index f3e8862..7ceb50a 100644 --- a/src/filters/index.js +++ b/src/filters/index.js @@ -1,4 +1,21 @@ /** + * This namespace contains WebGL-only display filters that can be applied + * to DisplayObjects using the {@link PIXI.DisplayObject#filters filters} property. + * @example + * // Create a new application + * const app = new PIXI.Application(); + * + * // Draw a green rectangle + * const rect = new PIXI.Graphics() + * .beginFill(0x00ff00) + * .drawRect(40, 40, 200, 200); + * + * // Add a blur filter + * rect.filters = [new PIXI.filters.BlurFilter()]; + * + * // Display rectangle + * app.stage.addChild(rect); + * document.body.appendChild(app.view); * @namespace PIXI.filters */ export { default as FXAAFilter } from './fxaa/FXAAFilter'; diff --git a/src/index.js b/src/index.js index c553427..77da490 100644 --- a/src/index.js +++ b/src/index.js @@ -20,6 +20,14 @@ import { utils } from './core'; utils.mixins.performMixins(); +/** + * Alias for {@link PIXI.loaders.shared}. + * @name loader + * @memberof PIXI + * @type {PIXI.loader.Loader} + */ +const loader = loaders.shared || null; + export { accessibility, extract, @@ -30,18 +38,8 @@ mesh, particles, prepare, + loader, }; -/** - * A premade instance of the loader that can be used to load resources. - * - * @name loader - * @memberof PIXI - * @property {PIXI.loaders.Loader} - */ -const loader = loaders && loaders.Loader ? new loaders.Loader() : null; // check is there in case user excludes loader lib - -export { loader }; - // Always export pixi globally. global.PIXI = exports; // eslint-disable-line diff --git a/src/interaction/InteractionData.js b/src/interaction/InteractionData.js index 02d53a3..449f8cc 100644 --- a/src/interaction/InteractionData.js +++ b/src/interaction/InteractionData.js @@ -30,7 +30,10 @@ /** * When passed to an event handler, this will be the original DOM Event that was captured * - * @member {Event} + * @see https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent + * @see https://developer.mozilla.org/en-US/docs/Web/API/TouchEvent + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent + * @member {MouseEvent|TouchEvent|PointerEvent} */ this.originalEvent = null; } diff --git a/src/interaction/InteractionManager.js b/src/interaction/InteractionManager.js index 8baf13a..2d75c7c 100644 --- a/src/interaction/InteractionManager.js +++ b/src/interaction/InteractionManager.js @@ -13,6 +13,14 @@ const MOUSE_POINTER_ID = 'MOUSE'; +// helpers for hitTest() - only used inside hitTest() +const hitTestEvent = { + target: null, + data: { + global: null, + }, +}; + /** * The interaction manager deals with mouse, touch and pointer events. Any DisplayObject can be interactive * if its interactive parameter is set to true @@ -245,67 +253,60 @@ this.setTargetElement(this.renderer.view, this.renderer.resolution); /** - * Fired when a pointer device button (usually a mouse button) is pressed on the display + * Fired when a pointer device button (usually a mouse left-button) is pressed on the display * object. * - * @event mousedown - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mousedown + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device secondary button (usually a mouse right-button) is pressed * on the display object. * - * @event rightdown - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightdown + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** - * Fired when a pointer device button (usually a mouse button) is released over the display + * Fired when a pointer device button (usually a mouse left-button) is released over the display * object. * - * @event mouseup - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseup + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device secondary button (usually a mouse right-button) is released * over the display object. * - * @event rightup - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightup + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** - * Fired when a pointer device button (usually a mouse button) is pressed and released on + * Fired when a pointer device button (usually a mouse left-button) is pressed and released on * the display object. * - * @event click - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#click + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device secondary button (usually a mouse right-button) is pressed * and released on the display object. * - * @event rightclick - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightclick + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** - * Fired when a pointer device button (usually a mouse button) is released outside the + * Fired when a pointer device button (usually a mouse left-button) is released outside the * display object that initially registered a * [mousedown]{@link PIXI.interaction.InteractionManager#event:mousedown}. * - * @event mouseupoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** @@ -313,146 +314,362 @@ * outside the display object that initially registered a * [rightdown]{@link PIXI.interaction.InteractionManager#event:rightdown}. * - * @event rightupoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device (usually a mouse) is moved while over the display object * - * @event mousemove - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mousemove + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device (usually a mouse) is moved onto the display object * - * @event mouseover - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseover + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device (usually a mouse) is moved off the display object * - * @event mouseout - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseout + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is pressed on the display object. * - * @event pointerdown - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerdown + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is released over the display object. * - * @event pointerup - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerup + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when the operating system cancels a pointer event * - * @event pointercancel - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointercancel + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is pressed and released on the display object. * - * @event pointertap - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointertap + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is released outside the display object that initially * registered a [pointerdown]{@link PIXI.interaction.InteractionManager#event:pointerdown}. * - * @event pointerupoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device is moved while over the display object * - * @event pointermove - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointermove + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device is moved onto the display object * - * @event pointerover - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerover + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device is moved off the display object * - * @event pointerout - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerout + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is placed on the display object. * - * @event touchstart - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchstart + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is removed from the display object. * - * @event touchend - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchend + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when the operating system cancels a touch * - * @event touchcancel - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchcancel + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is placed and removed from the display object. * - * @event tap - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#tap + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is removed outside of the display object that initially * registered a [touchstart]{@link PIXI.interaction.InteractionManager#event:touchstart}. * - * @event touchendoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchendoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is moved along the display object. * - * @event touchmove - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchmove + * @param {PIXI.interaction.InteractionData} event - Interaction data */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is pressed on the display. + * object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mousedown + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is pressed + * on the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightdown + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is released over the display + * object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseup + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is released + * over the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightup + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is pressed and released on + * the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#click + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is pressed + * and released on the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightclick + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is released outside the + * display object that initially registered a + * [mousedown]{@link PIXI.DisplayObject#event:mousedown}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is released + * outside the display object that initially registered a + * [rightdown]{@link PIXI.DisplayObject#event:rightdown}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device (usually a mouse) is moved while over the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mousemove + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device (usually a mouse) is moved onto the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseover + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device (usually a mouse) is moved off the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseout + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is pressed on the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerdown + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is released over the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerup + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when the operating system cancels a pointer event. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointercancel + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is pressed and released on the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointertap + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is released outside the display object that initially + * registered a [pointerdown]{@link PIXI.DisplayObject#event:pointerdown}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device is moved while over the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointermove + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device is moved onto the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerover + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device is moved off the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerout + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is placed on the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchstart + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is removed from the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchend + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when the operating system cancels a touch. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchcancel + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is placed and removed from the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#tap + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is removed outside of the display object that initially + * registered a [touchstart]{@link PIXI.DisplayObject#event:touchstart}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchendoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is moved along the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchmove + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + } + + /** + * Hit tests a point against the display tree, returning the first interactive object that is hit. + * + * @param {PIXI.Point} globalPoint - A point to hit test with, in global space. + * @param {PIXI.Container} [root] - The root display object to start from. If omitted, defaults + * to the last rendered root of the associated renderer. + * @return {PIXI.DisplayObject} The hit display object, if any. + */ + hitTest(globalPoint, root) + { + // clear the target for our hit test + hitTestEvent.target = null; + // assign the global point + hitTestEvent.data.global = globalPoint; + // ensure safety of the root + if (!root) + { + root = this.renderer._lastObjectRendered; + } + // run the hit test + this.processInteractive(hitTestEvent, root, null, true); + // return our found object - it'll be null if we didn't hit anything + + return hitTestEvent.target; } /** @@ -487,7 +704,7 @@ return; } - core.ticker.shared.add(this.update, this); + core.ticker.shared.add(this.update, this, core.UPDATE_PRIORITY.INTERACTION); if (window.navigator.msPointerEnabled) { @@ -804,8 +1021,6 @@ } } - let keepHitTestingAfterChildren = hitTest; - // ** FREE TIP **! If an object is not interactive or has no buttons in it // (such as a game scene!) set interactiveChildren to false for that displayObject. // This will allow pixi to completely ignore and bypass checking the displayObjects children. @@ -818,7 +1033,9 @@ const child = children[i]; // time to get recursive.. if this function will return if something is hit.. - if (this.processInteractive(interactionEvent, child, func, hitTest, interactiveParent)) + const childHit = this.processInteractive(interactionEvent, child, func, hitTest, interactiveParent); + + if (childHit) { // its a good idea to check if a child has lost its parent. // this means it has been removed whilst looping so its best @@ -827,8 +1044,6 @@ continue; } - hit = true; - // we no longer need to hit test any more objects in this container as we we // now know the parent has been hit interactiveParent = false; @@ -838,36 +1053,41 @@ // This means we no longer need to hit test anything else. We still need to run // through all objects, but we don't need to perform any hit tests. - keepHitTestingAfterChildren = false; - - if (child.interactive) + if (childHit) { - hitTest = false; + if (interactionEvent.target) + { + hitTest = false; + } + hit = true; } - - // we can break now as we have hit an object. } } } - hitTest = keepHitTestingAfterChildren; - // no point running this if the item is not interactive or does not have an interactive parent. if (interactive) { // if we are hit testing (as in we have no hit any objects yet) // We also don't need to worry about hit testing if once of the displayObjects children - // has already been hit! - if (hitTest && !hit) + // has already been hit - but only if it was interactive, otherwise we need to keep + // looking for an interactive child, just in case we hit one + if (hitTest && !interactionEvent.target) { if (displayObject.hitArea) { displayObject.worldTransform.applyInverse(point, this._tempPoint); - hit = displayObject.hitArea.contains(this._tempPoint.x, this._tempPoint.y); + if (displayObject.hitArea.contains(this._tempPoint.x, this._tempPoint.y)) + { + hit = true; + } } else if (displayObject.containsPoint) { - hit = displayObject.containsPoint(point); + if (displayObject.containsPoint(point)) + { + hit = true; + } } } @@ -878,7 +1098,10 @@ interactionEvent.target = displayObject; } - func(interactionEvent, displayObject, hit); + if (func) + { + func(interactionEvent, displayObject, !!hit); + } } } @@ -914,7 +1137,7 @@ { const event = events[i]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -998,7 +1221,7 @@ { const event = events[i]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1098,7 +1321,7 @@ const test = isRightButton ? flags.RIGHT_DOWN : flags.LEFT_DOWN; - const isDown = trackingData !== undefined && (trackingData.flags | test); + const isDown = trackingData !== undefined && (trackingData.flags & test); if (hit) { @@ -1118,11 +1341,11 @@ { if (isRightButton) { - trackingData.rightDown = hit; + trackingData.rightDown = false; } else { - trackingData.leftDown = hit; + trackingData.leftDown = false; } } } @@ -1180,7 +1403,7 @@ { const event = events[i]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1255,7 +1478,7 @@ this.setCursorMode(null); } - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1268,6 +1491,12 @@ { this.emit('mouseout', interactionEvent); } + else + { + // we can get touchleave events after touchend, so we want to make sure we don't + // introduce memory leaks + this.releaseInteractionDataForPointerId(interactionData.identifier); + } } /** @@ -1344,7 +1573,7 @@ // Only mouse and pointer can call onPointerOver, so events will always be length 1 const event = events[0]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1366,12 +1595,14 @@ * Get InteractionData for a given pointerId. Store that data as well * * @private - * @param {number} pointerId - Identifier from a pointer event + * @param {PointerEvent} event - Normalized pointer event, output from normalizeToPointerData * @return {InteractionData} - Interaction data for the given pointer identifier */ - getInteractionDataForPointerId(pointerId) + getInteractionDataForPointerId(event) { - if (pointerId === MOUSE_POINTER_ID) + const pointerId = event.pointerId; + + if (pointerId === MOUSE_POINTER_ID || event.pointerType === 'mouse') { return this.mouse; } diff --git a/src/interaction/InteractionTrackingData.js b/src/interaction/InteractionTrackingData.js index 6a1f69e..8493b63 100644 --- a/src/interaction/InteractionTrackingData.js +++ b/src/interaction/InteractionTrackingData.js @@ -35,8 +35,10 @@ } /** + * Unique pointer id of the event + * * @readonly - * @type {number} Unique pointer id of the event + * @member {number} */ get pointerId() { diff --git a/src/interaction/index.js b/src/interaction/index.js index 56c5b09..72f7d71 100644 --- a/src/interaction/index.js +++ b/src/interaction/index.js @@ -1,4 +1,8 @@ /** + * This namespace contains a renderer plugin for handling mouse, pointer, and touch events. + * + * Do not instantiate this plugin directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. * @namespace PIXI.interaction */ export { default as InteractionData } from './InteractionData'; diff --git a/src/interaction/interactiveTarget.js b/src/interaction/interactiveTarget.js index d252fba..cebac40 100644 --- a/src/interaction/interactiveTarget.js +++ b/src/interaction/interactiveTarget.js @@ -2,7 +2,7 @@ * Default property values of interactive objects * Used by {@link PIXI.interaction.InteractionManager} to automatically give all DisplayObjects these properties * - * @mixin + * @private * @name interactiveTarget * @memberof PIXI.interaction * @example @@ -14,18 +14,28 @@ * ); */ export default { + /** - * Determines if the displayObject be clicked/touched + * Enable interaction events for the DisplayObject. Touch, pointer and mouse + * events will not be emitted unless `interactive` is set to `true`. * - * @inner {boolean} + * @example + * const sprite = new PIXI.Sprite(texture); + * sprite.interactive = true; + * sprite.on('tap', (event) => { + * //handle event + * }); + * @member {boolean} + * @memberof PIXI.DisplayObject# */ interactive: false, /** * Determines if the children to the displayObject can be clicked/touched - * Setting this to false allows pixi to bypass a recursive hitTest function + * Setting this to false allows pixi to bypass a recursive `hitTest` function * - * @inner {boolean} + * @member {boolean} + * @memberof PIXI.Container# */ interactiveChildren: true, @@ -33,7 +43,12 @@ * Interaction shape. Children will be hit first, then this shape will be checked. * Setting this will cause this shape to be checked in hit tests rather than the displayObject's bounds. * - * @inner {PIXI.Rectangle|PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.RoundedRectangle} + * @example + * const sprite = new PIXI.Sprite(texture); + * sprite.interactive = true; + * sprite.hitArea = new PIXI.Rectangle(0, 0, 100, 100); + * @member {PIXI.Rectangle|PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.RoundedRectangle} + * @memberof PIXI.DisplayObject# */ hitArea: null, @@ -41,8 +56,12 @@ * If enabled, the mouse cursor use the pointer behavior when hovered over the displayObject if it is interactive * Setting this changes the 'cursor' property to `'pointer'`. * + * @example + * const sprite = new PIXI.Sprite(texture); + * sprite.interactive = true; + * sprite.buttonMode = true; * @member {boolean} - * @memberof PIXI.interaction.interactiveTarget# + * @memberof PIXI.DisplayObject# */ get buttonMode() { @@ -64,9 +83,14 @@ * This defines what cursor mode is used when the mouse cursor * is hovered over the displayObject. * + * @example + * const sprite = new PIXI.Sprite(texture); + * sprite.interactive = true; + * sprite.cursor = 'wait'; * @see https://developer.mozilla.org/en/docs/Web/CSS/cursor * - * @inner {string} + * @member {string} + * @memberof PIXI.DisplayObject# */ cursor: null, @@ -74,7 +98,7 @@ * Internal set of all active pointers, by identifier * * @member {Map} - * @memberof PIXI.interaction.interactiveTarget# + * @memberof PIXI.DisplayObject# * @private */ get trackedPointers() @@ -87,7 +111,8 @@ /** * Map of all tracked pointers, by identifier. Use trackedPointers to access. * - * @private {Map} + * @private + * @type {Map} */ _trackedPointers: undefined, }; diff --git a/src/loaders/index.js b/src/loaders/index.js index 5189793..daa1fc4 100644 --- a/src/loaders/index.js +++ b/src/loaders/index.js @@ -1,9 +1,22 @@ +import Application from '../core/Application'; +import Loader from './loader'; + /** + * This namespace contains APIs which extends the {@link https://github.com/englercj/resource-loader resource-loader} module + * for loading assets, data, and other resources dynamically. + * @example + * const loader = new PIXI.loaders.Loader(); + * loader.add('bunny', 'data/bunny.png') + * .add('spaceship', 'assets/spritesheet.json'); + * loader.load((loader, resources) => { + * // resources.bunny + * // resources.spaceship + * }); * @namespace PIXI.loaders */ -export { default as Loader } from './loader'; +export { Loader }; export { default as bitmapFontParser, parse as parseBitmapFontData } from './bitmapFontParser'; -export { default as spritesheetParser } from './spritesheetParser'; +export { default as spritesheetParser, getResourcePath } from './spritesheetParser'; export { default as textureParser } from './textureParser'; /** @@ -13,3 +26,55 @@ * @memberof PIXI.loaders */ export { Resource } from 'resource-loader'; + +/** + * A premade instance of the loader that can be used to load resources. + * @name shared + * @memberof PIXI.loaders + * @type {PIXI.loaders.Loader} + */ +const shared = new Loader(); + +shared.destroy = () => +{ + // protect destroying shared loader +}; + +export { shared }; + +// Mixin the loader construction +const AppPrototype = Application.prototype; + +AppPrototype._loader = null; + +/** + * Loader instance to help with asset loading. + * @name PIXI.Application#loader + * @type {PIXI.loaders.Loader} + */ +Object.defineProperty(AppPrototype, 'loader', { + get() + { + if (!this._loader) + { + const sharedLoader = this._options.sharedLoader; + + this._loader = sharedLoader ? shared : new Loader(); + } + + return this._loader; + }, +}); + +// Override the destroy function +// making sure to destroy the current Loader +AppPrototype._parentDestroy = AppPrototype.destroy; +AppPrototype.destroy = function destroy() +{ + if (this._loader) + { + this._loader.destroy(); + this._loader = null; + } + this._parentDestroy(); +}; diff --git a/src/loaders/loader.js b/src/loaders/loader.js index 767a411..59b8000 100644 --- a/src/loaders/loader.js +++ b/src/loaders/loader.js @@ -90,6 +90,15 @@ { Loader._pixiMiddleware.push(fn); } + + /** + * Destroy the loader, removes references. + */ + destroy() + { + this.removeAllListeners(); + this.reset(); + } } // Copy EE3 prototype (mixin) diff --git a/src/loaders/spritesheetParser.js b/src/loaders/spritesheetParser.js index e57dfb2..eda9584 100644 --- a/src/loaders/spritesheetParser.js +++ b/src/loaders/spritesheetParser.js @@ -1,12 +1,11 @@ import { Resource } from 'resource-loader'; -import path from 'path'; +import url from 'url'; import { Spritesheet } from '../core'; export default function () { return function spritesheetParser(resource, next) { - let resourcePath; const imageResourceName = `${resource.name}_image`; // skip if no data, its not json, it isn't spritesheet data, or the image resource already exists @@ -28,15 +27,7 @@ parentResource: resource, }; - // Prepend url path unless the resource image is a data url - if (resource.isDataUrl) - { - resourcePath = resource.data.meta.image; - } - else - { - resourcePath = `${path.dirname(resource.url.replace(this.baseUrl, ''))}/${resource.data.meta.image}`; - } + const resourcePath = getResourcePath(resource, this.baseUrl); // load the image for this sheet this.add(imageResourceName, resourcePath, loadOptions, function onImageLoad(res) @@ -56,3 +47,14 @@ }); }; } + +export function getResourcePath(resource, baseUrl) +{ + // Prepend url path unless the resource image is a data url + if (resource.isDataUrl) + { + return resource.data.meta.image; + } + + return url.resolve(resource.url.replace(baseUrl, ''), resource.data.meta.image); +} diff --git a/src/mesh/canvas/CanvasMeshRenderer.js b/src/mesh/canvas/CanvasMeshRenderer.js index 297205f..f7899e1 100644 --- a/src/mesh/canvas/CanvasMeshRenderer.js +++ b/src/mesh/canvas/CanvasMeshRenderer.js @@ -134,12 +134,33 @@ const textureWidth = base.width; const textureHeight = base.height; - const u0 = uvs[index0] * base.width; - const u1 = uvs[index1] * base.width; - const u2 = uvs[index2] * base.width; - const v0 = uvs[index0 + 1] * base.height; - const v1 = uvs[index1 + 1] * base.height; - const v2 = uvs[index2 + 1] * base.height; + let u0; + let u1; + let u2; + let v0; + let v1; + let v2; + + if (mesh.uploadUvTransform) + { + const ut = mesh._uvTransform.mapCoord; + + u0 = ((uvs[index0] * ut.a) + (uvs[index0 + 1] * ut.c) + ut.tx) * base.width; + u1 = ((uvs[index1] * ut.a) + (uvs[index1 + 1] * ut.c) + ut.tx) * base.width; + u2 = ((uvs[index2] * ut.a) + (uvs[index2 + 1] * ut.c) + ut.tx) * base.width; + v0 = ((uvs[index0] * ut.b) + (uvs[index0 + 1] * ut.d) + ut.ty) * base.height; + v1 = ((uvs[index1] * ut.b) + (uvs[index1 + 1] * ut.d) + ut.ty) * base.height; + v2 = ((uvs[index2] * ut.b) + (uvs[index2 + 1] * ut.d) + ut.ty) * base.height; + } + else + { + u0 = uvs[index0] * base.width; + u1 = uvs[index1] * base.width; + u2 = uvs[index2] * base.width; + v0 = uvs[index0 + 1] * base.height; + v1 = uvs[index1 + 1] * base.height; + v2 = uvs[index2 + 1] * base.height; + } let x0 = vertices[index0]; let x1 = vertices[index1]; diff --git a/src/mesh/webgl/MeshRenderer.js b/src/mesh/webgl/MeshRenderer.js index 78d24c3..ab99fcd 100644 --- a/src/mesh/webgl/MeshRenderer.js +++ b/src/mesh/webgl/MeshRenderer.js @@ -2,6 +2,8 @@ const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; +const matrixIdentity = core.Matrix.IDENTITY; + /** * WebGL renderer plugin for tiling sprites * @@ -64,6 +66,7 @@ mesh.geometry.glVertexArrayObjects[this.CONTEXT_UID].draw(mesh.drawMode, mesh.size, mesh.start, mesh.geometry.instanceCount); } + /** * draws mesh * @param {PIXI.mesh.RawMesh} mesh mesh instance diff --git a/src/mesh/webgl/mesh.vert b/src/mesh/webgl/mesh.vert index a337aef..acc096c 100644 --- a/src/mesh/webgl/mesh.vert +++ b/src/mesh/webgl/mesh.vert @@ -1,8 +1,9 @@ attribute vec2 aVertexPosition; attribute vec2 aTextureCoord; -uniform mat3 translationMatrix; uniform mat3 projectionMatrix; +uniform mat3 translationMatrix; +uniform mat3 uTransform; varying vec2 vTextureCoord; @@ -10,5 +11,5 @@ { gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - vTextureCoord = aTextureCoord; + vTextureCoord = (uTransform * vec3(aTextureCoord, 1.0)).xy; } diff --git a/src/particles/ParticleContainer.js b/src/particles/ParticleContainer.js index 67796cf..63f7791 100644 --- a/src/particles/ParticleContainer.js +++ b/src/particles/ParticleContainer.js @@ -313,10 +313,10 @@ frame.y * resolution, frame.width * resolution, frame.height * resolution, - positionX * resolution, - positionY * resolution, - finalWidth * resolution, - finalHeight * resolution + positionX * renderer.resolution, + positionY * renderer.resolution, + finalWidth * renderer.resolution, + finalHeight * renderer.resolution ); } } diff --git a/src/prepare/BasePrepare.js b/src/prepare/BasePrepare.js index 09eb5e5..e45c4dc 100644 --- a/src/prepare/BasePrepare.js +++ b/src/prepare/BasePrepare.js @@ -18,6 +18,18 @@ * basic queuing functionality and is extended by {@link PIXI.prepare.WebGLPrepare} and {@link PIXI.prepare.CanvasPrepare} * to provide preparation capabilities specific to their respective renderers. * + * @example + * // Create a sprite + * const sprite = new PIXI.Sprite.fromImage('something.png'); + * + * // Load object into GPU + * app.renderer.plugins.prepare.upload(sprite, () => { + * + * //Texture(s) has been uploaded to GPU + * app.stage.addChild(sprite); + * + * }) + * * @abstract * @class * @memberof PIXI.prepare @@ -100,8 +112,16 @@ this.prepareItems(); }; - this.register(findText, drawText); - this.register(findTextStyle, calculateTextStyle); + // hooks to find the correct texture + this.registerFindHook(findText); + this.registerFindHook(findTextStyle); + this.registerFindHook(findMultipleBaseTextures); + this.registerFindHook(findBaseTexture); + this.registerFindHook(findTexture); + + // upload hooks + this.registerUploadHook(drawText); + this.registerUploadHook(calculateTextStyle); } /** @@ -138,7 +158,7 @@ if (!this.ticking) { this.ticking = true; - SharedTicker.addOnce(this.tick, this); + SharedTicker.addOnce(this.tick, this, core.UPDATE_PRIORITY.UTILITY); } } else if (done) @@ -172,13 +192,16 @@ const item = this.queue[0]; let uploaded = false; - for (let i = 0, len = this.uploadHooks.length; i < len; i++) + if (item && !item._destroyed) { - if (this.uploadHooks[i](this.uploadHookHelper, item)) + for (let i = 0, len = this.uploadHooks.length; i < len; i++) { - this.queue.shift(); - uploaded = true; - break; + if (this.uploadHooks[i](this.uploadHookHelper, item)) + { + this.queue.shift(); + uploaded = true; + break; + } } } @@ -205,26 +228,36 @@ else { // if we are not finished, on the next rAF do this again - SharedTicker.addOnce(this.tick, this); + SharedTicker.addOnce(this.tick, this, core.UPDATE_PRIORITY.UTILITY); } } /** - * Adds hooks for finding and uploading items. + * Adds hooks for finding items. * - * @param {Function} [addHook] - Function call that takes two parameters: `item:*, queue:Array` - function must return `true` if it was able to add item to the queue. - * @param {Function} [uploadHook] - Function call that takes two parameters: `prepare:CanvasPrepare, item:*` and - * function must return `true` if it was able to handle upload of item. - * @return {PIXI.CanvasPrepare} Instance of plugin for chaining. + * @param {Function} addHook - Function call that takes two parameters: `item:*, queue:Array` + * function must return `true` if it was able to add item to the queue. + * @return {PIXI.BasePrepare} Instance of plugin for chaining. */ - register(addHook, uploadHook) + registerFindHook(addHook) { if (addHook) { this.addHooks.push(addHook); } + return this; + } + + /** + * Adds hooks for uploading items. + * + * @param {Function} uploadHook - Function call that takes two parameters: `prepare:CanvasPrepare, item:*` and + * function must return `true` if it was able to handle upload of item. + * @return {PIXI.BasePrepare} Instance of plugin for chaining. + */ + registerUploadHook(uploadHook) + { if (uploadHook) { this.uploadHooks.push(uploadHook); @@ -287,6 +320,88 @@ } /** + * Built-in hook to find multiple textures from objects like AnimatedSprites. + * + * @private + * @param {PIXI.DisplayObject} item - Display object to check + * @param {Array<*>} queue - Collection of items to upload + * @return {boolean} if a PIXI.Texture object was found. + */ +function findMultipleBaseTextures(item, queue) +{ + let result = false; + + // Objects with mutliple textures + if (item && item._textures && item._textures.length) + { + for (let i = 0; i < item._textures.length; i++) + { + if (item._textures[i] instanceof core.Texture) + { + const baseTexture = item._textures[i].baseTexture; + + if (queue.indexOf(baseTexture) === -1) + { + queue.push(baseTexture); + result = true; + } + } + } + } + + return result; +} + +/** + * Built-in hook to find BaseTextures from Sprites. + * + * @private + * @param {PIXI.DisplayObject} item - Display object to check + * @param {Array<*>} queue - Collection of items to upload + * @return {boolean} if a PIXI.Texture object was found. + */ +function findBaseTexture(item, queue) +{ + // Objects with textures, like Sprites/Text + if (item instanceof core.BaseTexture) + { + if (queue.indexOf(item) === -1) + { + queue.push(item); + } + + return true; + } + + return false; +} + +/** + * Built-in hook to find textures from objects. + * + * @private + * @param {PIXI.DisplayObject} item - Display object to check + * @param {Array<*>} queue - Collection of items to upload + * @return {boolean} if a PIXI.Texture object was found. + */ +function findTexture(item, queue) +{ + if (item._texture && item._texture instanceof core.Texture) + { + const texture = item._texture.baseTexture; + + if (queue.indexOf(texture) === -1) + { + queue.push(texture); + } + + return true; + } + + return false; +} + +/** * Built-in hook to draw PIXI.Text to its texture. * * @private diff --git a/src/prepare/canvas/CanvasPrepare.js b/src/prepare/canvas/CanvasPrepare.js index 0224bb5..09c207a 100644 --- a/src/prepare/canvas/CanvasPrepare.js +++ b/src/prepare/canvas/CanvasPrepare.js @@ -43,7 +43,7 @@ this.ctx = this.canvas.getContext('2d'); // Add textures to upload - this.register(findBaseTextures, uploadBaseTextures); + this.registerUploadHook(uploadBaseTextures); } /** @@ -89,39 +89,4 @@ return false; } -/** - * Built-in hook to find textures from Sprites. - * - * @private - * @param {PIXI.DisplayObject} item -Display object to check - * @param {Array<*>} queue - Collection of items to upload - * @return {boolean} if a PIXI.Texture object was found. - */ -function findBaseTextures(item, queue) -{ - // Objects with textures, like Sprites/Text - if (item instanceof core.BaseTexture) - { - if (queue.indexOf(item) === -1) - { - queue.push(item); - } - - return true; - } - else if (item._texture && item._texture instanceof core.Texture) - { - const texture = item._texture.baseTexture; - - if (queue.indexOf(texture) === -1) - { - queue.push(texture); - } - - return true; - } - - return false; -} - core.CanvasRenderer.registerPlugin('prepare', CanvasPrepare); diff --git a/src/prepare/index.js b/src/prepare/index.js index f559c45..e7491ec 100644 --- a/src/prepare/index.js +++ b/src/prepare/index.js @@ -1,4 +1,29 @@ /** + * The prepare namespace provides renderer-specific plugins for pre-rendering DisplayObjects. These plugins are useful for + * asynchronously preparing assets, textures, graphics waiting to be displayed. + * + * Do not instantiate these plugins directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. + * @example + * // Create a new application + * const app = new PIXI.Application(); + * document.body.appendChild(app.view); + * + * // Don't start rendering right away + * app.stop(); + * + * // create a display object + * const rect = new PIXI.Graphics() + * .beginFill(0x00ff00) + * .drawRect(40, 40, 200, 200); + * + * // Add to the stage + * app.stage.addChild(rect); + * + * // Don't start rendering until the graphic is uploaded to the GPU + * app.renderer.plugins.prepare.upload(app.stage, () => { + * app.start(); + * }); * @namespace PIXI.prepare */ export { default as webgl } from './webgl/WebGLPrepare'; diff --git a/src/prepare/webgl/WebGLPrepare.js b/src/prepare/webgl/WebGLPrepare.js index eb7023f..9df3c3f 100644 --- a/src/prepare/webgl/WebGLPrepare.js +++ b/src/prepare/webgl/WebGLPrepare.js @@ -22,12 +22,11 @@ this.uploadHookHelper = this.renderer; // Add textures and graphics to upload - this.register(findBaseTextures, uploadBaseTextures) - .register(findGraphics, uploadGraphics); + this.registerFindHook(findGraphics); + this.registerUploadHook(uploadBaseTextures); + this.registerUploadHook(uploadGraphics); } - } - /** * Built-in hook to upload PIXI.Texture objects to the GPU. * @@ -80,41 +79,6 @@ } /** - * Built-in hook to find textures from Sprites. - * - * @private - * @param {PIXI.DisplayObject} item - Display object to check - * @param {Array<*>} queue - Collection of items to upload - * @return {boolean} if a PIXI.Texture object was found. - */ -function findBaseTextures(item, queue) -{ - // Objects with textures, like Sprites/Text - if (item instanceof core.BaseTexture) - { - if (queue.indexOf(item) === -1) - { - queue.push(item); - } - - return true; - } - else if (item._texture && item._texture instanceof core.Texture) - { - const texture = item._texture.baseTexture; - - if (queue.indexOf(texture) === -1) - { - queue.push(texture); - } - - return true; - } - - return false; -} - -/** * Built-in hook to find graphics. * * @private diff --git a/test/.eslintrc.json b/test/.eslintrc.json index e03220d..ac12110 100644 --- a/test/.eslintrc.json +++ b/test/.eslintrc.json @@ -12,7 +12,8 @@ "sinon": false, "expect": false, "assert": false, - "PIXI": false + "PIXI": false, + "PointerEvent": true }, "rules": { "func-names": 0, diff --git a/test/core/BaseTexture.js b/test/core/BaseTexture.js index 8b93478..bb65068 100644 --- a/test/core/BaseTexture.js +++ b/test/core/BaseTexture.js @@ -14,4 +14,28 @@ expect(baseTexture.imageType).to.be.equals('png'); }); }); + + it('should remove Canvas BaseTexture from cache on destroy', function () + { + const canvas = document.createElement('canvas'); + const texture = PIXI.BaseTexture.fromCanvas(canvas); + const _pixiId = canvas._pixiId; + + expect(PIXI.utils.BaseTextureCache[_pixiId]).to.equal(texture); + texture.destroy(); + expect(PIXI.utils.BaseTextureCache[_pixiId]).to.equal(undefined); + }); + + it('should remove Image BaseTexture from cache on destroy', function () + { + const URL = 'foo.png'; + const NAME = 'bar'; + const image = new Image(); + + const texture = PIXI.Texture.fromLoader(image, URL, NAME); + + expect(PIXI.utils.BaseTextureCache[NAME]).to.equal(texture.baseTexture); + texture.destroy(true); + expect(PIXI.utils.BaseTextureCache[NAME]).to.equal(undefined); + }); }); diff --git a/test/core/Circle.js b/test/core/Circle.js index 183f98f..f621992 100644 --- a/test/core/Circle.js +++ b/test/core/Circle.js @@ -53,6 +53,10 @@ expect(circ1.contains(10, 16)).to.be.false; expect(circ1.contains(11, 15)).to.be.false; expect(circ1.contains(0, 0)).to.be.false; + + const circ2 = new PIXI.Circle(10, 10, 0); + + expect(circ2.contains(10, 10)).to.be.false; }); it('should return framing rectangle', function () diff --git a/test/core/Container.js b/test/core/Container.js index d11be33..cefde54 100644 --- a/test/core/Container.js +++ b/test/core/Container.js @@ -2,30 +2,36 @@ function testAddChild(fn) { - fn(function (container, obj) + return function () { - container.addChild(obj); - }); - fn(function (container, obj) - { - container.addChildAt(obj); - }); + fn(function (container, obj) + { + container.addChild(obj); + }); + fn(function (container, obj) + { + container.addChildAt(obj); + }); + }; } function testRemoveChild(fn) { - fn(function (container, obj) + return function () { - container.removeChild(obj); - }); - fn(function (container, obj) - { - container.removeChildAt(container.children.indexOf(obj)); - }); - fn(function (container, obj) - { - container.removeChildren(container.children.indexOf(obj), container.children.indexOf(obj) + 1); - }); + fn(function (container, obj) + { + container.removeChild(obj); + }); + fn(function (container, obj) + { + container.removeChildAt(container.children.indexOf(obj)); + }); + fn(function (container, obj) + { + container.removeChildren(container.children.indexOf(obj), container.children.indexOf(obj) + 1); + }); + }; } describe('PIXI.Container', function () diff --git a/test/core/Ellipse.js b/test/core/Ellipse.js index f2e8234..3f47442 100644 --- a/test/core/Ellipse.js +++ b/test/core/Ellipse.js @@ -56,6 +56,10 @@ expect(ellipse1.contains(10, 16)).to.be.false; expect(ellipse1.contains(11, 15)).to.be.false; expect(ellipse1.contains(0, 0)).to.be.false; + + const ellipse2 = new PIXI.Ellipse(10, 10, 0, 0); + + expect(ellipse2.contains(10, 10)).to.be.false; }); it('should return framing rectangle', function () diff --git a/test/core/Matrix.js b/test/core/Matrix.js index 9d2694b..76bd57c 100644 --- a/test/core/Matrix.js +++ b/test/core/Matrix.js @@ -107,10 +107,60 @@ expect(m1.ty).to.equal(m2.ty); }); + it('should prepend matrix', function () + { + const m1 = new PIXI.Matrix(); + const m2 = new PIXI.Matrix(); + + m2.set(2, 3, 4, 5, 100, 200); + m1.prepend(m2); + + expect(m1.a).to.equal(m2.a); + expect(m1.b).to.equal(m2.b); + expect(m1.c).to.equal(m2.c); + expect(m1.d).to.equal(m2.d); + expect(m1.tx).to.equal(m2.tx); + expect(m1.ty).to.equal(m2.ty); + + const m3 = new PIXI.Matrix(); + const m4 = new PIXI.Matrix(); + + m3.prepend(m4); + + expect(m3.a).to.equal(m4.a); + expect(m3.b).to.equal(m4.b); + expect(m3.c).to.equal(m4.c); + expect(m3.d).to.equal(m4.d); + expect(m3.tx).to.equal(m4.tx); + expect(m3.ty).to.equal(m4.ty); + }); + it('should get IDENTITY and TEMP_MATRIX', function () { expect(PIXI.Matrix.IDENTITY instanceof PIXI.Matrix).to.be.true; expect(PIXI.Matrix.TEMP_MATRIX instanceof PIXI.Matrix).to.be.true; }); -}); + it('should reset matrix to default when identity() is called', function () + { + const matrix = new PIXI.Matrix(); + + matrix.set(2, 3, 4, 5, 100, 200); + + expect(matrix.a).to.equal(2); + expect(matrix.b).to.equal(3); + expect(matrix.c).to.equal(4); + expect(matrix.d).to.equal(5); + expect(matrix.tx).to.equal(100); + expect(matrix.ty).to.equal(200); + + matrix.identity(); + + expect(matrix.a).to.equal(1); + expect(matrix.b).to.equal(0); + expect(matrix.c).to.equal(0); + expect(matrix.d).to.equal(1); + expect(matrix.tx).to.equal(0); + expect(matrix.ty).to.equal(0); + }); +}); diff --git a/test/core/Spritesheet.js b/test/core/Spritesheet.js index 283adf5..7601a0c 100644 --- a/test/core/Spritesheet.js +++ b/test/core/Spritesheet.js @@ -18,6 +18,7 @@ expect(textures[id]).to.be.an.instanceof(PIXI.Texture); expect(textures[id].width).to.equal(spritesheet.data.frames[id].frame.w / spritesheet.resolution); expect(textures[id].height).to.equal(spritesheet.data.frames[id].frame.h / spritesheet.resolution); + expect(textures[id].textureCacheId).to.equal(id); spritesheet.destroy(true); expect(spritesheet.textures).to.be.null; expect(spritesheet.baseTexture).to.be.null; diff --git a/.travis.yml b/.travis.yml index 3df8b22..14de334 100644 --- a/.travis.yml +++ b/.travis.yml @@ -110,3 +110,43 @@ on: all_branches: true condition: $TRAVIS_TAG + # Deploy config for latest release + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: dist + upload-dir: "release" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: docs + upload-dir: "release/docs" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: coverage + upload-dir: "release/coverage" + on: + all_branches: true + condition: $TRAVIS_TAG \ No newline at end of file diff --git a/README.md b/README.md index d26fadd..3b85f85 100644 --- a/README.md +++ b/README.md @@ -36,22 +36,14 @@ - Wiki: Other misc tutorials and resources are [on the Wiki](https://github.com/pixijs/pixi.js/wiki/Resources). ### Community ### -- Forums: Check out the [forum] (http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow] (http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. +- Forums: Check out the [forum](http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow](http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. - Inspiration: Check out the [gallery](http://www.pixijs.com/gallery) to see some of the amazing things people have created! - Chat: You can join us on [Gitter](https://gitter.im/pixijs/pixi.js) To chat about Pixi. We also now have a Slack channel. If you would like to join it please Send me an email (mat@goodboydigital.com) and I will invite you in. ### Setup ### -It's easy to get started with Pixi.js! Simply grab the pre-built versions from here: - -Release Branch - Nice and stable Pixi.js -- Unminified: [http://pixijs.download/release/pixi.js] -- Minified: [http://pixijs.download/release/pixi.min.js] - -Develop Branch - The bleeding edge version of Pixi.js -- Unminified: [http://pixijs.download/dev/pixi.js] -- Minified: [http://pixijs.download/dev/pixi.min.js] +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. @@ -70,10 +62,10 @@ #### CDN Install (via cdnjs) ```html - + ``` -_Note: `4.2.2` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ +_Note: `4.4.3` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ ### Demos ### @@ -130,7 +122,7 @@ // and the root stage PIXI.Container. var app = new PIXI.Application(); -// The application will create a canvas element for you that you +// The application will create a canvas element for you that you // can then insert into the DOM. document.body.appendChild(app.view); @@ -150,7 +142,7 @@ // Add the bunny to the scene we are building. app.stage.addChild(bunny); - + // Listen for frame updates app.ticker.add(function() { // each frame we spin the bunny around a bit diff --git a/bower.json b/bower.json deleted file mode 100644 index 41b668c..0000000 --- a/bower.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "pixi.js", - "main": "dist/pixi.min.js", - "ignore": [ - "**/.*", - "docs", - "node_modules", - "bower_components", - "test", - "scripts", - "bower.json", - "inch.json" - ], - "dependencies": { - }, - "devDependencies": { - } -} diff --git a/package.json b/package.json index d35cbb2..308469f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.3.5", + "version": "4.4.4", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ @@ -40,11 +40,11 @@ "lib": "babel src --out-dir lib -s", "predocs": "rimraf docs/**", "docs": "jsdoc -c scripts/jsdoc.conf.json -R README.md", - "publish:patch": "npm version patch --no-git-tag-version && npm publish", - "publish:minor": "npm version minor --no-git-tag-version && npm publish", - "publish:major": "npm version major --no-git-tag-version && npm publish", + "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", - "postpublish": "node scripts/release.js" + "postpublish": "git push && git push --tags" }, "files": [ "dist/", @@ -72,7 +72,6 @@ "electron": "^1.4.15", "eslint": "^3.5.0", "floss": "^2.0.1", - "gh-pages": "^0.11.0", "jaguarjs-jsdoc": "^1.0.1", "js-md5": "^0.4.1", "jsdoc": "^3.4.2", diff --git a/scripts/jsdoc.conf.json b/scripts/jsdoc.conf.json index 553e61c..3f52180 100644 --- a/scripts/jsdoc.conf.json +++ b/scripts/jsdoc.conf.json @@ -43,7 +43,7 @@ }, "markdown" : { "parser" : "gfm", - "hardwrap" : true + "hardwrap" : false }, "opts": { "encoding" : "utf8", diff --git a/scripts/release.js b/scripts/release.js deleted file mode 100644 index 156620c..0000000 --- a/scripts/release.js +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env node -'use strict'; - -// Publish script to push releases of the bin files -// the normally are gitignored -const ghpages = require('gh-pages'); -const path = require('path'); -const packageInfo = require(path.join(__dirname, '..', 'package.json')); - -const options = { - src: [ - 'dist/**/*', - 'lib/**/*', - 'src/**/*', - 'scripts/*', - 'scripts/renders/*', - 'test/**/*', - '*.json', - '*.md', - 'LICENSE', - '.eslintrc', - '.editorconfig', - '.travis.yml', - '.babelrc', - ], - dotfiles: true, - branch: 'release', - message: packageInfo.version, - logger: console.log.bind(console), -}; - -ghpages.publish(process.cwd(), options, (err) => -{ - if (err) - { - console.log(err); - process.exit(1); - - return; - } - - process.exit(0); -}); diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index f16bcb1..220427a 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -21,7 +21,7 @@ const DIV_HOOK_ZINDEX = 2; /** - * The Accessibility manager reacreates the ability to tab and and have content read by screen + * The Accessibility manager recreates the ability to tab and and have content read by screen * readers. This is very important as it can possibly help people with disabilities access pixi * content. * diff --git a/src/accessibility/index.js b/src/accessibility/index.js index 568cfb1..0ae108f 100644 --- a/src/accessibility/index.js +++ b/src/accessibility/index.js @@ -1,4 +1,9 @@ /** + * This namespace contains a renderer plugin for interaction accessibility for end-users + * with physical impairments which require screen-renders, keyboard navigation, etc. + * + * Do not instantiate this plugin directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. * @namespace PIXI.accessibility */ export { default as accessibleTarget } from './accessibleTarget'; diff --git a/src/core/Application.js b/src/core/Application.js index 5340578..2fcc729 100644 --- a/src/core/Application.js +++ b/src/core/Application.js @@ -1,6 +1,8 @@ import { autoDetectRenderer } from './autoDetectRenderer'; import Container from './display/Container'; import { shared, Ticker } from './ticker'; +import settings from './settings'; +import { UPDATE_PRIORITY } from './const'; /** * Convenience class to create a new PIXI application. @@ -22,28 +24,52 @@ */ export default class Application { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experience unexplained flickering try setting this to true. - * @param {boolean} [useSharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedLoader=false] - `true` to use PIXI.loaders.shared, `false` to create new Loader. */ - constructor(width, height, options, noWebGL, useSharedTicker = false) + constructor(options, arg2, arg3, arg4, arg5) { + // Support for constructor(width, height, options, noWebGL, useSharedTicker) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + forceCanvas: !!arg4, + sharedTicker: !!arg5, + }, arg3); + } + + /** + * The default options, so we mixin functionality later. + * @member {object} + * @protected + */ + this._options = options = Object.assign({ + sharedTicker: false, + forceCanvas: false, + sharedLoader: false, + }, options); + /** * WebGL renderer if available, otherwise CanvasRenderer * @member {PIXI.WebGLRenderer|PIXI.CanvasRenderer} */ - this.renderer = autoDetectRenderer(width, height, options, noWebGL); + this.renderer = autoDetectRenderer(options); /** * The root display container that's rendered. @@ -63,7 +89,7 @@ * @member {PIXI.ticker.Ticker} * @default PIXI.ticker.shared */ - this.ticker = useSharedTicker ? shared : new Ticker(); + this.ticker = options.sharedTicker ? shared : new Ticker(); // Start the rendering this.start(); @@ -78,7 +104,7 @@ this._ticker = ticker; if (ticker) { - ticker.add(this.render, this); + ticker.add(this.render, this, UPDATE_PRIORITY.LOW); } } get ticker() // eslint-disable-line require-jsdoc @@ -136,13 +162,18 @@ */ destroy(removeView) { - this.stop(); + const oldTicker = this._ticker; + this.ticker = null; + oldTicker.destroy(); + this.stage.destroy(); this.stage = null; this.renderer.destroy(removeView); this.renderer = null; + + this._options = null; } } diff --git a/src/core/autoDetectRenderer.js b/src/core/autoDetectRenderer.js index 8960425..9ec0364 100644 --- a/src/core/autoDetectRenderer.js +++ b/src/core/autoDetectRenderer.js @@ -2,6 +2,7 @@ import CanvasRenderer from './renderers/canvas/CanvasRenderer'; import WebGLRenderer from './renderers/webgl/WebGLRenderer'; +// eslint-disable-next-line valid-jsdoc /** * This helper function will automatically detect which renderer you should be using. * WebGL is the preferred renderer as it is a lot faster. If webGL is not supported by @@ -9,24 +10,32 @@ * * @memberof PIXI * @function autoDetectRenderer - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @return {PIXI.WebGLRenderer|PIXI.CanvasRenderer} Returns WebGL renderer if available, otherwise CanvasRenderer */ -export function autoDetectRenderer(width = 800, height = 600, options, noWebGL) +export function autoDetectRenderer(options, arg1, arg2, arg3) { - if (!noWebGL && utils.isWebGLSupported()) + // Backward-compatible support for noWebGL option + let forceCanvas = options && options.forceCanvas; + + if (arg3 !== undefined) { - return new WebGLRenderer(width, height, options); + forceCanvas = arg3; } - return new CanvasRenderer(width, height, options); + if (!forceCanvas && utils.isWebGLSupported()) + { + return new WebGLRenderer(options, arg1, arg2); + } + + return new CanvasRenderer(options, arg1, arg2); } diff --git a/src/core/const.js b/src/core/const.js index b9e99ae..2c68dee 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -341,3 +341,27 @@ LINEAR_VERTICAL: 0, LINEAR_HORIZONTAL: 1, }; + +/** + * Represents the update priorities used by internal PIXI classes when registered with + * the {@link PIXI.ticker.Ticker} object. Higher priority items are updated first and lower + * priority items, such as render, should go later. + * + * @static + * @constant + * @name UPDATE_PRIORITY + * @memberof PIXI + * @type {object} + * @property {number} INTERACTION=50 Highest priority, used for {@link PIXI.interaction.InteractionManager} + * @property {number} HIGH=25 High priority updating, {@link PIXI.VideoBaseTexture} and {@link PIXI.extras.AnimatedSprite} + * @property {number} NORMAL=0 Default priority for ticker events, see {@link PIXI.ticker.Ticker#add}. + * @property {number} LOW=-25 Low priority used for {@link PIXI.Application} rendering. + * @property {number} UTILITY=-50 Lowest priority used for {@link PIXI.prepare.BasePrepare} utility. + */ +export const UPDATE_PRIORITY = { + INTERACTION: 50, + HIGH: 25, + NORMAL: 0, + LOW: -25, + UTILITY: -50, +}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 4e6c77d..decb42a 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -13,7 +13,6 @@ * * @class * @extends EventEmitter - * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ export default class DisplayObject extends EventEmitter @@ -113,6 +112,29 @@ * @private */ this._mask = null; + + /** + * If the object has been destroyed via destroy(). If true, it should not be used. + * + * @member {boolean} + * @private + * @readonly + */ + this._destroyed = false; + + /** + * Fired when this DisplayObject is added to a Container. + * + * @event PIXI.DisplayObject#added + * @param {PIXI.Container} container - The container added to. + */ + + /** + * Fired when this DisplayObject is removed from a Container. + * + * @event PIXI.DisplayObject#removed + * @param {PIXI.Container} container - The container removed from. + */ } /** @@ -403,6 +425,8 @@ this.interactive = false; this.interactiveChildren = false; + + this._destroyed = true; } /** diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index aafdfe2..f787c4a 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -792,8 +792,8 @@ sprite.worldAlpha = this.worldAlpha * sprite.alpha; sprite.blendMode = this.blendMode; - sprite.texture._frame.width = rect.width; - sprite.texture._frame.height = rect.height; + sprite._texture._frame.width = rect.width; + sprite._texture._frame.height = rect.height; sprite.transform.worldTransform = this.transform.worldTransform; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index a24c775..b9f192d 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -19,11 +19,12 @@ */ export default class SystemRenderer extends EventEmitter { + // eslint-disable-next-line valid-jsdoc /** * @param {string} system - The name of the system this renderer is for. - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -37,27 +38,31 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(system, screenWidth, screenHeight, options) + constructor(system, options, arg2, arg3) { super(); sayHello(system); - // prepare options - if (options) + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') { - for (const i in settings.RENDER_OPTIONS) - { - if (typeof options[i] === 'undefined') - { - options[i] = settings.RENDER_OPTIONS[i]; - } - } + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); } - else - { - options = settings.RENDER_OPTIONS; - } + + // Add the default render options + options = Object.assign({}, settings.RENDER_OPTIONS, options); + + /** + * The supplied constructor options. + * + * @member {Object} + * @readOnly + */ + this.options = options; /** * The type of the renderer. @@ -75,7 +80,7 @@ * * @member {PIXI.Rectangle} */ - this.screen = new Rectangle(0, 0, screenWidth || 800, screenHeight || 600); + this.screen = new Rectangle(0, 0, options.width, options.height); /** * The canvas element that everything is drawn to @@ -279,6 +284,8 @@ this.blendModes = null; + this.options = null; + this.preserveDrawingBuffer = false; this.clearBeforeRender = false; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index e84e49c..691caf0 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -17,10 +17,11 @@ */ export default class CanvasRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -34,9 +35,9 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('Canvas', screenWidth, screenHeight, options); + super('Canvas', options, arg2, arg3); this.type = RENDERER_TYPE.CANVAS; @@ -96,7 +97,19 @@ this.context = null; this.renderingToScreen = false; - this.resize(screenWidth, screenHeight); + this.resize(this.options.width, this.options.height); + + /** + * Fired after rendering finishes. + * + * @event PIXI.CanvasRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.CanvasRenderer#prerender + */ } /** @@ -299,4 +312,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.CanvasRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.CanvasExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.CanvasPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.CanvasRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index e1cf7cd..f6fe123 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -31,11 +31,12 @@ */ export default class WebGLRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** * - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -55,9 +56,9 @@ * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('WebGL', screenWidth, screenHeight, options); + super('WebGL', options, arg2, arg3); /** * The type of this renderer as a standardised const @@ -89,12 +90,6 @@ }; - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ this._backgroundColorRgba[3] = this.transparent ? 0 : 1; this.globalUniforms = new UniformGroup({ @@ -117,6 +112,13 @@ this.initPlugins(); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ if(options.context) { this.context.initFromContext(options.context); @@ -169,6 +171,25 @@ } return this; + + /** + * Fired after rendering finishes. + * + * @event PIXI.WebGLRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.WebGLRenderer#prerender + */ + + /** + * Fired when the WebGL context is set. + * + * @event PIXI.WebGLRenderer#context + * @param {WebGLRenderingContext} gl - WebGL context. + */ } /** @@ -295,4 +316,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.WebGLRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.WebGLRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(WebGLRenderer); diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index 4eb6128..fa3d883 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -47,6 +47,14 @@ * @member {boolean} */ this.enabled = true; + + /** + * If enabled, pixi will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; } /** diff --git a/src/core/renderers/webgl/systems/ContextSystem.js b/src/core/renderers/webgl/systems/ContextSystem.js index ff958c7..8df85ea 100644 --- a/src/core/renderers/webgl/systems/ContextSystem.js +++ b/src/core/renderers/webgl/systems/ContextSystem.js @@ -75,7 +75,8 @@ */ createContext(canvas, options) { - var gl = canvas.getContext('webgl', options) || + var gl = //canvas.getContext('webgl2', options) || + canvas.getContext('webgl', options) || canvas.getContext('experimental-webgl', options); if (!gl) diff --git a/src/core/renderers/webgl/systems/FilterSystem.js b/src/core/renderers/webgl/systems/FilterSystem.js index f47335d..37e28d2 100644 --- a/src/core/renderers/webgl/systems/FilterSystem.js +++ b/src/core/renderers/webgl/systems/FilterSystem.js @@ -111,7 +111,7 @@ // TODO we should fit the rect around the transform.. } - else + else if (filters[0].autoFit) { sourceFrame.fit(filterData.stack[0].destinationFrame); } diff --git a/src/core/renderers/webgl/systems/shader/shader/mapType.js b/src/core/renderers/webgl/systems/shader/shader/mapType.js index 581dace..917538a 100644 --- a/src/core/renderers/webgl/systems/shader/shader/mapType.js +++ b/src/core/renderers/webgl/systems/shader/shader/mapType.js @@ -15,7 +15,7 @@ } } - return GL_TABLE[type]; + return GL_TABLE[type]; }; var GL_TABLE = null; diff --git a/src/core/settings.js b/src/core/settings.js index 18c5877..38ef45c 100644 --- a/src/core/settings.js +++ b/src/core/settings.js @@ -2,6 +2,15 @@ import canUploadSameBuffer from './utils/canUploadSameBuffer'; /** + * User's customizable globals for overriding the default PIXI settings, such + * as a renderer's default resolution, framerate, float percision, etc. + * @example + * // Use the native window resolution as the default resolution + * // will support high-density displays when rendering + * PIXI.settings.RESOLUTION = window.devicePixelRatio. + * + * // Disable interpolation when scaling, will make texture be pixelated + * PIXI.settings.SCALE_MODE = PIXI.SCALE_MODES.NEAREST; * @namespace PIXI.settings */ export default { @@ -101,6 +110,9 @@ * @property {boolean} clearBeforeRender=true * @property {boolean} preserveDrawingBuffer=false * @property {boolean} roundPixels=false + * @property {number} width=800 + * @property {number} height=600 + * @property {boolean} legacy=false */ RENDER_OPTIONS: { view: null, @@ -112,6 +124,9 @@ clearBeforeRender: true, preserveDrawingBuffer: false, roundPixels: false, + width: 800, + height: 600, + legacy: false, }, /** diff --git a/src/core/shader/Program.js b/src/core/shader/Program.js index c0b538b..07b4398 100644 --- a/src/core/shader/Program.js +++ b/src/core/shader/Program.js @@ -1,6 +1,6 @@ import extractUniformsFromSrc from './extractUniformsFromSrc'; import generateUniformsSync from './generateUniformsSync'; -import glCore from 'pixi-gl-core'; +import shaderUtils from '../renderers/webgl/systems/shader/shader'; import { ProgramCache } from '../utils'; import getTestContext from '../utils/getTestContext'; @@ -64,14 +64,17 @@ } else { - vertexSrc = glCore.shader.setPrecision(vertexSrc, 'mediump'); - fragmentSrc = glCore.shader.setPrecision(fragmentSrc, 'mediump'); + vertexSrc = shaderUtils.setPrecision(vertexSrc, 'mediump'); + fragmentSrc = shaderUtils.setPrecision(fragmentSrc, 'mediump'); - const program = glCore.shader.compileProgram(gl, vertexSrc, fragmentSrc); + const program = shaderUtils.compileProgram(gl, vertexSrc, fragmentSrc); + console.log("<<<<<<>>><<"); this.attributeData = this.getAttributeData(program, gl); this.uniformData = this.getUniformData(program, gl); + console.log(this.uniformData); + //gl.deleteProgram(program); } } @@ -95,13 +98,13 @@ for (let i = 0; i < totalAttributes; i++) { const attribData = gl.getActiveAttrib(program, i); - const type = glCore.shader.mapType(gl, attribData.type); + const type = shaderUtils.mapType(gl, attribData.type); /*eslint-disable */ const data = { type: type, name: attribData.name, - size: glCore.shader.mapSize(type), + size: shaderUtils.mapSize(type), location: 0, }; /*eslint-enable */ @@ -137,13 +140,14 @@ // TODO expose this as a prop? // const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); - const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); + //const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); for (let i = 0; i < totalUniforms; i++) { const uniformData = gl.getActiveUniform(program, i); const name = uniformData.name.replace(/\[.*?\]/, ''); - const type = glCore.shader.mapType(gl, uniformData.type); + const type = shaderUtils.mapType(gl, uniformData.type); + console.log('mapping ' + uniformData.type + ' to ' + type); // if (!name.match(maskRegex)) { @@ -151,7 +155,7 @@ uniforms[name] = { type: type, size: uniformData.size, - value: glCore.shader.defaultValue(type, uniformData.size), + value: shaderUtils.defaultValue(type, uniformData.size), }; /*eslint-enable */ } diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index e387e99..b57d095 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -147,12 +147,12 @@ // so if _width is 0 then width was not set.. if (this._width) { - this.scale.x = sign(this.scale.x) * this._width / this.texture.orig.width; + this.scale.x = sign(this.scale.x) * this._width / this._texture.orig.width; } if (this._height) { - this.scale.y = sign(this.scale.y) * this._height / this.texture.orig.height; + this.scale.y = sign(this.scale.y) * this._height / this._texture.orig.height; } } diff --git a/src/core/sprites/canvas/CanvasTinter.js b/src/core/sprites/canvas/CanvasTinter.js index 27e5e0c..fd9de5d 100644 --- a/src/core/sprites/canvas/CanvasTinter.js +++ b/src/core/sprites/canvas/CanvasTinter.js @@ -18,7 +18,7 @@ */ getTintedTexture: (sprite, color) => { - const texture = sprite.texture; + const texture = sprite._texture; color = CanvasTinter.roundColor(color); diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 5a4edda..8b9b797 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -344,6 +344,7 @@ const texture = this._texture; const style = this._style; + const padding = style.trim ? 0 : style.padding; texture.baseTexture.valid = true; texture.baseTexture.resolution = this.resolution; @@ -353,11 +354,11 @@ texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - texture.trim.x = -style.padding; - texture.trim.y = -style.padding; + texture.trim.x = -padding; + texture.trim.y = -padding; - texture.orig.width = texture._frame.width - (style.padding * 2); - texture.orig.height = texture._frame.height - (style.padding * 2); + texture.orig.width = texture._frame.width - (padding * 2); + texture.orig.height = texture._frame.height - (padding * 2); // call sprite onTextureUpdate to update scale if _width or _height were set this._onTextureUpdate(); diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index 853da3d..8a9c34b 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -114,6 +114,38 @@ this.cacheId = null; this.validate(); + + /** + * Fired when a not-immediately-available source finishes loading. + * + * @protected + * @event PIXI.BaseTexture#loaded + * @param {PIXI.BaseTexture} baseTexture - Resource loaded. + */ + + /** + * Fired when a not-immediately-available source fails to load. + * + * @protected + * @event PIXI.BaseTexture#error + * @param {PIXI.BaseTexture} baseTexture - Resource errored. + */ + + /** + * Fired when BaseTexture is updated. + * + * @protected + * @event PIXI.BaseTexture#update + * @param {PIXI.BaseTexture} baseTexture - Instance of texture being updated. + */ + + /** + * Fired when BaseTexture is destroyed. + * + * @protected + * @event PIXI.BaseTexture#dispose + * @param {PIXI.BaseTexture} baseTexture - Instance of texture being destroyed. + */ } setResource(resource) diff --git a/src/core/textures/Spritesheet.js b/src/core/textures/Spritesheet.js index d469c62..9b24f27 100644 --- a/src/core/textures/Spritesheet.js +++ b/src/core/textures/Spritesheet.js @@ -208,6 +208,7 @@ // lets also add the frame to pixi's global cache for fromFrame and fromImage functions TextureCache[i] = this.textures[i]; + this.textures[i].textureCacheId = i; } frameIndex++; diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 0a2c379..2213087 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -147,9 +147,9 @@ /** * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. * - * @event update - * @memberof PIXI.Texture# + * @event PIXI.Texture#update * @protected + * @param {PIXI.Texture} texture - Instance of texture being updated. */ this._updateID = 0; @@ -159,6 +159,15 @@ * @type {Object} */ this.transform = null; + + /** + * The id under which this Texture has been added to the texture cache. This is + * automatically set in certain cases, but may not always be accurate, particularly if + * the texture is in the cache under multiple ids. + * + * @member {string} + */ + this.textureCacheId = null; } /** @@ -225,11 +234,17 @@ this._uvs = null; this.trim = null; this.orig = null; + this.textureCacheId = null; this.valid = false; - this.off('dispose', this.dispose, this); - this.off('update', this.update, this); + for (const prop in TextureCache) + { + if (TextureCache[prop] === this) + { + delete TextureCache[prop]; + } + } } /** @@ -315,14 +330,15 @@ */ static fromLoader(source, imageUrl, name) { - // console.log('added from loader...') + // console.log('added from loader...') const resource = new ImageResource(source);//.from(imageUrl, crossorigin);// document.createElement('img'); - // console.log('base resource ' + resource.width); + // console.log('base resource ' + resource.width); const baseTexture = new BaseTexture(resource, settings.SCALE_MODE, getResolutionOfUrl(imageUrl)); + /// console.log('base width ' + baseTexture.width); const texture = new Texture(baseTexture); @@ -335,6 +351,7 @@ // lets also add the frame to pixi's global cache for fromFrame and fromImage fucntions BaseTextureCache[name] = baseTexture; TextureCache[name] = texture; + texture.textureCacheId = name; // also add references by url if they are different. if (name !== imageUrl) @@ -355,6 +372,10 @@ */ static addTextureToCache(texture, id) { + if (!texture.textureCacheId) + { + texture.textureCacheId = id; + } TextureCache[id] = texture; } @@ -495,6 +516,7 @@ */ Texture.EMPTY = new Texture(new BaseTexture()); removeAllHandlers(Texture.EMPTY); +removeAllHandlers(Texture.EMPTY.baseTexture); /** * A white texture of 10x10 size, used for graphics and other things @@ -505,3 +527,4 @@ */ Texture.WHITE = createWhiteTexture(); removeAllHandlers(Texture.WHITE); +removeAllHandlers(Texture.WHITE.baseTexture); diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index 59eb9fd..a9b9f51 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -1,6 +1,7 @@ import BaseTexture from './BaseTexture'; import { uid, BaseTextureCache } from '../utils'; -import * as ticker from '../ticker'; +import { shared } from '../ticker'; +import { UPDATE_PRIORITY } from '../const'; /** * A texture of a [playing] Video. @@ -125,7 +126,7 @@ if (!this._isAutoUpdating && this.autoUpdate) { - ticker.shared.add(this.update, this); + shared.add(this.update, this, UPDATE_PRIORITY.HIGH); this._isAutoUpdating = true; } } @@ -139,7 +140,7 @@ { if (this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); this._isAutoUpdating = false; } } @@ -187,7 +188,7 @@ { if (this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); } if (this.source && this.source._pixiId) @@ -281,12 +282,12 @@ if (!this._autoUpdate && this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); this._isAutoUpdating = false; } else if (this._autoUpdate && !this._isAutoUpdating) { - ticker.shared.add(this.update, this); + shared.add(this.update, this, UPDATE_PRIORITY.HIGH); this._isAutoUpdating = true; } } diff --git a/src/core/ticker/Ticker.js b/src/core/ticker/Ticker.js index 17a1517..40df138 100644 --- a/src/core/ticker/Ticker.js +++ b/src/core/ticker/Ticker.js @@ -1,12 +1,10 @@ import settings from '../settings'; -import EventEmitter from 'eventemitter3'; - -// Internal event used by composed emitter -const TICK = 'tick'; +import { UPDATE_PRIORITY } from '../const'; +import TickerListener from './TickerListener'; /** * A Ticker class that runs an update loop that other objects listen to. - * This class is composed around an EventEmitter object to add listeners + * This class is composed around listeners * meant for execution on the next requested animation frame. * Animation frames are requested only when necessary, * e.g. When the ticker is started and the emitter has listeners. @@ -22,10 +20,11 @@ constructor() { /** - * Internal emitter used to fire 'tick' event + * The first listener. All new listeners added are chained on this. * @private + * @type {TickerListener} */ - this._emitter = new EventEmitter(); + this._head = new TickerListener(null, null, Infinity); /** * Internal current frame request ID @@ -131,7 +130,7 @@ // Invoke listeners now this.update(time); // Listener side effects may have modified ticker state. - if (this.started && this._requestId === null && this._emitter.listeners(TICK, true)) + if (this.started && this._requestId === null && this._head.next) { this._requestId = requestAnimationFrame(this._tick); } @@ -148,7 +147,7 @@ */ _requestIfNeeded() { - if (this._requestId === null && this._emitter.listeners(TICK, true)) + if (this._requestId === null && this._head.next) { // ensure callbacks get correct delta this.lastTime = performance.now(); @@ -193,35 +192,72 @@ } /** - * Calls {@link module:eventemitter3.EventEmitter#on} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. + * Register a handler for tick events. Calls continuously unless + * it is removed or the ticker is stopped. * * @param {Function} fn - The listener function to be added for updates * @param {Function} [context] - The listener context + * @param {number} [priority=PIXI.UPDATE_PRIORITY.NORMAL] - The priority for emitting * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - add(fn, context) + add(fn, context, priority = UPDATE_PRIORITY.NORMAL) { - this._emitter.on(TICK, fn, context); - - this._startIfPossible(); - - return this; + return this._addListener(new TickerListener(fn, context, priority)); } /** - * Calls {@link module:eventemitter3.EventEmitter#once} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. + * Add a handler for the tick event which is only execute once. * * @param {Function} fn - The listener function to be added for one update * @param {Function} [context] - The listener context + * @param {number} [priority=PIXI.UPDATE_PRIORITY.NORMAL] - The priority for emitting * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - addOnce(fn, context) + addOnce(fn, context, priority = UPDATE_PRIORITY.NORMAL) { - this._emitter.once(TICK, fn, context); + return this._addListener(new TickerListener(fn, context, priority, true)); + } + + /** + * Internally adds the event handler so that it can be sorted by priority. + * Priority allows certain handler (user, AnimatedSprite, Interaction) to be run + * before the rendering. + * + * @private + * @param {TickerListener} listener - Current listener being added. + * @returns {PIXI.ticker.Ticker} This instance of a ticker + */ + _addListener(listener) + { + // For attaching to head + let current = this._head.next; + let previous = this._head; + + // Add the first item + if (!current) + { + listener.connect(previous); + } + else + { + // Go from highest to lowest priority + while (current) + { + if (listener.priority >= current.priority) + { + listener.connect(previous); + break; + } + previous = current; + current = current.next; + } + + // Not yet connected + if (!listener.previous) + { + listener.connect(previous); + } + } this._startIfPossible(); @@ -229,19 +265,33 @@ } /** - * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. - * It checks if the emitter has listeners for 'tick' event. - * If it does, then it cancels the animation frame. + * Removes any handlers matching the function and context parameters. + * If no handlers are left after removing, then it cancels the animation frame. * - * @param {Function} [fn] - The listener function to be removed + * @param {Function} fn - The listener function to be removed * @param {Function} [context] - The listener context to be removed * @returns {PIXI.ticker.Ticker} This instance of a ticker */ remove(fn, context) { - this._emitter.off(TICK, fn, context); + let listener = this._head.next; - if (!this._emitter.listeners(TICK, true)) + while (listener) + { + // We found a match, lets remove it + // no break to delete all possible matches + // incase a listener was added 2+ times + if (listener.match(fn, context)) + { + listener = listener.destroy(); + } + else + { + listener = listener.next; + } + } + + if (!this._head.next) { this._cancelIfNeeded(); } @@ -276,6 +326,25 @@ } /** + * Destroy the ticker and don't use after this. Calling + * this method removes all references to internal events. + */ + destroy() + { + this.stop(); + + let listener = this._head.next; + + while (listener) + { + listener = listener.destroy(true); + } + + this._head.destroy(); + this._head = null; + } + + /** * Triggers an update. An update entails setting the * current {@link PIXI.ticker.Ticker#elapsedMS}, * the current {@link PIXI.ticker.Ticker#deltaTime}, @@ -320,8 +389,22 @@ this.deltaTime = elapsedMS * settings.TARGET_FPMS * this.speed; + // Cache a local reference, in-case ticker is destroyed + // during the emit, we can still check for head.next + const head = this._head; + // Invoke listeners added to internal emitter - this._emitter.emit(TICK, this.deltaTime); + let listener = head.next; + + while (listener) + { + listener = listener.emit(this.deltaTime); + } + + if (!head.next) + { + this._cancelIfNeeded(); + } } else { diff --git a/src/core/ticker/TickerListener.js b/src/core/ticker/TickerListener.js new file mode 100644 index 0000000..2bedb34 --- /dev/null +++ b/src/core/ticker/TickerListener.js @@ -0,0 +1,158 @@ +/** + * Internal class for handling the priority sorting of ticker handlers. + * + * @private + * @class + * @memberof PIXI.ticker + */ +export default class TickerListener +{ + /** + * Constructor + * + * @param {Function} fn - The listener function to be added for one update + * @param {Function} [context=null] - The listener context + * @param {number} [priority=0] - The priority for emitting + * @param {boolean} [once=false] - If the handler should fire once + */ + constructor(fn, context = null, priority = 0, once = false) + { + /** + * The handler function to execute. + * @member {Function} + */ + this.fn = fn; + + /** + * The calling to execute. + * @member {Function} + */ + this.context = context; + + /** + * The current priority. + * @member {number} + */ + this.priority = priority; + + /** + * If this should only execute once. + * @member {boolean} + */ + this.once = once; + + /** + * The next item in chain. + * @member {TickerListener} + */ + this.next = null; + + /** + * The previous item in chain. + * @member {TickerListener} + */ + this.previous = null; + + /** + * `true` if this listener has been destroyed already. + * @member {boolean} + * @private + */ + this._destroyed = false; + } + + /** + * Simple compare function to figure out if a function and context match. + * + * @param {Function} fn - The listener function to be added for one update + * @param {Function} context - The listener context + * @return {boolean} `true` if the listener match the arguments + */ + match(fn, context) + { + context = context || null; + + return this.fn === fn && this.context === context; + } + + /** + * Emit by calling the current function. + * @param {number} deltaTime - time since the last emit. + * @return {TickerListener} Next ticker + */ + emit(deltaTime) + { + if (this.context) + { + this.fn.call(this.context, deltaTime); + } + else + { + this.fn(deltaTime); + } + + if (this.once) + { + this.destroy(); + } + + const redirect = this.next; + + // Soft-destroying should remove + // the next reference + if (this._destroyed) + { + this.next = null; + } + + return redirect; + } + + /** + * Connect to the list. + * @param {TickerListener} previous - Input node, previous listener + */ + connect(previous) + { + this.previous = previous; + if (previous.next) + { + previous.next.previous = this; + } + this.next = previous.next; + previous.next = this; + } + + /** + * Destroy and don't use after this. + * @param {boolean} [hard = false] `true` to remove the `next` reference, this + * is considered a hard destroy. Soft destroy maintains the next reference. + * @return {TickerListener} The listener to redirect while emitting or removing. + */ + destroy(hard = false) + { + this._destroyed = true; + this.fn = null; + this.context = null; + + // Disconnect, hook up next and previous + if (this.previous) + { + this.previous.next = this.next; + } + + if (this.next) + { + this.next.previous = this.previous; + } + + // Redirect to the next item + const redirect = this.previous; + + // Remove references + this.next = hard ? null : redirect; + this.previous = null; + + return redirect; + } +} diff --git a/src/core/ticker/index.js b/src/core/ticker/index.js index 4b3017c..c3020d4 100644 --- a/src/core/ticker/index.js +++ b/src/core/ticker/index.js @@ -45,8 +45,25 @@ const shared = new Ticker(); shared.autoStart = true; +shared.destroy = () => +{ + // protect destroying shared ticker + // this is used by other internal systems + // like AnimatedSprite and InteractionManager +}; /** + * This namespace contains an API for interacting with PIXI's internal global update loop. + * + * This ticker is used for rendering, {@link PIXI.extras.AnimatedSprite AnimatedSprite}, + * {@link PIXI.interaction.InteractionManager InteractionManager} and many other time-based PIXI systems. + * @example + * const ticker = new PIXI.ticker.Ticker(); + * ticker.stop(); + * ticker.add((deltaTime) => { + * // do something every frame + * }); + * ticker.start(); * @namespace PIXI.ticker */ export { shared, Ticker }; diff --git a/src/core/utils/index.js b/src/core/utils/index.js index be44750..7441f7b 100644 --- a/src/core/utils/index.js +++ b/src/core/utils/index.js @@ -9,6 +9,21 @@ let saidHello = false; /** + * Generalized convenience utilities for PIXI. + * @example + * // Extend PIXI's internal Event Emitter. + * class MyEmitter extends PIXI.utils.EventEmitter { + * constructor() { + * super(); + * console.log("Emitter created!"); + * } + * } + * + * // Get info on current device + * console.log(PIXI.utils.isMobile); + * + * // Convert hex color to string + * console.log(PIXI.utils.hex2string(0xff00ff)); // returns: "#ff00ff" * @namespace PIXI.utils */ export { diff --git a/src/deprecation.js b/src/deprecation.js index b9c73b3..33daeec 100644 --- a/src/deprecation.js +++ b/src/deprecation.js @@ -918,6 +918,35 @@ }); /** + * @method + * @name PIXI.prepare.BasePrepare#register + * @see PIXI.prepare.BasePrepare#registerFindHook + * @deprecated since version 4.4.2 + * @param {Function} [addHook] - Function call that takes two parameters: `item:*, queue:Array` + * function must return `true` if it was able to add item to the queue. + * @param {Function} [uploadHook] - Function call that takes two parameters: `prepare:CanvasPrepare, item:*` and + * function must return `true` if it was able to handle upload of item. + * @return {PIXI.BasePrepare} Instance of plugin for chaining. + */ +prepare.BasePrepare.prototype.register = function register(addHook, uploadHook) +{ + warn('renderer.plugins.prepare.register is now deprecated, ' + + 'please use renderer.plugins.prepare.registerFindHook & renderer.plugins.prepare.registerUploadHook'); + + if (addHook) + { + this.registerFindHook(addHook); + } + + if (uploadHook) + { + this.registerUploadHook(uploadHook); + } + + return this; +}; + +/** * The number of graphics or textures to upload to the GPU. * * @name PIXI.prepare.canvas.UPLOADS_PER_FRAME @@ -969,67 +998,73 @@ }, }); -Object.defineProperties(loaders.Resource.prototype, { - isJson: { - get() - { - warn('The isJson property is deprecated, please use `resource.type === Resource.TYPE.JSON`.'); +if (loaders.Loader) +{ + const Resource = loaders.Resource; + const Loader = loaders.Loader; - return this.type === loaders.Loader.Resource.TYPE.JSON; + Object.defineProperties(Resource.prototype, { + isJson: { + get() + { + warn('The isJson property is deprecated, please use `resource.type === Resource.TYPE.JSON`.'); + + return this.type === Resource.TYPE.JSON; + }, }, - }, - isXml: { - get() - { - warn('The isXml property is deprecated, please use `resource.type === Resource.TYPE.XML`.'); + isXml: { + get() + { + warn('The isXml property is deprecated, please use `resource.type === Resource.TYPE.XML`.'); - return this.type === loaders.Loader.Resource.TYPE.XML; + return this.type === Resource.TYPE.XML; + }, }, - }, - isImage: { - get() - { - warn('The isImage property is deprecated, please use `resource.type === Resource.TYPE.IMAGE`.'); + isImage: { + get() + { + warn('The isImage property is deprecated, please use `resource.type === Resource.TYPE.IMAGE`.'); - return this.type === loaders.Loader.Resource.TYPE.IMAGE; + return this.type === Resource.TYPE.IMAGE; + }, }, - }, - isAudio: { - get() - { - warn('The isAudio property is deprecated, please use `resource.type === Resource.TYPE.AUDIO`.'); + isAudio: { + get() + { + warn('The isAudio property is deprecated, please use `resource.type === Resource.TYPE.AUDIO`.'); - return this.type === loaders.Loader.Resource.TYPE.AUDIO; + return this.type === Resource.TYPE.AUDIO; + }, }, - }, - isVideo: { - get() - { - warn('The isVideo property is deprecated, please use `resource.type === Resource.TYPE.VIDEO`.'); + isVideo: { + get() + { + warn('The isVideo property is deprecated, please use `resource.type === Resource.TYPE.VIDEO`.'); - return this.type === loaders.Loader.Resource.TYPE.VIDEO; + return this.type === Resource.TYPE.VIDEO; + }, }, - }, -}); + }); -Object.defineProperties(loaders.Loader.prototype, { - before: { - get() - { - warn('The before() method is deprecated, please use pre().'); + Object.defineProperties(Loader.prototype, { + before: { + get() + { + warn('The before() method is deprecated, please use pre().'); - return this.pre; + return this.pre; + }, }, - }, - after: { - get() - { - warn('The after() method is deprecated, please use use().'); + after: { + get() + { + warn('The after() method is deprecated, please use use().'); - return this.use; + return this.use; + }, }, - }, -}); + }); +} /** * @name PIXI.interaction.interactiveTarget#defaultCursor diff --git a/src/extract/canvas/CanvasExtract.js b/src/extract/canvas/CanvasExtract.js index 941ca05..3ac5aee 100644 --- a/src/extract/canvas/CanvasExtract.js +++ b/src/extract/canvas/CanvasExtract.js @@ -8,7 +8,7 @@ * An instance of this class is automatically created by default, and can be found at renderer.plugins.extract * * @class - * @memberof PIXI + * @memberof PIXI.extract */ export default class CanvasExtract { @@ -21,9 +21,9 @@ /** * Collection of methods for extracting data (image, pixels, etc.) from a display object or render texture * - * @member {PIXI.CanvasExtract} extract + * @member {PIXI.extract.CanvasExtract} extract * @memberof PIXI.CanvasRenderer# - * @see PIXI.CanvasExtract + * @see PIXI.extract.CanvasExtract */ renderer.extract = this; } diff --git a/src/extract/index.js b/src/extract/index.js index 0f2170d..e9478c1 100644 --- a/src/extract/index.js +++ b/src/extract/index.js @@ -1,2 +1,22 @@ +/** + * This namespace provides renderer-specific plugins for exporting content from a renderer. + * For instance, these plugins can be used for saving an Image, Canvas element or for exporting the raw image data (pixels). + * + * Do not instantiate these plugins directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. + * @example + * // Create a new app (will auto-add extract plugin to renderer) + * const app = new PIXI.Application(); + * + * // Draw a red circle + * const graphics = new PIXI.Graphics() + * .beginFill(0xFF0000) + * .drawCircle(0, 0, 50); + * + * // Render the graphics as an HTMLImageElement + * const image = app.renderer.plugins.extract.image(graphics); + * document.body.appendChild(image); + * @namespace PIXI.extract + */ export { default as webgl } from './webgl/WebGLExtract'; export { default as canvas } from './canvas/CanvasExtract'; diff --git a/src/extract/webgl/WebGLExtract.js b/src/extract/webgl/WebGLExtract.js index 60228bb..1c3dd30 100644 --- a/src/extract/webgl/WebGLExtract.js +++ b/src/extract/webgl/WebGLExtract.js @@ -9,7 +9,7 @@ * An instance of this class is automatically created by default, and can be found at renderer.plugins.extract * * @class - * @memberof PIXI + * @memberof PIXI.extract */ export default class WebGLExtract { @@ -22,9 +22,9 @@ /** * Collection of methods for extracting data (image, pixels, etc.) from a display object or render texture * - * @member {PIXI.WebGLExtract} extract + * @member {PIXI.extract.WebGLExtract} extract * @memberof PIXI.WebGLRenderer# - * @see PIXI.WebGLExtract + * @see PIXI.extract.WebGLExtract */ renderer.extract = this; } diff --git a/src/extras/AnimatedSprite.js b/src/extras/AnimatedSprite.js index 13b804f..5297778 100644 --- a/src/extras/AnimatedSprite.js +++ b/src/extras/AnimatedSprite.js @@ -137,7 +137,7 @@ this.playing = true; if (this._autoUpdate) { - core.ticker.shared.add(this.update, this); + core.ticker.shared.add(this.update, this, core.UPDATE_PRIORITY.HIGH); } } diff --git a/src/extras/TextureTransform.js b/src/extras/TextureTransform.js index 78e6e40..16536d2 100644 --- a/src/extras/TextureTransform.js +++ b/src/extras/TextureTransform.js @@ -65,8 +65,36 @@ } /** + * Multiplies uvs array to transform + * @param {Float32Array} uvs mesh uvs + * @param {Float32Array} [out=uvs] output + * @returns {Float32Array} output + */ + multiplyUvs(uvs, out) + { + if (out === undefined) + { + out = uvs; + } + + const mat = this.mapCoord; + + for (let i = 0; i < uvs.length; i += 2) + { + const x = uvs[i]; + const y = uvs[i + 1]; + + out[i] = (x * mat.a) + (y * mat.c) + mat.tx; + out[i + 1] = (x * mat.b) + (y * mat.d) + mat.ty; + } + + return out; + } + + /** * updates matrices if texture was changed * @param {boolean} forceUpdate if true, matrices will be updated any case + * @returns {boolean} whether or not it was updated */ update(forceUpdate) { @@ -74,13 +102,13 @@ if (!tex || !tex.valid) { - return; + return false; } if (!forceUpdate && this._lastTextureID === tex._updateID) { - return; + return false; } this._lastTextureID = tex._updateID; @@ -110,5 +138,7 @@ frame[3] = (tex._frame.y + tex._frame.height - margin + offset) / texBase.height; this.uClampOffset[0] = offset / texBase.realWidth; this.uClampOffset[1] = offset / texBase.realHeight; + + return true; } } diff --git a/src/extras/TilingSprite.js b/src/extras/TilingSprite.js index 1f6014f..a3f027f 100644 --- a/src/extras/TilingSprite.js +++ b/src/extras/TilingSprite.js @@ -180,9 +180,9 @@ const transform = this.worldTransform; const resolution = renderer.resolution; const baseTexture = texture.baseTexture; - const baseTextureResolution = texture.baseTexture.resolution; - const modX = (this.tilePosition.x / this.tileScale.x) % texture._frame.width; - const modY = (this.tilePosition.y / this.tileScale.y) % texture._frame.height; + const baseTextureResolution = baseTexture.resolution; + const modX = ((this.tilePosition.x / this.tileScale.x) % texture._frame.width) * baseTextureResolution; + const modY = ((this.tilePosition.y / this.tileScale.y) % texture._frame.height) * baseTextureResolution; // create a nice shiny pattern! // TODO this needs to be refreshed if texture changes.. diff --git a/src/extras/cacheAsBitmap.js b/src/extras/cacheAsBitmap.js index 5dd5640..aea06a8 100644 --- a/src/extras/cacheAsBitmap.js +++ b/src/extras/cacheAsBitmap.js @@ -227,7 +227,16 @@ this.transform._parentID = -1; // restore the transform of the cached sprite to avoid the nasty flicker.. - this.updateTransform(); + if (!this.parent) + { + this.parent = renderer._tempDisplayObjectParent; + this.updateTransform(); + this.parent = null; + } + else + { + this.updateTransform(); + } // map the hit test.. this.containsPoint = cachedSprite.containsPoint.bind(cachedSprite); @@ -314,7 +323,17 @@ cachedSprite._bounds = this._bounds; cachedSprite.alpha = cacheAlpha; - this.updateTransform(); + if (!this.parent) + { + this.parent = renderer._tempDisplayObjectParent; + this.updateTransform(); + this.parent = null; + } + else + { + this.updateTransform(); + } + this.updateTransform = this.displayObjectUpdateTransform; this._cacheData.sprite = cachedSprite; @@ -358,9 +377,12 @@ * Destroys the cached object. * * @private + * @param {object|boolean} [options] - Options parameter. A boolean will act as if all options + * have been set to that value. + * Used when destroying containers, see the Container.destroy method. */ -DisplayObject.prototype._cacheAsBitmapDestroy = function _cacheAsBitmapDestroy() +DisplayObject.prototype._cacheAsBitmapDestroy = function _cacheAsBitmapDestroy(options) { this.cacheAsBitmap = false; - this.destroy(); + this.destroy(options); }; diff --git a/src/extras/index.js b/src/extras/index.js index bbd8bbf..a34d7ec 100644 --- a/src/extras/index.js +++ b/src/extras/index.js @@ -1,7 +1,7 @@ /** + * Additional PIXI DisplayObjects for animation, tiling and bitmap text. * @namespace PIXI.extras */ -export { default as TextureTransform } from './TextureTransform'; export { default as AnimatedSprite } from './AnimatedSprite'; export { default as TilingSprite } from './TilingSprite'; export { default as TilingSpriteRenderer } from './webgl/TilingSpriteRenderer'; diff --git a/src/extras/webgl/TilingSpriteRenderer.js b/src/extras/webgl/TilingSpriteRenderer.js index aeec172..eee3b5c 100644 --- a/src/extras/webgl/TilingSpriteRenderer.js +++ b/src/extras/webgl/TilingSpriteRenderer.js @@ -11,7 +11,7 @@ * WebGL renderer plugin for tiling sprites * * @class - * @memberof PIXI + * @memberof PIXI.extras * @extends PIXI.ObjectRenderer */ export default class TilingSpriteRenderer extends core.ObjectRenderer @@ -118,7 +118,7 @@ tempMat.invert(); if (isSimple) { - tempMat.append(uv.mapCoord); + tempMat.prepend(uv.mapCoord); } else { diff --git a/src/filters/displacement/DisplacementFilter.js b/src/filters/displacement/DisplacementFilter.js index d5355c3..b5d030c 100644 --- a/src/filters/displacement/DisplacementFilter.js +++ b/src/filters/displacement/DisplacementFilter.js @@ -35,7 +35,7 @@ this.maskSprite = sprite; this.maskMatrix = maskMatrix; - this.uniforms.mapSampler = sprite.texture; + this.uniforms.mapSampler = sprite._texture; this.uniforms.filterMatrix = maskMatrix; this.uniforms.scale = { x: 1, y: 1 }; diff --git a/src/filters/index.js b/src/filters/index.js index f3e8862..7ceb50a 100644 --- a/src/filters/index.js +++ b/src/filters/index.js @@ -1,4 +1,21 @@ /** + * This namespace contains WebGL-only display filters that can be applied + * to DisplayObjects using the {@link PIXI.DisplayObject#filters filters} property. + * @example + * // Create a new application + * const app = new PIXI.Application(); + * + * // Draw a green rectangle + * const rect = new PIXI.Graphics() + * .beginFill(0x00ff00) + * .drawRect(40, 40, 200, 200); + * + * // Add a blur filter + * rect.filters = [new PIXI.filters.BlurFilter()]; + * + * // Display rectangle + * app.stage.addChild(rect); + * document.body.appendChild(app.view); * @namespace PIXI.filters */ export { default as FXAAFilter } from './fxaa/FXAAFilter'; diff --git a/src/index.js b/src/index.js index c553427..77da490 100644 --- a/src/index.js +++ b/src/index.js @@ -20,6 +20,14 @@ import { utils } from './core'; utils.mixins.performMixins(); +/** + * Alias for {@link PIXI.loaders.shared}. + * @name loader + * @memberof PIXI + * @type {PIXI.loader.Loader} + */ +const loader = loaders.shared || null; + export { accessibility, extract, @@ -30,18 +38,8 @@ mesh, particles, prepare, + loader, }; -/** - * A premade instance of the loader that can be used to load resources. - * - * @name loader - * @memberof PIXI - * @property {PIXI.loaders.Loader} - */ -const loader = loaders && loaders.Loader ? new loaders.Loader() : null; // check is there in case user excludes loader lib - -export { loader }; - // Always export pixi globally. global.PIXI = exports; // eslint-disable-line diff --git a/src/interaction/InteractionData.js b/src/interaction/InteractionData.js index 02d53a3..449f8cc 100644 --- a/src/interaction/InteractionData.js +++ b/src/interaction/InteractionData.js @@ -30,7 +30,10 @@ /** * When passed to an event handler, this will be the original DOM Event that was captured * - * @member {Event} + * @see https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent + * @see https://developer.mozilla.org/en-US/docs/Web/API/TouchEvent + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent + * @member {MouseEvent|TouchEvent|PointerEvent} */ this.originalEvent = null; } diff --git a/src/interaction/InteractionManager.js b/src/interaction/InteractionManager.js index 8baf13a..2d75c7c 100644 --- a/src/interaction/InteractionManager.js +++ b/src/interaction/InteractionManager.js @@ -13,6 +13,14 @@ const MOUSE_POINTER_ID = 'MOUSE'; +// helpers for hitTest() - only used inside hitTest() +const hitTestEvent = { + target: null, + data: { + global: null, + }, +}; + /** * The interaction manager deals with mouse, touch and pointer events. Any DisplayObject can be interactive * if its interactive parameter is set to true @@ -245,67 +253,60 @@ this.setTargetElement(this.renderer.view, this.renderer.resolution); /** - * Fired when a pointer device button (usually a mouse button) is pressed on the display + * Fired when a pointer device button (usually a mouse left-button) is pressed on the display * object. * - * @event mousedown - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mousedown + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device secondary button (usually a mouse right-button) is pressed * on the display object. * - * @event rightdown - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightdown + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** - * Fired when a pointer device button (usually a mouse button) is released over the display + * Fired when a pointer device button (usually a mouse left-button) is released over the display * object. * - * @event mouseup - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseup + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device secondary button (usually a mouse right-button) is released * over the display object. * - * @event rightup - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightup + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** - * Fired when a pointer device button (usually a mouse button) is pressed and released on + * Fired when a pointer device button (usually a mouse left-button) is pressed and released on * the display object. * - * @event click - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#click + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device secondary button (usually a mouse right-button) is pressed * and released on the display object. * - * @event rightclick - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightclick + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** - * Fired when a pointer device button (usually a mouse button) is released outside the + * Fired when a pointer device button (usually a mouse left-button) is released outside the * display object that initially registered a * [mousedown]{@link PIXI.interaction.InteractionManager#event:mousedown}. * - * @event mouseupoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** @@ -313,146 +314,362 @@ * outside the display object that initially registered a * [rightdown]{@link PIXI.interaction.InteractionManager#event:rightdown}. * - * @event rightupoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device (usually a mouse) is moved while over the display object * - * @event mousemove - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mousemove + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device (usually a mouse) is moved onto the display object * - * @event mouseover - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseover + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device (usually a mouse) is moved off the display object * - * @event mouseout - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseout + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is pressed on the display object. * - * @event pointerdown - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerdown + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is released over the display object. * - * @event pointerup - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerup + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when the operating system cancels a pointer event * - * @event pointercancel - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointercancel + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is pressed and released on the display object. * - * @event pointertap - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointertap + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is released outside the display object that initially * registered a [pointerdown]{@link PIXI.interaction.InteractionManager#event:pointerdown}. * - * @event pointerupoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device is moved while over the display object * - * @event pointermove - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointermove + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device is moved onto the display object * - * @event pointerover - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerover + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device is moved off the display object * - * @event pointerout - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerout + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is placed on the display object. * - * @event touchstart - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchstart + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is removed from the display object. * - * @event touchend - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchend + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when the operating system cancels a touch * - * @event touchcancel - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchcancel + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is placed and removed from the display object. * - * @event tap - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#tap + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is removed outside of the display object that initially * registered a [touchstart]{@link PIXI.interaction.InteractionManager#event:touchstart}. * - * @event touchendoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchendoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is moved along the display object. * - * @event touchmove - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchmove + * @param {PIXI.interaction.InteractionData} event - Interaction data */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is pressed on the display. + * object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mousedown + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is pressed + * on the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightdown + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is released over the display + * object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseup + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is released + * over the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightup + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is pressed and released on + * the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#click + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is pressed + * and released on the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightclick + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is released outside the + * display object that initially registered a + * [mousedown]{@link PIXI.DisplayObject#event:mousedown}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is released + * outside the display object that initially registered a + * [rightdown]{@link PIXI.DisplayObject#event:rightdown}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device (usually a mouse) is moved while over the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mousemove + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device (usually a mouse) is moved onto the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseover + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device (usually a mouse) is moved off the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseout + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is pressed on the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerdown + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is released over the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerup + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when the operating system cancels a pointer event. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointercancel + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is pressed and released on the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointertap + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is released outside the display object that initially + * registered a [pointerdown]{@link PIXI.DisplayObject#event:pointerdown}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device is moved while over the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointermove + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device is moved onto the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerover + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device is moved off the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerout + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is placed on the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchstart + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is removed from the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchend + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when the operating system cancels a touch. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchcancel + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is placed and removed from the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#tap + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is removed outside of the display object that initially + * registered a [touchstart]{@link PIXI.DisplayObject#event:touchstart}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchendoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is moved along the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchmove + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + } + + /** + * Hit tests a point against the display tree, returning the first interactive object that is hit. + * + * @param {PIXI.Point} globalPoint - A point to hit test with, in global space. + * @param {PIXI.Container} [root] - The root display object to start from. If omitted, defaults + * to the last rendered root of the associated renderer. + * @return {PIXI.DisplayObject} The hit display object, if any. + */ + hitTest(globalPoint, root) + { + // clear the target for our hit test + hitTestEvent.target = null; + // assign the global point + hitTestEvent.data.global = globalPoint; + // ensure safety of the root + if (!root) + { + root = this.renderer._lastObjectRendered; + } + // run the hit test + this.processInteractive(hitTestEvent, root, null, true); + // return our found object - it'll be null if we didn't hit anything + + return hitTestEvent.target; } /** @@ -487,7 +704,7 @@ return; } - core.ticker.shared.add(this.update, this); + core.ticker.shared.add(this.update, this, core.UPDATE_PRIORITY.INTERACTION); if (window.navigator.msPointerEnabled) { @@ -804,8 +1021,6 @@ } } - let keepHitTestingAfterChildren = hitTest; - // ** FREE TIP **! If an object is not interactive or has no buttons in it // (such as a game scene!) set interactiveChildren to false for that displayObject. // This will allow pixi to completely ignore and bypass checking the displayObjects children. @@ -818,7 +1033,9 @@ const child = children[i]; // time to get recursive.. if this function will return if something is hit.. - if (this.processInteractive(interactionEvent, child, func, hitTest, interactiveParent)) + const childHit = this.processInteractive(interactionEvent, child, func, hitTest, interactiveParent); + + if (childHit) { // its a good idea to check if a child has lost its parent. // this means it has been removed whilst looping so its best @@ -827,8 +1044,6 @@ continue; } - hit = true; - // we no longer need to hit test any more objects in this container as we we // now know the parent has been hit interactiveParent = false; @@ -838,36 +1053,41 @@ // This means we no longer need to hit test anything else. We still need to run // through all objects, but we don't need to perform any hit tests. - keepHitTestingAfterChildren = false; - - if (child.interactive) + if (childHit) { - hitTest = false; + if (interactionEvent.target) + { + hitTest = false; + } + hit = true; } - - // we can break now as we have hit an object. } } } - hitTest = keepHitTestingAfterChildren; - // no point running this if the item is not interactive or does not have an interactive parent. if (interactive) { // if we are hit testing (as in we have no hit any objects yet) // We also don't need to worry about hit testing if once of the displayObjects children - // has already been hit! - if (hitTest && !hit) + // has already been hit - but only if it was interactive, otherwise we need to keep + // looking for an interactive child, just in case we hit one + if (hitTest && !interactionEvent.target) { if (displayObject.hitArea) { displayObject.worldTransform.applyInverse(point, this._tempPoint); - hit = displayObject.hitArea.contains(this._tempPoint.x, this._tempPoint.y); + if (displayObject.hitArea.contains(this._tempPoint.x, this._tempPoint.y)) + { + hit = true; + } } else if (displayObject.containsPoint) { - hit = displayObject.containsPoint(point); + if (displayObject.containsPoint(point)) + { + hit = true; + } } } @@ -878,7 +1098,10 @@ interactionEvent.target = displayObject; } - func(interactionEvent, displayObject, hit); + if (func) + { + func(interactionEvent, displayObject, !!hit); + } } } @@ -914,7 +1137,7 @@ { const event = events[i]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -998,7 +1221,7 @@ { const event = events[i]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1098,7 +1321,7 @@ const test = isRightButton ? flags.RIGHT_DOWN : flags.LEFT_DOWN; - const isDown = trackingData !== undefined && (trackingData.flags | test); + const isDown = trackingData !== undefined && (trackingData.flags & test); if (hit) { @@ -1118,11 +1341,11 @@ { if (isRightButton) { - trackingData.rightDown = hit; + trackingData.rightDown = false; } else { - trackingData.leftDown = hit; + trackingData.leftDown = false; } } } @@ -1180,7 +1403,7 @@ { const event = events[i]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1255,7 +1478,7 @@ this.setCursorMode(null); } - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1268,6 +1491,12 @@ { this.emit('mouseout', interactionEvent); } + else + { + // we can get touchleave events after touchend, so we want to make sure we don't + // introduce memory leaks + this.releaseInteractionDataForPointerId(interactionData.identifier); + } } /** @@ -1344,7 +1573,7 @@ // Only mouse and pointer can call onPointerOver, so events will always be length 1 const event = events[0]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1366,12 +1595,14 @@ * Get InteractionData for a given pointerId. Store that data as well * * @private - * @param {number} pointerId - Identifier from a pointer event + * @param {PointerEvent} event - Normalized pointer event, output from normalizeToPointerData * @return {InteractionData} - Interaction data for the given pointer identifier */ - getInteractionDataForPointerId(pointerId) + getInteractionDataForPointerId(event) { - if (pointerId === MOUSE_POINTER_ID) + const pointerId = event.pointerId; + + if (pointerId === MOUSE_POINTER_ID || event.pointerType === 'mouse') { return this.mouse; } diff --git a/src/interaction/InteractionTrackingData.js b/src/interaction/InteractionTrackingData.js index 6a1f69e..8493b63 100644 --- a/src/interaction/InteractionTrackingData.js +++ b/src/interaction/InteractionTrackingData.js @@ -35,8 +35,10 @@ } /** + * Unique pointer id of the event + * * @readonly - * @type {number} Unique pointer id of the event + * @member {number} */ get pointerId() { diff --git a/src/interaction/index.js b/src/interaction/index.js index 56c5b09..72f7d71 100644 --- a/src/interaction/index.js +++ b/src/interaction/index.js @@ -1,4 +1,8 @@ /** + * This namespace contains a renderer plugin for handling mouse, pointer, and touch events. + * + * Do not instantiate this plugin directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. * @namespace PIXI.interaction */ export { default as InteractionData } from './InteractionData'; diff --git a/src/interaction/interactiveTarget.js b/src/interaction/interactiveTarget.js index d252fba..cebac40 100644 --- a/src/interaction/interactiveTarget.js +++ b/src/interaction/interactiveTarget.js @@ -2,7 +2,7 @@ * Default property values of interactive objects * Used by {@link PIXI.interaction.InteractionManager} to automatically give all DisplayObjects these properties * - * @mixin + * @private * @name interactiveTarget * @memberof PIXI.interaction * @example @@ -14,18 +14,28 @@ * ); */ export default { + /** - * Determines if the displayObject be clicked/touched + * Enable interaction events for the DisplayObject. Touch, pointer and mouse + * events will not be emitted unless `interactive` is set to `true`. * - * @inner {boolean} + * @example + * const sprite = new PIXI.Sprite(texture); + * sprite.interactive = true; + * sprite.on('tap', (event) => { + * //handle event + * }); + * @member {boolean} + * @memberof PIXI.DisplayObject# */ interactive: false, /** * Determines if the children to the displayObject can be clicked/touched - * Setting this to false allows pixi to bypass a recursive hitTest function + * Setting this to false allows pixi to bypass a recursive `hitTest` function * - * @inner {boolean} + * @member {boolean} + * @memberof PIXI.Container# */ interactiveChildren: true, @@ -33,7 +43,12 @@ * Interaction shape. Children will be hit first, then this shape will be checked. * Setting this will cause this shape to be checked in hit tests rather than the displayObject's bounds. * - * @inner {PIXI.Rectangle|PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.RoundedRectangle} + * @example + * const sprite = new PIXI.Sprite(texture); + * sprite.interactive = true; + * sprite.hitArea = new PIXI.Rectangle(0, 0, 100, 100); + * @member {PIXI.Rectangle|PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.RoundedRectangle} + * @memberof PIXI.DisplayObject# */ hitArea: null, @@ -41,8 +56,12 @@ * If enabled, the mouse cursor use the pointer behavior when hovered over the displayObject if it is interactive * Setting this changes the 'cursor' property to `'pointer'`. * + * @example + * const sprite = new PIXI.Sprite(texture); + * sprite.interactive = true; + * sprite.buttonMode = true; * @member {boolean} - * @memberof PIXI.interaction.interactiveTarget# + * @memberof PIXI.DisplayObject# */ get buttonMode() { @@ -64,9 +83,14 @@ * This defines what cursor mode is used when the mouse cursor * is hovered over the displayObject. * + * @example + * const sprite = new PIXI.Sprite(texture); + * sprite.interactive = true; + * sprite.cursor = 'wait'; * @see https://developer.mozilla.org/en/docs/Web/CSS/cursor * - * @inner {string} + * @member {string} + * @memberof PIXI.DisplayObject# */ cursor: null, @@ -74,7 +98,7 @@ * Internal set of all active pointers, by identifier * * @member {Map} - * @memberof PIXI.interaction.interactiveTarget# + * @memberof PIXI.DisplayObject# * @private */ get trackedPointers() @@ -87,7 +111,8 @@ /** * Map of all tracked pointers, by identifier. Use trackedPointers to access. * - * @private {Map} + * @private + * @type {Map} */ _trackedPointers: undefined, }; diff --git a/src/loaders/index.js b/src/loaders/index.js index 5189793..daa1fc4 100644 --- a/src/loaders/index.js +++ b/src/loaders/index.js @@ -1,9 +1,22 @@ +import Application from '../core/Application'; +import Loader from './loader'; + /** + * This namespace contains APIs which extends the {@link https://github.com/englercj/resource-loader resource-loader} module + * for loading assets, data, and other resources dynamically. + * @example + * const loader = new PIXI.loaders.Loader(); + * loader.add('bunny', 'data/bunny.png') + * .add('spaceship', 'assets/spritesheet.json'); + * loader.load((loader, resources) => { + * // resources.bunny + * // resources.spaceship + * }); * @namespace PIXI.loaders */ -export { default as Loader } from './loader'; +export { Loader }; export { default as bitmapFontParser, parse as parseBitmapFontData } from './bitmapFontParser'; -export { default as spritesheetParser } from './spritesheetParser'; +export { default as spritesheetParser, getResourcePath } from './spritesheetParser'; export { default as textureParser } from './textureParser'; /** @@ -13,3 +26,55 @@ * @memberof PIXI.loaders */ export { Resource } from 'resource-loader'; + +/** + * A premade instance of the loader that can be used to load resources. + * @name shared + * @memberof PIXI.loaders + * @type {PIXI.loaders.Loader} + */ +const shared = new Loader(); + +shared.destroy = () => +{ + // protect destroying shared loader +}; + +export { shared }; + +// Mixin the loader construction +const AppPrototype = Application.prototype; + +AppPrototype._loader = null; + +/** + * Loader instance to help with asset loading. + * @name PIXI.Application#loader + * @type {PIXI.loaders.Loader} + */ +Object.defineProperty(AppPrototype, 'loader', { + get() + { + if (!this._loader) + { + const sharedLoader = this._options.sharedLoader; + + this._loader = sharedLoader ? shared : new Loader(); + } + + return this._loader; + }, +}); + +// Override the destroy function +// making sure to destroy the current Loader +AppPrototype._parentDestroy = AppPrototype.destroy; +AppPrototype.destroy = function destroy() +{ + if (this._loader) + { + this._loader.destroy(); + this._loader = null; + } + this._parentDestroy(); +}; diff --git a/src/loaders/loader.js b/src/loaders/loader.js index 767a411..59b8000 100644 --- a/src/loaders/loader.js +++ b/src/loaders/loader.js @@ -90,6 +90,15 @@ { Loader._pixiMiddleware.push(fn); } + + /** + * Destroy the loader, removes references. + */ + destroy() + { + this.removeAllListeners(); + this.reset(); + } } // Copy EE3 prototype (mixin) diff --git a/src/loaders/spritesheetParser.js b/src/loaders/spritesheetParser.js index e57dfb2..eda9584 100644 --- a/src/loaders/spritesheetParser.js +++ b/src/loaders/spritesheetParser.js @@ -1,12 +1,11 @@ import { Resource } from 'resource-loader'; -import path from 'path'; +import url from 'url'; import { Spritesheet } from '../core'; export default function () { return function spritesheetParser(resource, next) { - let resourcePath; const imageResourceName = `${resource.name}_image`; // skip if no data, its not json, it isn't spritesheet data, or the image resource already exists @@ -28,15 +27,7 @@ parentResource: resource, }; - // Prepend url path unless the resource image is a data url - if (resource.isDataUrl) - { - resourcePath = resource.data.meta.image; - } - else - { - resourcePath = `${path.dirname(resource.url.replace(this.baseUrl, ''))}/${resource.data.meta.image}`; - } + const resourcePath = getResourcePath(resource, this.baseUrl); // load the image for this sheet this.add(imageResourceName, resourcePath, loadOptions, function onImageLoad(res) @@ -56,3 +47,14 @@ }); }; } + +export function getResourcePath(resource, baseUrl) +{ + // Prepend url path unless the resource image is a data url + if (resource.isDataUrl) + { + return resource.data.meta.image; + } + + return url.resolve(resource.url.replace(baseUrl, ''), resource.data.meta.image); +} diff --git a/src/mesh/canvas/CanvasMeshRenderer.js b/src/mesh/canvas/CanvasMeshRenderer.js index 297205f..f7899e1 100644 --- a/src/mesh/canvas/CanvasMeshRenderer.js +++ b/src/mesh/canvas/CanvasMeshRenderer.js @@ -134,12 +134,33 @@ const textureWidth = base.width; const textureHeight = base.height; - const u0 = uvs[index0] * base.width; - const u1 = uvs[index1] * base.width; - const u2 = uvs[index2] * base.width; - const v0 = uvs[index0 + 1] * base.height; - const v1 = uvs[index1 + 1] * base.height; - const v2 = uvs[index2 + 1] * base.height; + let u0; + let u1; + let u2; + let v0; + let v1; + let v2; + + if (mesh.uploadUvTransform) + { + const ut = mesh._uvTransform.mapCoord; + + u0 = ((uvs[index0] * ut.a) + (uvs[index0 + 1] * ut.c) + ut.tx) * base.width; + u1 = ((uvs[index1] * ut.a) + (uvs[index1 + 1] * ut.c) + ut.tx) * base.width; + u2 = ((uvs[index2] * ut.a) + (uvs[index2 + 1] * ut.c) + ut.tx) * base.width; + v0 = ((uvs[index0] * ut.b) + (uvs[index0 + 1] * ut.d) + ut.ty) * base.height; + v1 = ((uvs[index1] * ut.b) + (uvs[index1 + 1] * ut.d) + ut.ty) * base.height; + v2 = ((uvs[index2] * ut.b) + (uvs[index2 + 1] * ut.d) + ut.ty) * base.height; + } + else + { + u0 = uvs[index0] * base.width; + u1 = uvs[index1] * base.width; + u2 = uvs[index2] * base.width; + v0 = uvs[index0 + 1] * base.height; + v1 = uvs[index1 + 1] * base.height; + v2 = uvs[index2 + 1] * base.height; + } let x0 = vertices[index0]; let x1 = vertices[index1]; diff --git a/src/mesh/webgl/MeshRenderer.js b/src/mesh/webgl/MeshRenderer.js index 78d24c3..ab99fcd 100644 --- a/src/mesh/webgl/MeshRenderer.js +++ b/src/mesh/webgl/MeshRenderer.js @@ -2,6 +2,8 @@ const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; +const matrixIdentity = core.Matrix.IDENTITY; + /** * WebGL renderer plugin for tiling sprites * @@ -64,6 +66,7 @@ mesh.geometry.glVertexArrayObjects[this.CONTEXT_UID].draw(mesh.drawMode, mesh.size, mesh.start, mesh.geometry.instanceCount); } + /** * draws mesh * @param {PIXI.mesh.RawMesh} mesh mesh instance diff --git a/src/mesh/webgl/mesh.vert b/src/mesh/webgl/mesh.vert index a337aef..acc096c 100644 --- a/src/mesh/webgl/mesh.vert +++ b/src/mesh/webgl/mesh.vert @@ -1,8 +1,9 @@ attribute vec2 aVertexPosition; attribute vec2 aTextureCoord; -uniform mat3 translationMatrix; uniform mat3 projectionMatrix; +uniform mat3 translationMatrix; +uniform mat3 uTransform; varying vec2 vTextureCoord; @@ -10,5 +11,5 @@ { gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - vTextureCoord = aTextureCoord; + vTextureCoord = (uTransform * vec3(aTextureCoord, 1.0)).xy; } diff --git a/src/particles/ParticleContainer.js b/src/particles/ParticleContainer.js index 67796cf..63f7791 100644 --- a/src/particles/ParticleContainer.js +++ b/src/particles/ParticleContainer.js @@ -313,10 +313,10 @@ frame.y * resolution, frame.width * resolution, frame.height * resolution, - positionX * resolution, - positionY * resolution, - finalWidth * resolution, - finalHeight * resolution + positionX * renderer.resolution, + positionY * renderer.resolution, + finalWidth * renderer.resolution, + finalHeight * renderer.resolution ); } } diff --git a/src/prepare/BasePrepare.js b/src/prepare/BasePrepare.js index 09eb5e5..e45c4dc 100644 --- a/src/prepare/BasePrepare.js +++ b/src/prepare/BasePrepare.js @@ -18,6 +18,18 @@ * basic queuing functionality and is extended by {@link PIXI.prepare.WebGLPrepare} and {@link PIXI.prepare.CanvasPrepare} * to provide preparation capabilities specific to their respective renderers. * + * @example + * // Create a sprite + * const sprite = new PIXI.Sprite.fromImage('something.png'); + * + * // Load object into GPU + * app.renderer.plugins.prepare.upload(sprite, () => { + * + * //Texture(s) has been uploaded to GPU + * app.stage.addChild(sprite); + * + * }) + * * @abstract * @class * @memberof PIXI.prepare @@ -100,8 +112,16 @@ this.prepareItems(); }; - this.register(findText, drawText); - this.register(findTextStyle, calculateTextStyle); + // hooks to find the correct texture + this.registerFindHook(findText); + this.registerFindHook(findTextStyle); + this.registerFindHook(findMultipleBaseTextures); + this.registerFindHook(findBaseTexture); + this.registerFindHook(findTexture); + + // upload hooks + this.registerUploadHook(drawText); + this.registerUploadHook(calculateTextStyle); } /** @@ -138,7 +158,7 @@ if (!this.ticking) { this.ticking = true; - SharedTicker.addOnce(this.tick, this); + SharedTicker.addOnce(this.tick, this, core.UPDATE_PRIORITY.UTILITY); } } else if (done) @@ -172,13 +192,16 @@ const item = this.queue[0]; let uploaded = false; - for (let i = 0, len = this.uploadHooks.length; i < len; i++) + if (item && !item._destroyed) { - if (this.uploadHooks[i](this.uploadHookHelper, item)) + for (let i = 0, len = this.uploadHooks.length; i < len; i++) { - this.queue.shift(); - uploaded = true; - break; + if (this.uploadHooks[i](this.uploadHookHelper, item)) + { + this.queue.shift(); + uploaded = true; + break; + } } } @@ -205,26 +228,36 @@ else { // if we are not finished, on the next rAF do this again - SharedTicker.addOnce(this.tick, this); + SharedTicker.addOnce(this.tick, this, core.UPDATE_PRIORITY.UTILITY); } } /** - * Adds hooks for finding and uploading items. + * Adds hooks for finding items. * - * @param {Function} [addHook] - Function call that takes two parameters: `item:*, queue:Array` - function must return `true` if it was able to add item to the queue. - * @param {Function} [uploadHook] - Function call that takes two parameters: `prepare:CanvasPrepare, item:*` and - * function must return `true` if it was able to handle upload of item. - * @return {PIXI.CanvasPrepare} Instance of plugin for chaining. + * @param {Function} addHook - Function call that takes two parameters: `item:*, queue:Array` + * function must return `true` if it was able to add item to the queue. + * @return {PIXI.BasePrepare} Instance of plugin for chaining. */ - register(addHook, uploadHook) + registerFindHook(addHook) { if (addHook) { this.addHooks.push(addHook); } + return this; + } + + /** + * Adds hooks for uploading items. + * + * @param {Function} uploadHook - Function call that takes two parameters: `prepare:CanvasPrepare, item:*` and + * function must return `true` if it was able to handle upload of item. + * @return {PIXI.BasePrepare} Instance of plugin for chaining. + */ + registerUploadHook(uploadHook) + { if (uploadHook) { this.uploadHooks.push(uploadHook); @@ -287,6 +320,88 @@ } /** + * Built-in hook to find multiple textures from objects like AnimatedSprites. + * + * @private + * @param {PIXI.DisplayObject} item - Display object to check + * @param {Array<*>} queue - Collection of items to upload + * @return {boolean} if a PIXI.Texture object was found. + */ +function findMultipleBaseTextures(item, queue) +{ + let result = false; + + // Objects with mutliple textures + if (item && item._textures && item._textures.length) + { + for (let i = 0; i < item._textures.length; i++) + { + if (item._textures[i] instanceof core.Texture) + { + const baseTexture = item._textures[i].baseTexture; + + if (queue.indexOf(baseTexture) === -1) + { + queue.push(baseTexture); + result = true; + } + } + } + } + + return result; +} + +/** + * Built-in hook to find BaseTextures from Sprites. + * + * @private + * @param {PIXI.DisplayObject} item - Display object to check + * @param {Array<*>} queue - Collection of items to upload + * @return {boolean} if a PIXI.Texture object was found. + */ +function findBaseTexture(item, queue) +{ + // Objects with textures, like Sprites/Text + if (item instanceof core.BaseTexture) + { + if (queue.indexOf(item) === -1) + { + queue.push(item); + } + + return true; + } + + return false; +} + +/** + * Built-in hook to find textures from objects. + * + * @private + * @param {PIXI.DisplayObject} item - Display object to check + * @param {Array<*>} queue - Collection of items to upload + * @return {boolean} if a PIXI.Texture object was found. + */ +function findTexture(item, queue) +{ + if (item._texture && item._texture instanceof core.Texture) + { + const texture = item._texture.baseTexture; + + if (queue.indexOf(texture) === -1) + { + queue.push(texture); + } + + return true; + } + + return false; +} + +/** * Built-in hook to draw PIXI.Text to its texture. * * @private diff --git a/src/prepare/canvas/CanvasPrepare.js b/src/prepare/canvas/CanvasPrepare.js index 0224bb5..09c207a 100644 --- a/src/prepare/canvas/CanvasPrepare.js +++ b/src/prepare/canvas/CanvasPrepare.js @@ -43,7 +43,7 @@ this.ctx = this.canvas.getContext('2d'); // Add textures to upload - this.register(findBaseTextures, uploadBaseTextures); + this.registerUploadHook(uploadBaseTextures); } /** @@ -89,39 +89,4 @@ return false; } -/** - * Built-in hook to find textures from Sprites. - * - * @private - * @param {PIXI.DisplayObject} item -Display object to check - * @param {Array<*>} queue - Collection of items to upload - * @return {boolean} if a PIXI.Texture object was found. - */ -function findBaseTextures(item, queue) -{ - // Objects with textures, like Sprites/Text - if (item instanceof core.BaseTexture) - { - if (queue.indexOf(item) === -1) - { - queue.push(item); - } - - return true; - } - else if (item._texture && item._texture instanceof core.Texture) - { - const texture = item._texture.baseTexture; - - if (queue.indexOf(texture) === -1) - { - queue.push(texture); - } - - return true; - } - - return false; -} - core.CanvasRenderer.registerPlugin('prepare', CanvasPrepare); diff --git a/src/prepare/index.js b/src/prepare/index.js index f559c45..e7491ec 100644 --- a/src/prepare/index.js +++ b/src/prepare/index.js @@ -1,4 +1,29 @@ /** + * The prepare namespace provides renderer-specific plugins for pre-rendering DisplayObjects. These plugins are useful for + * asynchronously preparing assets, textures, graphics waiting to be displayed. + * + * Do not instantiate these plugins directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. + * @example + * // Create a new application + * const app = new PIXI.Application(); + * document.body.appendChild(app.view); + * + * // Don't start rendering right away + * app.stop(); + * + * // create a display object + * const rect = new PIXI.Graphics() + * .beginFill(0x00ff00) + * .drawRect(40, 40, 200, 200); + * + * // Add to the stage + * app.stage.addChild(rect); + * + * // Don't start rendering until the graphic is uploaded to the GPU + * app.renderer.plugins.prepare.upload(app.stage, () => { + * app.start(); + * }); * @namespace PIXI.prepare */ export { default as webgl } from './webgl/WebGLPrepare'; diff --git a/src/prepare/webgl/WebGLPrepare.js b/src/prepare/webgl/WebGLPrepare.js index eb7023f..9df3c3f 100644 --- a/src/prepare/webgl/WebGLPrepare.js +++ b/src/prepare/webgl/WebGLPrepare.js @@ -22,12 +22,11 @@ this.uploadHookHelper = this.renderer; // Add textures and graphics to upload - this.register(findBaseTextures, uploadBaseTextures) - .register(findGraphics, uploadGraphics); + this.registerFindHook(findGraphics); + this.registerUploadHook(uploadBaseTextures); + this.registerUploadHook(uploadGraphics); } - } - /** * Built-in hook to upload PIXI.Texture objects to the GPU. * @@ -80,41 +79,6 @@ } /** - * Built-in hook to find textures from Sprites. - * - * @private - * @param {PIXI.DisplayObject} item - Display object to check - * @param {Array<*>} queue - Collection of items to upload - * @return {boolean} if a PIXI.Texture object was found. - */ -function findBaseTextures(item, queue) -{ - // Objects with textures, like Sprites/Text - if (item instanceof core.BaseTexture) - { - if (queue.indexOf(item) === -1) - { - queue.push(item); - } - - return true; - } - else if (item._texture && item._texture instanceof core.Texture) - { - const texture = item._texture.baseTexture; - - if (queue.indexOf(texture) === -1) - { - queue.push(texture); - } - - return true; - } - - return false; -} - -/** * Built-in hook to find graphics. * * @private diff --git a/test/.eslintrc.json b/test/.eslintrc.json index e03220d..ac12110 100644 --- a/test/.eslintrc.json +++ b/test/.eslintrc.json @@ -12,7 +12,8 @@ "sinon": false, "expect": false, "assert": false, - "PIXI": false + "PIXI": false, + "PointerEvent": true }, "rules": { "func-names": 0, diff --git a/test/core/BaseTexture.js b/test/core/BaseTexture.js index 8b93478..bb65068 100644 --- a/test/core/BaseTexture.js +++ b/test/core/BaseTexture.js @@ -14,4 +14,28 @@ expect(baseTexture.imageType).to.be.equals('png'); }); }); + + it('should remove Canvas BaseTexture from cache on destroy', function () + { + const canvas = document.createElement('canvas'); + const texture = PIXI.BaseTexture.fromCanvas(canvas); + const _pixiId = canvas._pixiId; + + expect(PIXI.utils.BaseTextureCache[_pixiId]).to.equal(texture); + texture.destroy(); + expect(PIXI.utils.BaseTextureCache[_pixiId]).to.equal(undefined); + }); + + it('should remove Image BaseTexture from cache on destroy', function () + { + const URL = 'foo.png'; + const NAME = 'bar'; + const image = new Image(); + + const texture = PIXI.Texture.fromLoader(image, URL, NAME); + + expect(PIXI.utils.BaseTextureCache[NAME]).to.equal(texture.baseTexture); + texture.destroy(true); + expect(PIXI.utils.BaseTextureCache[NAME]).to.equal(undefined); + }); }); diff --git a/test/core/Circle.js b/test/core/Circle.js index 183f98f..f621992 100644 --- a/test/core/Circle.js +++ b/test/core/Circle.js @@ -53,6 +53,10 @@ expect(circ1.contains(10, 16)).to.be.false; expect(circ1.contains(11, 15)).to.be.false; expect(circ1.contains(0, 0)).to.be.false; + + const circ2 = new PIXI.Circle(10, 10, 0); + + expect(circ2.contains(10, 10)).to.be.false; }); it('should return framing rectangle', function () diff --git a/test/core/Container.js b/test/core/Container.js index d11be33..cefde54 100644 --- a/test/core/Container.js +++ b/test/core/Container.js @@ -2,30 +2,36 @@ function testAddChild(fn) { - fn(function (container, obj) + return function () { - container.addChild(obj); - }); - fn(function (container, obj) - { - container.addChildAt(obj); - }); + fn(function (container, obj) + { + container.addChild(obj); + }); + fn(function (container, obj) + { + container.addChildAt(obj); + }); + }; } function testRemoveChild(fn) { - fn(function (container, obj) + return function () { - container.removeChild(obj); - }); - fn(function (container, obj) - { - container.removeChildAt(container.children.indexOf(obj)); - }); - fn(function (container, obj) - { - container.removeChildren(container.children.indexOf(obj), container.children.indexOf(obj) + 1); - }); + fn(function (container, obj) + { + container.removeChild(obj); + }); + fn(function (container, obj) + { + container.removeChildAt(container.children.indexOf(obj)); + }); + fn(function (container, obj) + { + container.removeChildren(container.children.indexOf(obj), container.children.indexOf(obj) + 1); + }); + }; } describe('PIXI.Container', function () diff --git a/test/core/Ellipse.js b/test/core/Ellipse.js index f2e8234..3f47442 100644 --- a/test/core/Ellipse.js +++ b/test/core/Ellipse.js @@ -56,6 +56,10 @@ expect(ellipse1.contains(10, 16)).to.be.false; expect(ellipse1.contains(11, 15)).to.be.false; expect(ellipse1.contains(0, 0)).to.be.false; + + const ellipse2 = new PIXI.Ellipse(10, 10, 0, 0); + + expect(ellipse2.contains(10, 10)).to.be.false; }); it('should return framing rectangle', function () diff --git a/test/core/Matrix.js b/test/core/Matrix.js index 9d2694b..76bd57c 100644 --- a/test/core/Matrix.js +++ b/test/core/Matrix.js @@ -107,10 +107,60 @@ expect(m1.ty).to.equal(m2.ty); }); + it('should prepend matrix', function () + { + const m1 = new PIXI.Matrix(); + const m2 = new PIXI.Matrix(); + + m2.set(2, 3, 4, 5, 100, 200); + m1.prepend(m2); + + expect(m1.a).to.equal(m2.a); + expect(m1.b).to.equal(m2.b); + expect(m1.c).to.equal(m2.c); + expect(m1.d).to.equal(m2.d); + expect(m1.tx).to.equal(m2.tx); + expect(m1.ty).to.equal(m2.ty); + + const m3 = new PIXI.Matrix(); + const m4 = new PIXI.Matrix(); + + m3.prepend(m4); + + expect(m3.a).to.equal(m4.a); + expect(m3.b).to.equal(m4.b); + expect(m3.c).to.equal(m4.c); + expect(m3.d).to.equal(m4.d); + expect(m3.tx).to.equal(m4.tx); + expect(m3.ty).to.equal(m4.ty); + }); + it('should get IDENTITY and TEMP_MATRIX', function () { expect(PIXI.Matrix.IDENTITY instanceof PIXI.Matrix).to.be.true; expect(PIXI.Matrix.TEMP_MATRIX instanceof PIXI.Matrix).to.be.true; }); -}); + it('should reset matrix to default when identity() is called', function () + { + const matrix = new PIXI.Matrix(); + + matrix.set(2, 3, 4, 5, 100, 200); + + expect(matrix.a).to.equal(2); + expect(matrix.b).to.equal(3); + expect(matrix.c).to.equal(4); + expect(matrix.d).to.equal(5); + expect(matrix.tx).to.equal(100); + expect(matrix.ty).to.equal(200); + + matrix.identity(); + + expect(matrix.a).to.equal(1); + expect(matrix.b).to.equal(0); + expect(matrix.c).to.equal(0); + expect(matrix.d).to.equal(1); + expect(matrix.tx).to.equal(0); + expect(matrix.ty).to.equal(0); + }); +}); diff --git a/test/core/Spritesheet.js b/test/core/Spritesheet.js index 283adf5..7601a0c 100644 --- a/test/core/Spritesheet.js +++ b/test/core/Spritesheet.js @@ -18,6 +18,7 @@ expect(textures[id]).to.be.an.instanceof(PIXI.Texture); expect(textures[id].width).to.equal(spritesheet.data.frames[id].frame.w / spritesheet.resolution); expect(textures[id].height).to.equal(spritesheet.data.frames[id].frame.h / spritesheet.resolution); + expect(textures[id].textureCacheId).to.equal(id); spritesheet.destroy(true); expect(spritesheet.textures).to.be.null; expect(spritesheet.baseTexture).to.be.null; diff --git a/test/core/Texture.js b/test/core/Texture.js index 8c4ef98..241ec3e 100644 --- a/test/core/Texture.js +++ b/test/core/Texture.js @@ -16,4 +16,20 @@ expect(PIXI.utils.TextureCache[URL]).to.equal(texture); expect(PIXI.utils.BaseTextureCache[URL]).to.equal(texture.baseTexture); }); + + it('should remove Texture from cache on destroy', function () + { + const NAME = 'foo'; + const NAME2 = 'bar'; + const texture = new PIXI.Texture(new PIXI.BaseTexture()); + + PIXI.Texture.addTextureToCache(texture, NAME); + PIXI.Texture.addTextureToCache(texture, NAME2); + expect(texture.textureCacheId).to.equal(NAME); + expect(PIXI.utils.TextureCache[NAME]).to.equal(texture); + expect(PIXI.utils.TextureCache[NAME2]).to.equal(texture); + texture.destroy(); + expect(PIXI.utils.TextureCache[NAME]).to.equal(undefined); + expect(PIXI.utils.TextureCache[NAME2]).to.equal(undefined); + }); }); diff --git a/.travis.yml b/.travis.yml index 3df8b22..14de334 100644 --- a/.travis.yml +++ b/.travis.yml @@ -110,3 +110,43 @@ on: all_branches: true condition: $TRAVIS_TAG + # Deploy config for latest release + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: dist + upload-dir: "release" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: docs + upload-dir: "release/docs" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: coverage + upload-dir: "release/coverage" + on: + all_branches: true + condition: $TRAVIS_TAG \ No newline at end of file diff --git a/README.md b/README.md index d26fadd..3b85f85 100644 --- a/README.md +++ b/README.md @@ -36,22 +36,14 @@ - Wiki: Other misc tutorials and resources are [on the Wiki](https://github.com/pixijs/pixi.js/wiki/Resources). ### Community ### -- Forums: Check out the [forum] (http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow] (http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. +- Forums: Check out the [forum](http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow](http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. - Inspiration: Check out the [gallery](http://www.pixijs.com/gallery) to see some of the amazing things people have created! - Chat: You can join us on [Gitter](https://gitter.im/pixijs/pixi.js) To chat about Pixi. We also now have a Slack channel. If you would like to join it please Send me an email (mat@goodboydigital.com) and I will invite you in. ### Setup ### -It's easy to get started with Pixi.js! Simply grab the pre-built versions from here: - -Release Branch - Nice and stable Pixi.js -- Unminified: [http://pixijs.download/release/pixi.js] -- Minified: [http://pixijs.download/release/pixi.min.js] - -Develop Branch - The bleeding edge version of Pixi.js -- Unminified: [http://pixijs.download/dev/pixi.js] -- Minified: [http://pixijs.download/dev/pixi.min.js] +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. @@ -70,10 +62,10 @@ #### CDN Install (via cdnjs) ```html - + ``` -_Note: `4.2.2` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ +_Note: `4.4.3` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ ### Demos ### @@ -130,7 +122,7 @@ // and the root stage PIXI.Container. var app = new PIXI.Application(); -// The application will create a canvas element for you that you +// The application will create a canvas element for you that you // can then insert into the DOM. document.body.appendChild(app.view); @@ -150,7 +142,7 @@ // Add the bunny to the scene we are building. app.stage.addChild(bunny); - + // Listen for frame updates app.ticker.add(function() { // each frame we spin the bunny around a bit diff --git a/bower.json b/bower.json deleted file mode 100644 index 41b668c..0000000 --- a/bower.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "pixi.js", - "main": "dist/pixi.min.js", - "ignore": [ - "**/.*", - "docs", - "node_modules", - "bower_components", - "test", - "scripts", - "bower.json", - "inch.json" - ], - "dependencies": { - }, - "devDependencies": { - } -} diff --git a/package.json b/package.json index d35cbb2..308469f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.3.5", + "version": "4.4.4", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ @@ -40,11 +40,11 @@ "lib": "babel src --out-dir lib -s", "predocs": "rimraf docs/**", "docs": "jsdoc -c scripts/jsdoc.conf.json -R README.md", - "publish:patch": "npm version patch --no-git-tag-version && npm publish", - "publish:minor": "npm version minor --no-git-tag-version && npm publish", - "publish:major": "npm version major --no-git-tag-version && npm publish", + "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", - "postpublish": "node scripts/release.js" + "postpublish": "git push && git push --tags" }, "files": [ "dist/", @@ -72,7 +72,6 @@ "electron": "^1.4.15", "eslint": "^3.5.0", "floss": "^2.0.1", - "gh-pages": "^0.11.0", "jaguarjs-jsdoc": "^1.0.1", "js-md5": "^0.4.1", "jsdoc": "^3.4.2", diff --git a/scripts/jsdoc.conf.json b/scripts/jsdoc.conf.json index 553e61c..3f52180 100644 --- a/scripts/jsdoc.conf.json +++ b/scripts/jsdoc.conf.json @@ -43,7 +43,7 @@ }, "markdown" : { "parser" : "gfm", - "hardwrap" : true + "hardwrap" : false }, "opts": { "encoding" : "utf8", diff --git a/scripts/release.js b/scripts/release.js deleted file mode 100644 index 156620c..0000000 --- a/scripts/release.js +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env node -'use strict'; - -// Publish script to push releases of the bin files -// the normally are gitignored -const ghpages = require('gh-pages'); -const path = require('path'); -const packageInfo = require(path.join(__dirname, '..', 'package.json')); - -const options = { - src: [ - 'dist/**/*', - 'lib/**/*', - 'src/**/*', - 'scripts/*', - 'scripts/renders/*', - 'test/**/*', - '*.json', - '*.md', - 'LICENSE', - '.eslintrc', - '.editorconfig', - '.travis.yml', - '.babelrc', - ], - dotfiles: true, - branch: 'release', - message: packageInfo.version, - logger: console.log.bind(console), -}; - -ghpages.publish(process.cwd(), options, (err) => -{ - if (err) - { - console.log(err); - process.exit(1); - - return; - } - - process.exit(0); -}); diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index f16bcb1..220427a 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -21,7 +21,7 @@ const DIV_HOOK_ZINDEX = 2; /** - * The Accessibility manager reacreates the ability to tab and and have content read by screen + * The Accessibility manager recreates the ability to tab and and have content read by screen * readers. This is very important as it can possibly help people with disabilities access pixi * content. * diff --git a/src/accessibility/index.js b/src/accessibility/index.js index 568cfb1..0ae108f 100644 --- a/src/accessibility/index.js +++ b/src/accessibility/index.js @@ -1,4 +1,9 @@ /** + * This namespace contains a renderer plugin for interaction accessibility for end-users + * with physical impairments which require screen-renders, keyboard navigation, etc. + * + * Do not instantiate this plugin directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. * @namespace PIXI.accessibility */ export { default as accessibleTarget } from './accessibleTarget'; diff --git a/src/core/Application.js b/src/core/Application.js index 5340578..2fcc729 100644 --- a/src/core/Application.js +++ b/src/core/Application.js @@ -1,6 +1,8 @@ import { autoDetectRenderer } from './autoDetectRenderer'; import Container from './display/Container'; import { shared, Ticker } from './ticker'; +import settings from './settings'; +import { UPDATE_PRIORITY } from './const'; /** * Convenience class to create a new PIXI application. @@ -22,28 +24,52 @@ */ export default class Application { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experience unexplained flickering try setting this to true. - * @param {boolean} [useSharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedLoader=false] - `true` to use PIXI.loaders.shared, `false` to create new Loader. */ - constructor(width, height, options, noWebGL, useSharedTicker = false) + constructor(options, arg2, arg3, arg4, arg5) { + // Support for constructor(width, height, options, noWebGL, useSharedTicker) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + forceCanvas: !!arg4, + sharedTicker: !!arg5, + }, arg3); + } + + /** + * The default options, so we mixin functionality later. + * @member {object} + * @protected + */ + this._options = options = Object.assign({ + sharedTicker: false, + forceCanvas: false, + sharedLoader: false, + }, options); + /** * WebGL renderer if available, otherwise CanvasRenderer * @member {PIXI.WebGLRenderer|PIXI.CanvasRenderer} */ - this.renderer = autoDetectRenderer(width, height, options, noWebGL); + this.renderer = autoDetectRenderer(options); /** * The root display container that's rendered. @@ -63,7 +89,7 @@ * @member {PIXI.ticker.Ticker} * @default PIXI.ticker.shared */ - this.ticker = useSharedTicker ? shared : new Ticker(); + this.ticker = options.sharedTicker ? shared : new Ticker(); // Start the rendering this.start(); @@ -78,7 +104,7 @@ this._ticker = ticker; if (ticker) { - ticker.add(this.render, this); + ticker.add(this.render, this, UPDATE_PRIORITY.LOW); } } get ticker() // eslint-disable-line require-jsdoc @@ -136,13 +162,18 @@ */ destroy(removeView) { - this.stop(); + const oldTicker = this._ticker; + this.ticker = null; + oldTicker.destroy(); + this.stage.destroy(); this.stage = null; this.renderer.destroy(removeView); this.renderer = null; + + this._options = null; } } diff --git a/src/core/autoDetectRenderer.js b/src/core/autoDetectRenderer.js index 8960425..9ec0364 100644 --- a/src/core/autoDetectRenderer.js +++ b/src/core/autoDetectRenderer.js @@ -2,6 +2,7 @@ import CanvasRenderer from './renderers/canvas/CanvasRenderer'; import WebGLRenderer from './renderers/webgl/WebGLRenderer'; +// eslint-disable-next-line valid-jsdoc /** * This helper function will automatically detect which renderer you should be using. * WebGL is the preferred renderer as it is a lot faster. If webGL is not supported by @@ -9,24 +10,32 @@ * * @memberof PIXI * @function autoDetectRenderer - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @return {PIXI.WebGLRenderer|PIXI.CanvasRenderer} Returns WebGL renderer if available, otherwise CanvasRenderer */ -export function autoDetectRenderer(width = 800, height = 600, options, noWebGL) +export function autoDetectRenderer(options, arg1, arg2, arg3) { - if (!noWebGL && utils.isWebGLSupported()) + // Backward-compatible support for noWebGL option + let forceCanvas = options && options.forceCanvas; + + if (arg3 !== undefined) { - return new WebGLRenderer(width, height, options); + forceCanvas = arg3; } - return new CanvasRenderer(width, height, options); + if (!forceCanvas && utils.isWebGLSupported()) + { + return new WebGLRenderer(options, arg1, arg2); + } + + return new CanvasRenderer(options, arg1, arg2); } diff --git a/src/core/const.js b/src/core/const.js index b9e99ae..2c68dee 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -341,3 +341,27 @@ LINEAR_VERTICAL: 0, LINEAR_HORIZONTAL: 1, }; + +/** + * Represents the update priorities used by internal PIXI classes when registered with + * the {@link PIXI.ticker.Ticker} object. Higher priority items are updated first and lower + * priority items, such as render, should go later. + * + * @static + * @constant + * @name UPDATE_PRIORITY + * @memberof PIXI + * @type {object} + * @property {number} INTERACTION=50 Highest priority, used for {@link PIXI.interaction.InteractionManager} + * @property {number} HIGH=25 High priority updating, {@link PIXI.VideoBaseTexture} and {@link PIXI.extras.AnimatedSprite} + * @property {number} NORMAL=0 Default priority for ticker events, see {@link PIXI.ticker.Ticker#add}. + * @property {number} LOW=-25 Low priority used for {@link PIXI.Application} rendering. + * @property {number} UTILITY=-50 Lowest priority used for {@link PIXI.prepare.BasePrepare} utility. + */ +export const UPDATE_PRIORITY = { + INTERACTION: 50, + HIGH: 25, + NORMAL: 0, + LOW: -25, + UTILITY: -50, +}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 4e6c77d..decb42a 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -13,7 +13,6 @@ * * @class * @extends EventEmitter - * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ export default class DisplayObject extends EventEmitter @@ -113,6 +112,29 @@ * @private */ this._mask = null; + + /** + * If the object has been destroyed via destroy(). If true, it should not be used. + * + * @member {boolean} + * @private + * @readonly + */ + this._destroyed = false; + + /** + * Fired when this DisplayObject is added to a Container. + * + * @event PIXI.DisplayObject#added + * @param {PIXI.Container} container - The container added to. + */ + + /** + * Fired when this DisplayObject is removed from a Container. + * + * @event PIXI.DisplayObject#removed + * @param {PIXI.Container} container - The container removed from. + */ } /** @@ -403,6 +425,8 @@ this.interactive = false; this.interactiveChildren = false; + + this._destroyed = true; } /** diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index aafdfe2..f787c4a 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -792,8 +792,8 @@ sprite.worldAlpha = this.worldAlpha * sprite.alpha; sprite.blendMode = this.blendMode; - sprite.texture._frame.width = rect.width; - sprite.texture._frame.height = rect.height; + sprite._texture._frame.width = rect.width; + sprite._texture._frame.height = rect.height; sprite.transform.worldTransform = this.transform.worldTransform; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index a24c775..b9f192d 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -19,11 +19,12 @@ */ export default class SystemRenderer extends EventEmitter { + // eslint-disable-next-line valid-jsdoc /** * @param {string} system - The name of the system this renderer is for. - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -37,27 +38,31 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(system, screenWidth, screenHeight, options) + constructor(system, options, arg2, arg3) { super(); sayHello(system); - // prepare options - if (options) + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') { - for (const i in settings.RENDER_OPTIONS) - { - if (typeof options[i] === 'undefined') - { - options[i] = settings.RENDER_OPTIONS[i]; - } - } + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); } - else - { - options = settings.RENDER_OPTIONS; - } + + // Add the default render options + options = Object.assign({}, settings.RENDER_OPTIONS, options); + + /** + * The supplied constructor options. + * + * @member {Object} + * @readOnly + */ + this.options = options; /** * The type of the renderer. @@ -75,7 +80,7 @@ * * @member {PIXI.Rectangle} */ - this.screen = new Rectangle(0, 0, screenWidth || 800, screenHeight || 600); + this.screen = new Rectangle(0, 0, options.width, options.height); /** * The canvas element that everything is drawn to @@ -279,6 +284,8 @@ this.blendModes = null; + this.options = null; + this.preserveDrawingBuffer = false; this.clearBeforeRender = false; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index e84e49c..691caf0 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -17,10 +17,11 @@ */ export default class CanvasRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -34,9 +35,9 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('Canvas', screenWidth, screenHeight, options); + super('Canvas', options, arg2, arg3); this.type = RENDERER_TYPE.CANVAS; @@ -96,7 +97,19 @@ this.context = null; this.renderingToScreen = false; - this.resize(screenWidth, screenHeight); + this.resize(this.options.width, this.options.height); + + /** + * Fired after rendering finishes. + * + * @event PIXI.CanvasRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.CanvasRenderer#prerender + */ } /** @@ -299,4 +312,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.CanvasRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.CanvasExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.CanvasPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.CanvasRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index e1cf7cd..f6fe123 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -31,11 +31,12 @@ */ export default class WebGLRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** * - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -55,9 +56,9 @@ * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('WebGL', screenWidth, screenHeight, options); + super('WebGL', options, arg2, arg3); /** * The type of this renderer as a standardised const @@ -89,12 +90,6 @@ }; - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ this._backgroundColorRgba[3] = this.transparent ? 0 : 1; this.globalUniforms = new UniformGroup({ @@ -117,6 +112,13 @@ this.initPlugins(); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ if(options.context) { this.context.initFromContext(options.context); @@ -169,6 +171,25 @@ } return this; + + /** + * Fired after rendering finishes. + * + * @event PIXI.WebGLRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.WebGLRenderer#prerender + */ + + /** + * Fired when the WebGL context is set. + * + * @event PIXI.WebGLRenderer#context + * @param {WebGLRenderingContext} gl - WebGL context. + */ } /** @@ -295,4 +316,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.WebGLRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.WebGLRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(WebGLRenderer); diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index 4eb6128..fa3d883 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -47,6 +47,14 @@ * @member {boolean} */ this.enabled = true; + + /** + * If enabled, pixi will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; } /** diff --git a/src/core/renderers/webgl/systems/ContextSystem.js b/src/core/renderers/webgl/systems/ContextSystem.js index ff958c7..8df85ea 100644 --- a/src/core/renderers/webgl/systems/ContextSystem.js +++ b/src/core/renderers/webgl/systems/ContextSystem.js @@ -75,7 +75,8 @@ */ createContext(canvas, options) { - var gl = canvas.getContext('webgl', options) || + var gl = //canvas.getContext('webgl2', options) || + canvas.getContext('webgl', options) || canvas.getContext('experimental-webgl', options); if (!gl) diff --git a/src/core/renderers/webgl/systems/FilterSystem.js b/src/core/renderers/webgl/systems/FilterSystem.js index f47335d..37e28d2 100644 --- a/src/core/renderers/webgl/systems/FilterSystem.js +++ b/src/core/renderers/webgl/systems/FilterSystem.js @@ -111,7 +111,7 @@ // TODO we should fit the rect around the transform.. } - else + else if (filters[0].autoFit) { sourceFrame.fit(filterData.stack[0].destinationFrame); } diff --git a/src/core/renderers/webgl/systems/shader/shader/mapType.js b/src/core/renderers/webgl/systems/shader/shader/mapType.js index 581dace..917538a 100644 --- a/src/core/renderers/webgl/systems/shader/shader/mapType.js +++ b/src/core/renderers/webgl/systems/shader/shader/mapType.js @@ -15,7 +15,7 @@ } } - return GL_TABLE[type]; + return GL_TABLE[type]; }; var GL_TABLE = null; diff --git a/src/core/settings.js b/src/core/settings.js index 18c5877..38ef45c 100644 --- a/src/core/settings.js +++ b/src/core/settings.js @@ -2,6 +2,15 @@ import canUploadSameBuffer from './utils/canUploadSameBuffer'; /** + * User's customizable globals for overriding the default PIXI settings, such + * as a renderer's default resolution, framerate, float percision, etc. + * @example + * // Use the native window resolution as the default resolution + * // will support high-density displays when rendering + * PIXI.settings.RESOLUTION = window.devicePixelRatio. + * + * // Disable interpolation when scaling, will make texture be pixelated + * PIXI.settings.SCALE_MODE = PIXI.SCALE_MODES.NEAREST; * @namespace PIXI.settings */ export default { @@ -101,6 +110,9 @@ * @property {boolean} clearBeforeRender=true * @property {boolean} preserveDrawingBuffer=false * @property {boolean} roundPixels=false + * @property {number} width=800 + * @property {number} height=600 + * @property {boolean} legacy=false */ RENDER_OPTIONS: { view: null, @@ -112,6 +124,9 @@ clearBeforeRender: true, preserveDrawingBuffer: false, roundPixels: false, + width: 800, + height: 600, + legacy: false, }, /** diff --git a/src/core/shader/Program.js b/src/core/shader/Program.js index c0b538b..07b4398 100644 --- a/src/core/shader/Program.js +++ b/src/core/shader/Program.js @@ -1,6 +1,6 @@ import extractUniformsFromSrc from './extractUniformsFromSrc'; import generateUniformsSync from './generateUniformsSync'; -import glCore from 'pixi-gl-core'; +import shaderUtils from '../renderers/webgl/systems/shader/shader'; import { ProgramCache } from '../utils'; import getTestContext from '../utils/getTestContext'; @@ -64,14 +64,17 @@ } else { - vertexSrc = glCore.shader.setPrecision(vertexSrc, 'mediump'); - fragmentSrc = glCore.shader.setPrecision(fragmentSrc, 'mediump'); + vertexSrc = shaderUtils.setPrecision(vertexSrc, 'mediump'); + fragmentSrc = shaderUtils.setPrecision(fragmentSrc, 'mediump'); - const program = glCore.shader.compileProgram(gl, vertexSrc, fragmentSrc); + const program = shaderUtils.compileProgram(gl, vertexSrc, fragmentSrc); + console.log("<<<<<<>>><<"); this.attributeData = this.getAttributeData(program, gl); this.uniformData = this.getUniformData(program, gl); + console.log(this.uniformData); + //gl.deleteProgram(program); } } @@ -95,13 +98,13 @@ for (let i = 0; i < totalAttributes; i++) { const attribData = gl.getActiveAttrib(program, i); - const type = glCore.shader.mapType(gl, attribData.type); + const type = shaderUtils.mapType(gl, attribData.type); /*eslint-disable */ const data = { type: type, name: attribData.name, - size: glCore.shader.mapSize(type), + size: shaderUtils.mapSize(type), location: 0, }; /*eslint-enable */ @@ -137,13 +140,14 @@ // TODO expose this as a prop? // const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); - const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); + //const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); for (let i = 0; i < totalUniforms; i++) { const uniformData = gl.getActiveUniform(program, i); const name = uniformData.name.replace(/\[.*?\]/, ''); - const type = glCore.shader.mapType(gl, uniformData.type); + const type = shaderUtils.mapType(gl, uniformData.type); + console.log('mapping ' + uniformData.type + ' to ' + type); // if (!name.match(maskRegex)) { @@ -151,7 +155,7 @@ uniforms[name] = { type: type, size: uniformData.size, - value: glCore.shader.defaultValue(type, uniformData.size), + value: shaderUtils.defaultValue(type, uniformData.size), }; /*eslint-enable */ } diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index e387e99..b57d095 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -147,12 +147,12 @@ // so if _width is 0 then width was not set.. if (this._width) { - this.scale.x = sign(this.scale.x) * this._width / this.texture.orig.width; + this.scale.x = sign(this.scale.x) * this._width / this._texture.orig.width; } if (this._height) { - this.scale.y = sign(this.scale.y) * this._height / this.texture.orig.height; + this.scale.y = sign(this.scale.y) * this._height / this._texture.orig.height; } } diff --git a/src/core/sprites/canvas/CanvasTinter.js b/src/core/sprites/canvas/CanvasTinter.js index 27e5e0c..fd9de5d 100644 --- a/src/core/sprites/canvas/CanvasTinter.js +++ b/src/core/sprites/canvas/CanvasTinter.js @@ -18,7 +18,7 @@ */ getTintedTexture: (sprite, color) => { - const texture = sprite.texture; + const texture = sprite._texture; color = CanvasTinter.roundColor(color); diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 5a4edda..8b9b797 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -344,6 +344,7 @@ const texture = this._texture; const style = this._style; + const padding = style.trim ? 0 : style.padding; texture.baseTexture.valid = true; texture.baseTexture.resolution = this.resolution; @@ -353,11 +354,11 @@ texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - texture.trim.x = -style.padding; - texture.trim.y = -style.padding; + texture.trim.x = -padding; + texture.trim.y = -padding; - texture.orig.width = texture._frame.width - (style.padding * 2); - texture.orig.height = texture._frame.height - (style.padding * 2); + texture.orig.width = texture._frame.width - (padding * 2); + texture.orig.height = texture._frame.height - (padding * 2); // call sprite onTextureUpdate to update scale if _width or _height were set this._onTextureUpdate(); diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index 853da3d..8a9c34b 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -114,6 +114,38 @@ this.cacheId = null; this.validate(); + + /** + * Fired when a not-immediately-available source finishes loading. + * + * @protected + * @event PIXI.BaseTexture#loaded + * @param {PIXI.BaseTexture} baseTexture - Resource loaded. + */ + + /** + * Fired when a not-immediately-available source fails to load. + * + * @protected + * @event PIXI.BaseTexture#error + * @param {PIXI.BaseTexture} baseTexture - Resource errored. + */ + + /** + * Fired when BaseTexture is updated. + * + * @protected + * @event PIXI.BaseTexture#update + * @param {PIXI.BaseTexture} baseTexture - Instance of texture being updated. + */ + + /** + * Fired when BaseTexture is destroyed. + * + * @protected + * @event PIXI.BaseTexture#dispose + * @param {PIXI.BaseTexture} baseTexture - Instance of texture being destroyed. + */ } setResource(resource) diff --git a/src/core/textures/Spritesheet.js b/src/core/textures/Spritesheet.js index d469c62..9b24f27 100644 --- a/src/core/textures/Spritesheet.js +++ b/src/core/textures/Spritesheet.js @@ -208,6 +208,7 @@ // lets also add the frame to pixi's global cache for fromFrame and fromImage functions TextureCache[i] = this.textures[i]; + this.textures[i].textureCacheId = i; } frameIndex++; diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 0a2c379..2213087 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -147,9 +147,9 @@ /** * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. * - * @event update - * @memberof PIXI.Texture# + * @event PIXI.Texture#update * @protected + * @param {PIXI.Texture} texture - Instance of texture being updated. */ this._updateID = 0; @@ -159,6 +159,15 @@ * @type {Object} */ this.transform = null; + + /** + * The id under which this Texture has been added to the texture cache. This is + * automatically set in certain cases, but may not always be accurate, particularly if + * the texture is in the cache under multiple ids. + * + * @member {string} + */ + this.textureCacheId = null; } /** @@ -225,11 +234,17 @@ this._uvs = null; this.trim = null; this.orig = null; + this.textureCacheId = null; this.valid = false; - this.off('dispose', this.dispose, this); - this.off('update', this.update, this); + for (const prop in TextureCache) + { + if (TextureCache[prop] === this) + { + delete TextureCache[prop]; + } + } } /** @@ -315,14 +330,15 @@ */ static fromLoader(source, imageUrl, name) { - // console.log('added from loader...') + // console.log('added from loader...') const resource = new ImageResource(source);//.from(imageUrl, crossorigin);// document.createElement('img'); - // console.log('base resource ' + resource.width); + // console.log('base resource ' + resource.width); const baseTexture = new BaseTexture(resource, settings.SCALE_MODE, getResolutionOfUrl(imageUrl)); + /// console.log('base width ' + baseTexture.width); const texture = new Texture(baseTexture); @@ -335,6 +351,7 @@ // lets also add the frame to pixi's global cache for fromFrame and fromImage fucntions BaseTextureCache[name] = baseTexture; TextureCache[name] = texture; + texture.textureCacheId = name; // also add references by url if they are different. if (name !== imageUrl) @@ -355,6 +372,10 @@ */ static addTextureToCache(texture, id) { + if (!texture.textureCacheId) + { + texture.textureCacheId = id; + } TextureCache[id] = texture; } @@ -495,6 +516,7 @@ */ Texture.EMPTY = new Texture(new BaseTexture()); removeAllHandlers(Texture.EMPTY); +removeAllHandlers(Texture.EMPTY.baseTexture); /** * A white texture of 10x10 size, used for graphics and other things @@ -505,3 +527,4 @@ */ Texture.WHITE = createWhiteTexture(); removeAllHandlers(Texture.WHITE); +removeAllHandlers(Texture.WHITE.baseTexture); diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index 59eb9fd..a9b9f51 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -1,6 +1,7 @@ import BaseTexture from './BaseTexture'; import { uid, BaseTextureCache } from '../utils'; -import * as ticker from '../ticker'; +import { shared } from '../ticker'; +import { UPDATE_PRIORITY } from '../const'; /** * A texture of a [playing] Video. @@ -125,7 +126,7 @@ if (!this._isAutoUpdating && this.autoUpdate) { - ticker.shared.add(this.update, this); + shared.add(this.update, this, UPDATE_PRIORITY.HIGH); this._isAutoUpdating = true; } } @@ -139,7 +140,7 @@ { if (this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); this._isAutoUpdating = false; } } @@ -187,7 +188,7 @@ { if (this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); } if (this.source && this.source._pixiId) @@ -281,12 +282,12 @@ if (!this._autoUpdate && this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); this._isAutoUpdating = false; } else if (this._autoUpdate && !this._isAutoUpdating) { - ticker.shared.add(this.update, this); + shared.add(this.update, this, UPDATE_PRIORITY.HIGH); this._isAutoUpdating = true; } } diff --git a/src/core/ticker/Ticker.js b/src/core/ticker/Ticker.js index 17a1517..40df138 100644 --- a/src/core/ticker/Ticker.js +++ b/src/core/ticker/Ticker.js @@ -1,12 +1,10 @@ import settings from '../settings'; -import EventEmitter from 'eventemitter3'; - -// Internal event used by composed emitter -const TICK = 'tick'; +import { UPDATE_PRIORITY } from '../const'; +import TickerListener from './TickerListener'; /** * A Ticker class that runs an update loop that other objects listen to. - * This class is composed around an EventEmitter object to add listeners + * This class is composed around listeners * meant for execution on the next requested animation frame. * Animation frames are requested only when necessary, * e.g. When the ticker is started and the emitter has listeners. @@ -22,10 +20,11 @@ constructor() { /** - * Internal emitter used to fire 'tick' event + * The first listener. All new listeners added are chained on this. * @private + * @type {TickerListener} */ - this._emitter = new EventEmitter(); + this._head = new TickerListener(null, null, Infinity); /** * Internal current frame request ID @@ -131,7 +130,7 @@ // Invoke listeners now this.update(time); // Listener side effects may have modified ticker state. - if (this.started && this._requestId === null && this._emitter.listeners(TICK, true)) + if (this.started && this._requestId === null && this._head.next) { this._requestId = requestAnimationFrame(this._tick); } @@ -148,7 +147,7 @@ */ _requestIfNeeded() { - if (this._requestId === null && this._emitter.listeners(TICK, true)) + if (this._requestId === null && this._head.next) { // ensure callbacks get correct delta this.lastTime = performance.now(); @@ -193,35 +192,72 @@ } /** - * Calls {@link module:eventemitter3.EventEmitter#on} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. + * Register a handler for tick events. Calls continuously unless + * it is removed or the ticker is stopped. * * @param {Function} fn - The listener function to be added for updates * @param {Function} [context] - The listener context + * @param {number} [priority=PIXI.UPDATE_PRIORITY.NORMAL] - The priority for emitting * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - add(fn, context) + add(fn, context, priority = UPDATE_PRIORITY.NORMAL) { - this._emitter.on(TICK, fn, context); - - this._startIfPossible(); - - return this; + return this._addListener(new TickerListener(fn, context, priority)); } /** - * Calls {@link module:eventemitter3.EventEmitter#once} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. + * Add a handler for the tick event which is only execute once. * * @param {Function} fn - The listener function to be added for one update * @param {Function} [context] - The listener context + * @param {number} [priority=PIXI.UPDATE_PRIORITY.NORMAL] - The priority for emitting * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - addOnce(fn, context) + addOnce(fn, context, priority = UPDATE_PRIORITY.NORMAL) { - this._emitter.once(TICK, fn, context); + return this._addListener(new TickerListener(fn, context, priority, true)); + } + + /** + * Internally adds the event handler so that it can be sorted by priority. + * Priority allows certain handler (user, AnimatedSprite, Interaction) to be run + * before the rendering. + * + * @private + * @param {TickerListener} listener - Current listener being added. + * @returns {PIXI.ticker.Ticker} This instance of a ticker + */ + _addListener(listener) + { + // For attaching to head + let current = this._head.next; + let previous = this._head; + + // Add the first item + if (!current) + { + listener.connect(previous); + } + else + { + // Go from highest to lowest priority + while (current) + { + if (listener.priority >= current.priority) + { + listener.connect(previous); + break; + } + previous = current; + current = current.next; + } + + // Not yet connected + if (!listener.previous) + { + listener.connect(previous); + } + } this._startIfPossible(); @@ -229,19 +265,33 @@ } /** - * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. - * It checks if the emitter has listeners for 'tick' event. - * If it does, then it cancels the animation frame. + * Removes any handlers matching the function and context parameters. + * If no handlers are left after removing, then it cancels the animation frame. * - * @param {Function} [fn] - The listener function to be removed + * @param {Function} fn - The listener function to be removed * @param {Function} [context] - The listener context to be removed * @returns {PIXI.ticker.Ticker} This instance of a ticker */ remove(fn, context) { - this._emitter.off(TICK, fn, context); + let listener = this._head.next; - if (!this._emitter.listeners(TICK, true)) + while (listener) + { + // We found a match, lets remove it + // no break to delete all possible matches + // incase a listener was added 2+ times + if (listener.match(fn, context)) + { + listener = listener.destroy(); + } + else + { + listener = listener.next; + } + } + + if (!this._head.next) { this._cancelIfNeeded(); } @@ -276,6 +326,25 @@ } /** + * Destroy the ticker and don't use after this. Calling + * this method removes all references to internal events. + */ + destroy() + { + this.stop(); + + let listener = this._head.next; + + while (listener) + { + listener = listener.destroy(true); + } + + this._head.destroy(); + this._head = null; + } + + /** * Triggers an update. An update entails setting the * current {@link PIXI.ticker.Ticker#elapsedMS}, * the current {@link PIXI.ticker.Ticker#deltaTime}, @@ -320,8 +389,22 @@ this.deltaTime = elapsedMS * settings.TARGET_FPMS * this.speed; + // Cache a local reference, in-case ticker is destroyed + // during the emit, we can still check for head.next + const head = this._head; + // Invoke listeners added to internal emitter - this._emitter.emit(TICK, this.deltaTime); + let listener = head.next; + + while (listener) + { + listener = listener.emit(this.deltaTime); + } + + if (!head.next) + { + this._cancelIfNeeded(); + } } else { diff --git a/src/core/ticker/TickerListener.js b/src/core/ticker/TickerListener.js new file mode 100644 index 0000000..2bedb34 --- /dev/null +++ b/src/core/ticker/TickerListener.js @@ -0,0 +1,158 @@ +/** + * Internal class for handling the priority sorting of ticker handlers. + * + * @private + * @class + * @memberof PIXI.ticker + */ +export default class TickerListener +{ + /** + * Constructor + * + * @param {Function} fn - The listener function to be added for one update + * @param {Function} [context=null] - The listener context + * @param {number} [priority=0] - The priority for emitting + * @param {boolean} [once=false] - If the handler should fire once + */ + constructor(fn, context = null, priority = 0, once = false) + { + /** + * The handler function to execute. + * @member {Function} + */ + this.fn = fn; + + /** + * The calling to execute. + * @member {Function} + */ + this.context = context; + + /** + * The current priority. + * @member {number} + */ + this.priority = priority; + + /** + * If this should only execute once. + * @member {boolean} + */ + this.once = once; + + /** + * The next item in chain. + * @member {TickerListener} + */ + this.next = null; + + /** + * The previous item in chain. + * @member {TickerListener} + */ + this.previous = null; + + /** + * `true` if this listener has been destroyed already. + * @member {boolean} + * @private + */ + this._destroyed = false; + } + + /** + * Simple compare function to figure out if a function and context match. + * + * @param {Function} fn - The listener function to be added for one update + * @param {Function} context - The listener context + * @return {boolean} `true` if the listener match the arguments + */ + match(fn, context) + { + context = context || null; + + return this.fn === fn && this.context === context; + } + + /** + * Emit by calling the current function. + * @param {number} deltaTime - time since the last emit. + * @return {TickerListener} Next ticker + */ + emit(deltaTime) + { + if (this.context) + { + this.fn.call(this.context, deltaTime); + } + else + { + this.fn(deltaTime); + } + + if (this.once) + { + this.destroy(); + } + + const redirect = this.next; + + // Soft-destroying should remove + // the next reference + if (this._destroyed) + { + this.next = null; + } + + return redirect; + } + + /** + * Connect to the list. + * @param {TickerListener} previous - Input node, previous listener + */ + connect(previous) + { + this.previous = previous; + if (previous.next) + { + previous.next.previous = this; + } + this.next = previous.next; + previous.next = this; + } + + /** + * Destroy and don't use after this. + * @param {boolean} [hard = false] `true` to remove the `next` reference, this + * is considered a hard destroy. Soft destroy maintains the next reference. + * @return {TickerListener} The listener to redirect while emitting or removing. + */ + destroy(hard = false) + { + this._destroyed = true; + this.fn = null; + this.context = null; + + // Disconnect, hook up next and previous + if (this.previous) + { + this.previous.next = this.next; + } + + if (this.next) + { + this.next.previous = this.previous; + } + + // Redirect to the next item + const redirect = this.previous; + + // Remove references + this.next = hard ? null : redirect; + this.previous = null; + + return redirect; + } +} diff --git a/src/core/ticker/index.js b/src/core/ticker/index.js index 4b3017c..c3020d4 100644 --- a/src/core/ticker/index.js +++ b/src/core/ticker/index.js @@ -45,8 +45,25 @@ const shared = new Ticker(); shared.autoStart = true; +shared.destroy = () => +{ + // protect destroying shared ticker + // this is used by other internal systems + // like AnimatedSprite and InteractionManager +}; /** + * This namespace contains an API for interacting with PIXI's internal global update loop. + * + * This ticker is used for rendering, {@link PIXI.extras.AnimatedSprite AnimatedSprite}, + * {@link PIXI.interaction.InteractionManager InteractionManager} and many other time-based PIXI systems. + * @example + * const ticker = new PIXI.ticker.Ticker(); + * ticker.stop(); + * ticker.add((deltaTime) => { + * // do something every frame + * }); + * ticker.start(); * @namespace PIXI.ticker */ export { shared, Ticker }; diff --git a/src/core/utils/index.js b/src/core/utils/index.js index be44750..7441f7b 100644 --- a/src/core/utils/index.js +++ b/src/core/utils/index.js @@ -9,6 +9,21 @@ let saidHello = false; /** + * Generalized convenience utilities for PIXI. + * @example + * // Extend PIXI's internal Event Emitter. + * class MyEmitter extends PIXI.utils.EventEmitter { + * constructor() { + * super(); + * console.log("Emitter created!"); + * } + * } + * + * // Get info on current device + * console.log(PIXI.utils.isMobile); + * + * // Convert hex color to string + * console.log(PIXI.utils.hex2string(0xff00ff)); // returns: "#ff00ff" * @namespace PIXI.utils */ export { diff --git a/src/deprecation.js b/src/deprecation.js index b9c73b3..33daeec 100644 --- a/src/deprecation.js +++ b/src/deprecation.js @@ -918,6 +918,35 @@ }); /** + * @method + * @name PIXI.prepare.BasePrepare#register + * @see PIXI.prepare.BasePrepare#registerFindHook + * @deprecated since version 4.4.2 + * @param {Function} [addHook] - Function call that takes two parameters: `item:*, queue:Array` + * function must return `true` if it was able to add item to the queue. + * @param {Function} [uploadHook] - Function call that takes two parameters: `prepare:CanvasPrepare, item:*` and + * function must return `true` if it was able to handle upload of item. + * @return {PIXI.BasePrepare} Instance of plugin for chaining. + */ +prepare.BasePrepare.prototype.register = function register(addHook, uploadHook) +{ + warn('renderer.plugins.prepare.register is now deprecated, ' + + 'please use renderer.plugins.prepare.registerFindHook & renderer.plugins.prepare.registerUploadHook'); + + if (addHook) + { + this.registerFindHook(addHook); + } + + if (uploadHook) + { + this.registerUploadHook(uploadHook); + } + + return this; +}; + +/** * The number of graphics or textures to upload to the GPU. * * @name PIXI.prepare.canvas.UPLOADS_PER_FRAME @@ -969,67 +998,73 @@ }, }); -Object.defineProperties(loaders.Resource.prototype, { - isJson: { - get() - { - warn('The isJson property is deprecated, please use `resource.type === Resource.TYPE.JSON`.'); +if (loaders.Loader) +{ + const Resource = loaders.Resource; + const Loader = loaders.Loader; - return this.type === loaders.Loader.Resource.TYPE.JSON; + Object.defineProperties(Resource.prototype, { + isJson: { + get() + { + warn('The isJson property is deprecated, please use `resource.type === Resource.TYPE.JSON`.'); + + return this.type === Resource.TYPE.JSON; + }, }, - }, - isXml: { - get() - { - warn('The isXml property is deprecated, please use `resource.type === Resource.TYPE.XML`.'); + isXml: { + get() + { + warn('The isXml property is deprecated, please use `resource.type === Resource.TYPE.XML`.'); - return this.type === loaders.Loader.Resource.TYPE.XML; + return this.type === Resource.TYPE.XML; + }, }, - }, - isImage: { - get() - { - warn('The isImage property is deprecated, please use `resource.type === Resource.TYPE.IMAGE`.'); + isImage: { + get() + { + warn('The isImage property is deprecated, please use `resource.type === Resource.TYPE.IMAGE`.'); - return this.type === loaders.Loader.Resource.TYPE.IMAGE; + return this.type === Resource.TYPE.IMAGE; + }, }, - }, - isAudio: { - get() - { - warn('The isAudio property is deprecated, please use `resource.type === Resource.TYPE.AUDIO`.'); + isAudio: { + get() + { + warn('The isAudio property is deprecated, please use `resource.type === Resource.TYPE.AUDIO`.'); - return this.type === loaders.Loader.Resource.TYPE.AUDIO; + return this.type === Resource.TYPE.AUDIO; + }, }, - }, - isVideo: { - get() - { - warn('The isVideo property is deprecated, please use `resource.type === Resource.TYPE.VIDEO`.'); + isVideo: { + get() + { + warn('The isVideo property is deprecated, please use `resource.type === Resource.TYPE.VIDEO`.'); - return this.type === loaders.Loader.Resource.TYPE.VIDEO; + return this.type === Resource.TYPE.VIDEO; + }, }, - }, -}); + }); -Object.defineProperties(loaders.Loader.prototype, { - before: { - get() - { - warn('The before() method is deprecated, please use pre().'); + Object.defineProperties(Loader.prototype, { + before: { + get() + { + warn('The before() method is deprecated, please use pre().'); - return this.pre; + return this.pre; + }, }, - }, - after: { - get() - { - warn('The after() method is deprecated, please use use().'); + after: { + get() + { + warn('The after() method is deprecated, please use use().'); - return this.use; + return this.use; + }, }, - }, -}); + }); +} /** * @name PIXI.interaction.interactiveTarget#defaultCursor diff --git a/src/extract/canvas/CanvasExtract.js b/src/extract/canvas/CanvasExtract.js index 941ca05..3ac5aee 100644 --- a/src/extract/canvas/CanvasExtract.js +++ b/src/extract/canvas/CanvasExtract.js @@ -8,7 +8,7 @@ * An instance of this class is automatically created by default, and can be found at renderer.plugins.extract * * @class - * @memberof PIXI + * @memberof PIXI.extract */ export default class CanvasExtract { @@ -21,9 +21,9 @@ /** * Collection of methods for extracting data (image, pixels, etc.) from a display object or render texture * - * @member {PIXI.CanvasExtract} extract + * @member {PIXI.extract.CanvasExtract} extract * @memberof PIXI.CanvasRenderer# - * @see PIXI.CanvasExtract + * @see PIXI.extract.CanvasExtract */ renderer.extract = this; } diff --git a/src/extract/index.js b/src/extract/index.js index 0f2170d..e9478c1 100644 --- a/src/extract/index.js +++ b/src/extract/index.js @@ -1,2 +1,22 @@ +/** + * This namespace provides renderer-specific plugins for exporting content from a renderer. + * For instance, these plugins can be used for saving an Image, Canvas element or for exporting the raw image data (pixels). + * + * Do not instantiate these plugins directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. + * @example + * // Create a new app (will auto-add extract plugin to renderer) + * const app = new PIXI.Application(); + * + * // Draw a red circle + * const graphics = new PIXI.Graphics() + * .beginFill(0xFF0000) + * .drawCircle(0, 0, 50); + * + * // Render the graphics as an HTMLImageElement + * const image = app.renderer.plugins.extract.image(graphics); + * document.body.appendChild(image); + * @namespace PIXI.extract + */ export { default as webgl } from './webgl/WebGLExtract'; export { default as canvas } from './canvas/CanvasExtract'; diff --git a/src/extract/webgl/WebGLExtract.js b/src/extract/webgl/WebGLExtract.js index 60228bb..1c3dd30 100644 --- a/src/extract/webgl/WebGLExtract.js +++ b/src/extract/webgl/WebGLExtract.js @@ -9,7 +9,7 @@ * An instance of this class is automatically created by default, and can be found at renderer.plugins.extract * * @class - * @memberof PIXI + * @memberof PIXI.extract */ export default class WebGLExtract { @@ -22,9 +22,9 @@ /** * Collection of methods for extracting data (image, pixels, etc.) from a display object or render texture * - * @member {PIXI.WebGLExtract} extract + * @member {PIXI.extract.WebGLExtract} extract * @memberof PIXI.WebGLRenderer# - * @see PIXI.WebGLExtract + * @see PIXI.extract.WebGLExtract */ renderer.extract = this; } diff --git a/src/extras/AnimatedSprite.js b/src/extras/AnimatedSprite.js index 13b804f..5297778 100644 --- a/src/extras/AnimatedSprite.js +++ b/src/extras/AnimatedSprite.js @@ -137,7 +137,7 @@ this.playing = true; if (this._autoUpdate) { - core.ticker.shared.add(this.update, this); + core.ticker.shared.add(this.update, this, core.UPDATE_PRIORITY.HIGH); } } diff --git a/src/extras/TextureTransform.js b/src/extras/TextureTransform.js index 78e6e40..16536d2 100644 --- a/src/extras/TextureTransform.js +++ b/src/extras/TextureTransform.js @@ -65,8 +65,36 @@ } /** + * Multiplies uvs array to transform + * @param {Float32Array} uvs mesh uvs + * @param {Float32Array} [out=uvs] output + * @returns {Float32Array} output + */ + multiplyUvs(uvs, out) + { + if (out === undefined) + { + out = uvs; + } + + const mat = this.mapCoord; + + for (let i = 0; i < uvs.length; i += 2) + { + const x = uvs[i]; + const y = uvs[i + 1]; + + out[i] = (x * mat.a) + (y * mat.c) + mat.tx; + out[i + 1] = (x * mat.b) + (y * mat.d) + mat.ty; + } + + return out; + } + + /** * updates matrices if texture was changed * @param {boolean} forceUpdate if true, matrices will be updated any case + * @returns {boolean} whether or not it was updated */ update(forceUpdate) { @@ -74,13 +102,13 @@ if (!tex || !tex.valid) { - return; + return false; } if (!forceUpdate && this._lastTextureID === tex._updateID) { - return; + return false; } this._lastTextureID = tex._updateID; @@ -110,5 +138,7 @@ frame[3] = (tex._frame.y + tex._frame.height - margin + offset) / texBase.height; this.uClampOffset[0] = offset / texBase.realWidth; this.uClampOffset[1] = offset / texBase.realHeight; + + return true; } } diff --git a/src/extras/TilingSprite.js b/src/extras/TilingSprite.js index 1f6014f..a3f027f 100644 --- a/src/extras/TilingSprite.js +++ b/src/extras/TilingSprite.js @@ -180,9 +180,9 @@ const transform = this.worldTransform; const resolution = renderer.resolution; const baseTexture = texture.baseTexture; - const baseTextureResolution = texture.baseTexture.resolution; - const modX = (this.tilePosition.x / this.tileScale.x) % texture._frame.width; - const modY = (this.tilePosition.y / this.tileScale.y) % texture._frame.height; + const baseTextureResolution = baseTexture.resolution; + const modX = ((this.tilePosition.x / this.tileScale.x) % texture._frame.width) * baseTextureResolution; + const modY = ((this.tilePosition.y / this.tileScale.y) % texture._frame.height) * baseTextureResolution; // create a nice shiny pattern! // TODO this needs to be refreshed if texture changes.. diff --git a/src/extras/cacheAsBitmap.js b/src/extras/cacheAsBitmap.js index 5dd5640..aea06a8 100644 --- a/src/extras/cacheAsBitmap.js +++ b/src/extras/cacheAsBitmap.js @@ -227,7 +227,16 @@ this.transform._parentID = -1; // restore the transform of the cached sprite to avoid the nasty flicker.. - this.updateTransform(); + if (!this.parent) + { + this.parent = renderer._tempDisplayObjectParent; + this.updateTransform(); + this.parent = null; + } + else + { + this.updateTransform(); + } // map the hit test.. this.containsPoint = cachedSprite.containsPoint.bind(cachedSprite); @@ -314,7 +323,17 @@ cachedSprite._bounds = this._bounds; cachedSprite.alpha = cacheAlpha; - this.updateTransform(); + if (!this.parent) + { + this.parent = renderer._tempDisplayObjectParent; + this.updateTransform(); + this.parent = null; + } + else + { + this.updateTransform(); + } + this.updateTransform = this.displayObjectUpdateTransform; this._cacheData.sprite = cachedSprite; @@ -358,9 +377,12 @@ * Destroys the cached object. * * @private + * @param {object|boolean} [options] - Options parameter. A boolean will act as if all options + * have been set to that value. + * Used when destroying containers, see the Container.destroy method. */ -DisplayObject.prototype._cacheAsBitmapDestroy = function _cacheAsBitmapDestroy() +DisplayObject.prototype._cacheAsBitmapDestroy = function _cacheAsBitmapDestroy(options) { this.cacheAsBitmap = false; - this.destroy(); + this.destroy(options); }; diff --git a/src/extras/index.js b/src/extras/index.js index bbd8bbf..a34d7ec 100644 --- a/src/extras/index.js +++ b/src/extras/index.js @@ -1,7 +1,7 @@ /** + * Additional PIXI DisplayObjects for animation, tiling and bitmap text. * @namespace PIXI.extras */ -export { default as TextureTransform } from './TextureTransform'; export { default as AnimatedSprite } from './AnimatedSprite'; export { default as TilingSprite } from './TilingSprite'; export { default as TilingSpriteRenderer } from './webgl/TilingSpriteRenderer'; diff --git a/src/extras/webgl/TilingSpriteRenderer.js b/src/extras/webgl/TilingSpriteRenderer.js index aeec172..eee3b5c 100644 --- a/src/extras/webgl/TilingSpriteRenderer.js +++ b/src/extras/webgl/TilingSpriteRenderer.js @@ -11,7 +11,7 @@ * WebGL renderer plugin for tiling sprites * * @class - * @memberof PIXI + * @memberof PIXI.extras * @extends PIXI.ObjectRenderer */ export default class TilingSpriteRenderer extends core.ObjectRenderer @@ -118,7 +118,7 @@ tempMat.invert(); if (isSimple) { - tempMat.append(uv.mapCoord); + tempMat.prepend(uv.mapCoord); } else { diff --git a/src/filters/displacement/DisplacementFilter.js b/src/filters/displacement/DisplacementFilter.js index d5355c3..b5d030c 100644 --- a/src/filters/displacement/DisplacementFilter.js +++ b/src/filters/displacement/DisplacementFilter.js @@ -35,7 +35,7 @@ this.maskSprite = sprite; this.maskMatrix = maskMatrix; - this.uniforms.mapSampler = sprite.texture; + this.uniforms.mapSampler = sprite._texture; this.uniforms.filterMatrix = maskMatrix; this.uniforms.scale = { x: 1, y: 1 }; diff --git a/src/filters/index.js b/src/filters/index.js index f3e8862..7ceb50a 100644 --- a/src/filters/index.js +++ b/src/filters/index.js @@ -1,4 +1,21 @@ /** + * This namespace contains WebGL-only display filters that can be applied + * to DisplayObjects using the {@link PIXI.DisplayObject#filters filters} property. + * @example + * // Create a new application + * const app = new PIXI.Application(); + * + * // Draw a green rectangle + * const rect = new PIXI.Graphics() + * .beginFill(0x00ff00) + * .drawRect(40, 40, 200, 200); + * + * // Add a blur filter + * rect.filters = [new PIXI.filters.BlurFilter()]; + * + * // Display rectangle + * app.stage.addChild(rect); + * document.body.appendChild(app.view); * @namespace PIXI.filters */ export { default as FXAAFilter } from './fxaa/FXAAFilter'; diff --git a/src/index.js b/src/index.js index c553427..77da490 100644 --- a/src/index.js +++ b/src/index.js @@ -20,6 +20,14 @@ import { utils } from './core'; utils.mixins.performMixins(); +/** + * Alias for {@link PIXI.loaders.shared}. + * @name loader + * @memberof PIXI + * @type {PIXI.loader.Loader} + */ +const loader = loaders.shared || null; + export { accessibility, extract, @@ -30,18 +38,8 @@ mesh, particles, prepare, + loader, }; -/** - * A premade instance of the loader that can be used to load resources. - * - * @name loader - * @memberof PIXI - * @property {PIXI.loaders.Loader} - */ -const loader = loaders && loaders.Loader ? new loaders.Loader() : null; // check is there in case user excludes loader lib - -export { loader }; - // Always export pixi globally. global.PIXI = exports; // eslint-disable-line diff --git a/src/interaction/InteractionData.js b/src/interaction/InteractionData.js index 02d53a3..449f8cc 100644 --- a/src/interaction/InteractionData.js +++ b/src/interaction/InteractionData.js @@ -30,7 +30,10 @@ /** * When passed to an event handler, this will be the original DOM Event that was captured * - * @member {Event} + * @see https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent + * @see https://developer.mozilla.org/en-US/docs/Web/API/TouchEvent + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent + * @member {MouseEvent|TouchEvent|PointerEvent} */ this.originalEvent = null; } diff --git a/src/interaction/InteractionManager.js b/src/interaction/InteractionManager.js index 8baf13a..2d75c7c 100644 --- a/src/interaction/InteractionManager.js +++ b/src/interaction/InteractionManager.js @@ -13,6 +13,14 @@ const MOUSE_POINTER_ID = 'MOUSE'; +// helpers for hitTest() - only used inside hitTest() +const hitTestEvent = { + target: null, + data: { + global: null, + }, +}; + /** * The interaction manager deals with mouse, touch and pointer events. Any DisplayObject can be interactive * if its interactive parameter is set to true @@ -245,67 +253,60 @@ this.setTargetElement(this.renderer.view, this.renderer.resolution); /** - * Fired when a pointer device button (usually a mouse button) is pressed on the display + * Fired when a pointer device button (usually a mouse left-button) is pressed on the display * object. * - * @event mousedown - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mousedown + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device secondary button (usually a mouse right-button) is pressed * on the display object. * - * @event rightdown - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightdown + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** - * Fired when a pointer device button (usually a mouse button) is released over the display + * Fired when a pointer device button (usually a mouse left-button) is released over the display * object. * - * @event mouseup - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseup + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device secondary button (usually a mouse right-button) is released * over the display object. * - * @event rightup - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightup + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** - * Fired when a pointer device button (usually a mouse button) is pressed and released on + * Fired when a pointer device button (usually a mouse left-button) is pressed and released on * the display object. * - * @event click - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#click + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device secondary button (usually a mouse right-button) is pressed * and released on the display object. * - * @event rightclick - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightclick + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** - * Fired when a pointer device button (usually a mouse button) is released outside the + * Fired when a pointer device button (usually a mouse left-button) is released outside the * display object that initially registered a * [mousedown]{@link PIXI.interaction.InteractionManager#event:mousedown}. * - * @event mouseupoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** @@ -313,146 +314,362 @@ * outside the display object that initially registered a * [rightdown]{@link PIXI.interaction.InteractionManager#event:rightdown}. * - * @event rightupoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device (usually a mouse) is moved while over the display object * - * @event mousemove - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mousemove + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device (usually a mouse) is moved onto the display object * - * @event mouseover - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseover + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device (usually a mouse) is moved off the display object * - * @event mouseout - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseout + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is pressed on the display object. * - * @event pointerdown - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerdown + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is released over the display object. * - * @event pointerup - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerup + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when the operating system cancels a pointer event * - * @event pointercancel - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointercancel + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is pressed and released on the display object. * - * @event pointertap - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointertap + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is released outside the display object that initially * registered a [pointerdown]{@link PIXI.interaction.InteractionManager#event:pointerdown}. * - * @event pointerupoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device is moved while over the display object * - * @event pointermove - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointermove + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device is moved onto the display object * - * @event pointerover - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerover + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device is moved off the display object * - * @event pointerout - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerout + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is placed on the display object. * - * @event touchstart - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchstart + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is removed from the display object. * - * @event touchend - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchend + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when the operating system cancels a touch * - * @event touchcancel - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchcancel + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is placed and removed from the display object. * - * @event tap - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#tap + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is removed outside of the display object that initially * registered a [touchstart]{@link PIXI.interaction.InteractionManager#event:touchstart}. * - * @event touchendoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchendoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is moved along the display object. * - * @event touchmove - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchmove + * @param {PIXI.interaction.InteractionData} event - Interaction data */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is pressed on the display. + * object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mousedown + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is pressed + * on the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightdown + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is released over the display + * object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseup + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is released + * over the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightup + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is pressed and released on + * the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#click + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is pressed + * and released on the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightclick + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is released outside the + * display object that initially registered a + * [mousedown]{@link PIXI.DisplayObject#event:mousedown}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is released + * outside the display object that initially registered a + * [rightdown]{@link PIXI.DisplayObject#event:rightdown}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device (usually a mouse) is moved while over the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mousemove + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device (usually a mouse) is moved onto the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseover + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device (usually a mouse) is moved off the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseout + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is pressed on the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerdown + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is released over the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerup + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when the operating system cancels a pointer event. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointercancel + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is pressed and released on the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointertap + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is released outside the display object that initially + * registered a [pointerdown]{@link PIXI.DisplayObject#event:pointerdown}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device is moved while over the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointermove + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device is moved onto the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerover + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device is moved off the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerout + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is placed on the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchstart + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is removed from the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchend + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when the operating system cancels a touch. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchcancel + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is placed and removed from the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#tap + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is removed outside of the display object that initially + * registered a [touchstart]{@link PIXI.DisplayObject#event:touchstart}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchendoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is moved along the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchmove + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + } + + /** + * Hit tests a point against the display tree, returning the first interactive object that is hit. + * + * @param {PIXI.Point} globalPoint - A point to hit test with, in global space. + * @param {PIXI.Container} [root] - The root display object to start from. If omitted, defaults + * to the last rendered root of the associated renderer. + * @return {PIXI.DisplayObject} The hit display object, if any. + */ + hitTest(globalPoint, root) + { + // clear the target for our hit test + hitTestEvent.target = null; + // assign the global point + hitTestEvent.data.global = globalPoint; + // ensure safety of the root + if (!root) + { + root = this.renderer._lastObjectRendered; + } + // run the hit test + this.processInteractive(hitTestEvent, root, null, true); + // return our found object - it'll be null if we didn't hit anything + + return hitTestEvent.target; } /** @@ -487,7 +704,7 @@ return; } - core.ticker.shared.add(this.update, this); + core.ticker.shared.add(this.update, this, core.UPDATE_PRIORITY.INTERACTION); if (window.navigator.msPointerEnabled) { @@ -804,8 +1021,6 @@ } } - let keepHitTestingAfterChildren = hitTest; - // ** FREE TIP **! If an object is not interactive or has no buttons in it // (such as a game scene!) set interactiveChildren to false for that displayObject. // This will allow pixi to completely ignore and bypass checking the displayObjects children. @@ -818,7 +1033,9 @@ const child = children[i]; // time to get recursive.. if this function will return if something is hit.. - if (this.processInteractive(interactionEvent, child, func, hitTest, interactiveParent)) + const childHit = this.processInteractive(interactionEvent, child, func, hitTest, interactiveParent); + + if (childHit) { // its a good idea to check if a child has lost its parent. // this means it has been removed whilst looping so its best @@ -827,8 +1044,6 @@ continue; } - hit = true; - // we no longer need to hit test any more objects in this container as we we // now know the parent has been hit interactiveParent = false; @@ -838,36 +1053,41 @@ // This means we no longer need to hit test anything else. We still need to run // through all objects, but we don't need to perform any hit tests. - keepHitTestingAfterChildren = false; - - if (child.interactive) + if (childHit) { - hitTest = false; + if (interactionEvent.target) + { + hitTest = false; + } + hit = true; } - - // we can break now as we have hit an object. } } } - hitTest = keepHitTestingAfterChildren; - // no point running this if the item is not interactive or does not have an interactive parent. if (interactive) { // if we are hit testing (as in we have no hit any objects yet) // We also don't need to worry about hit testing if once of the displayObjects children - // has already been hit! - if (hitTest && !hit) + // has already been hit - but only if it was interactive, otherwise we need to keep + // looking for an interactive child, just in case we hit one + if (hitTest && !interactionEvent.target) { if (displayObject.hitArea) { displayObject.worldTransform.applyInverse(point, this._tempPoint); - hit = displayObject.hitArea.contains(this._tempPoint.x, this._tempPoint.y); + if (displayObject.hitArea.contains(this._tempPoint.x, this._tempPoint.y)) + { + hit = true; + } } else if (displayObject.containsPoint) { - hit = displayObject.containsPoint(point); + if (displayObject.containsPoint(point)) + { + hit = true; + } } } @@ -878,7 +1098,10 @@ interactionEvent.target = displayObject; } - func(interactionEvent, displayObject, hit); + if (func) + { + func(interactionEvent, displayObject, !!hit); + } } } @@ -914,7 +1137,7 @@ { const event = events[i]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -998,7 +1221,7 @@ { const event = events[i]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1098,7 +1321,7 @@ const test = isRightButton ? flags.RIGHT_DOWN : flags.LEFT_DOWN; - const isDown = trackingData !== undefined && (trackingData.flags | test); + const isDown = trackingData !== undefined && (trackingData.flags & test); if (hit) { @@ -1118,11 +1341,11 @@ { if (isRightButton) { - trackingData.rightDown = hit; + trackingData.rightDown = false; } else { - trackingData.leftDown = hit; + trackingData.leftDown = false; } } } @@ -1180,7 +1403,7 @@ { const event = events[i]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1255,7 +1478,7 @@ this.setCursorMode(null); } - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1268,6 +1491,12 @@ { this.emit('mouseout', interactionEvent); } + else + { + // we can get touchleave events after touchend, so we want to make sure we don't + // introduce memory leaks + this.releaseInteractionDataForPointerId(interactionData.identifier); + } } /** @@ -1344,7 +1573,7 @@ // Only mouse and pointer can call onPointerOver, so events will always be length 1 const event = events[0]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1366,12 +1595,14 @@ * Get InteractionData for a given pointerId. Store that data as well * * @private - * @param {number} pointerId - Identifier from a pointer event + * @param {PointerEvent} event - Normalized pointer event, output from normalizeToPointerData * @return {InteractionData} - Interaction data for the given pointer identifier */ - getInteractionDataForPointerId(pointerId) + getInteractionDataForPointerId(event) { - if (pointerId === MOUSE_POINTER_ID) + const pointerId = event.pointerId; + + if (pointerId === MOUSE_POINTER_ID || event.pointerType === 'mouse') { return this.mouse; } diff --git a/src/interaction/InteractionTrackingData.js b/src/interaction/InteractionTrackingData.js index 6a1f69e..8493b63 100644 --- a/src/interaction/InteractionTrackingData.js +++ b/src/interaction/InteractionTrackingData.js @@ -35,8 +35,10 @@ } /** + * Unique pointer id of the event + * * @readonly - * @type {number} Unique pointer id of the event + * @member {number} */ get pointerId() { diff --git a/src/interaction/index.js b/src/interaction/index.js index 56c5b09..72f7d71 100644 --- a/src/interaction/index.js +++ b/src/interaction/index.js @@ -1,4 +1,8 @@ /** + * This namespace contains a renderer plugin for handling mouse, pointer, and touch events. + * + * Do not instantiate this plugin directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. * @namespace PIXI.interaction */ export { default as InteractionData } from './InteractionData'; diff --git a/src/interaction/interactiveTarget.js b/src/interaction/interactiveTarget.js index d252fba..cebac40 100644 --- a/src/interaction/interactiveTarget.js +++ b/src/interaction/interactiveTarget.js @@ -2,7 +2,7 @@ * Default property values of interactive objects * Used by {@link PIXI.interaction.InteractionManager} to automatically give all DisplayObjects these properties * - * @mixin + * @private * @name interactiveTarget * @memberof PIXI.interaction * @example @@ -14,18 +14,28 @@ * ); */ export default { + /** - * Determines if the displayObject be clicked/touched + * Enable interaction events for the DisplayObject. Touch, pointer and mouse + * events will not be emitted unless `interactive` is set to `true`. * - * @inner {boolean} + * @example + * const sprite = new PIXI.Sprite(texture); + * sprite.interactive = true; + * sprite.on('tap', (event) => { + * //handle event + * }); + * @member {boolean} + * @memberof PIXI.DisplayObject# */ interactive: false, /** * Determines if the children to the displayObject can be clicked/touched - * Setting this to false allows pixi to bypass a recursive hitTest function + * Setting this to false allows pixi to bypass a recursive `hitTest` function * - * @inner {boolean} + * @member {boolean} + * @memberof PIXI.Container# */ interactiveChildren: true, @@ -33,7 +43,12 @@ * Interaction shape. Children will be hit first, then this shape will be checked. * Setting this will cause this shape to be checked in hit tests rather than the displayObject's bounds. * - * @inner {PIXI.Rectangle|PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.RoundedRectangle} + * @example + * const sprite = new PIXI.Sprite(texture); + * sprite.interactive = true; + * sprite.hitArea = new PIXI.Rectangle(0, 0, 100, 100); + * @member {PIXI.Rectangle|PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.RoundedRectangle} + * @memberof PIXI.DisplayObject# */ hitArea: null, @@ -41,8 +56,12 @@ * If enabled, the mouse cursor use the pointer behavior when hovered over the displayObject if it is interactive * Setting this changes the 'cursor' property to `'pointer'`. * + * @example + * const sprite = new PIXI.Sprite(texture); + * sprite.interactive = true; + * sprite.buttonMode = true; * @member {boolean} - * @memberof PIXI.interaction.interactiveTarget# + * @memberof PIXI.DisplayObject# */ get buttonMode() { @@ -64,9 +83,14 @@ * This defines what cursor mode is used when the mouse cursor * is hovered over the displayObject. * + * @example + * const sprite = new PIXI.Sprite(texture); + * sprite.interactive = true; + * sprite.cursor = 'wait'; * @see https://developer.mozilla.org/en/docs/Web/CSS/cursor * - * @inner {string} + * @member {string} + * @memberof PIXI.DisplayObject# */ cursor: null, @@ -74,7 +98,7 @@ * Internal set of all active pointers, by identifier * * @member {Map} - * @memberof PIXI.interaction.interactiveTarget# + * @memberof PIXI.DisplayObject# * @private */ get trackedPointers() @@ -87,7 +111,8 @@ /** * Map of all tracked pointers, by identifier. Use trackedPointers to access. * - * @private {Map} + * @private + * @type {Map} */ _trackedPointers: undefined, }; diff --git a/src/loaders/index.js b/src/loaders/index.js index 5189793..daa1fc4 100644 --- a/src/loaders/index.js +++ b/src/loaders/index.js @@ -1,9 +1,22 @@ +import Application from '../core/Application'; +import Loader from './loader'; + /** + * This namespace contains APIs which extends the {@link https://github.com/englercj/resource-loader resource-loader} module + * for loading assets, data, and other resources dynamically. + * @example + * const loader = new PIXI.loaders.Loader(); + * loader.add('bunny', 'data/bunny.png') + * .add('spaceship', 'assets/spritesheet.json'); + * loader.load((loader, resources) => { + * // resources.bunny + * // resources.spaceship + * }); * @namespace PIXI.loaders */ -export { default as Loader } from './loader'; +export { Loader }; export { default as bitmapFontParser, parse as parseBitmapFontData } from './bitmapFontParser'; -export { default as spritesheetParser } from './spritesheetParser'; +export { default as spritesheetParser, getResourcePath } from './spritesheetParser'; export { default as textureParser } from './textureParser'; /** @@ -13,3 +26,55 @@ * @memberof PIXI.loaders */ export { Resource } from 'resource-loader'; + +/** + * A premade instance of the loader that can be used to load resources. + * @name shared + * @memberof PIXI.loaders + * @type {PIXI.loaders.Loader} + */ +const shared = new Loader(); + +shared.destroy = () => +{ + // protect destroying shared loader +}; + +export { shared }; + +// Mixin the loader construction +const AppPrototype = Application.prototype; + +AppPrototype._loader = null; + +/** + * Loader instance to help with asset loading. + * @name PIXI.Application#loader + * @type {PIXI.loaders.Loader} + */ +Object.defineProperty(AppPrototype, 'loader', { + get() + { + if (!this._loader) + { + const sharedLoader = this._options.sharedLoader; + + this._loader = sharedLoader ? shared : new Loader(); + } + + return this._loader; + }, +}); + +// Override the destroy function +// making sure to destroy the current Loader +AppPrototype._parentDestroy = AppPrototype.destroy; +AppPrototype.destroy = function destroy() +{ + if (this._loader) + { + this._loader.destroy(); + this._loader = null; + } + this._parentDestroy(); +}; diff --git a/src/loaders/loader.js b/src/loaders/loader.js index 767a411..59b8000 100644 --- a/src/loaders/loader.js +++ b/src/loaders/loader.js @@ -90,6 +90,15 @@ { Loader._pixiMiddleware.push(fn); } + + /** + * Destroy the loader, removes references. + */ + destroy() + { + this.removeAllListeners(); + this.reset(); + } } // Copy EE3 prototype (mixin) diff --git a/src/loaders/spritesheetParser.js b/src/loaders/spritesheetParser.js index e57dfb2..eda9584 100644 --- a/src/loaders/spritesheetParser.js +++ b/src/loaders/spritesheetParser.js @@ -1,12 +1,11 @@ import { Resource } from 'resource-loader'; -import path from 'path'; +import url from 'url'; import { Spritesheet } from '../core'; export default function () { return function spritesheetParser(resource, next) { - let resourcePath; const imageResourceName = `${resource.name}_image`; // skip if no data, its not json, it isn't spritesheet data, or the image resource already exists @@ -28,15 +27,7 @@ parentResource: resource, }; - // Prepend url path unless the resource image is a data url - if (resource.isDataUrl) - { - resourcePath = resource.data.meta.image; - } - else - { - resourcePath = `${path.dirname(resource.url.replace(this.baseUrl, ''))}/${resource.data.meta.image}`; - } + const resourcePath = getResourcePath(resource, this.baseUrl); // load the image for this sheet this.add(imageResourceName, resourcePath, loadOptions, function onImageLoad(res) @@ -56,3 +47,14 @@ }); }; } + +export function getResourcePath(resource, baseUrl) +{ + // Prepend url path unless the resource image is a data url + if (resource.isDataUrl) + { + return resource.data.meta.image; + } + + return url.resolve(resource.url.replace(baseUrl, ''), resource.data.meta.image); +} diff --git a/src/mesh/canvas/CanvasMeshRenderer.js b/src/mesh/canvas/CanvasMeshRenderer.js index 297205f..f7899e1 100644 --- a/src/mesh/canvas/CanvasMeshRenderer.js +++ b/src/mesh/canvas/CanvasMeshRenderer.js @@ -134,12 +134,33 @@ const textureWidth = base.width; const textureHeight = base.height; - const u0 = uvs[index0] * base.width; - const u1 = uvs[index1] * base.width; - const u2 = uvs[index2] * base.width; - const v0 = uvs[index0 + 1] * base.height; - const v1 = uvs[index1 + 1] * base.height; - const v2 = uvs[index2 + 1] * base.height; + let u0; + let u1; + let u2; + let v0; + let v1; + let v2; + + if (mesh.uploadUvTransform) + { + const ut = mesh._uvTransform.mapCoord; + + u0 = ((uvs[index0] * ut.a) + (uvs[index0 + 1] * ut.c) + ut.tx) * base.width; + u1 = ((uvs[index1] * ut.a) + (uvs[index1 + 1] * ut.c) + ut.tx) * base.width; + u2 = ((uvs[index2] * ut.a) + (uvs[index2 + 1] * ut.c) + ut.tx) * base.width; + v0 = ((uvs[index0] * ut.b) + (uvs[index0 + 1] * ut.d) + ut.ty) * base.height; + v1 = ((uvs[index1] * ut.b) + (uvs[index1 + 1] * ut.d) + ut.ty) * base.height; + v2 = ((uvs[index2] * ut.b) + (uvs[index2 + 1] * ut.d) + ut.ty) * base.height; + } + else + { + u0 = uvs[index0] * base.width; + u1 = uvs[index1] * base.width; + u2 = uvs[index2] * base.width; + v0 = uvs[index0 + 1] * base.height; + v1 = uvs[index1 + 1] * base.height; + v2 = uvs[index2 + 1] * base.height; + } let x0 = vertices[index0]; let x1 = vertices[index1]; diff --git a/src/mesh/webgl/MeshRenderer.js b/src/mesh/webgl/MeshRenderer.js index 78d24c3..ab99fcd 100644 --- a/src/mesh/webgl/MeshRenderer.js +++ b/src/mesh/webgl/MeshRenderer.js @@ -2,6 +2,8 @@ const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; +const matrixIdentity = core.Matrix.IDENTITY; + /** * WebGL renderer plugin for tiling sprites * @@ -64,6 +66,7 @@ mesh.geometry.glVertexArrayObjects[this.CONTEXT_UID].draw(mesh.drawMode, mesh.size, mesh.start, mesh.geometry.instanceCount); } + /** * draws mesh * @param {PIXI.mesh.RawMesh} mesh mesh instance diff --git a/src/mesh/webgl/mesh.vert b/src/mesh/webgl/mesh.vert index a337aef..acc096c 100644 --- a/src/mesh/webgl/mesh.vert +++ b/src/mesh/webgl/mesh.vert @@ -1,8 +1,9 @@ attribute vec2 aVertexPosition; attribute vec2 aTextureCoord; -uniform mat3 translationMatrix; uniform mat3 projectionMatrix; +uniform mat3 translationMatrix; +uniform mat3 uTransform; varying vec2 vTextureCoord; @@ -10,5 +11,5 @@ { gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - vTextureCoord = aTextureCoord; + vTextureCoord = (uTransform * vec3(aTextureCoord, 1.0)).xy; } diff --git a/src/particles/ParticleContainer.js b/src/particles/ParticleContainer.js index 67796cf..63f7791 100644 --- a/src/particles/ParticleContainer.js +++ b/src/particles/ParticleContainer.js @@ -313,10 +313,10 @@ frame.y * resolution, frame.width * resolution, frame.height * resolution, - positionX * resolution, - positionY * resolution, - finalWidth * resolution, - finalHeight * resolution + positionX * renderer.resolution, + positionY * renderer.resolution, + finalWidth * renderer.resolution, + finalHeight * renderer.resolution ); } } diff --git a/src/prepare/BasePrepare.js b/src/prepare/BasePrepare.js index 09eb5e5..e45c4dc 100644 --- a/src/prepare/BasePrepare.js +++ b/src/prepare/BasePrepare.js @@ -18,6 +18,18 @@ * basic queuing functionality and is extended by {@link PIXI.prepare.WebGLPrepare} and {@link PIXI.prepare.CanvasPrepare} * to provide preparation capabilities specific to their respective renderers. * + * @example + * // Create a sprite + * const sprite = new PIXI.Sprite.fromImage('something.png'); + * + * // Load object into GPU + * app.renderer.plugins.prepare.upload(sprite, () => { + * + * //Texture(s) has been uploaded to GPU + * app.stage.addChild(sprite); + * + * }) + * * @abstract * @class * @memberof PIXI.prepare @@ -100,8 +112,16 @@ this.prepareItems(); }; - this.register(findText, drawText); - this.register(findTextStyle, calculateTextStyle); + // hooks to find the correct texture + this.registerFindHook(findText); + this.registerFindHook(findTextStyle); + this.registerFindHook(findMultipleBaseTextures); + this.registerFindHook(findBaseTexture); + this.registerFindHook(findTexture); + + // upload hooks + this.registerUploadHook(drawText); + this.registerUploadHook(calculateTextStyle); } /** @@ -138,7 +158,7 @@ if (!this.ticking) { this.ticking = true; - SharedTicker.addOnce(this.tick, this); + SharedTicker.addOnce(this.tick, this, core.UPDATE_PRIORITY.UTILITY); } } else if (done) @@ -172,13 +192,16 @@ const item = this.queue[0]; let uploaded = false; - for (let i = 0, len = this.uploadHooks.length; i < len; i++) + if (item && !item._destroyed) { - if (this.uploadHooks[i](this.uploadHookHelper, item)) + for (let i = 0, len = this.uploadHooks.length; i < len; i++) { - this.queue.shift(); - uploaded = true; - break; + if (this.uploadHooks[i](this.uploadHookHelper, item)) + { + this.queue.shift(); + uploaded = true; + break; + } } } @@ -205,26 +228,36 @@ else { // if we are not finished, on the next rAF do this again - SharedTicker.addOnce(this.tick, this); + SharedTicker.addOnce(this.tick, this, core.UPDATE_PRIORITY.UTILITY); } } /** - * Adds hooks for finding and uploading items. + * Adds hooks for finding items. * - * @param {Function} [addHook] - Function call that takes two parameters: `item:*, queue:Array` - function must return `true` if it was able to add item to the queue. - * @param {Function} [uploadHook] - Function call that takes two parameters: `prepare:CanvasPrepare, item:*` and - * function must return `true` if it was able to handle upload of item. - * @return {PIXI.CanvasPrepare} Instance of plugin for chaining. + * @param {Function} addHook - Function call that takes two parameters: `item:*, queue:Array` + * function must return `true` if it was able to add item to the queue. + * @return {PIXI.BasePrepare} Instance of plugin for chaining. */ - register(addHook, uploadHook) + registerFindHook(addHook) { if (addHook) { this.addHooks.push(addHook); } + return this; + } + + /** + * Adds hooks for uploading items. + * + * @param {Function} uploadHook - Function call that takes two parameters: `prepare:CanvasPrepare, item:*` and + * function must return `true` if it was able to handle upload of item. + * @return {PIXI.BasePrepare} Instance of plugin for chaining. + */ + registerUploadHook(uploadHook) + { if (uploadHook) { this.uploadHooks.push(uploadHook); @@ -287,6 +320,88 @@ } /** + * Built-in hook to find multiple textures from objects like AnimatedSprites. + * + * @private + * @param {PIXI.DisplayObject} item - Display object to check + * @param {Array<*>} queue - Collection of items to upload + * @return {boolean} if a PIXI.Texture object was found. + */ +function findMultipleBaseTextures(item, queue) +{ + let result = false; + + // Objects with mutliple textures + if (item && item._textures && item._textures.length) + { + for (let i = 0; i < item._textures.length; i++) + { + if (item._textures[i] instanceof core.Texture) + { + const baseTexture = item._textures[i].baseTexture; + + if (queue.indexOf(baseTexture) === -1) + { + queue.push(baseTexture); + result = true; + } + } + } + } + + return result; +} + +/** + * Built-in hook to find BaseTextures from Sprites. + * + * @private + * @param {PIXI.DisplayObject} item - Display object to check + * @param {Array<*>} queue - Collection of items to upload + * @return {boolean} if a PIXI.Texture object was found. + */ +function findBaseTexture(item, queue) +{ + // Objects with textures, like Sprites/Text + if (item instanceof core.BaseTexture) + { + if (queue.indexOf(item) === -1) + { + queue.push(item); + } + + return true; + } + + return false; +} + +/** + * Built-in hook to find textures from objects. + * + * @private + * @param {PIXI.DisplayObject} item - Display object to check + * @param {Array<*>} queue - Collection of items to upload + * @return {boolean} if a PIXI.Texture object was found. + */ +function findTexture(item, queue) +{ + if (item._texture && item._texture instanceof core.Texture) + { + const texture = item._texture.baseTexture; + + if (queue.indexOf(texture) === -1) + { + queue.push(texture); + } + + return true; + } + + return false; +} + +/** * Built-in hook to draw PIXI.Text to its texture. * * @private diff --git a/src/prepare/canvas/CanvasPrepare.js b/src/prepare/canvas/CanvasPrepare.js index 0224bb5..09c207a 100644 --- a/src/prepare/canvas/CanvasPrepare.js +++ b/src/prepare/canvas/CanvasPrepare.js @@ -43,7 +43,7 @@ this.ctx = this.canvas.getContext('2d'); // Add textures to upload - this.register(findBaseTextures, uploadBaseTextures); + this.registerUploadHook(uploadBaseTextures); } /** @@ -89,39 +89,4 @@ return false; } -/** - * Built-in hook to find textures from Sprites. - * - * @private - * @param {PIXI.DisplayObject} item -Display object to check - * @param {Array<*>} queue - Collection of items to upload - * @return {boolean} if a PIXI.Texture object was found. - */ -function findBaseTextures(item, queue) -{ - // Objects with textures, like Sprites/Text - if (item instanceof core.BaseTexture) - { - if (queue.indexOf(item) === -1) - { - queue.push(item); - } - - return true; - } - else if (item._texture && item._texture instanceof core.Texture) - { - const texture = item._texture.baseTexture; - - if (queue.indexOf(texture) === -1) - { - queue.push(texture); - } - - return true; - } - - return false; -} - core.CanvasRenderer.registerPlugin('prepare', CanvasPrepare); diff --git a/src/prepare/index.js b/src/prepare/index.js index f559c45..e7491ec 100644 --- a/src/prepare/index.js +++ b/src/prepare/index.js @@ -1,4 +1,29 @@ /** + * The prepare namespace provides renderer-specific plugins for pre-rendering DisplayObjects. These plugins are useful for + * asynchronously preparing assets, textures, graphics waiting to be displayed. + * + * Do not instantiate these plugins directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. + * @example + * // Create a new application + * const app = new PIXI.Application(); + * document.body.appendChild(app.view); + * + * // Don't start rendering right away + * app.stop(); + * + * // create a display object + * const rect = new PIXI.Graphics() + * .beginFill(0x00ff00) + * .drawRect(40, 40, 200, 200); + * + * // Add to the stage + * app.stage.addChild(rect); + * + * // Don't start rendering until the graphic is uploaded to the GPU + * app.renderer.plugins.prepare.upload(app.stage, () => { + * app.start(); + * }); * @namespace PIXI.prepare */ export { default as webgl } from './webgl/WebGLPrepare'; diff --git a/src/prepare/webgl/WebGLPrepare.js b/src/prepare/webgl/WebGLPrepare.js index eb7023f..9df3c3f 100644 --- a/src/prepare/webgl/WebGLPrepare.js +++ b/src/prepare/webgl/WebGLPrepare.js @@ -22,12 +22,11 @@ this.uploadHookHelper = this.renderer; // Add textures and graphics to upload - this.register(findBaseTextures, uploadBaseTextures) - .register(findGraphics, uploadGraphics); + this.registerFindHook(findGraphics); + this.registerUploadHook(uploadBaseTextures); + this.registerUploadHook(uploadGraphics); } - } - /** * Built-in hook to upload PIXI.Texture objects to the GPU. * @@ -80,41 +79,6 @@ } /** - * Built-in hook to find textures from Sprites. - * - * @private - * @param {PIXI.DisplayObject} item - Display object to check - * @param {Array<*>} queue - Collection of items to upload - * @return {boolean} if a PIXI.Texture object was found. - */ -function findBaseTextures(item, queue) -{ - // Objects with textures, like Sprites/Text - if (item instanceof core.BaseTexture) - { - if (queue.indexOf(item) === -1) - { - queue.push(item); - } - - return true; - } - else if (item._texture && item._texture instanceof core.Texture) - { - const texture = item._texture.baseTexture; - - if (queue.indexOf(texture) === -1) - { - queue.push(texture); - } - - return true; - } - - return false; -} - -/** * Built-in hook to find graphics. * * @private diff --git a/test/.eslintrc.json b/test/.eslintrc.json index e03220d..ac12110 100644 --- a/test/.eslintrc.json +++ b/test/.eslintrc.json @@ -12,7 +12,8 @@ "sinon": false, "expect": false, "assert": false, - "PIXI": false + "PIXI": false, + "PointerEvent": true }, "rules": { "func-names": 0, diff --git a/test/core/BaseTexture.js b/test/core/BaseTexture.js index 8b93478..bb65068 100644 --- a/test/core/BaseTexture.js +++ b/test/core/BaseTexture.js @@ -14,4 +14,28 @@ expect(baseTexture.imageType).to.be.equals('png'); }); }); + + it('should remove Canvas BaseTexture from cache on destroy', function () + { + const canvas = document.createElement('canvas'); + const texture = PIXI.BaseTexture.fromCanvas(canvas); + const _pixiId = canvas._pixiId; + + expect(PIXI.utils.BaseTextureCache[_pixiId]).to.equal(texture); + texture.destroy(); + expect(PIXI.utils.BaseTextureCache[_pixiId]).to.equal(undefined); + }); + + it('should remove Image BaseTexture from cache on destroy', function () + { + const URL = 'foo.png'; + const NAME = 'bar'; + const image = new Image(); + + const texture = PIXI.Texture.fromLoader(image, URL, NAME); + + expect(PIXI.utils.BaseTextureCache[NAME]).to.equal(texture.baseTexture); + texture.destroy(true); + expect(PIXI.utils.BaseTextureCache[NAME]).to.equal(undefined); + }); }); diff --git a/test/core/Circle.js b/test/core/Circle.js index 183f98f..f621992 100644 --- a/test/core/Circle.js +++ b/test/core/Circle.js @@ -53,6 +53,10 @@ expect(circ1.contains(10, 16)).to.be.false; expect(circ1.contains(11, 15)).to.be.false; expect(circ1.contains(0, 0)).to.be.false; + + const circ2 = new PIXI.Circle(10, 10, 0); + + expect(circ2.contains(10, 10)).to.be.false; }); it('should return framing rectangle', function () diff --git a/test/core/Container.js b/test/core/Container.js index d11be33..cefde54 100644 --- a/test/core/Container.js +++ b/test/core/Container.js @@ -2,30 +2,36 @@ function testAddChild(fn) { - fn(function (container, obj) + return function () { - container.addChild(obj); - }); - fn(function (container, obj) - { - container.addChildAt(obj); - }); + fn(function (container, obj) + { + container.addChild(obj); + }); + fn(function (container, obj) + { + container.addChildAt(obj); + }); + }; } function testRemoveChild(fn) { - fn(function (container, obj) + return function () { - container.removeChild(obj); - }); - fn(function (container, obj) - { - container.removeChildAt(container.children.indexOf(obj)); - }); - fn(function (container, obj) - { - container.removeChildren(container.children.indexOf(obj), container.children.indexOf(obj) + 1); - }); + fn(function (container, obj) + { + container.removeChild(obj); + }); + fn(function (container, obj) + { + container.removeChildAt(container.children.indexOf(obj)); + }); + fn(function (container, obj) + { + container.removeChildren(container.children.indexOf(obj), container.children.indexOf(obj) + 1); + }); + }; } describe('PIXI.Container', function () diff --git a/test/core/Ellipse.js b/test/core/Ellipse.js index f2e8234..3f47442 100644 --- a/test/core/Ellipse.js +++ b/test/core/Ellipse.js @@ -56,6 +56,10 @@ expect(ellipse1.contains(10, 16)).to.be.false; expect(ellipse1.contains(11, 15)).to.be.false; expect(ellipse1.contains(0, 0)).to.be.false; + + const ellipse2 = new PIXI.Ellipse(10, 10, 0, 0); + + expect(ellipse2.contains(10, 10)).to.be.false; }); it('should return framing rectangle', function () diff --git a/test/core/Matrix.js b/test/core/Matrix.js index 9d2694b..76bd57c 100644 --- a/test/core/Matrix.js +++ b/test/core/Matrix.js @@ -107,10 +107,60 @@ expect(m1.ty).to.equal(m2.ty); }); + it('should prepend matrix', function () + { + const m1 = new PIXI.Matrix(); + const m2 = new PIXI.Matrix(); + + m2.set(2, 3, 4, 5, 100, 200); + m1.prepend(m2); + + expect(m1.a).to.equal(m2.a); + expect(m1.b).to.equal(m2.b); + expect(m1.c).to.equal(m2.c); + expect(m1.d).to.equal(m2.d); + expect(m1.tx).to.equal(m2.tx); + expect(m1.ty).to.equal(m2.ty); + + const m3 = new PIXI.Matrix(); + const m4 = new PIXI.Matrix(); + + m3.prepend(m4); + + expect(m3.a).to.equal(m4.a); + expect(m3.b).to.equal(m4.b); + expect(m3.c).to.equal(m4.c); + expect(m3.d).to.equal(m4.d); + expect(m3.tx).to.equal(m4.tx); + expect(m3.ty).to.equal(m4.ty); + }); + it('should get IDENTITY and TEMP_MATRIX', function () { expect(PIXI.Matrix.IDENTITY instanceof PIXI.Matrix).to.be.true; expect(PIXI.Matrix.TEMP_MATRIX instanceof PIXI.Matrix).to.be.true; }); -}); + it('should reset matrix to default when identity() is called', function () + { + const matrix = new PIXI.Matrix(); + + matrix.set(2, 3, 4, 5, 100, 200); + + expect(matrix.a).to.equal(2); + expect(matrix.b).to.equal(3); + expect(matrix.c).to.equal(4); + expect(matrix.d).to.equal(5); + expect(matrix.tx).to.equal(100); + expect(matrix.ty).to.equal(200); + + matrix.identity(); + + expect(matrix.a).to.equal(1); + expect(matrix.b).to.equal(0); + expect(matrix.c).to.equal(0); + expect(matrix.d).to.equal(1); + expect(matrix.tx).to.equal(0); + expect(matrix.ty).to.equal(0); + }); +}); diff --git a/test/core/Spritesheet.js b/test/core/Spritesheet.js index 283adf5..7601a0c 100644 --- a/test/core/Spritesheet.js +++ b/test/core/Spritesheet.js @@ -18,6 +18,7 @@ expect(textures[id]).to.be.an.instanceof(PIXI.Texture); expect(textures[id].width).to.equal(spritesheet.data.frames[id].frame.w / spritesheet.resolution); expect(textures[id].height).to.equal(spritesheet.data.frames[id].frame.h / spritesheet.resolution); + expect(textures[id].textureCacheId).to.equal(id); spritesheet.destroy(true); expect(spritesheet.textures).to.be.null; expect(spritesheet.baseTexture).to.be.null; diff --git a/test/core/Texture.js b/test/core/Texture.js index 8c4ef98..241ec3e 100644 --- a/test/core/Texture.js +++ b/test/core/Texture.js @@ -16,4 +16,20 @@ expect(PIXI.utils.TextureCache[URL]).to.equal(texture); expect(PIXI.utils.BaseTextureCache[URL]).to.equal(texture.baseTexture); }); + + it('should remove Texture from cache on destroy', function () + { + const NAME = 'foo'; + const NAME2 = 'bar'; + const texture = new PIXI.Texture(new PIXI.BaseTexture()); + + PIXI.Texture.addTextureToCache(texture, NAME); + PIXI.Texture.addTextureToCache(texture, NAME2); + expect(texture.textureCacheId).to.equal(NAME); + expect(PIXI.utils.TextureCache[NAME]).to.equal(texture); + expect(PIXI.utils.TextureCache[NAME2]).to.equal(texture); + texture.destroy(); + expect(PIXI.utils.TextureCache[NAME]).to.equal(undefined); + expect(PIXI.utils.TextureCache[NAME2]).to.equal(undefined); + }); }); diff --git a/test/core/Ticker.js b/test/core/Ticker.js new file mode 100644 index 0000000..188770f --- /dev/null +++ b/test/core/Ticker.js @@ -0,0 +1,322 @@ +'use strict'; + +const Ticker = PIXI.ticker.Ticker; +const shared = PIXI.ticker.shared; + +describe('PIXI.ticker.Ticker', function () +{ + before(function () + { + this.length = (ticker) => + { + ticker = ticker || shared; + + if (!ticker._head || !ticker._head.next) + { + return 0; + } + + let listener = ticker._head.next; + let i = 0; + + while (listener) + { + listener = listener.next; + i++; + } + + return i; + }; + }); + + it('should be available', function () + { + expect(Ticker).to.be.a.function; + expect(shared).to.be.an.instanceof(Ticker); + }); + + it('should create a new ticker and destroy it', function () + { + const ticker = new Ticker(); + + ticker.start(); + + const listener = sinon.spy(); + + expect(this.length(ticker)).to.equal(0); + + ticker.add(listener); + + expect(this.length(ticker)).to.equal(1); + + ticker.destroy(); + + expect(ticker._head).to.be.null; + expect(ticker.started).to.be.false; + expect(this.length(ticker)).to.equal(0); + }); + + it('should protect destroying shared ticker', function () + { + shared.destroy(); + expect(shared._head).to.not.be.null; + expect(shared.started).to.be.true; + }); + + it('should add and remove listener', function () + { + const listener = sinon.spy(); + const length = this.length(); + + shared.add(listener); + + expect(this.length()).to.equal(length + 1); + + shared.remove(listener); + + expect(this.length()).to.equal(length); + }); + + it('should update a listener', function () + { + const listener = sinon.spy(); + + shared.add(listener); + shared.update(); + + expect(listener.calledOnce).to.be.true; + + shared.remove(listener); + shared.update(); + + expect(listener.calledOnce).to.be.true; + }); + + it('should update a listener twice and remove once', function () + { + const listener = sinon.spy(); + const length = this.length(); + + shared.add(listener).add(listener); + shared.update(); + + expect(listener.calledTwice).to.be.true; + expect(this.length()).to.equal(length + 2); + + shared.remove(listener); + shared.update(); + + expect(listener.calledTwice).to.be.true; + expect(this.length()).to.equal(length); + }); + + it('should respect priority order', function () + { + const length = this.length(); + const listener1 = sinon.spy(); + const listener2 = sinon.spy(); + const listener3 = sinon.spy(); + const listener4 = sinon.spy(); + + shared.add(listener1, null, PIXI.UPDATE_PRIORITY.LOW) + .add(listener4, null, PIXI.UPDATE_PRIORITY.INTERACTION) + .add(listener3, null, PIXI.UPDATE_PRIORITY.HIGH) + .add(listener2, null, PIXI.UPDATE_PRIORITY.NORMAL); + + shared.update(); + + expect(this.length()).to.equal(length + 4); + + sinon.assert.callOrder(listener4, listener3, listener2, listener1); + + shared.remove(listener1) + .remove(listener2) + .remove(listener3) + .remove(listener4); + + expect(this.length()).to.equal(length); + }); + + it('should auto-remove once listeners', function () + { + const length = this.length(); + const listener = sinon.spy(); + + shared.addOnce(listener); + + shared.update(); + + expect(listener.calledOnce).to.be.true; + expect(this.length()).to.equal(length); + }); + + it('should call inserted item with a lower priority', function () + { + const length = this.length(); + const lowListener = sinon.spy(); + const highListener = sinon.spy(); + const mainListener = sinon.spy(() => + { + shared.add(highListener, null, PIXI.UPDATE_PRIORITY.HIGH); + shared.add(lowListener, null, PIXI.UPDATE_PRIORITY.LOW); + }); + + shared.add(mainListener, null, PIXI.UPDATE_PRIORITY.NORMAL); + + shared.update(); + + expect(this.length()).to.equal(length + 3); + + expect(mainListener.calledOnce).to.be.true; + expect(lowListener.calledOnce).to.be.true; + expect(highListener.calledOnce).to.be.false; + + shared.remove(mainListener) + .remove(highListener) + .remove(lowListener); + + expect(this.length()).to.equal(length); + }); + + it('should remove emit low-priority item during emit', function () + { + const length = this.length(); + const listener2 = sinon.spy(); + const listener1 = sinon.spy(() => + { + shared.add(listener2, null, PIXI.UPDATE_PRIORITY.LOW); + }); + + shared.add(listener1, null, PIXI.UPDATE_PRIORITY.NORMAL); + + shared.update(); + + expect(this.length()).to.equal(length + 2); + + expect(listener2.calledOnce).to.be.true; + expect(listener1.calledOnce).to.be.true; + + shared.remove(listener1) + .remove(listener2); + + expect(this.length()).to.equal(length); + }); + + it('should remove itself on emit after adding new item', function () + { + const length = this.length(); + const listener2 = sinon.spy(); + const listener1 = sinon.spy(() => + { + shared.add(listener2, null, PIXI.UPDATE_PRIORITY.LOW); + shared.remove(listener1); + + // listener is removed right away + expect(this.length()).to.equal(length + 1); + }); + + shared.add(listener1, null, PIXI.UPDATE_PRIORITY.NORMAL); + + shared.update(); + + expect(this.length()).to.equal(length + 1); + + expect(listener2.calledOnce).to.be.true; + expect(listener1.calledOnce).to.be.true; + + shared.remove(listener2); + + expect(this.length()).to.equal(length); + }); + + it('should remove itself before, still calling new item', function () + { + const length = this.length(); + const listener2 = sinon.spy(); + const listener1 = sinon.spy(() => + { + shared.remove(listener1); + shared.add(listener2, null, PIXI.UPDATE_PRIORITY.LOW); + + // listener is removed right away + expect(this.length()).to.equal(length + 1); + }); + + shared.add(listener1, null, PIXI.UPDATE_PRIORITY.NORMAL); + + shared.update(); + + expect(this.length()).to.equal(length + 1); + + expect(listener2.calledOnce).to.be.true; + expect(listener1.calledOnce).to.be.true; + + shared.remove(listener2); + + expect(this.length()).to.equal(length); + }); + + it('should remove items before and after current priority', function () + { + const length = this.length(); + const listener2 = sinon.spy(); + const listener3 = sinon.spy(); + const listener4 = sinon.spy(); + + shared.add(listener2, null, PIXI.UPDATE_PRIORITY.HIGH); + shared.add(listener3, null, PIXI.UPDATE_PRIORITY.LOW); + shared.add(listener4, null, PIXI.UPDATE_PRIORITY.LOW); + + const listener1 = sinon.spy(() => + { + shared.remove(listener2) + .remove(listener3); + + // listener is removed right away + expect(this.length()).to.equal(length + 2); + }); + + shared.add(listener1, null, PIXI.UPDATE_PRIORITY.NORMAL); + + shared.update(); + + expect(this.length()).to.equal(length + 2); + + expect(listener2.calledOnce).to.be.true; + expect(listener3.calledOnce).to.be.false; + expect(listener4.calledOnce).to.be.true; + expect(listener1.calledOnce).to.be.true; + + shared.update(); + + expect(listener2.calledOnce).to.be.true; + expect(listener3.calledOnce).to.be.false; + expect(listener4.calledTwice).to.be.true; + expect(listener1.calledTwice).to.be.true; + + shared.remove(listener1) + .remove(listener4); + + expect(this.length()).to.equal(length); + }); + + it('should destroy on listener', function (done) + { + const ticker = new Ticker(); + const listener2 = sinon.spy(); + const listener = sinon.spy(() => + { + ticker.destroy(); + setTimeout(() => + { + expect(listener2.called).to.be.false; + expect(listener.calledOnce).to.be.true; + done(); + }, 0); + }); + + ticker.add(listener); + ticker.add(listener2, null, PIXI.UPDATE_PRIORITY.LOW); + ticker.start(); + }); +}); diff --git a/.travis.yml b/.travis.yml index 3df8b22..14de334 100644 --- a/.travis.yml +++ b/.travis.yml @@ -110,3 +110,43 @@ on: all_branches: true condition: $TRAVIS_TAG + # Deploy config for latest release + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: dist + upload-dir: "release" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: docs + upload-dir: "release/docs" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: coverage + upload-dir: "release/coverage" + on: + all_branches: true + condition: $TRAVIS_TAG \ No newline at end of file diff --git a/README.md b/README.md index d26fadd..3b85f85 100644 --- a/README.md +++ b/README.md @@ -36,22 +36,14 @@ - Wiki: Other misc tutorials and resources are [on the Wiki](https://github.com/pixijs/pixi.js/wiki/Resources). ### Community ### -- Forums: Check out the [forum] (http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow] (http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. +- Forums: Check out the [forum](http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow](http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. - Inspiration: Check out the [gallery](http://www.pixijs.com/gallery) to see some of the amazing things people have created! - Chat: You can join us on [Gitter](https://gitter.im/pixijs/pixi.js) To chat about Pixi. We also now have a Slack channel. If you would like to join it please Send me an email (mat@goodboydigital.com) and I will invite you in. ### Setup ### -It's easy to get started with Pixi.js! Simply grab the pre-built versions from here: - -Release Branch - Nice and stable Pixi.js -- Unminified: [http://pixijs.download/release/pixi.js] -- Minified: [http://pixijs.download/release/pixi.min.js] - -Develop Branch - The bleeding edge version of Pixi.js -- Unminified: [http://pixijs.download/dev/pixi.js] -- Minified: [http://pixijs.download/dev/pixi.min.js] +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. @@ -70,10 +62,10 @@ #### CDN Install (via cdnjs) ```html - + ``` -_Note: `4.2.2` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ +_Note: `4.4.3` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ ### Demos ### @@ -130,7 +122,7 @@ // and the root stage PIXI.Container. var app = new PIXI.Application(); -// The application will create a canvas element for you that you +// The application will create a canvas element for you that you // can then insert into the DOM. document.body.appendChild(app.view); @@ -150,7 +142,7 @@ // Add the bunny to the scene we are building. app.stage.addChild(bunny); - + // Listen for frame updates app.ticker.add(function() { // each frame we spin the bunny around a bit diff --git a/bower.json b/bower.json deleted file mode 100644 index 41b668c..0000000 --- a/bower.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "pixi.js", - "main": "dist/pixi.min.js", - "ignore": [ - "**/.*", - "docs", - "node_modules", - "bower_components", - "test", - "scripts", - "bower.json", - "inch.json" - ], - "dependencies": { - }, - "devDependencies": { - } -} diff --git a/package.json b/package.json index d35cbb2..308469f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.3.5", + "version": "4.4.4", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ @@ -40,11 +40,11 @@ "lib": "babel src --out-dir lib -s", "predocs": "rimraf docs/**", "docs": "jsdoc -c scripts/jsdoc.conf.json -R README.md", - "publish:patch": "npm version patch --no-git-tag-version && npm publish", - "publish:minor": "npm version minor --no-git-tag-version && npm publish", - "publish:major": "npm version major --no-git-tag-version && npm publish", + "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", - "postpublish": "node scripts/release.js" + "postpublish": "git push && git push --tags" }, "files": [ "dist/", @@ -72,7 +72,6 @@ "electron": "^1.4.15", "eslint": "^3.5.0", "floss": "^2.0.1", - "gh-pages": "^0.11.0", "jaguarjs-jsdoc": "^1.0.1", "js-md5": "^0.4.1", "jsdoc": "^3.4.2", diff --git a/scripts/jsdoc.conf.json b/scripts/jsdoc.conf.json index 553e61c..3f52180 100644 --- a/scripts/jsdoc.conf.json +++ b/scripts/jsdoc.conf.json @@ -43,7 +43,7 @@ }, "markdown" : { "parser" : "gfm", - "hardwrap" : true + "hardwrap" : false }, "opts": { "encoding" : "utf8", diff --git a/scripts/release.js b/scripts/release.js deleted file mode 100644 index 156620c..0000000 --- a/scripts/release.js +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env node -'use strict'; - -// Publish script to push releases of the bin files -// the normally are gitignored -const ghpages = require('gh-pages'); -const path = require('path'); -const packageInfo = require(path.join(__dirname, '..', 'package.json')); - -const options = { - src: [ - 'dist/**/*', - 'lib/**/*', - 'src/**/*', - 'scripts/*', - 'scripts/renders/*', - 'test/**/*', - '*.json', - '*.md', - 'LICENSE', - '.eslintrc', - '.editorconfig', - '.travis.yml', - '.babelrc', - ], - dotfiles: true, - branch: 'release', - message: packageInfo.version, - logger: console.log.bind(console), -}; - -ghpages.publish(process.cwd(), options, (err) => -{ - if (err) - { - console.log(err); - process.exit(1); - - return; - } - - process.exit(0); -}); diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index f16bcb1..220427a 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -21,7 +21,7 @@ const DIV_HOOK_ZINDEX = 2; /** - * The Accessibility manager reacreates the ability to tab and and have content read by screen + * The Accessibility manager recreates the ability to tab and and have content read by screen * readers. This is very important as it can possibly help people with disabilities access pixi * content. * diff --git a/src/accessibility/index.js b/src/accessibility/index.js index 568cfb1..0ae108f 100644 --- a/src/accessibility/index.js +++ b/src/accessibility/index.js @@ -1,4 +1,9 @@ /** + * This namespace contains a renderer plugin for interaction accessibility for end-users + * with physical impairments which require screen-renders, keyboard navigation, etc. + * + * Do not instantiate this plugin directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. * @namespace PIXI.accessibility */ export { default as accessibleTarget } from './accessibleTarget'; diff --git a/src/core/Application.js b/src/core/Application.js index 5340578..2fcc729 100644 --- a/src/core/Application.js +++ b/src/core/Application.js @@ -1,6 +1,8 @@ import { autoDetectRenderer } from './autoDetectRenderer'; import Container from './display/Container'; import { shared, Ticker } from './ticker'; +import settings from './settings'; +import { UPDATE_PRIORITY } from './const'; /** * Convenience class to create a new PIXI application. @@ -22,28 +24,52 @@ */ export default class Application { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experience unexplained flickering try setting this to true. - * @param {boolean} [useSharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedLoader=false] - `true` to use PIXI.loaders.shared, `false` to create new Loader. */ - constructor(width, height, options, noWebGL, useSharedTicker = false) + constructor(options, arg2, arg3, arg4, arg5) { + // Support for constructor(width, height, options, noWebGL, useSharedTicker) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + forceCanvas: !!arg4, + sharedTicker: !!arg5, + }, arg3); + } + + /** + * The default options, so we mixin functionality later. + * @member {object} + * @protected + */ + this._options = options = Object.assign({ + sharedTicker: false, + forceCanvas: false, + sharedLoader: false, + }, options); + /** * WebGL renderer if available, otherwise CanvasRenderer * @member {PIXI.WebGLRenderer|PIXI.CanvasRenderer} */ - this.renderer = autoDetectRenderer(width, height, options, noWebGL); + this.renderer = autoDetectRenderer(options); /** * The root display container that's rendered. @@ -63,7 +89,7 @@ * @member {PIXI.ticker.Ticker} * @default PIXI.ticker.shared */ - this.ticker = useSharedTicker ? shared : new Ticker(); + this.ticker = options.sharedTicker ? shared : new Ticker(); // Start the rendering this.start(); @@ -78,7 +104,7 @@ this._ticker = ticker; if (ticker) { - ticker.add(this.render, this); + ticker.add(this.render, this, UPDATE_PRIORITY.LOW); } } get ticker() // eslint-disable-line require-jsdoc @@ -136,13 +162,18 @@ */ destroy(removeView) { - this.stop(); + const oldTicker = this._ticker; + this.ticker = null; + oldTicker.destroy(); + this.stage.destroy(); this.stage = null; this.renderer.destroy(removeView); this.renderer = null; + + this._options = null; } } diff --git a/src/core/autoDetectRenderer.js b/src/core/autoDetectRenderer.js index 8960425..9ec0364 100644 --- a/src/core/autoDetectRenderer.js +++ b/src/core/autoDetectRenderer.js @@ -2,6 +2,7 @@ import CanvasRenderer from './renderers/canvas/CanvasRenderer'; import WebGLRenderer from './renderers/webgl/WebGLRenderer'; +// eslint-disable-next-line valid-jsdoc /** * This helper function will automatically detect which renderer you should be using. * WebGL is the preferred renderer as it is a lot faster. If webGL is not supported by @@ -9,24 +10,32 @@ * * @memberof PIXI * @function autoDetectRenderer - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @return {PIXI.WebGLRenderer|PIXI.CanvasRenderer} Returns WebGL renderer if available, otherwise CanvasRenderer */ -export function autoDetectRenderer(width = 800, height = 600, options, noWebGL) +export function autoDetectRenderer(options, arg1, arg2, arg3) { - if (!noWebGL && utils.isWebGLSupported()) + // Backward-compatible support for noWebGL option + let forceCanvas = options && options.forceCanvas; + + if (arg3 !== undefined) { - return new WebGLRenderer(width, height, options); + forceCanvas = arg3; } - return new CanvasRenderer(width, height, options); + if (!forceCanvas && utils.isWebGLSupported()) + { + return new WebGLRenderer(options, arg1, arg2); + } + + return new CanvasRenderer(options, arg1, arg2); } diff --git a/src/core/const.js b/src/core/const.js index b9e99ae..2c68dee 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -341,3 +341,27 @@ LINEAR_VERTICAL: 0, LINEAR_HORIZONTAL: 1, }; + +/** + * Represents the update priorities used by internal PIXI classes when registered with + * the {@link PIXI.ticker.Ticker} object. Higher priority items are updated first and lower + * priority items, such as render, should go later. + * + * @static + * @constant + * @name UPDATE_PRIORITY + * @memberof PIXI + * @type {object} + * @property {number} INTERACTION=50 Highest priority, used for {@link PIXI.interaction.InteractionManager} + * @property {number} HIGH=25 High priority updating, {@link PIXI.VideoBaseTexture} and {@link PIXI.extras.AnimatedSprite} + * @property {number} NORMAL=0 Default priority for ticker events, see {@link PIXI.ticker.Ticker#add}. + * @property {number} LOW=-25 Low priority used for {@link PIXI.Application} rendering. + * @property {number} UTILITY=-50 Lowest priority used for {@link PIXI.prepare.BasePrepare} utility. + */ +export const UPDATE_PRIORITY = { + INTERACTION: 50, + HIGH: 25, + NORMAL: 0, + LOW: -25, + UTILITY: -50, +}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 4e6c77d..decb42a 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -13,7 +13,6 @@ * * @class * @extends EventEmitter - * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ export default class DisplayObject extends EventEmitter @@ -113,6 +112,29 @@ * @private */ this._mask = null; + + /** + * If the object has been destroyed via destroy(). If true, it should not be used. + * + * @member {boolean} + * @private + * @readonly + */ + this._destroyed = false; + + /** + * Fired when this DisplayObject is added to a Container. + * + * @event PIXI.DisplayObject#added + * @param {PIXI.Container} container - The container added to. + */ + + /** + * Fired when this DisplayObject is removed from a Container. + * + * @event PIXI.DisplayObject#removed + * @param {PIXI.Container} container - The container removed from. + */ } /** @@ -403,6 +425,8 @@ this.interactive = false; this.interactiveChildren = false; + + this._destroyed = true; } /** diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index aafdfe2..f787c4a 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -792,8 +792,8 @@ sprite.worldAlpha = this.worldAlpha * sprite.alpha; sprite.blendMode = this.blendMode; - sprite.texture._frame.width = rect.width; - sprite.texture._frame.height = rect.height; + sprite._texture._frame.width = rect.width; + sprite._texture._frame.height = rect.height; sprite.transform.worldTransform = this.transform.worldTransform; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index a24c775..b9f192d 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -19,11 +19,12 @@ */ export default class SystemRenderer extends EventEmitter { + // eslint-disable-next-line valid-jsdoc /** * @param {string} system - The name of the system this renderer is for. - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -37,27 +38,31 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(system, screenWidth, screenHeight, options) + constructor(system, options, arg2, arg3) { super(); sayHello(system); - // prepare options - if (options) + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') { - for (const i in settings.RENDER_OPTIONS) - { - if (typeof options[i] === 'undefined') - { - options[i] = settings.RENDER_OPTIONS[i]; - } - } + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); } - else - { - options = settings.RENDER_OPTIONS; - } + + // Add the default render options + options = Object.assign({}, settings.RENDER_OPTIONS, options); + + /** + * The supplied constructor options. + * + * @member {Object} + * @readOnly + */ + this.options = options; /** * The type of the renderer. @@ -75,7 +80,7 @@ * * @member {PIXI.Rectangle} */ - this.screen = new Rectangle(0, 0, screenWidth || 800, screenHeight || 600); + this.screen = new Rectangle(0, 0, options.width, options.height); /** * The canvas element that everything is drawn to @@ -279,6 +284,8 @@ this.blendModes = null; + this.options = null; + this.preserveDrawingBuffer = false; this.clearBeforeRender = false; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index e84e49c..691caf0 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -17,10 +17,11 @@ */ export default class CanvasRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -34,9 +35,9 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('Canvas', screenWidth, screenHeight, options); + super('Canvas', options, arg2, arg3); this.type = RENDERER_TYPE.CANVAS; @@ -96,7 +97,19 @@ this.context = null; this.renderingToScreen = false; - this.resize(screenWidth, screenHeight); + this.resize(this.options.width, this.options.height); + + /** + * Fired after rendering finishes. + * + * @event PIXI.CanvasRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.CanvasRenderer#prerender + */ } /** @@ -299,4 +312,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.CanvasRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.CanvasExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.CanvasPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.CanvasRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index e1cf7cd..f6fe123 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -31,11 +31,12 @@ */ export default class WebGLRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** * - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -55,9 +56,9 @@ * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('WebGL', screenWidth, screenHeight, options); + super('WebGL', options, arg2, arg3); /** * The type of this renderer as a standardised const @@ -89,12 +90,6 @@ }; - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ this._backgroundColorRgba[3] = this.transparent ? 0 : 1; this.globalUniforms = new UniformGroup({ @@ -117,6 +112,13 @@ this.initPlugins(); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ if(options.context) { this.context.initFromContext(options.context); @@ -169,6 +171,25 @@ } return this; + + /** + * Fired after rendering finishes. + * + * @event PIXI.WebGLRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.WebGLRenderer#prerender + */ + + /** + * Fired when the WebGL context is set. + * + * @event PIXI.WebGLRenderer#context + * @param {WebGLRenderingContext} gl - WebGL context. + */ } /** @@ -295,4 +316,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.WebGLRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.WebGLRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(WebGLRenderer); diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index 4eb6128..fa3d883 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -47,6 +47,14 @@ * @member {boolean} */ this.enabled = true; + + /** + * If enabled, pixi will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; } /** diff --git a/src/core/renderers/webgl/systems/ContextSystem.js b/src/core/renderers/webgl/systems/ContextSystem.js index ff958c7..8df85ea 100644 --- a/src/core/renderers/webgl/systems/ContextSystem.js +++ b/src/core/renderers/webgl/systems/ContextSystem.js @@ -75,7 +75,8 @@ */ createContext(canvas, options) { - var gl = canvas.getContext('webgl', options) || + var gl = //canvas.getContext('webgl2', options) || + canvas.getContext('webgl', options) || canvas.getContext('experimental-webgl', options); if (!gl) diff --git a/src/core/renderers/webgl/systems/FilterSystem.js b/src/core/renderers/webgl/systems/FilterSystem.js index f47335d..37e28d2 100644 --- a/src/core/renderers/webgl/systems/FilterSystem.js +++ b/src/core/renderers/webgl/systems/FilterSystem.js @@ -111,7 +111,7 @@ // TODO we should fit the rect around the transform.. } - else + else if (filters[0].autoFit) { sourceFrame.fit(filterData.stack[0].destinationFrame); } diff --git a/src/core/renderers/webgl/systems/shader/shader/mapType.js b/src/core/renderers/webgl/systems/shader/shader/mapType.js index 581dace..917538a 100644 --- a/src/core/renderers/webgl/systems/shader/shader/mapType.js +++ b/src/core/renderers/webgl/systems/shader/shader/mapType.js @@ -15,7 +15,7 @@ } } - return GL_TABLE[type]; + return GL_TABLE[type]; }; var GL_TABLE = null; diff --git a/src/core/settings.js b/src/core/settings.js index 18c5877..38ef45c 100644 --- a/src/core/settings.js +++ b/src/core/settings.js @@ -2,6 +2,15 @@ import canUploadSameBuffer from './utils/canUploadSameBuffer'; /** + * User's customizable globals for overriding the default PIXI settings, such + * as a renderer's default resolution, framerate, float percision, etc. + * @example + * // Use the native window resolution as the default resolution + * // will support high-density displays when rendering + * PIXI.settings.RESOLUTION = window.devicePixelRatio. + * + * // Disable interpolation when scaling, will make texture be pixelated + * PIXI.settings.SCALE_MODE = PIXI.SCALE_MODES.NEAREST; * @namespace PIXI.settings */ export default { @@ -101,6 +110,9 @@ * @property {boolean} clearBeforeRender=true * @property {boolean} preserveDrawingBuffer=false * @property {boolean} roundPixels=false + * @property {number} width=800 + * @property {number} height=600 + * @property {boolean} legacy=false */ RENDER_OPTIONS: { view: null, @@ -112,6 +124,9 @@ clearBeforeRender: true, preserveDrawingBuffer: false, roundPixels: false, + width: 800, + height: 600, + legacy: false, }, /** diff --git a/src/core/shader/Program.js b/src/core/shader/Program.js index c0b538b..07b4398 100644 --- a/src/core/shader/Program.js +++ b/src/core/shader/Program.js @@ -1,6 +1,6 @@ import extractUniformsFromSrc from './extractUniformsFromSrc'; import generateUniformsSync from './generateUniformsSync'; -import glCore from 'pixi-gl-core'; +import shaderUtils from '../renderers/webgl/systems/shader/shader'; import { ProgramCache } from '../utils'; import getTestContext from '../utils/getTestContext'; @@ -64,14 +64,17 @@ } else { - vertexSrc = glCore.shader.setPrecision(vertexSrc, 'mediump'); - fragmentSrc = glCore.shader.setPrecision(fragmentSrc, 'mediump'); + vertexSrc = shaderUtils.setPrecision(vertexSrc, 'mediump'); + fragmentSrc = shaderUtils.setPrecision(fragmentSrc, 'mediump'); - const program = glCore.shader.compileProgram(gl, vertexSrc, fragmentSrc); + const program = shaderUtils.compileProgram(gl, vertexSrc, fragmentSrc); + console.log("<<<<<<>>><<"); this.attributeData = this.getAttributeData(program, gl); this.uniformData = this.getUniformData(program, gl); + console.log(this.uniformData); + //gl.deleteProgram(program); } } @@ -95,13 +98,13 @@ for (let i = 0; i < totalAttributes; i++) { const attribData = gl.getActiveAttrib(program, i); - const type = glCore.shader.mapType(gl, attribData.type); + const type = shaderUtils.mapType(gl, attribData.type); /*eslint-disable */ const data = { type: type, name: attribData.name, - size: glCore.shader.mapSize(type), + size: shaderUtils.mapSize(type), location: 0, }; /*eslint-enable */ @@ -137,13 +140,14 @@ // TODO expose this as a prop? // const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); - const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); + //const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); for (let i = 0; i < totalUniforms; i++) { const uniformData = gl.getActiveUniform(program, i); const name = uniformData.name.replace(/\[.*?\]/, ''); - const type = glCore.shader.mapType(gl, uniformData.type); + const type = shaderUtils.mapType(gl, uniformData.type); + console.log('mapping ' + uniformData.type + ' to ' + type); // if (!name.match(maskRegex)) { @@ -151,7 +155,7 @@ uniforms[name] = { type: type, size: uniformData.size, - value: glCore.shader.defaultValue(type, uniformData.size), + value: shaderUtils.defaultValue(type, uniformData.size), }; /*eslint-enable */ } diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index e387e99..b57d095 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -147,12 +147,12 @@ // so if _width is 0 then width was not set.. if (this._width) { - this.scale.x = sign(this.scale.x) * this._width / this.texture.orig.width; + this.scale.x = sign(this.scale.x) * this._width / this._texture.orig.width; } if (this._height) { - this.scale.y = sign(this.scale.y) * this._height / this.texture.orig.height; + this.scale.y = sign(this.scale.y) * this._height / this._texture.orig.height; } } diff --git a/src/core/sprites/canvas/CanvasTinter.js b/src/core/sprites/canvas/CanvasTinter.js index 27e5e0c..fd9de5d 100644 --- a/src/core/sprites/canvas/CanvasTinter.js +++ b/src/core/sprites/canvas/CanvasTinter.js @@ -18,7 +18,7 @@ */ getTintedTexture: (sprite, color) => { - const texture = sprite.texture; + const texture = sprite._texture; color = CanvasTinter.roundColor(color); diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 5a4edda..8b9b797 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -344,6 +344,7 @@ const texture = this._texture; const style = this._style; + const padding = style.trim ? 0 : style.padding; texture.baseTexture.valid = true; texture.baseTexture.resolution = this.resolution; @@ -353,11 +354,11 @@ texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - texture.trim.x = -style.padding; - texture.trim.y = -style.padding; + texture.trim.x = -padding; + texture.trim.y = -padding; - texture.orig.width = texture._frame.width - (style.padding * 2); - texture.orig.height = texture._frame.height - (style.padding * 2); + texture.orig.width = texture._frame.width - (padding * 2); + texture.orig.height = texture._frame.height - (padding * 2); // call sprite onTextureUpdate to update scale if _width or _height were set this._onTextureUpdate(); diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index 853da3d..8a9c34b 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -114,6 +114,38 @@ this.cacheId = null; this.validate(); + + /** + * Fired when a not-immediately-available source finishes loading. + * + * @protected + * @event PIXI.BaseTexture#loaded + * @param {PIXI.BaseTexture} baseTexture - Resource loaded. + */ + + /** + * Fired when a not-immediately-available source fails to load. + * + * @protected + * @event PIXI.BaseTexture#error + * @param {PIXI.BaseTexture} baseTexture - Resource errored. + */ + + /** + * Fired when BaseTexture is updated. + * + * @protected + * @event PIXI.BaseTexture#update + * @param {PIXI.BaseTexture} baseTexture - Instance of texture being updated. + */ + + /** + * Fired when BaseTexture is destroyed. + * + * @protected + * @event PIXI.BaseTexture#dispose + * @param {PIXI.BaseTexture} baseTexture - Instance of texture being destroyed. + */ } setResource(resource) diff --git a/src/core/textures/Spritesheet.js b/src/core/textures/Spritesheet.js index d469c62..9b24f27 100644 --- a/src/core/textures/Spritesheet.js +++ b/src/core/textures/Spritesheet.js @@ -208,6 +208,7 @@ // lets also add the frame to pixi's global cache for fromFrame and fromImage functions TextureCache[i] = this.textures[i]; + this.textures[i].textureCacheId = i; } frameIndex++; diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 0a2c379..2213087 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -147,9 +147,9 @@ /** * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. * - * @event update - * @memberof PIXI.Texture# + * @event PIXI.Texture#update * @protected + * @param {PIXI.Texture} texture - Instance of texture being updated. */ this._updateID = 0; @@ -159,6 +159,15 @@ * @type {Object} */ this.transform = null; + + /** + * The id under which this Texture has been added to the texture cache. This is + * automatically set in certain cases, but may not always be accurate, particularly if + * the texture is in the cache under multiple ids. + * + * @member {string} + */ + this.textureCacheId = null; } /** @@ -225,11 +234,17 @@ this._uvs = null; this.trim = null; this.orig = null; + this.textureCacheId = null; this.valid = false; - this.off('dispose', this.dispose, this); - this.off('update', this.update, this); + for (const prop in TextureCache) + { + if (TextureCache[prop] === this) + { + delete TextureCache[prop]; + } + } } /** @@ -315,14 +330,15 @@ */ static fromLoader(source, imageUrl, name) { - // console.log('added from loader...') + // console.log('added from loader...') const resource = new ImageResource(source);//.from(imageUrl, crossorigin);// document.createElement('img'); - // console.log('base resource ' + resource.width); + // console.log('base resource ' + resource.width); const baseTexture = new BaseTexture(resource, settings.SCALE_MODE, getResolutionOfUrl(imageUrl)); + /// console.log('base width ' + baseTexture.width); const texture = new Texture(baseTexture); @@ -335,6 +351,7 @@ // lets also add the frame to pixi's global cache for fromFrame and fromImage fucntions BaseTextureCache[name] = baseTexture; TextureCache[name] = texture; + texture.textureCacheId = name; // also add references by url if they are different. if (name !== imageUrl) @@ -355,6 +372,10 @@ */ static addTextureToCache(texture, id) { + if (!texture.textureCacheId) + { + texture.textureCacheId = id; + } TextureCache[id] = texture; } @@ -495,6 +516,7 @@ */ Texture.EMPTY = new Texture(new BaseTexture()); removeAllHandlers(Texture.EMPTY); +removeAllHandlers(Texture.EMPTY.baseTexture); /** * A white texture of 10x10 size, used for graphics and other things @@ -505,3 +527,4 @@ */ Texture.WHITE = createWhiteTexture(); removeAllHandlers(Texture.WHITE); +removeAllHandlers(Texture.WHITE.baseTexture); diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index 59eb9fd..a9b9f51 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -1,6 +1,7 @@ import BaseTexture from './BaseTexture'; import { uid, BaseTextureCache } from '../utils'; -import * as ticker from '../ticker'; +import { shared } from '../ticker'; +import { UPDATE_PRIORITY } from '../const'; /** * A texture of a [playing] Video. @@ -125,7 +126,7 @@ if (!this._isAutoUpdating && this.autoUpdate) { - ticker.shared.add(this.update, this); + shared.add(this.update, this, UPDATE_PRIORITY.HIGH); this._isAutoUpdating = true; } } @@ -139,7 +140,7 @@ { if (this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); this._isAutoUpdating = false; } } @@ -187,7 +188,7 @@ { if (this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); } if (this.source && this.source._pixiId) @@ -281,12 +282,12 @@ if (!this._autoUpdate && this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); this._isAutoUpdating = false; } else if (this._autoUpdate && !this._isAutoUpdating) { - ticker.shared.add(this.update, this); + shared.add(this.update, this, UPDATE_PRIORITY.HIGH); this._isAutoUpdating = true; } } diff --git a/src/core/ticker/Ticker.js b/src/core/ticker/Ticker.js index 17a1517..40df138 100644 --- a/src/core/ticker/Ticker.js +++ b/src/core/ticker/Ticker.js @@ -1,12 +1,10 @@ import settings from '../settings'; -import EventEmitter from 'eventemitter3'; - -// Internal event used by composed emitter -const TICK = 'tick'; +import { UPDATE_PRIORITY } from '../const'; +import TickerListener from './TickerListener'; /** * A Ticker class that runs an update loop that other objects listen to. - * This class is composed around an EventEmitter object to add listeners + * This class is composed around listeners * meant for execution on the next requested animation frame. * Animation frames are requested only when necessary, * e.g. When the ticker is started and the emitter has listeners. @@ -22,10 +20,11 @@ constructor() { /** - * Internal emitter used to fire 'tick' event + * The first listener. All new listeners added are chained on this. * @private + * @type {TickerListener} */ - this._emitter = new EventEmitter(); + this._head = new TickerListener(null, null, Infinity); /** * Internal current frame request ID @@ -131,7 +130,7 @@ // Invoke listeners now this.update(time); // Listener side effects may have modified ticker state. - if (this.started && this._requestId === null && this._emitter.listeners(TICK, true)) + if (this.started && this._requestId === null && this._head.next) { this._requestId = requestAnimationFrame(this._tick); } @@ -148,7 +147,7 @@ */ _requestIfNeeded() { - if (this._requestId === null && this._emitter.listeners(TICK, true)) + if (this._requestId === null && this._head.next) { // ensure callbacks get correct delta this.lastTime = performance.now(); @@ -193,35 +192,72 @@ } /** - * Calls {@link module:eventemitter3.EventEmitter#on} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. + * Register a handler for tick events. Calls continuously unless + * it is removed or the ticker is stopped. * * @param {Function} fn - The listener function to be added for updates * @param {Function} [context] - The listener context + * @param {number} [priority=PIXI.UPDATE_PRIORITY.NORMAL] - The priority for emitting * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - add(fn, context) + add(fn, context, priority = UPDATE_PRIORITY.NORMAL) { - this._emitter.on(TICK, fn, context); - - this._startIfPossible(); - - return this; + return this._addListener(new TickerListener(fn, context, priority)); } /** - * Calls {@link module:eventemitter3.EventEmitter#once} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. + * Add a handler for the tick event which is only execute once. * * @param {Function} fn - The listener function to be added for one update * @param {Function} [context] - The listener context + * @param {number} [priority=PIXI.UPDATE_PRIORITY.NORMAL] - The priority for emitting * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - addOnce(fn, context) + addOnce(fn, context, priority = UPDATE_PRIORITY.NORMAL) { - this._emitter.once(TICK, fn, context); + return this._addListener(new TickerListener(fn, context, priority, true)); + } + + /** + * Internally adds the event handler so that it can be sorted by priority. + * Priority allows certain handler (user, AnimatedSprite, Interaction) to be run + * before the rendering. + * + * @private + * @param {TickerListener} listener - Current listener being added. + * @returns {PIXI.ticker.Ticker} This instance of a ticker + */ + _addListener(listener) + { + // For attaching to head + let current = this._head.next; + let previous = this._head; + + // Add the first item + if (!current) + { + listener.connect(previous); + } + else + { + // Go from highest to lowest priority + while (current) + { + if (listener.priority >= current.priority) + { + listener.connect(previous); + break; + } + previous = current; + current = current.next; + } + + // Not yet connected + if (!listener.previous) + { + listener.connect(previous); + } + } this._startIfPossible(); @@ -229,19 +265,33 @@ } /** - * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. - * It checks if the emitter has listeners for 'tick' event. - * If it does, then it cancels the animation frame. + * Removes any handlers matching the function and context parameters. + * If no handlers are left after removing, then it cancels the animation frame. * - * @param {Function} [fn] - The listener function to be removed + * @param {Function} fn - The listener function to be removed * @param {Function} [context] - The listener context to be removed * @returns {PIXI.ticker.Ticker} This instance of a ticker */ remove(fn, context) { - this._emitter.off(TICK, fn, context); + let listener = this._head.next; - if (!this._emitter.listeners(TICK, true)) + while (listener) + { + // We found a match, lets remove it + // no break to delete all possible matches + // incase a listener was added 2+ times + if (listener.match(fn, context)) + { + listener = listener.destroy(); + } + else + { + listener = listener.next; + } + } + + if (!this._head.next) { this._cancelIfNeeded(); } @@ -276,6 +326,25 @@ } /** + * Destroy the ticker and don't use after this. Calling + * this method removes all references to internal events. + */ + destroy() + { + this.stop(); + + let listener = this._head.next; + + while (listener) + { + listener = listener.destroy(true); + } + + this._head.destroy(); + this._head = null; + } + + /** * Triggers an update. An update entails setting the * current {@link PIXI.ticker.Ticker#elapsedMS}, * the current {@link PIXI.ticker.Ticker#deltaTime}, @@ -320,8 +389,22 @@ this.deltaTime = elapsedMS * settings.TARGET_FPMS * this.speed; + // Cache a local reference, in-case ticker is destroyed + // during the emit, we can still check for head.next + const head = this._head; + // Invoke listeners added to internal emitter - this._emitter.emit(TICK, this.deltaTime); + let listener = head.next; + + while (listener) + { + listener = listener.emit(this.deltaTime); + } + + if (!head.next) + { + this._cancelIfNeeded(); + } } else { diff --git a/src/core/ticker/TickerListener.js b/src/core/ticker/TickerListener.js new file mode 100644 index 0000000..2bedb34 --- /dev/null +++ b/src/core/ticker/TickerListener.js @@ -0,0 +1,158 @@ +/** + * Internal class for handling the priority sorting of ticker handlers. + * + * @private + * @class + * @memberof PIXI.ticker + */ +export default class TickerListener +{ + /** + * Constructor + * + * @param {Function} fn - The listener function to be added for one update + * @param {Function} [context=null] - The listener context + * @param {number} [priority=0] - The priority for emitting + * @param {boolean} [once=false] - If the handler should fire once + */ + constructor(fn, context = null, priority = 0, once = false) + { + /** + * The handler function to execute. + * @member {Function} + */ + this.fn = fn; + + /** + * The calling to execute. + * @member {Function} + */ + this.context = context; + + /** + * The current priority. + * @member {number} + */ + this.priority = priority; + + /** + * If this should only execute once. + * @member {boolean} + */ + this.once = once; + + /** + * The next item in chain. + * @member {TickerListener} + */ + this.next = null; + + /** + * The previous item in chain. + * @member {TickerListener} + */ + this.previous = null; + + /** + * `true` if this listener has been destroyed already. + * @member {boolean} + * @private + */ + this._destroyed = false; + } + + /** + * Simple compare function to figure out if a function and context match. + * + * @param {Function} fn - The listener function to be added for one update + * @param {Function} context - The listener context + * @return {boolean} `true` if the listener match the arguments + */ + match(fn, context) + { + context = context || null; + + return this.fn === fn && this.context === context; + } + + /** + * Emit by calling the current function. + * @param {number} deltaTime - time since the last emit. + * @return {TickerListener} Next ticker + */ + emit(deltaTime) + { + if (this.context) + { + this.fn.call(this.context, deltaTime); + } + else + { + this.fn(deltaTime); + } + + if (this.once) + { + this.destroy(); + } + + const redirect = this.next; + + // Soft-destroying should remove + // the next reference + if (this._destroyed) + { + this.next = null; + } + + return redirect; + } + + /** + * Connect to the list. + * @param {TickerListener} previous - Input node, previous listener + */ + connect(previous) + { + this.previous = previous; + if (previous.next) + { + previous.next.previous = this; + } + this.next = previous.next; + previous.next = this; + } + + /** + * Destroy and don't use after this. + * @param {boolean} [hard = false] `true` to remove the `next` reference, this + * is considered a hard destroy. Soft destroy maintains the next reference. + * @return {TickerListener} The listener to redirect while emitting or removing. + */ + destroy(hard = false) + { + this._destroyed = true; + this.fn = null; + this.context = null; + + // Disconnect, hook up next and previous + if (this.previous) + { + this.previous.next = this.next; + } + + if (this.next) + { + this.next.previous = this.previous; + } + + // Redirect to the next item + const redirect = this.previous; + + // Remove references + this.next = hard ? null : redirect; + this.previous = null; + + return redirect; + } +} diff --git a/src/core/ticker/index.js b/src/core/ticker/index.js index 4b3017c..c3020d4 100644 --- a/src/core/ticker/index.js +++ b/src/core/ticker/index.js @@ -45,8 +45,25 @@ const shared = new Ticker(); shared.autoStart = true; +shared.destroy = () => +{ + // protect destroying shared ticker + // this is used by other internal systems + // like AnimatedSprite and InteractionManager +}; /** + * This namespace contains an API for interacting with PIXI's internal global update loop. + * + * This ticker is used for rendering, {@link PIXI.extras.AnimatedSprite AnimatedSprite}, + * {@link PIXI.interaction.InteractionManager InteractionManager} and many other time-based PIXI systems. + * @example + * const ticker = new PIXI.ticker.Ticker(); + * ticker.stop(); + * ticker.add((deltaTime) => { + * // do something every frame + * }); + * ticker.start(); * @namespace PIXI.ticker */ export { shared, Ticker }; diff --git a/src/core/utils/index.js b/src/core/utils/index.js index be44750..7441f7b 100644 --- a/src/core/utils/index.js +++ b/src/core/utils/index.js @@ -9,6 +9,21 @@ let saidHello = false; /** + * Generalized convenience utilities for PIXI. + * @example + * // Extend PIXI's internal Event Emitter. + * class MyEmitter extends PIXI.utils.EventEmitter { + * constructor() { + * super(); + * console.log("Emitter created!"); + * } + * } + * + * // Get info on current device + * console.log(PIXI.utils.isMobile); + * + * // Convert hex color to string + * console.log(PIXI.utils.hex2string(0xff00ff)); // returns: "#ff00ff" * @namespace PIXI.utils */ export { diff --git a/src/deprecation.js b/src/deprecation.js index b9c73b3..33daeec 100644 --- a/src/deprecation.js +++ b/src/deprecation.js @@ -918,6 +918,35 @@ }); /** + * @method + * @name PIXI.prepare.BasePrepare#register + * @see PIXI.prepare.BasePrepare#registerFindHook + * @deprecated since version 4.4.2 + * @param {Function} [addHook] - Function call that takes two parameters: `item:*, queue:Array` + * function must return `true` if it was able to add item to the queue. + * @param {Function} [uploadHook] - Function call that takes two parameters: `prepare:CanvasPrepare, item:*` and + * function must return `true` if it was able to handle upload of item. + * @return {PIXI.BasePrepare} Instance of plugin for chaining. + */ +prepare.BasePrepare.prototype.register = function register(addHook, uploadHook) +{ + warn('renderer.plugins.prepare.register is now deprecated, ' + + 'please use renderer.plugins.prepare.registerFindHook & renderer.plugins.prepare.registerUploadHook'); + + if (addHook) + { + this.registerFindHook(addHook); + } + + if (uploadHook) + { + this.registerUploadHook(uploadHook); + } + + return this; +}; + +/** * The number of graphics or textures to upload to the GPU. * * @name PIXI.prepare.canvas.UPLOADS_PER_FRAME @@ -969,67 +998,73 @@ }, }); -Object.defineProperties(loaders.Resource.prototype, { - isJson: { - get() - { - warn('The isJson property is deprecated, please use `resource.type === Resource.TYPE.JSON`.'); +if (loaders.Loader) +{ + const Resource = loaders.Resource; + const Loader = loaders.Loader; - return this.type === loaders.Loader.Resource.TYPE.JSON; + Object.defineProperties(Resource.prototype, { + isJson: { + get() + { + warn('The isJson property is deprecated, please use `resource.type === Resource.TYPE.JSON`.'); + + return this.type === Resource.TYPE.JSON; + }, }, - }, - isXml: { - get() - { - warn('The isXml property is deprecated, please use `resource.type === Resource.TYPE.XML`.'); + isXml: { + get() + { + warn('The isXml property is deprecated, please use `resource.type === Resource.TYPE.XML`.'); - return this.type === loaders.Loader.Resource.TYPE.XML; + return this.type === Resource.TYPE.XML; + }, }, - }, - isImage: { - get() - { - warn('The isImage property is deprecated, please use `resource.type === Resource.TYPE.IMAGE`.'); + isImage: { + get() + { + warn('The isImage property is deprecated, please use `resource.type === Resource.TYPE.IMAGE`.'); - return this.type === loaders.Loader.Resource.TYPE.IMAGE; + return this.type === Resource.TYPE.IMAGE; + }, }, - }, - isAudio: { - get() - { - warn('The isAudio property is deprecated, please use `resource.type === Resource.TYPE.AUDIO`.'); + isAudio: { + get() + { + warn('The isAudio property is deprecated, please use `resource.type === Resource.TYPE.AUDIO`.'); - return this.type === loaders.Loader.Resource.TYPE.AUDIO; + return this.type === Resource.TYPE.AUDIO; + }, }, - }, - isVideo: { - get() - { - warn('The isVideo property is deprecated, please use `resource.type === Resource.TYPE.VIDEO`.'); + isVideo: { + get() + { + warn('The isVideo property is deprecated, please use `resource.type === Resource.TYPE.VIDEO`.'); - return this.type === loaders.Loader.Resource.TYPE.VIDEO; + return this.type === Resource.TYPE.VIDEO; + }, }, - }, -}); + }); -Object.defineProperties(loaders.Loader.prototype, { - before: { - get() - { - warn('The before() method is deprecated, please use pre().'); + Object.defineProperties(Loader.prototype, { + before: { + get() + { + warn('The before() method is deprecated, please use pre().'); - return this.pre; + return this.pre; + }, }, - }, - after: { - get() - { - warn('The after() method is deprecated, please use use().'); + after: { + get() + { + warn('The after() method is deprecated, please use use().'); - return this.use; + return this.use; + }, }, - }, -}); + }); +} /** * @name PIXI.interaction.interactiveTarget#defaultCursor diff --git a/src/extract/canvas/CanvasExtract.js b/src/extract/canvas/CanvasExtract.js index 941ca05..3ac5aee 100644 --- a/src/extract/canvas/CanvasExtract.js +++ b/src/extract/canvas/CanvasExtract.js @@ -8,7 +8,7 @@ * An instance of this class is automatically created by default, and can be found at renderer.plugins.extract * * @class - * @memberof PIXI + * @memberof PIXI.extract */ export default class CanvasExtract { @@ -21,9 +21,9 @@ /** * Collection of methods for extracting data (image, pixels, etc.) from a display object or render texture * - * @member {PIXI.CanvasExtract} extract + * @member {PIXI.extract.CanvasExtract} extract * @memberof PIXI.CanvasRenderer# - * @see PIXI.CanvasExtract + * @see PIXI.extract.CanvasExtract */ renderer.extract = this; } diff --git a/src/extract/index.js b/src/extract/index.js index 0f2170d..e9478c1 100644 --- a/src/extract/index.js +++ b/src/extract/index.js @@ -1,2 +1,22 @@ +/** + * This namespace provides renderer-specific plugins for exporting content from a renderer. + * For instance, these plugins can be used for saving an Image, Canvas element or for exporting the raw image data (pixels). + * + * Do not instantiate these plugins directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. + * @example + * // Create a new app (will auto-add extract plugin to renderer) + * const app = new PIXI.Application(); + * + * // Draw a red circle + * const graphics = new PIXI.Graphics() + * .beginFill(0xFF0000) + * .drawCircle(0, 0, 50); + * + * // Render the graphics as an HTMLImageElement + * const image = app.renderer.plugins.extract.image(graphics); + * document.body.appendChild(image); + * @namespace PIXI.extract + */ export { default as webgl } from './webgl/WebGLExtract'; export { default as canvas } from './canvas/CanvasExtract'; diff --git a/src/extract/webgl/WebGLExtract.js b/src/extract/webgl/WebGLExtract.js index 60228bb..1c3dd30 100644 --- a/src/extract/webgl/WebGLExtract.js +++ b/src/extract/webgl/WebGLExtract.js @@ -9,7 +9,7 @@ * An instance of this class is automatically created by default, and can be found at renderer.plugins.extract * * @class - * @memberof PIXI + * @memberof PIXI.extract */ export default class WebGLExtract { @@ -22,9 +22,9 @@ /** * Collection of methods for extracting data (image, pixels, etc.) from a display object or render texture * - * @member {PIXI.WebGLExtract} extract + * @member {PIXI.extract.WebGLExtract} extract * @memberof PIXI.WebGLRenderer# - * @see PIXI.WebGLExtract + * @see PIXI.extract.WebGLExtract */ renderer.extract = this; } diff --git a/src/extras/AnimatedSprite.js b/src/extras/AnimatedSprite.js index 13b804f..5297778 100644 --- a/src/extras/AnimatedSprite.js +++ b/src/extras/AnimatedSprite.js @@ -137,7 +137,7 @@ this.playing = true; if (this._autoUpdate) { - core.ticker.shared.add(this.update, this); + core.ticker.shared.add(this.update, this, core.UPDATE_PRIORITY.HIGH); } } diff --git a/src/extras/TextureTransform.js b/src/extras/TextureTransform.js index 78e6e40..16536d2 100644 --- a/src/extras/TextureTransform.js +++ b/src/extras/TextureTransform.js @@ -65,8 +65,36 @@ } /** + * Multiplies uvs array to transform + * @param {Float32Array} uvs mesh uvs + * @param {Float32Array} [out=uvs] output + * @returns {Float32Array} output + */ + multiplyUvs(uvs, out) + { + if (out === undefined) + { + out = uvs; + } + + const mat = this.mapCoord; + + for (let i = 0; i < uvs.length; i += 2) + { + const x = uvs[i]; + const y = uvs[i + 1]; + + out[i] = (x * mat.a) + (y * mat.c) + mat.tx; + out[i + 1] = (x * mat.b) + (y * mat.d) + mat.ty; + } + + return out; + } + + /** * updates matrices if texture was changed * @param {boolean} forceUpdate if true, matrices will be updated any case + * @returns {boolean} whether or not it was updated */ update(forceUpdate) { @@ -74,13 +102,13 @@ if (!tex || !tex.valid) { - return; + return false; } if (!forceUpdate && this._lastTextureID === tex._updateID) { - return; + return false; } this._lastTextureID = tex._updateID; @@ -110,5 +138,7 @@ frame[3] = (tex._frame.y + tex._frame.height - margin + offset) / texBase.height; this.uClampOffset[0] = offset / texBase.realWidth; this.uClampOffset[1] = offset / texBase.realHeight; + + return true; } } diff --git a/src/extras/TilingSprite.js b/src/extras/TilingSprite.js index 1f6014f..a3f027f 100644 --- a/src/extras/TilingSprite.js +++ b/src/extras/TilingSprite.js @@ -180,9 +180,9 @@ const transform = this.worldTransform; const resolution = renderer.resolution; const baseTexture = texture.baseTexture; - const baseTextureResolution = texture.baseTexture.resolution; - const modX = (this.tilePosition.x / this.tileScale.x) % texture._frame.width; - const modY = (this.tilePosition.y / this.tileScale.y) % texture._frame.height; + const baseTextureResolution = baseTexture.resolution; + const modX = ((this.tilePosition.x / this.tileScale.x) % texture._frame.width) * baseTextureResolution; + const modY = ((this.tilePosition.y / this.tileScale.y) % texture._frame.height) * baseTextureResolution; // create a nice shiny pattern! // TODO this needs to be refreshed if texture changes.. diff --git a/src/extras/cacheAsBitmap.js b/src/extras/cacheAsBitmap.js index 5dd5640..aea06a8 100644 --- a/src/extras/cacheAsBitmap.js +++ b/src/extras/cacheAsBitmap.js @@ -227,7 +227,16 @@ this.transform._parentID = -1; // restore the transform of the cached sprite to avoid the nasty flicker.. - this.updateTransform(); + if (!this.parent) + { + this.parent = renderer._tempDisplayObjectParent; + this.updateTransform(); + this.parent = null; + } + else + { + this.updateTransform(); + } // map the hit test.. this.containsPoint = cachedSprite.containsPoint.bind(cachedSprite); @@ -314,7 +323,17 @@ cachedSprite._bounds = this._bounds; cachedSprite.alpha = cacheAlpha; - this.updateTransform(); + if (!this.parent) + { + this.parent = renderer._tempDisplayObjectParent; + this.updateTransform(); + this.parent = null; + } + else + { + this.updateTransform(); + } + this.updateTransform = this.displayObjectUpdateTransform; this._cacheData.sprite = cachedSprite; @@ -358,9 +377,12 @@ * Destroys the cached object. * * @private + * @param {object|boolean} [options] - Options parameter. A boolean will act as if all options + * have been set to that value. + * Used when destroying containers, see the Container.destroy method. */ -DisplayObject.prototype._cacheAsBitmapDestroy = function _cacheAsBitmapDestroy() +DisplayObject.prototype._cacheAsBitmapDestroy = function _cacheAsBitmapDestroy(options) { this.cacheAsBitmap = false; - this.destroy(); + this.destroy(options); }; diff --git a/src/extras/index.js b/src/extras/index.js index bbd8bbf..a34d7ec 100644 --- a/src/extras/index.js +++ b/src/extras/index.js @@ -1,7 +1,7 @@ /** + * Additional PIXI DisplayObjects for animation, tiling and bitmap text. * @namespace PIXI.extras */ -export { default as TextureTransform } from './TextureTransform'; export { default as AnimatedSprite } from './AnimatedSprite'; export { default as TilingSprite } from './TilingSprite'; export { default as TilingSpriteRenderer } from './webgl/TilingSpriteRenderer'; diff --git a/src/extras/webgl/TilingSpriteRenderer.js b/src/extras/webgl/TilingSpriteRenderer.js index aeec172..eee3b5c 100644 --- a/src/extras/webgl/TilingSpriteRenderer.js +++ b/src/extras/webgl/TilingSpriteRenderer.js @@ -11,7 +11,7 @@ * WebGL renderer plugin for tiling sprites * * @class - * @memberof PIXI + * @memberof PIXI.extras * @extends PIXI.ObjectRenderer */ export default class TilingSpriteRenderer extends core.ObjectRenderer @@ -118,7 +118,7 @@ tempMat.invert(); if (isSimple) { - tempMat.append(uv.mapCoord); + tempMat.prepend(uv.mapCoord); } else { diff --git a/src/filters/displacement/DisplacementFilter.js b/src/filters/displacement/DisplacementFilter.js index d5355c3..b5d030c 100644 --- a/src/filters/displacement/DisplacementFilter.js +++ b/src/filters/displacement/DisplacementFilter.js @@ -35,7 +35,7 @@ this.maskSprite = sprite; this.maskMatrix = maskMatrix; - this.uniforms.mapSampler = sprite.texture; + this.uniforms.mapSampler = sprite._texture; this.uniforms.filterMatrix = maskMatrix; this.uniforms.scale = { x: 1, y: 1 }; diff --git a/src/filters/index.js b/src/filters/index.js index f3e8862..7ceb50a 100644 --- a/src/filters/index.js +++ b/src/filters/index.js @@ -1,4 +1,21 @@ /** + * This namespace contains WebGL-only display filters that can be applied + * to DisplayObjects using the {@link PIXI.DisplayObject#filters filters} property. + * @example + * // Create a new application + * const app = new PIXI.Application(); + * + * // Draw a green rectangle + * const rect = new PIXI.Graphics() + * .beginFill(0x00ff00) + * .drawRect(40, 40, 200, 200); + * + * // Add a blur filter + * rect.filters = [new PIXI.filters.BlurFilter()]; + * + * // Display rectangle + * app.stage.addChild(rect); + * document.body.appendChild(app.view); * @namespace PIXI.filters */ export { default as FXAAFilter } from './fxaa/FXAAFilter'; diff --git a/src/index.js b/src/index.js index c553427..77da490 100644 --- a/src/index.js +++ b/src/index.js @@ -20,6 +20,14 @@ import { utils } from './core'; utils.mixins.performMixins(); +/** + * Alias for {@link PIXI.loaders.shared}. + * @name loader + * @memberof PIXI + * @type {PIXI.loader.Loader} + */ +const loader = loaders.shared || null; + export { accessibility, extract, @@ -30,18 +38,8 @@ mesh, particles, prepare, + loader, }; -/** - * A premade instance of the loader that can be used to load resources. - * - * @name loader - * @memberof PIXI - * @property {PIXI.loaders.Loader} - */ -const loader = loaders && loaders.Loader ? new loaders.Loader() : null; // check is there in case user excludes loader lib - -export { loader }; - // Always export pixi globally. global.PIXI = exports; // eslint-disable-line diff --git a/src/interaction/InteractionData.js b/src/interaction/InteractionData.js index 02d53a3..449f8cc 100644 --- a/src/interaction/InteractionData.js +++ b/src/interaction/InteractionData.js @@ -30,7 +30,10 @@ /** * When passed to an event handler, this will be the original DOM Event that was captured * - * @member {Event} + * @see https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent + * @see https://developer.mozilla.org/en-US/docs/Web/API/TouchEvent + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent + * @member {MouseEvent|TouchEvent|PointerEvent} */ this.originalEvent = null; } diff --git a/src/interaction/InteractionManager.js b/src/interaction/InteractionManager.js index 8baf13a..2d75c7c 100644 --- a/src/interaction/InteractionManager.js +++ b/src/interaction/InteractionManager.js @@ -13,6 +13,14 @@ const MOUSE_POINTER_ID = 'MOUSE'; +// helpers for hitTest() - only used inside hitTest() +const hitTestEvent = { + target: null, + data: { + global: null, + }, +}; + /** * The interaction manager deals with mouse, touch and pointer events. Any DisplayObject can be interactive * if its interactive parameter is set to true @@ -245,67 +253,60 @@ this.setTargetElement(this.renderer.view, this.renderer.resolution); /** - * Fired when a pointer device button (usually a mouse button) is pressed on the display + * Fired when a pointer device button (usually a mouse left-button) is pressed on the display * object. * - * @event mousedown - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mousedown + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device secondary button (usually a mouse right-button) is pressed * on the display object. * - * @event rightdown - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightdown + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** - * Fired when a pointer device button (usually a mouse button) is released over the display + * Fired when a pointer device button (usually a mouse left-button) is released over the display * object. * - * @event mouseup - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseup + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device secondary button (usually a mouse right-button) is released * over the display object. * - * @event rightup - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightup + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** - * Fired when a pointer device button (usually a mouse button) is pressed and released on + * Fired when a pointer device button (usually a mouse left-button) is pressed and released on * the display object. * - * @event click - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#click + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device secondary button (usually a mouse right-button) is pressed * and released on the display object. * - * @event rightclick - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightclick + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** - * Fired when a pointer device button (usually a mouse button) is released outside the + * Fired when a pointer device button (usually a mouse left-button) is released outside the * display object that initially registered a * [mousedown]{@link PIXI.interaction.InteractionManager#event:mousedown}. * - * @event mouseupoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** @@ -313,146 +314,362 @@ * outside the display object that initially registered a * [rightdown]{@link PIXI.interaction.InteractionManager#event:rightdown}. * - * @event rightupoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device (usually a mouse) is moved while over the display object * - * @event mousemove - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mousemove + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device (usually a mouse) is moved onto the display object * - * @event mouseover - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseover + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device (usually a mouse) is moved off the display object * - * @event mouseout - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseout + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is pressed on the display object. * - * @event pointerdown - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerdown + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is released over the display object. * - * @event pointerup - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerup + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when the operating system cancels a pointer event * - * @event pointercancel - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointercancel + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is pressed and released on the display object. * - * @event pointertap - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointertap + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is released outside the display object that initially * registered a [pointerdown]{@link PIXI.interaction.InteractionManager#event:pointerdown}. * - * @event pointerupoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device is moved while over the display object * - * @event pointermove - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointermove + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device is moved onto the display object * - * @event pointerover - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerover + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device is moved off the display object * - * @event pointerout - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerout + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is placed on the display object. * - * @event touchstart - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchstart + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is removed from the display object. * - * @event touchend - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchend + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when the operating system cancels a touch * - * @event touchcancel - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchcancel + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is placed and removed from the display object. * - * @event tap - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#tap + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is removed outside of the display object that initially * registered a [touchstart]{@link PIXI.interaction.InteractionManager#event:touchstart}. * - * @event touchendoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchendoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is moved along the display object. * - * @event touchmove - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchmove + * @param {PIXI.interaction.InteractionData} event - Interaction data */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is pressed on the display. + * object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mousedown + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is pressed + * on the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightdown + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is released over the display + * object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseup + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is released + * over the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightup + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is pressed and released on + * the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#click + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is pressed + * and released on the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightclick + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is released outside the + * display object that initially registered a + * [mousedown]{@link PIXI.DisplayObject#event:mousedown}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is released + * outside the display object that initially registered a + * [rightdown]{@link PIXI.DisplayObject#event:rightdown}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device (usually a mouse) is moved while over the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mousemove + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device (usually a mouse) is moved onto the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseover + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device (usually a mouse) is moved off the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseout + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is pressed on the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerdown + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is released over the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerup + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when the operating system cancels a pointer event. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointercancel + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is pressed and released on the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointertap + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is released outside the display object that initially + * registered a [pointerdown]{@link PIXI.DisplayObject#event:pointerdown}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device is moved while over the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointermove + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device is moved onto the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerover + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device is moved off the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerout + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is placed on the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchstart + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is removed from the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchend + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when the operating system cancels a touch. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchcancel + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is placed and removed from the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#tap + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is removed outside of the display object that initially + * registered a [touchstart]{@link PIXI.DisplayObject#event:touchstart}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchendoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is moved along the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchmove + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + } + + /** + * Hit tests a point against the display tree, returning the first interactive object that is hit. + * + * @param {PIXI.Point} globalPoint - A point to hit test with, in global space. + * @param {PIXI.Container} [root] - The root display object to start from. If omitted, defaults + * to the last rendered root of the associated renderer. + * @return {PIXI.DisplayObject} The hit display object, if any. + */ + hitTest(globalPoint, root) + { + // clear the target for our hit test + hitTestEvent.target = null; + // assign the global point + hitTestEvent.data.global = globalPoint; + // ensure safety of the root + if (!root) + { + root = this.renderer._lastObjectRendered; + } + // run the hit test + this.processInteractive(hitTestEvent, root, null, true); + // return our found object - it'll be null if we didn't hit anything + + return hitTestEvent.target; } /** @@ -487,7 +704,7 @@ return; } - core.ticker.shared.add(this.update, this); + core.ticker.shared.add(this.update, this, core.UPDATE_PRIORITY.INTERACTION); if (window.navigator.msPointerEnabled) { @@ -804,8 +1021,6 @@ } } - let keepHitTestingAfterChildren = hitTest; - // ** FREE TIP **! If an object is not interactive or has no buttons in it // (such as a game scene!) set interactiveChildren to false for that displayObject. // This will allow pixi to completely ignore and bypass checking the displayObjects children. @@ -818,7 +1033,9 @@ const child = children[i]; // time to get recursive.. if this function will return if something is hit.. - if (this.processInteractive(interactionEvent, child, func, hitTest, interactiveParent)) + const childHit = this.processInteractive(interactionEvent, child, func, hitTest, interactiveParent); + + if (childHit) { // its a good idea to check if a child has lost its parent. // this means it has been removed whilst looping so its best @@ -827,8 +1044,6 @@ continue; } - hit = true; - // we no longer need to hit test any more objects in this container as we we // now know the parent has been hit interactiveParent = false; @@ -838,36 +1053,41 @@ // This means we no longer need to hit test anything else. We still need to run // through all objects, but we don't need to perform any hit tests. - keepHitTestingAfterChildren = false; - - if (child.interactive) + if (childHit) { - hitTest = false; + if (interactionEvent.target) + { + hitTest = false; + } + hit = true; } - - // we can break now as we have hit an object. } } } - hitTest = keepHitTestingAfterChildren; - // no point running this if the item is not interactive or does not have an interactive parent. if (interactive) { // if we are hit testing (as in we have no hit any objects yet) // We also don't need to worry about hit testing if once of the displayObjects children - // has already been hit! - if (hitTest && !hit) + // has already been hit - but only if it was interactive, otherwise we need to keep + // looking for an interactive child, just in case we hit one + if (hitTest && !interactionEvent.target) { if (displayObject.hitArea) { displayObject.worldTransform.applyInverse(point, this._tempPoint); - hit = displayObject.hitArea.contains(this._tempPoint.x, this._tempPoint.y); + if (displayObject.hitArea.contains(this._tempPoint.x, this._tempPoint.y)) + { + hit = true; + } } else if (displayObject.containsPoint) { - hit = displayObject.containsPoint(point); + if (displayObject.containsPoint(point)) + { + hit = true; + } } } @@ -878,7 +1098,10 @@ interactionEvent.target = displayObject; } - func(interactionEvent, displayObject, hit); + if (func) + { + func(interactionEvent, displayObject, !!hit); + } } } @@ -914,7 +1137,7 @@ { const event = events[i]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -998,7 +1221,7 @@ { const event = events[i]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1098,7 +1321,7 @@ const test = isRightButton ? flags.RIGHT_DOWN : flags.LEFT_DOWN; - const isDown = trackingData !== undefined && (trackingData.flags | test); + const isDown = trackingData !== undefined && (trackingData.flags & test); if (hit) { @@ -1118,11 +1341,11 @@ { if (isRightButton) { - trackingData.rightDown = hit; + trackingData.rightDown = false; } else { - trackingData.leftDown = hit; + trackingData.leftDown = false; } } } @@ -1180,7 +1403,7 @@ { const event = events[i]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1255,7 +1478,7 @@ this.setCursorMode(null); } - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1268,6 +1491,12 @@ { this.emit('mouseout', interactionEvent); } + else + { + // we can get touchleave events after touchend, so we want to make sure we don't + // introduce memory leaks + this.releaseInteractionDataForPointerId(interactionData.identifier); + } } /** @@ -1344,7 +1573,7 @@ // Only mouse and pointer can call onPointerOver, so events will always be length 1 const event = events[0]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1366,12 +1595,14 @@ * Get InteractionData for a given pointerId. Store that data as well * * @private - * @param {number} pointerId - Identifier from a pointer event + * @param {PointerEvent} event - Normalized pointer event, output from normalizeToPointerData * @return {InteractionData} - Interaction data for the given pointer identifier */ - getInteractionDataForPointerId(pointerId) + getInteractionDataForPointerId(event) { - if (pointerId === MOUSE_POINTER_ID) + const pointerId = event.pointerId; + + if (pointerId === MOUSE_POINTER_ID || event.pointerType === 'mouse') { return this.mouse; } diff --git a/src/interaction/InteractionTrackingData.js b/src/interaction/InteractionTrackingData.js index 6a1f69e..8493b63 100644 --- a/src/interaction/InteractionTrackingData.js +++ b/src/interaction/InteractionTrackingData.js @@ -35,8 +35,10 @@ } /** + * Unique pointer id of the event + * * @readonly - * @type {number} Unique pointer id of the event + * @member {number} */ get pointerId() { diff --git a/src/interaction/index.js b/src/interaction/index.js index 56c5b09..72f7d71 100644 --- a/src/interaction/index.js +++ b/src/interaction/index.js @@ -1,4 +1,8 @@ /** + * This namespace contains a renderer plugin for handling mouse, pointer, and touch events. + * + * Do not instantiate this plugin directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. * @namespace PIXI.interaction */ export { default as InteractionData } from './InteractionData'; diff --git a/src/interaction/interactiveTarget.js b/src/interaction/interactiveTarget.js index d252fba..cebac40 100644 --- a/src/interaction/interactiveTarget.js +++ b/src/interaction/interactiveTarget.js @@ -2,7 +2,7 @@ * Default property values of interactive objects * Used by {@link PIXI.interaction.InteractionManager} to automatically give all DisplayObjects these properties * - * @mixin + * @private * @name interactiveTarget * @memberof PIXI.interaction * @example @@ -14,18 +14,28 @@ * ); */ export default { + /** - * Determines if the displayObject be clicked/touched + * Enable interaction events for the DisplayObject. Touch, pointer and mouse + * events will not be emitted unless `interactive` is set to `true`. * - * @inner {boolean} + * @example + * const sprite = new PIXI.Sprite(texture); + * sprite.interactive = true; + * sprite.on('tap', (event) => { + * //handle event + * }); + * @member {boolean} + * @memberof PIXI.DisplayObject# */ interactive: false, /** * Determines if the children to the displayObject can be clicked/touched - * Setting this to false allows pixi to bypass a recursive hitTest function + * Setting this to false allows pixi to bypass a recursive `hitTest` function * - * @inner {boolean} + * @member {boolean} + * @memberof PIXI.Container# */ interactiveChildren: true, @@ -33,7 +43,12 @@ * Interaction shape. Children will be hit first, then this shape will be checked. * Setting this will cause this shape to be checked in hit tests rather than the displayObject's bounds. * - * @inner {PIXI.Rectangle|PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.RoundedRectangle} + * @example + * const sprite = new PIXI.Sprite(texture); + * sprite.interactive = true; + * sprite.hitArea = new PIXI.Rectangle(0, 0, 100, 100); + * @member {PIXI.Rectangle|PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.RoundedRectangle} + * @memberof PIXI.DisplayObject# */ hitArea: null, @@ -41,8 +56,12 @@ * If enabled, the mouse cursor use the pointer behavior when hovered over the displayObject if it is interactive * Setting this changes the 'cursor' property to `'pointer'`. * + * @example + * const sprite = new PIXI.Sprite(texture); + * sprite.interactive = true; + * sprite.buttonMode = true; * @member {boolean} - * @memberof PIXI.interaction.interactiveTarget# + * @memberof PIXI.DisplayObject# */ get buttonMode() { @@ -64,9 +83,14 @@ * This defines what cursor mode is used when the mouse cursor * is hovered over the displayObject. * + * @example + * const sprite = new PIXI.Sprite(texture); + * sprite.interactive = true; + * sprite.cursor = 'wait'; * @see https://developer.mozilla.org/en/docs/Web/CSS/cursor * - * @inner {string} + * @member {string} + * @memberof PIXI.DisplayObject# */ cursor: null, @@ -74,7 +98,7 @@ * Internal set of all active pointers, by identifier * * @member {Map} - * @memberof PIXI.interaction.interactiveTarget# + * @memberof PIXI.DisplayObject# * @private */ get trackedPointers() @@ -87,7 +111,8 @@ /** * Map of all tracked pointers, by identifier. Use trackedPointers to access. * - * @private {Map} + * @private + * @type {Map} */ _trackedPointers: undefined, }; diff --git a/src/loaders/index.js b/src/loaders/index.js index 5189793..daa1fc4 100644 --- a/src/loaders/index.js +++ b/src/loaders/index.js @@ -1,9 +1,22 @@ +import Application from '../core/Application'; +import Loader from './loader'; + /** + * This namespace contains APIs which extends the {@link https://github.com/englercj/resource-loader resource-loader} module + * for loading assets, data, and other resources dynamically. + * @example + * const loader = new PIXI.loaders.Loader(); + * loader.add('bunny', 'data/bunny.png') + * .add('spaceship', 'assets/spritesheet.json'); + * loader.load((loader, resources) => { + * // resources.bunny + * // resources.spaceship + * }); * @namespace PIXI.loaders */ -export { default as Loader } from './loader'; +export { Loader }; export { default as bitmapFontParser, parse as parseBitmapFontData } from './bitmapFontParser'; -export { default as spritesheetParser } from './spritesheetParser'; +export { default as spritesheetParser, getResourcePath } from './spritesheetParser'; export { default as textureParser } from './textureParser'; /** @@ -13,3 +26,55 @@ * @memberof PIXI.loaders */ export { Resource } from 'resource-loader'; + +/** + * A premade instance of the loader that can be used to load resources. + * @name shared + * @memberof PIXI.loaders + * @type {PIXI.loaders.Loader} + */ +const shared = new Loader(); + +shared.destroy = () => +{ + // protect destroying shared loader +}; + +export { shared }; + +// Mixin the loader construction +const AppPrototype = Application.prototype; + +AppPrototype._loader = null; + +/** + * Loader instance to help with asset loading. + * @name PIXI.Application#loader + * @type {PIXI.loaders.Loader} + */ +Object.defineProperty(AppPrototype, 'loader', { + get() + { + if (!this._loader) + { + const sharedLoader = this._options.sharedLoader; + + this._loader = sharedLoader ? shared : new Loader(); + } + + return this._loader; + }, +}); + +// Override the destroy function +// making sure to destroy the current Loader +AppPrototype._parentDestroy = AppPrototype.destroy; +AppPrototype.destroy = function destroy() +{ + if (this._loader) + { + this._loader.destroy(); + this._loader = null; + } + this._parentDestroy(); +}; diff --git a/src/loaders/loader.js b/src/loaders/loader.js index 767a411..59b8000 100644 --- a/src/loaders/loader.js +++ b/src/loaders/loader.js @@ -90,6 +90,15 @@ { Loader._pixiMiddleware.push(fn); } + + /** + * Destroy the loader, removes references. + */ + destroy() + { + this.removeAllListeners(); + this.reset(); + } } // Copy EE3 prototype (mixin) diff --git a/src/loaders/spritesheetParser.js b/src/loaders/spritesheetParser.js index e57dfb2..eda9584 100644 --- a/src/loaders/spritesheetParser.js +++ b/src/loaders/spritesheetParser.js @@ -1,12 +1,11 @@ import { Resource } from 'resource-loader'; -import path from 'path'; +import url from 'url'; import { Spritesheet } from '../core'; export default function () { return function spritesheetParser(resource, next) { - let resourcePath; const imageResourceName = `${resource.name}_image`; // skip if no data, its not json, it isn't spritesheet data, or the image resource already exists @@ -28,15 +27,7 @@ parentResource: resource, }; - // Prepend url path unless the resource image is a data url - if (resource.isDataUrl) - { - resourcePath = resource.data.meta.image; - } - else - { - resourcePath = `${path.dirname(resource.url.replace(this.baseUrl, ''))}/${resource.data.meta.image}`; - } + const resourcePath = getResourcePath(resource, this.baseUrl); // load the image for this sheet this.add(imageResourceName, resourcePath, loadOptions, function onImageLoad(res) @@ -56,3 +47,14 @@ }); }; } + +export function getResourcePath(resource, baseUrl) +{ + // Prepend url path unless the resource image is a data url + if (resource.isDataUrl) + { + return resource.data.meta.image; + } + + return url.resolve(resource.url.replace(baseUrl, ''), resource.data.meta.image); +} diff --git a/src/mesh/canvas/CanvasMeshRenderer.js b/src/mesh/canvas/CanvasMeshRenderer.js index 297205f..f7899e1 100644 --- a/src/mesh/canvas/CanvasMeshRenderer.js +++ b/src/mesh/canvas/CanvasMeshRenderer.js @@ -134,12 +134,33 @@ const textureWidth = base.width; const textureHeight = base.height; - const u0 = uvs[index0] * base.width; - const u1 = uvs[index1] * base.width; - const u2 = uvs[index2] * base.width; - const v0 = uvs[index0 + 1] * base.height; - const v1 = uvs[index1 + 1] * base.height; - const v2 = uvs[index2 + 1] * base.height; + let u0; + let u1; + let u2; + let v0; + let v1; + let v2; + + if (mesh.uploadUvTransform) + { + const ut = mesh._uvTransform.mapCoord; + + u0 = ((uvs[index0] * ut.a) + (uvs[index0 + 1] * ut.c) + ut.tx) * base.width; + u1 = ((uvs[index1] * ut.a) + (uvs[index1 + 1] * ut.c) + ut.tx) * base.width; + u2 = ((uvs[index2] * ut.a) + (uvs[index2 + 1] * ut.c) + ut.tx) * base.width; + v0 = ((uvs[index0] * ut.b) + (uvs[index0 + 1] * ut.d) + ut.ty) * base.height; + v1 = ((uvs[index1] * ut.b) + (uvs[index1 + 1] * ut.d) + ut.ty) * base.height; + v2 = ((uvs[index2] * ut.b) + (uvs[index2 + 1] * ut.d) + ut.ty) * base.height; + } + else + { + u0 = uvs[index0] * base.width; + u1 = uvs[index1] * base.width; + u2 = uvs[index2] * base.width; + v0 = uvs[index0 + 1] * base.height; + v1 = uvs[index1 + 1] * base.height; + v2 = uvs[index2 + 1] * base.height; + } let x0 = vertices[index0]; let x1 = vertices[index1]; diff --git a/src/mesh/webgl/MeshRenderer.js b/src/mesh/webgl/MeshRenderer.js index 78d24c3..ab99fcd 100644 --- a/src/mesh/webgl/MeshRenderer.js +++ b/src/mesh/webgl/MeshRenderer.js @@ -2,6 +2,8 @@ const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; +const matrixIdentity = core.Matrix.IDENTITY; + /** * WebGL renderer plugin for tiling sprites * @@ -64,6 +66,7 @@ mesh.geometry.glVertexArrayObjects[this.CONTEXT_UID].draw(mesh.drawMode, mesh.size, mesh.start, mesh.geometry.instanceCount); } + /** * draws mesh * @param {PIXI.mesh.RawMesh} mesh mesh instance diff --git a/src/mesh/webgl/mesh.vert b/src/mesh/webgl/mesh.vert index a337aef..acc096c 100644 --- a/src/mesh/webgl/mesh.vert +++ b/src/mesh/webgl/mesh.vert @@ -1,8 +1,9 @@ attribute vec2 aVertexPosition; attribute vec2 aTextureCoord; -uniform mat3 translationMatrix; uniform mat3 projectionMatrix; +uniform mat3 translationMatrix; +uniform mat3 uTransform; varying vec2 vTextureCoord; @@ -10,5 +11,5 @@ { gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - vTextureCoord = aTextureCoord; + vTextureCoord = (uTransform * vec3(aTextureCoord, 1.0)).xy; } diff --git a/src/particles/ParticleContainer.js b/src/particles/ParticleContainer.js index 67796cf..63f7791 100644 --- a/src/particles/ParticleContainer.js +++ b/src/particles/ParticleContainer.js @@ -313,10 +313,10 @@ frame.y * resolution, frame.width * resolution, frame.height * resolution, - positionX * resolution, - positionY * resolution, - finalWidth * resolution, - finalHeight * resolution + positionX * renderer.resolution, + positionY * renderer.resolution, + finalWidth * renderer.resolution, + finalHeight * renderer.resolution ); } } diff --git a/src/prepare/BasePrepare.js b/src/prepare/BasePrepare.js index 09eb5e5..e45c4dc 100644 --- a/src/prepare/BasePrepare.js +++ b/src/prepare/BasePrepare.js @@ -18,6 +18,18 @@ * basic queuing functionality and is extended by {@link PIXI.prepare.WebGLPrepare} and {@link PIXI.prepare.CanvasPrepare} * to provide preparation capabilities specific to their respective renderers. * + * @example + * // Create a sprite + * const sprite = new PIXI.Sprite.fromImage('something.png'); + * + * // Load object into GPU + * app.renderer.plugins.prepare.upload(sprite, () => { + * + * //Texture(s) has been uploaded to GPU + * app.stage.addChild(sprite); + * + * }) + * * @abstract * @class * @memberof PIXI.prepare @@ -100,8 +112,16 @@ this.prepareItems(); }; - this.register(findText, drawText); - this.register(findTextStyle, calculateTextStyle); + // hooks to find the correct texture + this.registerFindHook(findText); + this.registerFindHook(findTextStyle); + this.registerFindHook(findMultipleBaseTextures); + this.registerFindHook(findBaseTexture); + this.registerFindHook(findTexture); + + // upload hooks + this.registerUploadHook(drawText); + this.registerUploadHook(calculateTextStyle); } /** @@ -138,7 +158,7 @@ if (!this.ticking) { this.ticking = true; - SharedTicker.addOnce(this.tick, this); + SharedTicker.addOnce(this.tick, this, core.UPDATE_PRIORITY.UTILITY); } } else if (done) @@ -172,13 +192,16 @@ const item = this.queue[0]; let uploaded = false; - for (let i = 0, len = this.uploadHooks.length; i < len; i++) + if (item && !item._destroyed) { - if (this.uploadHooks[i](this.uploadHookHelper, item)) + for (let i = 0, len = this.uploadHooks.length; i < len; i++) { - this.queue.shift(); - uploaded = true; - break; + if (this.uploadHooks[i](this.uploadHookHelper, item)) + { + this.queue.shift(); + uploaded = true; + break; + } } } @@ -205,26 +228,36 @@ else { // if we are not finished, on the next rAF do this again - SharedTicker.addOnce(this.tick, this); + SharedTicker.addOnce(this.tick, this, core.UPDATE_PRIORITY.UTILITY); } } /** - * Adds hooks for finding and uploading items. + * Adds hooks for finding items. * - * @param {Function} [addHook] - Function call that takes two parameters: `item:*, queue:Array` - function must return `true` if it was able to add item to the queue. - * @param {Function} [uploadHook] - Function call that takes two parameters: `prepare:CanvasPrepare, item:*` and - * function must return `true` if it was able to handle upload of item. - * @return {PIXI.CanvasPrepare} Instance of plugin for chaining. + * @param {Function} addHook - Function call that takes two parameters: `item:*, queue:Array` + * function must return `true` if it was able to add item to the queue. + * @return {PIXI.BasePrepare} Instance of plugin for chaining. */ - register(addHook, uploadHook) + registerFindHook(addHook) { if (addHook) { this.addHooks.push(addHook); } + return this; + } + + /** + * Adds hooks for uploading items. + * + * @param {Function} uploadHook - Function call that takes two parameters: `prepare:CanvasPrepare, item:*` and + * function must return `true` if it was able to handle upload of item. + * @return {PIXI.BasePrepare} Instance of plugin for chaining. + */ + registerUploadHook(uploadHook) + { if (uploadHook) { this.uploadHooks.push(uploadHook); @@ -287,6 +320,88 @@ } /** + * Built-in hook to find multiple textures from objects like AnimatedSprites. + * + * @private + * @param {PIXI.DisplayObject} item - Display object to check + * @param {Array<*>} queue - Collection of items to upload + * @return {boolean} if a PIXI.Texture object was found. + */ +function findMultipleBaseTextures(item, queue) +{ + let result = false; + + // Objects with mutliple textures + if (item && item._textures && item._textures.length) + { + for (let i = 0; i < item._textures.length; i++) + { + if (item._textures[i] instanceof core.Texture) + { + const baseTexture = item._textures[i].baseTexture; + + if (queue.indexOf(baseTexture) === -1) + { + queue.push(baseTexture); + result = true; + } + } + } + } + + return result; +} + +/** + * Built-in hook to find BaseTextures from Sprites. + * + * @private + * @param {PIXI.DisplayObject} item - Display object to check + * @param {Array<*>} queue - Collection of items to upload + * @return {boolean} if a PIXI.Texture object was found. + */ +function findBaseTexture(item, queue) +{ + // Objects with textures, like Sprites/Text + if (item instanceof core.BaseTexture) + { + if (queue.indexOf(item) === -1) + { + queue.push(item); + } + + return true; + } + + return false; +} + +/** + * Built-in hook to find textures from objects. + * + * @private + * @param {PIXI.DisplayObject} item - Display object to check + * @param {Array<*>} queue - Collection of items to upload + * @return {boolean} if a PIXI.Texture object was found. + */ +function findTexture(item, queue) +{ + if (item._texture && item._texture instanceof core.Texture) + { + const texture = item._texture.baseTexture; + + if (queue.indexOf(texture) === -1) + { + queue.push(texture); + } + + return true; + } + + return false; +} + +/** * Built-in hook to draw PIXI.Text to its texture. * * @private diff --git a/src/prepare/canvas/CanvasPrepare.js b/src/prepare/canvas/CanvasPrepare.js index 0224bb5..09c207a 100644 --- a/src/prepare/canvas/CanvasPrepare.js +++ b/src/prepare/canvas/CanvasPrepare.js @@ -43,7 +43,7 @@ this.ctx = this.canvas.getContext('2d'); // Add textures to upload - this.register(findBaseTextures, uploadBaseTextures); + this.registerUploadHook(uploadBaseTextures); } /** @@ -89,39 +89,4 @@ return false; } -/** - * Built-in hook to find textures from Sprites. - * - * @private - * @param {PIXI.DisplayObject} item -Display object to check - * @param {Array<*>} queue - Collection of items to upload - * @return {boolean} if a PIXI.Texture object was found. - */ -function findBaseTextures(item, queue) -{ - // Objects with textures, like Sprites/Text - if (item instanceof core.BaseTexture) - { - if (queue.indexOf(item) === -1) - { - queue.push(item); - } - - return true; - } - else if (item._texture && item._texture instanceof core.Texture) - { - const texture = item._texture.baseTexture; - - if (queue.indexOf(texture) === -1) - { - queue.push(texture); - } - - return true; - } - - return false; -} - core.CanvasRenderer.registerPlugin('prepare', CanvasPrepare); diff --git a/src/prepare/index.js b/src/prepare/index.js index f559c45..e7491ec 100644 --- a/src/prepare/index.js +++ b/src/prepare/index.js @@ -1,4 +1,29 @@ /** + * The prepare namespace provides renderer-specific plugins for pre-rendering DisplayObjects. These plugins are useful for + * asynchronously preparing assets, textures, graphics waiting to be displayed. + * + * Do not instantiate these plugins directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. + * @example + * // Create a new application + * const app = new PIXI.Application(); + * document.body.appendChild(app.view); + * + * // Don't start rendering right away + * app.stop(); + * + * // create a display object + * const rect = new PIXI.Graphics() + * .beginFill(0x00ff00) + * .drawRect(40, 40, 200, 200); + * + * // Add to the stage + * app.stage.addChild(rect); + * + * // Don't start rendering until the graphic is uploaded to the GPU + * app.renderer.plugins.prepare.upload(app.stage, () => { + * app.start(); + * }); * @namespace PIXI.prepare */ export { default as webgl } from './webgl/WebGLPrepare'; diff --git a/src/prepare/webgl/WebGLPrepare.js b/src/prepare/webgl/WebGLPrepare.js index eb7023f..9df3c3f 100644 --- a/src/prepare/webgl/WebGLPrepare.js +++ b/src/prepare/webgl/WebGLPrepare.js @@ -22,12 +22,11 @@ this.uploadHookHelper = this.renderer; // Add textures and graphics to upload - this.register(findBaseTextures, uploadBaseTextures) - .register(findGraphics, uploadGraphics); + this.registerFindHook(findGraphics); + this.registerUploadHook(uploadBaseTextures); + this.registerUploadHook(uploadGraphics); } - } - /** * Built-in hook to upload PIXI.Texture objects to the GPU. * @@ -80,41 +79,6 @@ } /** - * Built-in hook to find textures from Sprites. - * - * @private - * @param {PIXI.DisplayObject} item - Display object to check - * @param {Array<*>} queue - Collection of items to upload - * @return {boolean} if a PIXI.Texture object was found. - */ -function findBaseTextures(item, queue) -{ - // Objects with textures, like Sprites/Text - if (item instanceof core.BaseTexture) - { - if (queue.indexOf(item) === -1) - { - queue.push(item); - } - - return true; - } - else if (item._texture && item._texture instanceof core.Texture) - { - const texture = item._texture.baseTexture; - - if (queue.indexOf(texture) === -1) - { - queue.push(texture); - } - - return true; - } - - return false; -} - -/** * Built-in hook to find graphics. * * @private diff --git a/test/.eslintrc.json b/test/.eslintrc.json index e03220d..ac12110 100644 --- a/test/.eslintrc.json +++ b/test/.eslintrc.json @@ -12,7 +12,8 @@ "sinon": false, "expect": false, "assert": false, - "PIXI": false + "PIXI": false, + "PointerEvent": true }, "rules": { "func-names": 0, diff --git a/test/core/BaseTexture.js b/test/core/BaseTexture.js index 8b93478..bb65068 100644 --- a/test/core/BaseTexture.js +++ b/test/core/BaseTexture.js @@ -14,4 +14,28 @@ expect(baseTexture.imageType).to.be.equals('png'); }); }); + + it('should remove Canvas BaseTexture from cache on destroy', function () + { + const canvas = document.createElement('canvas'); + const texture = PIXI.BaseTexture.fromCanvas(canvas); + const _pixiId = canvas._pixiId; + + expect(PIXI.utils.BaseTextureCache[_pixiId]).to.equal(texture); + texture.destroy(); + expect(PIXI.utils.BaseTextureCache[_pixiId]).to.equal(undefined); + }); + + it('should remove Image BaseTexture from cache on destroy', function () + { + const URL = 'foo.png'; + const NAME = 'bar'; + const image = new Image(); + + const texture = PIXI.Texture.fromLoader(image, URL, NAME); + + expect(PIXI.utils.BaseTextureCache[NAME]).to.equal(texture.baseTexture); + texture.destroy(true); + expect(PIXI.utils.BaseTextureCache[NAME]).to.equal(undefined); + }); }); diff --git a/test/core/Circle.js b/test/core/Circle.js index 183f98f..f621992 100644 --- a/test/core/Circle.js +++ b/test/core/Circle.js @@ -53,6 +53,10 @@ expect(circ1.contains(10, 16)).to.be.false; expect(circ1.contains(11, 15)).to.be.false; expect(circ1.contains(0, 0)).to.be.false; + + const circ2 = new PIXI.Circle(10, 10, 0); + + expect(circ2.contains(10, 10)).to.be.false; }); it('should return framing rectangle', function () diff --git a/test/core/Container.js b/test/core/Container.js index d11be33..cefde54 100644 --- a/test/core/Container.js +++ b/test/core/Container.js @@ -2,30 +2,36 @@ function testAddChild(fn) { - fn(function (container, obj) + return function () { - container.addChild(obj); - }); - fn(function (container, obj) - { - container.addChildAt(obj); - }); + fn(function (container, obj) + { + container.addChild(obj); + }); + fn(function (container, obj) + { + container.addChildAt(obj); + }); + }; } function testRemoveChild(fn) { - fn(function (container, obj) + return function () { - container.removeChild(obj); - }); - fn(function (container, obj) - { - container.removeChildAt(container.children.indexOf(obj)); - }); - fn(function (container, obj) - { - container.removeChildren(container.children.indexOf(obj), container.children.indexOf(obj) + 1); - }); + fn(function (container, obj) + { + container.removeChild(obj); + }); + fn(function (container, obj) + { + container.removeChildAt(container.children.indexOf(obj)); + }); + fn(function (container, obj) + { + container.removeChildren(container.children.indexOf(obj), container.children.indexOf(obj) + 1); + }); + }; } describe('PIXI.Container', function () diff --git a/test/core/Ellipse.js b/test/core/Ellipse.js index f2e8234..3f47442 100644 --- a/test/core/Ellipse.js +++ b/test/core/Ellipse.js @@ -56,6 +56,10 @@ expect(ellipse1.contains(10, 16)).to.be.false; expect(ellipse1.contains(11, 15)).to.be.false; expect(ellipse1.contains(0, 0)).to.be.false; + + const ellipse2 = new PIXI.Ellipse(10, 10, 0, 0); + + expect(ellipse2.contains(10, 10)).to.be.false; }); it('should return framing rectangle', function () diff --git a/test/core/Matrix.js b/test/core/Matrix.js index 9d2694b..76bd57c 100644 --- a/test/core/Matrix.js +++ b/test/core/Matrix.js @@ -107,10 +107,60 @@ expect(m1.ty).to.equal(m2.ty); }); + it('should prepend matrix', function () + { + const m1 = new PIXI.Matrix(); + const m2 = new PIXI.Matrix(); + + m2.set(2, 3, 4, 5, 100, 200); + m1.prepend(m2); + + expect(m1.a).to.equal(m2.a); + expect(m1.b).to.equal(m2.b); + expect(m1.c).to.equal(m2.c); + expect(m1.d).to.equal(m2.d); + expect(m1.tx).to.equal(m2.tx); + expect(m1.ty).to.equal(m2.ty); + + const m3 = new PIXI.Matrix(); + const m4 = new PIXI.Matrix(); + + m3.prepend(m4); + + expect(m3.a).to.equal(m4.a); + expect(m3.b).to.equal(m4.b); + expect(m3.c).to.equal(m4.c); + expect(m3.d).to.equal(m4.d); + expect(m3.tx).to.equal(m4.tx); + expect(m3.ty).to.equal(m4.ty); + }); + it('should get IDENTITY and TEMP_MATRIX', function () { expect(PIXI.Matrix.IDENTITY instanceof PIXI.Matrix).to.be.true; expect(PIXI.Matrix.TEMP_MATRIX instanceof PIXI.Matrix).to.be.true; }); -}); + it('should reset matrix to default when identity() is called', function () + { + const matrix = new PIXI.Matrix(); + + matrix.set(2, 3, 4, 5, 100, 200); + + expect(matrix.a).to.equal(2); + expect(matrix.b).to.equal(3); + expect(matrix.c).to.equal(4); + expect(matrix.d).to.equal(5); + expect(matrix.tx).to.equal(100); + expect(matrix.ty).to.equal(200); + + matrix.identity(); + + expect(matrix.a).to.equal(1); + expect(matrix.b).to.equal(0); + expect(matrix.c).to.equal(0); + expect(matrix.d).to.equal(1); + expect(matrix.tx).to.equal(0); + expect(matrix.ty).to.equal(0); + }); +}); diff --git a/test/core/Spritesheet.js b/test/core/Spritesheet.js index 283adf5..7601a0c 100644 --- a/test/core/Spritesheet.js +++ b/test/core/Spritesheet.js @@ -18,6 +18,7 @@ expect(textures[id]).to.be.an.instanceof(PIXI.Texture); expect(textures[id].width).to.equal(spritesheet.data.frames[id].frame.w / spritesheet.resolution); expect(textures[id].height).to.equal(spritesheet.data.frames[id].frame.h / spritesheet.resolution); + expect(textures[id].textureCacheId).to.equal(id); spritesheet.destroy(true); expect(spritesheet.textures).to.be.null; expect(spritesheet.baseTexture).to.be.null; diff --git a/test/core/Texture.js b/test/core/Texture.js index 8c4ef98..241ec3e 100644 --- a/test/core/Texture.js +++ b/test/core/Texture.js @@ -16,4 +16,20 @@ expect(PIXI.utils.TextureCache[URL]).to.equal(texture); expect(PIXI.utils.BaseTextureCache[URL]).to.equal(texture.baseTexture); }); + + it('should remove Texture from cache on destroy', function () + { + const NAME = 'foo'; + const NAME2 = 'bar'; + const texture = new PIXI.Texture(new PIXI.BaseTexture()); + + PIXI.Texture.addTextureToCache(texture, NAME); + PIXI.Texture.addTextureToCache(texture, NAME2); + expect(texture.textureCacheId).to.equal(NAME); + expect(PIXI.utils.TextureCache[NAME]).to.equal(texture); + expect(PIXI.utils.TextureCache[NAME2]).to.equal(texture); + texture.destroy(); + expect(PIXI.utils.TextureCache[NAME]).to.equal(undefined); + expect(PIXI.utils.TextureCache[NAME2]).to.equal(undefined); + }); }); diff --git a/test/core/Ticker.js b/test/core/Ticker.js new file mode 100644 index 0000000..188770f --- /dev/null +++ b/test/core/Ticker.js @@ -0,0 +1,322 @@ +'use strict'; + +const Ticker = PIXI.ticker.Ticker; +const shared = PIXI.ticker.shared; + +describe('PIXI.ticker.Ticker', function () +{ + before(function () + { + this.length = (ticker) => + { + ticker = ticker || shared; + + if (!ticker._head || !ticker._head.next) + { + return 0; + } + + let listener = ticker._head.next; + let i = 0; + + while (listener) + { + listener = listener.next; + i++; + } + + return i; + }; + }); + + it('should be available', function () + { + expect(Ticker).to.be.a.function; + expect(shared).to.be.an.instanceof(Ticker); + }); + + it('should create a new ticker and destroy it', function () + { + const ticker = new Ticker(); + + ticker.start(); + + const listener = sinon.spy(); + + expect(this.length(ticker)).to.equal(0); + + ticker.add(listener); + + expect(this.length(ticker)).to.equal(1); + + ticker.destroy(); + + expect(ticker._head).to.be.null; + expect(ticker.started).to.be.false; + expect(this.length(ticker)).to.equal(0); + }); + + it('should protect destroying shared ticker', function () + { + shared.destroy(); + expect(shared._head).to.not.be.null; + expect(shared.started).to.be.true; + }); + + it('should add and remove listener', function () + { + const listener = sinon.spy(); + const length = this.length(); + + shared.add(listener); + + expect(this.length()).to.equal(length + 1); + + shared.remove(listener); + + expect(this.length()).to.equal(length); + }); + + it('should update a listener', function () + { + const listener = sinon.spy(); + + shared.add(listener); + shared.update(); + + expect(listener.calledOnce).to.be.true; + + shared.remove(listener); + shared.update(); + + expect(listener.calledOnce).to.be.true; + }); + + it('should update a listener twice and remove once', function () + { + const listener = sinon.spy(); + const length = this.length(); + + shared.add(listener).add(listener); + shared.update(); + + expect(listener.calledTwice).to.be.true; + expect(this.length()).to.equal(length + 2); + + shared.remove(listener); + shared.update(); + + expect(listener.calledTwice).to.be.true; + expect(this.length()).to.equal(length); + }); + + it('should respect priority order', function () + { + const length = this.length(); + const listener1 = sinon.spy(); + const listener2 = sinon.spy(); + const listener3 = sinon.spy(); + const listener4 = sinon.spy(); + + shared.add(listener1, null, PIXI.UPDATE_PRIORITY.LOW) + .add(listener4, null, PIXI.UPDATE_PRIORITY.INTERACTION) + .add(listener3, null, PIXI.UPDATE_PRIORITY.HIGH) + .add(listener2, null, PIXI.UPDATE_PRIORITY.NORMAL); + + shared.update(); + + expect(this.length()).to.equal(length + 4); + + sinon.assert.callOrder(listener4, listener3, listener2, listener1); + + shared.remove(listener1) + .remove(listener2) + .remove(listener3) + .remove(listener4); + + expect(this.length()).to.equal(length); + }); + + it('should auto-remove once listeners', function () + { + const length = this.length(); + const listener = sinon.spy(); + + shared.addOnce(listener); + + shared.update(); + + expect(listener.calledOnce).to.be.true; + expect(this.length()).to.equal(length); + }); + + it('should call inserted item with a lower priority', function () + { + const length = this.length(); + const lowListener = sinon.spy(); + const highListener = sinon.spy(); + const mainListener = sinon.spy(() => + { + shared.add(highListener, null, PIXI.UPDATE_PRIORITY.HIGH); + shared.add(lowListener, null, PIXI.UPDATE_PRIORITY.LOW); + }); + + shared.add(mainListener, null, PIXI.UPDATE_PRIORITY.NORMAL); + + shared.update(); + + expect(this.length()).to.equal(length + 3); + + expect(mainListener.calledOnce).to.be.true; + expect(lowListener.calledOnce).to.be.true; + expect(highListener.calledOnce).to.be.false; + + shared.remove(mainListener) + .remove(highListener) + .remove(lowListener); + + expect(this.length()).to.equal(length); + }); + + it('should remove emit low-priority item during emit', function () + { + const length = this.length(); + const listener2 = sinon.spy(); + const listener1 = sinon.spy(() => + { + shared.add(listener2, null, PIXI.UPDATE_PRIORITY.LOW); + }); + + shared.add(listener1, null, PIXI.UPDATE_PRIORITY.NORMAL); + + shared.update(); + + expect(this.length()).to.equal(length + 2); + + expect(listener2.calledOnce).to.be.true; + expect(listener1.calledOnce).to.be.true; + + shared.remove(listener1) + .remove(listener2); + + expect(this.length()).to.equal(length); + }); + + it('should remove itself on emit after adding new item', function () + { + const length = this.length(); + const listener2 = sinon.spy(); + const listener1 = sinon.spy(() => + { + shared.add(listener2, null, PIXI.UPDATE_PRIORITY.LOW); + shared.remove(listener1); + + // listener is removed right away + expect(this.length()).to.equal(length + 1); + }); + + shared.add(listener1, null, PIXI.UPDATE_PRIORITY.NORMAL); + + shared.update(); + + expect(this.length()).to.equal(length + 1); + + expect(listener2.calledOnce).to.be.true; + expect(listener1.calledOnce).to.be.true; + + shared.remove(listener2); + + expect(this.length()).to.equal(length); + }); + + it('should remove itself before, still calling new item', function () + { + const length = this.length(); + const listener2 = sinon.spy(); + const listener1 = sinon.spy(() => + { + shared.remove(listener1); + shared.add(listener2, null, PIXI.UPDATE_PRIORITY.LOW); + + // listener is removed right away + expect(this.length()).to.equal(length + 1); + }); + + shared.add(listener1, null, PIXI.UPDATE_PRIORITY.NORMAL); + + shared.update(); + + expect(this.length()).to.equal(length + 1); + + expect(listener2.calledOnce).to.be.true; + expect(listener1.calledOnce).to.be.true; + + shared.remove(listener2); + + expect(this.length()).to.equal(length); + }); + + it('should remove items before and after current priority', function () + { + const length = this.length(); + const listener2 = sinon.spy(); + const listener3 = sinon.spy(); + const listener4 = sinon.spy(); + + shared.add(listener2, null, PIXI.UPDATE_PRIORITY.HIGH); + shared.add(listener3, null, PIXI.UPDATE_PRIORITY.LOW); + shared.add(listener4, null, PIXI.UPDATE_PRIORITY.LOW); + + const listener1 = sinon.spy(() => + { + shared.remove(listener2) + .remove(listener3); + + // listener is removed right away + expect(this.length()).to.equal(length + 2); + }); + + shared.add(listener1, null, PIXI.UPDATE_PRIORITY.NORMAL); + + shared.update(); + + expect(this.length()).to.equal(length + 2); + + expect(listener2.calledOnce).to.be.true; + expect(listener3.calledOnce).to.be.false; + expect(listener4.calledOnce).to.be.true; + expect(listener1.calledOnce).to.be.true; + + shared.update(); + + expect(listener2.calledOnce).to.be.true; + expect(listener3.calledOnce).to.be.false; + expect(listener4.calledTwice).to.be.true; + expect(listener1.calledTwice).to.be.true; + + shared.remove(listener1) + .remove(listener4); + + expect(this.length()).to.equal(length); + }); + + it('should destroy on listener', function (done) + { + const ticker = new Ticker(); + const listener2 = sinon.spy(); + const listener = sinon.spy(() => + { + ticker.destroy(); + setTimeout(() => + { + expect(listener2.called).to.be.false; + expect(listener.calledOnce).to.be.true; + done(); + }, 0); + }); + + ticker.add(listener); + ticker.add(listener2, null, PIXI.UPDATE_PRIORITY.LOW); + ticker.start(); + }); +}); diff --git a/test/core/TilingSprite.js b/test/core/TilingSprite.js index 2e449a7..7694792 100644 --- a/test/core/TilingSprite.js +++ b/test/core/TilingSprite.js @@ -24,4 +24,25 @@ expect(bounds.height).to.equal(600); }); }); + + it('checks if tilingSprite contains a point', function () + { + const texture = new PIXI.Texture(new PIXI.BaseTexture()); + const tilingSprite = new PIXI.extras.TilingSprite(texture, 200, 300); + + expect(tilingSprite.containsPoint(new PIXI.Point(1, 1))).to.equal(true); + expect(tilingSprite.containsPoint(new PIXI.Point(300, 400))).to.equal(false); + }); + + it('gets and sets height and width correctly', function () + { + const texture = new PIXI.Texture(new PIXI.BaseTexture()); + const tilingSprite = new PIXI.extras.TilingSprite(texture, 200, 300); + + tilingSprite.width = 400; + tilingSprite.height = 600; + + expect(tilingSprite.width).to.equal(400); + expect(tilingSprite.height).to.equal(600); + }); }); diff --git a/.travis.yml b/.travis.yml index 3df8b22..14de334 100644 --- a/.travis.yml +++ b/.travis.yml @@ -110,3 +110,43 @@ on: all_branches: true condition: $TRAVIS_TAG + # Deploy config for latest release + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: dist + upload-dir: "release" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: docs + upload-dir: "release/docs" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: coverage + upload-dir: "release/coverage" + on: + all_branches: true + condition: $TRAVIS_TAG \ No newline at end of file diff --git a/README.md b/README.md index d26fadd..3b85f85 100644 --- a/README.md +++ b/README.md @@ -36,22 +36,14 @@ - Wiki: Other misc tutorials and resources are [on the Wiki](https://github.com/pixijs/pixi.js/wiki/Resources). ### Community ### -- Forums: Check out the [forum] (http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow] (http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. +- Forums: Check out the [forum](http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow](http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. - Inspiration: Check out the [gallery](http://www.pixijs.com/gallery) to see some of the amazing things people have created! - Chat: You can join us on [Gitter](https://gitter.im/pixijs/pixi.js) To chat about Pixi. We also now have a Slack channel. If you would like to join it please Send me an email (mat@goodboydigital.com) and I will invite you in. ### Setup ### -It's easy to get started with Pixi.js! Simply grab the pre-built versions from here: - -Release Branch - Nice and stable Pixi.js -- Unminified: [http://pixijs.download/release/pixi.js] -- Minified: [http://pixijs.download/release/pixi.min.js] - -Develop Branch - The bleeding edge version of Pixi.js -- Unminified: [http://pixijs.download/dev/pixi.js] -- Minified: [http://pixijs.download/dev/pixi.min.js] +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. @@ -70,10 +62,10 @@ #### CDN Install (via cdnjs) ```html - + ``` -_Note: `4.2.2` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ +_Note: `4.4.3` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ ### Demos ### @@ -130,7 +122,7 @@ // and the root stage PIXI.Container. var app = new PIXI.Application(); -// The application will create a canvas element for you that you +// The application will create a canvas element for you that you // can then insert into the DOM. document.body.appendChild(app.view); @@ -150,7 +142,7 @@ // Add the bunny to the scene we are building. app.stage.addChild(bunny); - + // Listen for frame updates app.ticker.add(function() { // each frame we spin the bunny around a bit diff --git a/bower.json b/bower.json deleted file mode 100644 index 41b668c..0000000 --- a/bower.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "pixi.js", - "main": "dist/pixi.min.js", - "ignore": [ - "**/.*", - "docs", - "node_modules", - "bower_components", - "test", - "scripts", - "bower.json", - "inch.json" - ], - "dependencies": { - }, - "devDependencies": { - } -} diff --git a/package.json b/package.json index d35cbb2..308469f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.3.5", + "version": "4.4.4", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ @@ -40,11 +40,11 @@ "lib": "babel src --out-dir lib -s", "predocs": "rimraf docs/**", "docs": "jsdoc -c scripts/jsdoc.conf.json -R README.md", - "publish:patch": "npm version patch --no-git-tag-version && npm publish", - "publish:minor": "npm version minor --no-git-tag-version && npm publish", - "publish:major": "npm version major --no-git-tag-version && npm publish", + "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", - "postpublish": "node scripts/release.js" + "postpublish": "git push && git push --tags" }, "files": [ "dist/", @@ -72,7 +72,6 @@ "electron": "^1.4.15", "eslint": "^3.5.0", "floss": "^2.0.1", - "gh-pages": "^0.11.0", "jaguarjs-jsdoc": "^1.0.1", "js-md5": "^0.4.1", "jsdoc": "^3.4.2", diff --git a/scripts/jsdoc.conf.json b/scripts/jsdoc.conf.json index 553e61c..3f52180 100644 --- a/scripts/jsdoc.conf.json +++ b/scripts/jsdoc.conf.json @@ -43,7 +43,7 @@ }, "markdown" : { "parser" : "gfm", - "hardwrap" : true + "hardwrap" : false }, "opts": { "encoding" : "utf8", diff --git a/scripts/release.js b/scripts/release.js deleted file mode 100644 index 156620c..0000000 --- a/scripts/release.js +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env node -'use strict'; - -// Publish script to push releases of the bin files -// the normally are gitignored -const ghpages = require('gh-pages'); -const path = require('path'); -const packageInfo = require(path.join(__dirname, '..', 'package.json')); - -const options = { - src: [ - 'dist/**/*', - 'lib/**/*', - 'src/**/*', - 'scripts/*', - 'scripts/renders/*', - 'test/**/*', - '*.json', - '*.md', - 'LICENSE', - '.eslintrc', - '.editorconfig', - '.travis.yml', - '.babelrc', - ], - dotfiles: true, - branch: 'release', - message: packageInfo.version, - logger: console.log.bind(console), -}; - -ghpages.publish(process.cwd(), options, (err) => -{ - if (err) - { - console.log(err); - process.exit(1); - - return; - } - - process.exit(0); -}); diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index f16bcb1..220427a 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -21,7 +21,7 @@ const DIV_HOOK_ZINDEX = 2; /** - * The Accessibility manager reacreates the ability to tab and and have content read by screen + * The Accessibility manager recreates the ability to tab and and have content read by screen * readers. This is very important as it can possibly help people with disabilities access pixi * content. * diff --git a/src/accessibility/index.js b/src/accessibility/index.js index 568cfb1..0ae108f 100644 --- a/src/accessibility/index.js +++ b/src/accessibility/index.js @@ -1,4 +1,9 @@ /** + * This namespace contains a renderer plugin for interaction accessibility for end-users + * with physical impairments which require screen-renders, keyboard navigation, etc. + * + * Do not instantiate this plugin directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. * @namespace PIXI.accessibility */ export { default as accessibleTarget } from './accessibleTarget'; diff --git a/src/core/Application.js b/src/core/Application.js index 5340578..2fcc729 100644 --- a/src/core/Application.js +++ b/src/core/Application.js @@ -1,6 +1,8 @@ import { autoDetectRenderer } from './autoDetectRenderer'; import Container from './display/Container'; import { shared, Ticker } from './ticker'; +import settings from './settings'; +import { UPDATE_PRIORITY } from './const'; /** * Convenience class to create a new PIXI application. @@ -22,28 +24,52 @@ */ export default class Application { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experience unexplained flickering try setting this to true. - * @param {boolean} [useSharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedLoader=false] - `true` to use PIXI.loaders.shared, `false` to create new Loader. */ - constructor(width, height, options, noWebGL, useSharedTicker = false) + constructor(options, arg2, arg3, arg4, arg5) { + // Support for constructor(width, height, options, noWebGL, useSharedTicker) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + forceCanvas: !!arg4, + sharedTicker: !!arg5, + }, arg3); + } + + /** + * The default options, so we mixin functionality later. + * @member {object} + * @protected + */ + this._options = options = Object.assign({ + sharedTicker: false, + forceCanvas: false, + sharedLoader: false, + }, options); + /** * WebGL renderer if available, otherwise CanvasRenderer * @member {PIXI.WebGLRenderer|PIXI.CanvasRenderer} */ - this.renderer = autoDetectRenderer(width, height, options, noWebGL); + this.renderer = autoDetectRenderer(options); /** * The root display container that's rendered. @@ -63,7 +89,7 @@ * @member {PIXI.ticker.Ticker} * @default PIXI.ticker.shared */ - this.ticker = useSharedTicker ? shared : new Ticker(); + this.ticker = options.sharedTicker ? shared : new Ticker(); // Start the rendering this.start(); @@ -78,7 +104,7 @@ this._ticker = ticker; if (ticker) { - ticker.add(this.render, this); + ticker.add(this.render, this, UPDATE_PRIORITY.LOW); } } get ticker() // eslint-disable-line require-jsdoc @@ -136,13 +162,18 @@ */ destroy(removeView) { - this.stop(); + const oldTicker = this._ticker; + this.ticker = null; + oldTicker.destroy(); + this.stage.destroy(); this.stage = null; this.renderer.destroy(removeView); this.renderer = null; + + this._options = null; } } diff --git a/src/core/autoDetectRenderer.js b/src/core/autoDetectRenderer.js index 8960425..9ec0364 100644 --- a/src/core/autoDetectRenderer.js +++ b/src/core/autoDetectRenderer.js @@ -2,6 +2,7 @@ import CanvasRenderer from './renderers/canvas/CanvasRenderer'; import WebGLRenderer from './renderers/webgl/WebGLRenderer'; +// eslint-disable-next-line valid-jsdoc /** * This helper function will automatically detect which renderer you should be using. * WebGL is the preferred renderer as it is a lot faster. If webGL is not supported by @@ -9,24 +10,32 @@ * * @memberof PIXI * @function autoDetectRenderer - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @return {PIXI.WebGLRenderer|PIXI.CanvasRenderer} Returns WebGL renderer if available, otherwise CanvasRenderer */ -export function autoDetectRenderer(width = 800, height = 600, options, noWebGL) +export function autoDetectRenderer(options, arg1, arg2, arg3) { - if (!noWebGL && utils.isWebGLSupported()) + // Backward-compatible support for noWebGL option + let forceCanvas = options && options.forceCanvas; + + if (arg3 !== undefined) { - return new WebGLRenderer(width, height, options); + forceCanvas = arg3; } - return new CanvasRenderer(width, height, options); + if (!forceCanvas && utils.isWebGLSupported()) + { + return new WebGLRenderer(options, arg1, arg2); + } + + return new CanvasRenderer(options, arg1, arg2); } diff --git a/src/core/const.js b/src/core/const.js index b9e99ae..2c68dee 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -341,3 +341,27 @@ LINEAR_VERTICAL: 0, LINEAR_HORIZONTAL: 1, }; + +/** + * Represents the update priorities used by internal PIXI classes when registered with + * the {@link PIXI.ticker.Ticker} object. Higher priority items are updated first and lower + * priority items, such as render, should go later. + * + * @static + * @constant + * @name UPDATE_PRIORITY + * @memberof PIXI + * @type {object} + * @property {number} INTERACTION=50 Highest priority, used for {@link PIXI.interaction.InteractionManager} + * @property {number} HIGH=25 High priority updating, {@link PIXI.VideoBaseTexture} and {@link PIXI.extras.AnimatedSprite} + * @property {number} NORMAL=0 Default priority for ticker events, see {@link PIXI.ticker.Ticker#add}. + * @property {number} LOW=-25 Low priority used for {@link PIXI.Application} rendering. + * @property {number} UTILITY=-50 Lowest priority used for {@link PIXI.prepare.BasePrepare} utility. + */ +export const UPDATE_PRIORITY = { + INTERACTION: 50, + HIGH: 25, + NORMAL: 0, + LOW: -25, + UTILITY: -50, +}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 4e6c77d..decb42a 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -13,7 +13,6 @@ * * @class * @extends EventEmitter - * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ export default class DisplayObject extends EventEmitter @@ -113,6 +112,29 @@ * @private */ this._mask = null; + + /** + * If the object has been destroyed via destroy(). If true, it should not be used. + * + * @member {boolean} + * @private + * @readonly + */ + this._destroyed = false; + + /** + * Fired when this DisplayObject is added to a Container. + * + * @event PIXI.DisplayObject#added + * @param {PIXI.Container} container - The container added to. + */ + + /** + * Fired when this DisplayObject is removed from a Container. + * + * @event PIXI.DisplayObject#removed + * @param {PIXI.Container} container - The container removed from. + */ } /** @@ -403,6 +425,8 @@ this.interactive = false; this.interactiveChildren = false; + + this._destroyed = true; } /** diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index aafdfe2..f787c4a 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -792,8 +792,8 @@ sprite.worldAlpha = this.worldAlpha * sprite.alpha; sprite.blendMode = this.blendMode; - sprite.texture._frame.width = rect.width; - sprite.texture._frame.height = rect.height; + sprite._texture._frame.width = rect.width; + sprite._texture._frame.height = rect.height; sprite.transform.worldTransform = this.transform.worldTransform; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index a24c775..b9f192d 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -19,11 +19,12 @@ */ export default class SystemRenderer extends EventEmitter { + // eslint-disable-next-line valid-jsdoc /** * @param {string} system - The name of the system this renderer is for. - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -37,27 +38,31 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(system, screenWidth, screenHeight, options) + constructor(system, options, arg2, arg3) { super(); sayHello(system); - // prepare options - if (options) + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') { - for (const i in settings.RENDER_OPTIONS) - { - if (typeof options[i] === 'undefined') - { - options[i] = settings.RENDER_OPTIONS[i]; - } - } + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); } - else - { - options = settings.RENDER_OPTIONS; - } + + // Add the default render options + options = Object.assign({}, settings.RENDER_OPTIONS, options); + + /** + * The supplied constructor options. + * + * @member {Object} + * @readOnly + */ + this.options = options; /** * The type of the renderer. @@ -75,7 +80,7 @@ * * @member {PIXI.Rectangle} */ - this.screen = new Rectangle(0, 0, screenWidth || 800, screenHeight || 600); + this.screen = new Rectangle(0, 0, options.width, options.height); /** * The canvas element that everything is drawn to @@ -279,6 +284,8 @@ this.blendModes = null; + this.options = null; + this.preserveDrawingBuffer = false; this.clearBeforeRender = false; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index e84e49c..691caf0 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -17,10 +17,11 @@ */ export default class CanvasRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -34,9 +35,9 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('Canvas', screenWidth, screenHeight, options); + super('Canvas', options, arg2, arg3); this.type = RENDERER_TYPE.CANVAS; @@ -96,7 +97,19 @@ this.context = null; this.renderingToScreen = false; - this.resize(screenWidth, screenHeight); + this.resize(this.options.width, this.options.height); + + /** + * Fired after rendering finishes. + * + * @event PIXI.CanvasRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.CanvasRenderer#prerender + */ } /** @@ -299,4 +312,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.CanvasRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.CanvasExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.CanvasPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.CanvasRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index e1cf7cd..f6fe123 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -31,11 +31,12 @@ */ export default class WebGLRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** * - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -55,9 +56,9 @@ * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('WebGL', screenWidth, screenHeight, options); + super('WebGL', options, arg2, arg3); /** * The type of this renderer as a standardised const @@ -89,12 +90,6 @@ }; - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ this._backgroundColorRgba[3] = this.transparent ? 0 : 1; this.globalUniforms = new UniformGroup({ @@ -117,6 +112,13 @@ this.initPlugins(); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ if(options.context) { this.context.initFromContext(options.context); @@ -169,6 +171,25 @@ } return this; + + /** + * Fired after rendering finishes. + * + * @event PIXI.WebGLRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.WebGLRenderer#prerender + */ + + /** + * Fired when the WebGL context is set. + * + * @event PIXI.WebGLRenderer#context + * @param {WebGLRenderingContext} gl - WebGL context. + */ } /** @@ -295,4 +316,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.WebGLRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.WebGLRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(WebGLRenderer); diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index 4eb6128..fa3d883 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -47,6 +47,14 @@ * @member {boolean} */ this.enabled = true; + + /** + * If enabled, pixi will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; } /** diff --git a/src/core/renderers/webgl/systems/ContextSystem.js b/src/core/renderers/webgl/systems/ContextSystem.js index ff958c7..8df85ea 100644 --- a/src/core/renderers/webgl/systems/ContextSystem.js +++ b/src/core/renderers/webgl/systems/ContextSystem.js @@ -75,7 +75,8 @@ */ createContext(canvas, options) { - var gl = canvas.getContext('webgl', options) || + var gl = //canvas.getContext('webgl2', options) || + canvas.getContext('webgl', options) || canvas.getContext('experimental-webgl', options); if (!gl) diff --git a/src/core/renderers/webgl/systems/FilterSystem.js b/src/core/renderers/webgl/systems/FilterSystem.js index f47335d..37e28d2 100644 --- a/src/core/renderers/webgl/systems/FilterSystem.js +++ b/src/core/renderers/webgl/systems/FilterSystem.js @@ -111,7 +111,7 @@ // TODO we should fit the rect around the transform.. } - else + else if (filters[0].autoFit) { sourceFrame.fit(filterData.stack[0].destinationFrame); } diff --git a/src/core/renderers/webgl/systems/shader/shader/mapType.js b/src/core/renderers/webgl/systems/shader/shader/mapType.js index 581dace..917538a 100644 --- a/src/core/renderers/webgl/systems/shader/shader/mapType.js +++ b/src/core/renderers/webgl/systems/shader/shader/mapType.js @@ -15,7 +15,7 @@ } } - return GL_TABLE[type]; + return GL_TABLE[type]; }; var GL_TABLE = null; diff --git a/src/core/settings.js b/src/core/settings.js index 18c5877..38ef45c 100644 --- a/src/core/settings.js +++ b/src/core/settings.js @@ -2,6 +2,15 @@ import canUploadSameBuffer from './utils/canUploadSameBuffer'; /** + * User's customizable globals for overriding the default PIXI settings, such + * as a renderer's default resolution, framerate, float percision, etc. + * @example + * // Use the native window resolution as the default resolution + * // will support high-density displays when rendering + * PIXI.settings.RESOLUTION = window.devicePixelRatio. + * + * // Disable interpolation when scaling, will make texture be pixelated + * PIXI.settings.SCALE_MODE = PIXI.SCALE_MODES.NEAREST; * @namespace PIXI.settings */ export default { @@ -101,6 +110,9 @@ * @property {boolean} clearBeforeRender=true * @property {boolean} preserveDrawingBuffer=false * @property {boolean} roundPixels=false + * @property {number} width=800 + * @property {number} height=600 + * @property {boolean} legacy=false */ RENDER_OPTIONS: { view: null, @@ -112,6 +124,9 @@ clearBeforeRender: true, preserveDrawingBuffer: false, roundPixels: false, + width: 800, + height: 600, + legacy: false, }, /** diff --git a/src/core/shader/Program.js b/src/core/shader/Program.js index c0b538b..07b4398 100644 --- a/src/core/shader/Program.js +++ b/src/core/shader/Program.js @@ -1,6 +1,6 @@ import extractUniformsFromSrc from './extractUniformsFromSrc'; import generateUniformsSync from './generateUniformsSync'; -import glCore from 'pixi-gl-core'; +import shaderUtils from '../renderers/webgl/systems/shader/shader'; import { ProgramCache } from '../utils'; import getTestContext from '../utils/getTestContext'; @@ -64,14 +64,17 @@ } else { - vertexSrc = glCore.shader.setPrecision(vertexSrc, 'mediump'); - fragmentSrc = glCore.shader.setPrecision(fragmentSrc, 'mediump'); + vertexSrc = shaderUtils.setPrecision(vertexSrc, 'mediump'); + fragmentSrc = shaderUtils.setPrecision(fragmentSrc, 'mediump'); - const program = glCore.shader.compileProgram(gl, vertexSrc, fragmentSrc); + const program = shaderUtils.compileProgram(gl, vertexSrc, fragmentSrc); + console.log("<<<<<<>>><<"); this.attributeData = this.getAttributeData(program, gl); this.uniformData = this.getUniformData(program, gl); + console.log(this.uniformData); + //gl.deleteProgram(program); } } @@ -95,13 +98,13 @@ for (let i = 0; i < totalAttributes; i++) { const attribData = gl.getActiveAttrib(program, i); - const type = glCore.shader.mapType(gl, attribData.type); + const type = shaderUtils.mapType(gl, attribData.type); /*eslint-disable */ const data = { type: type, name: attribData.name, - size: glCore.shader.mapSize(type), + size: shaderUtils.mapSize(type), location: 0, }; /*eslint-enable */ @@ -137,13 +140,14 @@ // TODO expose this as a prop? // const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); - const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); + //const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); for (let i = 0; i < totalUniforms; i++) { const uniformData = gl.getActiveUniform(program, i); const name = uniformData.name.replace(/\[.*?\]/, ''); - const type = glCore.shader.mapType(gl, uniformData.type); + const type = shaderUtils.mapType(gl, uniformData.type); + console.log('mapping ' + uniformData.type + ' to ' + type); // if (!name.match(maskRegex)) { @@ -151,7 +155,7 @@ uniforms[name] = { type: type, size: uniformData.size, - value: glCore.shader.defaultValue(type, uniformData.size), + value: shaderUtils.defaultValue(type, uniformData.size), }; /*eslint-enable */ } diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index e387e99..b57d095 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -147,12 +147,12 @@ // so if _width is 0 then width was not set.. if (this._width) { - this.scale.x = sign(this.scale.x) * this._width / this.texture.orig.width; + this.scale.x = sign(this.scale.x) * this._width / this._texture.orig.width; } if (this._height) { - this.scale.y = sign(this.scale.y) * this._height / this.texture.orig.height; + this.scale.y = sign(this.scale.y) * this._height / this._texture.orig.height; } } diff --git a/src/core/sprites/canvas/CanvasTinter.js b/src/core/sprites/canvas/CanvasTinter.js index 27e5e0c..fd9de5d 100644 --- a/src/core/sprites/canvas/CanvasTinter.js +++ b/src/core/sprites/canvas/CanvasTinter.js @@ -18,7 +18,7 @@ */ getTintedTexture: (sprite, color) => { - const texture = sprite.texture; + const texture = sprite._texture; color = CanvasTinter.roundColor(color); diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 5a4edda..8b9b797 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -344,6 +344,7 @@ const texture = this._texture; const style = this._style; + const padding = style.trim ? 0 : style.padding; texture.baseTexture.valid = true; texture.baseTexture.resolution = this.resolution; @@ -353,11 +354,11 @@ texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - texture.trim.x = -style.padding; - texture.trim.y = -style.padding; + texture.trim.x = -padding; + texture.trim.y = -padding; - texture.orig.width = texture._frame.width - (style.padding * 2); - texture.orig.height = texture._frame.height - (style.padding * 2); + texture.orig.width = texture._frame.width - (padding * 2); + texture.orig.height = texture._frame.height - (padding * 2); // call sprite onTextureUpdate to update scale if _width or _height were set this._onTextureUpdate(); diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index 853da3d..8a9c34b 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -114,6 +114,38 @@ this.cacheId = null; this.validate(); + + /** + * Fired when a not-immediately-available source finishes loading. + * + * @protected + * @event PIXI.BaseTexture#loaded + * @param {PIXI.BaseTexture} baseTexture - Resource loaded. + */ + + /** + * Fired when a not-immediately-available source fails to load. + * + * @protected + * @event PIXI.BaseTexture#error + * @param {PIXI.BaseTexture} baseTexture - Resource errored. + */ + + /** + * Fired when BaseTexture is updated. + * + * @protected + * @event PIXI.BaseTexture#update + * @param {PIXI.BaseTexture} baseTexture - Instance of texture being updated. + */ + + /** + * Fired when BaseTexture is destroyed. + * + * @protected + * @event PIXI.BaseTexture#dispose + * @param {PIXI.BaseTexture} baseTexture - Instance of texture being destroyed. + */ } setResource(resource) diff --git a/src/core/textures/Spritesheet.js b/src/core/textures/Spritesheet.js index d469c62..9b24f27 100644 --- a/src/core/textures/Spritesheet.js +++ b/src/core/textures/Spritesheet.js @@ -208,6 +208,7 @@ // lets also add the frame to pixi's global cache for fromFrame and fromImage functions TextureCache[i] = this.textures[i]; + this.textures[i].textureCacheId = i; } frameIndex++; diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 0a2c379..2213087 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -147,9 +147,9 @@ /** * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. * - * @event update - * @memberof PIXI.Texture# + * @event PIXI.Texture#update * @protected + * @param {PIXI.Texture} texture - Instance of texture being updated. */ this._updateID = 0; @@ -159,6 +159,15 @@ * @type {Object} */ this.transform = null; + + /** + * The id under which this Texture has been added to the texture cache. This is + * automatically set in certain cases, but may not always be accurate, particularly if + * the texture is in the cache under multiple ids. + * + * @member {string} + */ + this.textureCacheId = null; } /** @@ -225,11 +234,17 @@ this._uvs = null; this.trim = null; this.orig = null; + this.textureCacheId = null; this.valid = false; - this.off('dispose', this.dispose, this); - this.off('update', this.update, this); + for (const prop in TextureCache) + { + if (TextureCache[prop] === this) + { + delete TextureCache[prop]; + } + } } /** @@ -315,14 +330,15 @@ */ static fromLoader(source, imageUrl, name) { - // console.log('added from loader...') + // console.log('added from loader...') const resource = new ImageResource(source);//.from(imageUrl, crossorigin);// document.createElement('img'); - // console.log('base resource ' + resource.width); + // console.log('base resource ' + resource.width); const baseTexture = new BaseTexture(resource, settings.SCALE_MODE, getResolutionOfUrl(imageUrl)); + /// console.log('base width ' + baseTexture.width); const texture = new Texture(baseTexture); @@ -335,6 +351,7 @@ // lets also add the frame to pixi's global cache for fromFrame and fromImage fucntions BaseTextureCache[name] = baseTexture; TextureCache[name] = texture; + texture.textureCacheId = name; // also add references by url if they are different. if (name !== imageUrl) @@ -355,6 +372,10 @@ */ static addTextureToCache(texture, id) { + if (!texture.textureCacheId) + { + texture.textureCacheId = id; + } TextureCache[id] = texture; } @@ -495,6 +516,7 @@ */ Texture.EMPTY = new Texture(new BaseTexture()); removeAllHandlers(Texture.EMPTY); +removeAllHandlers(Texture.EMPTY.baseTexture); /** * A white texture of 10x10 size, used for graphics and other things @@ -505,3 +527,4 @@ */ Texture.WHITE = createWhiteTexture(); removeAllHandlers(Texture.WHITE); +removeAllHandlers(Texture.WHITE.baseTexture); diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index 59eb9fd..a9b9f51 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -1,6 +1,7 @@ import BaseTexture from './BaseTexture'; import { uid, BaseTextureCache } from '../utils'; -import * as ticker from '../ticker'; +import { shared } from '../ticker'; +import { UPDATE_PRIORITY } from '../const'; /** * A texture of a [playing] Video. @@ -125,7 +126,7 @@ if (!this._isAutoUpdating && this.autoUpdate) { - ticker.shared.add(this.update, this); + shared.add(this.update, this, UPDATE_PRIORITY.HIGH); this._isAutoUpdating = true; } } @@ -139,7 +140,7 @@ { if (this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); this._isAutoUpdating = false; } } @@ -187,7 +188,7 @@ { if (this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); } if (this.source && this.source._pixiId) @@ -281,12 +282,12 @@ if (!this._autoUpdate && this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); this._isAutoUpdating = false; } else if (this._autoUpdate && !this._isAutoUpdating) { - ticker.shared.add(this.update, this); + shared.add(this.update, this, UPDATE_PRIORITY.HIGH); this._isAutoUpdating = true; } } diff --git a/src/core/ticker/Ticker.js b/src/core/ticker/Ticker.js index 17a1517..40df138 100644 --- a/src/core/ticker/Ticker.js +++ b/src/core/ticker/Ticker.js @@ -1,12 +1,10 @@ import settings from '../settings'; -import EventEmitter from 'eventemitter3'; - -// Internal event used by composed emitter -const TICK = 'tick'; +import { UPDATE_PRIORITY } from '../const'; +import TickerListener from './TickerListener'; /** * A Ticker class that runs an update loop that other objects listen to. - * This class is composed around an EventEmitter object to add listeners + * This class is composed around listeners * meant for execution on the next requested animation frame. * Animation frames are requested only when necessary, * e.g. When the ticker is started and the emitter has listeners. @@ -22,10 +20,11 @@ constructor() { /** - * Internal emitter used to fire 'tick' event + * The first listener. All new listeners added are chained on this. * @private + * @type {TickerListener} */ - this._emitter = new EventEmitter(); + this._head = new TickerListener(null, null, Infinity); /** * Internal current frame request ID @@ -131,7 +130,7 @@ // Invoke listeners now this.update(time); // Listener side effects may have modified ticker state. - if (this.started && this._requestId === null && this._emitter.listeners(TICK, true)) + if (this.started && this._requestId === null && this._head.next) { this._requestId = requestAnimationFrame(this._tick); } @@ -148,7 +147,7 @@ */ _requestIfNeeded() { - if (this._requestId === null && this._emitter.listeners(TICK, true)) + if (this._requestId === null && this._head.next) { // ensure callbacks get correct delta this.lastTime = performance.now(); @@ -193,35 +192,72 @@ } /** - * Calls {@link module:eventemitter3.EventEmitter#on} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. + * Register a handler for tick events. Calls continuously unless + * it is removed or the ticker is stopped. * * @param {Function} fn - The listener function to be added for updates * @param {Function} [context] - The listener context + * @param {number} [priority=PIXI.UPDATE_PRIORITY.NORMAL] - The priority for emitting * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - add(fn, context) + add(fn, context, priority = UPDATE_PRIORITY.NORMAL) { - this._emitter.on(TICK, fn, context); - - this._startIfPossible(); - - return this; + return this._addListener(new TickerListener(fn, context, priority)); } /** - * Calls {@link module:eventemitter3.EventEmitter#once} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. + * Add a handler for the tick event which is only execute once. * * @param {Function} fn - The listener function to be added for one update * @param {Function} [context] - The listener context + * @param {number} [priority=PIXI.UPDATE_PRIORITY.NORMAL] - The priority for emitting * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - addOnce(fn, context) + addOnce(fn, context, priority = UPDATE_PRIORITY.NORMAL) { - this._emitter.once(TICK, fn, context); + return this._addListener(new TickerListener(fn, context, priority, true)); + } + + /** + * Internally adds the event handler so that it can be sorted by priority. + * Priority allows certain handler (user, AnimatedSprite, Interaction) to be run + * before the rendering. + * + * @private + * @param {TickerListener} listener - Current listener being added. + * @returns {PIXI.ticker.Ticker} This instance of a ticker + */ + _addListener(listener) + { + // For attaching to head + let current = this._head.next; + let previous = this._head; + + // Add the first item + if (!current) + { + listener.connect(previous); + } + else + { + // Go from highest to lowest priority + while (current) + { + if (listener.priority >= current.priority) + { + listener.connect(previous); + break; + } + previous = current; + current = current.next; + } + + // Not yet connected + if (!listener.previous) + { + listener.connect(previous); + } + } this._startIfPossible(); @@ -229,19 +265,33 @@ } /** - * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. - * It checks if the emitter has listeners for 'tick' event. - * If it does, then it cancels the animation frame. + * Removes any handlers matching the function and context parameters. + * If no handlers are left after removing, then it cancels the animation frame. * - * @param {Function} [fn] - The listener function to be removed + * @param {Function} fn - The listener function to be removed * @param {Function} [context] - The listener context to be removed * @returns {PIXI.ticker.Ticker} This instance of a ticker */ remove(fn, context) { - this._emitter.off(TICK, fn, context); + let listener = this._head.next; - if (!this._emitter.listeners(TICK, true)) + while (listener) + { + // We found a match, lets remove it + // no break to delete all possible matches + // incase a listener was added 2+ times + if (listener.match(fn, context)) + { + listener = listener.destroy(); + } + else + { + listener = listener.next; + } + } + + if (!this._head.next) { this._cancelIfNeeded(); } @@ -276,6 +326,25 @@ } /** + * Destroy the ticker and don't use after this. Calling + * this method removes all references to internal events. + */ + destroy() + { + this.stop(); + + let listener = this._head.next; + + while (listener) + { + listener = listener.destroy(true); + } + + this._head.destroy(); + this._head = null; + } + + /** * Triggers an update. An update entails setting the * current {@link PIXI.ticker.Ticker#elapsedMS}, * the current {@link PIXI.ticker.Ticker#deltaTime}, @@ -320,8 +389,22 @@ this.deltaTime = elapsedMS * settings.TARGET_FPMS * this.speed; + // Cache a local reference, in-case ticker is destroyed + // during the emit, we can still check for head.next + const head = this._head; + // Invoke listeners added to internal emitter - this._emitter.emit(TICK, this.deltaTime); + let listener = head.next; + + while (listener) + { + listener = listener.emit(this.deltaTime); + } + + if (!head.next) + { + this._cancelIfNeeded(); + } } else { diff --git a/src/core/ticker/TickerListener.js b/src/core/ticker/TickerListener.js new file mode 100644 index 0000000..2bedb34 --- /dev/null +++ b/src/core/ticker/TickerListener.js @@ -0,0 +1,158 @@ +/** + * Internal class for handling the priority sorting of ticker handlers. + * + * @private + * @class + * @memberof PIXI.ticker + */ +export default class TickerListener +{ + /** + * Constructor + * + * @param {Function} fn - The listener function to be added for one update + * @param {Function} [context=null] - The listener context + * @param {number} [priority=0] - The priority for emitting + * @param {boolean} [once=false] - If the handler should fire once + */ + constructor(fn, context = null, priority = 0, once = false) + { + /** + * The handler function to execute. + * @member {Function} + */ + this.fn = fn; + + /** + * The calling to execute. + * @member {Function} + */ + this.context = context; + + /** + * The current priority. + * @member {number} + */ + this.priority = priority; + + /** + * If this should only execute once. + * @member {boolean} + */ + this.once = once; + + /** + * The next item in chain. + * @member {TickerListener} + */ + this.next = null; + + /** + * The previous item in chain. + * @member {TickerListener} + */ + this.previous = null; + + /** + * `true` if this listener has been destroyed already. + * @member {boolean} + * @private + */ + this._destroyed = false; + } + + /** + * Simple compare function to figure out if a function and context match. + * + * @param {Function} fn - The listener function to be added for one update + * @param {Function} context - The listener context + * @return {boolean} `true` if the listener match the arguments + */ + match(fn, context) + { + context = context || null; + + return this.fn === fn && this.context === context; + } + + /** + * Emit by calling the current function. + * @param {number} deltaTime - time since the last emit. + * @return {TickerListener} Next ticker + */ + emit(deltaTime) + { + if (this.context) + { + this.fn.call(this.context, deltaTime); + } + else + { + this.fn(deltaTime); + } + + if (this.once) + { + this.destroy(); + } + + const redirect = this.next; + + // Soft-destroying should remove + // the next reference + if (this._destroyed) + { + this.next = null; + } + + return redirect; + } + + /** + * Connect to the list. + * @param {TickerListener} previous - Input node, previous listener + */ + connect(previous) + { + this.previous = previous; + if (previous.next) + { + previous.next.previous = this; + } + this.next = previous.next; + previous.next = this; + } + + /** + * Destroy and don't use after this. + * @param {boolean} [hard = false] `true` to remove the `next` reference, this + * is considered a hard destroy. Soft destroy maintains the next reference. + * @return {TickerListener} The listener to redirect while emitting or removing. + */ + destroy(hard = false) + { + this._destroyed = true; + this.fn = null; + this.context = null; + + // Disconnect, hook up next and previous + if (this.previous) + { + this.previous.next = this.next; + } + + if (this.next) + { + this.next.previous = this.previous; + } + + // Redirect to the next item + const redirect = this.previous; + + // Remove references + this.next = hard ? null : redirect; + this.previous = null; + + return redirect; + } +} diff --git a/src/core/ticker/index.js b/src/core/ticker/index.js index 4b3017c..c3020d4 100644 --- a/src/core/ticker/index.js +++ b/src/core/ticker/index.js @@ -45,8 +45,25 @@ const shared = new Ticker(); shared.autoStart = true; +shared.destroy = () => +{ + // protect destroying shared ticker + // this is used by other internal systems + // like AnimatedSprite and InteractionManager +}; /** + * This namespace contains an API for interacting with PIXI's internal global update loop. + * + * This ticker is used for rendering, {@link PIXI.extras.AnimatedSprite AnimatedSprite}, + * {@link PIXI.interaction.InteractionManager InteractionManager} and many other time-based PIXI systems. + * @example + * const ticker = new PIXI.ticker.Ticker(); + * ticker.stop(); + * ticker.add((deltaTime) => { + * // do something every frame + * }); + * ticker.start(); * @namespace PIXI.ticker */ export { shared, Ticker }; diff --git a/src/core/utils/index.js b/src/core/utils/index.js index be44750..7441f7b 100644 --- a/src/core/utils/index.js +++ b/src/core/utils/index.js @@ -9,6 +9,21 @@ let saidHello = false; /** + * Generalized convenience utilities for PIXI. + * @example + * // Extend PIXI's internal Event Emitter. + * class MyEmitter extends PIXI.utils.EventEmitter { + * constructor() { + * super(); + * console.log("Emitter created!"); + * } + * } + * + * // Get info on current device + * console.log(PIXI.utils.isMobile); + * + * // Convert hex color to string + * console.log(PIXI.utils.hex2string(0xff00ff)); // returns: "#ff00ff" * @namespace PIXI.utils */ export { diff --git a/src/deprecation.js b/src/deprecation.js index b9c73b3..33daeec 100644 --- a/src/deprecation.js +++ b/src/deprecation.js @@ -918,6 +918,35 @@ }); /** + * @method + * @name PIXI.prepare.BasePrepare#register + * @see PIXI.prepare.BasePrepare#registerFindHook + * @deprecated since version 4.4.2 + * @param {Function} [addHook] - Function call that takes two parameters: `item:*, queue:Array` + * function must return `true` if it was able to add item to the queue. + * @param {Function} [uploadHook] - Function call that takes two parameters: `prepare:CanvasPrepare, item:*` and + * function must return `true` if it was able to handle upload of item. + * @return {PIXI.BasePrepare} Instance of plugin for chaining. + */ +prepare.BasePrepare.prototype.register = function register(addHook, uploadHook) +{ + warn('renderer.plugins.prepare.register is now deprecated, ' + + 'please use renderer.plugins.prepare.registerFindHook & renderer.plugins.prepare.registerUploadHook'); + + if (addHook) + { + this.registerFindHook(addHook); + } + + if (uploadHook) + { + this.registerUploadHook(uploadHook); + } + + return this; +}; + +/** * The number of graphics or textures to upload to the GPU. * * @name PIXI.prepare.canvas.UPLOADS_PER_FRAME @@ -969,67 +998,73 @@ }, }); -Object.defineProperties(loaders.Resource.prototype, { - isJson: { - get() - { - warn('The isJson property is deprecated, please use `resource.type === Resource.TYPE.JSON`.'); +if (loaders.Loader) +{ + const Resource = loaders.Resource; + const Loader = loaders.Loader; - return this.type === loaders.Loader.Resource.TYPE.JSON; + Object.defineProperties(Resource.prototype, { + isJson: { + get() + { + warn('The isJson property is deprecated, please use `resource.type === Resource.TYPE.JSON`.'); + + return this.type === Resource.TYPE.JSON; + }, }, - }, - isXml: { - get() - { - warn('The isXml property is deprecated, please use `resource.type === Resource.TYPE.XML`.'); + isXml: { + get() + { + warn('The isXml property is deprecated, please use `resource.type === Resource.TYPE.XML`.'); - return this.type === loaders.Loader.Resource.TYPE.XML; + return this.type === Resource.TYPE.XML; + }, }, - }, - isImage: { - get() - { - warn('The isImage property is deprecated, please use `resource.type === Resource.TYPE.IMAGE`.'); + isImage: { + get() + { + warn('The isImage property is deprecated, please use `resource.type === Resource.TYPE.IMAGE`.'); - return this.type === loaders.Loader.Resource.TYPE.IMAGE; + return this.type === Resource.TYPE.IMAGE; + }, }, - }, - isAudio: { - get() - { - warn('The isAudio property is deprecated, please use `resource.type === Resource.TYPE.AUDIO`.'); + isAudio: { + get() + { + warn('The isAudio property is deprecated, please use `resource.type === Resource.TYPE.AUDIO`.'); - return this.type === loaders.Loader.Resource.TYPE.AUDIO; + return this.type === Resource.TYPE.AUDIO; + }, }, - }, - isVideo: { - get() - { - warn('The isVideo property is deprecated, please use `resource.type === Resource.TYPE.VIDEO`.'); + isVideo: { + get() + { + warn('The isVideo property is deprecated, please use `resource.type === Resource.TYPE.VIDEO`.'); - return this.type === loaders.Loader.Resource.TYPE.VIDEO; + return this.type === Resource.TYPE.VIDEO; + }, }, - }, -}); + }); -Object.defineProperties(loaders.Loader.prototype, { - before: { - get() - { - warn('The before() method is deprecated, please use pre().'); + Object.defineProperties(Loader.prototype, { + before: { + get() + { + warn('The before() method is deprecated, please use pre().'); - return this.pre; + return this.pre; + }, }, - }, - after: { - get() - { - warn('The after() method is deprecated, please use use().'); + after: { + get() + { + warn('The after() method is deprecated, please use use().'); - return this.use; + return this.use; + }, }, - }, -}); + }); +} /** * @name PIXI.interaction.interactiveTarget#defaultCursor diff --git a/src/extract/canvas/CanvasExtract.js b/src/extract/canvas/CanvasExtract.js index 941ca05..3ac5aee 100644 --- a/src/extract/canvas/CanvasExtract.js +++ b/src/extract/canvas/CanvasExtract.js @@ -8,7 +8,7 @@ * An instance of this class is automatically created by default, and can be found at renderer.plugins.extract * * @class - * @memberof PIXI + * @memberof PIXI.extract */ export default class CanvasExtract { @@ -21,9 +21,9 @@ /** * Collection of methods for extracting data (image, pixels, etc.) from a display object or render texture * - * @member {PIXI.CanvasExtract} extract + * @member {PIXI.extract.CanvasExtract} extract * @memberof PIXI.CanvasRenderer# - * @see PIXI.CanvasExtract + * @see PIXI.extract.CanvasExtract */ renderer.extract = this; } diff --git a/src/extract/index.js b/src/extract/index.js index 0f2170d..e9478c1 100644 --- a/src/extract/index.js +++ b/src/extract/index.js @@ -1,2 +1,22 @@ +/** + * This namespace provides renderer-specific plugins for exporting content from a renderer. + * For instance, these plugins can be used for saving an Image, Canvas element or for exporting the raw image data (pixels). + * + * Do not instantiate these plugins directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. + * @example + * // Create a new app (will auto-add extract plugin to renderer) + * const app = new PIXI.Application(); + * + * // Draw a red circle + * const graphics = new PIXI.Graphics() + * .beginFill(0xFF0000) + * .drawCircle(0, 0, 50); + * + * // Render the graphics as an HTMLImageElement + * const image = app.renderer.plugins.extract.image(graphics); + * document.body.appendChild(image); + * @namespace PIXI.extract + */ export { default as webgl } from './webgl/WebGLExtract'; export { default as canvas } from './canvas/CanvasExtract'; diff --git a/src/extract/webgl/WebGLExtract.js b/src/extract/webgl/WebGLExtract.js index 60228bb..1c3dd30 100644 --- a/src/extract/webgl/WebGLExtract.js +++ b/src/extract/webgl/WebGLExtract.js @@ -9,7 +9,7 @@ * An instance of this class is automatically created by default, and can be found at renderer.plugins.extract * * @class - * @memberof PIXI + * @memberof PIXI.extract */ export default class WebGLExtract { @@ -22,9 +22,9 @@ /** * Collection of methods for extracting data (image, pixels, etc.) from a display object or render texture * - * @member {PIXI.WebGLExtract} extract + * @member {PIXI.extract.WebGLExtract} extract * @memberof PIXI.WebGLRenderer# - * @see PIXI.WebGLExtract + * @see PIXI.extract.WebGLExtract */ renderer.extract = this; } diff --git a/src/extras/AnimatedSprite.js b/src/extras/AnimatedSprite.js index 13b804f..5297778 100644 --- a/src/extras/AnimatedSprite.js +++ b/src/extras/AnimatedSprite.js @@ -137,7 +137,7 @@ this.playing = true; if (this._autoUpdate) { - core.ticker.shared.add(this.update, this); + core.ticker.shared.add(this.update, this, core.UPDATE_PRIORITY.HIGH); } } diff --git a/src/extras/TextureTransform.js b/src/extras/TextureTransform.js index 78e6e40..16536d2 100644 --- a/src/extras/TextureTransform.js +++ b/src/extras/TextureTransform.js @@ -65,8 +65,36 @@ } /** + * Multiplies uvs array to transform + * @param {Float32Array} uvs mesh uvs + * @param {Float32Array} [out=uvs] output + * @returns {Float32Array} output + */ + multiplyUvs(uvs, out) + { + if (out === undefined) + { + out = uvs; + } + + const mat = this.mapCoord; + + for (let i = 0; i < uvs.length; i += 2) + { + const x = uvs[i]; + const y = uvs[i + 1]; + + out[i] = (x * mat.a) + (y * mat.c) + mat.tx; + out[i + 1] = (x * mat.b) + (y * mat.d) + mat.ty; + } + + return out; + } + + /** * updates matrices if texture was changed * @param {boolean} forceUpdate if true, matrices will be updated any case + * @returns {boolean} whether or not it was updated */ update(forceUpdate) { @@ -74,13 +102,13 @@ if (!tex || !tex.valid) { - return; + return false; } if (!forceUpdate && this._lastTextureID === tex._updateID) { - return; + return false; } this._lastTextureID = tex._updateID; @@ -110,5 +138,7 @@ frame[3] = (tex._frame.y + tex._frame.height - margin + offset) / texBase.height; this.uClampOffset[0] = offset / texBase.realWidth; this.uClampOffset[1] = offset / texBase.realHeight; + + return true; } } diff --git a/src/extras/TilingSprite.js b/src/extras/TilingSprite.js index 1f6014f..a3f027f 100644 --- a/src/extras/TilingSprite.js +++ b/src/extras/TilingSprite.js @@ -180,9 +180,9 @@ const transform = this.worldTransform; const resolution = renderer.resolution; const baseTexture = texture.baseTexture; - const baseTextureResolution = texture.baseTexture.resolution; - const modX = (this.tilePosition.x / this.tileScale.x) % texture._frame.width; - const modY = (this.tilePosition.y / this.tileScale.y) % texture._frame.height; + const baseTextureResolution = baseTexture.resolution; + const modX = ((this.tilePosition.x / this.tileScale.x) % texture._frame.width) * baseTextureResolution; + const modY = ((this.tilePosition.y / this.tileScale.y) % texture._frame.height) * baseTextureResolution; // create a nice shiny pattern! // TODO this needs to be refreshed if texture changes.. diff --git a/src/extras/cacheAsBitmap.js b/src/extras/cacheAsBitmap.js index 5dd5640..aea06a8 100644 --- a/src/extras/cacheAsBitmap.js +++ b/src/extras/cacheAsBitmap.js @@ -227,7 +227,16 @@ this.transform._parentID = -1; // restore the transform of the cached sprite to avoid the nasty flicker.. - this.updateTransform(); + if (!this.parent) + { + this.parent = renderer._tempDisplayObjectParent; + this.updateTransform(); + this.parent = null; + } + else + { + this.updateTransform(); + } // map the hit test.. this.containsPoint = cachedSprite.containsPoint.bind(cachedSprite); @@ -314,7 +323,17 @@ cachedSprite._bounds = this._bounds; cachedSprite.alpha = cacheAlpha; - this.updateTransform(); + if (!this.parent) + { + this.parent = renderer._tempDisplayObjectParent; + this.updateTransform(); + this.parent = null; + } + else + { + this.updateTransform(); + } + this.updateTransform = this.displayObjectUpdateTransform; this._cacheData.sprite = cachedSprite; @@ -358,9 +377,12 @@ * Destroys the cached object. * * @private + * @param {object|boolean} [options] - Options parameter. A boolean will act as if all options + * have been set to that value. + * Used when destroying containers, see the Container.destroy method. */ -DisplayObject.prototype._cacheAsBitmapDestroy = function _cacheAsBitmapDestroy() +DisplayObject.prototype._cacheAsBitmapDestroy = function _cacheAsBitmapDestroy(options) { this.cacheAsBitmap = false; - this.destroy(); + this.destroy(options); }; diff --git a/src/extras/index.js b/src/extras/index.js index bbd8bbf..a34d7ec 100644 --- a/src/extras/index.js +++ b/src/extras/index.js @@ -1,7 +1,7 @@ /** + * Additional PIXI DisplayObjects for animation, tiling and bitmap text. * @namespace PIXI.extras */ -export { default as TextureTransform } from './TextureTransform'; export { default as AnimatedSprite } from './AnimatedSprite'; export { default as TilingSprite } from './TilingSprite'; export { default as TilingSpriteRenderer } from './webgl/TilingSpriteRenderer'; diff --git a/src/extras/webgl/TilingSpriteRenderer.js b/src/extras/webgl/TilingSpriteRenderer.js index aeec172..eee3b5c 100644 --- a/src/extras/webgl/TilingSpriteRenderer.js +++ b/src/extras/webgl/TilingSpriteRenderer.js @@ -11,7 +11,7 @@ * WebGL renderer plugin for tiling sprites * * @class - * @memberof PIXI + * @memberof PIXI.extras * @extends PIXI.ObjectRenderer */ export default class TilingSpriteRenderer extends core.ObjectRenderer @@ -118,7 +118,7 @@ tempMat.invert(); if (isSimple) { - tempMat.append(uv.mapCoord); + tempMat.prepend(uv.mapCoord); } else { diff --git a/src/filters/displacement/DisplacementFilter.js b/src/filters/displacement/DisplacementFilter.js index d5355c3..b5d030c 100644 --- a/src/filters/displacement/DisplacementFilter.js +++ b/src/filters/displacement/DisplacementFilter.js @@ -35,7 +35,7 @@ this.maskSprite = sprite; this.maskMatrix = maskMatrix; - this.uniforms.mapSampler = sprite.texture; + this.uniforms.mapSampler = sprite._texture; this.uniforms.filterMatrix = maskMatrix; this.uniforms.scale = { x: 1, y: 1 }; diff --git a/src/filters/index.js b/src/filters/index.js index f3e8862..7ceb50a 100644 --- a/src/filters/index.js +++ b/src/filters/index.js @@ -1,4 +1,21 @@ /** + * This namespace contains WebGL-only display filters that can be applied + * to DisplayObjects using the {@link PIXI.DisplayObject#filters filters} property. + * @example + * // Create a new application + * const app = new PIXI.Application(); + * + * // Draw a green rectangle + * const rect = new PIXI.Graphics() + * .beginFill(0x00ff00) + * .drawRect(40, 40, 200, 200); + * + * // Add a blur filter + * rect.filters = [new PIXI.filters.BlurFilter()]; + * + * // Display rectangle + * app.stage.addChild(rect); + * document.body.appendChild(app.view); * @namespace PIXI.filters */ export { default as FXAAFilter } from './fxaa/FXAAFilter'; diff --git a/src/index.js b/src/index.js index c553427..77da490 100644 --- a/src/index.js +++ b/src/index.js @@ -20,6 +20,14 @@ import { utils } from './core'; utils.mixins.performMixins(); +/** + * Alias for {@link PIXI.loaders.shared}. + * @name loader + * @memberof PIXI + * @type {PIXI.loader.Loader} + */ +const loader = loaders.shared || null; + export { accessibility, extract, @@ -30,18 +38,8 @@ mesh, particles, prepare, + loader, }; -/** - * A premade instance of the loader that can be used to load resources. - * - * @name loader - * @memberof PIXI - * @property {PIXI.loaders.Loader} - */ -const loader = loaders && loaders.Loader ? new loaders.Loader() : null; // check is there in case user excludes loader lib - -export { loader }; - // Always export pixi globally. global.PIXI = exports; // eslint-disable-line diff --git a/src/interaction/InteractionData.js b/src/interaction/InteractionData.js index 02d53a3..449f8cc 100644 --- a/src/interaction/InteractionData.js +++ b/src/interaction/InteractionData.js @@ -30,7 +30,10 @@ /** * When passed to an event handler, this will be the original DOM Event that was captured * - * @member {Event} + * @see https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent + * @see https://developer.mozilla.org/en-US/docs/Web/API/TouchEvent + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent + * @member {MouseEvent|TouchEvent|PointerEvent} */ this.originalEvent = null; } diff --git a/src/interaction/InteractionManager.js b/src/interaction/InteractionManager.js index 8baf13a..2d75c7c 100644 --- a/src/interaction/InteractionManager.js +++ b/src/interaction/InteractionManager.js @@ -13,6 +13,14 @@ const MOUSE_POINTER_ID = 'MOUSE'; +// helpers for hitTest() - only used inside hitTest() +const hitTestEvent = { + target: null, + data: { + global: null, + }, +}; + /** * The interaction manager deals with mouse, touch and pointer events. Any DisplayObject can be interactive * if its interactive parameter is set to true @@ -245,67 +253,60 @@ this.setTargetElement(this.renderer.view, this.renderer.resolution); /** - * Fired when a pointer device button (usually a mouse button) is pressed on the display + * Fired when a pointer device button (usually a mouse left-button) is pressed on the display * object. * - * @event mousedown - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mousedown + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device secondary button (usually a mouse right-button) is pressed * on the display object. * - * @event rightdown - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightdown + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** - * Fired when a pointer device button (usually a mouse button) is released over the display + * Fired when a pointer device button (usually a mouse left-button) is released over the display * object. * - * @event mouseup - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseup + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device secondary button (usually a mouse right-button) is released * over the display object. * - * @event rightup - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightup + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** - * Fired when a pointer device button (usually a mouse button) is pressed and released on + * Fired when a pointer device button (usually a mouse left-button) is pressed and released on * the display object. * - * @event click - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#click + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device secondary button (usually a mouse right-button) is pressed * and released on the display object. * - * @event rightclick - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightclick + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** - * Fired when a pointer device button (usually a mouse button) is released outside the + * Fired when a pointer device button (usually a mouse left-button) is released outside the * display object that initially registered a * [mousedown]{@link PIXI.interaction.InteractionManager#event:mousedown}. * - * @event mouseupoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** @@ -313,146 +314,362 @@ * outside the display object that initially registered a * [rightdown]{@link PIXI.interaction.InteractionManager#event:rightdown}. * - * @event rightupoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device (usually a mouse) is moved while over the display object * - * @event mousemove - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mousemove + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device (usually a mouse) is moved onto the display object * - * @event mouseover - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseover + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device (usually a mouse) is moved off the display object * - * @event mouseout - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseout + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is pressed on the display object. * - * @event pointerdown - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerdown + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is released over the display object. * - * @event pointerup - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerup + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when the operating system cancels a pointer event * - * @event pointercancel - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointercancel + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is pressed and released on the display object. * - * @event pointertap - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointertap + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is released outside the display object that initially * registered a [pointerdown]{@link PIXI.interaction.InteractionManager#event:pointerdown}. * - * @event pointerupoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device is moved while over the display object * - * @event pointermove - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointermove + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device is moved onto the display object * - * @event pointerover - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerover + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device is moved off the display object * - * @event pointerout - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerout + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is placed on the display object. * - * @event touchstart - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchstart + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is removed from the display object. * - * @event touchend - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchend + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when the operating system cancels a touch * - * @event touchcancel - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchcancel + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is placed and removed from the display object. * - * @event tap - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#tap + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is removed outside of the display object that initially * registered a [touchstart]{@link PIXI.interaction.InteractionManager#event:touchstart}. * - * @event touchendoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchendoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is moved along the display object. * - * @event touchmove - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchmove + * @param {PIXI.interaction.InteractionData} event - Interaction data */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is pressed on the display. + * object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mousedown + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is pressed + * on the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightdown + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is released over the display + * object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseup + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is released + * over the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightup + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is pressed and released on + * the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#click + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is pressed + * and released on the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightclick + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is released outside the + * display object that initially registered a + * [mousedown]{@link PIXI.DisplayObject#event:mousedown}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is released + * outside the display object that initially registered a + * [rightdown]{@link PIXI.DisplayObject#event:rightdown}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device (usually a mouse) is moved while over the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mousemove + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device (usually a mouse) is moved onto the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseover + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device (usually a mouse) is moved off the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseout + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is pressed on the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerdown + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is released over the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerup + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when the operating system cancels a pointer event. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointercancel + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is pressed and released on the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointertap + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is released outside the display object that initially + * registered a [pointerdown]{@link PIXI.DisplayObject#event:pointerdown}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device is moved while over the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointermove + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device is moved onto the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerover + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device is moved off the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerout + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is placed on the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchstart + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is removed from the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchend + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when the operating system cancels a touch. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchcancel + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is placed and removed from the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#tap + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is removed outside of the display object that initially + * registered a [touchstart]{@link PIXI.DisplayObject#event:touchstart}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchendoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is moved along the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchmove + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + } + + /** + * Hit tests a point against the display tree, returning the first interactive object that is hit. + * + * @param {PIXI.Point} globalPoint - A point to hit test with, in global space. + * @param {PIXI.Container} [root] - The root display object to start from. If omitted, defaults + * to the last rendered root of the associated renderer. + * @return {PIXI.DisplayObject} The hit display object, if any. + */ + hitTest(globalPoint, root) + { + // clear the target for our hit test + hitTestEvent.target = null; + // assign the global point + hitTestEvent.data.global = globalPoint; + // ensure safety of the root + if (!root) + { + root = this.renderer._lastObjectRendered; + } + // run the hit test + this.processInteractive(hitTestEvent, root, null, true); + // return our found object - it'll be null if we didn't hit anything + + return hitTestEvent.target; } /** @@ -487,7 +704,7 @@ return; } - core.ticker.shared.add(this.update, this); + core.ticker.shared.add(this.update, this, core.UPDATE_PRIORITY.INTERACTION); if (window.navigator.msPointerEnabled) { @@ -804,8 +1021,6 @@ } } - let keepHitTestingAfterChildren = hitTest; - // ** FREE TIP **! If an object is not interactive or has no buttons in it // (such as a game scene!) set interactiveChildren to false for that displayObject. // This will allow pixi to completely ignore and bypass checking the displayObjects children. @@ -818,7 +1033,9 @@ const child = children[i]; // time to get recursive.. if this function will return if something is hit.. - if (this.processInteractive(interactionEvent, child, func, hitTest, interactiveParent)) + const childHit = this.processInteractive(interactionEvent, child, func, hitTest, interactiveParent); + + if (childHit) { // its a good idea to check if a child has lost its parent. // this means it has been removed whilst looping so its best @@ -827,8 +1044,6 @@ continue; } - hit = true; - // we no longer need to hit test any more objects in this container as we we // now know the parent has been hit interactiveParent = false; @@ -838,36 +1053,41 @@ // This means we no longer need to hit test anything else. We still need to run // through all objects, but we don't need to perform any hit tests. - keepHitTestingAfterChildren = false; - - if (child.interactive) + if (childHit) { - hitTest = false; + if (interactionEvent.target) + { + hitTest = false; + } + hit = true; } - - // we can break now as we have hit an object. } } } - hitTest = keepHitTestingAfterChildren; - // no point running this if the item is not interactive or does not have an interactive parent. if (interactive) { // if we are hit testing (as in we have no hit any objects yet) // We also don't need to worry about hit testing if once of the displayObjects children - // has already been hit! - if (hitTest && !hit) + // has already been hit - but only if it was interactive, otherwise we need to keep + // looking for an interactive child, just in case we hit one + if (hitTest && !interactionEvent.target) { if (displayObject.hitArea) { displayObject.worldTransform.applyInverse(point, this._tempPoint); - hit = displayObject.hitArea.contains(this._tempPoint.x, this._tempPoint.y); + if (displayObject.hitArea.contains(this._tempPoint.x, this._tempPoint.y)) + { + hit = true; + } } else if (displayObject.containsPoint) { - hit = displayObject.containsPoint(point); + if (displayObject.containsPoint(point)) + { + hit = true; + } } } @@ -878,7 +1098,10 @@ interactionEvent.target = displayObject; } - func(interactionEvent, displayObject, hit); + if (func) + { + func(interactionEvent, displayObject, !!hit); + } } } @@ -914,7 +1137,7 @@ { const event = events[i]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -998,7 +1221,7 @@ { const event = events[i]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1098,7 +1321,7 @@ const test = isRightButton ? flags.RIGHT_DOWN : flags.LEFT_DOWN; - const isDown = trackingData !== undefined && (trackingData.flags | test); + const isDown = trackingData !== undefined && (trackingData.flags & test); if (hit) { @@ -1118,11 +1341,11 @@ { if (isRightButton) { - trackingData.rightDown = hit; + trackingData.rightDown = false; } else { - trackingData.leftDown = hit; + trackingData.leftDown = false; } } } @@ -1180,7 +1403,7 @@ { const event = events[i]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1255,7 +1478,7 @@ this.setCursorMode(null); } - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1268,6 +1491,12 @@ { this.emit('mouseout', interactionEvent); } + else + { + // we can get touchleave events after touchend, so we want to make sure we don't + // introduce memory leaks + this.releaseInteractionDataForPointerId(interactionData.identifier); + } } /** @@ -1344,7 +1573,7 @@ // Only mouse and pointer can call onPointerOver, so events will always be length 1 const event = events[0]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1366,12 +1595,14 @@ * Get InteractionData for a given pointerId. Store that data as well * * @private - * @param {number} pointerId - Identifier from a pointer event + * @param {PointerEvent} event - Normalized pointer event, output from normalizeToPointerData * @return {InteractionData} - Interaction data for the given pointer identifier */ - getInteractionDataForPointerId(pointerId) + getInteractionDataForPointerId(event) { - if (pointerId === MOUSE_POINTER_ID) + const pointerId = event.pointerId; + + if (pointerId === MOUSE_POINTER_ID || event.pointerType === 'mouse') { return this.mouse; } diff --git a/src/interaction/InteractionTrackingData.js b/src/interaction/InteractionTrackingData.js index 6a1f69e..8493b63 100644 --- a/src/interaction/InteractionTrackingData.js +++ b/src/interaction/InteractionTrackingData.js @@ -35,8 +35,10 @@ } /** + * Unique pointer id of the event + * * @readonly - * @type {number} Unique pointer id of the event + * @member {number} */ get pointerId() { diff --git a/src/interaction/index.js b/src/interaction/index.js index 56c5b09..72f7d71 100644 --- a/src/interaction/index.js +++ b/src/interaction/index.js @@ -1,4 +1,8 @@ /** + * This namespace contains a renderer plugin for handling mouse, pointer, and touch events. + * + * Do not instantiate this plugin directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. * @namespace PIXI.interaction */ export { default as InteractionData } from './InteractionData'; diff --git a/src/interaction/interactiveTarget.js b/src/interaction/interactiveTarget.js index d252fba..cebac40 100644 --- a/src/interaction/interactiveTarget.js +++ b/src/interaction/interactiveTarget.js @@ -2,7 +2,7 @@ * Default property values of interactive objects * Used by {@link PIXI.interaction.InteractionManager} to automatically give all DisplayObjects these properties * - * @mixin + * @private * @name interactiveTarget * @memberof PIXI.interaction * @example @@ -14,18 +14,28 @@ * ); */ export default { + /** - * Determines if the displayObject be clicked/touched + * Enable interaction events for the DisplayObject. Touch, pointer and mouse + * events will not be emitted unless `interactive` is set to `true`. * - * @inner {boolean} + * @example + * const sprite = new PIXI.Sprite(texture); + * sprite.interactive = true; + * sprite.on('tap', (event) => { + * //handle event + * }); + * @member {boolean} + * @memberof PIXI.DisplayObject# */ interactive: false, /** * Determines if the children to the displayObject can be clicked/touched - * Setting this to false allows pixi to bypass a recursive hitTest function + * Setting this to false allows pixi to bypass a recursive `hitTest` function * - * @inner {boolean} + * @member {boolean} + * @memberof PIXI.Container# */ interactiveChildren: true, @@ -33,7 +43,12 @@ * Interaction shape. Children will be hit first, then this shape will be checked. * Setting this will cause this shape to be checked in hit tests rather than the displayObject's bounds. * - * @inner {PIXI.Rectangle|PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.RoundedRectangle} + * @example + * const sprite = new PIXI.Sprite(texture); + * sprite.interactive = true; + * sprite.hitArea = new PIXI.Rectangle(0, 0, 100, 100); + * @member {PIXI.Rectangle|PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.RoundedRectangle} + * @memberof PIXI.DisplayObject# */ hitArea: null, @@ -41,8 +56,12 @@ * If enabled, the mouse cursor use the pointer behavior when hovered over the displayObject if it is interactive * Setting this changes the 'cursor' property to `'pointer'`. * + * @example + * const sprite = new PIXI.Sprite(texture); + * sprite.interactive = true; + * sprite.buttonMode = true; * @member {boolean} - * @memberof PIXI.interaction.interactiveTarget# + * @memberof PIXI.DisplayObject# */ get buttonMode() { @@ -64,9 +83,14 @@ * This defines what cursor mode is used when the mouse cursor * is hovered over the displayObject. * + * @example + * const sprite = new PIXI.Sprite(texture); + * sprite.interactive = true; + * sprite.cursor = 'wait'; * @see https://developer.mozilla.org/en/docs/Web/CSS/cursor * - * @inner {string} + * @member {string} + * @memberof PIXI.DisplayObject# */ cursor: null, @@ -74,7 +98,7 @@ * Internal set of all active pointers, by identifier * * @member {Map} - * @memberof PIXI.interaction.interactiveTarget# + * @memberof PIXI.DisplayObject# * @private */ get trackedPointers() @@ -87,7 +111,8 @@ /** * Map of all tracked pointers, by identifier. Use trackedPointers to access. * - * @private {Map} + * @private + * @type {Map} */ _trackedPointers: undefined, }; diff --git a/src/loaders/index.js b/src/loaders/index.js index 5189793..daa1fc4 100644 --- a/src/loaders/index.js +++ b/src/loaders/index.js @@ -1,9 +1,22 @@ +import Application from '../core/Application'; +import Loader from './loader'; + /** + * This namespace contains APIs which extends the {@link https://github.com/englercj/resource-loader resource-loader} module + * for loading assets, data, and other resources dynamically. + * @example + * const loader = new PIXI.loaders.Loader(); + * loader.add('bunny', 'data/bunny.png') + * .add('spaceship', 'assets/spritesheet.json'); + * loader.load((loader, resources) => { + * // resources.bunny + * // resources.spaceship + * }); * @namespace PIXI.loaders */ -export { default as Loader } from './loader'; +export { Loader }; export { default as bitmapFontParser, parse as parseBitmapFontData } from './bitmapFontParser'; -export { default as spritesheetParser } from './spritesheetParser'; +export { default as spritesheetParser, getResourcePath } from './spritesheetParser'; export { default as textureParser } from './textureParser'; /** @@ -13,3 +26,55 @@ * @memberof PIXI.loaders */ export { Resource } from 'resource-loader'; + +/** + * A premade instance of the loader that can be used to load resources. + * @name shared + * @memberof PIXI.loaders + * @type {PIXI.loaders.Loader} + */ +const shared = new Loader(); + +shared.destroy = () => +{ + // protect destroying shared loader +}; + +export { shared }; + +// Mixin the loader construction +const AppPrototype = Application.prototype; + +AppPrototype._loader = null; + +/** + * Loader instance to help with asset loading. + * @name PIXI.Application#loader + * @type {PIXI.loaders.Loader} + */ +Object.defineProperty(AppPrototype, 'loader', { + get() + { + if (!this._loader) + { + const sharedLoader = this._options.sharedLoader; + + this._loader = sharedLoader ? shared : new Loader(); + } + + return this._loader; + }, +}); + +// Override the destroy function +// making sure to destroy the current Loader +AppPrototype._parentDestroy = AppPrototype.destroy; +AppPrototype.destroy = function destroy() +{ + if (this._loader) + { + this._loader.destroy(); + this._loader = null; + } + this._parentDestroy(); +}; diff --git a/src/loaders/loader.js b/src/loaders/loader.js index 767a411..59b8000 100644 --- a/src/loaders/loader.js +++ b/src/loaders/loader.js @@ -90,6 +90,15 @@ { Loader._pixiMiddleware.push(fn); } + + /** + * Destroy the loader, removes references. + */ + destroy() + { + this.removeAllListeners(); + this.reset(); + } } // Copy EE3 prototype (mixin) diff --git a/src/loaders/spritesheetParser.js b/src/loaders/spritesheetParser.js index e57dfb2..eda9584 100644 --- a/src/loaders/spritesheetParser.js +++ b/src/loaders/spritesheetParser.js @@ -1,12 +1,11 @@ import { Resource } from 'resource-loader'; -import path from 'path'; +import url from 'url'; import { Spritesheet } from '../core'; export default function () { return function spritesheetParser(resource, next) { - let resourcePath; const imageResourceName = `${resource.name}_image`; // skip if no data, its not json, it isn't spritesheet data, or the image resource already exists @@ -28,15 +27,7 @@ parentResource: resource, }; - // Prepend url path unless the resource image is a data url - if (resource.isDataUrl) - { - resourcePath = resource.data.meta.image; - } - else - { - resourcePath = `${path.dirname(resource.url.replace(this.baseUrl, ''))}/${resource.data.meta.image}`; - } + const resourcePath = getResourcePath(resource, this.baseUrl); // load the image for this sheet this.add(imageResourceName, resourcePath, loadOptions, function onImageLoad(res) @@ -56,3 +47,14 @@ }); }; } + +export function getResourcePath(resource, baseUrl) +{ + // Prepend url path unless the resource image is a data url + if (resource.isDataUrl) + { + return resource.data.meta.image; + } + + return url.resolve(resource.url.replace(baseUrl, ''), resource.data.meta.image); +} diff --git a/src/mesh/canvas/CanvasMeshRenderer.js b/src/mesh/canvas/CanvasMeshRenderer.js index 297205f..f7899e1 100644 --- a/src/mesh/canvas/CanvasMeshRenderer.js +++ b/src/mesh/canvas/CanvasMeshRenderer.js @@ -134,12 +134,33 @@ const textureWidth = base.width; const textureHeight = base.height; - const u0 = uvs[index0] * base.width; - const u1 = uvs[index1] * base.width; - const u2 = uvs[index2] * base.width; - const v0 = uvs[index0 + 1] * base.height; - const v1 = uvs[index1 + 1] * base.height; - const v2 = uvs[index2 + 1] * base.height; + let u0; + let u1; + let u2; + let v0; + let v1; + let v2; + + if (mesh.uploadUvTransform) + { + const ut = mesh._uvTransform.mapCoord; + + u0 = ((uvs[index0] * ut.a) + (uvs[index0 + 1] * ut.c) + ut.tx) * base.width; + u1 = ((uvs[index1] * ut.a) + (uvs[index1 + 1] * ut.c) + ut.tx) * base.width; + u2 = ((uvs[index2] * ut.a) + (uvs[index2 + 1] * ut.c) + ut.tx) * base.width; + v0 = ((uvs[index0] * ut.b) + (uvs[index0 + 1] * ut.d) + ut.ty) * base.height; + v1 = ((uvs[index1] * ut.b) + (uvs[index1 + 1] * ut.d) + ut.ty) * base.height; + v2 = ((uvs[index2] * ut.b) + (uvs[index2 + 1] * ut.d) + ut.ty) * base.height; + } + else + { + u0 = uvs[index0] * base.width; + u1 = uvs[index1] * base.width; + u2 = uvs[index2] * base.width; + v0 = uvs[index0 + 1] * base.height; + v1 = uvs[index1 + 1] * base.height; + v2 = uvs[index2 + 1] * base.height; + } let x0 = vertices[index0]; let x1 = vertices[index1]; diff --git a/src/mesh/webgl/MeshRenderer.js b/src/mesh/webgl/MeshRenderer.js index 78d24c3..ab99fcd 100644 --- a/src/mesh/webgl/MeshRenderer.js +++ b/src/mesh/webgl/MeshRenderer.js @@ -2,6 +2,8 @@ const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; +const matrixIdentity = core.Matrix.IDENTITY; + /** * WebGL renderer plugin for tiling sprites * @@ -64,6 +66,7 @@ mesh.geometry.glVertexArrayObjects[this.CONTEXT_UID].draw(mesh.drawMode, mesh.size, mesh.start, mesh.geometry.instanceCount); } + /** * draws mesh * @param {PIXI.mesh.RawMesh} mesh mesh instance diff --git a/src/mesh/webgl/mesh.vert b/src/mesh/webgl/mesh.vert index a337aef..acc096c 100644 --- a/src/mesh/webgl/mesh.vert +++ b/src/mesh/webgl/mesh.vert @@ -1,8 +1,9 @@ attribute vec2 aVertexPosition; attribute vec2 aTextureCoord; -uniform mat3 translationMatrix; uniform mat3 projectionMatrix; +uniform mat3 translationMatrix; +uniform mat3 uTransform; varying vec2 vTextureCoord; @@ -10,5 +11,5 @@ { gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - vTextureCoord = aTextureCoord; + vTextureCoord = (uTransform * vec3(aTextureCoord, 1.0)).xy; } diff --git a/src/particles/ParticleContainer.js b/src/particles/ParticleContainer.js index 67796cf..63f7791 100644 --- a/src/particles/ParticleContainer.js +++ b/src/particles/ParticleContainer.js @@ -313,10 +313,10 @@ frame.y * resolution, frame.width * resolution, frame.height * resolution, - positionX * resolution, - positionY * resolution, - finalWidth * resolution, - finalHeight * resolution + positionX * renderer.resolution, + positionY * renderer.resolution, + finalWidth * renderer.resolution, + finalHeight * renderer.resolution ); } } diff --git a/src/prepare/BasePrepare.js b/src/prepare/BasePrepare.js index 09eb5e5..e45c4dc 100644 --- a/src/prepare/BasePrepare.js +++ b/src/prepare/BasePrepare.js @@ -18,6 +18,18 @@ * basic queuing functionality and is extended by {@link PIXI.prepare.WebGLPrepare} and {@link PIXI.prepare.CanvasPrepare} * to provide preparation capabilities specific to their respective renderers. * + * @example + * // Create a sprite + * const sprite = new PIXI.Sprite.fromImage('something.png'); + * + * // Load object into GPU + * app.renderer.plugins.prepare.upload(sprite, () => { + * + * //Texture(s) has been uploaded to GPU + * app.stage.addChild(sprite); + * + * }) + * * @abstract * @class * @memberof PIXI.prepare @@ -100,8 +112,16 @@ this.prepareItems(); }; - this.register(findText, drawText); - this.register(findTextStyle, calculateTextStyle); + // hooks to find the correct texture + this.registerFindHook(findText); + this.registerFindHook(findTextStyle); + this.registerFindHook(findMultipleBaseTextures); + this.registerFindHook(findBaseTexture); + this.registerFindHook(findTexture); + + // upload hooks + this.registerUploadHook(drawText); + this.registerUploadHook(calculateTextStyle); } /** @@ -138,7 +158,7 @@ if (!this.ticking) { this.ticking = true; - SharedTicker.addOnce(this.tick, this); + SharedTicker.addOnce(this.tick, this, core.UPDATE_PRIORITY.UTILITY); } } else if (done) @@ -172,13 +192,16 @@ const item = this.queue[0]; let uploaded = false; - for (let i = 0, len = this.uploadHooks.length; i < len; i++) + if (item && !item._destroyed) { - if (this.uploadHooks[i](this.uploadHookHelper, item)) + for (let i = 0, len = this.uploadHooks.length; i < len; i++) { - this.queue.shift(); - uploaded = true; - break; + if (this.uploadHooks[i](this.uploadHookHelper, item)) + { + this.queue.shift(); + uploaded = true; + break; + } } } @@ -205,26 +228,36 @@ else { // if we are not finished, on the next rAF do this again - SharedTicker.addOnce(this.tick, this); + SharedTicker.addOnce(this.tick, this, core.UPDATE_PRIORITY.UTILITY); } } /** - * Adds hooks for finding and uploading items. + * Adds hooks for finding items. * - * @param {Function} [addHook] - Function call that takes two parameters: `item:*, queue:Array` - function must return `true` if it was able to add item to the queue. - * @param {Function} [uploadHook] - Function call that takes two parameters: `prepare:CanvasPrepare, item:*` and - * function must return `true` if it was able to handle upload of item. - * @return {PIXI.CanvasPrepare} Instance of plugin for chaining. + * @param {Function} addHook - Function call that takes two parameters: `item:*, queue:Array` + * function must return `true` if it was able to add item to the queue. + * @return {PIXI.BasePrepare} Instance of plugin for chaining. */ - register(addHook, uploadHook) + registerFindHook(addHook) { if (addHook) { this.addHooks.push(addHook); } + return this; + } + + /** + * Adds hooks for uploading items. + * + * @param {Function} uploadHook - Function call that takes two parameters: `prepare:CanvasPrepare, item:*` and + * function must return `true` if it was able to handle upload of item. + * @return {PIXI.BasePrepare} Instance of plugin for chaining. + */ + registerUploadHook(uploadHook) + { if (uploadHook) { this.uploadHooks.push(uploadHook); @@ -287,6 +320,88 @@ } /** + * Built-in hook to find multiple textures from objects like AnimatedSprites. + * + * @private + * @param {PIXI.DisplayObject} item - Display object to check + * @param {Array<*>} queue - Collection of items to upload + * @return {boolean} if a PIXI.Texture object was found. + */ +function findMultipleBaseTextures(item, queue) +{ + let result = false; + + // Objects with mutliple textures + if (item && item._textures && item._textures.length) + { + for (let i = 0; i < item._textures.length; i++) + { + if (item._textures[i] instanceof core.Texture) + { + const baseTexture = item._textures[i].baseTexture; + + if (queue.indexOf(baseTexture) === -1) + { + queue.push(baseTexture); + result = true; + } + } + } + } + + return result; +} + +/** + * Built-in hook to find BaseTextures from Sprites. + * + * @private + * @param {PIXI.DisplayObject} item - Display object to check + * @param {Array<*>} queue - Collection of items to upload + * @return {boolean} if a PIXI.Texture object was found. + */ +function findBaseTexture(item, queue) +{ + // Objects with textures, like Sprites/Text + if (item instanceof core.BaseTexture) + { + if (queue.indexOf(item) === -1) + { + queue.push(item); + } + + return true; + } + + return false; +} + +/** + * Built-in hook to find textures from objects. + * + * @private + * @param {PIXI.DisplayObject} item - Display object to check + * @param {Array<*>} queue - Collection of items to upload + * @return {boolean} if a PIXI.Texture object was found. + */ +function findTexture(item, queue) +{ + if (item._texture && item._texture instanceof core.Texture) + { + const texture = item._texture.baseTexture; + + if (queue.indexOf(texture) === -1) + { + queue.push(texture); + } + + return true; + } + + return false; +} + +/** * Built-in hook to draw PIXI.Text to its texture. * * @private diff --git a/src/prepare/canvas/CanvasPrepare.js b/src/prepare/canvas/CanvasPrepare.js index 0224bb5..09c207a 100644 --- a/src/prepare/canvas/CanvasPrepare.js +++ b/src/prepare/canvas/CanvasPrepare.js @@ -43,7 +43,7 @@ this.ctx = this.canvas.getContext('2d'); // Add textures to upload - this.register(findBaseTextures, uploadBaseTextures); + this.registerUploadHook(uploadBaseTextures); } /** @@ -89,39 +89,4 @@ return false; } -/** - * Built-in hook to find textures from Sprites. - * - * @private - * @param {PIXI.DisplayObject} item -Display object to check - * @param {Array<*>} queue - Collection of items to upload - * @return {boolean} if a PIXI.Texture object was found. - */ -function findBaseTextures(item, queue) -{ - // Objects with textures, like Sprites/Text - if (item instanceof core.BaseTexture) - { - if (queue.indexOf(item) === -1) - { - queue.push(item); - } - - return true; - } - else if (item._texture && item._texture instanceof core.Texture) - { - const texture = item._texture.baseTexture; - - if (queue.indexOf(texture) === -1) - { - queue.push(texture); - } - - return true; - } - - return false; -} - core.CanvasRenderer.registerPlugin('prepare', CanvasPrepare); diff --git a/src/prepare/index.js b/src/prepare/index.js index f559c45..e7491ec 100644 --- a/src/prepare/index.js +++ b/src/prepare/index.js @@ -1,4 +1,29 @@ /** + * The prepare namespace provides renderer-specific plugins for pre-rendering DisplayObjects. These plugins are useful for + * asynchronously preparing assets, textures, graphics waiting to be displayed. + * + * Do not instantiate these plugins directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. + * @example + * // Create a new application + * const app = new PIXI.Application(); + * document.body.appendChild(app.view); + * + * // Don't start rendering right away + * app.stop(); + * + * // create a display object + * const rect = new PIXI.Graphics() + * .beginFill(0x00ff00) + * .drawRect(40, 40, 200, 200); + * + * // Add to the stage + * app.stage.addChild(rect); + * + * // Don't start rendering until the graphic is uploaded to the GPU + * app.renderer.plugins.prepare.upload(app.stage, () => { + * app.start(); + * }); * @namespace PIXI.prepare */ export { default as webgl } from './webgl/WebGLPrepare'; diff --git a/src/prepare/webgl/WebGLPrepare.js b/src/prepare/webgl/WebGLPrepare.js index eb7023f..9df3c3f 100644 --- a/src/prepare/webgl/WebGLPrepare.js +++ b/src/prepare/webgl/WebGLPrepare.js @@ -22,12 +22,11 @@ this.uploadHookHelper = this.renderer; // Add textures and graphics to upload - this.register(findBaseTextures, uploadBaseTextures) - .register(findGraphics, uploadGraphics); + this.registerFindHook(findGraphics); + this.registerUploadHook(uploadBaseTextures); + this.registerUploadHook(uploadGraphics); } - } - /** * Built-in hook to upload PIXI.Texture objects to the GPU. * @@ -80,41 +79,6 @@ } /** - * Built-in hook to find textures from Sprites. - * - * @private - * @param {PIXI.DisplayObject} item - Display object to check - * @param {Array<*>} queue - Collection of items to upload - * @return {boolean} if a PIXI.Texture object was found. - */ -function findBaseTextures(item, queue) -{ - // Objects with textures, like Sprites/Text - if (item instanceof core.BaseTexture) - { - if (queue.indexOf(item) === -1) - { - queue.push(item); - } - - return true; - } - else if (item._texture && item._texture instanceof core.Texture) - { - const texture = item._texture.baseTexture; - - if (queue.indexOf(texture) === -1) - { - queue.push(texture); - } - - return true; - } - - return false; -} - -/** * Built-in hook to find graphics. * * @private diff --git a/test/.eslintrc.json b/test/.eslintrc.json index e03220d..ac12110 100644 --- a/test/.eslintrc.json +++ b/test/.eslintrc.json @@ -12,7 +12,8 @@ "sinon": false, "expect": false, "assert": false, - "PIXI": false + "PIXI": false, + "PointerEvent": true }, "rules": { "func-names": 0, diff --git a/test/core/BaseTexture.js b/test/core/BaseTexture.js index 8b93478..bb65068 100644 --- a/test/core/BaseTexture.js +++ b/test/core/BaseTexture.js @@ -14,4 +14,28 @@ expect(baseTexture.imageType).to.be.equals('png'); }); }); + + it('should remove Canvas BaseTexture from cache on destroy', function () + { + const canvas = document.createElement('canvas'); + const texture = PIXI.BaseTexture.fromCanvas(canvas); + const _pixiId = canvas._pixiId; + + expect(PIXI.utils.BaseTextureCache[_pixiId]).to.equal(texture); + texture.destroy(); + expect(PIXI.utils.BaseTextureCache[_pixiId]).to.equal(undefined); + }); + + it('should remove Image BaseTexture from cache on destroy', function () + { + const URL = 'foo.png'; + const NAME = 'bar'; + const image = new Image(); + + const texture = PIXI.Texture.fromLoader(image, URL, NAME); + + expect(PIXI.utils.BaseTextureCache[NAME]).to.equal(texture.baseTexture); + texture.destroy(true); + expect(PIXI.utils.BaseTextureCache[NAME]).to.equal(undefined); + }); }); diff --git a/test/core/Circle.js b/test/core/Circle.js index 183f98f..f621992 100644 --- a/test/core/Circle.js +++ b/test/core/Circle.js @@ -53,6 +53,10 @@ expect(circ1.contains(10, 16)).to.be.false; expect(circ1.contains(11, 15)).to.be.false; expect(circ1.contains(0, 0)).to.be.false; + + const circ2 = new PIXI.Circle(10, 10, 0); + + expect(circ2.contains(10, 10)).to.be.false; }); it('should return framing rectangle', function () diff --git a/test/core/Container.js b/test/core/Container.js index d11be33..cefde54 100644 --- a/test/core/Container.js +++ b/test/core/Container.js @@ -2,30 +2,36 @@ function testAddChild(fn) { - fn(function (container, obj) + return function () { - container.addChild(obj); - }); - fn(function (container, obj) - { - container.addChildAt(obj); - }); + fn(function (container, obj) + { + container.addChild(obj); + }); + fn(function (container, obj) + { + container.addChildAt(obj); + }); + }; } function testRemoveChild(fn) { - fn(function (container, obj) + return function () { - container.removeChild(obj); - }); - fn(function (container, obj) - { - container.removeChildAt(container.children.indexOf(obj)); - }); - fn(function (container, obj) - { - container.removeChildren(container.children.indexOf(obj), container.children.indexOf(obj) + 1); - }); + fn(function (container, obj) + { + container.removeChild(obj); + }); + fn(function (container, obj) + { + container.removeChildAt(container.children.indexOf(obj)); + }); + fn(function (container, obj) + { + container.removeChildren(container.children.indexOf(obj), container.children.indexOf(obj) + 1); + }); + }; } describe('PIXI.Container', function () diff --git a/test/core/Ellipse.js b/test/core/Ellipse.js index f2e8234..3f47442 100644 --- a/test/core/Ellipse.js +++ b/test/core/Ellipse.js @@ -56,6 +56,10 @@ expect(ellipse1.contains(10, 16)).to.be.false; expect(ellipse1.contains(11, 15)).to.be.false; expect(ellipse1.contains(0, 0)).to.be.false; + + const ellipse2 = new PIXI.Ellipse(10, 10, 0, 0); + + expect(ellipse2.contains(10, 10)).to.be.false; }); it('should return framing rectangle', function () diff --git a/test/core/Matrix.js b/test/core/Matrix.js index 9d2694b..76bd57c 100644 --- a/test/core/Matrix.js +++ b/test/core/Matrix.js @@ -107,10 +107,60 @@ expect(m1.ty).to.equal(m2.ty); }); + it('should prepend matrix', function () + { + const m1 = new PIXI.Matrix(); + const m2 = new PIXI.Matrix(); + + m2.set(2, 3, 4, 5, 100, 200); + m1.prepend(m2); + + expect(m1.a).to.equal(m2.a); + expect(m1.b).to.equal(m2.b); + expect(m1.c).to.equal(m2.c); + expect(m1.d).to.equal(m2.d); + expect(m1.tx).to.equal(m2.tx); + expect(m1.ty).to.equal(m2.ty); + + const m3 = new PIXI.Matrix(); + const m4 = new PIXI.Matrix(); + + m3.prepend(m4); + + expect(m3.a).to.equal(m4.a); + expect(m3.b).to.equal(m4.b); + expect(m3.c).to.equal(m4.c); + expect(m3.d).to.equal(m4.d); + expect(m3.tx).to.equal(m4.tx); + expect(m3.ty).to.equal(m4.ty); + }); + it('should get IDENTITY and TEMP_MATRIX', function () { expect(PIXI.Matrix.IDENTITY instanceof PIXI.Matrix).to.be.true; expect(PIXI.Matrix.TEMP_MATRIX instanceof PIXI.Matrix).to.be.true; }); -}); + it('should reset matrix to default when identity() is called', function () + { + const matrix = new PIXI.Matrix(); + + matrix.set(2, 3, 4, 5, 100, 200); + + expect(matrix.a).to.equal(2); + expect(matrix.b).to.equal(3); + expect(matrix.c).to.equal(4); + expect(matrix.d).to.equal(5); + expect(matrix.tx).to.equal(100); + expect(matrix.ty).to.equal(200); + + matrix.identity(); + + expect(matrix.a).to.equal(1); + expect(matrix.b).to.equal(0); + expect(matrix.c).to.equal(0); + expect(matrix.d).to.equal(1); + expect(matrix.tx).to.equal(0); + expect(matrix.ty).to.equal(0); + }); +}); diff --git a/test/core/Spritesheet.js b/test/core/Spritesheet.js index 283adf5..7601a0c 100644 --- a/test/core/Spritesheet.js +++ b/test/core/Spritesheet.js @@ -18,6 +18,7 @@ expect(textures[id]).to.be.an.instanceof(PIXI.Texture); expect(textures[id].width).to.equal(spritesheet.data.frames[id].frame.w / spritesheet.resolution); expect(textures[id].height).to.equal(spritesheet.data.frames[id].frame.h / spritesheet.resolution); + expect(textures[id].textureCacheId).to.equal(id); spritesheet.destroy(true); expect(spritesheet.textures).to.be.null; expect(spritesheet.baseTexture).to.be.null; diff --git a/test/core/Texture.js b/test/core/Texture.js index 8c4ef98..241ec3e 100644 --- a/test/core/Texture.js +++ b/test/core/Texture.js @@ -16,4 +16,20 @@ expect(PIXI.utils.TextureCache[URL]).to.equal(texture); expect(PIXI.utils.BaseTextureCache[URL]).to.equal(texture.baseTexture); }); + + it('should remove Texture from cache on destroy', function () + { + const NAME = 'foo'; + const NAME2 = 'bar'; + const texture = new PIXI.Texture(new PIXI.BaseTexture()); + + PIXI.Texture.addTextureToCache(texture, NAME); + PIXI.Texture.addTextureToCache(texture, NAME2); + expect(texture.textureCacheId).to.equal(NAME); + expect(PIXI.utils.TextureCache[NAME]).to.equal(texture); + expect(PIXI.utils.TextureCache[NAME2]).to.equal(texture); + texture.destroy(); + expect(PIXI.utils.TextureCache[NAME]).to.equal(undefined); + expect(PIXI.utils.TextureCache[NAME2]).to.equal(undefined); + }); }); diff --git a/test/core/Ticker.js b/test/core/Ticker.js new file mode 100644 index 0000000..188770f --- /dev/null +++ b/test/core/Ticker.js @@ -0,0 +1,322 @@ +'use strict'; + +const Ticker = PIXI.ticker.Ticker; +const shared = PIXI.ticker.shared; + +describe('PIXI.ticker.Ticker', function () +{ + before(function () + { + this.length = (ticker) => + { + ticker = ticker || shared; + + if (!ticker._head || !ticker._head.next) + { + return 0; + } + + let listener = ticker._head.next; + let i = 0; + + while (listener) + { + listener = listener.next; + i++; + } + + return i; + }; + }); + + it('should be available', function () + { + expect(Ticker).to.be.a.function; + expect(shared).to.be.an.instanceof(Ticker); + }); + + it('should create a new ticker and destroy it', function () + { + const ticker = new Ticker(); + + ticker.start(); + + const listener = sinon.spy(); + + expect(this.length(ticker)).to.equal(0); + + ticker.add(listener); + + expect(this.length(ticker)).to.equal(1); + + ticker.destroy(); + + expect(ticker._head).to.be.null; + expect(ticker.started).to.be.false; + expect(this.length(ticker)).to.equal(0); + }); + + it('should protect destroying shared ticker', function () + { + shared.destroy(); + expect(shared._head).to.not.be.null; + expect(shared.started).to.be.true; + }); + + it('should add and remove listener', function () + { + const listener = sinon.spy(); + const length = this.length(); + + shared.add(listener); + + expect(this.length()).to.equal(length + 1); + + shared.remove(listener); + + expect(this.length()).to.equal(length); + }); + + it('should update a listener', function () + { + const listener = sinon.spy(); + + shared.add(listener); + shared.update(); + + expect(listener.calledOnce).to.be.true; + + shared.remove(listener); + shared.update(); + + expect(listener.calledOnce).to.be.true; + }); + + it('should update a listener twice and remove once', function () + { + const listener = sinon.spy(); + const length = this.length(); + + shared.add(listener).add(listener); + shared.update(); + + expect(listener.calledTwice).to.be.true; + expect(this.length()).to.equal(length + 2); + + shared.remove(listener); + shared.update(); + + expect(listener.calledTwice).to.be.true; + expect(this.length()).to.equal(length); + }); + + it('should respect priority order', function () + { + const length = this.length(); + const listener1 = sinon.spy(); + const listener2 = sinon.spy(); + const listener3 = sinon.spy(); + const listener4 = sinon.spy(); + + shared.add(listener1, null, PIXI.UPDATE_PRIORITY.LOW) + .add(listener4, null, PIXI.UPDATE_PRIORITY.INTERACTION) + .add(listener3, null, PIXI.UPDATE_PRIORITY.HIGH) + .add(listener2, null, PIXI.UPDATE_PRIORITY.NORMAL); + + shared.update(); + + expect(this.length()).to.equal(length + 4); + + sinon.assert.callOrder(listener4, listener3, listener2, listener1); + + shared.remove(listener1) + .remove(listener2) + .remove(listener3) + .remove(listener4); + + expect(this.length()).to.equal(length); + }); + + it('should auto-remove once listeners', function () + { + const length = this.length(); + const listener = sinon.spy(); + + shared.addOnce(listener); + + shared.update(); + + expect(listener.calledOnce).to.be.true; + expect(this.length()).to.equal(length); + }); + + it('should call inserted item with a lower priority', function () + { + const length = this.length(); + const lowListener = sinon.spy(); + const highListener = sinon.spy(); + const mainListener = sinon.spy(() => + { + shared.add(highListener, null, PIXI.UPDATE_PRIORITY.HIGH); + shared.add(lowListener, null, PIXI.UPDATE_PRIORITY.LOW); + }); + + shared.add(mainListener, null, PIXI.UPDATE_PRIORITY.NORMAL); + + shared.update(); + + expect(this.length()).to.equal(length + 3); + + expect(mainListener.calledOnce).to.be.true; + expect(lowListener.calledOnce).to.be.true; + expect(highListener.calledOnce).to.be.false; + + shared.remove(mainListener) + .remove(highListener) + .remove(lowListener); + + expect(this.length()).to.equal(length); + }); + + it('should remove emit low-priority item during emit', function () + { + const length = this.length(); + const listener2 = sinon.spy(); + const listener1 = sinon.spy(() => + { + shared.add(listener2, null, PIXI.UPDATE_PRIORITY.LOW); + }); + + shared.add(listener1, null, PIXI.UPDATE_PRIORITY.NORMAL); + + shared.update(); + + expect(this.length()).to.equal(length + 2); + + expect(listener2.calledOnce).to.be.true; + expect(listener1.calledOnce).to.be.true; + + shared.remove(listener1) + .remove(listener2); + + expect(this.length()).to.equal(length); + }); + + it('should remove itself on emit after adding new item', function () + { + const length = this.length(); + const listener2 = sinon.spy(); + const listener1 = sinon.spy(() => + { + shared.add(listener2, null, PIXI.UPDATE_PRIORITY.LOW); + shared.remove(listener1); + + // listener is removed right away + expect(this.length()).to.equal(length + 1); + }); + + shared.add(listener1, null, PIXI.UPDATE_PRIORITY.NORMAL); + + shared.update(); + + expect(this.length()).to.equal(length + 1); + + expect(listener2.calledOnce).to.be.true; + expect(listener1.calledOnce).to.be.true; + + shared.remove(listener2); + + expect(this.length()).to.equal(length); + }); + + it('should remove itself before, still calling new item', function () + { + const length = this.length(); + const listener2 = sinon.spy(); + const listener1 = sinon.spy(() => + { + shared.remove(listener1); + shared.add(listener2, null, PIXI.UPDATE_PRIORITY.LOW); + + // listener is removed right away + expect(this.length()).to.equal(length + 1); + }); + + shared.add(listener1, null, PIXI.UPDATE_PRIORITY.NORMAL); + + shared.update(); + + expect(this.length()).to.equal(length + 1); + + expect(listener2.calledOnce).to.be.true; + expect(listener1.calledOnce).to.be.true; + + shared.remove(listener2); + + expect(this.length()).to.equal(length); + }); + + it('should remove items before and after current priority', function () + { + const length = this.length(); + const listener2 = sinon.spy(); + const listener3 = sinon.spy(); + const listener4 = sinon.spy(); + + shared.add(listener2, null, PIXI.UPDATE_PRIORITY.HIGH); + shared.add(listener3, null, PIXI.UPDATE_PRIORITY.LOW); + shared.add(listener4, null, PIXI.UPDATE_PRIORITY.LOW); + + const listener1 = sinon.spy(() => + { + shared.remove(listener2) + .remove(listener3); + + // listener is removed right away + expect(this.length()).to.equal(length + 2); + }); + + shared.add(listener1, null, PIXI.UPDATE_PRIORITY.NORMAL); + + shared.update(); + + expect(this.length()).to.equal(length + 2); + + expect(listener2.calledOnce).to.be.true; + expect(listener3.calledOnce).to.be.false; + expect(listener4.calledOnce).to.be.true; + expect(listener1.calledOnce).to.be.true; + + shared.update(); + + expect(listener2.calledOnce).to.be.true; + expect(listener3.calledOnce).to.be.false; + expect(listener4.calledTwice).to.be.true; + expect(listener1.calledTwice).to.be.true; + + shared.remove(listener1) + .remove(listener4); + + expect(this.length()).to.equal(length); + }); + + it('should destroy on listener', function (done) + { + const ticker = new Ticker(); + const listener2 = sinon.spy(); + const listener = sinon.spy(() => + { + ticker.destroy(); + setTimeout(() => + { + expect(listener2.called).to.be.false; + expect(listener.calledOnce).to.be.true; + done(); + }, 0); + }); + + ticker.add(listener); + ticker.add(listener2, null, PIXI.UPDATE_PRIORITY.LOW); + ticker.start(); + }); +}); diff --git a/test/core/TilingSprite.js b/test/core/TilingSprite.js index 2e449a7..7694792 100644 --- a/test/core/TilingSprite.js +++ b/test/core/TilingSprite.js @@ -24,4 +24,25 @@ expect(bounds.height).to.equal(600); }); }); + + it('checks if tilingSprite contains a point', function () + { + const texture = new PIXI.Texture(new PIXI.BaseTexture()); + const tilingSprite = new PIXI.extras.TilingSprite(texture, 200, 300); + + expect(tilingSprite.containsPoint(new PIXI.Point(1, 1))).to.equal(true); + expect(tilingSprite.containsPoint(new PIXI.Point(300, 400))).to.equal(false); + }); + + it('gets and sets height and width correctly', function () + { + const texture = new PIXI.Texture(new PIXI.BaseTexture()); + const tilingSprite = new PIXI.extras.TilingSprite(texture, 200, 300); + + tilingSprite.width = 400; + tilingSprite.height = 600; + + expect(tilingSprite.width).to.equal(400); + expect(tilingSprite.height).to.equal(600); + }); }); diff --git a/test/core/index.js b/test/core/index.js index 8530131..58dc30e 100755 --- a/test/core/index.js +++ b/test/core/index.js @@ -17,6 +17,7 @@ require('./util'); require('./Plane'); require('./Point'); +require('./Polygon'); require('./ObservablePoint'); require('./Matrix'); require('./Rectangle'); @@ -27,4 +28,5 @@ require('./WebGLRenderer'); require('./Ellipse'); require('./Texture'); +require('./Ticker'); require('./filters'); diff --git a/.travis.yml b/.travis.yml index 3df8b22..14de334 100644 --- a/.travis.yml +++ b/.travis.yml @@ -110,3 +110,43 @@ on: all_branches: true condition: $TRAVIS_TAG + # Deploy config for latest release + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: dist + upload-dir: "release" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: docs + upload-dir: "release/docs" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: coverage + upload-dir: "release/coverage" + on: + all_branches: true + condition: $TRAVIS_TAG \ No newline at end of file diff --git a/README.md b/README.md index d26fadd..3b85f85 100644 --- a/README.md +++ b/README.md @@ -36,22 +36,14 @@ - Wiki: Other misc tutorials and resources are [on the Wiki](https://github.com/pixijs/pixi.js/wiki/Resources). ### Community ### -- Forums: Check out the [forum] (http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow] (http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. +- Forums: Check out the [forum](http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow](http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. - Inspiration: Check out the [gallery](http://www.pixijs.com/gallery) to see some of the amazing things people have created! - Chat: You can join us on [Gitter](https://gitter.im/pixijs/pixi.js) To chat about Pixi. We also now have a Slack channel. If you would like to join it please Send me an email (mat@goodboydigital.com) and I will invite you in. ### Setup ### -It's easy to get started with Pixi.js! Simply grab the pre-built versions from here: - -Release Branch - Nice and stable Pixi.js -- Unminified: [http://pixijs.download/release/pixi.js] -- Minified: [http://pixijs.download/release/pixi.min.js] - -Develop Branch - The bleeding edge version of Pixi.js -- Unminified: [http://pixijs.download/dev/pixi.js] -- Minified: [http://pixijs.download/dev/pixi.min.js] +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. @@ -70,10 +62,10 @@ #### CDN Install (via cdnjs) ```html - + ``` -_Note: `4.2.2` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ +_Note: `4.4.3` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ ### Demos ### @@ -130,7 +122,7 @@ // and the root stage PIXI.Container. var app = new PIXI.Application(); -// The application will create a canvas element for you that you +// The application will create a canvas element for you that you // can then insert into the DOM. document.body.appendChild(app.view); @@ -150,7 +142,7 @@ // Add the bunny to the scene we are building. app.stage.addChild(bunny); - + // Listen for frame updates app.ticker.add(function() { // each frame we spin the bunny around a bit diff --git a/bower.json b/bower.json deleted file mode 100644 index 41b668c..0000000 --- a/bower.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "pixi.js", - "main": "dist/pixi.min.js", - "ignore": [ - "**/.*", - "docs", - "node_modules", - "bower_components", - "test", - "scripts", - "bower.json", - "inch.json" - ], - "dependencies": { - }, - "devDependencies": { - } -} diff --git a/package.json b/package.json index d35cbb2..308469f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.3.5", + "version": "4.4.4", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ @@ -40,11 +40,11 @@ "lib": "babel src --out-dir lib -s", "predocs": "rimraf docs/**", "docs": "jsdoc -c scripts/jsdoc.conf.json -R README.md", - "publish:patch": "npm version patch --no-git-tag-version && npm publish", - "publish:minor": "npm version minor --no-git-tag-version && npm publish", - "publish:major": "npm version major --no-git-tag-version && npm publish", + "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", - "postpublish": "node scripts/release.js" + "postpublish": "git push && git push --tags" }, "files": [ "dist/", @@ -72,7 +72,6 @@ "electron": "^1.4.15", "eslint": "^3.5.0", "floss": "^2.0.1", - "gh-pages": "^0.11.0", "jaguarjs-jsdoc": "^1.0.1", "js-md5": "^0.4.1", "jsdoc": "^3.4.2", diff --git a/scripts/jsdoc.conf.json b/scripts/jsdoc.conf.json index 553e61c..3f52180 100644 --- a/scripts/jsdoc.conf.json +++ b/scripts/jsdoc.conf.json @@ -43,7 +43,7 @@ }, "markdown" : { "parser" : "gfm", - "hardwrap" : true + "hardwrap" : false }, "opts": { "encoding" : "utf8", diff --git a/scripts/release.js b/scripts/release.js deleted file mode 100644 index 156620c..0000000 --- a/scripts/release.js +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env node -'use strict'; - -// Publish script to push releases of the bin files -// the normally are gitignored -const ghpages = require('gh-pages'); -const path = require('path'); -const packageInfo = require(path.join(__dirname, '..', 'package.json')); - -const options = { - src: [ - 'dist/**/*', - 'lib/**/*', - 'src/**/*', - 'scripts/*', - 'scripts/renders/*', - 'test/**/*', - '*.json', - '*.md', - 'LICENSE', - '.eslintrc', - '.editorconfig', - '.travis.yml', - '.babelrc', - ], - dotfiles: true, - branch: 'release', - message: packageInfo.version, - logger: console.log.bind(console), -}; - -ghpages.publish(process.cwd(), options, (err) => -{ - if (err) - { - console.log(err); - process.exit(1); - - return; - } - - process.exit(0); -}); diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index f16bcb1..220427a 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -21,7 +21,7 @@ const DIV_HOOK_ZINDEX = 2; /** - * The Accessibility manager reacreates the ability to tab and and have content read by screen + * The Accessibility manager recreates the ability to tab and and have content read by screen * readers. This is very important as it can possibly help people with disabilities access pixi * content. * diff --git a/src/accessibility/index.js b/src/accessibility/index.js index 568cfb1..0ae108f 100644 --- a/src/accessibility/index.js +++ b/src/accessibility/index.js @@ -1,4 +1,9 @@ /** + * This namespace contains a renderer plugin for interaction accessibility for end-users + * with physical impairments which require screen-renders, keyboard navigation, etc. + * + * Do not instantiate this plugin directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. * @namespace PIXI.accessibility */ export { default as accessibleTarget } from './accessibleTarget'; diff --git a/src/core/Application.js b/src/core/Application.js index 5340578..2fcc729 100644 --- a/src/core/Application.js +++ b/src/core/Application.js @@ -1,6 +1,8 @@ import { autoDetectRenderer } from './autoDetectRenderer'; import Container from './display/Container'; import { shared, Ticker } from './ticker'; +import settings from './settings'; +import { UPDATE_PRIORITY } from './const'; /** * Convenience class to create a new PIXI application. @@ -22,28 +24,52 @@ */ export default class Application { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experience unexplained flickering try setting this to true. - * @param {boolean} [useSharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedLoader=false] - `true` to use PIXI.loaders.shared, `false` to create new Loader. */ - constructor(width, height, options, noWebGL, useSharedTicker = false) + constructor(options, arg2, arg3, arg4, arg5) { + // Support for constructor(width, height, options, noWebGL, useSharedTicker) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + forceCanvas: !!arg4, + sharedTicker: !!arg5, + }, arg3); + } + + /** + * The default options, so we mixin functionality later. + * @member {object} + * @protected + */ + this._options = options = Object.assign({ + sharedTicker: false, + forceCanvas: false, + sharedLoader: false, + }, options); + /** * WebGL renderer if available, otherwise CanvasRenderer * @member {PIXI.WebGLRenderer|PIXI.CanvasRenderer} */ - this.renderer = autoDetectRenderer(width, height, options, noWebGL); + this.renderer = autoDetectRenderer(options); /** * The root display container that's rendered. @@ -63,7 +89,7 @@ * @member {PIXI.ticker.Ticker} * @default PIXI.ticker.shared */ - this.ticker = useSharedTicker ? shared : new Ticker(); + this.ticker = options.sharedTicker ? shared : new Ticker(); // Start the rendering this.start(); @@ -78,7 +104,7 @@ this._ticker = ticker; if (ticker) { - ticker.add(this.render, this); + ticker.add(this.render, this, UPDATE_PRIORITY.LOW); } } get ticker() // eslint-disable-line require-jsdoc @@ -136,13 +162,18 @@ */ destroy(removeView) { - this.stop(); + const oldTicker = this._ticker; + this.ticker = null; + oldTicker.destroy(); + this.stage.destroy(); this.stage = null; this.renderer.destroy(removeView); this.renderer = null; + + this._options = null; } } diff --git a/src/core/autoDetectRenderer.js b/src/core/autoDetectRenderer.js index 8960425..9ec0364 100644 --- a/src/core/autoDetectRenderer.js +++ b/src/core/autoDetectRenderer.js @@ -2,6 +2,7 @@ import CanvasRenderer from './renderers/canvas/CanvasRenderer'; import WebGLRenderer from './renderers/webgl/WebGLRenderer'; +// eslint-disable-next-line valid-jsdoc /** * This helper function will automatically detect which renderer you should be using. * WebGL is the preferred renderer as it is a lot faster. If webGL is not supported by @@ -9,24 +10,32 @@ * * @memberof PIXI * @function autoDetectRenderer - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @return {PIXI.WebGLRenderer|PIXI.CanvasRenderer} Returns WebGL renderer if available, otherwise CanvasRenderer */ -export function autoDetectRenderer(width = 800, height = 600, options, noWebGL) +export function autoDetectRenderer(options, arg1, arg2, arg3) { - if (!noWebGL && utils.isWebGLSupported()) + // Backward-compatible support for noWebGL option + let forceCanvas = options && options.forceCanvas; + + if (arg3 !== undefined) { - return new WebGLRenderer(width, height, options); + forceCanvas = arg3; } - return new CanvasRenderer(width, height, options); + if (!forceCanvas && utils.isWebGLSupported()) + { + return new WebGLRenderer(options, arg1, arg2); + } + + return new CanvasRenderer(options, arg1, arg2); } diff --git a/src/core/const.js b/src/core/const.js index b9e99ae..2c68dee 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -341,3 +341,27 @@ LINEAR_VERTICAL: 0, LINEAR_HORIZONTAL: 1, }; + +/** + * Represents the update priorities used by internal PIXI classes when registered with + * the {@link PIXI.ticker.Ticker} object. Higher priority items are updated first and lower + * priority items, such as render, should go later. + * + * @static + * @constant + * @name UPDATE_PRIORITY + * @memberof PIXI + * @type {object} + * @property {number} INTERACTION=50 Highest priority, used for {@link PIXI.interaction.InteractionManager} + * @property {number} HIGH=25 High priority updating, {@link PIXI.VideoBaseTexture} and {@link PIXI.extras.AnimatedSprite} + * @property {number} NORMAL=0 Default priority for ticker events, see {@link PIXI.ticker.Ticker#add}. + * @property {number} LOW=-25 Low priority used for {@link PIXI.Application} rendering. + * @property {number} UTILITY=-50 Lowest priority used for {@link PIXI.prepare.BasePrepare} utility. + */ +export const UPDATE_PRIORITY = { + INTERACTION: 50, + HIGH: 25, + NORMAL: 0, + LOW: -25, + UTILITY: -50, +}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 4e6c77d..decb42a 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -13,7 +13,6 @@ * * @class * @extends EventEmitter - * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ export default class DisplayObject extends EventEmitter @@ -113,6 +112,29 @@ * @private */ this._mask = null; + + /** + * If the object has been destroyed via destroy(). If true, it should not be used. + * + * @member {boolean} + * @private + * @readonly + */ + this._destroyed = false; + + /** + * Fired when this DisplayObject is added to a Container. + * + * @event PIXI.DisplayObject#added + * @param {PIXI.Container} container - The container added to. + */ + + /** + * Fired when this DisplayObject is removed from a Container. + * + * @event PIXI.DisplayObject#removed + * @param {PIXI.Container} container - The container removed from. + */ } /** @@ -403,6 +425,8 @@ this.interactive = false; this.interactiveChildren = false; + + this._destroyed = true; } /** diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index aafdfe2..f787c4a 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -792,8 +792,8 @@ sprite.worldAlpha = this.worldAlpha * sprite.alpha; sprite.blendMode = this.blendMode; - sprite.texture._frame.width = rect.width; - sprite.texture._frame.height = rect.height; + sprite._texture._frame.width = rect.width; + sprite._texture._frame.height = rect.height; sprite.transform.worldTransform = this.transform.worldTransform; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index a24c775..b9f192d 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -19,11 +19,12 @@ */ export default class SystemRenderer extends EventEmitter { + // eslint-disable-next-line valid-jsdoc /** * @param {string} system - The name of the system this renderer is for. - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -37,27 +38,31 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(system, screenWidth, screenHeight, options) + constructor(system, options, arg2, arg3) { super(); sayHello(system); - // prepare options - if (options) + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') { - for (const i in settings.RENDER_OPTIONS) - { - if (typeof options[i] === 'undefined') - { - options[i] = settings.RENDER_OPTIONS[i]; - } - } + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); } - else - { - options = settings.RENDER_OPTIONS; - } + + // Add the default render options + options = Object.assign({}, settings.RENDER_OPTIONS, options); + + /** + * The supplied constructor options. + * + * @member {Object} + * @readOnly + */ + this.options = options; /** * The type of the renderer. @@ -75,7 +80,7 @@ * * @member {PIXI.Rectangle} */ - this.screen = new Rectangle(0, 0, screenWidth || 800, screenHeight || 600); + this.screen = new Rectangle(0, 0, options.width, options.height); /** * The canvas element that everything is drawn to @@ -279,6 +284,8 @@ this.blendModes = null; + this.options = null; + this.preserveDrawingBuffer = false; this.clearBeforeRender = false; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index e84e49c..691caf0 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -17,10 +17,11 @@ */ export default class CanvasRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -34,9 +35,9 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('Canvas', screenWidth, screenHeight, options); + super('Canvas', options, arg2, arg3); this.type = RENDERER_TYPE.CANVAS; @@ -96,7 +97,19 @@ this.context = null; this.renderingToScreen = false; - this.resize(screenWidth, screenHeight); + this.resize(this.options.width, this.options.height); + + /** + * Fired after rendering finishes. + * + * @event PIXI.CanvasRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.CanvasRenderer#prerender + */ } /** @@ -299,4 +312,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.CanvasRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.CanvasExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.CanvasPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.CanvasRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index e1cf7cd..f6fe123 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -31,11 +31,12 @@ */ export default class WebGLRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** * - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -55,9 +56,9 @@ * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('WebGL', screenWidth, screenHeight, options); + super('WebGL', options, arg2, arg3); /** * The type of this renderer as a standardised const @@ -89,12 +90,6 @@ }; - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ this._backgroundColorRgba[3] = this.transparent ? 0 : 1; this.globalUniforms = new UniformGroup({ @@ -117,6 +112,13 @@ this.initPlugins(); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ if(options.context) { this.context.initFromContext(options.context); @@ -169,6 +171,25 @@ } return this; + + /** + * Fired after rendering finishes. + * + * @event PIXI.WebGLRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.WebGLRenderer#prerender + */ + + /** + * Fired when the WebGL context is set. + * + * @event PIXI.WebGLRenderer#context + * @param {WebGLRenderingContext} gl - WebGL context. + */ } /** @@ -295,4 +316,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.WebGLRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.WebGLRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(WebGLRenderer); diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index 4eb6128..fa3d883 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -47,6 +47,14 @@ * @member {boolean} */ this.enabled = true; + + /** + * If enabled, pixi will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; } /** diff --git a/src/core/renderers/webgl/systems/ContextSystem.js b/src/core/renderers/webgl/systems/ContextSystem.js index ff958c7..8df85ea 100644 --- a/src/core/renderers/webgl/systems/ContextSystem.js +++ b/src/core/renderers/webgl/systems/ContextSystem.js @@ -75,7 +75,8 @@ */ createContext(canvas, options) { - var gl = canvas.getContext('webgl', options) || + var gl = //canvas.getContext('webgl2', options) || + canvas.getContext('webgl', options) || canvas.getContext('experimental-webgl', options); if (!gl) diff --git a/src/core/renderers/webgl/systems/FilterSystem.js b/src/core/renderers/webgl/systems/FilterSystem.js index f47335d..37e28d2 100644 --- a/src/core/renderers/webgl/systems/FilterSystem.js +++ b/src/core/renderers/webgl/systems/FilterSystem.js @@ -111,7 +111,7 @@ // TODO we should fit the rect around the transform.. } - else + else if (filters[0].autoFit) { sourceFrame.fit(filterData.stack[0].destinationFrame); } diff --git a/src/core/renderers/webgl/systems/shader/shader/mapType.js b/src/core/renderers/webgl/systems/shader/shader/mapType.js index 581dace..917538a 100644 --- a/src/core/renderers/webgl/systems/shader/shader/mapType.js +++ b/src/core/renderers/webgl/systems/shader/shader/mapType.js @@ -15,7 +15,7 @@ } } - return GL_TABLE[type]; + return GL_TABLE[type]; }; var GL_TABLE = null; diff --git a/src/core/settings.js b/src/core/settings.js index 18c5877..38ef45c 100644 --- a/src/core/settings.js +++ b/src/core/settings.js @@ -2,6 +2,15 @@ import canUploadSameBuffer from './utils/canUploadSameBuffer'; /** + * User's customizable globals for overriding the default PIXI settings, such + * as a renderer's default resolution, framerate, float percision, etc. + * @example + * // Use the native window resolution as the default resolution + * // will support high-density displays when rendering + * PIXI.settings.RESOLUTION = window.devicePixelRatio. + * + * // Disable interpolation when scaling, will make texture be pixelated + * PIXI.settings.SCALE_MODE = PIXI.SCALE_MODES.NEAREST; * @namespace PIXI.settings */ export default { @@ -101,6 +110,9 @@ * @property {boolean} clearBeforeRender=true * @property {boolean} preserveDrawingBuffer=false * @property {boolean} roundPixels=false + * @property {number} width=800 + * @property {number} height=600 + * @property {boolean} legacy=false */ RENDER_OPTIONS: { view: null, @@ -112,6 +124,9 @@ clearBeforeRender: true, preserveDrawingBuffer: false, roundPixels: false, + width: 800, + height: 600, + legacy: false, }, /** diff --git a/src/core/shader/Program.js b/src/core/shader/Program.js index c0b538b..07b4398 100644 --- a/src/core/shader/Program.js +++ b/src/core/shader/Program.js @@ -1,6 +1,6 @@ import extractUniformsFromSrc from './extractUniformsFromSrc'; import generateUniformsSync from './generateUniformsSync'; -import glCore from 'pixi-gl-core'; +import shaderUtils from '../renderers/webgl/systems/shader/shader'; import { ProgramCache } from '../utils'; import getTestContext from '../utils/getTestContext'; @@ -64,14 +64,17 @@ } else { - vertexSrc = glCore.shader.setPrecision(vertexSrc, 'mediump'); - fragmentSrc = glCore.shader.setPrecision(fragmentSrc, 'mediump'); + vertexSrc = shaderUtils.setPrecision(vertexSrc, 'mediump'); + fragmentSrc = shaderUtils.setPrecision(fragmentSrc, 'mediump'); - const program = glCore.shader.compileProgram(gl, vertexSrc, fragmentSrc); + const program = shaderUtils.compileProgram(gl, vertexSrc, fragmentSrc); + console.log("<<<<<<>>><<"); this.attributeData = this.getAttributeData(program, gl); this.uniformData = this.getUniformData(program, gl); + console.log(this.uniformData); + //gl.deleteProgram(program); } } @@ -95,13 +98,13 @@ for (let i = 0; i < totalAttributes; i++) { const attribData = gl.getActiveAttrib(program, i); - const type = glCore.shader.mapType(gl, attribData.type); + const type = shaderUtils.mapType(gl, attribData.type); /*eslint-disable */ const data = { type: type, name: attribData.name, - size: glCore.shader.mapSize(type), + size: shaderUtils.mapSize(type), location: 0, }; /*eslint-enable */ @@ -137,13 +140,14 @@ // TODO expose this as a prop? // const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); - const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); + //const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); for (let i = 0; i < totalUniforms; i++) { const uniformData = gl.getActiveUniform(program, i); const name = uniformData.name.replace(/\[.*?\]/, ''); - const type = glCore.shader.mapType(gl, uniformData.type); + const type = shaderUtils.mapType(gl, uniformData.type); + console.log('mapping ' + uniformData.type + ' to ' + type); // if (!name.match(maskRegex)) { @@ -151,7 +155,7 @@ uniforms[name] = { type: type, size: uniformData.size, - value: glCore.shader.defaultValue(type, uniformData.size), + value: shaderUtils.defaultValue(type, uniformData.size), }; /*eslint-enable */ } diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index e387e99..b57d095 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -147,12 +147,12 @@ // so if _width is 0 then width was not set.. if (this._width) { - this.scale.x = sign(this.scale.x) * this._width / this.texture.orig.width; + this.scale.x = sign(this.scale.x) * this._width / this._texture.orig.width; } if (this._height) { - this.scale.y = sign(this.scale.y) * this._height / this.texture.orig.height; + this.scale.y = sign(this.scale.y) * this._height / this._texture.orig.height; } } diff --git a/src/core/sprites/canvas/CanvasTinter.js b/src/core/sprites/canvas/CanvasTinter.js index 27e5e0c..fd9de5d 100644 --- a/src/core/sprites/canvas/CanvasTinter.js +++ b/src/core/sprites/canvas/CanvasTinter.js @@ -18,7 +18,7 @@ */ getTintedTexture: (sprite, color) => { - const texture = sprite.texture; + const texture = sprite._texture; color = CanvasTinter.roundColor(color); diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 5a4edda..8b9b797 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -344,6 +344,7 @@ const texture = this._texture; const style = this._style; + const padding = style.trim ? 0 : style.padding; texture.baseTexture.valid = true; texture.baseTexture.resolution = this.resolution; @@ -353,11 +354,11 @@ texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - texture.trim.x = -style.padding; - texture.trim.y = -style.padding; + texture.trim.x = -padding; + texture.trim.y = -padding; - texture.orig.width = texture._frame.width - (style.padding * 2); - texture.orig.height = texture._frame.height - (style.padding * 2); + texture.orig.width = texture._frame.width - (padding * 2); + texture.orig.height = texture._frame.height - (padding * 2); // call sprite onTextureUpdate to update scale if _width or _height were set this._onTextureUpdate(); diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index 853da3d..8a9c34b 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -114,6 +114,38 @@ this.cacheId = null; this.validate(); + + /** + * Fired when a not-immediately-available source finishes loading. + * + * @protected + * @event PIXI.BaseTexture#loaded + * @param {PIXI.BaseTexture} baseTexture - Resource loaded. + */ + + /** + * Fired when a not-immediately-available source fails to load. + * + * @protected + * @event PIXI.BaseTexture#error + * @param {PIXI.BaseTexture} baseTexture - Resource errored. + */ + + /** + * Fired when BaseTexture is updated. + * + * @protected + * @event PIXI.BaseTexture#update + * @param {PIXI.BaseTexture} baseTexture - Instance of texture being updated. + */ + + /** + * Fired when BaseTexture is destroyed. + * + * @protected + * @event PIXI.BaseTexture#dispose + * @param {PIXI.BaseTexture} baseTexture - Instance of texture being destroyed. + */ } setResource(resource) diff --git a/src/core/textures/Spritesheet.js b/src/core/textures/Spritesheet.js index d469c62..9b24f27 100644 --- a/src/core/textures/Spritesheet.js +++ b/src/core/textures/Spritesheet.js @@ -208,6 +208,7 @@ // lets also add the frame to pixi's global cache for fromFrame and fromImage functions TextureCache[i] = this.textures[i]; + this.textures[i].textureCacheId = i; } frameIndex++; diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 0a2c379..2213087 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -147,9 +147,9 @@ /** * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. * - * @event update - * @memberof PIXI.Texture# + * @event PIXI.Texture#update * @protected + * @param {PIXI.Texture} texture - Instance of texture being updated. */ this._updateID = 0; @@ -159,6 +159,15 @@ * @type {Object} */ this.transform = null; + + /** + * The id under which this Texture has been added to the texture cache. This is + * automatically set in certain cases, but may not always be accurate, particularly if + * the texture is in the cache under multiple ids. + * + * @member {string} + */ + this.textureCacheId = null; } /** @@ -225,11 +234,17 @@ this._uvs = null; this.trim = null; this.orig = null; + this.textureCacheId = null; this.valid = false; - this.off('dispose', this.dispose, this); - this.off('update', this.update, this); + for (const prop in TextureCache) + { + if (TextureCache[prop] === this) + { + delete TextureCache[prop]; + } + } } /** @@ -315,14 +330,15 @@ */ static fromLoader(source, imageUrl, name) { - // console.log('added from loader...') + // console.log('added from loader...') const resource = new ImageResource(source);//.from(imageUrl, crossorigin);// document.createElement('img'); - // console.log('base resource ' + resource.width); + // console.log('base resource ' + resource.width); const baseTexture = new BaseTexture(resource, settings.SCALE_MODE, getResolutionOfUrl(imageUrl)); + /// console.log('base width ' + baseTexture.width); const texture = new Texture(baseTexture); @@ -335,6 +351,7 @@ // lets also add the frame to pixi's global cache for fromFrame and fromImage fucntions BaseTextureCache[name] = baseTexture; TextureCache[name] = texture; + texture.textureCacheId = name; // also add references by url if they are different. if (name !== imageUrl) @@ -355,6 +372,10 @@ */ static addTextureToCache(texture, id) { + if (!texture.textureCacheId) + { + texture.textureCacheId = id; + } TextureCache[id] = texture; } @@ -495,6 +516,7 @@ */ Texture.EMPTY = new Texture(new BaseTexture()); removeAllHandlers(Texture.EMPTY); +removeAllHandlers(Texture.EMPTY.baseTexture); /** * A white texture of 10x10 size, used for graphics and other things @@ -505,3 +527,4 @@ */ Texture.WHITE = createWhiteTexture(); removeAllHandlers(Texture.WHITE); +removeAllHandlers(Texture.WHITE.baseTexture); diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index 59eb9fd..a9b9f51 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -1,6 +1,7 @@ import BaseTexture from './BaseTexture'; import { uid, BaseTextureCache } from '../utils'; -import * as ticker from '../ticker'; +import { shared } from '../ticker'; +import { UPDATE_PRIORITY } from '../const'; /** * A texture of a [playing] Video. @@ -125,7 +126,7 @@ if (!this._isAutoUpdating && this.autoUpdate) { - ticker.shared.add(this.update, this); + shared.add(this.update, this, UPDATE_PRIORITY.HIGH); this._isAutoUpdating = true; } } @@ -139,7 +140,7 @@ { if (this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); this._isAutoUpdating = false; } } @@ -187,7 +188,7 @@ { if (this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); } if (this.source && this.source._pixiId) @@ -281,12 +282,12 @@ if (!this._autoUpdate && this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); this._isAutoUpdating = false; } else if (this._autoUpdate && !this._isAutoUpdating) { - ticker.shared.add(this.update, this); + shared.add(this.update, this, UPDATE_PRIORITY.HIGH); this._isAutoUpdating = true; } } diff --git a/src/core/ticker/Ticker.js b/src/core/ticker/Ticker.js index 17a1517..40df138 100644 --- a/src/core/ticker/Ticker.js +++ b/src/core/ticker/Ticker.js @@ -1,12 +1,10 @@ import settings from '../settings'; -import EventEmitter from 'eventemitter3'; - -// Internal event used by composed emitter -const TICK = 'tick'; +import { UPDATE_PRIORITY } from '../const'; +import TickerListener from './TickerListener'; /** * A Ticker class that runs an update loop that other objects listen to. - * This class is composed around an EventEmitter object to add listeners + * This class is composed around listeners * meant for execution on the next requested animation frame. * Animation frames are requested only when necessary, * e.g. When the ticker is started and the emitter has listeners. @@ -22,10 +20,11 @@ constructor() { /** - * Internal emitter used to fire 'tick' event + * The first listener. All new listeners added are chained on this. * @private + * @type {TickerListener} */ - this._emitter = new EventEmitter(); + this._head = new TickerListener(null, null, Infinity); /** * Internal current frame request ID @@ -131,7 +130,7 @@ // Invoke listeners now this.update(time); // Listener side effects may have modified ticker state. - if (this.started && this._requestId === null && this._emitter.listeners(TICK, true)) + if (this.started && this._requestId === null && this._head.next) { this._requestId = requestAnimationFrame(this._tick); } @@ -148,7 +147,7 @@ */ _requestIfNeeded() { - if (this._requestId === null && this._emitter.listeners(TICK, true)) + if (this._requestId === null && this._head.next) { // ensure callbacks get correct delta this.lastTime = performance.now(); @@ -193,35 +192,72 @@ } /** - * Calls {@link module:eventemitter3.EventEmitter#on} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. + * Register a handler for tick events. Calls continuously unless + * it is removed or the ticker is stopped. * * @param {Function} fn - The listener function to be added for updates * @param {Function} [context] - The listener context + * @param {number} [priority=PIXI.UPDATE_PRIORITY.NORMAL] - The priority for emitting * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - add(fn, context) + add(fn, context, priority = UPDATE_PRIORITY.NORMAL) { - this._emitter.on(TICK, fn, context); - - this._startIfPossible(); - - return this; + return this._addListener(new TickerListener(fn, context, priority)); } /** - * Calls {@link module:eventemitter3.EventEmitter#once} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. + * Add a handler for the tick event which is only execute once. * * @param {Function} fn - The listener function to be added for one update * @param {Function} [context] - The listener context + * @param {number} [priority=PIXI.UPDATE_PRIORITY.NORMAL] - The priority for emitting * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - addOnce(fn, context) + addOnce(fn, context, priority = UPDATE_PRIORITY.NORMAL) { - this._emitter.once(TICK, fn, context); + return this._addListener(new TickerListener(fn, context, priority, true)); + } + + /** + * Internally adds the event handler so that it can be sorted by priority. + * Priority allows certain handler (user, AnimatedSprite, Interaction) to be run + * before the rendering. + * + * @private + * @param {TickerListener} listener - Current listener being added. + * @returns {PIXI.ticker.Ticker} This instance of a ticker + */ + _addListener(listener) + { + // For attaching to head + let current = this._head.next; + let previous = this._head; + + // Add the first item + if (!current) + { + listener.connect(previous); + } + else + { + // Go from highest to lowest priority + while (current) + { + if (listener.priority >= current.priority) + { + listener.connect(previous); + break; + } + previous = current; + current = current.next; + } + + // Not yet connected + if (!listener.previous) + { + listener.connect(previous); + } + } this._startIfPossible(); @@ -229,19 +265,33 @@ } /** - * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. - * It checks if the emitter has listeners for 'tick' event. - * If it does, then it cancels the animation frame. + * Removes any handlers matching the function and context parameters. + * If no handlers are left after removing, then it cancels the animation frame. * - * @param {Function} [fn] - The listener function to be removed + * @param {Function} fn - The listener function to be removed * @param {Function} [context] - The listener context to be removed * @returns {PIXI.ticker.Ticker} This instance of a ticker */ remove(fn, context) { - this._emitter.off(TICK, fn, context); + let listener = this._head.next; - if (!this._emitter.listeners(TICK, true)) + while (listener) + { + // We found a match, lets remove it + // no break to delete all possible matches + // incase a listener was added 2+ times + if (listener.match(fn, context)) + { + listener = listener.destroy(); + } + else + { + listener = listener.next; + } + } + + if (!this._head.next) { this._cancelIfNeeded(); } @@ -276,6 +326,25 @@ } /** + * Destroy the ticker and don't use after this. Calling + * this method removes all references to internal events. + */ + destroy() + { + this.stop(); + + let listener = this._head.next; + + while (listener) + { + listener = listener.destroy(true); + } + + this._head.destroy(); + this._head = null; + } + + /** * Triggers an update. An update entails setting the * current {@link PIXI.ticker.Ticker#elapsedMS}, * the current {@link PIXI.ticker.Ticker#deltaTime}, @@ -320,8 +389,22 @@ this.deltaTime = elapsedMS * settings.TARGET_FPMS * this.speed; + // Cache a local reference, in-case ticker is destroyed + // during the emit, we can still check for head.next + const head = this._head; + // Invoke listeners added to internal emitter - this._emitter.emit(TICK, this.deltaTime); + let listener = head.next; + + while (listener) + { + listener = listener.emit(this.deltaTime); + } + + if (!head.next) + { + this._cancelIfNeeded(); + } } else { diff --git a/src/core/ticker/TickerListener.js b/src/core/ticker/TickerListener.js new file mode 100644 index 0000000..2bedb34 --- /dev/null +++ b/src/core/ticker/TickerListener.js @@ -0,0 +1,158 @@ +/** + * Internal class for handling the priority sorting of ticker handlers. + * + * @private + * @class + * @memberof PIXI.ticker + */ +export default class TickerListener +{ + /** + * Constructor + * + * @param {Function} fn - The listener function to be added for one update + * @param {Function} [context=null] - The listener context + * @param {number} [priority=0] - The priority for emitting + * @param {boolean} [once=false] - If the handler should fire once + */ + constructor(fn, context = null, priority = 0, once = false) + { + /** + * The handler function to execute. + * @member {Function} + */ + this.fn = fn; + + /** + * The calling to execute. + * @member {Function} + */ + this.context = context; + + /** + * The current priority. + * @member {number} + */ + this.priority = priority; + + /** + * If this should only execute once. + * @member {boolean} + */ + this.once = once; + + /** + * The next item in chain. + * @member {TickerListener} + */ + this.next = null; + + /** + * The previous item in chain. + * @member {TickerListener} + */ + this.previous = null; + + /** + * `true` if this listener has been destroyed already. + * @member {boolean} + * @private + */ + this._destroyed = false; + } + + /** + * Simple compare function to figure out if a function and context match. + * + * @param {Function} fn - The listener function to be added for one update + * @param {Function} context - The listener context + * @return {boolean} `true` if the listener match the arguments + */ + match(fn, context) + { + context = context || null; + + return this.fn === fn && this.context === context; + } + + /** + * Emit by calling the current function. + * @param {number} deltaTime - time since the last emit. + * @return {TickerListener} Next ticker + */ + emit(deltaTime) + { + if (this.context) + { + this.fn.call(this.context, deltaTime); + } + else + { + this.fn(deltaTime); + } + + if (this.once) + { + this.destroy(); + } + + const redirect = this.next; + + // Soft-destroying should remove + // the next reference + if (this._destroyed) + { + this.next = null; + } + + return redirect; + } + + /** + * Connect to the list. + * @param {TickerListener} previous - Input node, previous listener + */ + connect(previous) + { + this.previous = previous; + if (previous.next) + { + previous.next.previous = this; + } + this.next = previous.next; + previous.next = this; + } + + /** + * Destroy and don't use after this. + * @param {boolean} [hard = false] `true` to remove the `next` reference, this + * is considered a hard destroy. Soft destroy maintains the next reference. + * @return {TickerListener} The listener to redirect while emitting or removing. + */ + destroy(hard = false) + { + this._destroyed = true; + this.fn = null; + this.context = null; + + // Disconnect, hook up next and previous + if (this.previous) + { + this.previous.next = this.next; + } + + if (this.next) + { + this.next.previous = this.previous; + } + + // Redirect to the next item + const redirect = this.previous; + + // Remove references + this.next = hard ? null : redirect; + this.previous = null; + + return redirect; + } +} diff --git a/src/core/ticker/index.js b/src/core/ticker/index.js index 4b3017c..c3020d4 100644 --- a/src/core/ticker/index.js +++ b/src/core/ticker/index.js @@ -45,8 +45,25 @@ const shared = new Ticker(); shared.autoStart = true; +shared.destroy = () => +{ + // protect destroying shared ticker + // this is used by other internal systems + // like AnimatedSprite and InteractionManager +}; /** + * This namespace contains an API for interacting with PIXI's internal global update loop. + * + * This ticker is used for rendering, {@link PIXI.extras.AnimatedSprite AnimatedSprite}, + * {@link PIXI.interaction.InteractionManager InteractionManager} and many other time-based PIXI systems. + * @example + * const ticker = new PIXI.ticker.Ticker(); + * ticker.stop(); + * ticker.add((deltaTime) => { + * // do something every frame + * }); + * ticker.start(); * @namespace PIXI.ticker */ export { shared, Ticker }; diff --git a/src/core/utils/index.js b/src/core/utils/index.js index be44750..7441f7b 100644 --- a/src/core/utils/index.js +++ b/src/core/utils/index.js @@ -9,6 +9,21 @@ let saidHello = false; /** + * Generalized convenience utilities for PIXI. + * @example + * // Extend PIXI's internal Event Emitter. + * class MyEmitter extends PIXI.utils.EventEmitter { + * constructor() { + * super(); + * console.log("Emitter created!"); + * } + * } + * + * // Get info on current device + * console.log(PIXI.utils.isMobile); + * + * // Convert hex color to string + * console.log(PIXI.utils.hex2string(0xff00ff)); // returns: "#ff00ff" * @namespace PIXI.utils */ export { diff --git a/src/deprecation.js b/src/deprecation.js index b9c73b3..33daeec 100644 --- a/src/deprecation.js +++ b/src/deprecation.js @@ -918,6 +918,35 @@ }); /** + * @method + * @name PIXI.prepare.BasePrepare#register + * @see PIXI.prepare.BasePrepare#registerFindHook + * @deprecated since version 4.4.2 + * @param {Function} [addHook] - Function call that takes two parameters: `item:*, queue:Array` + * function must return `true` if it was able to add item to the queue. + * @param {Function} [uploadHook] - Function call that takes two parameters: `prepare:CanvasPrepare, item:*` and + * function must return `true` if it was able to handle upload of item. + * @return {PIXI.BasePrepare} Instance of plugin for chaining. + */ +prepare.BasePrepare.prototype.register = function register(addHook, uploadHook) +{ + warn('renderer.plugins.prepare.register is now deprecated, ' + + 'please use renderer.plugins.prepare.registerFindHook & renderer.plugins.prepare.registerUploadHook'); + + if (addHook) + { + this.registerFindHook(addHook); + } + + if (uploadHook) + { + this.registerUploadHook(uploadHook); + } + + return this; +}; + +/** * The number of graphics or textures to upload to the GPU. * * @name PIXI.prepare.canvas.UPLOADS_PER_FRAME @@ -969,67 +998,73 @@ }, }); -Object.defineProperties(loaders.Resource.prototype, { - isJson: { - get() - { - warn('The isJson property is deprecated, please use `resource.type === Resource.TYPE.JSON`.'); +if (loaders.Loader) +{ + const Resource = loaders.Resource; + const Loader = loaders.Loader; - return this.type === loaders.Loader.Resource.TYPE.JSON; + Object.defineProperties(Resource.prototype, { + isJson: { + get() + { + warn('The isJson property is deprecated, please use `resource.type === Resource.TYPE.JSON`.'); + + return this.type === Resource.TYPE.JSON; + }, }, - }, - isXml: { - get() - { - warn('The isXml property is deprecated, please use `resource.type === Resource.TYPE.XML`.'); + isXml: { + get() + { + warn('The isXml property is deprecated, please use `resource.type === Resource.TYPE.XML`.'); - return this.type === loaders.Loader.Resource.TYPE.XML; + return this.type === Resource.TYPE.XML; + }, }, - }, - isImage: { - get() - { - warn('The isImage property is deprecated, please use `resource.type === Resource.TYPE.IMAGE`.'); + isImage: { + get() + { + warn('The isImage property is deprecated, please use `resource.type === Resource.TYPE.IMAGE`.'); - return this.type === loaders.Loader.Resource.TYPE.IMAGE; + return this.type === Resource.TYPE.IMAGE; + }, }, - }, - isAudio: { - get() - { - warn('The isAudio property is deprecated, please use `resource.type === Resource.TYPE.AUDIO`.'); + isAudio: { + get() + { + warn('The isAudio property is deprecated, please use `resource.type === Resource.TYPE.AUDIO`.'); - return this.type === loaders.Loader.Resource.TYPE.AUDIO; + return this.type === Resource.TYPE.AUDIO; + }, }, - }, - isVideo: { - get() - { - warn('The isVideo property is deprecated, please use `resource.type === Resource.TYPE.VIDEO`.'); + isVideo: { + get() + { + warn('The isVideo property is deprecated, please use `resource.type === Resource.TYPE.VIDEO`.'); - return this.type === loaders.Loader.Resource.TYPE.VIDEO; + return this.type === Resource.TYPE.VIDEO; + }, }, - }, -}); + }); -Object.defineProperties(loaders.Loader.prototype, { - before: { - get() - { - warn('The before() method is deprecated, please use pre().'); + Object.defineProperties(Loader.prototype, { + before: { + get() + { + warn('The before() method is deprecated, please use pre().'); - return this.pre; + return this.pre; + }, }, - }, - after: { - get() - { - warn('The after() method is deprecated, please use use().'); + after: { + get() + { + warn('The after() method is deprecated, please use use().'); - return this.use; + return this.use; + }, }, - }, -}); + }); +} /** * @name PIXI.interaction.interactiveTarget#defaultCursor diff --git a/src/extract/canvas/CanvasExtract.js b/src/extract/canvas/CanvasExtract.js index 941ca05..3ac5aee 100644 --- a/src/extract/canvas/CanvasExtract.js +++ b/src/extract/canvas/CanvasExtract.js @@ -8,7 +8,7 @@ * An instance of this class is automatically created by default, and can be found at renderer.plugins.extract * * @class - * @memberof PIXI + * @memberof PIXI.extract */ export default class CanvasExtract { @@ -21,9 +21,9 @@ /** * Collection of methods for extracting data (image, pixels, etc.) from a display object or render texture * - * @member {PIXI.CanvasExtract} extract + * @member {PIXI.extract.CanvasExtract} extract * @memberof PIXI.CanvasRenderer# - * @see PIXI.CanvasExtract + * @see PIXI.extract.CanvasExtract */ renderer.extract = this; } diff --git a/src/extract/index.js b/src/extract/index.js index 0f2170d..e9478c1 100644 --- a/src/extract/index.js +++ b/src/extract/index.js @@ -1,2 +1,22 @@ +/** + * This namespace provides renderer-specific plugins for exporting content from a renderer. + * For instance, these plugins can be used for saving an Image, Canvas element or for exporting the raw image data (pixels). + * + * Do not instantiate these plugins directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. + * @example + * // Create a new app (will auto-add extract plugin to renderer) + * const app = new PIXI.Application(); + * + * // Draw a red circle + * const graphics = new PIXI.Graphics() + * .beginFill(0xFF0000) + * .drawCircle(0, 0, 50); + * + * // Render the graphics as an HTMLImageElement + * const image = app.renderer.plugins.extract.image(graphics); + * document.body.appendChild(image); + * @namespace PIXI.extract + */ export { default as webgl } from './webgl/WebGLExtract'; export { default as canvas } from './canvas/CanvasExtract'; diff --git a/src/extract/webgl/WebGLExtract.js b/src/extract/webgl/WebGLExtract.js index 60228bb..1c3dd30 100644 --- a/src/extract/webgl/WebGLExtract.js +++ b/src/extract/webgl/WebGLExtract.js @@ -9,7 +9,7 @@ * An instance of this class is automatically created by default, and can be found at renderer.plugins.extract * * @class - * @memberof PIXI + * @memberof PIXI.extract */ export default class WebGLExtract { @@ -22,9 +22,9 @@ /** * Collection of methods for extracting data (image, pixels, etc.) from a display object or render texture * - * @member {PIXI.WebGLExtract} extract + * @member {PIXI.extract.WebGLExtract} extract * @memberof PIXI.WebGLRenderer# - * @see PIXI.WebGLExtract + * @see PIXI.extract.WebGLExtract */ renderer.extract = this; } diff --git a/src/extras/AnimatedSprite.js b/src/extras/AnimatedSprite.js index 13b804f..5297778 100644 --- a/src/extras/AnimatedSprite.js +++ b/src/extras/AnimatedSprite.js @@ -137,7 +137,7 @@ this.playing = true; if (this._autoUpdate) { - core.ticker.shared.add(this.update, this); + core.ticker.shared.add(this.update, this, core.UPDATE_PRIORITY.HIGH); } } diff --git a/src/extras/TextureTransform.js b/src/extras/TextureTransform.js index 78e6e40..16536d2 100644 --- a/src/extras/TextureTransform.js +++ b/src/extras/TextureTransform.js @@ -65,8 +65,36 @@ } /** + * Multiplies uvs array to transform + * @param {Float32Array} uvs mesh uvs + * @param {Float32Array} [out=uvs] output + * @returns {Float32Array} output + */ + multiplyUvs(uvs, out) + { + if (out === undefined) + { + out = uvs; + } + + const mat = this.mapCoord; + + for (let i = 0; i < uvs.length; i += 2) + { + const x = uvs[i]; + const y = uvs[i + 1]; + + out[i] = (x * mat.a) + (y * mat.c) + mat.tx; + out[i + 1] = (x * mat.b) + (y * mat.d) + mat.ty; + } + + return out; + } + + /** * updates matrices if texture was changed * @param {boolean} forceUpdate if true, matrices will be updated any case + * @returns {boolean} whether or not it was updated */ update(forceUpdate) { @@ -74,13 +102,13 @@ if (!tex || !tex.valid) { - return; + return false; } if (!forceUpdate && this._lastTextureID === tex._updateID) { - return; + return false; } this._lastTextureID = tex._updateID; @@ -110,5 +138,7 @@ frame[3] = (tex._frame.y + tex._frame.height - margin + offset) / texBase.height; this.uClampOffset[0] = offset / texBase.realWidth; this.uClampOffset[1] = offset / texBase.realHeight; + + return true; } } diff --git a/src/extras/TilingSprite.js b/src/extras/TilingSprite.js index 1f6014f..a3f027f 100644 --- a/src/extras/TilingSprite.js +++ b/src/extras/TilingSprite.js @@ -180,9 +180,9 @@ const transform = this.worldTransform; const resolution = renderer.resolution; const baseTexture = texture.baseTexture; - const baseTextureResolution = texture.baseTexture.resolution; - const modX = (this.tilePosition.x / this.tileScale.x) % texture._frame.width; - const modY = (this.tilePosition.y / this.tileScale.y) % texture._frame.height; + const baseTextureResolution = baseTexture.resolution; + const modX = ((this.tilePosition.x / this.tileScale.x) % texture._frame.width) * baseTextureResolution; + const modY = ((this.tilePosition.y / this.tileScale.y) % texture._frame.height) * baseTextureResolution; // create a nice shiny pattern! // TODO this needs to be refreshed if texture changes.. diff --git a/src/extras/cacheAsBitmap.js b/src/extras/cacheAsBitmap.js index 5dd5640..aea06a8 100644 --- a/src/extras/cacheAsBitmap.js +++ b/src/extras/cacheAsBitmap.js @@ -227,7 +227,16 @@ this.transform._parentID = -1; // restore the transform of the cached sprite to avoid the nasty flicker.. - this.updateTransform(); + if (!this.parent) + { + this.parent = renderer._tempDisplayObjectParent; + this.updateTransform(); + this.parent = null; + } + else + { + this.updateTransform(); + } // map the hit test.. this.containsPoint = cachedSprite.containsPoint.bind(cachedSprite); @@ -314,7 +323,17 @@ cachedSprite._bounds = this._bounds; cachedSprite.alpha = cacheAlpha; - this.updateTransform(); + if (!this.parent) + { + this.parent = renderer._tempDisplayObjectParent; + this.updateTransform(); + this.parent = null; + } + else + { + this.updateTransform(); + } + this.updateTransform = this.displayObjectUpdateTransform; this._cacheData.sprite = cachedSprite; @@ -358,9 +377,12 @@ * Destroys the cached object. * * @private + * @param {object|boolean} [options] - Options parameter. A boolean will act as if all options + * have been set to that value. + * Used when destroying containers, see the Container.destroy method. */ -DisplayObject.prototype._cacheAsBitmapDestroy = function _cacheAsBitmapDestroy() +DisplayObject.prototype._cacheAsBitmapDestroy = function _cacheAsBitmapDestroy(options) { this.cacheAsBitmap = false; - this.destroy(); + this.destroy(options); }; diff --git a/src/extras/index.js b/src/extras/index.js index bbd8bbf..a34d7ec 100644 --- a/src/extras/index.js +++ b/src/extras/index.js @@ -1,7 +1,7 @@ /** + * Additional PIXI DisplayObjects for animation, tiling and bitmap text. * @namespace PIXI.extras */ -export { default as TextureTransform } from './TextureTransform'; export { default as AnimatedSprite } from './AnimatedSprite'; export { default as TilingSprite } from './TilingSprite'; export { default as TilingSpriteRenderer } from './webgl/TilingSpriteRenderer'; diff --git a/src/extras/webgl/TilingSpriteRenderer.js b/src/extras/webgl/TilingSpriteRenderer.js index aeec172..eee3b5c 100644 --- a/src/extras/webgl/TilingSpriteRenderer.js +++ b/src/extras/webgl/TilingSpriteRenderer.js @@ -11,7 +11,7 @@ * WebGL renderer plugin for tiling sprites * * @class - * @memberof PIXI + * @memberof PIXI.extras * @extends PIXI.ObjectRenderer */ export default class TilingSpriteRenderer extends core.ObjectRenderer @@ -118,7 +118,7 @@ tempMat.invert(); if (isSimple) { - tempMat.append(uv.mapCoord); + tempMat.prepend(uv.mapCoord); } else { diff --git a/src/filters/displacement/DisplacementFilter.js b/src/filters/displacement/DisplacementFilter.js index d5355c3..b5d030c 100644 --- a/src/filters/displacement/DisplacementFilter.js +++ b/src/filters/displacement/DisplacementFilter.js @@ -35,7 +35,7 @@ this.maskSprite = sprite; this.maskMatrix = maskMatrix; - this.uniforms.mapSampler = sprite.texture; + this.uniforms.mapSampler = sprite._texture; this.uniforms.filterMatrix = maskMatrix; this.uniforms.scale = { x: 1, y: 1 }; diff --git a/src/filters/index.js b/src/filters/index.js index f3e8862..7ceb50a 100644 --- a/src/filters/index.js +++ b/src/filters/index.js @@ -1,4 +1,21 @@ /** + * This namespace contains WebGL-only display filters that can be applied + * to DisplayObjects using the {@link PIXI.DisplayObject#filters filters} property. + * @example + * // Create a new application + * const app = new PIXI.Application(); + * + * // Draw a green rectangle + * const rect = new PIXI.Graphics() + * .beginFill(0x00ff00) + * .drawRect(40, 40, 200, 200); + * + * // Add a blur filter + * rect.filters = [new PIXI.filters.BlurFilter()]; + * + * // Display rectangle + * app.stage.addChild(rect); + * document.body.appendChild(app.view); * @namespace PIXI.filters */ export { default as FXAAFilter } from './fxaa/FXAAFilter'; diff --git a/src/index.js b/src/index.js index c553427..77da490 100644 --- a/src/index.js +++ b/src/index.js @@ -20,6 +20,14 @@ import { utils } from './core'; utils.mixins.performMixins(); +/** + * Alias for {@link PIXI.loaders.shared}. + * @name loader + * @memberof PIXI + * @type {PIXI.loader.Loader} + */ +const loader = loaders.shared || null; + export { accessibility, extract, @@ -30,18 +38,8 @@ mesh, particles, prepare, + loader, }; -/** - * A premade instance of the loader that can be used to load resources. - * - * @name loader - * @memberof PIXI - * @property {PIXI.loaders.Loader} - */ -const loader = loaders && loaders.Loader ? new loaders.Loader() : null; // check is there in case user excludes loader lib - -export { loader }; - // Always export pixi globally. global.PIXI = exports; // eslint-disable-line diff --git a/src/interaction/InteractionData.js b/src/interaction/InteractionData.js index 02d53a3..449f8cc 100644 --- a/src/interaction/InteractionData.js +++ b/src/interaction/InteractionData.js @@ -30,7 +30,10 @@ /** * When passed to an event handler, this will be the original DOM Event that was captured * - * @member {Event} + * @see https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent + * @see https://developer.mozilla.org/en-US/docs/Web/API/TouchEvent + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent + * @member {MouseEvent|TouchEvent|PointerEvent} */ this.originalEvent = null; } diff --git a/src/interaction/InteractionManager.js b/src/interaction/InteractionManager.js index 8baf13a..2d75c7c 100644 --- a/src/interaction/InteractionManager.js +++ b/src/interaction/InteractionManager.js @@ -13,6 +13,14 @@ const MOUSE_POINTER_ID = 'MOUSE'; +// helpers for hitTest() - only used inside hitTest() +const hitTestEvent = { + target: null, + data: { + global: null, + }, +}; + /** * The interaction manager deals with mouse, touch and pointer events. Any DisplayObject can be interactive * if its interactive parameter is set to true @@ -245,67 +253,60 @@ this.setTargetElement(this.renderer.view, this.renderer.resolution); /** - * Fired when a pointer device button (usually a mouse button) is pressed on the display + * Fired when a pointer device button (usually a mouse left-button) is pressed on the display * object. * - * @event mousedown - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mousedown + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device secondary button (usually a mouse right-button) is pressed * on the display object. * - * @event rightdown - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightdown + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** - * Fired when a pointer device button (usually a mouse button) is released over the display + * Fired when a pointer device button (usually a mouse left-button) is released over the display * object. * - * @event mouseup - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseup + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device secondary button (usually a mouse right-button) is released * over the display object. * - * @event rightup - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightup + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** - * Fired when a pointer device button (usually a mouse button) is pressed and released on + * Fired when a pointer device button (usually a mouse left-button) is pressed and released on * the display object. * - * @event click - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#click + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device secondary button (usually a mouse right-button) is pressed * and released on the display object. * - * @event rightclick - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightclick + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** - * Fired when a pointer device button (usually a mouse button) is released outside the + * Fired when a pointer device button (usually a mouse left-button) is released outside the * display object that initially registered a * [mousedown]{@link PIXI.interaction.InteractionManager#event:mousedown}. * - * @event mouseupoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** @@ -313,146 +314,362 @@ * outside the display object that initially registered a * [rightdown]{@link PIXI.interaction.InteractionManager#event:rightdown}. * - * @event rightupoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device (usually a mouse) is moved while over the display object * - * @event mousemove - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mousemove + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device (usually a mouse) is moved onto the display object * - * @event mouseover - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseover + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device (usually a mouse) is moved off the display object * - * @event mouseout - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseout + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is pressed on the display object. * - * @event pointerdown - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerdown + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is released over the display object. * - * @event pointerup - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerup + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when the operating system cancels a pointer event * - * @event pointercancel - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointercancel + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is pressed and released on the display object. * - * @event pointertap - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointertap + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is released outside the display object that initially * registered a [pointerdown]{@link PIXI.interaction.InteractionManager#event:pointerdown}. * - * @event pointerupoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device is moved while over the display object * - * @event pointermove - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointermove + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device is moved onto the display object * - * @event pointerover - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerover + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device is moved off the display object * - * @event pointerout - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerout + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is placed on the display object. * - * @event touchstart - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchstart + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is removed from the display object. * - * @event touchend - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchend + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when the operating system cancels a touch * - * @event touchcancel - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchcancel + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is placed and removed from the display object. * - * @event tap - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#tap + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is removed outside of the display object that initially * registered a [touchstart]{@link PIXI.interaction.InteractionManager#event:touchstart}. * - * @event touchendoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchendoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is moved along the display object. * - * @event touchmove - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchmove + * @param {PIXI.interaction.InteractionData} event - Interaction data */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is pressed on the display. + * object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mousedown + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is pressed + * on the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightdown + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is released over the display + * object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseup + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is released + * over the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightup + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is pressed and released on + * the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#click + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is pressed + * and released on the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightclick + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is released outside the + * display object that initially registered a + * [mousedown]{@link PIXI.DisplayObject#event:mousedown}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is released + * outside the display object that initially registered a + * [rightdown]{@link PIXI.DisplayObject#event:rightdown}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device (usually a mouse) is moved while over the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mousemove + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device (usually a mouse) is moved onto the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseover + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device (usually a mouse) is moved off the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseout + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is pressed on the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerdown + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is released over the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerup + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when the operating system cancels a pointer event. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointercancel + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is pressed and released on the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointertap + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is released outside the display object that initially + * registered a [pointerdown]{@link PIXI.DisplayObject#event:pointerdown}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device is moved while over the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointermove + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device is moved onto the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerover + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device is moved off the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerout + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is placed on the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchstart + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is removed from the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchend + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when the operating system cancels a touch. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchcancel + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is placed and removed from the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#tap + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is removed outside of the display object that initially + * registered a [touchstart]{@link PIXI.DisplayObject#event:touchstart}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchendoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is moved along the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchmove + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + } + + /** + * Hit tests a point against the display tree, returning the first interactive object that is hit. + * + * @param {PIXI.Point} globalPoint - A point to hit test with, in global space. + * @param {PIXI.Container} [root] - The root display object to start from. If omitted, defaults + * to the last rendered root of the associated renderer. + * @return {PIXI.DisplayObject} The hit display object, if any. + */ + hitTest(globalPoint, root) + { + // clear the target for our hit test + hitTestEvent.target = null; + // assign the global point + hitTestEvent.data.global = globalPoint; + // ensure safety of the root + if (!root) + { + root = this.renderer._lastObjectRendered; + } + // run the hit test + this.processInteractive(hitTestEvent, root, null, true); + // return our found object - it'll be null if we didn't hit anything + + return hitTestEvent.target; } /** @@ -487,7 +704,7 @@ return; } - core.ticker.shared.add(this.update, this); + core.ticker.shared.add(this.update, this, core.UPDATE_PRIORITY.INTERACTION); if (window.navigator.msPointerEnabled) { @@ -804,8 +1021,6 @@ } } - let keepHitTestingAfterChildren = hitTest; - // ** FREE TIP **! If an object is not interactive or has no buttons in it // (such as a game scene!) set interactiveChildren to false for that displayObject. // This will allow pixi to completely ignore and bypass checking the displayObjects children. @@ -818,7 +1033,9 @@ const child = children[i]; // time to get recursive.. if this function will return if something is hit.. - if (this.processInteractive(interactionEvent, child, func, hitTest, interactiveParent)) + const childHit = this.processInteractive(interactionEvent, child, func, hitTest, interactiveParent); + + if (childHit) { // its a good idea to check if a child has lost its parent. // this means it has been removed whilst looping so its best @@ -827,8 +1044,6 @@ continue; } - hit = true; - // we no longer need to hit test any more objects in this container as we we // now know the parent has been hit interactiveParent = false; @@ -838,36 +1053,41 @@ // This means we no longer need to hit test anything else. We still need to run // through all objects, but we don't need to perform any hit tests. - keepHitTestingAfterChildren = false; - - if (child.interactive) + if (childHit) { - hitTest = false; + if (interactionEvent.target) + { + hitTest = false; + } + hit = true; } - - // we can break now as we have hit an object. } } } - hitTest = keepHitTestingAfterChildren; - // no point running this if the item is not interactive or does not have an interactive parent. if (interactive) { // if we are hit testing (as in we have no hit any objects yet) // We also don't need to worry about hit testing if once of the displayObjects children - // has already been hit! - if (hitTest && !hit) + // has already been hit - but only if it was interactive, otherwise we need to keep + // looking for an interactive child, just in case we hit one + if (hitTest && !interactionEvent.target) { if (displayObject.hitArea) { displayObject.worldTransform.applyInverse(point, this._tempPoint); - hit = displayObject.hitArea.contains(this._tempPoint.x, this._tempPoint.y); + if (displayObject.hitArea.contains(this._tempPoint.x, this._tempPoint.y)) + { + hit = true; + } } else if (displayObject.containsPoint) { - hit = displayObject.containsPoint(point); + if (displayObject.containsPoint(point)) + { + hit = true; + } } } @@ -878,7 +1098,10 @@ interactionEvent.target = displayObject; } - func(interactionEvent, displayObject, hit); + if (func) + { + func(interactionEvent, displayObject, !!hit); + } } } @@ -914,7 +1137,7 @@ { const event = events[i]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -998,7 +1221,7 @@ { const event = events[i]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1098,7 +1321,7 @@ const test = isRightButton ? flags.RIGHT_DOWN : flags.LEFT_DOWN; - const isDown = trackingData !== undefined && (trackingData.flags | test); + const isDown = trackingData !== undefined && (trackingData.flags & test); if (hit) { @@ -1118,11 +1341,11 @@ { if (isRightButton) { - trackingData.rightDown = hit; + trackingData.rightDown = false; } else { - trackingData.leftDown = hit; + trackingData.leftDown = false; } } } @@ -1180,7 +1403,7 @@ { const event = events[i]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1255,7 +1478,7 @@ this.setCursorMode(null); } - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1268,6 +1491,12 @@ { this.emit('mouseout', interactionEvent); } + else + { + // we can get touchleave events after touchend, so we want to make sure we don't + // introduce memory leaks + this.releaseInteractionDataForPointerId(interactionData.identifier); + } } /** @@ -1344,7 +1573,7 @@ // Only mouse and pointer can call onPointerOver, so events will always be length 1 const event = events[0]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1366,12 +1595,14 @@ * Get InteractionData for a given pointerId. Store that data as well * * @private - * @param {number} pointerId - Identifier from a pointer event + * @param {PointerEvent} event - Normalized pointer event, output from normalizeToPointerData * @return {InteractionData} - Interaction data for the given pointer identifier */ - getInteractionDataForPointerId(pointerId) + getInteractionDataForPointerId(event) { - if (pointerId === MOUSE_POINTER_ID) + const pointerId = event.pointerId; + + if (pointerId === MOUSE_POINTER_ID || event.pointerType === 'mouse') { return this.mouse; } diff --git a/src/interaction/InteractionTrackingData.js b/src/interaction/InteractionTrackingData.js index 6a1f69e..8493b63 100644 --- a/src/interaction/InteractionTrackingData.js +++ b/src/interaction/InteractionTrackingData.js @@ -35,8 +35,10 @@ } /** + * Unique pointer id of the event + * * @readonly - * @type {number} Unique pointer id of the event + * @member {number} */ get pointerId() { diff --git a/src/interaction/index.js b/src/interaction/index.js index 56c5b09..72f7d71 100644 --- a/src/interaction/index.js +++ b/src/interaction/index.js @@ -1,4 +1,8 @@ /** + * This namespace contains a renderer plugin for handling mouse, pointer, and touch events. + * + * Do not instantiate this plugin directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. * @namespace PIXI.interaction */ export { default as InteractionData } from './InteractionData'; diff --git a/src/interaction/interactiveTarget.js b/src/interaction/interactiveTarget.js index d252fba..cebac40 100644 --- a/src/interaction/interactiveTarget.js +++ b/src/interaction/interactiveTarget.js @@ -2,7 +2,7 @@ * Default property values of interactive objects * Used by {@link PIXI.interaction.InteractionManager} to automatically give all DisplayObjects these properties * - * @mixin + * @private * @name interactiveTarget * @memberof PIXI.interaction * @example @@ -14,18 +14,28 @@ * ); */ export default { + /** - * Determines if the displayObject be clicked/touched + * Enable interaction events for the DisplayObject. Touch, pointer and mouse + * events will not be emitted unless `interactive` is set to `true`. * - * @inner {boolean} + * @example + * const sprite = new PIXI.Sprite(texture); + * sprite.interactive = true; + * sprite.on('tap', (event) => { + * //handle event + * }); + * @member {boolean} + * @memberof PIXI.DisplayObject# */ interactive: false, /** * Determines if the children to the displayObject can be clicked/touched - * Setting this to false allows pixi to bypass a recursive hitTest function + * Setting this to false allows pixi to bypass a recursive `hitTest` function * - * @inner {boolean} + * @member {boolean} + * @memberof PIXI.Container# */ interactiveChildren: true, @@ -33,7 +43,12 @@ * Interaction shape. Children will be hit first, then this shape will be checked. * Setting this will cause this shape to be checked in hit tests rather than the displayObject's bounds. * - * @inner {PIXI.Rectangle|PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.RoundedRectangle} + * @example + * const sprite = new PIXI.Sprite(texture); + * sprite.interactive = true; + * sprite.hitArea = new PIXI.Rectangle(0, 0, 100, 100); + * @member {PIXI.Rectangle|PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.RoundedRectangle} + * @memberof PIXI.DisplayObject# */ hitArea: null, @@ -41,8 +56,12 @@ * If enabled, the mouse cursor use the pointer behavior when hovered over the displayObject if it is interactive * Setting this changes the 'cursor' property to `'pointer'`. * + * @example + * const sprite = new PIXI.Sprite(texture); + * sprite.interactive = true; + * sprite.buttonMode = true; * @member {boolean} - * @memberof PIXI.interaction.interactiveTarget# + * @memberof PIXI.DisplayObject# */ get buttonMode() { @@ -64,9 +83,14 @@ * This defines what cursor mode is used when the mouse cursor * is hovered over the displayObject. * + * @example + * const sprite = new PIXI.Sprite(texture); + * sprite.interactive = true; + * sprite.cursor = 'wait'; * @see https://developer.mozilla.org/en/docs/Web/CSS/cursor * - * @inner {string} + * @member {string} + * @memberof PIXI.DisplayObject# */ cursor: null, @@ -74,7 +98,7 @@ * Internal set of all active pointers, by identifier * * @member {Map} - * @memberof PIXI.interaction.interactiveTarget# + * @memberof PIXI.DisplayObject# * @private */ get trackedPointers() @@ -87,7 +111,8 @@ /** * Map of all tracked pointers, by identifier. Use trackedPointers to access. * - * @private {Map} + * @private + * @type {Map} */ _trackedPointers: undefined, }; diff --git a/src/loaders/index.js b/src/loaders/index.js index 5189793..daa1fc4 100644 --- a/src/loaders/index.js +++ b/src/loaders/index.js @@ -1,9 +1,22 @@ +import Application from '../core/Application'; +import Loader from './loader'; + /** + * This namespace contains APIs which extends the {@link https://github.com/englercj/resource-loader resource-loader} module + * for loading assets, data, and other resources dynamically. + * @example + * const loader = new PIXI.loaders.Loader(); + * loader.add('bunny', 'data/bunny.png') + * .add('spaceship', 'assets/spritesheet.json'); + * loader.load((loader, resources) => { + * // resources.bunny + * // resources.spaceship + * }); * @namespace PIXI.loaders */ -export { default as Loader } from './loader'; +export { Loader }; export { default as bitmapFontParser, parse as parseBitmapFontData } from './bitmapFontParser'; -export { default as spritesheetParser } from './spritesheetParser'; +export { default as spritesheetParser, getResourcePath } from './spritesheetParser'; export { default as textureParser } from './textureParser'; /** @@ -13,3 +26,55 @@ * @memberof PIXI.loaders */ export { Resource } from 'resource-loader'; + +/** + * A premade instance of the loader that can be used to load resources. + * @name shared + * @memberof PIXI.loaders + * @type {PIXI.loaders.Loader} + */ +const shared = new Loader(); + +shared.destroy = () => +{ + // protect destroying shared loader +}; + +export { shared }; + +// Mixin the loader construction +const AppPrototype = Application.prototype; + +AppPrototype._loader = null; + +/** + * Loader instance to help with asset loading. + * @name PIXI.Application#loader + * @type {PIXI.loaders.Loader} + */ +Object.defineProperty(AppPrototype, 'loader', { + get() + { + if (!this._loader) + { + const sharedLoader = this._options.sharedLoader; + + this._loader = sharedLoader ? shared : new Loader(); + } + + return this._loader; + }, +}); + +// Override the destroy function +// making sure to destroy the current Loader +AppPrototype._parentDestroy = AppPrototype.destroy; +AppPrototype.destroy = function destroy() +{ + if (this._loader) + { + this._loader.destroy(); + this._loader = null; + } + this._parentDestroy(); +}; diff --git a/src/loaders/loader.js b/src/loaders/loader.js index 767a411..59b8000 100644 --- a/src/loaders/loader.js +++ b/src/loaders/loader.js @@ -90,6 +90,15 @@ { Loader._pixiMiddleware.push(fn); } + + /** + * Destroy the loader, removes references. + */ + destroy() + { + this.removeAllListeners(); + this.reset(); + } } // Copy EE3 prototype (mixin) diff --git a/src/loaders/spritesheetParser.js b/src/loaders/spritesheetParser.js index e57dfb2..eda9584 100644 --- a/src/loaders/spritesheetParser.js +++ b/src/loaders/spritesheetParser.js @@ -1,12 +1,11 @@ import { Resource } from 'resource-loader'; -import path from 'path'; +import url from 'url'; import { Spritesheet } from '../core'; export default function () { return function spritesheetParser(resource, next) { - let resourcePath; const imageResourceName = `${resource.name}_image`; // skip if no data, its not json, it isn't spritesheet data, or the image resource already exists @@ -28,15 +27,7 @@ parentResource: resource, }; - // Prepend url path unless the resource image is a data url - if (resource.isDataUrl) - { - resourcePath = resource.data.meta.image; - } - else - { - resourcePath = `${path.dirname(resource.url.replace(this.baseUrl, ''))}/${resource.data.meta.image}`; - } + const resourcePath = getResourcePath(resource, this.baseUrl); // load the image for this sheet this.add(imageResourceName, resourcePath, loadOptions, function onImageLoad(res) @@ -56,3 +47,14 @@ }); }; } + +export function getResourcePath(resource, baseUrl) +{ + // Prepend url path unless the resource image is a data url + if (resource.isDataUrl) + { + return resource.data.meta.image; + } + + return url.resolve(resource.url.replace(baseUrl, ''), resource.data.meta.image); +} diff --git a/src/mesh/canvas/CanvasMeshRenderer.js b/src/mesh/canvas/CanvasMeshRenderer.js index 297205f..f7899e1 100644 --- a/src/mesh/canvas/CanvasMeshRenderer.js +++ b/src/mesh/canvas/CanvasMeshRenderer.js @@ -134,12 +134,33 @@ const textureWidth = base.width; const textureHeight = base.height; - const u0 = uvs[index0] * base.width; - const u1 = uvs[index1] * base.width; - const u2 = uvs[index2] * base.width; - const v0 = uvs[index0 + 1] * base.height; - const v1 = uvs[index1 + 1] * base.height; - const v2 = uvs[index2 + 1] * base.height; + let u0; + let u1; + let u2; + let v0; + let v1; + let v2; + + if (mesh.uploadUvTransform) + { + const ut = mesh._uvTransform.mapCoord; + + u0 = ((uvs[index0] * ut.a) + (uvs[index0 + 1] * ut.c) + ut.tx) * base.width; + u1 = ((uvs[index1] * ut.a) + (uvs[index1 + 1] * ut.c) + ut.tx) * base.width; + u2 = ((uvs[index2] * ut.a) + (uvs[index2 + 1] * ut.c) + ut.tx) * base.width; + v0 = ((uvs[index0] * ut.b) + (uvs[index0 + 1] * ut.d) + ut.ty) * base.height; + v1 = ((uvs[index1] * ut.b) + (uvs[index1 + 1] * ut.d) + ut.ty) * base.height; + v2 = ((uvs[index2] * ut.b) + (uvs[index2 + 1] * ut.d) + ut.ty) * base.height; + } + else + { + u0 = uvs[index0] * base.width; + u1 = uvs[index1] * base.width; + u2 = uvs[index2] * base.width; + v0 = uvs[index0 + 1] * base.height; + v1 = uvs[index1 + 1] * base.height; + v2 = uvs[index2 + 1] * base.height; + } let x0 = vertices[index0]; let x1 = vertices[index1]; diff --git a/src/mesh/webgl/MeshRenderer.js b/src/mesh/webgl/MeshRenderer.js index 78d24c3..ab99fcd 100644 --- a/src/mesh/webgl/MeshRenderer.js +++ b/src/mesh/webgl/MeshRenderer.js @@ -2,6 +2,8 @@ const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; +const matrixIdentity = core.Matrix.IDENTITY; + /** * WebGL renderer plugin for tiling sprites * @@ -64,6 +66,7 @@ mesh.geometry.glVertexArrayObjects[this.CONTEXT_UID].draw(mesh.drawMode, mesh.size, mesh.start, mesh.geometry.instanceCount); } + /** * draws mesh * @param {PIXI.mesh.RawMesh} mesh mesh instance diff --git a/src/mesh/webgl/mesh.vert b/src/mesh/webgl/mesh.vert index a337aef..acc096c 100644 --- a/src/mesh/webgl/mesh.vert +++ b/src/mesh/webgl/mesh.vert @@ -1,8 +1,9 @@ attribute vec2 aVertexPosition; attribute vec2 aTextureCoord; -uniform mat3 translationMatrix; uniform mat3 projectionMatrix; +uniform mat3 translationMatrix; +uniform mat3 uTransform; varying vec2 vTextureCoord; @@ -10,5 +11,5 @@ { gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - vTextureCoord = aTextureCoord; + vTextureCoord = (uTransform * vec3(aTextureCoord, 1.0)).xy; } diff --git a/src/particles/ParticleContainer.js b/src/particles/ParticleContainer.js index 67796cf..63f7791 100644 --- a/src/particles/ParticleContainer.js +++ b/src/particles/ParticleContainer.js @@ -313,10 +313,10 @@ frame.y * resolution, frame.width * resolution, frame.height * resolution, - positionX * resolution, - positionY * resolution, - finalWidth * resolution, - finalHeight * resolution + positionX * renderer.resolution, + positionY * renderer.resolution, + finalWidth * renderer.resolution, + finalHeight * renderer.resolution ); } } diff --git a/src/prepare/BasePrepare.js b/src/prepare/BasePrepare.js index 09eb5e5..e45c4dc 100644 --- a/src/prepare/BasePrepare.js +++ b/src/prepare/BasePrepare.js @@ -18,6 +18,18 @@ * basic queuing functionality and is extended by {@link PIXI.prepare.WebGLPrepare} and {@link PIXI.prepare.CanvasPrepare} * to provide preparation capabilities specific to their respective renderers. * + * @example + * // Create a sprite + * const sprite = new PIXI.Sprite.fromImage('something.png'); + * + * // Load object into GPU + * app.renderer.plugins.prepare.upload(sprite, () => { + * + * //Texture(s) has been uploaded to GPU + * app.stage.addChild(sprite); + * + * }) + * * @abstract * @class * @memberof PIXI.prepare @@ -100,8 +112,16 @@ this.prepareItems(); }; - this.register(findText, drawText); - this.register(findTextStyle, calculateTextStyle); + // hooks to find the correct texture + this.registerFindHook(findText); + this.registerFindHook(findTextStyle); + this.registerFindHook(findMultipleBaseTextures); + this.registerFindHook(findBaseTexture); + this.registerFindHook(findTexture); + + // upload hooks + this.registerUploadHook(drawText); + this.registerUploadHook(calculateTextStyle); } /** @@ -138,7 +158,7 @@ if (!this.ticking) { this.ticking = true; - SharedTicker.addOnce(this.tick, this); + SharedTicker.addOnce(this.tick, this, core.UPDATE_PRIORITY.UTILITY); } } else if (done) @@ -172,13 +192,16 @@ const item = this.queue[0]; let uploaded = false; - for (let i = 0, len = this.uploadHooks.length; i < len; i++) + if (item && !item._destroyed) { - if (this.uploadHooks[i](this.uploadHookHelper, item)) + for (let i = 0, len = this.uploadHooks.length; i < len; i++) { - this.queue.shift(); - uploaded = true; - break; + if (this.uploadHooks[i](this.uploadHookHelper, item)) + { + this.queue.shift(); + uploaded = true; + break; + } } } @@ -205,26 +228,36 @@ else { // if we are not finished, on the next rAF do this again - SharedTicker.addOnce(this.tick, this); + SharedTicker.addOnce(this.tick, this, core.UPDATE_PRIORITY.UTILITY); } } /** - * Adds hooks for finding and uploading items. + * Adds hooks for finding items. * - * @param {Function} [addHook] - Function call that takes two parameters: `item:*, queue:Array` - function must return `true` if it was able to add item to the queue. - * @param {Function} [uploadHook] - Function call that takes two parameters: `prepare:CanvasPrepare, item:*` and - * function must return `true` if it was able to handle upload of item. - * @return {PIXI.CanvasPrepare} Instance of plugin for chaining. + * @param {Function} addHook - Function call that takes two parameters: `item:*, queue:Array` + * function must return `true` if it was able to add item to the queue. + * @return {PIXI.BasePrepare} Instance of plugin for chaining. */ - register(addHook, uploadHook) + registerFindHook(addHook) { if (addHook) { this.addHooks.push(addHook); } + return this; + } + + /** + * Adds hooks for uploading items. + * + * @param {Function} uploadHook - Function call that takes two parameters: `prepare:CanvasPrepare, item:*` and + * function must return `true` if it was able to handle upload of item. + * @return {PIXI.BasePrepare} Instance of plugin for chaining. + */ + registerUploadHook(uploadHook) + { if (uploadHook) { this.uploadHooks.push(uploadHook); @@ -287,6 +320,88 @@ } /** + * Built-in hook to find multiple textures from objects like AnimatedSprites. + * + * @private + * @param {PIXI.DisplayObject} item - Display object to check + * @param {Array<*>} queue - Collection of items to upload + * @return {boolean} if a PIXI.Texture object was found. + */ +function findMultipleBaseTextures(item, queue) +{ + let result = false; + + // Objects with mutliple textures + if (item && item._textures && item._textures.length) + { + for (let i = 0; i < item._textures.length; i++) + { + if (item._textures[i] instanceof core.Texture) + { + const baseTexture = item._textures[i].baseTexture; + + if (queue.indexOf(baseTexture) === -1) + { + queue.push(baseTexture); + result = true; + } + } + } + } + + return result; +} + +/** + * Built-in hook to find BaseTextures from Sprites. + * + * @private + * @param {PIXI.DisplayObject} item - Display object to check + * @param {Array<*>} queue - Collection of items to upload + * @return {boolean} if a PIXI.Texture object was found. + */ +function findBaseTexture(item, queue) +{ + // Objects with textures, like Sprites/Text + if (item instanceof core.BaseTexture) + { + if (queue.indexOf(item) === -1) + { + queue.push(item); + } + + return true; + } + + return false; +} + +/** + * Built-in hook to find textures from objects. + * + * @private + * @param {PIXI.DisplayObject} item - Display object to check + * @param {Array<*>} queue - Collection of items to upload + * @return {boolean} if a PIXI.Texture object was found. + */ +function findTexture(item, queue) +{ + if (item._texture && item._texture instanceof core.Texture) + { + const texture = item._texture.baseTexture; + + if (queue.indexOf(texture) === -1) + { + queue.push(texture); + } + + return true; + } + + return false; +} + +/** * Built-in hook to draw PIXI.Text to its texture. * * @private diff --git a/src/prepare/canvas/CanvasPrepare.js b/src/prepare/canvas/CanvasPrepare.js index 0224bb5..09c207a 100644 --- a/src/prepare/canvas/CanvasPrepare.js +++ b/src/prepare/canvas/CanvasPrepare.js @@ -43,7 +43,7 @@ this.ctx = this.canvas.getContext('2d'); // Add textures to upload - this.register(findBaseTextures, uploadBaseTextures); + this.registerUploadHook(uploadBaseTextures); } /** @@ -89,39 +89,4 @@ return false; } -/** - * Built-in hook to find textures from Sprites. - * - * @private - * @param {PIXI.DisplayObject} item -Display object to check - * @param {Array<*>} queue - Collection of items to upload - * @return {boolean} if a PIXI.Texture object was found. - */ -function findBaseTextures(item, queue) -{ - // Objects with textures, like Sprites/Text - if (item instanceof core.BaseTexture) - { - if (queue.indexOf(item) === -1) - { - queue.push(item); - } - - return true; - } - else if (item._texture && item._texture instanceof core.Texture) - { - const texture = item._texture.baseTexture; - - if (queue.indexOf(texture) === -1) - { - queue.push(texture); - } - - return true; - } - - return false; -} - core.CanvasRenderer.registerPlugin('prepare', CanvasPrepare); diff --git a/src/prepare/index.js b/src/prepare/index.js index f559c45..e7491ec 100644 --- a/src/prepare/index.js +++ b/src/prepare/index.js @@ -1,4 +1,29 @@ /** + * The prepare namespace provides renderer-specific plugins for pre-rendering DisplayObjects. These plugins are useful for + * asynchronously preparing assets, textures, graphics waiting to be displayed. + * + * Do not instantiate these plugins directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. + * @example + * // Create a new application + * const app = new PIXI.Application(); + * document.body.appendChild(app.view); + * + * // Don't start rendering right away + * app.stop(); + * + * // create a display object + * const rect = new PIXI.Graphics() + * .beginFill(0x00ff00) + * .drawRect(40, 40, 200, 200); + * + * // Add to the stage + * app.stage.addChild(rect); + * + * // Don't start rendering until the graphic is uploaded to the GPU + * app.renderer.plugins.prepare.upload(app.stage, () => { + * app.start(); + * }); * @namespace PIXI.prepare */ export { default as webgl } from './webgl/WebGLPrepare'; diff --git a/src/prepare/webgl/WebGLPrepare.js b/src/prepare/webgl/WebGLPrepare.js index eb7023f..9df3c3f 100644 --- a/src/prepare/webgl/WebGLPrepare.js +++ b/src/prepare/webgl/WebGLPrepare.js @@ -22,12 +22,11 @@ this.uploadHookHelper = this.renderer; // Add textures and graphics to upload - this.register(findBaseTextures, uploadBaseTextures) - .register(findGraphics, uploadGraphics); + this.registerFindHook(findGraphics); + this.registerUploadHook(uploadBaseTextures); + this.registerUploadHook(uploadGraphics); } - } - /** * Built-in hook to upload PIXI.Texture objects to the GPU. * @@ -80,41 +79,6 @@ } /** - * Built-in hook to find textures from Sprites. - * - * @private - * @param {PIXI.DisplayObject} item - Display object to check - * @param {Array<*>} queue - Collection of items to upload - * @return {boolean} if a PIXI.Texture object was found. - */ -function findBaseTextures(item, queue) -{ - // Objects with textures, like Sprites/Text - if (item instanceof core.BaseTexture) - { - if (queue.indexOf(item) === -1) - { - queue.push(item); - } - - return true; - } - else if (item._texture && item._texture instanceof core.Texture) - { - const texture = item._texture.baseTexture; - - if (queue.indexOf(texture) === -1) - { - queue.push(texture); - } - - return true; - } - - return false; -} - -/** * Built-in hook to find graphics. * * @private diff --git a/test/.eslintrc.json b/test/.eslintrc.json index e03220d..ac12110 100644 --- a/test/.eslintrc.json +++ b/test/.eslintrc.json @@ -12,7 +12,8 @@ "sinon": false, "expect": false, "assert": false, - "PIXI": false + "PIXI": false, + "PointerEvent": true }, "rules": { "func-names": 0, diff --git a/test/core/BaseTexture.js b/test/core/BaseTexture.js index 8b93478..bb65068 100644 --- a/test/core/BaseTexture.js +++ b/test/core/BaseTexture.js @@ -14,4 +14,28 @@ expect(baseTexture.imageType).to.be.equals('png'); }); }); + + it('should remove Canvas BaseTexture from cache on destroy', function () + { + const canvas = document.createElement('canvas'); + const texture = PIXI.BaseTexture.fromCanvas(canvas); + const _pixiId = canvas._pixiId; + + expect(PIXI.utils.BaseTextureCache[_pixiId]).to.equal(texture); + texture.destroy(); + expect(PIXI.utils.BaseTextureCache[_pixiId]).to.equal(undefined); + }); + + it('should remove Image BaseTexture from cache on destroy', function () + { + const URL = 'foo.png'; + const NAME = 'bar'; + const image = new Image(); + + const texture = PIXI.Texture.fromLoader(image, URL, NAME); + + expect(PIXI.utils.BaseTextureCache[NAME]).to.equal(texture.baseTexture); + texture.destroy(true); + expect(PIXI.utils.BaseTextureCache[NAME]).to.equal(undefined); + }); }); diff --git a/test/core/Circle.js b/test/core/Circle.js index 183f98f..f621992 100644 --- a/test/core/Circle.js +++ b/test/core/Circle.js @@ -53,6 +53,10 @@ expect(circ1.contains(10, 16)).to.be.false; expect(circ1.contains(11, 15)).to.be.false; expect(circ1.contains(0, 0)).to.be.false; + + const circ2 = new PIXI.Circle(10, 10, 0); + + expect(circ2.contains(10, 10)).to.be.false; }); it('should return framing rectangle', function () diff --git a/test/core/Container.js b/test/core/Container.js index d11be33..cefde54 100644 --- a/test/core/Container.js +++ b/test/core/Container.js @@ -2,30 +2,36 @@ function testAddChild(fn) { - fn(function (container, obj) + return function () { - container.addChild(obj); - }); - fn(function (container, obj) - { - container.addChildAt(obj); - }); + fn(function (container, obj) + { + container.addChild(obj); + }); + fn(function (container, obj) + { + container.addChildAt(obj); + }); + }; } function testRemoveChild(fn) { - fn(function (container, obj) + return function () { - container.removeChild(obj); - }); - fn(function (container, obj) - { - container.removeChildAt(container.children.indexOf(obj)); - }); - fn(function (container, obj) - { - container.removeChildren(container.children.indexOf(obj), container.children.indexOf(obj) + 1); - }); + fn(function (container, obj) + { + container.removeChild(obj); + }); + fn(function (container, obj) + { + container.removeChildAt(container.children.indexOf(obj)); + }); + fn(function (container, obj) + { + container.removeChildren(container.children.indexOf(obj), container.children.indexOf(obj) + 1); + }); + }; } describe('PIXI.Container', function () diff --git a/test/core/Ellipse.js b/test/core/Ellipse.js index f2e8234..3f47442 100644 --- a/test/core/Ellipse.js +++ b/test/core/Ellipse.js @@ -56,6 +56,10 @@ expect(ellipse1.contains(10, 16)).to.be.false; expect(ellipse1.contains(11, 15)).to.be.false; expect(ellipse1.contains(0, 0)).to.be.false; + + const ellipse2 = new PIXI.Ellipse(10, 10, 0, 0); + + expect(ellipse2.contains(10, 10)).to.be.false; }); it('should return framing rectangle', function () diff --git a/test/core/Matrix.js b/test/core/Matrix.js index 9d2694b..76bd57c 100644 --- a/test/core/Matrix.js +++ b/test/core/Matrix.js @@ -107,10 +107,60 @@ expect(m1.ty).to.equal(m2.ty); }); + it('should prepend matrix', function () + { + const m1 = new PIXI.Matrix(); + const m2 = new PIXI.Matrix(); + + m2.set(2, 3, 4, 5, 100, 200); + m1.prepend(m2); + + expect(m1.a).to.equal(m2.a); + expect(m1.b).to.equal(m2.b); + expect(m1.c).to.equal(m2.c); + expect(m1.d).to.equal(m2.d); + expect(m1.tx).to.equal(m2.tx); + expect(m1.ty).to.equal(m2.ty); + + const m3 = new PIXI.Matrix(); + const m4 = new PIXI.Matrix(); + + m3.prepend(m4); + + expect(m3.a).to.equal(m4.a); + expect(m3.b).to.equal(m4.b); + expect(m3.c).to.equal(m4.c); + expect(m3.d).to.equal(m4.d); + expect(m3.tx).to.equal(m4.tx); + expect(m3.ty).to.equal(m4.ty); + }); + it('should get IDENTITY and TEMP_MATRIX', function () { expect(PIXI.Matrix.IDENTITY instanceof PIXI.Matrix).to.be.true; expect(PIXI.Matrix.TEMP_MATRIX instanceof PIXI.Matrix).to.be.true; }); -}); + it('should reset matrix to default when identity() is called', function () + { + const matrix = new PIXI.Matrix(); + + matrix.set(2, 3, 4, 5, 100, 200); + + expect(matrix.a).to.equal(2); + expect(matrix.b).to.equal(3); + expect(matrix.c).to.equal(4); + expect(matrix.d).to.equal(5); + expect(matrix.tx).to.equal(100); + expect(matrix.ty).to.equal(200); + + matrix.identity(); + + expect(matrix.a).to.equal(1); + expect(matrix.b).to.equal(0); + expect(matrix.c).to.equal(0); + expect(matrix.d).to.equal(1); + expect(matrix.tx).to.equal(0); + expect(matrix.ty).to.equal(0); + }); +}); diff --git a/test/core/Spritesheet.js b/test/core/Spritesheet.js index 283adf5..7601a0c 100644 --- a/test/core/Spritesheet.js +++ b/test/core/Spritesheet.js @@ -18,6 +18,7 @@ expect(textures[id]).to.be.an.instanceof(PIXI.Texture); expect(textures[id].width).to.equal(spritesheet.data.frames[id].frame.w / spritesheet.resolution); expect(textures[id].height).to.equal(spritesheet.data.frames[id].frame.h / spritesheet.resolution); + expect(textures[id].textureCacheId).to.equal(id); spritesheet.destroy(true); expect(spritesheet.textures).to.be.null; expect(spritesheet.baseTexture).to.be.null; diff --git a/test/core/Texture.js b/test/core/Texture.js index 8c4ef98..241ec3e 100644 --- a/test/core/Texture.js +++ b/test/core/Texture.js @@ -16,4 +16,20 @@ expect(PIXI.utils.TextureCache[URL]).to.equal(texture); expect(PIXI.utils.BaseTextureCache[URL]).to.equal(texture.baseTexture); }); + + it('should remove Texture from cache on destroy', function () + { + const NAME = 'foo'; + const NAME2 = 'bar'; + const texture = new PIXI.Texture(new PIXI.BaseTexture()); + + PIXI.Texture.addTextureToCache(texture, NAME); + PIXI.Texture.addTextureToCache(texture, NAME2); + expect(texture.textureCacheId).to.equal(NAME); + expect(PIXI.utils.TextureCache[NAME]).to.equal(texture); + expect(PIXI.utils.TextureCache[NAME2]).to.equal(texture); + texture.destroy(); + expect(PIXI.utils.TextureCache[NAME]).to.equal(undefined); + expect(PIXI.utils.TextureCache[NAME2]).to.equal(undefined); + }); }); diff --git a/test/core/Ticker.js b/test/core/Ticker.js new file mode 100644 index 0000000..188770f --- /dev/null +++ b/test/core/Ticker.js @@ -0,0 +1,322 @@ +'use strict'; + +const Ticker = PIXI.ticker.Ticker; +const shared = PIXI.ticker.shared; + +describe('PIXI.ticker.Ticker', function () +{ + before(function () + { + this.length = (ticker) => + { + ticker = ticker || shared; + + if (!ticker._head || !ticker._head.next) + { + return 0; + } + + let listener = ticker._head.next; + let i = 0; + + while (listener) + { + listener = listener.next; + i++; + } + + return i; + }; + }); + + it('should be available', function () + { + expect(Ticker).to.be.a.function; + expect(shared).to.be.an.instanceof(Ticker); + }); + + it('should create a new ticker and destroy it', function () + { + const ticker = new Ticker(); + + ticker.start(); + + const listener = sinon.spy(); + + expect(this.length(ticker)).to.equal(0); + + ticker.add(listener); + + expect(this.length(ticker)).to.equal(1); + + ticker.destroy(); + + expect(ticker._head).to.be.null; + expect(ticker.started).to.be.false; + expect(this.length(ticker)).to.equal(0); + }); + + it('should protect destroying shared ticker', function () + { + shared.destroy(); + expect(shared._head).to.not.be.null; + expect(shared.started).to.be.true; + }); + + it('should add and remove listener', function () + { + const listener = sinon.spy(); + const length = this.length(); + + shared.add(listener); + + expect(this.length()).to.equal(length + 1); + + shared.remove(listener); + + expect(this.length()).to.equal(length); + }); + + it('should update a listener', function () + { + const listener = sinon.spy(); + + shared.add(listener); + shared.update(); + + expect(listener.calledOnce).to.be.true; + + shared.remove(listener); + shared.update(); + + expect(listener.calledOnce).to.be.true; + }); + + it('should update a listener twice and remove once', function () + { + const listener = sinon.spy(); + const length = this.length(); + + shared.add(listener).add(listener); + shared.update(); + + expect(listener.calledTwice).to.be.true; + expect(this.length()).to.equal(length + 2); + + shared.remove(listener); + shared.update(); + + expect(listener.calledTwice).to.be.true; + expect(this.length()).to.equal(length); + }); + + it('should respect priority order', function () + { + const length = this.length(); + const listener1 = sinon.spy(); + const listener2 = sinon.spy(); + const listener3 = sinon.spy(); + const listener4 = sinon.spy(); + + shared.add(listener1, null, PIXI.UPDATE_PRIORITY.LOW) + .add(listener4, null, PIXI.UPDATE_PRIORITY.INTERACTION) + .add(listener3, null, PIXI.UPDATE_PRIORITY.HIGH) + .add(listener2, null, PIXI.UPDATE_PRIORITY.NORMAL); + + shared.update(); + + expect(this.length()).to.equal(length + 4); + + sinon.assert.callOrder(listener4, listener3, listener2, listener1); + + shared.remove(listener1) + .remove(listener2) + .remove(listener3) + .remove(listener4); + + expect(this.length()).to.equal(length); + }); + + it('should auto-remove once listeners', function () + { + const length = this.length(); + const listener = sinon.spy(); + + shared.addOnce(listener); + + shared.update(); + + expect(listener.calledOnce).to.be.true; + expect(this.length()).to.equal(length); + }); + + it('should call inserted item with a lower priority', function () + { + const length = this.length(); + const lowListener = sinon.spy(); + const highListener = sinon.spy(); + const mainListener = sinon.spy(() => + { + shared.add(highListener, null, PIXI.UPDATE_PRIORITY.HIGH); + shared.add(lowListener, null, PIXI.UPDATE_PRIORITY.LOW); + }); + + shared.add(mainListener, null, PIXI.UPDATE_PRIORITY.NORMAL); + + shared.update(); + + expect(this.length()).to.equal(length + 3); + + expect(mainListener.calledOnce).to.be.true; + expect(lowListener.calledOnce).to.be.true; + expect(highListener.calledOnce).to.be.false; + + shared.remove(mainListener) + .remove(highListener) + .remove(lowListener); + + expect(this.length()).to.equal(length); + }); + + it('should remove emit low-priority item during emit', function () + { + const length = this.length(); + const listener2 = sinon.spy(); + const listener1 = sinon.spy(() => + { + shared.add(listener2, null, PIXI.UPDATE_PRIORITY.LOW); + }); + + shared.add(listener1, null, PIXI.UPDATE_PRIORITY.NORMAL); + + shared.update(); + + expect(this.length()).to.equal(length + 2); + + expect(listener2.calledOnce).to.be.true; + expect(listener1.calledOnce).to.be.true; + + shared.remove(listener1) + .remove(listener2); + + expect(this.length()).to.equal(length); + }); + + it('should remove itself on emit after adding new item', function () + { + const length = this.length(); + const listener2 = sinon.spy(); + const listener1 = sinon.spy(() => + { + shared.add(listener2, null, PIXI.UPDATE_PRIORITY.LOW); + shared.remove(listener1); + + // listener is removed right away + expect(this.length()).to.equal(length + 1); + }); + + shared.add(listener1, null, PIXI.UPDATE_PRIORITY.NORMAL); + + shared.update(); + + expect(this.length()).to.equal(length + 1); + + expect(listener2.calledOnce).to.be.true; + expect(listener1.calledOnce).to.be.true; + + shared.remove(listener2); + + expect(this.length()).to.equal(length); + }); + + it('should remove itself before, still calling new item', function () + { + const length = this.length(); + const listener2 = sinon.spy(); + const listener1 = sinon.spy(() => + { + shared.remove(listener1); + shared.add(listener2, null, PIXI.UPDATE_PRIORITY.LOW); + + // listener is removed right away + expect(this.length()).to.equal(length + 1); + }); + + shared.add(listener1, null, PIXI.UPDATE_PRIORITY.NORMAL); + + shared.update(); + + expect(this.length()).to.equal(length + 1); + + expect(listener2.calledOnce).to.be.true; + expect(listener1.calledOnce).to.be.true; + + shared.remove(listener2); + + expect(this.length()).to.equal(length); + }); + + it('should remove items before and after current priority', function () + { + const length = this.length(); + const listener2 = sinon.spy(); + const listener3 = sinon.spy(); + const listener4 = sinon.spy(); + + shared.add(listener2, null, PIXI.UPDATE_PRIORITY.HIGH); + shared.add(listener3, null, PIXI.UPDATE_PRIORITY.LOW); + shared.add(listener4, null, PIXI.UPDATE_PRIORITY.LOW); + + const listener1 = sinon.spy(() => + { + shared.remove(listener2) + .remove(listener3); + + // listener is removed right away + expect(this.length()).to.equal(length + 2); + }); + + shared.add(listener1, null, PIXI.UPDATE_PRIORITY.NORMAL); + + shared.update(); + + expect(this.length()).to.equal(length + 2); + + expect(listener2.calledOnce).to.be.true; + expect(listener3.calledOnce).to.be.false; + expect(listener4.calledOnce).to.be.true; + expect(listener1.calledOnce).to.be.true; + + shared.update(); + + expect(listener2.calledOnce).to.be.true; + expect(listener3.calledOnce).to.be.false; + expect(listener4.calledTwice).to.be.true; + expect(listener1.calledTwice).to.be.true; + + shared.remove(listener1) + .remove(listener4); + + expect(this.length()).to.equal(length); + }); + + it('should destroy on listener', function (done) + { + const ticker = new Ticker(); + const listener2 = sinon.spy(); + const listener = sinon.spy(() => + { + ticker.destroy(); + setTimeout(() => + { + expect(listener2.called).to.be.false; + expect(listener.calledOnce).to.be.true; + done(); + }, 0); + }); + + ticker.add(listener); + ticker.add(listener2, null, PIXI.UPDATE_PRIORITY.LOW); + ticker.start(); + }); +}); diff --git a/test/core/TilingSprite.js b/test/core/TilingSprite.js index 2e449a7..7694792 100644 --- a/test/core/TilingSprite.js +++ b/test/core/TilingSprite.js @@ -24,4 +24,25 @@ expect(bounds.height).to.equal(600); }); }); + + it('checks if tilingSprite contains a point', function () + { + const texture = new PIXI.Texture(new PIXI.BaseTexture()); + const tilingSprite = new PIXI.extras.TilingSprite(texture, 200, 300); + + expect(tilingSprite.containsPoint(new PIXI.Point(1, 1))).to.equal(true); + expect(tilingSprite.containsPoint(new PIXI.Point(300, 400))).to.equal(false); + }); + + it('gets and sets height and width correctly', function () + { + const texture = new PIXI.Texture(new PIXI.BaseTexture()); + const tilingSprite = new PIXI.extras.TilingSprite(texture, 200, 300); + + tilingSprite.width = 400; + tilingSprite.height = 600; + + expect(tilingSprite.width).to.equal(400); + expect(tilingSprite.height).to.equal(600); + }); }); diff --git a/test/core/index.js b/test/core/index.js index 8530131..58dc30e 100755 --- a/test/core/index.js +++ b/test/core/index.js @@ -17,6 +17,7 @@ require('./util'); require('./Plane'); require('./Point'); +require('./Polygon'); require('./ObservablePoint'); require('./Matrix'); require('./Rectangle'); @@ -27,4 +28,5 @@ require('./WebGLRenderer'); require('./Ellipse'); require('./Texture'); +require('./Ticker'); require('./filters'); diff --git a/test/interaction/InteractionManager.js b/test/interaction/InteractionManager.js index 0e7e707..31385b9 100644 --- a/test/interaction/InteractionManager.js +++ b/test/interaction/InteractionManager.js @@ -4,6 +4,16 @@ describe('PIXI.interaction.InteractionManager', function () { + afterEach(function () + { + // if we made a MockPointer for the test, clean it up + if (this.pointer) + { + this.pointer.cleanUp(); + this.pointer = null; + } + }); + describe('event basics', function () { it('should call mousedown handler', function () @@ -11,7 +21,7 @@ const stage = new PIXI.Container(); const graphics = new PIXI.Graphics(); const eventSpy = sinon.spy(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); stage.addChild(graphics); graphics.beginFill(0xFFFFFF); @@ -29,7 +39,7 @@ const stage = new PIXI.Container(); const graphics = new PIXI.Graphics(); const eventSpy = sinon.spy(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); stage.addChild(graphics); graphics.beginFill(0xFFFFFF); @@ -47,7 +57,7 @@ const stage = new PIXI.Container(); const graphics = new PIXI.Graphics(); const eventSpy = sinon.spy(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); stage.addChild(graphics); graphics.beginFill(0xFFFFFF); @@ -66,7 +76,7 @@ const stage = new PIXI.Container(); const graphics = new PIXI.Graphics(); const eventSpy = sinon.spy(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); stage.addChild(graphics); graphics.beginFill(0xFFFFFF); @@ -84,7 +94,7 @@ const stage = new PIXI.Container(); const graphics = new PIXI.Graphics(); const eventSpy = sinon.spy(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); stage.addChild(graphics); graphics.beginFill(0xFFFFFF); @@ -286,7 +296,7 @@ const stage = new PIXI.Container(); const graphics = new PIXI.Graphics(); const clickSpy = sinon.spy(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); stage.addChild(graphics); graphics.beginFill(0xFFFFFF); @@ -304,7 +314,7 @@ const stage = new PIXI.Container(); const graphics = new PIXI.Graphics(); const clickSpy = sinon.spy(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); stage.addChild(graphics); graphics.beginFill(0xFFFFFF); @@ -316,6 +326,29 @@ expect(clickSpy).to.not.have.been.called; }); + + it('should not call handler when mousedown not received', function () + { + const stage = new PIXI.Container(); + const graphics = new PIXI.Graphics(); + const clickSpy = sinon.spy(); + const pointer = new MockPointer(stage); + + stage.addChild(graphics); + graphics.beginFill(0xFFFFFF); + graphics.drawRect(0, 0, 50, 50); + graphics.interactive = true; + graphics.on('click', clickSpy); + + pointer.mouseup(10, 10); + + expect(clickSpy, 'click should not happen on first mouseup').to.not.have.been.called; + + // test again, just because it was a bug that was reported + pointer.mouseup(20, 20); + + expect(clickSpy, 'click should not happen on second mouseup').to.not.have.been.called; + }); }); describe('onTap', function () @@ -325,7 +358,7 @@ const stage = new PIXI.Container(); const graphics = new PIXI.Graphics(); const clickSpy = sinon.spy(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); stage.addChild(graphics); graphics.beginFill(0xFFFFFF); @@ -343,7 +376,7 @@ const stage = new PIXI.Container(); const graphics = new PIXI.Graphics(); const clickSpy = sinon.spy(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); stage.addChild(graphics); graphics.beginFill(0xFFFFFF); @@ -359,32 +392,62 @@ describe('overlapping children', function () { - function getScene(callbackEventName) + function getScene(callbackEventName, splitParents) { const behindChild = new PIXI.Graphics(); const frontChild = new PIXI.Graphics(); const parent = new PIXI.Container(); - const behindChildCallback = sinon.spy(); - const frontChildCallback = sinon.spy(); - const parentCallback = sinon.spy(); + const behindChildCallback = sinon.spy(function behindSpy() { /* no op*/ }); + const frontChildCallback = sinon.spy(function frontSpy() { /* no op*/ }); + const parentCallback = sinon.spy(function parentSpy() { /* no op*/ }); + let behindParent; + let frontParent; + let behindParentCallback; + let frontParentCallback; behindChild.beginFill(0xFF); behindChild.drawRect(0, 0, 50, 50); behindChild.on(callbackEventName, behindChildCallback); + behindChild.name = 'behind'; frontChild.beginFill(0x00FF); frontChild.drawRect(0, 0, 50, 50); frontChild.on(callbackEventName, frontChildCallback); + frontChild.name = 'front'; - parent.addChild(behindChild, frontChild); + if (splitParents) + { + behindParent = new PIXI.Container(); + behindParent.name = 'behindParent'; + frontParent = new PIXI.Container(); + frontParent.name = 'frontParent'; + behindParentCallback = sinon.spy(function behindParentSpy() { /* no op*/ }); + frontParentCallback = sinon.spy(function frontParentSpy() { /* no op*/ }); + behindParent.on(callbackEventName, behindParentCallback); + frontParent.on(callbackEventName, frontParentCallback); + + parent.addChild(behindParent, frontParent); + behindParent.addChild(behindChild); + frontParent.addChild(frontChild); + + parent.name = 'parent'; + } + else + { + parent.addChild(behindChild, frontChild); + } parent.on(callbackEventName, parentCallback); return { behindChild, frontChild, + behindParent, + frontParent, parent, behindChildCallback, frontChildCallback, + behindParentCallback, + frontParentCallback, parentCallback, }; } @@ -396,7 +459,7 @@ it('should callback front child when clicking front child', function () { const stage = new PIXI.Container(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); const scene = getScene('click'); scene.behindChild.interactive = true; @@ -414,7 +477,7 @@ it('should callback front child when clicking overlap', function () { const stage = new PIXI.Container(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); const scene = getScene('click'); scene.behindChild.interactive = true; @@ -432,7 +495,7 @@ it('should callback behind child when clicking behind child', function () { const stage = new PIXI.Container(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); const scene = getScene('click'); scene.behindChild.interactive = true; @@ -446,6 +509,48 @@ expect(scene.behindChildCallback).to.have.been.calledOnce; expect(scene.parentCallback).to.not.have.been.called; }); + + it('should callback front child of different non-interactive parents when clicking overlap', function () + { + const stage = new PIXI.Container(); + const pointer = this.pointer = new MockPointer(stage); + const scene = getScene('click', true); + + scene.behindChild.interactive = true; + scene.behindChild.x = 25; + scene.frontChild.interactive = true; + + stage.addChild(scene.parent); + pointer.click(40, 10); + + expect(scene.behindChildCallback).to.not.have.been.called; + expect(scene.frontChildCallback).to.have.been.calledOnce; + expect(scene.parentCallback).to.not.have.been.called; + expect(scene.behindParentCallback).to.not.have.been.called; + expect(scene.frontParentCallback).to.not.have.been.called; + }); + + it('should callback front child of different interactive parents when clicking overlap', function () + { + const stage = new PIXI.Container(); + const pointer = this.pointer = new MockPointer(stage); + const scene = getScene('click', true); + + scene.behindChild.interactive = true; + scene.behindChild.x = 25; + scene.frontChild.interactive = true; + scene.behindParent.interactive = true; + scene.frontParent.interactive = true; + + stage.addChild(scene.parent); + pointer.click(40, 10); + + expect(scene.behindChildCallback).to.not.have.been.called; + expect(scene.frontChildCallback).to.have.been.calledOnce; + expect(scene.parentCallback).to.not.have.been.called; + expect(scene.behindParentCallback).to.not.have.been.called; + expect(scene.frontParentCallback).to.have.been.calledOnce; + }); }); describe('when front child is non-interactive', function () @@ -453,7 +558,7 @@ it('should not callback when clicking front child', function () { const stage = new PIXI.Container(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); const scene = getScene('click'); scene.behindChild.interactive = true; @@ -470,7 +575,7 @@ it('should callback behind child when clicking overlap', function () { const stage = new PIXI.Container(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); const scene = getScene('click'); scene.behindChild.interactive = true; @@ -487,7 +592,7 @@ it('should callback behind child when clicking behind child', function () { const stage = new PIXI.Container(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); const scene = getScene('click'); scene.behindChild.interactive = true; @@ -507,7 +612,7 @@ it('should callback front child when clicking front child', function () { const stage = new PIXI.Container(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); const scene = getScene('click'); scene.behindChild.x = 25; @@ -524,7 +629,7 @@ it('should callback front child when clicking overlap', function () { const stage = new PIXI.Container(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); const scene = getScene('click'); scene.behindChild.x = 25; @@ -541,7 +646,7 @@ it('should not callback when clicking behind child', function () { const stage = new PIXI.Container(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); const scene = getScene('click'); scene.behindChild.x = 25; @@ -564,7 +669,7 @@ it('should callback parent and front child when clicking front child', function () { const stage = new PIXI.Container(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); const scene = getScene('click'); scene.behindChild.interactive = true; @@ -583,7 +688,7 @@ it('should callback parent and front child when clicking overlap', function () { const stage = new PIXI.Container(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); const scene = getScene('click'); scene.behindChild.interactive = true; @@ -602,7 +707,7 @@ it('should callback parent and behind child when clicking behind child', function () { const stage = new PIXI.Container(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); const scene = getScene('click'); scene.behindChild.interactive = true; @@ -617,6 +722,50 @@ expect(scene.behindChildCallback).to.have.been.calledOnce; expect(scene.parentCallback).to.have.been.calledOnce; }); + + it('should callback front child of different non-interactive parents when clicking overlap', function () + { + const stage = new PIXI.Container(); + const pointer = this.pointer = new MockPointer(stage); + const scene = getScene('click', true); + + scene.behindChild.interactive = true; + scene.behindChild.x = 25; + scene.frontChild.interactive = true; + scene.parent.interactive = true; + + stage.addChild(scene.parent); + pointer.click(40, 10); + + expect(scene.behindChildCallback).to.not.have.been.called; + expect(scene.frontChildCallback).to.have.been.calledOnce; + expect(scene.parentCallback).to.have.been.calledOnce; + expect(scene.behindParentCallback).to.not.have.been.called; + expect(scene.frontParentCallback).to.not.have.been.called; + }); + + it('should callback front child of different interactive parents when clicking overlap', function () + { + const stage = new PIXI.Container(); + const pointer = this.pointer = new MockPointer(stage); + const scene = getScene('click', true); + + scene.behindChild.interactive = true; + scene.behindChild.x = 25; + scene.frontChild.interactive = true; + scene.parent.interactive = true; + scene.behindParent.interactive = true; + scene.frontParent.interactive = true; + + stage.addChild(scene.parent); + pointer.click(40, 10); + + expect(scene.behindChildCallback).to.not.have.been.called; + expect(scene.frontChildCallback).to.have.been.calledOnce; + expect(scene.parentCallback).to.have.been.calledOnce; + expect(scene.behindParentCallback).to.not.have.been.called; + expect(scene.frontParentCallback).to.have.been.calledOnce; + }); }); describe('when front child is non-interactive', function () @@ -624,7 +773,7 @@ it('should callback parent when clicking front child', function () { const stage = new PIXI.Container(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); const scene = getScene('click'); scene.behindChild.interactive = true; @@ -642,7 +791,7 @@ it('should callback parent and behind child when clicking overlap', function () { const stage = new PIXI.Container(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); const scene = getScene('click'); scene.behindChild.interactive = true; @@ -660,7 +809,7 @@ it('should callback parent and behind child when clicking behind child', function () { const stage = new PIXI.Container(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); const scene = getScene('click'); scene.behindChild.interactive = true; @@ -681,7 +830,7 @@ it('should callback parent and front child when clicking front child', function () { const stage = new PIXI.Container(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); const scene = getScene('click'); scene.behindChild.x = 25; @@ -699,7 +848,7 @@ it('should callback parent and front child when clicking overlap', function () { const stage = new PIXI.Container(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); const scene = getScene('click'); scene.behindChild.x = 25; @@ -717,7 +866,7 @@ it('should callback parent when clicking behind child', function () { const stage = new PIXI.Container(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); const scene = getScene('click'); scene.behindChild.x = 25; @@ -733,6 +882,42 @@ }); }); }); + + it('Semi-complicated nesting with overlap, should not call behind callback', function () + { + const stage = new PIXI.Container(); + const frontParent = new PIXI.Container(); + const frontChild = new PIXI.Graphics(); + const behindParent = new PIXI.Container(); + const subParent = new PIXI.Container(); + const behindChild = new PIXI.Graphics(); + const behindCallback = sinon.spy(function behindSpy() { /* no op*/ }); + const frontCallback = sinon.spy(function frontSpy() { /* no op*/ }); + + behindChild.beginFill(0xFF); + behindChild.drawRect(0, 0, 50, 50); + subParent.on('click', behindCallback); + + frontChild.beginFill(0x00FF); + frontChild.drawRect(0, 0, 50, 50); + frontParent.on('click', frontCallback); + const pointer = this.pointer = new MockPointer(stage); + + behindParent.x = 25; + subParent.interactive = true; + frontParent.interactive = true; + + behindParent.addChild(subParent); + subParent.addChild(behindChild); + stage.addChild(behindParent); + frontParent.addChild(frontChild); + stage.addChild(frontParent); + + pointer.click(40, 10); + + expect(behindCallback).to.not.have.been.called; + expect(frontCallback).to.have.been.calledOnce; + }); }); describe('cursor changes', function () @@ -741,7 +926,7 @@ { const stage = new PIXI.Container(); const graphics = new PIXI.Graphics(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); stage.addChild(graphics); graphics.beginFill(0xFFFFFF); @@ -759,7 +944,7 @@ { const stage = new PIXI.Container(); const graphics = new PIXI.Graphics(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); stage.addChild(graphics); graphics.beginFill(0xFFFFFF); @@ -778,7 +963,7 @@ { const stage = new PIXI.Container(); const graphics = new PIXI.Graphics(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); stage.addChild(graphics); graphics.beginFill(0xFFFFFF); @@ -797,7 +982,7 @@ { const stage = new PIXI.Container(); const graphics = new PIXI.Graphics(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); stage.addChild(graphics); graphics.beginFill(0xFFFFFF); @@ -818,7 +1003,7 @@ const graphics = new PIXI.Graphics(); const overSpy = sinon.spy(); const defaultSpy = sinon.spy(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); stage.addChild(graphics); graphics.beginFill(0xFFFFFF); @@ -840,7 +1025,7 @@ const stage = new PIXI.Container(); const graphics = new PIXI.Graphics(); const defaultSpy = sinon.spy(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); stage.addChild(graphics); graphics.beginFill(0xFFFFFF); @@ -859,7 +1044,7 @@ { const stage = new PIXI.Container(); const graphics = new PIXI.Graphics(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); stage.addChild(graphics); graphics.beginFill(0xFFFFFF); @@ -881,7 +1066,7 @@ { const stage = new PIXI.Container(); const graphics = new PIXI.Graphics(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); stage.addChild(graphics); graphics.beginFill(0xFFFFFF); @@ -932,7 +1117,7 @@ it('should stop hitTesting after first hit', function () { const scene = getScene(); - const pointer = new MockPointer(scene.stage); + const pointer = this.pointer = new MockPointer(scene.stage); const frontHitTest = sinon.spy(scene.frontChild, 'containsPoint'); const middleHitTest = sinon.spy(scene.middleChild, 'containsPoint'); const behindHitTest = sinon.spy(scene.behindChild, 'containsPoint'); @@ -954,7 +1139,7 @@ it('should stop hitTesting after first hit', function () { const scene = getScene(); - const pointer = new MockPointer(scene.stage); + const pointer = this.pointer = new MockPointer(scene.stage); const frontHitTest = sinon.spy(scene.frontChild, 'containsPoint'); const middleHitTest = sinon.spy(scene.middleChild, 'containsPoint'); const behindHitTest = sinon.spy(scene.behindChild, 'containsPoint'); @@ -971,4 +1156,127 @@ }); }); }); + + describe('pointer handling', function () + { + it('pointer event from mouse should use single mouse data', function () + { + const stage = new PIXI.Container(); + const graphics = new PIXI.Graphics(); + const pointer = this.pointer = new MockPointer(stage, 100, 100, true); + + stage.addChild(graphics); + graphics.beginFill(0xFFFFFF); + graphics.drawRect(0, 0, 50, 50); + graphics.interactive = true; + + pointer.mousemove(20, 10, true); + + expect(pointer.interaction.mouse.global.x).to.equal(20); + expect(pointer.interaction.mouse.global.y).to.equal(10); + }); + }); + + describe('data cleanup', function () + { + it('touchleave after touchout should not orphan data', 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, 42); + expect(pointer.interaction.activeInteractionData[42]).to.exist; + pointer.touchend(10, 10, 42); + expect(pointer.interaction.activeInteractionData[42]).to.be.undefined; + pointer.touchleave(10, 10, 42); + expect(pointer.interaction.activeInteractionData[42]).to.be.undefined; + }); + }); + + describe('hitTest()', function () + { + it('should return hit', 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.render(); + const hit = pointer.interaction.hitTest(new PIXI.Point(10, 10)); + + expect(hit).to.equal(graphics); + }); + + it('should return null if not hit', 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.render(); + const hit = pointer.interaction.hitTest(new PIXI.Point(60, 60)); + + expect(hit).to.be.null; + }); + + it('should return top thing that was hit', function () + { + const stage = new PIXI.Container(); + const graphics = new PIXI.Graphics(); + const behind = new PIXI.Graphics(); + const pointer = this.pointer = new MockPointer(stage); + + stage.addChild(behind); + stage.addChild(graphics); + graphics.beginFill(0xFFFFFF); + graphics.drawRect(0, 0, 50, 50); + graphics.interactive = true; + behind.beginFill(0xFFFFFF); + behind.drawRect(0, 0, 50, 50); + behind.interactive = true; + + pointer.render(); + const hit = pointer.interaction.hitTest(new PIXI.Point(10, 10)); + + expect(hit).to.equal(graphics); + }); + + it('should return hit when passing in root', function () + { + const stage = new PIXI.Container(); + const graphics = new PIXI.Graphics(); + const behind = new PIXI.Graphics(); + const pointer = this.pointer = new MockPointer(stage); + + stage.addChild(behind); + stage.addChild(graphics); + graphics.beginFill(0xFFFFFF); + graphics.drawRect(0, 0, 50, 50); + graphics.interactive = true; + behind.beginFill(0xFFFFFF); + behind.drawRect(0, 0, 50, 50); + behind.interactive = true; + + pointer.render(); + const hit = pointer.interaction.hitTest(new PIXI.Point(10, 10), behind); + + expect(hit).to.equal(behind); + }); + }); }); diff --git a/.travis.yml b/.travis.yml index 3df8b22..14de334 100644 --- a/.travis.yml +++ b/.travis.yml @@ -110,3 +110,43 @@ on: all_branches: true condition: $TRAVIS_TAG + # Deploy config for latest release + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: dist + upload-dir: "release" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: docs + upload-dir: "release/docs" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: coverage + upload-dir: "release/coverage" + on: + all_branches: true + condition: $TRAVIS_TAG \ No newline at end of file diff --git a/README.md b/README.md index d26fadd..3b85f85 100644 --- a/README.md +++ b/README.md @@ -36,22 +36,14 @@ - Wiki: Other misc tutorials and resources are [on the Wiki](https://github.com/pixijs/pixi.js/wiki/Resources). ### Community ### -- Forums: Check out the [forum] (http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow] (http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. +- Forums: Check out the [forum](http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow](http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. - Inspiration: Check out the [gallery](http://www.pixijs.com/gallery) to see some of the amazing things people have created! - Chat: You can join us on [Gitter](https://gitter.im/pixijs/pixi.js) To chat about Pixi. We also now have a Slack channel. If you would like to join it please Send me an email (mat@goodboydigital.com) and I will invite you in. ### Setup ### -It's easy to get started with Pixi.js! Simply grab the pre-built versions from here: - -Release Branch - Nice and stable Pixi.js -- Unminified: [http://pixijs.download/release/pixi.js] -- Minified: [http://pixijs.download/release/pixi.min.js] - -Develop Branch - The bleeding edge version of Pixi.js -- Unminified: [http://pixijs.download/dev/pixi.js] -- Minified: [http://pixijs.download/dev/pixi.min.js] +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. @@ -70,10 +62,10 @@ #### CDN Install (via cdnjs) ```html - + ``` -_Note: `4.2.2` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ +_Note: `4.4.3` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ ### Demos ### @@ -130,7 +122,7 @@ // and the root stage PIXI.Container. var app = new PIXI.Application(); -// The application will create a canvas element for you that you +// The application will create a canvas element for you that you // can then insert into the DOM. document.body.appendChild(app.view); @@ -150,7 +142,7 @@ // Add the bunny to the scene we are building. app.stage.addChild(bunny); - + // Listen for frame updates app.ticker.add(function() { // each frame we spin the bunny around a bit diff --git a/bower.json b/bower.json deleted file mode 100644 index 41b668c..0000000 --- a/bower.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "pixi.js", - "main": "dist/pixi.min.js", - "ignore": [ - "**/.*", - "docs", - "node_modules", - "bower_components", - "test", - "scripts", - "bower.json", - "inch.json" - ], - "dependencies": { - }, - "devDependencies": { - } -} diff --git a/package.json b/package.json index d35cbb2..308469f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.3.5", + "version": "4.4.4", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ @@ -40,11 +40,11 @@ "lib": "babel src --out-dir lib -s", "predocs": "rimraf docs/**", "docs": "jsdoc -c scripts/jsdoc.conf.json -R README.md", - "publish:patch": "npm version patch --no-git-tag-version && npm publish", - "publish:minor": "npm version minor --no-git-tag-version && npm publish", - "publish:major": "npm version major --no-git-tag-version && npm publish", + "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", - "postpublish": "node scripts/release.js" + "postpublish": "git push && git push --tags" }, "files": [ "dist/", @@ -72,7 +72,6 @@ "electron": "^1.4.15", "eslint": "^3.5.0", "floss": "^2.0.1", - "gh-pages": "^0.11.0", "jaguarjs-jsdoc": "^1.0.1", "js-md5": "^0.4.1", "jsdoc": "^3.4.2", diff --git a/scripts/jsdoc.conf.json b/scripts/jsdoc.conf.json index 553e61c..3f52180 100644 --- a/scripts/jsdoc.conf.json +++ b/scripts/jsdoc.conf.json @@ -43,7 +43,7 @@ }, "markdown" : { "parser" : "gfm", - "hardwrap" : true + "hardwrap" : false }, "opts": { "encoding" : "utf8", diff --git a/scripts/release.js b/scripts/release.js deleted file mode 100644 index 156620c..0000000 --- a/scripts/release.js +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env node -'use strict'; - -// Publish script to push releases of the bin files -// the normally are gitignored -const ghpages = require('gh-pages'); -const path = require('path'); -const packageInfo = require(path.join(__dirname, '..', 'package.json')); - -const options = { - src: [ - 'dist/**/*', - 'lib/**/*', - 'src/**/*', - 'scripts/*', - 'scripts/renders/*', - 'test/**/*', - '*.json', - '*.md', - 'LICENSE', - '.eslintrc', - '.editorconfig', - '.travis.yml', - '.babelrc', - ], - dotfiles: true, - branch: 'release', - message: packageInfo.version, - logger: console.log.bind(console), -}; - -ghpages.publish(process.cwd(), options, (err) => -{ - if (err) - { - console.log(err); - process.exit(1); - - return; - } - - process.exit(0); -}); diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index f16bcb1..220427a 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -21,7 +21,7 @@ const DIV_HOOK_ZINDEX = 2; /** - * The Accessibility manager reacreates the ability to tab and and have content read by screen + * The Accessibility manager recreates the ability to tab and and have content read by screen * readers. This is very important as it can possibly help people with disabilities access pixi * content. * diff --git a/src/accessibility/index.js b/src/accessibility/index.js index 568cfb1..0ae108f 100644 --- a/src/accessibility/index.js +++ b/src/accessibility/index.js @@ -1,4 +1,9 @@ /** + * This namespace contains a renderer plugin for interaction accessibility for end-users + * with physical impairments which require screen-renders, keyboard navigation, etc. + * + * Do not instantiate this plugin directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. * @namespace PIXI.accessibility */ export { default as accessibleTarget } from './accessibleTarget'; diff --git a/src/core/Application.js b/src/core/Application.js index 5340578..2fcc729 100644 --- a/src/core/Application.js +++ b/src/core/Application.js @@ -1,6 +1,8 @@ import { autoDetectRenderer } from './autoDetectRenderer'; import Container from './display/Container'; import { shared, Ticker } from './ticker'; +import settings from './settings'; +import { UPDATE_PRIORITY } from './const'; /** * Convenience class to create a new PIXI application. @@ -22,28 +24,52 @@ */ export default class Application { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experience unexplained flickering try setting this to true. - * @param {boolean} [useSharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedLoader=false] - `true` to use PIXI.loaders.shared, `false` to create new Loader. */ - constructor(width, height, options, noWebGL, useSharedTicker = false) + constructor(options, arg2, arg3, arg4, arg5) { + // Support for constructor(width, height, options, noWebGL, useSharedTicker) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + forceCanvas: !!arg4, + sharedTicker: !!arg5, + }, arg3); + } + + /** + * The default options, so we mixin functionality later. + * @member {object} + * @protected + */ + this._options = options = Object.assign({ + sharedTicker: false, + forceCanvas: false, + sharedLoader: false, + }, options); + /** * WebGL renderer if available, otherwise CanvasRenderer * @member {PIXI.WebGLRenderer|PIXI.CanvasRenderer} */ - this.renderer = autoDetectRenderer(width, height, options, noWebGL); + this.renderer = autoDetectRenderer(options); /** * The root display container that's rendered. @@ -63,7 +89,7 @@ * @member {PIXI.ticker.Ticker} * @default PIXI.ticker.shared */ - this.ticker = useSharedTicker ? shared : new Ticker(); + this.ticker = options.sharedTicker ? shared : new Ticker(); // Start the rendering this.start(); @@ -78,7 +104,7 @@ this._ticker = ticker; if (ticker) { - ticker.add(this.render, this); + ticker.add(this.render, this, UPDATE_PRIORITY.LOW); } } get ticker() // eslint-disable-line require-jsdoc @@ -136,13 +162,18 @@ */ destroy(removeView) { - this.stop(); + const oldTicker = this._ticker; + this.ticker = null; + oldTicker.destroy(); + this.stage.destroy(); this.stage = null; this.renderer.destroy(removeView); this.renderer = null; + + this._options = null; } } diff --git a/src/core/autoDetectRenderer.js b/src/core/autoDetectRenderer.js index 8960425..9ec0364 100644 --- a/src/core/autoDetectRenderer.js +++ b/src/core/autoDetectRenderer.js @@ -2,6 +2,7 @@ import CanvasRenderer from './renderers/canvas/CanvasRenderer'; import WebGLRenderer from './renderers/webgl/WebGLRenderer'; +// eslint-disable-next-line valid-jsdoc /** * This helper function will automatically detect which renderer you should be using. * WebGL is the preferred renderer as it is a lot faster. If webGL is not supported by @@ -9,24 +10,32 @@ * * @memberof PIXI * @function autoDetectRenderer - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @return {PIXI.WebGLRenderer|PIXI.CanvasRenderer} Returns WebGL renderer if available, otherwise CanvasRenderer */ -export function autoDetectRenderer(width = 800, height = 600, options, noWebGL) +export function autoDetectRenderer(options, arg1, arg2, arg3) { - if (!noWebGL && utils.isWebGLSupported()) + // Backward-compatible support for noWebGL option + let forceCanvas = options && options.forceCanvas; + + if (arg3 !== undefined) { - return new WebGLRenderer(width, height, options); + forceCanvas = arg3; } - return new CanvasRenderer(width, height, options); + if (!forceCanvas && utils.isWebGLSupported()) + { + return new WebGLRenderer(options, arg1, arg2); + } + + return new CanvasRenderer(options, arg1, arg2); } diff --git a/src/core/const.js b/src/core/const.js index b9e99ae..2c68dee 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -341,3 +341,27 @@ LINEAR_VERTICAL: 0, LINEAR_HORIZONTAL: 1, }; + +/** + * Represents the update priorities used by internal PIXI classes when registered with + * the {@link PIXI.ticker.Ticker} object. Higher priority items are updated first and lower + * priority items, such as render, should go later. + * + * @static + * @constant + * @name UPDATE_PRIORITY + * @memberof PIXI + * @type {object} + * @property {number} INTERACTION=50 Highest priority, used for {@link PIXI.interaction.InteractionManager} + * @property {number} HIGH=25 High priority updating, {@link PIXI.VideoBaseTexture} and {@link PIXI.extras.AnimatedSprite} + * @property {number} NORMAL=0 Default priority for ticker events, see {@link PIXI.ticker.Ticker#add}. + * @property {number} LOW=-25 Low priority used for {@link PIXI.Application} rendering. + * @property {number} UTILITY=-50 Lowest priority used for {@link PIXI.prepare.BasePrepare} utility. + */ +export const UPDATE_PRIORITY = { + INTERACTION: 50, + HIGH: 25, + NORMAL: 0, + LOW: -25, + UTILITY: -50, +}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 4e6c77d..decb42a 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -13,7 +13,6 @@ * * @class * @extends EventEmitter - * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ export default class DisplayObject extends EventEmitter @@ -113,6 +112,29 @@ * @private */ this._mask = null; + + /** + * If the object has been destroyed via destroy(). If true, it should not be used. + * + * @member {boolean} + * @private + * @readonly + */ + this._destroyed = false; + + /** + * Fired when this DisplayObject is added to a Container. + * + * @event PIXI.DisplayObject#added + * @param {PIXI.Container} container - The container added to. + */ + + /** + * Fired when this DisplayObject is removed from a Container. + * + * @event PIXI.DisplayObject#removed + * @param {PIXI.Container} container - The container removed from. + */ } /** @@ -403,6 +425,8 @@ this.interactive = false; this.interactiveChildren = false; + + this._destroyed = true; } /** diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index aafdfe2..f787c4a 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -792,8 +792,8 @@ sprite.worldAlpha = this.worldAlpha * sprite.alpha; sprite.blendMode = this.blendMode; - sprite.texture._frame.width = rect.width; - sprite.texture._frame.height = rect.height; + sprite._texture._frame.width = rect.width; + sprite._texture._frame.height = rect.height; sprite.transform.worldTransform = this.transform.worldTransform; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index a24c775..b9f192d 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -19,11 +19,12 @@ */ export default class SystemRenderer extends EventEmitter { + // eslint-disable-next-line valid-jsdoc /** * @param {string} system - The name of the system this renderer is for. - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -37,27 +38,31 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(system, screenWidth, screenHeight, options) + constructor(system, options, arg2, arg3) { super(); sayHello(system); - // prepare options - if (options) + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') { - for (const i in settings.RENDER_OPTIONS) - { - if (typeof options[i] === 'undefined') - { - options[i] = settings.RENDER_OPTIONS[i]; - } - } + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); } - else - { - options = settings.RENDER_OPTIONS; - } + + // Add the default render options + options = Object.assign({}, settings.RENDER_OPTIONS, options); + + /** + * The supplied constructor options. + * + * @member {Object} + * @readOnly + */ + this.options = options; /** * The type of the renderer. @@ -75,7 +80,7 @@ * * @member {PIXI.Rectangle} */ - this.screen = new Rectangle(0, 0, screenWidth || 800, screenHeight || 600); + this.screen = new Rectangle(0, 0, options.width, options.height); /** * The canvas element that everything is drawn to @@ -279,6 +284,8 @@ this.blendModes = null; + this.options = null; + this.preserveDrawingBuffer = false; this.clearBeforeRender = false; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index e84e49c..691caf0 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -17,10 +17,11 @@ */ export default class CanvasRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -34,9 +35,9 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('Canvas', screenWidth, screenHeight, options); + super('Canvas', options, arg2, arg3); this.type = RENDERER_TYPE.CANVAS; @@ -96,7 +97,19 @@ this.context = null; this.renderingToScreen = false; - this.resize(screenWidth, screenHeight); + this.resize(this.options.width, this.options.height); + + /** + * Fired after rendering finishes. + * + * @event PIXI.CanvasRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.CanvasRenderer#prerender + */ } /** @@ -299,4 +312,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.CanvasRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.CanvasExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.CanvasPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.CanvasRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index e1cf7cd..f6fe123 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -31,11 +31,12 @@ */ export default class WebGLRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** * - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -55,9 +56,9 @@ * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('WebGL', screenWidth, screenHeight, options); + super('WebGL', options, arg2, arg3); /** * The type of this renderer as a standardised const @@ -89,12 +90,6 @@ }; - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ this._backgroundColorRgba[3] = this.transparent ? 0 : 1; this.globalUniforms = new UniformGroup({ @@ -117,6 +112,13 @@ this.initPlugins(); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ if(options.context) { this.context.initFromContext(options.context); @@ -169,6 +171,25 @@ } return this; + + /** + * Fired after rendering finishes. + * + * @event PIXI.WebGLRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.WebGLRenderer#prerender + */ + + /** + * Fired when the WebGL context is set. + * + * @event PIXI.WebGLRenderer#context + * @param {WebGLRenderingContext} gl - WebGL context. + */ } /** @@ -295,4 +316,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.WebGLRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.WebGLRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(WebGLRenderer); diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index 4eb6128..fa3d883 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -47,6 +47,14 @@ * @member {boolean} */ this.enabled = true; + + /** + * If enabled, pixi will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; } /** diff --git a/src/core/renderers/webgl/systems/ContextSystem.js b/src/core/renderers/webgl/systems/ContextSystem.js index ff958c7..8df85ea 100644 --- a/src/core/renderers/webgl/systems/ContextSystem.js +++ b/src/core/renderers/webgl/systems/ContextSystem.js @@ -75,7 +75,8 @@ */ createContext(canvas, options) { - var gl = canvas.getContext('webgl', options) || + var gl = //canvas.getContext('webgl2', options) || + canvas.getContext('webgl', options) || canvas.getContext('experimental-webgl', options); if (!gl) diff --git a/src/core/renderers/webgl/systems/FilterSystem.js b/src/core/renderers/webgl/systems/FilterSystem.js index f47335d..37e28d2 100644 --- a/src/core/renderers/webgl/systems/FilterSystem.js +++ b/src/core/renderers/webgl/systems/FilterSystem.js @@ -111,7 +111,7 @@ // TODO we should fit the rect around the transform.. } - else + else if (filters[0].autoFit) { sourceFrame.fit(filterData.stack[0].destinationFrame); } diff --git a/src/core/renderers/webgl/systems/shader/shader/mapType.js b/src/core/renderers/webgl/systems/shader/shader/mapType.js index 581dace..917538a 100644 --- a/src/core/renderers/webgl/systems/shader/shader/mapType.js +++ b/src/core/renderers/webgl/systems/shader/shader/mapType.js @@ -15,7 +15,7 @@ } } - return GL_TABLE[type]; + return GL_TABLE[type]; }; var GL_TABLE = null; diff --git a/src/core/settings.js b/src/core/settings.js index 18c5877..38ef45c 100644 --- a/src/core/settings.js +++ b/src/core/settings.js @@ -2,6 +2,15 @@ import canUploadSameBuffer from './utils/canUploadSameBuffer'; /** + * User's customizable globals for overriding the default PIXI settings, such + * as a renderer's default resolution, framerate, float percision, etc. + * @example + * // Use the native window resolution as the default resolution + * // will support high-density displays when rendering + * PIXI.settings.RESOLUTION = window.devicePixelRatio. + * + * // Disable interpolation when scaling, will make texture be pixelated + * PIXI.settings.SCALE_MODE = PIXI.SCALE_MODES.NEAREST; * @namespace PIXI.settings */ export default { @@ -101,6 +110,9 @@ * @property {boolean} clearBeforeRender=true * @property {boolean} preserveDrawingBuffer=false * @property {boolean} roundPixels=false + * @property {number} width=800 + * @property {number} height=600 + * @property {boolean} legacy=false */ RENDER_OPTIONS: { view: null, @@ -112,6 +124,9 @@ clearBeforeRender: true, preserveDrawingBuffer: false, roundPixels: false, + width: 800, + height: 600, + legacy: false, }, /** diff --git a/src/core/shader/Program.js b/src/core/shader/Program.js index c0b538b..07b4398 100644 --- a/src/core/shader/Program.js +++ b/src/core/shader/Program.js @@ -1,6 +1,6 @@ import extractUniformsFromSrc from './extractUniformsFromSrc'; import generateUniformsSync from './generateUniformsSync'; -import glCore from 'pixi-gl-core'; +import shaderUtils from '../renderers/webgl/systems/shader/shader'; import { ProgramCache } from '../utils'; import getTestContext from '../utils/getTestContext'; @@ -64,14 +64,17 @@ } else { - vertexSrc = glCore.shader.setPrecision(vertexSrc, 'mediump'); - fragmentSrc = glCore.shader.setPrecision(fragmentSrc, 'mediump'); + vertexSrc = shaderUtils.setPrecision(vertexSrc, 'mediump'); + fragmentSrc = shaderUtils.setPrecision(fragmentSrc, 'mediump'); - const program = glCore.shader.compileProgram(gl, vertexSrc, fragmentSrc); + const program = shaderUtils.compileProgram(gl, vertexSrc, fragmentSrc); + console.log("<<<<<<>>><<"); this.attributeData = this.getAttributeData(program, gl); this.uniformData = this.getUniformData(program, gl); + console.log(this.uniformData); + //gl.deleteProgram(program); } } @@ -95,13 +98,13 @@ for (let i = 0; i < totalAttributes; i++) { const attribData = gl.getActiveAttrib(program, i); - const type = glCore.shader.mapType(gl, attribData.type); + const type = shaderUtils.mapType(gl, attribData.type); /*eslint-disable */ const data = { type: type, name: attribData.name, - size: glCore.shader.mapSize(type), + size: shaderUtils.mapSize(type), location: 0, }; /*eslint-enable */ @@ -137,13 +140,14 @@ // TODO expose this as a prop? // const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); - const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); + //const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); for (let i = 0; i < totalUniforms; i++) { const uniformData = gl.getActiveUniform(program, i); const name = uniformData.name.replace(/\[.*?\]/, ''); - const type = glCore.shader.mapType(gl, uniformData.type); + const type = shaderUtils.mapType(gl, uniformData.type); + console.log('mapping ' + uniformData.type + ' to ' + type); // if (!name.match(maskRegex)) { @@ -151,7 +155,7 @@ uniforms[name] = { type: type, size: uniformData.size, - value: glCore.shader.defaultValue(type, uniformData.size), + value: shaderUtils.defaultValue(type, uniformData.size), }; /*eslint-enable */ } diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index e387e99..b57d095 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -147,12 +147,12 @@ // so if _width is 0 then width was not set.. if (this._width) { - this.scale.x = sign(this.scale.x) * this._width / this.texture.orig.width; + this.scale.x = sign(this.scale.x) * this._width / this._texture.orig.width; } if (this._height) { - this.scale.y = sign(this.scale.y) * this._height / this.texture.orig.height; + this.scale.y = sign(this.scale.y) * this._height / this._texture.orig.height; } } diff --git a/src/core/sprites/canvas/CanvasTinter.js b/src/core/sprites/canvas/CanvasTinter.js index 27e5e0c..fd9de5d 100644 --- a/src/core/sprites/canvas/CanvasTinter.js +++ b/src/core/sprites/canvas/CanvasTinter.js @@ -18,7 +18,7 @@ */ getTintedTexture: (sprite, color) => { - const texture = sprite.texture; + const texture = sprite._texture; color = CanvasTinter.roundColor(color); diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 5a4edda..8b9b797 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -344,6 +344,7 @@ const texture = this._texture; const style = this._style; + const padding = style.trim ? 0 : style.padding; texture.baseTexture.valid = true; texture.baseTexture.resolution = this.resolution; @@ -353,11 +354,11 @@ texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - texture.trim.x = -style.padding; - texture.trim.y = -style.padding; + texture.trim.x = -padding; + texture.trim.y = -padding; - texture.orig.width = texture._frame.width - (style.padding * 2); - texture.orig.height = texture._frame.height - (style.padding * 2); + texture.orig.width = texture._frame.width - (padding * 2); + texture.orig.height = texture._frame.height - (padding * 2); // call sprite onTextureUpdate to update scale if _width or _height were set this._onTextureUpdate(); diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index 853da3d..8a9c34b 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -114,6 +114,38 @@ this.cacheId = null; this.validate(); + + /** + * Fired when a not-immediately-available source finishes loading. + * + * @protected + * @event PIXI.BaseTexture#loaded + * @param {PIXI.BaseTexture} baseTexture - Resource loaded. + */ + + /** + * Fired when a not-immediately-available source fails to load. + * + * @protected + * @event PIXI.BaseTexture#error + * @param {PIXI.BaseTexture} baseTexture - Resource errored. + */ + + /** + * Fired when BaseTexture is updated. + * + * @protected + * @event PIXI.BaseTexture#update + * @param {PIXI.BaseTexture} baseTexture - Instance of texture being updated. + */ + + /** + * Fired when BaseTexture is destroyed. + * + * @protected + * @event PIXI.BaseTexture#dispose + * @param {PIXI.BaseTexture} baseTexture - Instance of texture being destroyed. + */ } setResource(resource) diff --git a/src/core/textures/Spritesheet.js b/src/core/textures/Spritesheet.js index d469c62..9b24f27 100644 --- a/src/core/textures/Spritesheet.js +++ b/src/core/textures/Spritesheet.js @@ -208,6 +208,7 @@ // lets also add the frame to pixi's global cache for fromFrame and fromImage functions TextureCache[i] = this.textures[i]; + this.textures[i].textureCacheId = i; } frameIndex++; diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 0a2c379..2213087 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -147,9 +147,9 @@ /** * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. * - * @event update - * @memberof PIXI.Texture# + * @event PIXI.Texture#update * @protected + * @param {PIXI.Texture} texture - Instance of texture being updated. */ this._updateID = 0; @@ -159,6 +159,15 @@ * @type {Object} */ this.transform = null; + + /** + * The id under which this Texture has been added to the texture cache. This is + * automatically set in certain cases, but may not always be accurate, particularly if + * the texture is in the cache under multiple ids. + * + * @member {string} + */ + this.textureCacheId = null; } /** @@ -225,11 +234,17 @@ this._uvs = null; this.trim = null; this.orig = null; + this.textureCacheId = null; this.valid = false; - this.off('dispose', this.dispose, this); - this.off('update', this.update, this); + for (const prop in TextureCache) + { + if (TextureCache[prop] === this) + { + delete TextureCache[prop]; + } + } } /** @@ -315,14 +330,15 @@ */ static fromLoader(source, imageUrl, name) { - // console.log('added from loader...') + // console.log('added from loader...') const resource = new ImageResource(source);//.from(imageUrl, crossorigin);// document.createElement('img'); - // console.log('base resource ' + resource.width); + // console.log('base resource ' + resource.width); const baseTexture = new BaseTexture(resource, settings.SCALE_MODE, getResolutionOfUrl(imageUrl)); + /// console.log('base width ' + baseTexture.width); const texture = new Texture(baseTexture); @@ -335,6 +351,7 @@ // lets also add the frame to pixi's global cache for fromFrame and fromImage fucntions BaseTextureCache[name] = baseTexture; TextureCache[name] = texture; + texture.textureCacheId = name; // also add references by url if they are different. if (name !== imageUrl) @@ -355,6 +372,10 @@ */ static addTextureToCache(texture, id) { + if (!texture.textureCacheId) + { + texture.textureCacheId = id; + } TextureCache[id] = texture; } @@ -495,6 +516,7 @@ */ Texture.EMPTY = new Texture(new BaseTexture()); removeAllHandlers(Texture.EMPTY); +removeAllHandlers(Texture.EMPTY.baseTexture); /** * A white texture of 10x10 size, used for graphics and other things @@ -505,3 +527,4 @@ */ Texture.WHITE = createWhiteTexture(); removeAllHandlers(Texture.WHITE); +removeAllHandlers(Texture.WHITE.baseTexture); diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index 59eb9fd..a9b9f51 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -1,6 +1,7 @@ import BaseTexture from './BaseTexture'; import { uid, BaseTextureCache } from '../utils'; -import * as ticker from '../ticker'; +import { shared } from '../ticker'; +import { UPDATE_PRIORITY } from '../const'; /** * A texture of a [playing] Video. @@ -125,7 +126,7 @@ if (!this._isAutoUpdating && this.autoUpdate) { - ticker.shared.add(this.update, this); + shared.add(this.update, this, UPDATE_PRIORITY.HIGH); this._isAutoUpdating = true; } } @@ -139,7 +140,7 @@ { if (this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); this._isAutoUpdating = false; } } @@ -187,7 +188,7 @@ { if (this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); } if (this.source && this.source._pixiId) @@ -281,12 +282,12 @@ if (!this._autoUpdate && this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); this._isAutoUpdating = false; } else if (this._autoUpdate && !this._isAutoUpdating) { - ticker.shared.add(this.update, this); + shared.add(this.update, this, UPDATE_PRIORITY.HIGH); this._isAutoUpdating = true; } } diff --git a/src/core/ticker/Ticker.js b/src/core/ticker/Ticker.js index 17a1517..40df138 100644 --- a/src/core/ticker/Ticker.js +++ b/src/core/ticker/Ticker.js @@ -1,12 +1,10 @@ import settings from '../settings'; -import EventEmitter from 'eventemitter3'; - -// Internal event used by composed emitter -const TICK = 'tick'; +import { UPDATE_PRIORITY } from '../const'; +import TickerListener from './TickerListener'; /** * A Ticker class that runs an update loop that other objects listen to. - * This class is composed around an EventEmitter object to add listeners + * This class is composed around listeners * meant for execution on the next requested animation frame. * Animation frames are requested only when necessary, * e.g. When the ticker is started and the emitter has listeners. @@ -22,10 +20,11 @@ constructor() { /** - * Internal emitter used to fire 'tick' event + * The first listener. All new listeners added are chained on this. * @private + * @type {TickerListener} */ - this._emitter = new EventEmitter(); + this._head = new TickerListener(null, null, Infinity); /** * Internal current frame request ID @@ -131,7 +130,7 @@ // Invoke listeners now this.update(time); // Listener side effects may have modified ticker state. - if (this.started && this._requestId === null && this._emitter.listeners(TICK, true)) + if (this.started && this._requestId === null && this._head.next) { this._requestId = requestAnimationFrame(this._tick); } @@ -148,7 +147,7 @@ */ _requestIfNeeded() { - if (this._requestId === null && this._emitter.listeners(TICK, true)) + if (this._requestId === null && this._head.next) { // ensure callbacks get correct delta this.lastTime = performance.now(); @@ -193,35 +192,72 @@ } /** - * Calls {@link module:eventemitter3.EventEmitter#on} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. + * Register a handler for tick events. Calls continuously unless + * it is removed or the ticker is stopped. * * @param {Function} fn - The listener function to be added for updates * @param {Function} [context] - The listener context + * @param {number} [priority=PIXI.UPDATE_PRIORITY.NORMAL] - The priority for emitting * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - add(fn, context) + add(fn, context, priority = UPDATE_PRIORITY.NORMAL) { - this._emitter.on(TICK, fn, context); - - this._startIfPossible(); - - return this; + return this._addListener(new TickerListener(fn, context, priority)); } /** - * Calls {@link module:eventemitter3.EventEmitter#once} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. + * Add a handler for the tick event which is only execute once. * * @param {Function} fn - The listener function to be added for one update * @param {Function} [context] - The listener context + * @param {number} [priority=PIXI.UPDATE_PRIORITY.NORMAL] - The priority for emitting * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - addOnce(fn, context) + addOnce(fn, context, priority = UPDATE_PRIORITY.NORMAL) { - this._emitter.once(TICK, fn, context); + return this._addListener(new TickerListener(fn, context, priority, true)); + } + + /** + * Internally adds the event handler so that it can be sorted by priority. + * Priority allows certain handler (user, AnimatedSprite, Interaction) to be run + * before the rendering. + * + * @private + * @param {TickerListener} listener - Current listener being added. + * @returns {PIXI.ticker.Ticker} This instance of a ticker + */ + _addListener(listener) + { + // For attaching to head + let current = this._head.next; + let previous = this._head; + + // Add the first item + if (!current) + { + listener.connect(previous); + } + else + { + // Go from highest to lowest priority + while (current) + { + if (listener.priority >= current.priority) + { + listener.connect(previous); + break; + } + previous = current; + current = current.next; + } + + // Not yet connected + if (!listener.previous) + { + listener.connect(previous); + } + } this._startIfPossible(); @@ -229,19 +265,33 @@ } /** - * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. - * It checks if the emitter has listeners for 'tick' event. - * If it does, then it cancels the animation frame. + * Removes any handlers matching the function and context parameters. + * If no handlers are left after removing, then it cancels the animation frame. * - * @param {Function} [fn] - The listener function to be removed + * @param {Function} fn - The listener function to be removed * @param {Function} [context] - The listener context to be removed * @returns {PIXI.ticker.Ticker} This instance of a ticker */ remove(fn, context) { - this._emitter.off(TICK, fn, context); + let listener = this._head.next; - if (!this._emitter.listeners(TICK, true)) + while (listener) + { + // We found a match, lets remove it + // no break to delete all possible matches + // incase a listener was added 2+ times + if (listener.match(fn, context)) + { + listener = listener.destroy(); + } + else + { + listener = listener.next; + } + } + + if (!this._head.next) { this._cancelIfNeeded(); } @@ -276,6 +326,25 @@ } /** + * Destroy the ticker and don't use after this. Calling + * this method removes all references to internal events. + */ + destroy() + { + this.stop(); + + let listener = this._head.next; + + while (listener) + { + listener = listener.destroy(true); + } + + this._head.destroy(); + this._head = null; + } + + /** * Triggers an update. An update entails setting the * current {@link PIXI.ticker.Ticker#elapsedMS}, * the current {@link PIXI.ticker.Ticker#deltaTime}, @@ -320,8 +389,22 @@ this.deltaTime = elapsedMS * settings.TARGET_FPMS * this.speed; + // Cache a local reference, in-case ticker is destroyed + // during the emit, we can still check for head.next + const head = this._head; + // Invoke listeners added to internal emitter - this._emitter.emit(TICK, this.deltaTime); + let listener = head.next; + + while (listener) + { + listener = listener.emit(this.deltaTime); + } + + if (!head.next) + { + this._cancelIfNeeded(); + } } else { diff --git a/src/core/ticker/TickerListener.js b/src/core/ticker/TickerListener.js new file mode 100644 index 0000000..2bedb34 --- /dev/null +++ b/src/core/ticker/TickerListener.js @@ -0,0 +1,158 @@ +/** + * Internal class for handling the priority sorting of ticker handlers. + * + * @private + * @class + * @memberof PIXI.ticker + */ +export default class TickerListener +{ + /** + * Constructor + * + * @param {Function} fn - The listener function to be added for one update + * @param {Function} [context=null] - The listener context + * @param {number} [priority=0] - The priority for emitting + * @param {boolean} [once=false] - If the handler should fire once + */ + constructor(fn, context = null, priority = 0, once = false) + { + /** + * The handler function to execute. + * @member {Function} + */ + this.fn = fn; + + /** + * The calling to execute. + * @member {Function} + */ + this.context = context; + + /** + * The current priority. + * @member {number} + */ + this.priority = priority; + + /** + * If this should only execute once. + * @member {boolean} + */ + this.once = once; + + /** + * The next item in chain. + * @member {TickerListener} + */ + this.next = null; + + /** + * The previous item in chain. + * @member {TickerListener} + */ + this.previous = null; + + /** + * `true` if this listener has been destroyed already. + * @member {boolean} + * @private + */ + this._destroyed = false; + } + + /** + * Simple compare function to figure out if a function and context match. + * + * @param {Function} fn - The listener function to be added for one update + * @param {Function} context - The listener context + * @return {boolean} `true` if the listener match the arguments + */ + match(fn, context) + { + context = context || null; + + return this.fn === fn && this.context === context; + } + + /** + * Emit by calling the current function. + * @param {number} deltaTime - time since the last emit. + * @return {TickerListener} Next ticker + */ + emit(deltaTime) + { + if (this.context) + { + this.fn.call(this.context, deltaTime); + } + else + { + this.fn(deltaTime); + } + + if (this.once) + { + this.destroy(); + } + + const redirect = this.next; + + // Soft-destroying should remove + // the next reference + if (this._destroyed) + { + this.next = null; + } + + return redirect; + } + + /** + * Connect to the list. + * @param {TickerListener} previous - Input node, previous listener + */ + connect(previous) + { + this.previous = previous; + if (previous.next) + { + previous.next.previous = this; + } + this.next = previous.next; + previous.next = this; + } + + /** + * Destroy and don't use after this. + * @param {boolean} [hard = false] `true` to remove the `next` reference, this + * is considered a hard destroy. Soft destroy maintains the next reference. + * @return {TickerListener} The listener to redirect while emitting or removing. + */ + destroy(hard = false) + { + this._destroyed = true; + this.fn = null; + this.context = null; + + // Disconnect, hook up next and previous + if (this.previous) + { + this.previous.next = this.next; + } + + if (this.next) + { + this.next.previous = this.previous; + } + + // Redirect to the next item + const redirect = this.previous; + + // Remove references + this.next = hard ? null : redirect; + this.previous = null; + + return redirect; + } +} diff --git a/src/core/ticker/index.js b/src/core/ticker/index.js index 4b3017c..c3020d4 100644 --- a/src/core/ticker/index.js +++ b/src/core/ticker/index.js @@ -45,8 +45,25 @@ const shared = new Ticker(); shared.autoStart = true; +shared.destroy = () => +{ + // protect destroying shared ticker + // this is used by other internal systems + // like AnimatedSprite and InteractionManager +}; /** + * This namespace contains an API for interacting with PIXI's internal global update loop. + * + * This ticker is used for rendering, {@link PIXI.extras.AnimatedSprite AnimatedSprite}, + * {@link PIXI.interaction.InteractionManager InteractionManager} and many other time-based PIXI systems. + * @example + * const ticker = new PIXI.ticker.Ticker(); + * ticker.stop(); + * ticker.add((deltaTime) => { + * // do something every frame + * }); + * ticker.start(); * @namespace PIXI.ticker */ export { shared, Ticker }; diff --git a/src/core/utils/index.js b/src/core/utils/index.js index be44750..7441f7b 100644 --- a/src/core/utils/index.js +++ b/src/core/utils/index.js @@ -9,6 +9,21 @@ let saidHello = false; /** + * Generalized convenience utilities for PIXI. + * @example + * // Extend PIXI's internal Event Emitter. + * class MyEmitter extends PIXI.utils.EventEmitter { + * constructor() { + * super(); + * console.log("Emitter created!"); + * } + * } + * + * // Get info on current device + * console.log(PIXI.utils.isMobile); + * + * // Convert hex color to string + * console.log(PIXI.utils.hex2string(0xff00ff)); // returns: "#ff00ff" * @namespace PIXI.utils */ export { diff --git a/src/deprecation.js b/src/deprecation.js index b9c73b3..33daeec 100644 --- a/src/deprecation.js +++ b/src/deprecation.js @@ -918,6 +918,35 @@ }); /** + * @method + * @name PIXI.prepare.BasePrepare#register + * @see PIXI.prepare.BasePrepare#registerFindHook + * @deprecated since version 4.4.2 + * @param {Function} [addHook] - Function call that takes two parameters: `item:*, queue:Array` + * function must return `true` if it was able to add item to the queue. + * @param {Function} [uploadHook] - Function call that takes two parameters: `prepare:CanvasPrepare, item:*` and + * function must return `true` if it was able to handle upload of item. + * @return {PIXI.BasePrepare} Instance of plugin for chaining. + */ +prepare.BasePrepare.prototype.register = function register(addHook, uploadHook) +{ + warn('renderer.plugins.prepare.register is now deprecated, ' + + 'please use renderer.plugins.prepare.registerFindHook & renderer.plugins.prepare.registerUploadHook'); + + if (addHook) + { + this.registerFindHook(addHook); + } + + if (uploadHook) + { + this.registerUploadHook(uploadHook); + } + + return this; +}; + +/** * The number of graphics or textures to upload to the GPU. * * @name PIXI.prepare.canvas.UPLOADS_PER_FRAME @@ -969,67 +998,73 @@ }, }); -Object.defineProperties(loaders.Resource.prototype, { - isJson: { - get() - { - warn('The isJson property is deprecated, please use `resource.type === Resource.TYPE.JSON`.'); +if (loaders.Loader) +{ + const Resource = loaders.Resource; + const Loader = loaders.Loader; - return this.type === loaders.Loader.Resource.TYPE.JSON; + Object.defineProperties(Resource.prototype, { + isJson: { + get() + { + warn('The isJson property is deprecated, please use `resource.type === Resource.TYPE.JSON`.'); + + return this.type === Resource.TYPE.JSON; + }, }, - }, - isXml: { - get() - { - warn('The isXml property is deprecated, please use `resource.type === Resource.TYPE.XML`.'); + isXml: { + get() + { + warn('The isXml property is deprecated, please use `resource.type === Resource.TYPE.XML`.'); - return this.type === loaders.Loader.Resource.TYPE.XML; + return this.type === Resource.TYPE.XML; + }, }, - }, - isImage: { - get() - { - warn('The isImage property is deprecated, please use `resource.type === Resource.TYPE.IMAGE`.'); + isImage: { + get() + { + warn('The isImage property is deprecated, please use `resource.type === Resource.TYPE.IMAGE`.'); - return this.type === loaders.Loader.Resource.TYPE.IMAGE; + return this.type === Resource.TYPE.IMAGE; + }, }, - }, - isAudio: { - get() - { - warn('The isAudio property is deprecated, please use `resource.type === Resource.TYPE.AUDIO`.'); + isAudio: { + get() + { + warn('The isAudio property is deprecated, please use `resource.type === Resource.TYPE.AUDIO`.'); - return this.type === loaders.Loader.Resource.TYPE.AUDIO; + return this.type === Resource.TYPE.AUDIO; + }, }, - }, - isVideo: { - get() - { - warn('The isVideo property is deprecated, please use `resource.type === Resource.TYPE.VIDEO`.'); + isVideo: { + get() + { + warn('The isVideo property is deprecated, please use `resource.type === Resource.TYPE.VIDEO`.'); - return this.type === loaders.Loader.Resource.TYPE.VIDEO; + return this.type === Resource.TYPE.VIDEO; + }, }, - }, -}); + }); -Object.defineProperties(loaders.Loader.prototype, { - before: { - get() - { - warn('The before() method is deprecated, please use pre().'); + Object.defineProperties(Loader.prototype, { + before: { + get() + { + warn('The before() method is deprecated, please use pre().'); - return this.pre; + return this.pre; + }, }, - }, - after: { - get() - { - warn('The after() method is deprecated, please use use().'); + after: { + get() + { + warn('The after() method is deprecated, please use use().'); - return this.use; + return this.use; + }, }, - }, -}); + }); +} /** * @name PIXI.interaction.interactiveTarget#defaultCursor diff --git a/src/extract/canvas/CanvasExtract.js b/src/extract/canvas/CanvasExtract.js index 941ca05..3ac5aee 100644 --- a/src/extract/canvas/CanvasExtract.js +++ b/src/extract/canvas/CanvasExtract.js @@ -8,7 +8,7 @@ * An instance of this class is automatically created by default, and can be found at renderer.plugins.extract * * @class - * @memberof PIXI + * @memberof PIXI.extract */ export default class CanvasExtract { @@ -21,9 +21,9 @@ /** * Collection of methods for extracting data (image, pixels, etc.) from a display object or render texture * - * @member {PIXI.CanvasExtract} extract + * @member {PIXI.extract.CanvasExtract} extract * @memberof PIXI.CanvasRenderer# - * @see PIXI.CanvasExtract + * @see PIXI.extract.CanvasExtract */ renderer.extract = this; } diff --git a/src/extract/index.js b/src/extract/index.js index 0f2170d..e9478c1 100644 --- a/src/extract/index.js +++ b/src/extract/index.js @@ -1,2 +1,22 @@ +/** + * This namespace provides renderer-specific plugins for exporting content from a renderer. + * For instance, these plugins can be used for saving an Image, Canvas element or for exporting the raw image data (pixels). + * + * Do not instantiate these plugins directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. + * @example + * // Create a new app (will auto-add extract plugin to renderer) + * const app = new PIXI.Application(); + * + * // Draw a red circle + * const graphics = new PIXI.Graphics() + * .beginFill(0xFF0000) + * .drawCircle(0, 0, 50); + * + * // Render the graphics as an HTMLImageElement + * const image = app.renderer.plugins.extract.image(graphics); + * document.body.appendChild(image); + * @namespace PIXI.extract + */ export { default as webgl } from './webgl/WebGLExtract'; export { default as canvas } from './canvas/CanvasExtract'; diff --git a/src/extract/webgl/WebGLExtract.js b/src/extract/webgl/WebGLExtract.js index 60228bb..1c3dd30 100644 --- a/src/extract/webgl/WebGLExtract.js +++ b/src/extract/webgl/WebGLExtract.js @@ -9,7 +9,7 @@ * An instance of this class is automatically created by default, and can be found at renderer.plugins.extract * * @class - * @memberof PIXI + * @memberof PIXI.extract */ export default class WebGLExtract { @@ -22,9 +22,9 @@ /** * Collection of methods for extracting data (image, pixels, etc.) from a display object or render texture * - * @member {PIXI.WebGLExtract} extract + * @member {PIXI.extract.WebGLExtract} extract * @memberof PIXI.WebGLRenderer# - * @see PIXI.WebGLExtract + * @see PIXI.extract.WebGLExtract */ renderer.extract = this; } diff --git a/src/extras/AnimatedSprite.js b/src/extras/AnimatedSprite.js index 13b804f..5297778 100644 --- a/src/extras/AnimatedSprite.js +++ b/src/extras/AnimatedSprite.js @@ -137,7 +137,7 @@ this.playing = true; if (this._autoUpdate) { - core.ticker.shared.add(this.update, this); + core.ticker.shared.add(this.update, this, core.UPDATE_PRIORITY.HIGH); } } diff --git a/src/extras/TextureTransform.js b/src/extras/TextureTransform.js index 78e6e40..16536d2 100644 --- a/src/extras/TextureTransform.js +++ b/src/extras/TextureTransform.js @@ -65,8 +65,36 @@ } /** + * Multiplies uvs array to transform + * @param {Float32Array} uvs mesh uvs + * @param {Float32Array} [out=uvs] output + * @returns {Float32Array} output + */ + multiplyUvs(uvs, out) + { + if (out === undefined) + { + out = uvs; + } + + const mat = this.mapCoord; + + for (let i = 0; i < uvs.length; i += 2) + { + const x = uvs[i]; + const y = uvs[i + 1]; + + out[i] = (x * mat.a) + (y * mat.c) + mat.tx; + out[i + 1] = (x * mat.b) + (y * mat.d) + mat.ty; + } + + return out; + } + + /** * updates matrices if texture was changed * @param {boolean} forceUpdate if true, matrices will be updated any case + * @returns {boolean} whether or not it was updated */ update(forceUpdate) { @@ -74,13 +102,13 @@ if (!tex || !tex.valid) { - return; + return false; } if (!forceUpdate && this._lastTextureID === tex._updateID) { - return; + return false; } this._lastTextureID = tex._updateID; @@ -110,5 +138,7 @@ frame[3] = (tex._frame.y + tex._frame.height - margin + offset) / texBase.height; this.uClampOffset[0] = offset / texBase.realWidth; this.uClampOffset[1] = offset / texBase.realHeight; + + return true; } } diff --git a/src/extras/TilingSprite.js b/src/extras/TilingSprite.js index 1f6014f..a3f027f 100644 --- a/src/extras/TilingSprite.js +++ b/src/extras/TilingSprite.js @@ -180,9 +180,9 @@ const transform = this.worldTransform; const resolution = renderer.resolution; const baseTexture = texture.baseTexture; - const baseTextureResolution = texture.baseTexture.resolution; - const modX = (this.tilePosition.x / this.tileScale.x) % texture._frame.width; - const modY = (this.tilePosition.y / this.tileScale.y) % texture._frame.height; + const baseTextureResolution = baseTexture.resolution; + const modX = ((this.tilePosition.x / this.tileScale.x) % texture._frame.width) * baseTextureResolution; + const modY = ((this.tilePosition.y / this.tileScale.y) % texture._frame.height) * baseTextureResolution; // create a nice shiny pattern! // TODO this needs to be refreshed if texture changes.. diff --git a/src/extras/cacheAsBitmap.js b/src/extras/cacheAsBitmap.js index 5dd5640..aea06a8 100644 --- a/src/extras/cacheAsBitmap.js +++ b/src/extras/cacheAsBitmap.js @@ -227,7 +227,16 @@ this.transform._parentID = -1; // restore the transform of the cached sprite to avoid the nasty flicker.. - this.updateTransform(); + if (!this.parent) + { + this.parent = renderer._tempDisplayObjectParent; + this.updateTransform(); + this.parent = null; + } + else + { + this.updateTransform(); + } // map the hit test.. this.containsPoint = cachedSprite.containsPoint.bind(cachedSprite); @@ -314,7 +323,17 @@ cachedSprite._bounds = this._bounds; cachedSprite.alpha = cacheAlpha; - this.updateTransform(); + if (!this.parent) + { + this.parent = renderer._tempDisplayObjectParent; + this.updateTransform(); + this.parent = null; + } + else + { + this.updateTransform(); + } + this.updateTransform = this.displayObjectUpdateTransform; this._cacheData.sprite = cachedSprite; @@ -358,9 +377,12 @@ * Destroys the cached object. * * @private + * @param {object|boolean} [options] - Options parameter. A boolean will act as if all options + * have been set to that value. + * Used when destroying containers, see the Container.destroy method. */ -DisplayObject.prototype._cacheAsBitmapDestroy = function _cacheAsBitmapDestroy() +DisplayObject.prototype._cacheAsBitmapDestroy = function _cacheAsBitmapDestroy(options) { this.cacheAsBitmap = false; - this.destroy(); + this.destroy(options); }; diff --git a/src/extras/index.js b/src/extras/index.js index bbd8bbf..a34d7ec 100644 --- a/src/extras/index.js +++ b/src/extras/index.js @@ -1,7 +1,7 @@ /** + * Additional PIXI DisplayObjects for animation, tiling and bitmap text. * @namespace PIXI.extras */ -export { default as TextureTransform } from './TextureTransform'; export { default as AnimatedSprite } from './AnimatedSprite'; export { default as TilingSprite } from './TilingSprite'; export { default as TilingSpriteRenderer } from './webgl/TilingSpriteRenderer'; diff --git a/src/extras/webgl/TilingSpriteRenderer.js b/src/extras/webgl/TilingSpriteRenderer.js index aeec172..eee3b5c 100644 --- a/src/extras/webgl/TilingSpriteRenderer.js +++ b/src/extras/webgl/TilingSpriteRenderer.js @@ -11,7 +11,7 @@ * WebGL renderer plugin for tiling sprites * * @class - * @memberof PIXI + * @memberof PIXI.extras * @extends PIXI.ObjectRenderer */ export default class TilingSpriteRenderer extends core.ObjectRenderer @@ -118,7 +118,7 @@ tempMat.invert(); if (isSimple) { - tempMat.append(uv.mapCoord); + tempMat.prepend(uv.mapCoord); } else { diff --git a/src/filters/displacement/DisplacementFilter.js b/src/filters/displacement/DisplacementFilter.js index d5355c3..b5d030c 100644 --- a/src/filters/displacement/DisplacementFilter.js +++ b/src/filters/displacement/DisplacementFilter.js @@ -35,7 +35,7 @@ this.maskSprite = sprite; this.maskMatrix = maskMatrix; - this.uniforms.mapSampler = sprite.texture; + this.uniforms.mapSampler = sprite._texture; this.uniforms.filterMatrix = maskMatrix; this.uniforms.scale = { x: 1, y: 1 }; diff --git a/src/filters/index.js b/src/filters/index.js index f3e8862..7ceb50a 100644 --- a/src/filters/index.js +++ b/src/filters/index.js @@ -1,4 +1,21 @@ /** + * This namespace contains WebGL-only display filters that can be applied + * to DisplayObjects using the {@link PIXI.DisplayObject#filters filters} property. + * @example + * // Create a new application + * const app = new PIXI.Application(); + * + * // Draw a green rectangle + * const rect = new PIXI.Graphics() + * .beginFill(0x00ff00) + * .drawRect(40, 40, 200, 200); + * + * // Add a blur filter + * rect.filters = [new PIXI.filters.BlurFilter()]; + * + * // Display rectangle + * app.stage.addChild(rect); + * document.body.appendChild(app.view); * @namespace PIXI.filters */ export { default as FXAAFilter } from './fxaa/FXAAFilter'; diff --git a/src/index.js b/src/index.js index c553427..77da490 100644 --- a/src/index.js +++ b/src/index.js @@ -20,6 +20,14 @@ import { utils } from './core'; utils.mixins.performMixins(); +/** + * Alias for {@link PIXI.loaders.shared}. + * @name loader + * @memberof PIXI + * @type {PIXI.loader.Loader} + */ +const loader = loaders.shared || null; + export { accessibility, extract, @@ -30,18 +38,8 @@ mesh, particles, prepare, + loader, }; -/** - * A premade instance of the loader that can be used to load resources. - * - * @name loader - * @memberof PIXI - * @property {PIXI.loaders.Loader} - */ -const loader = loaders && loaders.Loader ? new loaders.Loader() : null; // check is there in case user excludes loader lib - -export { loader }; - // Always export pixi globally. global.PIXI = exports; // eslint-disable-line diff --git a/src/interaction/InteractionData.js b/src/interaction/InteractionData.js index 02d53a3..449f8cc 100644 --- a/src/interaction/InteractionData.js +++ b/src/interaction/InteractionData.js @@ -30,7 +30,10 @@ /** * When passed to an event handler, this will be the original DOM Event that was captured * - * @member {Event} + * @see https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent + * @see https://developer.mozilla.org/en-US/docs/Web/API/TouchEvent + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent + * @member {MouseEvent|TouchEvent|PointerEvent} */ this.originalEvent = null; } diff --git a/src/interaction/InteractionManager.js b/src/interaction/InteractionManager.js index 8baf13a..2d75c7c 100644 --- a/src/interaction/InteractionManager.js +++ b/src/interaction/InteractionManager.js @@ -13,6 +13,14 @@ const MOUSE_POINTER_ID = 'MOUSE'; +// helpers for hitTest() - only used inside hitTest() +const hitTestEvent = { + target: null, + data: { + global: null, + }, +}; + /** * The interaction manager deals with mouse, touch and pointer events. Any DisplayObject can be interactive * if its interactive parameter is set to true @@ -245,67 +253,60 @@ this.setTargetElement(this.renderer.view, this.renderer.resolution); /** - * Fired when a pointer device button (usually a mouse button) is pressed on the display + * Fired when a pointer device button (usually a mouse left-button) is pressed on the display * object. * - * @event mousedown - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mousedown + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device secondary button (usually a mouse right-button) is pressed * on the display object. * - * @event rightdown - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightdown + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** - * Fired when a pointer device button (usually a mouse button) is released over the display + * Fired when a pointer device button (usually a mouse left-button) is released over the display * object. * - * @event mouseup - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseup + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device secondary button (usually a mouse right-button) is released * over the display object. * - * @event rightup - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightup + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** - * Fired when a pointer device button (usually a mouse button) is pressed and released on + * Fired when a pointer device button (usually a mouse left-button) is pressed and released on * the display object. * - * @event click - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#click + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device secondary button (usually a mouse right-button) is pressed * and released on the display object. * - * @event rightclick - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightclick + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** - * Fired when a pointer device button (usually a mouse button) is released outside the + * Fired when a pointer device button (usually a mouse left-button) is released outside the * display object that initially registered a * [mousedown]{@link PIXI.interaction.InteractionManager#event:mousedown}. * - * @event mouseupoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** @@ -313,146 +314,362 @@ * outside the display object that initially registered a * [rightdown]{@link PIXI.interaction.InteractionManager#event:rightdown}. * - * @event rightupoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device (usually a mouse) is moved while over the display object * - * @event mousemove - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mousemove + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device (usually a mouse) is moved onto the display object * - * @event mouseover - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseover + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device (usually a mouse) is moved off the display object * - * @event mouseout - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseout + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is pressed on the display object. * - * @event pointerdown - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerdown + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is released over the display object. * - * @event pointerup - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerup + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when the operating system cancels a pointer event * - * @event pointercancel - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointercancel + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is pressed and released on the display object. * - * @event pointertap - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointertap + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is released outside the display object that initially * registered a [pointerdown]{@link PIXI.interaction.InteractionManager#event:pointerdown}. * - * @event pointerupoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device is moved while over the display object * - * @event pointermove - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointermove + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device is moved onto the display object * - * @event pointerover - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerover + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device is moved off the display object * - * @event pointerout - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerout + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is placed on the display object. * - * @event touchstart - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchstart + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is removed from the display object. * - * @event touchend - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchend + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when the operating system cancels a touch * - * @event touchcancel - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchcancel + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is placed and removed from the display object. * - * @event tap - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#tap + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is removed outside of the display object that initially * registered a [touchstart]{@link PIXI.interaction.InteractionManager#event:touchstart}. * - * @event touchendoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchendoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is moved along the display object. * - * @event touchmove - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchmove + * @param {PIXI.interaction.InteractionData} event - Interaction data */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is pressed on the display. + * object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mousedown + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is pressed + * on the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightdown + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is released over the display + * object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseup + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is released + * over the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightup + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is pressed and released on + * the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#click + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is pressed + * and released on the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightclick + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is released outside the + * display object that initially registered a + * [mousedown]{@link PIXI.DisplayObject#event:mousedown}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is released + * outside the display object that initially registered a + * [rightdown]{@link PIXI.DisplayObject#event:rightdown}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device (usually a mouse) is moved while over the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mousemove + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device (usually a mouse) is moved onto the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseover + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device (usually a mouse) is moved off the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseout + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is pressed on the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerdown + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is released over the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerup + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when the operating system cancels a pointer event. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointercancel + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is pressed and released on the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointertap + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is released outside the display object that initially + * registered a [pointerdown]{@link PIXI.DisplayObject#event:pointerdown}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device is moved while over the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointermove + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device is moved onto the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerover + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device is moved off the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerout + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is placed on the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchstart + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is removed from the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchend + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when the operating system cancels a touch. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchcancel + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is placed and removed from the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#tap + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is removed outside of the display object that initially + * registered a [touchstart]{@link PIXI.DisplayObject#event:touchstart}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchendoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is moved along the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchmove + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + } + + /** + * Hit tests a point against the display tree, returning the first interactive object that is hit. + * + * @param {PIXI.Point} globalPoint - A point to hit test with, in global space. + * @param {PIXI.Container} [root] - The root display object to start from. If omitted, defaults + * to the last rendered root of the associated renderer. + * @return {PIXI.DisplayObject} The hit display object, if any. + */ + hitTest(globalPoint, root) + { + // clear the target for our hit test + hitTestEvent.target = null; + // assign the global point + hitTestEvent.data.global = globalPoint; + // ensure safety of the root + if (!root) + { + root = this.renderer._lastObjectRendered; + } + // run the hit test + this.processInteractive(hitTestEvent, root, null, true); + // return our found object - it'll be null if we didn't hit anything + + return hitTestEvent.target; } /** @@ -487,7 +704,7 @@ return; } - core.ticker.shared.add(this.update, this); + core.ticker.shared.add(this.update, this, core.UPDATE_PRIORITY.INTERACTION); if (window.navigator.msPointerEnabled) { @@ -804,8 +1021,6 @@ } } - let keepHitTestingAfterChildren = hitTest; - // ** FREE TIP **! If an object is not interactive or has no buttons in it // (such as a game scene!) set interactiveChildren to false for that displayObject. // This will allow pixi to completely ignore and bypass checking the displayObjects children. @@ -818,7 +1033,9 @@ const child = children[i]; // time to get recursive.. if this function will return if something is hit.. - if (this.processInteractive(interactionEvent, child, func, hitTest, interactiveParent)) + const childHit = this.processInteractive(interactionEvent, child, func, hitTest, interactiveParent); + + if (childHit) { // its a good idea to check if a child has lost its parent. // this means it has been removed whilst looping so its best @@ -827,8 +1044,6 @@ continue; } - hit = true; - // we no longer need to hit test any more objects in this container as we we // now know the parent has been hit interactiveParent = false; @@ -838,36 +1053,41 @@ // This means we no longer need to hit test anything else. We still need to run // through all objects, but we don't need to perform any hit tests. - keepHitTestingAfterChildren = false; - - if (child.interactive) + if (childHit) { - hitTest = false; + if (interactionEvent.target) + { + hitTest = false; + } + hit = true; } - - // we can break now as we have hit an object. } } } - hitTest = keepHitTestingAfterChildren; - // no point running this if the item is not interactive or does not have an interactive parent. if (interactive) { // if we are hit testing (as in we have no hit any objects yet) // We also don't need to worry about hit testing if once of the displayObjects children - // has already been hit! - if (hitTest && !hit) + // has already been hit - but only if it was interactive, otherwise we need to keep + // looking for an interactive child, just in case we hit one + if (hitTest && !interactionEvent.target) { if (displayObject.hitArea) { displayObject.worldTransform.applyInverse(point, this._tempPoint); - hit = displayObject.hitArea.contains(this._tempPoint.x, this._tempPoint.y); + if (displayObject.hitArea.contains(this._tempPoint.x, this._tempPoint.y)) + { + hit = true; + } } else if (displayObject.containsPoint) { - hit = displayObject.containsPoint(point); + if (displayObject.containsPoint(point)) + { + hit = true; + } } } @@ -878,7 +1098,10 @@ interactionEvent.target = displayObject; } - func(interactionEvent, displayObject, hit); + if (func) + { + func(interactionEvent, displayObject, !!hit); + } } } @@ -914,7 +1137,7 @@ { const event = events[i]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -998,7 +1221,7 @@ { const event = events[i]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1098,7 +1321,7 @@ const test = isRightButton ? flags.RIGHT_DOWN : flags.LEFT_DOWN; - const isDown = trackingData !== undefined && (trackingData.flags | test); + const isDown = trackingData !== undefined && (trackingData.flags & test); if (hit) { @@ -1118,11 +1341,11 @@ { if (isRightButton) { - trackingData.rightDown = hit; + trackingData.rightDown = false; } else { - trackingData.leftDown = hit; + trackingData.leftDown = false; } } } @@ -1180,7 +1403,7 @@ { const event = events[i]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1255,7 +1478,7 @@ this.setCursorMode(null); } - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1268,6 +1491,12 @@ { this.emit('mouseout', interactionEvent); } + else + { + // we can get touchleave events after touchend, so we want to make sure we don't + // introduce memory leaks + this.releaseInteractionDataForPointerId(interactionData.identifier); + } } /** @@ -1344,7 +1573,7 @@ // Only mouse and pointer can call onPointerOver, so events will always be length 1 const event = events[0]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1366,12 +1595,14 @@ * Get InteractionData for a given pointerId. Store that data as well * * @private - * @param {number} pointerId - Identifier from a pointer event + * @param {PointerEvent} event - Normalized pointer event, output from normalizeToPointerData * @return {InteractionData} - Interaction data for the given pointer identifier */ - getInteractionDataForPointerId(pointerId) + getInteractionDataForPointerId(event) { - if (pointerId === MOUSE_POINTER_ID) + const pointerId = event.pointerId; + + if (pointerId === MOUSE_POINTER_ID || event.pointerType === 'mouse') { return this.mouse; } diff --git a/src/interaction/InteractionTrackingData.js b/src/interaction/InteractionTrackingData.js index 6a1f69e..8493b63 100644 --- a/src/interaction/InteractionTrackingData.js +++ b/src/interaction/InteractionTrackingData.js @@ -35,8 +35,10 @@ } /** + * Unique pointer id of the event + * * @readonly - * @type {number} Unique pointer id of the event + * @member {number} */ get pointerId() { diff --git a/src/interaction/index.js b/src/interaction/index.js index 56c5b09..72f7d71 100644 --- a/src/interaction/index.js +++ b/src/interaction/index.js @@ -1,4 +1,8 @@ /** + * This namespace contains a renderer plugin for handling mouse, pointer, and touch events. + * + * Do not instantiate this plugin directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. * @namespace PIXI.interaction */ export { default as InteractionData } from './InteractionData'; diff --git a/src/interaction/interactiveTarget.js b/src/interaction/interactiveTarget.js index d252fba..cebac40 100644 --- a/src/interaction/interactiveTarget.js +++ b/src/interaction/interactiveTarget.js @@ -2,7 +2,7 @@ * Default property values of interactive objects * Used by {@link PIXI.interaction.InteractionManager} to automatically give all DisplayObjects these properties * - * @mixin + * @private * @name interactiveTarget * @memberof PIXI.interaction * @example @@ -14,18 +14,28 @@ * ); */ export default { + /** - * Determines if the displayObject be clicked/touched + * Enable interaction events for the DisplayObject. Touch, pointer and mouse + * events will not be emitted unless `interactive` is set to `true`. * - * @inner {boolean} + * @example + * const sprite = new PIXI.Sprite(texture); + * sprite.interactive = true; + * sprite.on('tap', (event) => { + * //handle event + * }); + * @member {boolean} + * @memberof PIXI.DisplayObject# */ interactive: false, /** * Determines if the children to the displayObject can be clicked/touched - * Setting this to false allows pixi to bypass a recursive hitTest function + * Setting this to false allows pixi to bypass a recursive `hitTest` function * - * @inner {boolean} + * @member {boolean} + * @memberof PIXI.Container# */ interactiveChildren: true, @@ -33,7 +43,12 @@ * Interaction shape. Children will be hit first, then this shape will be checked. * Setting this will cause this shape to be checked in hit tests rather than the displayObject's bounds. * - * @inner {PIXI.Rectangle|PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.RoundedRectangle} + * @example + * const sprite = new PIXI.Sprite(texture); + * sprite.interactive = true; + * sprite.hitArea = new PIXI.Rectangle(0, 0, 100, 100); + * @member {PIXI.Rectangle|PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.RoundedRectangle} + * @memberof PIXI.DisplayObject# */ hitArea: null, @@ -41,8 +56,12 @@ * If enabled, the mouse cursor use the pointer behavior when hovered over the displayObject if it is interactive * Setting this changes the 'cursor' property to `'pointer'`. * + * @example + * const sprite = new PIXI.Sprite(texture); + * sprite.interactive = true; + * sprite.buttonMode = true; * @member {boolean} - * @memberof PIXI.interaction.interactiveTarget# + * @memberof PIXI.DisplayObject# */ get buttonMode() { @@ -64,9 +83,14 @@ * This defines what cursor mode is used when the mouse cursor * is hovered over the displayObject. * + * @example + * const sprite = new PIXI.Sprite(texture); + * sprite.interactive = true; + * sprite.cursor = 'wait'; * @see https://developer.mozilla.org/en/docs/Web/CSS/cursor * - * @inner {string} + * @member {string} + * @memberof PIXI.DisplayObject# */ cursor: null, @@ -74,7 +98,7 @@ * Internal set of all active pointers, by identifier * * @member {Map} - * @memberof PIXI.interaction.interactiveTarget# + * @memberof PIXI.DisplayObject# * @private */ get trackedPointers() @@ -87,7 +111,8 @@ /** * Map of all tracked pointers, by identifier. Use trackedPointers to access. * - * @private {Map} + * @private + * @type {Map} */ _trackedPointers: undefined, }; diff --git a/src/loaders/index.js b/src/loaders/index.js index 5189793..daa1fc4 100644 --- a/src/loaders/index.js +++ b/src/loaders/index.js @@ -1,9 +1,22 @@ +import Application from '../core/Application'; +import Loader from './loader'; + /** + * This namespace contains APIs which extends the {@link https://github.com/englercj/resource-loader resource-loader} module + * for loading assets, data, and other resources dynamically. + * @example + * const loader = new PIXI.loaders.Loader(); + * loader.add('bunny', 'data/bunny.png') + * .add('spaceship', 'assets/spritesheet.json'); + * loader.load((loader, resources) => { + * // resources.bunny + * // resources.spaceship + * }); * @namespace PIXI.loaders */ -export { default as Loader } from './loader'; +export { Loader }; export { default as bitmapFontParser, parse as parseBitmapFontData } from './bitmapFontParser'; -export { default as spritesheetParser } from './spritesheetParser'; +export { default as spritesheetParser, getResourcePath } from './spritesheetParser'; export { default as textureParser } from './textureParser'; /** @@ -13,3 +26,55 @@ * @memberof PIXI.loaders */ export { Resource } from 'resource-loader'; + +/** + * A premade instance of the loader that can be used to load resources. + * @name shared + * @memberof PIXI.loaders + * @type {PIXI.loaders.Loader} + */ +const shared = new Loader(); + +shared.destroy = () => +{ + // protect destroying shared loader +}; + +export { shared }; + +// Mixin the loader construction +const AppPrototype = Application.prototype; + +AppPrototype._loader = null; + +/** + * Loader instance to help with asset loading. + * @name PIXI.Application#loader + * @type {PIXI.loaders.Loader} + */ +Object.defineProperty(AppPrototype, 'loader', { + get() + { + if (!this._loader) + { + const sharedLoader = this._options.sharedLoader; + + this._loader = sharedLoader ? shared : new Loader(); + } + + return this._loader; + }, +}); + +// Override the destroy function +// making sure to destroy the current Loader +AppPrototype._parentDestroy = AppPrototype.destroy; +AppPrototype.destroy = function destroy() +{ + if (this._loader) + { + this._loader.destroy(); + this._loader = null; + } + this._parentDestroy(); +}; diff --git a/src/loaders/loader.js b/src/loaders/loader.js index 767a411..59b8000 100644 --- a/src/loaders/loader.js +++ b/src/loaders/loader.js @@ -90,6 +90,15 @@ { Loader._pixiMiddleware.push(fn); } + + /** + * Destroy the loader, removes references. + */ + destroy() + { + this.removeAllListeners(); + this.reset(); + } } // Copy EE3 prototype (mixin) diff --git a/src/loaders/spritesheetParser.js b/src/loaders/spritesheetParser.js index e57dfb2..eda9584 100644 --- a/src/loaders/spritesheetParser.js +++ b/src/loaders/spritesheetParser.js @@ -1,12 +1,11 @@ import { Resource } from 'resource-loader'; -import path from 'path'; +import url from 'url'; import { Spritesheet } from '../core'; export default function () { return function spritesheetParser(resource, next) { - let resourcePath; const imageResourceName = `${resource.name}_image`; // skip if no data, its not json, it isn't spritesheet data, or the image resource already exists @@ -28,15 +27,7 @@ parentResource: resource, }; - // Prepend url path unless the resource image is a data url - if (resource.isDataUrl) - { - resourcePath = resource.data.meta.image; - } - else - { - resourcePath = `${path.dirname(resource.url.replace(this.baseUrl, ''))}/${resource.data.meta.image}`; - } + const resourcePath = getResourcePath(resource, this.baseUrl); // load the image for this sheet this.add(imageResourceName, resourcePath, loadOptions, function onImageLoad(res) @@ -56,3 +47,14 @@ }); }; } + +export function getResourcePath(resource, baseUrl) +{ + // Prepend url path unless the resource image is a data url + if (resource.isDataUrl) + { + return resource.data.meta.image; + } + + return url.resolve(resource.url.replace(baseUrl, ''), resource.data.meta.image); +} diff --git a/src/mesh/canvas/CanvasMeshRenderer.js b/src/mesh/canvas/CanvasMeshRenderer.js index 297205f..f7899e1 100644 --- a/src/mesh/canvas/CanvasMeshRenderer.js +++ b/src/mesh/canvas/CanvasMeshRenderer.js @@ -134,12 +134,33 @@ const textureWidth = base.width; const textureHeight = base.height; - const u0 = uvs[index0] * base.width; - const u1 = uvs[index1] * base.width; - const u2 = uvs[index2] * base.width; - const v0 = uvs[index0 + 1] * base.height; - const v1 = uvs[index1 + 1] * base.height; - const v2 = uvs[index2 + 1] * base.height; + let u0; + let u1; + let u2; + let v0; + let v1; + let v2; + + if (mesh.uploadUvTransform) + { + const ut = mesh._uvTransform.mapCoord; + + u0 = ((uvs[index0] * ut.a) + (uvs[index0 + 1] * ut.c) + ut.tx) * base.width; + u1 = ((uvs[index1] * ut.a) + (uvs[index1 + 1] * ut.c) + ut.tx) * base.width; + u2 = ((uvs[index2] * ut.a) + (uvs[index2 + 1] * ut.c) + ut.tx) * base.width; + v0 = ((uvs[index0] * ut.b) + (uvs[index0 + 1] * ut.d) + ut.ty) * base.height; + v1 = ((uvs[index1] * ut.b) + (uvs[index1 + 1] * ut.d) + ut.ty) * base.height; + v2 = ((uvs[index2] * ut.b) + (uvs[index2 + 1] * ut.d) + ut.ty) * base.height; + } + else + { + u0 = uvs[index0] * base.width; + u1 = uvs[index1] * base.width; + u2 = uvs[index2] * base.width; + v0 = uvs[index0 + 1] * base.height; + v1 = uvs[index1 + 1] * base.height; + v2 = uvs[index2 + 1] * base.height; + } let x0 = vertices[index0]; let x1 = vertices[index1]; diff --git a/src/mesh/webgl/MeshRenderer.js b/src/mesh/webgl/MeshRenderer.js index 78d24c3..ab99fcd 100644 --- a/src/mesh/webgl/MeshRenderer.js +++ b/src/mesh/webgl/MeshRenderer.js @@ -2,6 +2,8 @@ const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; +const matrixIdentity = core.Matrix.IDENTITY; + /** * WebGL renderer plugin for tiling sprites * @@ -64,6 +66,7 @@ mesh.geometry.glVertexArrayObjects[this.CONTEXT_UID].draw(mesh.drawMode, mesh.size, mesh.start, mesh.geometry.instanceCount); } + /** * draws mesh * @param {PIXI.mesh.RawMesh} mesh mesh instance diff --git a/src/mesh/webgl/mesh.vert b/src/mesh/webgl/mesh.vert index a337aef..acc096c 100644 --- a/src/mesh/webgl/mesh.vert +++ b/src/mesh/webgl/mesh.vert @@ -1,8 +1,9 @@ attribute vec2 aVertexPosition; attribute vec2 aTextureCoord; -uniform mat3 translationMatrix; uniform mat3 projectionMatrix; +uniform mat3 translationMatrix; +uniform mat3 uTransform; varying vec2 vTextureCoord; @@ -10,5 +11,5 @@ { gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - vTextureCoord = aTextureCoord; + vTextureCoord = (uTransform * vec3(aTextureCoord, 1.0)).xy; } diff --git a/src/particles/ParticleContainer.js b/src/particles/ParticleContainer.js index 67796cf..63f7791 100644 --- a/src/particles/ParticleContainer.js +++ b/src/particles/ParticleContainer.js @@ -313,10 +313,10 @@ frame.y * resolution, frame.width * resolution, frame.height * resolution, - positionX * resolution, - positionY * resolution, - finalWidth * resolution, - finalHeight * resolution + positionX * renderer.resolution, + positionY * renderer.resolution, + finalWidth * renderer.resolution, + finalHeight * renderer.resolution ); } } diff --git a/src/prepare/BasePrepare.js b/src/prepare/BasePrepare.js index 09eb5e5..e45c4dc 100644 --- a/src/prepare/BasePrepare.js +++ b/src/prepare/BasePrepare.js @@ -18,6 +18,18 @@ * basic queuing functionality and is extended by {@link PIXI.prepare.WebGLPrepare} and {@link PIXI.prepare.CanvasPrepare} * to provide preparation capabilities specific to their respective renderers. * + * @example + * // Create a sprite + * const sprite = new PIXI.Sprite.fromImage('something.png'); + * + * // Load object into GPU + * app.renderer.plugins.prepare.upload(sprite, () => { + * + * //Texture(s) has been uploaded to GPU + * app.stage.addChild(sprite); + * + * }) + * * @abstract * @class * @memberof PIXI.prepare @@ -100,8 +112,16 @@ this.prepareItems(); }; - this.register(findText, drawText); - this.register(findTextStyle, calculateTextStyle); + // hooks to find the correct texture + this.registerFindHook(findText); + this.registerFindHook(findTextStyle); + this.registerFindHook(findMultipleBaseTextures); + this.registerFindHook(findBaseTexture); + this.registerFindHook(findTexture); + + // upload hooks + this.registerUploadHook(drawText); + this.registerUploadHook(calculateTextStyle); } /** @@ -138,7 +158,7 @@ if (!this.ticking) { this.ticking = true; - SharedTicker.addOnce(this.tick, this); + SharedTicker.addOnce(this.tick, this, core.UPDATE_PRIORITY.UTILITY); } } else if (done) @@ -172,13 +192,16 @@ const item = this.queue[0]; let uploaded = false; - for (let i = 0, len = this.uploadHooks.length; i < len; i++) + if (item && !item._destroyed) { - if (this.uploadHooks[i](this.uploadHookHelper, item)) + for (let i = 0, len = this.uploadHooks.length; i < len; i++) { - this.queue.shift(); - uploaded = true; - break; + if (this.uploadHooks[i](this.uploadHookHelper, item)) + { + this.queue.shift(); + uploaded = true; + break; + } } } @@ -205,26 +228,36 @@ else { // if we are not finished, on the next rAF do this again - SharedTicker.addOnce(this.tick, this); + SharedTicker.addOnce(this.tick, this, core.UPDATE_PRIORITY.UTILITY); } } /** - * Adds hooks for finding and uploading items. + * Adds hooks for finding items. * - * @param {Function} [addHook] - Function call that takes two parameters: `item:*, queue:Array` - function must return `true` if it was able to add item to the queue. - * @param {Function} [uploadHook] - Function call that takes two parameters: `prepare:CanvasPrepare, item:*` and - * function must return `true` if it was able to handle upload of item. - * @return {PIXI.CanvasPrepare} Instance of plugin for chaining. + * @param {Function} addHook - Function call that takes two parameters: `item:*, queue:Array` + * function must return `true` if it was able to add item to the queue. + * @return {PIXI.BasePrepare} Instance of plugin for chaining. */ - register(addHook, uploadHook) + registerFindHook(addHook) { if (addHook) { this.addHooks.push(addHook); } + return this; + } + + /** + * Adds hooks for uploading items. + * + * @param {Function} uploadHook - Function call that takes two parameters: `prepare:CanvasPrepare, item:*` and + * function must return `true` if it was able to handle upload of item. + * @return {PIXI.BasePrepare} Instance of plugin for chaining. + */ + registerUploadHook(uploadHook) + { if (uploadHook) { this.uploadHooks.push(uploadHook); @@ -287,6 +320,88 @@ } /** + * Built-in hook to find multiple textures from objects like AnimatedSprites. + * + * @private + * @param {PIXI.DisplayObject} item - Display object to check + * @param {Array<*>} queue - Collection of items to upload + * @return {boolean} if a PIXI.Texture object was found. + */ +function findMultipleBaseTextures(item, queue) +{ + let result = false; + + // Objects with mutliple textures + if (item && item._textures && item._textures.length) + { + for (let i = 0; i < item._textures.length; i++) + { + if (item._textures[i] instanceof core.Texture) + { + const baseTexture = item._textures[i].baseTexture; + + if (queue.indexOf(baseTexture) === -1) + { + queue.push(baseTexture); + result = true; + } + } + } + } + + return result; +} + +/** + * Built-in hook to find BaseTextures from Sprites. + * + * @private + * @param {PIXI.DisplayObject} item - Display object to check + * @param {Array<*>} queue - Collection of items to upload + * @return {boolean} if a PIXI.Texture object was found. + */ +function findBaseTexture(item, queue) +{ + // Objects with textures, like Sprites/Text + if (item instanceof core.BaseTexture) + { + if (queue.indexOf(item) === -1) + { + queue.push(item); + } + + return true; + } + + return false; +} + +/** + * Built-in hook to find textures from objects. + * + * @private + * @param {PIXI.DisplayObject} item - Display object to check + * @param {Array<*>} queue - Collection of items to upload + * @return {boolean} if a PIXI.Texture object was found. + */ +function findTexture(item, queue) +{ + if (item._texture && item._texture instanceof core.Texture) + { + const texture = item._texture.baseTexture; + + if (queue.indexOf(texture) === -1) + { + queue.push(texture); + } + + return true; + } + + return false; +} + +/** * Built-in hook to draw PIXI.Text to its texture. * * @private diff --git a/src/prepare/canvas/CanvasPrepare.js b/src/prepare/canvas/CanvasPrepare.js index 0224bb5..09c207a 100644 --- a/src/prepare/canvas/CanvasPrepare.js +++ b/src/prepare/canvas/CanvasPrepare.js @@ -43,7 +43,7 @@ this.ctx = this.canvas.getContext('2d'); // Add textures to upload - this.register(findBaseTextures, uploadBaseTextures); + this.registerUploadHook(uploadBaseTextures); } /** @@ -89,39 +89,4 @@ return false; } -/** - * Built-in hook to find textures from Sprites. - * - * @private - * @param {PIXI.DisplayObject} item -Display object to check - * @param {Array<*>} queue - Collection of items to upload - * @return {boolean} if a PIXI.Texture object was found. - */ -function findBaseTextures(item, queue) -{ - // Objects with textures, like Sprites/Text - if (item instanceof core.BaseTexture) - { - if (queue.indexOf(item) === -1) - { - queue.push(item); - } - - return true; - } - else if (item._texture && item._texture instanceof core.Texture) - { - const texture = item._texture.baseTexture; - - if (queue.indexOf(texture) === -1) - { - queue.push(texture); - } - - return true; - } - - return false; -} - core.CanvasRenderer.registerPlugin('prepare', CanvasPrepare); diff --git a/src/prepare/index.js b/src/prepare/index.js index f559c45..e7491ec 100644 --- a/src/prepare/index.js +++ b/src/prepare/index.js @@ -1,4 +1,29 @@ /** + * The prepare namespace provides renderer-specific plugins for pre-rendering DisplayObjects. These plugins are useful for + * asynchronously preparing assets, textures, graphics waiting to be displayed. + * + * Do not instantiate these plugins directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. + * @example + * // Create a new application + * const app = new PIXI.Application(); + * document.body.appendChild(app.view); + * + * // Don't start rendering right away + * app.stop(); + * + * // create a display object + * const rect = new PIXI.Graphics() + * .beginFill(0x00ff00) + * .drawRect(40, 40, 200, 200); + * + * // Add to the stage + * app.stage.addChild(rect); + * + * // Don't start rendering until the graphic is uploaded to the GPU + * app.renderer.plugins.prepare.upload(app.stage, () => { + * app.start(); + * }); * @namespace PIXI.prepare */ export { default as webgl } from './webgl/WebGLPrepare'; diff --git a/src/prepare/webgl/WebGLPrepare.js b/src/prepare/webgl/WebGLPrepare.js index eb7023f..9df3c3f 100644 --- a/src/prepare/webgl/WebGLPrepare.js +++ b/src/prepare/webgl/WebGLPrepare.js @@ -22,12 +22,11 @@ this.uploadHookHelper = this.renderer; // Add textures and graphics to upload - this.register(findBaseTextures, uploadBaseTextures) - .register(findGraphics, uploadGraphics); + this.registerFindHook(findGraphics); + this.registerUploadHook(uploadBaseTextures); + this.registerUploadHook(uploadGraphics); } - } - /** * Built-in hook to upload PIXI.Texture objects to the GPU. * @@ -80,41 +79,6 @@ } /** - * Built-in hook to find textures from Sprites. - * - * @private - * @param {PIXI.DisplayObject} item - Display object to check - * @param {Array<*>} queue - Collection of items to upload - * @return {boolean} if a PIXI.Texture object was found. - */ -function findBaseTextures(item, queue) -{ - // Objects with textures, like Sprites/Text - if (item instanceof core.BaseTexture) - { - if (queue.indexOf(item) === -1) - { - queue.push(item); - } - - return true; - } - else if (item._texture && item._texture instanceof core.Texture) - { - const texture = item._texture.baseTexture; - - if (queue.indexOf(texture) === -1) - { - queue.push(texture); - } - - return true; - } - - return false; -} - -/** * Built-in hook to find graphics. * * @private diff --git a/test/.eslintrc.json b/test/.eslintrc.json index e03220d..ac12110 100644 --- a/test/.eslintrc.json +++ b/test/.eslintrc.json @@ -12,7 +12,8 @@ "sinon": false, "expect": false, "assert": false, - "PIXI": false + "PIXI": false, + "PointerEvent": true }, "rules": { "func-names": 0, diff --git a/test/core/BaseTexture.js b/test/core/BaseTexture.js index 8b93478..bb65068 100644 --- a/test/core/BaseTexture.js +++ b/test/core/BaseTexture.js @@ -14,4 +14,28 @@ expect(baseTexture.imageType).to.be.equals('png'); }); }); + + it('should remove Canvas BaseTexture from cache on destroy', function () + { + const canvas = document.createElement('canvas'); + const texture = PIXI.BaseTexture.fromCanvas(canvas); + const _pixiId = canvas._pixiId; + + expect(PIXI.utils.BaseTextureCache[_pixiId]).to.equal(texture); + texture.destroy(); + expect(PIXI.utils.BaseTextureCache[_pixiId]).to.equal(undefined); + }); + + it('should remove Image BaseTexture from cache on destroy', function () + { + const URL = 'foo.png'; + const NAME = 'bar'; + const image = new Image(); + + const texture = PIXI.Texture.fromLoader(image, URL, NAME); + + expect(PIXI.utils.BaseTextureCache[NAME]).to.equal(texture.baseTexture); + texture.destroy(true); + expect(PIXI.utils.BaseTextureCache[NAME]).to.equal(undefined); + }); }); diff --git a/test/core/Circle.js b/test/core/Circle.js index 183f98f..f621992 100644 --- a/test/core/Circle.js +++ b/test/core/Circle.js @@ -53,6 +53,10 @@ expect(circ1.contains(10, 16)).to.be.false; expect(circ1.contains(11, 15)).to.be.false; expect(circ1.contains(0, 0)).to.be.false; + + const circ2 = new PIXI.Circle(10, 10, 0); + + expect(circ2.contains(10, 10)).to.be.false; }); it('should return framing rectangle', function () diff --git a/test/core/Container.js b/test/core/Container.js index d11be33..cefde54 100644 --- a/test/core/Container.js +++ b/test/core/Container.js @@ -2,30 +2,36 @@ function testAddChild(fn) { - fn(function (container, obj) + return function () { - container.addChild(obj); - }); - fn(function (container, obj) - { - container.addChildAt(obj); - }); + fn(function (container, obj) + { + container.addChild(obj); + }); + fn(function (container, obj) + { + container.addChildAt(obj); + }); + }; } function testRemoveChild(fn) { - fn(function (container, obj) + return function () { - container.removeChild(obj); - }); - fn(function (container, obj) - { - container.removeChildAt(container.children.indexOf(obj)); - }); - fn(function (container, obj) - { - container.removeChildren(container.children.indexOf(obj), container.children.indexOf(obj) + 1); - }); + fn(function (container, obj) + { + container.removeChild(obj); + }); + fn(function (container, obj) + { + container.removeChildAt(container.children.indexOf(obj)); + }); + fn(function (container, obj) + { + container.removeChildren(container.children.indexOf(obj), container.children.indexOf(obj) + 1); + }); + }; } describe('PIXI.Container', function () diff --git a/test/core/Ellipse.js b/test/core/Ellipse.js index f2e8234..3f47442 100644 --- a/test/core/Ellipse.js +++ b/test/core/Ellipse.js @@ -56,6 +56,10 @@ expect(ellipse1.contains(10, 16)).to.be.false; expect(ellipse1.contains(11, 15)).to.be.false; expect(ellipse1.contains(0, 0)).to.be.false; + + const ellipse2 = new PIXI.Ellipse(10, 10, 0, 0); + + expect(ellipse2.contains(10, 10)).to.be.false; }); it('should return framing rectangle', function () diff --git a/test/core/Matrix.js b/test/core/Matrix.js index 9d2694b..76bd57c 100644 --- a/test/core/Matrix.js +++ b/test/core/Matrix.js @@ -107,10 +107,60 @@ expect(m1.ty).to.equal(m2.ty); }); + it('should prepend matrix', function () + { + const m1 = new PIXI.Matrix(); + const m2 = new PIXI.Matrix(); + + m2.set(2, 3, 4, 5, 100, 200); + m1.prepend(m2); + + expect(m1.a).to.equal(m2.a); + expect(m1.b).to.equal(m2.b); + expect(m1.c).to.equal(m2.c); + expect(m1.d).to.equal(m2.d); + expect(m1.tx).to.equal(m2.tx); + expect(m1.ty).to.equal(m2.ty); + + const m3 = new PIXI.Matrix(); + const m4 = new PIXI.Matrix(); + + m3.prepend(m4); + + expect(m3.a).to.equal(m4.a); + expect(m3.b).to.equal(m4.b); + expect(m3.c).to.equal(m4.c); + expect(m3.d).to.equal(m4.d); + expect(m3.tx).to.equal(m4.tx); + expect(m3.ty).to.equal(m4.ty); + }); + it('should get IDENTITY and TEMP_MATRIX', function () { expect(PIXI.Matrix.IDENTITY instanceof PIXI.Matrix).to.be.true; expect(PIXI.Matrix.TEMP_MATRIX instanceof PIXI.Matrix).to.be.true; }); -}); + it('should reset matrix to default when identity() is called', function () + { + const matrix = new PIXI.Matrix(); + + matrix.set(2, 3, 4, 5, 100, 200); + + expect(matrix.a).to.equal(2); + expect(matrix.b).to.equal(3); + expect(matrix.c).to.equal(4); + expect(matrix.d).to.equal(5); + expect(matrix.tx).to.equal(100); + expect(matrix.ty).to.equal(200); + + matrix.identity(); + + expect(matrix.a).to.equal(1); + expect(matrix.b).to.equal(0); + expect(matrix.c).to.equal(0); + expect(matrix.d).to.equal(1); + expect(matrix.tx).to.equal(0); + expect(matrix.ty).to.equal(0); + }); +}); diff --git a/test/core/Spritesheet.js b/test/core/Spritesheet.js index 283adf5..7601a0c 100644 --- a/test/core/Spritesheet.js +++ b/test/core/Spritesheet.js @@ -18,6 +18,7 @@ expect(textures[id]).to.be.an.instanceof(PIXI.Texture); expect(textures[id].width).to.equal(spritesheet.data.frames[id].frame.w / spritesheet.resolution); expect(textures[id].height).to.equal(spritesheet.data.frames[id].frame.h / spritesheet.resolution); + expect(textures[id].textureCacheId).to.equal(id); spritesheet.destroy(true); expect(spritesheet.textures).to.be.null; expect(spritesheet.baseTexture).to.be.null; diff --git a/test/core/Texture.js b/test/core/Texture.js index 8c4ef98..241ec3e 100644 --- a/test/core/Texture.js +++ b/test/core/Texture.js @@ -16,4 +16,20 @@ expect(PIXI.utils.TextureCache[URL]).to.equal(texture); expect(PIXI.utils.BaseTextureCache[URL]).to.equal(texture.baseTexture); }); + + it('should remove Texture from cache on destroy', function () + { + const NAME = 'foo'; + const NAME2 = 'bar'; + const texture = new PIXI.Texture(new PIXI.BaseTexture()); + + PIXI.Texture.addTextureToCache(texture, NAME); + PIXI.Texture.addTextureToCache(texture, NAME2); + expect(texture.textureCacheId).to.equal(NAME); + expect(PIXI.utils.TextureCache[NAME]).to.equal(texture); + expect(PIXI.utils.TextureCache[NAME2]).to.equal(texture); + texture.destroy(); + expect(PIXI.utils.TextureCache[NAME]).to.equal(undefined); + expect(PIXI.utils.TextureCache[NAME2]).to.equal(undefined); + }); }); diff --git a/test/core/Ticker.js b/test/core/Ticker.js new file mode 100644 index 0000000..188770f --- /dev/null +++ b/test/core/Ticker.js @@ -0,0 +1,322 @@ +'use strict'; + +const Ticker = PIXI.ticker.Ticker; +const shared = PIXI.ticker.shared; + +describe('PIXI.ticker.Ticker', function () +{ + before(function () + { + this.length = (ticker) => + { + ticker = ticker || shared; + + if (!ticker._head || !ticker._head.next) + { + return 0; + } + + let listener = ticker._head.next; + let i = 0; + + while (listener) + { + listener = listener.next; + i++; + } + + return i; + }; + }); + + it('should be available', function () + { + expect(Ticker).to.be.a.function; + expect(shared).to.be.an.instanceof(Ticker); + }); + + it('should create a new ticker and destroy it', function () + { + const ticker = new Ticker(); + + ticker.start(); + + const listener = sinon.spy(); + + expect(this.length(ticker)).to.equal(0); + + ticker.add(listener); + + expect(this.length(ticker)).to.equal(1); + + ticker.destroy(); + + expect(ticker._head).to.be.null; + expect(ticker.started).to.be.false; + expect(this.length(ticker)).to.equal(0); + }); + + it('should protect destroying shared ticker', function () + { + shared.destroy(); + expect(shared._head).to.not.be.null; + expect(shared.started).to.be.true; + }); + + it('should add and remove listener', function () + { + const listener = sinon.spy(); + const length = this.length(); + + shared.add(listener); + + expect(this.length()).to.equal(length + 1); + + shared.remove(listener); + + expect(this.length()).to.equal(length); + }); + + it('should update a listener', function () + { + const listener = sinon.spy(); + + shared.add(listener); + shared.update(); + + expect(listener.calledOnce).to.be.true; + + shared.remove(listener); + shared.update(); + + expect(listener.calledOnce).to.be.true; + }); + + it('should update a listener twice and remove once', function () + { + const listener = sinon.spy(); + const length = this.length(); + + shared.add(listener).add(listener); + shared.update(); + + expect(listener.calledTwice).to.be.true; + expect(this.length()).to.equal(length + 2); + + shared.remove(listener); + shared.update(); + + expect(listener.calledTwice).to.be.true; + expect(this.length()).to.equal(length); + }); + + it('should respect priority order', function () + { + const length = this.length(); + const listener1 = sinon.spy(); + const listener2 = sinon.spy(); + const listener3 = sinon.spy(); + const listener4 = sinon.spy(); + + shared.add(listener1, null, PIXI.UPDATE_PRIORITY.LOW) + .add(listener4, null, PIXI.UPDATE_PRIORITY.INTERACTION) + .add(listener3, null, PIXI.UPDATE_PRIORITY.HIGH) + .add(listener2, null, PIXI.UPDATE_PRIORITY.NORMAL); + + shared.update(); + + expect(this.length()).to.equal(length + 4); + + sinon.assert.callOrder(listener4, listener3, listener2, listener1); + + shared.remove(listener1) + .remove(listener2) + .remove(listener3) + .remove(listener4); + + expect(this.length()).to.equal(length); + }); + + it('should auto-remove once listeners', function () + { + const length = this.length(); + const listener = sinon.spy(); + + shared.addOnce(listener); + + shared.update(); + + expect(listener.calledOnce).to.be.true; + expect(this.length()).to.equal(length); + }); + + it('should call inserted item with a lower priority', function () + { + const length = this.length(); + const lowListener = sinon.spy(); + const highListener = sinon.spy(); + const mainListener = sinon.spy(() => + { + shared.add(highListener, null, PIXI.UPDATE_PRIORITY.HIGH); + shared.add(lowListener, null, PIXI.UPDATE_PRIORITY.LOW); + }); + + shared.add(mainListener, null, PIXI.UPDATE_PRIORITY.NORMAL); + + shared.update(); + + expect(this.length()).to.equal(length + 3); + + expect(mainListener.calledOnce).to.be.true; + expect(lowListener.calledOnce).to.be.true; + expect(highListener.calledOnce).to.be.false; + + shared.remove(mainListener) + .remove(highListener) + .remove(lowListener); + + expect(this.length()).to.equal(length); + }); + + it('should remove emit low-priority item during emit', function () + { + const length = this.length(); + const listener2 = sinon.spy(); + const listener1 = sinon.spy(() => + { + shared.add(listener2, null, PIXI.UPDATE_PRIORITY.LOW); + }); + + shared.add(listener1, null, PIXI.UPDATE_PRIORITY.NORMAL); + + shared.update(); + + expect(this.length()).to.equal(length + 2); + + expect(listener2.calledOnce).to.be.true; + expect(listener1.calledOnce).to.be.true; + + shared.remove(listener1) + .remove(listener2); + + expect(this.length()).to.equal(length); + }); + + it('should remove itself on emit after adding new item', function () + { + const length = this.length(); + const listener2 = sinon.spy(); + const listener1 = sinon.spy(() => + { + shared.add(listener2, null, PIXI.UPDATE_PRIORITY.LOW); + shared.remove(listener1); + + // listener is removed right away + expect(this.length()).to.equal(length + 1); + }); + + shared.add(listener1, null, PIXI.UPDATE_PRIORITY.NORMAL); + + shared.update(); + + expect(this.length()).to.equal(length + 1); + + expect(listener2.calledOnce).to.be.true; + expect(listener1.calledOnce).to.be.true; + + shared.remove(listener2); + + expect(this.length()).to.equal(length); + }); + + it('should remove itself before, still calling new item', function () + { + const length = this.length(); + const listener2 = sinon.spy(); + const listener1 = sinon.spy(() => + { + shared.remove(listener1); + shared.add(listener2, null, PIXI.UPDATE_PRIORITY.LOW); + + // listener is removed right away + expect(this.length()).to.equal(length + 1); + }); + + shared.add(listener1, null, PIXI.UPDATE_PRIORITY.NORMAL); + + shared.update(); + + expect(this.length()).to.equal(length + 1); + + expect(listener2.calledOnce).to.be.true; + expect(listener1.calledOnce).to.be.true; + + shared.remove(listener2); + + expect(this.length()).to.equal(length); + }); + + it('should remove items before and after current priority', function () + { + const length = this.length(); + const listener2 = sinon.spy(); + const listener3 = sinon.spy(); + const listener4 = sinon.spy(); + + shared.add(listener2, null, PIXI.UPDATE_PRIORITY.HIGH); + shared.add(listener3, null, PIXI.UPDATE_PRIORITY.LOW); + shared.add(listener4, null, PIXI.UPDATE_PRIORITY.LOW); + + const listener1 = sinon.spy(() => + { + shared.remove(listener2) + .remove(listener3); + + // listener is removed right away + expect(this.length()).to.equal(length + 2); + }); + + shared.add(listener1, null, PIXI.UPDATE_PRIORITY.NORMAL); + + shared.update(); + + expect(this.length()).to.equal(length + 2); + + expect(listener2.calledOnce).to.be.true; + expect(listener3.calledOnce).to.be.false; + expect(listener4.calledOnce).to.be.true; + expect(listener1.calledOnce).to.be.true; + + shared.update(); + + expect(listener2.calledOnce).to.be.true; + expect(listener3.calledOnce).to.be.false; + expect(listener4.calledTwice).to.be.true; + expect(listener1.calledTwice).to.be.true; + + shared.remove(listener1) + .remove(listener4); + + expect(this.length()).to.equal(length); + }); + + it('should destroy on listener', function (done) + { + const ticker = new Ticker(); + const listener2 = sinon.spy(); + const listener = sinon.spy(() => + { + ticker.destroy(); + setTimeout(() => + { + expect(listener2.called).to.be.false; + expect(listener.calledOnce).to.be.true; + done(); + }, 0); + }); + + ticker.add(listener); + ticker.add(listener2, null, PIXI.UPDATE_PRIORITY.LOW); + ticker.start(); + }); +}); diff --git a/test/core/TilingSprite.js b/test/core/TilingSprite.js index 2e449a7..7694792 100644 --- a/test/core/TilingSprite.js +++ b/test/core/TilingSprite.js @@ -24,4 +24,25 @@ expect(bounds.height).to.equal(600); }); }); + + it('checks if tilingSprite contains a point', function () + { + const texture = new PIXI.Texture(new PIXI.BaseTexture()); + const tilingSprite = new PIXI.extras.TilingSprite(texture, 200, 300); + + expect(tilingSprite.containsPoint(new PIXI.Point(1, 1))).to.equal(true); + expect(tilingSprite.containsPoint(new PIXI.Point(300, 400))).to.equal(false); + }); + + it('gets and sets height and width correctly', function () + { + const texture = new PIXI.Texture(new PIXI.BaseTexture()); + const tilingSprite = new PIXI.extras.TilingSprite(texture, 200, 300); + + tilingSprite.width = 400; + tilingSprite.height = 600; + + expect(tilingSprite.width).to.equal(400); + expect(tilingSprite.height).to.equal(600); + }); }); diff --git a/test/core/index.js b/test/core/index.js index 8530131..58dc30e 100755 --- a/test/core/index.js +++ b/test/core/index.js @@ -17,6 +17,7 @@ require('./util'); require('./Plane'); require('./Point'); +require('./Polygon'); require('./ObservablePoint'); require('./Matrix'); require('./Rectangle'); @@ -27,4 +28,5 @@ require('./WebGLRenderer'); require('./Ellipse'); require('./Texture'); +require('./Ticker'); require('./filters'); diff --git a/test/interaction/InteractionManager.js b/test/interaction/InteractionManager.js index 0e7e707..31385b9 100644 --- a/test/interaction/InteractionManager.js +++ b/test/interaction/InteractionManager.js @@ -4,6 +4,16 @@ describe('PIXI.interaction.InteractionManager', function () { + afterEach(function () + { + // if we made a MockPointer for the test, clean it up + if (this.pointer) + { + this.pointer.cleanUp(); + this.pointer = null; + } + }); + describe('event basics', function () { it('should call mousedown handler', function () @@ -11,7 +21,7 @@ const stage = new PIXI.Container(); const graphics = new PIXI.Graphics(); const eventSpy = sinon.spy(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); stage.addChild(graphics); graphics.beginFill(0xFFFFFF); @@ -29,7 +39,7 @@ const stage = new PIXI.Container(); const graphics = new PIXI.Graphics(); const eventSpy = sinon.spy(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); stage.addChild(graphics); graphics.beginFill(0xFFFFFF); @@ -47,7 +57,7 @@ const stage = new PIXI.Container(); const graphics = new PIXI.Graphics(); const eventSpy = sinon.spy(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); stage.addChild(graphics); graphics.beginFill(0xFFFFFF); @@ -66,7 +76,7 @@ const stage = new PIXI.Container(); const graphics = new PIXI.Graphics(); const eventSpy = sinon.spy(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); stage.addChild(graphics); graphics.beginFill(0xFFFFFF); @@ -84,7 +94,7 @@ const stage = new PIXI.Container(); const graphics = new PIXI.Graphics(); const eventSpy = sinon.spy(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); stage.addChild(graphics); graphics.beginFill(0xFFFFFF); @@ -286,7 +296,7 @@ const stage = new PIXI.Container(); const graphics = new PIXI.Graphics(); const clickSpy = sinon.spy(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); stage.addChild(graphics); graphics.beginFill(0xFFFFFF); @@ -304,7 +314,7 @@ const stage = new PIXI.Container(); const graphics = new PIXI.Graphics(); const clickSpy = sinon.spy(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); stage.addChild(graphics); graphics.beginFill(0xFFFFFF); @@ -316,6 +326,29 @@ expect(clickSpy).to.not.have.been.called; }); + + it('should not call handler when mousedown not received', function () + { + const stage = new PIXI.Container(); + const graphics = new PIXI.Graphics(); + const clickSpy = sinon.spy(); + const pointer = new MockPointer(stage); + + stage.addChild(graphics); + graphics.beginFill(0xFFFFFF); + graphics.drawRect(0, 0, 50, 50); + graphics.interactive = true; + graphics.on('click', clickSpy); + + pointer.mouseup(10, 10); + + expect(clickSpy, 'click should not happen on first mouseup').to.not.have.been.called; + + // test again, just because it was a bug that was reported + pointer.mouseup(20, 20); + + expect(clickSpy, 'click should not happen on second mouseup').to.not.have.been.called; + }); }); describe('onTap', function () @@ -325,7 +358,7 @@ const stage = new PIXI.Container(); const graphics = new PIXI.Graphics(); const clickSpy = sinon.spy(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); stage.addChild(graphics); graphics.beginFill(0xFFFFFF); @@ -343,7 +376,7 @@ const stage = new PIXI.Container(); const graphics = new PIXI.Graphics(); const clickSpy = sinon.spy(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); stage.addChild(graphics); graphics.beginFill(0xFFFFFF); @@ -359,32 +392,62 @@ describe('overlapping children', function () { - function getScene(callbackEventName) + function getScene(callbackEventName, splitParents) { const behindChild = new PIXI.Graphics(); const frontChild = new PIXI.Graphics(); const parent = new PIXI.Container(); - const behindChildCallback = sinon.spy(); - const frontChildCallback = sinon.spy(); - const parentCallback = sinon.spy(); + const behindChildCallback = sinon.spy(function behindSpy() { /* no op*/ }); + const frontChildCallback = sinon.spy(function frontSpy() { /* no op*/ }); + const parentCallback = sinon.spy(function parentSpy() { /* no op*/ }); + let behindParent; + let frontParent; + let behindParentCallback; + let frontParentCallback; behindChild.beginFill(0xFF); behindChild.drawRect(0, 0, 50, 50); behindChild.on(callbackEventName, behindChildCallback); + behindChild.name = 'behind'; frontChild.beginFill(0x00FF); frontChild.drawRect(0, 0, 50, 50); frontChild.on(callbackEventName, frontChildCallback); + frontChild.name = 'front'; - parent.addChild(behindChild, frontChild); + if (splitParents) + { + behindParent = new PIXI.Container(); + behindParent.name = 'behindParent'; + frontParent = new PIXI.Container(); + frontParent.name = 'frontParent'; + behindParentCallback = sinon.spy(function behindParentSpy() { /* no op*/ }); + frontParentCallback = sinon.spy(function frontParentSpy() { /* no op*/ }); + behindParent.on(callbackEventName, behindParentCallback); + frontParent.on(callbackEventName, frontParentCallback); + + parent.addChild(behindParent, frontParent); + behindParent.addChild(behindChild); + frontParent.addChild(frontChild); + + parent.name = 'parent'; + } + else + { + parent.addChild(behindChild, frontChild); + } parent.on(callbackEventName, parentCallback); return { behindChild, frontChild, + behindParent, + frontParent, parent, behindChildCallback, frontChildCallback, + behindParentCallback, + frontParentCallback, parentCallback, }; } @@ -396,7 +459,7 @@ it('should callback front child when clicking front child', function () { const stage = new PIXI.Container(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); const scene = getScene('click'); scene.behindChild.interactive = true; @@ -414,7 +477,7 @@ it('should callback front child when clicking overlap', function () { const stage = new PIXI.Container(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); const scene = getScene('click'); scene.behindChild.interactive = true; @@ -432,7 +495,7 @@ it('should callback behind child when clicking behind child', function () { const stage = new PIXI.Container(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); const scene = getScene('click'); scene.behindChild.interactive = true; @@ -446,6 +509,48 @@ expect(scene.behindChildCallback).to.have.been.calledOnce; expect(scene.parentCallback).to.not.have.been.called; }); + + it('should callback front child of different non-interactive parents when clicking overlap', function () + { + const stage = new PIXI.Container(); + const pointer = this.pointer = new MockPointer(stage); + const scene = getScene('click', true); + + scene.behindChild.interactive = true; + scene.behindChild.x = 25; + scene.frontChild.interactive = true; + + stage.addChild(scene.parent); + pointer.click(40, 10); + + expect(scene.behindChildCallback).to.not.have.been.called; + expect(scene.frontChildCallback).to.have.been.calledOnce; + expect(scene.parentCallback).to.not.have.been.called; + expect(scene.behindParentCallback).to.not.have.been.called; + expect(scene.frontParentCallback).to.not.have.been.called; + }); + + it('should callback front child of different interactive parents when clicking overlap', function () + { + const stage = new PIXI.Container(); + const pointer = this.pointer = new MockPointer(stage); + const scene = getScene('click', true); + + scene.behindChild.interactive = true; + scene.behindChild.x = 25; + scene.frontChild.interactive = true; + scene.behindParent.interactive = true; + scene.frontParent.interactive = true; + + stage.addChild(scene.parent); + pointer.click(40, 10); + + expect(scene.behindChildCallback).to.not.have.been.called; + expect(scene.frontChildCallback).to.have.been.calledOnce; + expect(scene.parentCallback).to.not.have.been.called; + expect(scene.behindParentCallback).to.not.have.been.called; + expect(scene.frontParentCallback).to.have.been.calledOnce; + }); }); describe('when front child is non-interactive', function () @@ -453,7 +558,7 @@ it('should not callback when clicking front child', function () { const stage = new PIXI.Container(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); const scene = getScene('click'); scene.behindChild.interactive = true; @@ -470,7 +575,7 @@ it('should callback behind child when clicking overlap', function () { const stage = new PIXI.Container(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); const scene = getScene('click'); scene.behindChild.interactive = true; @@ -487,7 +592,7 @@ it('should callback behind child when clicking behind child', function () { const stage = new PIXI.Container(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); const scene = getScene('click'); scene.behindChild.interactive = true; @@ -507,7 +612,7 @@ it('should callback front child when clicking front child', function () { const stage = new PIXI.Container(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); const scene = getScene('click'); scene.behindChild.x = 25; @@ -524,7 +629,7 @@ it('should callback front child when clicking overlap', function () { const stage = new PIXI.Container(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); const scene = getScene('click'); scene.behindChild.x = 25; @@ -541,7 +646,7 @@ it('should not callback when clicking behind child', function () { const stage = new PIXI.Container(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); const scene = getScene('click'); scene.behindChild.x = 25; @@ -564,7 +669,7 @@ it('should callback parent and front child when clicking front child', function () { const stage = new PIXI.Container(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); const scene = getScene('click'); scene.behindChild.interactive = true; @@ -583,7 +688,7 @@ it('should callback parent and front child when clicking overlap', function () { const stage = new PIXI.Container(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); const scene = getScene('click'); scene.behindChild.interactive = true; @@ -602,7 +707,7 @@ it('should callback parent and behind child when clicking behind child', function () { const stage = new PIXI.Container(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); const scene = getScene('click'); scene.behindChild.interactive = true; @@ -617,6 +722,50 @@ expect(scene.behindChildCallback).to.have.been.calledOnce; expect(scene.parentCallback).to.have.been.calledOnce; }); + + it('should callback front child of different non-interactive parents when clicking overlap', function () + { + const stage = new PIXI.Container(); + const pointer = this.pointer = new MockPointer(stage); + const scene = getScene('click', true); + + scene.behindChild.interactive = true; + scene.behindChild.x = 25; + scene.frontChild.interactive = true; + scene.parent.interactive = true; + + stage.addChild(scene.parent); + pointer.click(40, 10); + + expect(scene.behindChildCallback).to.not.have.been.called; + expect(scene.frontChildCallback).to.have.been.calledOnce; + expect(scene.parentCallback).to.have.been.calledOnce; + expect(scene.behindParentCallback).to.not.have.been.called; + expect(scene.frontParentCallback).to.not.have.been.called; + }); + + it('should callback front child of different interactive parents when clicking overlap', function () + { + const stage = new PIXI.Container(); + const pointer = this.pointer = new MockPointer(stage); + const scene = getScene('click', true); + + scene.behindChild.interactive = true; + scene.behindChild.x = 25; + scene.frontChild.interactive = true; + scene.parent.interactive = true; + scene.behindParent.interactive = true; + scene.frontParent.interactive = true; + + stage.addChild(scene.parent); + pointer.click(40, 10); + + expect(scene.behindChildCallback).to.not.have.been.called; + expect(scene.frontChildCallback).to.have.been.calledOnce; + expect(scene.parentCallback).to.have.been.calledOnce; + expect(scene.behindParentCallback).to.not.have.been.called; + expect(scene.frontParentCallback).to.have.been.calledOnce; + }); }); describe('when front child is non-interactive', function () @@ -624,7 +773,7 @@ it('should callback parent when clicking front child', function () { const stage = new PIXI.Container(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); const scene = getScene('click'); scene.behindChild.interactive = true; @@ -642,7 +791,7 @@ it('should callback parent and behind child when clicking overlap', function () { const stage = new PIXI.Container(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); const scene = getScene('click'); scene.behindChild.interactive = true; @@ -660,7 +809,7 @@ it('should callback parent and behind child when clicking behind child', function () { const stage = new PIXI.Container(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); const scene = getScene('click'); scene.behindChild.interactive = true; @@ -681,7 +830,7 @@ it('should callback parent and front child when clicking front child', function () { const stage = new PIXI.Container(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); const scene = getScene('click'); scene.behindChild.x = 25; @@ -699,7 +848,7 @@ it('should callback parent and front child when clicking overlap', function () { const stage = new PIXI.Container(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); const scene = getScene('click'); scene.behindChild.x = 25; @@ -717,7 +866,7 @@ it('should callback parent when clicking behind child', function () { const stage = new PIXI.Container(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); const scene = getScene('click'); scene.behindChild.x = 25; @@ -733,6 +882,42 @@ }); }); }); + + it('Semi-complicated nesting with overlap, should not call behind callback', function () + { + const stage = new PIXI.Container(); + const frontParent = new PIXI.Container(); + const frontChild = new PIXI.Graphics(); + const behindParent = new PIXI.Container(); + const subParent = new PIXI.Container(); + const behindChild = new PIXI.Graphics(); + const behindCallback = sinon.spy(function behindSpy() { /* no op*/ }); + const frontCallback = sinon.spy(function frontSpy() { /* no op*/ }); + + behindChild.beginFill(0xFF); + behindChild.drawRect(0, 0, 50, 50); + subParent.on('click', behindCallback); + + frontChild.beginFill(0x00FF); + frontChild.drawRect(0, 0, 50, 50); + frontParent.on('click', frontCallback); + const pointer = this.pointer = new MockPointer(stage); + + behindParent.x = 25; + subParent.interactive = true; + frontParent.interactive = true; + + behindParent.addChild(subParent); + subParent.addChild(behindChild); + stage.addChild(behindParent); + frontParent.addChild(frontChild); + stage.addChild(frontParent); + + pointer.click(40, 10); + + expect(behindCallback).to.not.have.been.called; + expect(frontCallback).to.have.been.calledOnce; + }); }); describe('cursor changes', function () @@ -741,7 +926,7 @@ { const stage = new PIXI.Container(); const graphics = new PIXI.Graphics(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); stage.addChild(graphics); graphics.beginFill(0xFFFFFF); @@ -759,7 +944,7 @@ { const stage = new PIXI.Container(); const graphics = new PIXI.Graphics(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); stage.addChild(graphics); graphics.beginFill(0xFFFFFF); @@ -778,7 +963,7 @@ { const stage = new PIXI.Container(); const graphics = new PIXI.Graphics(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); stage.addChild(graphics); graphics.beginFill(0xFFFFFF); @@ -797,7 +982,7 @@ { const stage = new PIXI.Container(); const graphics = new PIXI.Graphics(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); stage.addChild(graphics); graphics.beginFill(0xFFFFFF); @@ -818,7 +1003,7 @@ const graphics = new PIXI.Graphics(); const overSpy = sinon.spy(); const defaultSpy = sinon.spy(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); stage.addChild(graphics); graphics.beginFill(0xFFFFFF); @@ -840,7 +1025,7 @@ const stage = new PIXI.Container(); const graphics = new PIXI.Graphics(); const defaultSpy = sinon.spy(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); stage.addChild(graphics); graphics.beginFill(0xFFFFFF); @@ -859,7 +1044,7 @@ { const stage = new PIXI.Container(); const graphics = new PIXI.Graphics(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); stage.addChild(graphics); graphics.beginFill(0xFFFFFF); @@ -881,7 +1066,7 @@ { const stage = new PIXI.Container(); const graphics = new PIXI.Graphics(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); stage.addChild(graphics); graphics.beginFill(0xFFFFFF); @@ -932,7 +1117,7 @@ it('should stop hitTesting after first hit', function () { const scene = getScene(); - const pointer = new MockPointer(scene.stage); + const pointer = this.pointer = new MockPointer(scene.stage); const frontHitTest = sinon.spy(scene.frontChild, 'containsPoint'); const middleHitTest = sinon.spy(scene.middleChild, 'containsPoint'); const behindHitTest = sinon.spy(scene.behindChild, 'containsPoint'); @@ -954,7 +1139,7 @@ it('should stop hitTesting after first hit', function () { const scene = getScene(); - const pointer = new MockPointer(scene.stage); + const pointer = this.pointer = new MockPointer(scene.stage); const frontHitTest = sinon.spy(scene.frontChild, 'containsPoint'); const middleHitTest = sinon.spy(scene.middleChild, 'containsPoint'); const behindHitTest = sinon.spy(scene.behindChild, 'containsPoint'); @@ -971,4 +1156,127 @@ }); }); }); + + describe('pointer handling', function () + { + it('pointer event from mouse should use single mouse data', function () + { + const stage = new PIXI.Container(); + const graphics = new PIXI.Graphics(); + const pointer = this.pointer = new MockPointer(stage, 100, 100, true); + + stage.addChild(graphics); + graphics.beginFill(0xFFFFFF); + graphics.drawRect(0, 0, 50, 50); + graphics.interactive = true; + + pointer.mousemove(20, 10, true); + + expect(pointer.interaction.mouse.global.x).to.equal(20); + expect(pointer.interaction.mouse.global.y).to.equal(10); + }); + }); + + describe('data cleanup', function () + { + it('touchleave after touchout should not orphan data', 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, 42); + expect(pointer.interaction.activeInteractionData[42]).to.exist; + pointer.touchend(10, 10, 42); + expect(pointer.interaction.activeInteractionData[42]).to.be.undefined; + pointer.touchleave(10, 10, 42); + expect(pointer.interaction.activeInteractionData[42]).to.be.undefined; + }); + }); + + describe('hitTest()', function () + { + it('should return hit', 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.render(); + const hit = pointer.interaction.hitTest(new PIXI.Point(10, 10)); + + expect(hit).to.equal(graphics); + }); + + it('should return null if not hit', 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.render(); + const hit = pointer.interaction.hitTest(new PIXI.Point(60, 60)); + + expect(hit).to.be.null; + }); + + it('should return top thing that was hit', function () + { + const stage = new PIXI.Container(); + const graphics = new PIXI.Graphics(); + const behind = new PIXI.Graphics(); + const pointer = this.pointer = new MockPointer(stage); + + stage.addChild(behind); + stage.addChild(graphics); + graphics.beginFill(0xFFFFFF); + graphics.drawRect(0, 0, 50, 50); + graphics.interactive = true; + behind.beginFill(0xFFFFFF); + behind.drawRect(0, 0, 50, 50); + behind.interactive = true; + + pointer.render(); + const hit = pointer.interaction.hitTest(new PIXI.Point(10, 10)); + + expect(hit).to.equal(graphics); + }); + + it('should return hit when passing in root', function () + { + const stage = new PIXI.Container(); + const graphics = new PIXI.Graphics(); + const behind = new PIXI.Graphics(); + const pointer = this.pointer = new MockPointer(stage); + + stage.addChild(behind); + stage.addChild(graphics); + graphics.beginFill(0xFFFFFF); + graphics.drawRect(0, 0, 50, 50); + graphics.interactive = true; + behind.beginFill(0xFFFFFF); + behind.drawRect(0, 0, 50, 50); + behind.interactive = true; + + pointer.render(); + const hit = pointer.interaction.hitTest(new PIXI.Point(10, 10), behind); + + expect(hit).to.equal(behind); + }); + }); }); diff --git a/test/interaction/MockPointer.js b/test/interaction/MockPointer.js index 67878af..c9c6228 100644 --- a/test/interaction/MockPointer.js +++ b/test/interaction/MockPointer.js @@ -11,13 +11,43 @@ * @param {PIXI.Container} stage - The root of the scene tree * @param {number} [width=100] - Width of the renderer * @param {number} [height=100] - Height of the renderer + * @param {boolean} [ensurePointerEvents=false] - If we should make sure that PointerEvents are 'supported' */ - constructor(stage, width, height) + constructor(stage, width, height, ensurePointerEvents) { + // fake PointerEvent existing + if (ensurePointerEvents && !window.PointerEvent) + { + window.PointerEvent = class PointerEvent extends MouseEvent + { + //eslint-disable-next-line + constructor(type, opts) + { + super(type, opts); + this.pointerType = opts.pointerType; + } + }; + this.createdPointerEvent = true; + } + this.stage = stage; this.renderer = new PIXI.CanvasRenderer(width || 100, height || 100); this.renderer.sayHello = () => { /* empty */ }; this.interaction = this.renderer.plugins.interaction; + this.interaction.supportsTouchEvents = true; + PIXI.ticker.shared.remove(this.interaction.update, this.interaction); + } + + /** + * Cleans up after tests + */ + cleanup() + { + if (this.createdPointerEvent) + { + delete window.PointerEvent; + } + this.renderer.destroy(); } /** @@ -45,14 +75,29 @@ /** * @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) + mousemove(x, y, asPointer) { - const mouseEvent = new MouseEvent('mousemove', { - clientX: x, - clientY: y, - preventDefault: sinon.stub(), - }); + 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(), + }); + } this.setPosition(x, y); this.render(); @@ -97,9 +142,15 @@ */ mousedown(x, y) { + const mouseEvent = new MouseEvent('mousedown', { + clientX: x, + clientY: y, + preventDefault: sinon.stub(), + }); + this.setPosition(x, y); this.render(); - this.interaction.onMouseDown({ clientX: 0, clientY: 0, preventDefault: sinon.stub() }); + this.interaction.onPointerDown(mouseEvent); } /** @@ -108,47 +159,83 @@ */ mouseup(x, y) { - this.setPosition(x, y); - this.render(); - this.interaction.onMouseUp({ clientX: 0, clientY: 0, preventDefault: sinon.stub() }); - } - - /** - * @param {number} x - pointer x position - * @param {number} y - pointer y position - */ - tap(x, y) - { - this.touchstart(x, y); - this.touchend(x, y); - } - - /** - * @param {number} x - pointer x position - * @param {number} y - pointer y position - */ - touchstart(x, y) - { - this.setPosition(x, y); - this.render(); - this.interaction.onTouchStart({ + const mouseEvent = new MouseEvent('mouseup', { + clientX: x, + clientY: y, preventDefault: sinon.stub(), - changedTouches: [new Touch({ identifier: 0, target: this.renderer.view })], }); + + 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 */ - touchend(x, y) + tap(x, y, identifier) { + this.touchstart(x, y, identifier); + this.touchend(x, y, identifier); + } + + /** + * @param {number} x - pointer x position + * @param {number} y - pointer y position + * @param {number} [identifier] - pointer id + */ + touchstart(x, y, identifier) + { + const touchEvent = new TouchEvent('touchstart', { + preventDefault: sinon.stub(), + changedTouches: [ + new Touch({ identifier: identifier || 0, target: this.renderer.view }), + ], + }); + this.setPosition(x, y); this.render(); - this.interaction.onTouchEnd({ + this.interaction.onPointerDown(touchEvent); + } + + /** + * @param {number} x - pointer x position + * @param {number} y - pointer y position + * @param {number} [identifier] - pointer id + */ + touchend(x, y, identifier) + { + const touchEvent = new TouchEvent('touchend', { preventDefault: sinon.stub(), - changedTouches: [new Touch({ identifier: 0, target: this.renderer.view })], + changedTouches: [ + new Touch({ identifier: identifier || 0, target: 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 }), + ], + }); + + this.setPosition(x, y); + this.render(); + this.interaction.onPointerOut(touchEvent); } } diff --git a/.travis.yml b/.travis.yml index 3df8b22..14de334 100644 --- a/.travis.yml +++ b/.travis.yml @@ -110,3 +110,43 @@ on: all_branches: true condition: $TRAVIS_TAG + # Deploy config for latest release + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: dist + upload-dir: "release" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: docs + upload-dir: "release/docs" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: coverage + upload-dir: "release/coverage" + on: + all_branches: true + condition: $TRAVIS_TAG \ No newline at end of file diff --git a/README.md b/README.md index d26fadd..3b85f85 100644 --- a/README.md +++ b/README.md @@ -36,22 +36,14 @@ - Wiki: Other misc tutorials and resources are [on the Wiki](https://github.com/pixijs/pixi.js/wiki/Resources). ### Community ### -- Forums: Check out the [forum] (http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow] (http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. +- Forums: Check out the [forum](http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow](http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. - Inspiration: Check out the [gallery](http://www.pixijs.com/gallery) to see some of the amazing things people have created! - Chat: You can join us on [Gitter](https://gitter.im/pixijs/pixi.js) To chat about Pixi. We also now have a Slack channel. If you would like to join it please Send me an email (mat@goodboydigital.com) and I will invite you in. ### Setup ### -It's easy to get started with Pixi.js! Simply grab the pre-built versions from here: - -Release Branch - Nice and stable Pixi.js -- Unminified: [http://pixijs.download/release/pixi.js] -- Minified: [http://pixijs.download/release/pixi.min.js] - -Develop Branch - The bleeding edge version of Pixi.js -- Unminified: [http://pixijs.download/dev/pixi.js] -- Minified: [http://pixijs.download/dev/pixi.min.js] +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. @@ -70,10 +62,10 @@ #### CDN Install (via cdnjs) ```html - + ``` -_Note: `4.2.2` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ +_Note: `4.4.3` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ ### Demos ### @@ -130,7 +122,7 @@ // and the root stage PIXI.Container. var app = new PIXI.Application(); -// The application will create a canvas element for you that you +// The application will create a canvas element for you that you // can then insert into the DOM. document.body.appendChild(app.view); @@ -150,7 +142,7 @@ // Add the bunny to the scene we are building. app.stage.addChild(bunny); - + // Listen for frame updates app.ticker.add(function() { // each frame we spin the bunny around a bit diff --git a/bower.json b/bower.json deleted file mode 100644 index 41b668c..0000000 --- a/bower.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "pixi.js", - "main": "dist/pixi.min.js", - "ignore": [ - "**/.*", - "docs", - "node_modules", - "bower_components", - "test", - "scripts", - "bower.json", - "inch.json" - ], - "dependencies": { - }, - "devDependencies": { - } -} diff --git a/package.json b/package.json index d35cbb2..308469f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.3.5", + "version": "4.4.4", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ @@ -40,11 +40,11 @@ "lib": "babel src --out-dir lib -s", "predocs": "rimraf docs/**", "docs": "jsdoc -c scripts/jsdoc.conf.json -R README.md", - "publish:patch": "npm version patch --no-git-tag-version && npm publish", - "publish:minor": "npm version minor --no-git-tag-version && npm publish", - "publish:major": "npm version major --no-git-tag-version && npm publish", + "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", - "postpublish": "node scripts/release.js" + "postpublish": "git push && git push --tags" }, "files": [ "dist/", @@ -72,7 +72,6 @@ "electron": "^1.4.15", "eslint": "^3.5.0", "floss": "^2.0.1", - "gh-pages": "^0.11.0", "jaguarjs-jsdoc": "^1.0.1", "js-md5": "^0.4.1", "jsdoc": "^3.4.2", diff --git a/scripts/jsdoc.conf.json b/scripts/jsdoc.conf.json index 553e61c..3f52180 100644 --- a/scripts/jsdoc.conf.json +++ b/scripts/jsdoc.conf.json @@ -43,7 +43,7 @@ }, "markdown" : { "parser" : "gfm", - "hardwrap" : true + "hardwrap" : false }, "opts": { "encoding" : "utf8", diff --git a/scripts/release.js b/scripts/release.js deleted file mode 100644 index 156620c..0000000 --- a/scripts/release.js +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env node -'use strict'; - -// Publish script to push releases of the bin files -// the normally are gitignored -const ghpages = require('gh-pages'); -const path = require('path'); -const packageInfo = require(path.join(__dirname, '..', 'package.json')); - -const options = { - src: [ - 'dist/**/*', - 'lib/**/*', - 'src/**/*', - 'scripts/*', - 'scripts/renders/*', - 'test/**/*', - '*.json', - '*.md', - 'LICENSE', - '.eslintrc', - '.editorconfig', - '.travis.yml', - '.babelrc', - ], - dotfiles: true, - branch: 'release', - message: packageInfo.version, - logger: console.log.bind(console), -}; - -ghpages.publish(process.cwd(), options, (err) => -{ - if (err) - { - console.log(err); - process.exit(1); - - return; - } - - process.exit(0); -}); diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index f16bcb1..220427a 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -21,7 +21,7 @@ const DIV_HOOK_ZINDEX = 2; /** - * The Accessibility manager reacreates the ability to tab and and have content read by screen + * The Accessibility manager recreates the ability to tab and and have content read by screen * readers. This is very important as it can possibly help people with disabilities access pixi * content. * diff --git a/src/accessibility/index.js b/src/accessibility/index.js index 568cfb1..0ae108f 100644 --- a/src/accessibility/index.js +++ b/src/accessibility/index.js @@ -1,4 +1,9 @@ /** + * This namespace contains a renderer plugin for interaction accessibility for end-users + * with physical impairments which require screen-renders, keyboard navigation, etc. + * + * Do not instantiate this plugin directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. * @namespace PIXI.accessibility */ export { default as accessibleTarget } from './accessibleTarget'; diff --git a/src/core/Application.js b/src/core/Application.js index 5340578..2fcc729 100644 --- a/src/core/Application.js +++ b/src/core/Application.js @@ -1,6 +1,8 @@ import { autoDetectRenderer } from './autoDetectRenderer'; import Container from './display/Container'; import { shared, Ticker } from './ticker'; +import settings from './settings'; +import { UPDATE_PRIORITY } from './const'; /** * Convenience class to create a new PIXI application. @@ -22,28 +24,52 @@ */ export default class Application { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experience unexplained flickering try setting this to true. - * @param {boolean} [useSharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedLoader=false] - `true` to use PIXI.loaders.shared, `false` to create new Loader. */ - constructor(width, height, options, noWebGL, useSharedTicker = false) + constructor(options, arg2, arg3, arg4, arg5) { + // Support for constructor(width, height, options, noWebGL, useSharedTicker) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + forceCanvas: !!arg4, + sharedTicker: !!arg5, + }, arg3); + } + + /** + * The default options, so we mixin functionality later. + * @member {object} + * @protected + */ + this._options = options = Object.assign({ + sharedTicker: false, + forceCanvas: false, + sharedLoader: false, + }, options); + /** * WebGL renderer if available, otherwise CanvasRenderer * @member {PIXI.WebGLRenderer|PIXI.CanvasRenderer} */ - this.renderer = autoDetectRenderer(width, height, options, noWebGL); + this.renderer = autoDetectRenderer(options); /** * The root display container that's rendered. @@ -63,7 +89,7 @@ * @member {PIXI.ticker.Ticker} * @default PIXI.ticker.shared */ - this.ticker = useSharedTicker ? shared : new Ticker(); + this.ticker = options.sharedTicker ? shared : new Ticker(); // Start the rendering this.start(); @@ -78,7 +104,7 @@ this._ticker = ticker; if (ticker) { - ticker.add(this.render, this); + ticker.add(this.render, this, UPDATE_PRIORITY.LOW); } } get ticker() // eslint-disable-line require-jsdoc @@ -136,13 +162,18 @@ */ destroy(removeView) { - this.stop(); + const oldTicker = this._ticker; + this.ticker = null; + oldTicker.destroy(); + this.stage.destroy(); this.stage = null; this.renderer.destroy(removeView); this.renderer = null; + + this._options = null; } } diff --git a/src/core/autoDetectRenderer.js b/src/core/autoDetectRenderer.js index 8960425..9ec0364 100644 --- a/src/core/autoDetectRenderer.js +++ b/src/core/autoDetectRenderer.js @@ -2,6 +2,7 @@ import CanvasRenderer from './renderers/canvas/CanvasRenderer'; import WebGLRenderer from './renderers/webgl/WebGLRenderer'; +// eslint-disable-next-line valid-jsdoc /** * This helper function will automatically detect which renderer you should be using. * WebGL is the preferred renderer as it is a lot faster. If webGL is not supported by @@ -9,24 +10,32 @@ * * @memberof PIXI * @function autoDetectRenderer - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @return {PIXI.WebGLRenderer|PIXI.CanvasRenderer} Returns WebGL renderer if available, otherwise CanvasRenderer */ -export function autoDetectRenderer(width = 800, height = 600, options, noWebGL) +export function autoDetectRenderer(options, arg1, arg2, arg3) { - if (!noWebGL && utils.isWebGLSupported()) + // Backward-compatible support for noWebGL option + let forceCanvas = options && options.forceCanvas; + + if (arg3 !== undefined) { - return new WebGLRenderer(width, height, options); + forceCanvas = arg3; } - return new CanvasRenderer(width, height, options); + if (!forceCanvas && utils.isWebGLSupported()) + { + return new WebGLRenderer(options, arg1, arg2); + } + + return new CanvasRenderer(options, arg1, arg2); } diff --git a/src/core/const.js b/src/core/const.js index b9e99ae..2c68dee 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -341,3 +341,27 @@ LINEAR_VERTICAL: 0, LINEAR_HORIZONTAL: 1, }; + +/** + * Represents the update priorities used by internal PIXI classes when registered with + * the {@link PIXI.ticker.Ticker} object. Higher priority items are updated first and lower + * priority items, such as render, should go later. + * + * @static + * @constant + * @name UPDATE_PRIORITY + * @memberof PIXI + * @type {object} + * @property {number} INTERACTION=50 Highest priority, used for {@link PIXI.interaction.InteractionManager} + * @property {number} HIGH=25 High priority updating, {@link PIXI.VideoBaseTexture} and {@link PIXI.extras.AnimatedSprite} + * @property {number} NORMAL=0 Default priority for ticker events, see {@link PIXI.ticker.Ticker#add}. + * @property {number} LOW=-25 Low priority used for {@link PIXI.Application} rendering. + * @property {number} UTILITY=-50 Lowest priority used for {@link PIXI.prepare.BasePrepare} utility. + */ +export const UPDATE_PRIORITY = { + INTERACTION: 50, + HIGH: 25, + NORMAL: 0, + LOW: -25, + UTILITY: -50, +}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 4e6c77d..decb42a 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -13,7 +13,6 @@ * * @class * @extends EventEmitter - * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ export default class DisplayObject extends EventEmitter @@ -113,6 +112,29 @@ * @private */ this._mask = null; + + /** + * If the object has been destroyed via destroy(). If true, it should not be used. + * + * @member {boolean} + * @private + * @readonly + */ + this._destroyed = false; + + /** + * Fired when this DisplayObject is added to a Container. + * + * @event PIXI.DisplayObject#added + * @param {PIXI.Container} container - The container added to. + */ + + /** + * Fired when this DisplayObject is removed from a Container. + * + * @event PIXI.DisplayObject#removed + * @param {PIXI.Container} container - The container removed from. + */ } /** @@ -403,6 +425,8 @@ this.interactive = false; this.interactiveChildren = false; + + this._destroyed = true; } /** diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index aafdfe2..f787c4a 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -792,8 +792,8 @@ sprite.worldAlpha = this.worldAlpha * sprite.alpha; sprite.blendMode = this.blendMode; - sprite.texture._frame.width = rect.width; - sprite.texture._frame.height = rect.height; + sprite._texture._frame.width = rect.width; + sprite._texture._frame.height = rect.height; sprite.transform.worldTransform = this.transform.worldTransform; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index a24c775..b9f192d 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -19,11 +19,12 @@ */ export default class SystemRenderer extends EventEmitter { + // eslint-disable-next-line valid-jsdoc /** * @param {string} system - The name of the system this renderer is for. - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -37,27 +38,31 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(system, screenWidth, screenHeight, options) + constructor(system, options, arg2, arg3) { super(); sayHello(system); - // prepare options - if (options) + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') { - for (const i in settings.RENDER_OPTIONS) - { - if (typeof options[i] === 'undefined') - { - options[i] = settings.RENDER_OPTIONS[i]; - } - } + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); } - else - { - options = settings.RENDER_OPTIONS; - } + + // Add the default render options + options = Object.assign({}, settings.RENDER_OPTIONS, options); + + /** + * The supplied constructor options. + * + * @member {Object} + * @readOnly + */ + this.options = options; /** * The type of the renderer. @@ -75,7 +80,7 @@ * * @member {PIXI.Rectangle} */ - this.screen = new Rectangle(0, 0, screenWidth || 800, screenHeight || 600); + this.screen = new Rectangle(0, 0, options.width, options.height); /** * The canvas element that everything is drawn to @@ -279,6 +284,8 @@ this.blendModes = null; + this.options = null; + this.preserveDrawingBuffer = false; this.clearBeforeRender = false; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index e84e49c..691caf0 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -17,10 +17,11 @@ */ export default class CanvasRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -34,9 +35,9 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('Canvas', screenWidth, screenHeight, options); + super('Canvas', options, arg2, arg3); this.type = RENDERER_TYPE.CANVAS; @@ -96,7 +97,19 @@ this.context = null; this.renderingToScreen = false; - this.resize(screenWidth, screenHeight); + this.resize(this.options.width, this.options.height); + + /** + * Fired after rendering finishes. + * + * @event PIXI.CanvasRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.CanvasRenderer#prerender + */ } /** @@ -299,4 +312,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.CanvasRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.CanvasExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.CanvasPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.CanvasRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index e1cf7cd..f6fe123 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -31,11 +31,12 @@ */ export default class WebGLRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** * - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -55,9 +56,9 @@ * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('WebGL', screenWidth, screenHeight, options); + super('WebGL', options, arg2, arg3); /** * The type of this renderer as a standardised const @@ -89,12 +90,6 @@ }; - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ this._backgroundColorRgba[3] = this.transparent ? 0 : 1; this.globalUniforms = new UniformGroup({ @@ -117,6 +112,13 @@ this.initPlugins(); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ if(options.context) { this.context.initFromContext(options.context); @@ -169,6 +171,25 @@ } return this; + + /** + * Fired after rendering finishes. + * + * @event PIXI.WebGLRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.WebGLRenderer#prerender + */ + + /** + * Fired when the WebGL context is set. + * + * @event PIXI.WebGLRenderer#context + * @param {WebGLRenderingContext} gl - WebGL context. + */ } /** @@ -295,4 +316,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.WebGLRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.WebGLRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(WebGLRenderer); diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index 4eb6128..fa3d883 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -47,6 +47,14 @@ * @member {boolean} */ this.enabled = true; + + /** + * If enabled, pixi will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; } /** diff --git a/src/core/renderers/webgl/systems/ContextSystem.js b/src/core/renderers/webgl/systems/ContextSystem.js index ff958c7..8df85ea 100644 --- a/src/core/renderers/webgl/systems/ContextSystem.js +++ b/src/core/renderers/webgl/systems/ContextSystem.js @@ -75,7 +75,8 @@ */ createContext(canvas, options) { - var gl = canvas.getContext('webgl', options) || + var gl = //canvas.getContext('webgl2', options) || + canvas.getContext('webgl', options) || canvas.getContext('experimental-webgl', options); if (!gl) diff --git a/src/core/renderers/webgl/systems/FilterSystem.js b/src/core/renderers/webgl/systems/FilterSystem.js index f47335d..37e28d2 100644 --- a/src/core/renderers/webgl/systems/FilterSystem.js +++ b/src/core/renderers/webgl/systems/FilterSystem.js @@ -111,7 +111,7 @@ // TODO we should fit the rect around the transform.. } - else + else if (filters[0].autoFit) { sourceFrame.fit(filterData.stack[0].destinationFrame); } diff --git a/src/core/renderers/webgl/systems/shader/shader/mapType.js b/src/core/renderers/webgl/systems/shader/shader/mapType.js index 581dace..917538a 100644 --- a/src/core/renderers/webgl/systems/shader/shader/mapType.js +++ b/src/core/renderers/webgl/systems/shader/shader/mapType.js @@ -15,7 +15,7 @@ } } - return GL_TABLE[type]; + return GL_TABLE[type]; }; var GL_TABLE = null; diff --git a/src/core/settings.js b/src/core/settings.js index 18c5877..38ef45c 100644 --- a/src/core/settings.js +++ b/src/core/settings.js @@ -2,6 +2,15 @@ import canUploadSameBuffer from './utils/canUploadSameBuffer'; /** + * User's customizable globals for overriding the default PIXI settings, such + * as a renderer's default resolution, framerate, float percision, etc. + * @example + * // Use the native window resolution as the default resolution + * // will support high-density displays when rendering + * PIXI.settings.RESOLUTION = window.devicePixelRatio. + * + * // Disable interpolation when scaling, will make texture be pixelated + * PIXI.settings.SCALE_MODE = PIXI.SCALE_MODES.NEAREST; * @namespace PIXI.settings */ export default { @@ -101,6 +110,9 @@ * @property {boolean} clearBeforeRender=true * @property {boolean} preserveDrawingBuffer=false * @property {boolean} roundPixels=false + * @property {number} width=800 + * @property {number} height=600 + * @property {boolean} legacy=false */ RENDER_OPTIONS: { view: null, @@ -112,6 +124,9 @@ clearBeforeRender: true, preserveDrawingBuffer: false, roundPixels: false, + width: 800, + height: 600, + legacy: false, }, /** diff --git a/src/core/shader/Program.js b/src/core/shader/Program.js index c0b538b..07b4398 100644 --- a/src/core/shader/Program.js +++ b/src/core/shader/Program.js @@ -1,6 +1,6 @@ import extractUniformsFromSrc from './extractUniformsFromSrc'; import generateUniformsSync from './generateUniformsSync'; -import glCore from 'pixi-gl-core'; +import shaderUtils from '../renderers/webgl/systems/shader/shader'; import { ProgramCache } from '../utils'; import getTestContext from '../utils/getTestContext'; @@ -64,14 +64,17 @@ } else { - vertexSrc = glCore.shader.setPrecision(vertexSrc, 'mediump'); - fragmentSrc = glCore.shader.setPrecision(fragmentSrc, 'mediump'); + vertexSrc = shaderUtils.setPrecision(vertexSrc, 'mediump'); + fragmentSrc = shaderUtils.setPrecision(fragmentSrc, 'mediump'); - const program = glCore.shader.compileProgram(gl, vertexSrc, fragmentSrc); + const program = shaderUtils.compileProgram(gl, vertexSrc, fragmentSrc); + console.log("<<<<<<>>><<"); this.attributeData = this.getAttributeData(program, gl); this.uniformData = this.getUniformData(program, gl); + console.log(this.uniformData); + //gl.deleteProgram(program); } } @@ -95,13 +98,13 @@ for (let i = 0; i < totalAttributes; i++) { const attribData = gl.getActiveAttrib(program, i); - const type = glCore.shader.mapType(gl, attribData.type); + const type = shaderUtils.mapType(gl, attribData.type); /*eslint-disable */ const data = { type: type, name: attribData.name, - size: glCore.shader.mapSize(type), + size: shaderUtils.mapSize(type), location: 0, }; /*eslint-enable */ @@ -137,13 +140,14 @@ // TODO expose this as a prop? // const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); - const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); + //const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); for (let i = 0; i < totalUniforms; i++) { const uniformData = gl.getActiveUniform(program, i); const name = uniformData.name.replace(/\[.*?\]/, ''); - const type = glCore.shader.mapType(gl, uniformData.type); + const type = shaderUtils.mapType(gl, uniformData.type); + console.log('mapping ' + uniformData.type + ' to ' + type); // if (!name.match(maskRegex)) { @@ -151,7 +155,7 @@ uniforms[name] = { type: type, size: uniformData.size, - value: glCore.shader.defaultValue(type, uniformData.size), + value: shaderUtils.defaultValue(type, uniformData.size), }; /*eslint-enable */ } diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index e387e99..b57d095 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -147,12 +147,12 @@ // so if _width is 0 then width was not set.. if (this._width) { - this.scale.x = sign(this.scale.x) * this._width / this.texture.orig.width; + this.scale.x = sign(this.scale.x) * this._width / this._texture.orig.width; } if (this._height) { - this.scale.y = sign(this.scale.y) * this._height / this.texture.orig.height; + this.scale.y = sign(this.scale.y) * this._height / this._texture.orig.height; } } diff --git a/src/core/sprites/canvas/CanvasTinter.js b/src/core/sprites/canvas/CanvasTinter.js index 27e5e0c..fd9de5d 100644 --- a/src/core/sprites/canvas/CanvasTinter.js +++ b/src/core/sprites/canvas/CanvasTinter.js @@ -18,7 +18,7 @@ */ getTintedTexture: (sprite, color) => { - const texture = sprite.texture; + const texture = sprite._texture; color = CanvasTinter.roundColor(color); diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 5a4edda..8b9b797 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -344,6 +344,7 @@ const texture = this._texture; const style = this._style; + const padding = style.trim ? 0 : style.padding; texture.baseTexture.valid = true; texture.baseTexture.resolution = this.resolution; @@ -353,11 +354,11 @@ texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - texture.trim.x = -style.padding; - texture.trim.y = -style.padding; + texture.trim.x = -padding; + texture.trim.y = -padding; - texture.orig.width = texture._frame.width - (style.padding * 2); - texture.orig.height = texture._frame.height - (style.padding * 2); + texture.orig.width = texture._frame.width - (padding * 2); + texture.orig.height = texture._frame.height - (padding * 2); // call sprite onTextureUpdate to update scale if _width or _height were set this._onTextureUpdate(); diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index 853da3d..8a9c34b 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -114,6 +114,38 @@ this.cacheId = null; this.validate(); + + /** + * Fired when a not-immediately-available source finishes loading. + * + * @protected + * @event PIXI.BaseTexture#loaded + * @param {PIXI.BaseTexture} baseTexture - Resource loaded. + */ + + /** + * Fired when a not-immediately-available source fails to load. + * + * @protected + * @event PIXI.BaseTexture#error + * @param {PIXI.BaseTexture} baseTexture - Resource errored. + */ + + /** + * Fired when BaseTexture is updated. + * + * @protected + * @event PIXI.BaseTexture#update + * @param {PIXI.BaseTexture} baseTexture - Instance of texture being updated. + */ + + /** + * Fired when BaseTexture is destroyed. + * + * @protected + * @event PIXI.BaseTexture#dispose + * @param {PIXI.BaseTexture} baseTexture - Instance of texture being destroyed. + */ } setResource(resource) diff --git a/src/core/textures/Spritesheet.js b/src/core/textures/Spritesheet.js index d469c62..9b24f27 100644 --- a/src/core/textures/Spritesheet.js +++ b/src/core/textures/Spritesheet.js @@ -208,6 +208,7 @@ // lets also add the frame to pixi's global cache for fromFrame and fromImage functions TextureCache[i] = this.textures[i]; + this.textures[i].textureCacheId = i; } frameIndex++; diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 0a2c379..2213087 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -147,9 +147,9 @@ /** * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. * - * @event update - * @memberof PIXI.Texture# + * @event PIXI.Texture#update * @protected + * @param {PIXI.Texture} texture - Instance of texture being updated. */ this._updateID = 0; @@ -159,6 +159,15 @@ * @type {Object} */ this.transform = null; + + /** + * The id under which this Texture has been added to the texture cache. This is + * automatically set in certain cases, but may not always be accurate, particularly if + * the texture is in the cache under multiple ids. + * + * @member {string} + */ + this.textureCacheId = null; } /** @@ -225,11 +234,17 @@ this._uvs = null; this.trim = null; this.orig = null; + this.textureCacheId = null; this.valid = false; - this.off('dispose', this.dispose, this); - this.off('update', this.update, this); + for (const prop in TextureCache) + { + if (TextureCache[prop] === this) + { + delete TextureCache[prop]; + } + } } /** @@ -315,14 +330,15 @@ */ static fromLoader(source, imageUrl, name) { - // console.log('added from loader...') + // console.log('added from loader...') const resource = new ImageResource(source);//.from(imageUrl, crossorigin);// document.createElement('img'); - // console.log('base resource ' + resource.width); + // console.log('base resource ' + resource.width); const baseTexture = new BaseTexture(resource, settings.SCALE_MODE, getResolutionOfUrl(imageUrl)); + /// console.log('base width ' + baseTexture.width); const texture = new Texture(baseTexture); @@ -335,6 +351,7 @@ // lets also add the frame to pixi's global cache for fromFrame and fromImage fucntions BaseTextureCache[name] = baseTexture; TextureCache[name] = texture; + texture.textureCacheId = name; // also add references by url if they are different. if (name !== imageUrl) @@ -355,6 +372,10 @@ */ static addTextureToCache(texture, id) { + if (!texture.textureCacheId) + { + texture.textureCacheId = id; + } TextureCache[id] = texture; } @@ -495,6 +516,7 @@ */ Texture.EMPTY = new Texture(new BaseTexture()); removeAllHandlers(Texture.EMPTY); +removeAllHandlers(Texture.EMPTY.baseTexture); /** * A white texture of 10x10 size, used for graphics and other things @@ -505,3 +527,4 @@ */ Texture.WHITE = createWhiteTexture(); removeAllHandlers(Texture.WHITE); +removeAllHandlers(Texture.WHITE.baseTexture); diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index 59eb9fd..a9b9f51 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -1,6 +1,7 @@ import BaseTexture from './BaseTexture'; import { uid, BaseTextureCache } from '../utils'; -import * as ticker from '../ticker'; +import { shared } from '../ticker'; +import { UPDATE_PRIORITY } from '../const'; /** * A texture of a [playing] Video. @@ -125,7 +126,7 @@ if (!this._isAutoUpdating && this.autoUpdate) { - ticker.shared.add(this.update, this); + shared.add(this.update, this, UPDATE_PRIORITY.HIGH); this._isAutoUpdating = true; } } @@ -139,7 +140,7 @@ { if (this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); this._isAutoUpdating = false; } } @@ -187,7 +188,7 @@ { if (this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); } if (this.source && this.source._pixiId) @@ -281,12 +282,12 @@ if (!this._autoUpdate && this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); this._isAutoUpdating = false; } else if (this._autoUpdate && !this._isAutoUpdating) { - ticker.shared.add(this.update, this); + shared.add(this.update, this, UPDATE_PRIORITY.HIGH); this._isAutoUpdating = true; } } diff --git a/src/core/ticker/Ticker.js b/src/core/ticker/Ticker.js index 17a1517..40df138 100644 --- a/src/core/ticker/Ticker.js +++ b/src/core/ticker/Ticker.js @@ -1,12 +1,10 @@ import settings from '../settings'; -import EventEmitter from 'eventemitter3'; - -// Internal event used by composed emitter -const TICK = 'tick'; +import { UPDATE_PRIORITY } from '../const'; +import TickerListener from './TickerListener'; /** * A Ticker class that runs an update loop that other objects listen to. - * This class is composed around an EventEmitter object to add listeners + * This class is composed around listeners * meant for execution on the next requested animation frame. * Animation frames are requested only when necessary, * e.g. When the ticker is started and the emitter has listeners. @@ -22,10 +20,11 @@ constructor() { /** - * Internal emitter used to fire 'tick' event + * The first listener. All new listeners added are chained on this. * @private + * @type {TickerListener} */ - this._emitter = new EventEmitter(); + this._head = new TickerListener(null, null, Infinity); /** * Internal current frame request ID @@ -131,7 +130,7 @@ // Invoke listeners now this.update(time); // Listener side effects may have modified ticker state. - if (this.started && this._requestId === null && this._emitter.listeners(TICK, true)) + if (this.started && this._requestId === null && this._head.next) { this._requestId = requestAnimationFrame(this._tick); } @@ -148,7 +147,7 @@ */ _requestIfNeeded() { - if (this._requestId === null && this._emitter.listeners(TICK, true)) + if (this._requestId === null && this._head.next) { // ensure callbacks get correct delta this.lastTime = performance.now(); @@ -193,35 +192,72 @@ } /** - * Calls {@link module:eventemitter3.EventEmitter#on} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. + * Register a handler for tick events. Calls continuously unless + * it is removed or the ticker is stopped. * * @param {Function} fn - The listener function to be added for updates * @param {Function} [context] - The listener context + * @param {number} [priority=PIXI.UPDATE_PRIORITY.NORMAL] - The priority for emitting * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - add(fn, context) + add(fn, context, priority = UPDATE_PRIORITY.NORMAL) { - this._emitter.on(TICK, fn, context); - - this._startIfPossible(); - - return this; + return this._addListener(new TickerListener(fn, context, priority)); } /** - * Calls {@link module:eventemitter3.EventEmitter#once} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. + * Add a handler for the tick event which is only execute once. * * @param {Function} fn - The listener function to be added for one update * @param {Function} [context] - The listener context + * @param {number} [priority=PIXI.UPDATE_PRIORITY.NORMAL] - The priority for emitting * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - addOnce(fn, context) + addOnce(fn, context, priority = UPDATE_PRIORITY.NORMAL) { - this._emitter.once(TICK, fn, context); + return this._addListener(new TickerListener(fn, context, priority, true)); + } + + /** + * Internally adds the event handler so that it can be sorted by priority. + * Priority allows certain handler (user, AnimatedSprite, Interaction) to be run + * before the rendering. + * + * @private + * @param {TickerListener} listener - Current listener being added. + * @returns {PIXI.ticker.Ticker} This instance of a ticker + */ + _addListener(listener) + { + // For attaching to head + let current = this._head.next; + let previous = this._head; + + // Add the first item + if (!current) + { + listener.connect(previous); + } + else + { + // Go from highest to lowest priority + while (current) + { + if (listener.priority >= current.priority) + { + listener.connect(previous); + break; + } + previous = current; + current = current.next; + } + + // Not yet connected + if (!listener.previous) + { + listener.connect(previous); + } + } this._startIfPossible(); @@ -229,19 +265,33 @@ } /** - * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. - * It checks if the emitter has listeners for 'tick' event. - * If it does, then it cancels the animation frame. + * Removes any handlers matching the function and context parameters. + * If no handlers are left after removing, then it cancels the animation frame. * - * @param {Function} [fn] - The listener function to be removed + * @param {Function} fn - The listener function to be removed * @param {Function} [context] - The listener context to be removed * @returns {PIXI.ticker.Ticker} This instance of a ticker */ remove(fn, context) { - this._emitter.off(TICK, fn, context); + let listener = this._head.next; - if (!this._emitter.listeners(TICK, true)) + while (listener) + { + // We found a match, lets remove it + // no break to delete all possible matches + // incase a listener was added 2+ times + if (listener.match(fn, context)) + { + listener = listener.destroy(); + } + else + { + listener = listener.next; + } + } + + if (!this._head.next) { this._cancelIfNeeded(); } @@ -276,6 +326,25 @@ } /** + * Destroy the ticker and don't use after this. Calling + * this method removes all references to internal events. + */ + destroy() + { + this.stop(); + + let listener = this._head.next; + + while (listener) + { + listener = listener.destroy(true); + } + + this._head.destroy(); + this._head = null; + } + + /** * Triggers an update. An update entails setting the * current {@link PIXI.ticker.Ticker#elapsedMS}, * the current {@link PIXI.ticker.Ticker#deltaTime}, @@ -320,8 +389,22 @@ this.deltaTime = elapsedMS * settings.TARGET_FPMS * this.speed; + // Cache a local reference, in-case ticker is destroyed + // during the emit, we can still check for head.next + const head = this._head; + // Invoke listeners added to internal emitter - this._emitter.emit(TICK, this.deltaTime); + let listener = head.next; + + while (listener) + { + listener = listener.emit(this.deltaTime); + } + + if (!head.next) + { + this._cancelIfNeeded(); + } } else { diff --git a/src/core/ticker/TickerListener.js b/src/core/ticker/TickerListener.js new file mode 100644 index 0000000..2bedb34 --- /dev/null +++ b/src/core/ticker/TickerListener.js @@ -0,0 +1,158 @@ +/** + * Internal class for handling the priority sorting of ticker handlers. + * + * @private + * @class + * @memberof PIXI.ticker + */ +export default class TickerListener +{ + /** + * Constructor + * + * @param {Function} fn - The listener function to be added for one update + * @param {Function} [context=null] - The listener context + * @param {number} [priority=0] - The priority for emitting + * @param {boolean} [once=false] - If the handler should fire once + */ + constructor(fn, context = null, priority = 0, once = false) + { + /** + * The handler function to execute. + * @member {Function} + */ + this.fn = fn; + + /** + * The calling to execute. + * @member {Function} + */ + this.context = context; + + /** + * The current priority. + * @member {number} + */ + this.priority = priority; + + /** + * If this should only execute once. + * @member {boolean} + */ + this.once = once; + + /** + * The next item in chain. + * @member {TickerListener} + */ + this.next = null; + + /** + * The previous item in chain. + * @member {TickerListener} + */ + this.previous = null; + + /** + * `true` if this listener has been destroyed already. + * @member {boolean} + * @private + */ + this._destroyed = false; + } + + /** + * Simple compare function to figure out if a function and context match. + * + * @param {Function} fn - The listener function to be added for one update + * @param {Function} context - The listener context + * @return {boolean} `true` if the listener match the arguments + */ + match(fn, context) + { + context = context || null; + + return this.fn === fn && this.context === context; + } + + /** + * Emit by calling the current function. + * @param {number} deltaTime - time since the last emit. + * @return {TickerListener} Next ticker + */ + emit(deltaTime) + { + if (this.context) + { + this.fn.call(this.context, deltaTime); + } + else + { + this.fn(deltaTime); + } + + if (this.once) + { + this.destroy(); + } + + const redirect = this.next; + + // Soft-destroying should remove + // the next reference + if (this._destroyed) + { + this.next = null; + } + + return redirect; + } + + /** + * Connect to the list. + * @param {TickerListener} previous - Input node, previous listener + */ + connect(previous) + { + this.previous = previous; + if (previous.next) + { + previous.next.previous = this; + } + this.next = previous.next; + previous.next = this; + } + + /** + * Destroy and don't use after this. + * @param {boolean} [hard = false] `true` to remove the `next` reference, this + * is considered a hard destroy. Soft destroy maintains the next reference. + * @return {TickerListener} The listener to redirect while emitting or removing. + */ + destroy(hard = false) + { + this._destroyed = true; + this.fn = null; + this.context = null; + + // Disconnect, hook up next and previous + if (this.previous) + { + this.previous.next = this.next; + } + + if (this.next) + { + this.next.previous = this.previous; + } + + // Redirect to the next item + const redirect = this.previous; + + // Remove references + this.next = hard ? null : redirect; + this.previous = null; + + return redirect; + } +} diff --git a/src/core/ticker/index.js b/src/core/ticker/index.js index 4b3017c..c3020d4 100644 --- a/src/core/ticker/index.js +++ b/src/core/ticker/index.js @@ -45,8 +45,25 @@ const shared = new Ticker(); shared.autoStart = true; +shared.destroy = () => +{ + // protect destroying shared ticker + // this is used by other internal systems + // like AnimatedSprite and InteractionManager +}; /** + * This namespace contains an API for interacting with PIXI's internal global update loop. + * + * This ticker is used for rendering, {@link PIXI.extras.AnimatedSprite AnimatedSprite}, + * {@link PIXI.interaction.InteractionManager InteractionManager} and many other time-based PIXI systems. + * @example + * const ticker = new PIXI.ticker.Ticker(); + * ticker.stop(); + * ticker.add((deltaTime) => { + * // do something every frame + * }); + * ticker.start(); * @namespace PIXI.ticker */ export { shared, Ticker }; diff --git a/src/core/utils/index.js b/src/core/utils/index.js index be44750..7441f7b 100644 --- a/src/core/utils/index.js +++ b/src/core/utils/index.js @@ -9,6 +9,21 @@ let saidHello = false; /** + * Generalized convenience utilities for PIXI. + * @example + * // Extend PIXI's internal Event Emitter. + * class MyEmitter extends PIXI.utils.EventEmitter { + * constructor() { + * super(); + * console.log("Emitter created!"); + * } + * } + * + * // Get info on current device + * console.log(PIXI.utils.isMobile); + * + * // Convert hex color to string + * console.log(PIXI.utils.hex2string(0xff00ff)); // returns: "#ff00ff" * @namespace PIXI.utils */ export { diff --git a/src/deprecation.js b/src/deprecation.js index b9c73b3..33daeec 100644 --- a/src/deprecation.js +++ b/src/deprecation.js @@ -918,6 +918,35 @@ }); /** + * @method + * @name PIXI.prepare.BasePrepare#register + * @see PIXI.prepare.BasePrepare#registerFindHook + * @deprecated since version 4.4.2 + * @param {Function} [addHook] - Function call that takes two parameters: `item:*, queue:Array` + * function must return `true` if it was able to add item to the queue. + * @param {Function} [uploadHook] - Function call that takes two parameters: `prepare:CanvasPrepare, item:*` and + * function must return `true` if it was able to handle upload of item. + * @return {PIXI.BasePrepare} Instance of plugin for chaining. + */ +prepare.BasePrepare.prototype.register = function register(addHook, uploadHook) +{ + warn('renderer.plugins.prepare.register is now deprecated, ' + + 'please use renderer.plugins.prepare.registerFindHook & renderer.plugins.prepare.registerUploadHook'); + + if (addHook) + { + this.registerFindHook(addHook); + } + + if (uploadHook) + { + this.registerUploadHook(uploadHook); + } + + return this; +}; + +/** * The number of graphics or textures to upload to the GPU. * * @name PIXI.prepare.canvas.UPLOADS_PER_FRAME @@ -969,67 +998,73 @@ }, }); -Object.defineProperties(loaders.Resource.prototype, { - isJson: { - get() - { - warn('The isJson property is deprecated, please use `resource.type === Resource.TYPE.JSON`.'); +if (loaders.Loader) +{ + const Resource = loaders.Resource; + const Loader = loaders.Loader; - return this.type === loaders.Loader.Resource.TYPE.JSON; + Object.defineProperties(Resource.prototype, { + isJson: { + get() + { + warn('The isJson property is deprecated, please use `resource.type === Resource.TYPE.JSON`.'); + + return this.type === Resource.TYPE.JSON; + }, }, - }, - isXml: { - get() - { - warn('The isXml property is deprecated, please use `resource.type === Resource.TYPE.XML`.'); + isXml: { + get() + { + warn('The isXml property is deprecated, please use `resource.type === Resource.TYPE.XML`.'); - return this.type === loaders.Loader.Resource.TYPE.XML; + return this.type === Resource.TYPE.XML; + }, }, - }, - isImage: { - get() - { - warn('The isImage property is deprecated, please use `resource.type === Resource.TYPE.IMAGE`.'); + isImage: { + get() + { + warn('The isImage property is deprecated, please use `resource.type === Resource.TYPE.IMAGE`.'); - return this.type === loaders.Loader.Resource.TYPE.IMAGE; + return this.type === Resource.TYPE.IMAGE; + }, }, - }, - isAudio: { - get() - { - warn('The isAudio property is deprecated, please use `resource.type === Resource.TYPE.AUDIO`.'); + isAudio: { + get() + { + warn('The isAudio property is deprecated, please use `resource.type === Resource.TYPE.AUDIO`.'); - return this.type === loaders.Loader.Resource.TYPE.AUDIO; + return this.type === Resource.TYPE.AUDIO; + }, }, - }, - isVideo: { - get() - { - warn('The isVideo property is deprecated, please use `resource.type === Resource.TYPE.VIDEO`.'); + isVideo: { + get() + { + warn('The isVideo property is deprecated, please use `resource.type === Resource.TYPE.VIDEO`.'); - return this.type === loaders.Loader.Resource.TYPE.VIDEO; + return this.type === Resource.TYPE.VIDEO; + }, }, - }, -}); + }); -Object.defineProperties(loaders.Loader.prototype, { - before: { - get() - { - warn('The before() method is deprecated, please use pre().'); + Object.defineProperties(Loader.prototype, { + before: { + get() + { + warn('The before() method is deprecated, please use pre().'); - return this.pre; + return this.pre; + }, }, - }, - after: { - get() - { - warn('The after() method is deprecated, please use use().'); + after: { + get() + { + warn('The after() method is deprecated, please use use().'); - return this.use; + return this.use; + }, }, - }, -}); + }); +} /** * @name PIXI.interaction.interactiveTarget#defaultCursor diff --git a/src/extract/canvas/CanvasExtract.js b/src/extract/canvas/CanvasExtract.js index 941ca05..3ac5aee 100644 --- a/src/extract/canvas/CanvasExtract.js +++ b/src/extract/canvas/CanvasExtract.js @@ -8,7 +8,7 @@ * An instance of this class is automatically created by default, and can be found at renderer.plugins.extract * * @class - * @memberof PIXI + * @memberof PIXI.extract */ export default class CanvasExtract { @@ -21,9 +21,9 @@ /** * Collection of methods for extracting data (image, pixels, etc.) from a display object or render texture * - * @member {PIXI.CanvasExtract} extract + * @member {PIXI.extract.CanvasExtract} extract * @memberof PIXI.CanvasRenderer# - * @see PIXI.CanvasExtract + * @see PIXI.extract.CanvasExtract */ renderer.extract = this; } diff --git a/src/extract/index.js b/src/extract/index.js index 0f2170d..e9478c1 100644 --- a/src/extract/index.js +++ b/src/extract/index.js @@ -1,2 +1,22 @@ +/** + * This namespace provides renderer-specific plugins for exporting content from a renderer. + * For instance, these plugins can be used for saving an Image, Canvas element or for exporting the raw image data (pixels). + * + * Do not instantiate these plugins directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. + * @example + * // Create a new app (will auto-add extract plugin to renderer) + * const app = new PIXI.Application(); + * + * // Draw a red circle + * const graphics = new PIXI.Graphics() + * .beginFill(0xFF0000) + * .drawCircle(0, 0, 50); + * + * // Render the graphics as an HTMLImageElement + * const image = app.renderer.plugins.extract.image(graphics); + * document.body.appendChild(image); + * @namespace PIXI.extract + */ export { default as webgl } from './webgl/WebGLExtract'; export { default as canvas } from './canvas/CanvasExtract'; diff --git a/src/extract/webgl/WebGLExtract.js b/src/extract/webgl/WebGLExtract.js index 60228bb..1c3dd30 100644 --- a/src/extract/webgl/WebGLExtract.js +++ b/src/extract/webgl/WebGLExtract.js @@ -9,7 +9,7 @@ * An instance of this class is automatically created by default, and can be found at renderer.plugins.extract * * @class - * @memberof PIXI + * @memberof PIXI.extract */ export default class WebGLExtract { @@ -22,9 +22,9 @@ /** * Collection of methods for extracting data (image, pixels, etc.) from a display object or render texture * - * @member {PIXI.WebGLExtract} extract + * @member {PIXI.extract.WebGLExtract} extract * @memberof PIXI.WebGLRenderer# - * @see PIXI.WebGLExtract + * @see PIXI.extract.WebGLExtract */ renderer.extract = this; } diff --git a/src/extras/AnimatedSprite.js b/src/extras/AnimatedSprite.js index 13b804f..5297778 100644 --- a/src/extras/AnimatedSprite.js +++ b/src/extras/AnimatedSprite.js @@ -137,7 +137,7 @@ this.playing = true; if (this._autoUpdate) { - core.ticker.shared.add(this.update, this); + core.ticker.shared.add(this.update, this, core.UPDATE_PRIORITY.HIGH); } } diff --git a/src/extras/TextureTransform.js b/src/extras/TextureTransform.js index 78e6e40..16536d2 100644 --- a/src/extras/TextureTransform.js +++ b/src/extras/TextureTransform.js @@ -65,8 +65,36 @@ } /** + * Multiplies uvs array to transform + * @param {Float32Array} uvs mesh uvs + * @param {Float32Array} [out=uvs] output + * @returns {Float32Array} output + */ + multiplyUvs(uvs, out) + { + if (out === undefined) + { + out = uvs; + } + + const mat = this.mapCoord; + + for (let i = 0; i < uvs.length; i += 2) + { + const x = uvs[i]; + const y = uvs[i + 1]; + + out[i] = (x * mat.a) + (y * mat.c) + mat.tx; + out[i + 1] = (x * mat.b) + (y * mat.d) + mat.ty; + } + + return out; + } + + /** * updates matrices if texture was changed * @param {boolean} forceUpdate if true, matrices will be updated any case + * @returns {boolean} whether or not it was updated */ update(forceUpdate) { @@ -74,13 +102,13 @@ if (!tex || !tex.valid) { - return; + return false; } if (!forceUpdate && this._lastTextureID === tex._updateID) { - return; + return false; } this._lastTextureID = tex._updateID; @@ -110,5 +138,7 @@ frame[3] = (tex._frame.y + tex._frame.height - margin + offset) / texBase.height; this.uClampOffset[0] = offset / texBase.realWidth; this.uClampOffset[1] = offset / texBase.realHeight; + + return true; } } diff --git a/src/extras/TilingSprite.js b/src/extras/TilingSprite.js index 1f6014f..a3f027f 100644 --- a/src/extras/TilingSprite.js +++ b/src/extras/TilingSprite.js @@ -180,9 +180,9 @@ const transform = this.worldTransform; const resolution = renderer.resolution; const baseTexture = texture.baseTexture; - const baseTextureResolution = texture.baseTexture.resolution; - const modX = (this.tilePosition.x / this.tileScale.x) % texture._frame.width; - const modY = (this.tilePosition.y / this.tileScale.y) % texture._frame.height; + const baseTextureResolution = baseTexture.resolution; + const modX = ((this.tilePosition.x / this.tileScale.x) % texture._frame.width) * baseTextureResolution; + const modY = ((this.tilePosition.y / this.tileScale.y) % texture._frame.height) * baseTextureResolution; // create a nice shiny pattern! // TODO this needs to be refreshed if texture changes.. diff --git a/src/extras/cacheAsBitmap.js b/src/extras/cacheAsBitmap.js index 5dd5640..aea06a8 100644 --- a/src/extras/cacheAsBitmap.js +++ b/src/extras/cacheAsBitmap.js @@ -227,7 +227,16 @@ this.transform._parentID = -1; // restore the transform of the cached sprite to avoid the nasty flicker.. - this.updateTransform(); + if (!this.parent) + { + this.parent = renderer._tempDisplayObjectParent; + this.updateTransform(); + this.parent = null; + } + else + { + this.updateTransform(); + } // map the hit test.. this.containsPoint = cachedSprite.containsPoint.bind(cachedSprite); @@ -314,7 +323,17 @@ cachedSprite._bounds = this._bounds; cachedSprite.alpha = cacheAlpha; - this.updateTransform(); + if (!this.parent) + { + this.parent = renderer._tempDisplayObjectParent; + this.updateTransform(); + this.parent = null; + } + else + { + this.updateTransform(); + } + this.updateTransform = this.displayObjectUpdateTransform; this._cacheData.sprite = cachedSprite; @@ -358,9 +377,12 @@ * Destroys the cached object. * * @private + * @param {object|boolean} [options] - Options parameter. A boolean will act as if all options + * have been set to that value. + * Used when destroying containers, see the Container.destroy method. */ -DisplayObject.prototype._cacheAsBitmapDestroy = function _cacheAsBitmapDestroy() +DisplayObject.prototype._cacheAsBitmapDestroy = function _cacheAsBitmapDestroy(options) { this.cacheAsBitmap = false; - this.destroy(); + this.destroy(options); }; diff --git a/src/extras/index.js b/src/extras/index.js index bbd8bbf..a34d7ec 100644 --- a/src/extras/index.js +++ b/src/extras/index.js @@ -1,7 +1,7 @@ /** + * Additional PIXI DisplayObjects for animation, tiling and bitmap text. * @namespace PIXI.extras */ -export { default as TextureTransform } from './TextureTransform'; export { default as AnimatedSprite } from './AnimatedSprite'; export { default as TilingSprite } from './TilingSprite'; export { default as TilingSpriteRenderer } from './webgl/TilingSpriteRenderer'; diff --git a/src/extras/webgl/TilingSpriteRenderer.js b/src/extras/webgl/TilingSpriteRenderer.js index aeec172..eee3b5c 100644 --- a/src/extras/webgl/TilingSpriteRenderer.js +++ b/src/extras/webgl/TilingSpriteRenderer.js @@ -11,7 +11,7 @@ * WebGL renderer plugin for tiling sprites * * @class - * @memberof PIXI + * @memberof PIXI.extras * @extends PIXI.ObjectRenderer */ export default class TilingSpriteRenderer extends core.ObjectRenderer @@ -118,7 +118,7 @@ tempMat.invert(); if (isSimple) { - tempMat.append(uv.mapCoord); + tempMat.prepend(uv.mapCoord); } else { diff --git a/src/filters/displacement/DisplacementFilter.js b/src/filters/displacement/DisplacementFilter.js index d5355c3..b5d030c 100644 --- a/src/filters/displacement/DisplacementFilter.js +++ b/src/filters/displacement/DisplacementFilter.js @@ -35,7 +35,7 @@ this.maskSprite = sprite; this.maskMatrix = maskMatrix; - this.uniforms.mapSampler = sprite.texture; + this.uniforms.mapSampler = sprite._texture; this.uniforms.filterMatrix = maskMatrix; this.uniforms.scale = { x: 1, y: 1 }; diff --git a/src/filters/index.js b/src/filters/index.js index f3e8862..7ceb50a 100644 --- a/src/filters/index.js +++ b/src/filters/index.js @@ -1,4 +1,21 @@ /** + * This namespace contains WebGL-only display filters that can be applied + * to DisplayObjects using the {@link PIXI.DisplayObject#filters filters} property. + * @example + * // Create a new application + * const app = new PIXI.Application(); + * + * // Draw a green rectangle + * const rect = new PIXI.Graphics() + * .beginFill(0x00ff00) + * .drawRect(40, 40, 200, 200); + * + * // Add a blur filter + * rect.filters = [new PIXI.filters.BlurFilter()]; + * + * // Display rectangle + * app.stage.addChild(rect); + * document.body.appendChild(app.view); * @namespace PIXI.filters */ export { default as FXAAFilter } from './fxaa/FXAAFilter'; diff --git a/src/index.js b/src/index.js index c553427..77da490 100644 --- a/src/index.js +++ b/src/index.js @@ -20,6 +20,14 @@ import { utils } from './core'; utils.mixins.performMixins(); +/** + * Alias for {@link PIXI.loaders.shared}. + * @name loader + * @memberof PIXI + * @type {PIXI.loader.Loader} + */ +const loader = loaders.shared || null; + export { accessibility, extract, @@ -30,18 +38,8 @@ mesh, particles, prepare, + loader, }; -/** - * A premade instance of the loader that can be used to load resources. - * - * @name loader - * @memberof PIXI - * @property {PIXI.loaders.Loader} - */ -const loader = loaders && loaders.Loader ? new loaders.Loader() : null; // check is there in case user excludes loader lib - -export { loader }; - // Always export pixi globally. global.PIXI = exports; // eslint-disable-line diff --git a/src/interaction/InteractionData.js b/src/interaction/InteractionData.js index 02d53a3..449f8cc 100644 --- a/src/interaction/InteractionData.js +++ b/src/interaction/InteractionData.js @@ -30,7 +30,10 @@ /** * When passed to an event handler, this will be the original DOM Event that was captured * - * @member {Event} + * @see https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent + * @see https://developer.mozilla.org/en-US/docs/Web/API/TouchEvent + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent + * @member {MouseEvent|TouchEvent|PointerEvent} */ this.originalEvent = null; } diff --git a/src/interaction/InteractionManager.js b/src/interaction/InteractionManager.js index 8baf13a..2d75c7c 100644 --- a/src/interaction/InteractionManager.js +++ b/src/interaction/InteractionManager.js @@ -13,6 +13,14 @@ const MOUSE_POINTER_ID = 'MOUSE'; +// helpers for hitTest() - only used inside hitTest() +const hitTestEvent = { + target: null, + data: { + global: null, + }, +}; + /** * The interaction manager deals with mouse, touch and pointer events. Any DisplayObject can be interactive * if its interactive parameter is set to true @@ -245,67 +253,60 @@ this.setTargetElement(this.renderer.view, this.renderer.resolution); /** - * Fired when a pointer device button (usually a mouse button) is pressed on the display + * Fired when a pointer device button (usually a mouse left-button) is pressed on the display * object. * - * @event mousedown - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mousedown + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device secondary button (usually a mouse right-button) is pressed * on the display object. * - * @event rightdown - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightdown + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** - * Fired when a pointer device button (usually a mouse button) is released over the display + * Fired when a pointer device button (usually a mouse left-button) is released over the display * object. * - * @event mouseup - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseup + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device secondary button (usually a mouse right-button) is released * over the display object. * - * @event rightup - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightup + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** - * Fired when a pointer device button (usually a mouse button) is pressed and released on + * Fired when a pointer device button (usually a mouse left-button) is pressed and released on * the display object. * - * @event click - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#click + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device secondary button (usually a mouse right-button) is pressed * and released on the display object. * - * @event rightclick - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightclick + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** - * Fired when a pointer device button (usually a mouse button) is released outside the + * Fired when a pointer device button (usually a mouse left-button) is released outside the * display object that initially registered a * [mousedown]{@link PIXI.interaction.InteractionManager#event:mousedown}. * - * @event mouseupoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** @@ -313,146 +314,362 @@ * outside the display object that initially registered a * [rightdown]{@link PIXI.interaction.InteractionManager#event:rightdown}. * - * @event rightupoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device (usually a mouse) is moved while over the display object * - * @event mousemove - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mousemove + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device (usually a mouse) is moved onto the display object * - * @event mouseover - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseover + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device (usually a mouse) is moved off the display object * - * @event mouseout - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseout + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is pressed on the display object. * - * @event pointerdown - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerdown + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is released over the display object. * - * @event pointerup - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerup + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when the operating system cancels a pointer event * - * @event pointercancel - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointercancel + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is pressed and released on the display object. * - * @event pointertap - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointertap + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is released outside the display object that initially * registered a [pointerdown]{@link PIXI.interaction.InteractionManager#event:pointerdown}. * - * @event pointerupoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device is moved while over the display object * - * @event pointermove - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointermove + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device is moved onto the display object * - * @event pointerover - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerover + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device is moved off the display object * - * @event pointerout - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerout + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is placed on the display object. * - * @event touchstart - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchstart + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is removed from the display object. * - * @event touchend - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchend + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when the operating system cancels a touch * - * @event touchcancel - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchcancel + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is placed and removed from the display object. * - * @event tap - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#tap + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is removed outside of the display object that initially * registered a [touchstart]{@link PIXI.interaction.InteractionManager#event:touchstart}. * - * @event touchendoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchendoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is moved along the display object. * - * @event touchmove - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchmove + * @param {PIXI.interaction.InteractionData} event - Interaction data */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is pressed on the display. + * object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mousedown + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is pressed + * on the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightdown + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is released over the display + * object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseup + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is released + * over the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightup + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is pressed and released on + * the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#click + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is pressed + * and released on the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightclick + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is released outside the + * display object that initially registered a + * [mousedown]{@link PIXI.DisplayObject#event:mousedown}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is released + * outside the display object that initially registered a + * [rightdown]{@link PIXI.DisplayObject#event:rightdown}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device (usually a mouse) is moved while over the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mousemove + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device (usually a mouse) is moved onto the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseover + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device (usually a mouse) is moved off the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseout + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is pressed on the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerdown + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is released over the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerup + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when the operating system cancels a pointer event. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointercancel + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is pressed and released on the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointertap + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is released outside the display object that initially + * registered a [pointerdown]{@link PIXI.DisplayObject#event:pointerdown}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device is moved while over the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointermove + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device is moved onto the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerover + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device is moved off the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerout + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is placed on the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchstart + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is removed from the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchend + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when the operating system cancels a touch. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchcancel + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is placed and removed from the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#tap + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is removed outside of the display object that initially + * registered a [touchstart]{@link PIXI.DisplayObject#event:touchstart}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchendoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is moved along the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchmove + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + } + + /** + * Hit tests a point against the display tree, returning the first interactive object that is hit. + * + * @param {PIXI.Point} globalPoint - A point to hit test with, in global space. + * @param {PIXI.Container} [root] - The root display object to start from. If omitted, defaults + * to the last rendered root of the associated renderer. + * @return {PIXI.DisplayObject} The hit display object, if any. + */ + hitTest(globalPoint, root) + { + // clear the target for our hit test + hitTestEvent.target = null; + // assign the global point + hitTestEvent.data.global = globalPoint; + // ensure safety of the root + if (!root) + { + root = this.renderer._lastObjectRendered; + } + // run the hit test + this.processInteractive(hitTestEvent, root, null, true); + // return our found object - it'll be null if we didn't hit anything + + return hitTestEvent.target; } /** @@ -487,7 +704,7 @@ return; } - core.ticker.shared.add(this.update, this); + core.ticker.shared.add(this.update, this, core.UPDATE_PRIORITY.INTERACTION); if (window.navigator.msPointerEnabled) { @@ -804,8 +1021,6 @@ } } - let keepHitTestingAfterChildren = hitTest; - // ** FREE TIP **! If an object is not interactive or has no buttons in it // (such as a game scene!) set interactiveChildren to false for that displayObject. // This will allow pixi to completely ignore and bypass checking the displayObjects children. @@ -818,7 +1033,9 @@ const child = children[i]; // time to get recursive.. if this function will return if something is hit.. - if (this.processInteractive(interactionEvent, child, func, hitTest, interactiveParent)) + const childHit = this.processInteractive(interactionEvent, child, func, hitTest, interactiveParent); + + if (childHit) { // its a good idea to check if a child has lost its parent. // this means it has been removed whilst looping so its best @@ -827,8 +1044,6 @@ continue; } - hit = true; - // we no longer need to hit test any more objects in this container as we we // now know the parent has been hit interactiveParent = false; @@ -838,36 +1053,41 @@ // This means we no longer need to hit test anything else. We still need to run // through all objects, but we don't need to perform any hit tests. - keepHitTestingAfterChildren = false; - - if (child.interactive) + if (childHit) { - hitTest = false; + if (interactionEvent.target) + { + hitTest = false; + } + hit = true; } - - // we can break now as we have hit an object. } } } - hitTest = keepHitTestingAfterChildren; - // no point running this if the item is not interactive or does not have an interactive parent. if (interactive) { // if we are hit testing (as in we have no hit any objects yet) // We also don't need to worry about hit testing if once of the displayObjects children - // has already been hit! - if (hitTest && !hit) + // has already been hit - but only if it was interactive, otherwise we need to keep + // looking for an interactive child, just in case we hit one + if (hitTest && !interactionEvent.target) { if (displayObject.hitArea) { displayObject.worldTransform.applyInverse(point, this._tempPoint); - hit = displayObject.hitArea.contains(this._tempPoint.x, this._tempPoint.y); + if (displayObject.hitArea.contains(this._tempPoint.x, this._tempPoint.y)) + { + hit = true; + } } else if (displayObject.containsPoint) { - hit = displayObject.containsPoint(point); + if (displayObject.containsPoint(point)) + { + hit = true; + } } } @@ -878,7 +1098,10 @@ interactionEvent.target = displayObject; } - func(interactionEvent, displayObject, hit); + if (func) + { + func(interactionEvent, displayObject, !!hit); + } } } @@ -914,7 +1137,7 @@ { const event = events[i]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -998,7 +1221,7 @@ { const event = events[i]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1098,7 +1321,7 @@ const test = isRightButton ? flags.RIGHT_DOWN : flags.LEFT_DOWN; - const isDown = trackingData !== undefined && (trackingData.flags | test); + const isDown = trackingData !== undefined && (trackingData.flags & test); if (hit) { @@ -1118,11 +1341,11 @@ { if (isRightButton) { - trackingData.rightDown = hit; + trackingData.rightDown = false; } else { - trackingData.leftDown = hit; + trackingData.leftDown = false; } } } @@ -1180,7 +1403,7 @@ { const event = events[i]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1255,7 +1478,7 @@ this.setCursorMode(null); } - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1268,6 +1491,12 @@ { this.emit('mouseout', interactionEvent); } + else + { + // we can get touchleave events after touchend, so we want to make sure we don't + // introduce memory leaks + this.releaseInteractionDataForPointerId(interactionData.identifier); + } } /** @@ -1344,7 +1573,7 @@ // Only mouse and pointer can call onPointerOver, so events will always be length 1 const event = events[0]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1366,12 +1595,14 @@ * Get InteractionData for a given pointerId. Store that data as well * * @private - * @param {number} pointerId - Identifier from a pointer event + * @param {PointerEvent} event - Normalized pointer event, output from normalizeToPointerData * @return {InteractionData} - Interaction data for the given pointer identifier */ - getInteractionDataForPointerId(pointerId) + getInteractionDataForPointerId(event) { - if (pointerId === MOUSE_POINTER_ID) + const pointerId = event.pointerId; + + if (pointerId === MOUSE_POINTER_ID || event.pointerType === 'mouse') { return this.mouse; } diff --git a/src/interaction/InteractionTrackingData.js b/src/interaction/InteractionTrackingData.js index 6a1f69e..8493b63 100644 --- a/src/interaction/InteractionTrackingData.js +++ b/src/interaction/InteractionTrackingData.js @@ -35,8 +35,10 @@ } /** + * Unique pointer id of the event + * * @readonly - * @type {number} Unique pointer id of the event + * @member {number} */ get pointerId() { diff --git a/src/interaction/index.js b/src/interaction/index.js index 56c5b09..72f7d71 100644 --- a/src/interaction/index.js +++ b/src/interaction/index.js @@ -1,4 +1,8 @@ /** + * This namespace contains a renderer plugin for handling mouse, pointer, and touch events. + * + * Do not instantiate this plugin directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. * @namespace PIXI.interaction */ export { default as InteractionData } from './InteractionData'; diff --git a/src/interaction/interactiveTarget.js b/src/interaction/interactiveTarget.js index d252fba..cebac40 100644 --- a/src/interaction/interactiveTarget.js +++ b/src/interaction/interactiveTarget.js @@ -2,7 +2,7 @@ * Default property values of interactive objects * Used by {@link PIXI.interaction.InteractionManager} to automatically give all DisplayObjects these properties * - * @mixin + * @private * @name interactiveTarget * @memberof PIXI.interaction * @example @@ -14,18 +14,28 @@ * ); */ export default { + /** - * Determines if the displayObject be clicked/touched + * Enable interaction events for the DisplayObject. Touch, pointer and mouse + * events will not be emitted unless `interactive` is set to `true`. * - * @inner {boolean} + * @example + * const sprite = new PIXI.Sprite(texture); + * sprite.interactive = true; + * sprite.on('tap', (event) => { + * //handle event + * }); + * @member {boolean} + * @memberof PIXI.DisplayObject# */ interactive: false, /** * Determines if the children to the displayObject can be clicked/touched - * Setting this to false allows pixi to bypass a recursive hitTest function + * Setting this to false allows pixi to bypass a recursive `hitTest` function * - * @inner {boolean} + * @member {boolean} + * @memberof PIXI.Container# */ interactiveChildren: true, @@ -33,7 +43,12 @@ * Interaction shape. Children will be hit first, then this shape will be checked. * Setting this will cause this shape to be checked in hit tests rather than the displayObject's bounds. * - * @inner {PIXI.Rectangle|PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.RoundedRectangle} + * @example + * const sprite = new PIXI.Sprite(texture); + * sprite.interactive = true; + * sprite.hitArea = new PIXI.Rectangle(0, 0, 100, 100); + * @member {PIXI.Rectangle|PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.RoundedRectangle} + * @memberof PIXI.DisplayObject# */ hitArea: null, @@ -41,8 +56,12 @@ * If enabled, the mouse cursor use the pointer behavior when hovered over the displayObject if it is interactive * Setting this changes the 'cursor' property to `'pointer'`. * + * @example + * const sprite = new PIXI.Sprite(texture); + * sprite.interactive = true; + * sprite.buttonMode = true; * @member {boolean} - * @memberof PIXI.interaction.interactiveTarget# + * @memberof PIXI.DisplayObject# */ get buttonMode() { @@ -64,9 +83,14 @@ * This defines what cursor mode is used when the mouse cursor * is hovered over the displayObject. * + * @example + * const sprite = new PIXI.Sprite(texture); + * sprite.interactive = true; + * sprite.cursor = 'wait'; * @see https://developer.mozilla.org/en/docs/Web/CSS/cursor * - * @inner {string} + * @member {string} + * @memberof PIXI.DisplayObject# */ cursor: null, @@ -74,7 +98,7 @@ * Internal set of all active pointers, by identifier * * @member {Map} - * @memberof PIXI.interaction.interactiveTarget# + * @memberof PIXI.DisplayObject# * @private */ get trackedPointers() @@ -87,7 +111,8 @@ /** * Map of all tracked pointers, by identifier. Use trackedPointers to access. * - * @private {Map} + * @private + * @type {Map} */ _trackedPointers: undefined, }; diff --git a/src/loaders/index.js b/src/loaders/index.js index 5189793..daa1fc4 100644 --- a/src/loaders/index.js +++ b/src/loaders/index.js @@ -1,9 +1,22 @@ +import Application from '../core/Application'; +import Loader from './loader'; + /** + * This namespace contains APIs which extends the {@link https://github.com/englercj/resource-loader resource-loader} module + * for loading assets, data, and other resources dynamically. + * @example + * const loader = new PIXI.loaders.Loader(); + * loader.add('bunny', 'data/bunny.png') + * .add('spaceship', 'assets/spritesheet.json'); + * loader.load((loader, resources) => { + * // resources.bunny + * // resources.spaceship + * }); * @namespace PIXI.loaders */ -export { default as Loader } from './loader'; +export { Loader }; export { default as bitmapFontParser, parse as parseBitmapFontData } from './bitmapFontParser'; -export { default as spritesheetParser } from './spritesheetParser'; +export { default as spritesheetParser, getResourcePath } from './spritesheetParser'; export { default as textureParser } from './textureParser'; /** @@ -13,3 +26,55 @@ * @memberof PIXI.loaders */ export { Resource } from 'resource-loader'; + +/** + * A premade instance of the loader that can be used to load resources. + * @name shared + * @memberof PIXI.loaders + * @type {PIXI.loaders.Loader} + */ +const shared = new Loader(); + +shared.destroy = () => +{ + // protect destroying shared loader +}; + +export { shared }; + +// Mixin the loader construction +const AppPrototype = Application.prototype; + +AppPrototype._loader = null; + +/** + * Loader instance to help with asset loading. + * @name PIXI.Application#loader + * @type {PIXI.loaders.Loader} + */ +Object.defineProperty(AppPrototype, 'loader', { + get() + { + if (!this._loader) + { + const sharedLoader = this._options.sharedLoader; + + this._loader = sharedLoader ? shared : new Loader(); + } + + return this._loader; + }, +}); + +// Override the destroy function +// making sure to destroy the current Loader +AppPrototype._parentDestroy = AppPrototype.destroy; +AppPrototype.destroy = function destroy() +{ + if (this._loader) + { + this._loader.destroy(); + this._loader = null; + } + this._parentDestroy(); +}; diff --git a/src/loaders/loader.js b/src/loaders/loader.js index 767a411..59b8000 100644 --- a/src/loaders/loader.js +++ b/src/loaders/loader.js @@ -90,6 +90,15 @@ { Loader._pixiMiddleware.push(fn); } + + /** + * Destroy the loader, removes references. + */ + destroy() + { + this.removeAllListeners(); + this.reset(); + } } // Copy EE3 prototype (mixin) diff --git a/src/loaders/spritesheetParser.js b/src/loaders/spritesheetParser.js index e57dfb2..eda9584 100644 --- a/src/loaders/spritesheetParser.js +++ b/src/loaders/spritesheetParser.js @@ -1,12 +1,11 @@ import { Resource } from 'resource-loader'; -import path from 'path'; +import url from 'url'; import { Spritesheet } from '../core'; export default function () { return function spritesheetParser(resource, next) { - let resourcePath; const imageResourceName = `${resource.name}_image`; // skip if no data, its not json, it isn't spritesheet data, or the image resource already exists @@ -28,15 +27,7 @@ parentResource: resource, }; - // Prepend url path unless the resource image is a data url - if (resource.isDataUrl) - { - resourcePath = resource.data.meta.image; - } - else - { - resourcePath = `${path.dirname(resource.url.replace(this.baseUrl, ''))}/${resource.data.meta.image}`; - } + const resourcePath = getResourcePath(resource, this.baseUrl); // load the image for this sheet this.add(imageResourceName, resourcePath, loadOptions, function onImageLoad(res) @@ -56,3 +47,14 @@ }); }; } + +export function getResourcePath(resource, baseUrl) +{ + // Prepend url path unless the resource image is a data url + if (resource.isDataUrl) + { + return resource.data.meta.image; + } + + return url.resolve(resource.url.replace(baseUrl, ''), resource.data.meta.image); +} diff --git a/src/mesh/canvas/CanvasMeshRenderer.js b/src/mesh/canvas/CanvasMeshRenderer.js index 297205f..f7899e1 100644 --- a/src/mesh/canvas/CanvasMeshRenderer.js +++ b/src/mesh/canvas/CanvasMeshRenderer.js @@ -134,12 +134,33 @@ const textureWidth = base.width; const textureHeight = base.height; - const u0 = uvs[index0] * base.width; - const u1 = uvs[index1] * base.width; - const u2 = uvs[index2] * base.width; - const v0 = uvs[index0 + 1] * base.height; - const v1 = uvs[index1 + 1] * base.height; - const v2 = uvs[index2 + 1] * base.height; + let u0; + let u1; + let u2; + let v0; + let v1; + let v2; + + if (mesh.uploadUvTransform) + { + const ut = mesh._uvTransform.mapCoord; + + u0 = ((uvs[index0] * ut.a) + (uvs[index0 + 1] * ut.c) + ut.tx) * base.width; + u1 = ((uvs[index1] * ut.a) + (uvs[index1 + 1] * ut.c) + ut.tx) * base.width; + u2 = ((uvs[index2] * ut.a) + (uvs[index2 + 1] * ut.c) + ut.tx) * base.width; + v0 = ((uvs[index0] * ut.b) + (uvs[index0 + 1] * ut.d) + ut.ty) * base.height; + v1 = ((uvs[index1] * ut.b) + (uvs[index1 + 1] * ut.d) + ut.ty) * base.height; + v2 = ((uvs[index2] * ut.b) + (uvs[index2 + 1] * ut.d) + ut.ty) * base.height; + } + else + { + u0 = uvs[index0] * base.width; + u1 = uvs[index1] * base.width; + u2 = uvs[index2] * base.width; + v0 = uvs[index0 + 1] * base.height; + v1 = uvs[index1 + 1] * base.height; + v2 = uvs[index2 + 1] * base.height; + } let x0 = vertices[index0]; let x1 = vertices[index1]; diff --git a/src/mesh/webgl/MeshRenderer.js b/src/mesh/webgl/MeshRenderer.js index 78d24c3..ab99fcd 100644 --- a/src/mesh/webgl/MeshRenderer.js +++ b/src/mesh/webgl/MeshRenderer.js @@ -2,6 +2,8 @@ const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; +const matrixIdentity = core.Matrix.IDENTITY; + /** * WebGL renderer plugin for tiling sprites * @@ -64,6 +66,7 @@ mesh.geometry.glVertexArrayObjects[this.CONTEXT_UID].draw(mesh.drawMode, mesh.size, mesh.start, mesh.geometry.instanceCount); } + /** * draws mesh * @param {PIXI.mesh.RawMesh} mesh mesh instance diff --git a/src/mesh/webgl/mesh.vert b/src/mesh/webgl/mesh.vert index a337aef..acc096c 100644 --- a/src/mesh/webgl/mesh.vert +++ b/src/mesh/webgl/mesh.vert @@ -1,8 +1,9 @@ attribute vec2 aVertexPosition; attribute vec2 aTextureCoord; -uniform mat3 translationMatrix; uniform mat3 projectionMatrix; +uniform mat3 translationMatrix; +uniform mat3 uTransform; varying vec2 vTextureCoord; @@ -10,5 +11,5 @@ { gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - vTextureCoord = aTextureCoord; + vTextureCoord = (uTransform * vec3(aTextureCoord, 1.0)).xy; } diff --git a/src/particles/ParticleContainer.js b/src/particles/ParticleContainer.js index 67796cf..63f7791 100644 --- a/src/particles/ParticleContainer.js +++ b/src/particles/ParticleContainer.js @@ -313,10 +313,10 @@ frame.y * resolution, frame.width * resolution, frame.height * resolution, - positionX * resolution, - positionY * resolution, - finalWidth * resolution, - finalHeight * resolution + positionX * renderer.resolution, + positionY * renderer.resolution, + finalWidth * renderer.resolution, + finalHeight * renderer.resolution ); } } diff --git a/src/prepare/BasePrepare.js b/src/prepare/BasePrepare.js index 09eb5e5..e45c4dc 100644 --- a/src/prepare/BasePrepare.js +++ b/src/prepare/BasePrepare.js @@ -18,6 +18,18 @@ * basic queuing functionality and is extended by {@link PIXI.prepare.WebGLPrepare} and {@link PIXI.prepare.CanvasPrepare} * to provide preparation capabilities specific to their respective renderers. * + * @example + * // Create a sprite + * const sprite = new PIXI.Sprite.fromImage('something.png'); + * + * // Load object into GPU + * app.renderer.plugins.prepare.upload(sprite, () => { + * + * //Texture(s) has been uploaded to GPU + * app.stage.addChild(sprite); + * + * }) + * * @abstract * @class * @memberof PIXI.prepare @@ -100,8 +112,16 @@ this.prepareItems(); }; - this.register(findText, drawText); - this.register(findTextStyle, calculateTextStyle); + // hooks to find the correct texture + this.registerFindHook(findText); + this.registerFindHook(findTextStyle); + this.registerFindHook(findMultipleBaseTextures); + this.registerFindHook(findBaseTexture); + this.registerFindHook(findTexture); + + // upload hooks + this.registerUploadHook(drawText); + this.registerUploadHook(calculateTextStyle); } /** @@ -138,7 +158,7 @@ if (!this.ticking) { this.ticking = true; - SharedTicker.addOnce(this.tick, this); + SharedTicker.addOnce(this.tick, this, core.UPDATE_PRIORITY.UTILITY); } } else if (done) @@ -172,13 +192,16 @@ const item = this.queue[0]; let uploaded = false; - for (let i = 0, len = this.uploadHooks.length; i < len; i++) + if (item && !item._destroyed) { - if (this.uploadHooks[i](this.uploadHookHelper, item)) + for (let i = 0, len = this.uploadHooks.length; i < len; i++) { - this.queue.shift(); - uploaded = true; - break; + if (this.uploadHooks[i](this.uploadHookHelper, item)) + { + this.queue.shift(); + uploaded = true; + break; + } } } @@ -205,26 +228,36 @@ else { // if we are not finished, on the next rAF do this again - SharedTicker.addOnce(this.tick, this); + SharedTicker.addOnce(this.tick, this, core.UPDATE_PRIORITY.UTILITY); } } /** - * Adds hooks for finding and uploading items. + * Adds hooks for finding items. * - * @param {Function} [addHook] - Function call that takes two parameters: `item:*, queue:Array` - function must return `true` if it was able to add item to the queue. - * @param {Function} [uploadHook] - Function call that takes two parameters: `prepare:CanvasPrepare, item:*` and - * function must return `true` if it was able to handle upload of item. - * @return {PIXI.CanvasPrepare} Instance of plugin for chaining. + * @param {Function} addHook - Function call that takes two parameters: `item:*, queue:Array` + * function must return `true` if it was able to add item to the queue. + * @return {PIXI.BasePrepare} Instance of plugin for chaining. */ - register(addHook, uploadHook) + registerFindHook(addHook) { if (addHook) { this.addHooks.push(addHook); } + return this; + } + + /** + * Adds hooks for uploading items. + * + * @param {Function} uploadHook - Function call that takes two parameters: `prepare:CanvasPrepare, item:*` and + * function must return `true` if it was able to handle upload of item. + * @return {PIXI.BasePrepare} Instance of plugin for chaining. + */ + registerUploadHook(uploadHook) + { if (uploadHook) { this.uploadHooks.push(uploadHook); @@ -287,6 +320,88 @@ } /** + * Built-in hook to find multiple textures from objects like AnimatedSprites. + * + * @private + * @param {PIXI.DisplayObject} item - Display object to check + * @param {Array<*>} queue - Collection of items to upload + * @return {boolean} if a PIXI.Texture object was found. + */ +function findMultipleBaseTextures(item, queue) +{ + let result = false; + + // Objects with mutliple textures + if (item && item._textures && item._textures.length) + { + for (let i = 0; i < item._textures.length; i++) + { + if (item._textures[i] instanceof core.Texture) + { + const baseTexture = item._textures[i].baseTexture; + + if (queue.indexOf(baseTexture) === -1) + { + queue.push(baseTexture); + result = true; + } + } + } + } + + return result; +} + +/** + * Built-in hook to find BaseTextures from Sprites. + * + * @private + * @param {PIXI.DisplayObject} item - Display object to check + * @param {Array<*>} queue - Collection of items to upload + * @return {boolean} if a PIXI.Texture object was found. + */ +function findBaseTexture(item, queue) +{ + // Objects with textures, like Sprites/Text + if (item instanceof core.BaseTexture) + { + if (queue.indexOf(item) === -1) + { + queue.push(item); + } + + return true; + } + + return false; +} + +/** + * Built-in hook to find textures from objects. + * + * @private + * @param {PIXI.DisplayObject} item - Display object to check + * @param {Array<*>} queue - Collection of items to upload + * @return {boolean} if a PIXI.Texture object was found. + */ +function findTexture(item, queue) +{ + if (item._texture && item._texture instanceof core.Texture) + { + const texture = item._texture.baseTexture; + + if (queue.indexOf(texture) === -1) + { + queue.push(texture); + } + + return true; + } + + return false; +} + +/** * Built-in hook to draw PIXI.Text to its texture. * * @private diff --git a/src/prepare/canvas/CanvasPrepare.js b/src/prepare/canvas/CanvasPrepare.js index 0224bb5..09c207a 100644 --- a/src/prepare/canvas/CanvasPrepare.js +++ b/src/prepare/canvas/CanvasPrepare.js @@ -43,7 +43,7 @@ this.ctx = this.canvas.getContext('2d'); // Add textures to upload - this.register(findBaseTextures, uploadBaseTextures); + this.registerUploadHook(uploadBaseTextures); } /** @@ -89,39 +89,4 @@ return false; } -/** - * Built-in hook to find textures from Sprites. - * - * @private - * @param {PIXI.DisplayObject} item -Display object to check - * @param {Array<*>} queue - Collection of items to upload - * @return {boolean} if a PIXI.Texture object was found. - */ -function findBaseTextures(item, queue) -{ - // Objects with textures, like Sprites/Text - if (item instanceof core.BaseTexture) - { - if (queue.indexOf(item) === -1) - { - queue.push(item); - } - - return true; - } - else if (item._texture && item._texture instanceof core.Texture) - { - const texture = item._texture.baseTexture; - - if (queue.indexOf(texture) === -1) - { - queue.push(texture); - } - - return true; - } - - return false; -} - core.CanvasRenderer.registerPlugin('prepare', CanvasPrepare); diff --git a/src/prepare/index.js b/src/prepare/index.js index f559c45..e7491ec 100644 --- a/src/prepare/index.js +++ b/src/prepare/index.js @@ -1,4 +1,29 @@ /** + * The prepare namespace provides renderer-specific plugins for pre-rendering DisplayObjects. These plugins are useful for + * asynchronously preparing assets, textures, graphics waiting to be displayed. + * + * Do not instantiate these plugins directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. + * @example + * // Create a new application + * const app = new PIXI.Application(); + * document.body.appendChild(app.view); + * + * // Don't start rendering right away + * app.stop(); + * + * // create a display object + * const rect = new PIXI.Graphics() + * .beginFill(0x00ff00) + * .drawRect(40, 40, 200, 200); + * + * // Add to the stage + * app.stage.addChild(rect); + * + * // Don't start rendering until the graphic is uploaded to the GPU + * app.renderer.plugins.prepare.upload(app.stage, () => { + * app.start(); + * }); * @namespace PIXI.prepare */ export { default as webgl } from './webgl/WebGLPrepare'; diff --git a/src/prepare/webgl/WebGLPrepare.js b/src/prepare/webgl/WebGLPrepare.js index eb7023f..9df3c3f 100644 --- a/src/prepare/webgl/WebGLPrepare.js +++ b/src/prepare/webgl/WebGLPrepare.js @@ -22,12 +22,11 @@ this.uploadHookHelper = this.renderer; // Add textures and graphics to upload - this.register(findBaseTextures, uploadBaseTextures) - .register(findGraphics, uploadGraphics); + this.registerFindHook(findGraphics); + this.registerUploadHook(uploadBaseTextures); + this.registerUploadHook(uploadGraphics); } - } - /** * Built-in hook to upload PIXI.Texture objects to the GPU. * @@ -80,41 +79,6 @@ } /** - * Built-in hook to find textures from Sprites. - * - * @private - * @param {PIXI.DisplayObject} item - Display object to check - * @param {Array<*>} queue - Collection of items to upload - * @return {boolean} if a PIXI.Texture object was found. - */ -function findBaseTextures(item, queue) -{ - // Objects with textures, like Sprites/Text - if (item instanceof core.BaseTexture) - { - if (queue.indexOf(item) === -1) - { - queue.push(item); - } - - return true; - } - else if (item._texture && item._texture instanceof core.Texture) - { - const texture = item._texture.baseTexture; - - if (queue.indexOf(texture) === -1) - { - queue.push(texture); - } - - return true; - } - - return false; -} - -/** * Built-in hook to find graphics. * * @private diff --git a/test/.eslintrc.json b/test/.eslintrc.json index e03220d..ac12110 100644 --- a/test/.eslintrc.json +++ b/test/.eslintrc.json @@ -12,7 +12,8 @@ "sinon": false, "expect": false, "assert": false, - "PIXI": false + "PIXI": false, + "PointerEvent": true }, "rules": { "func-names": 0, diff --git a/test/core/BaseTexture.js b/test/core/BaseTexture.js index 8b93478..bb65068 100644 --- a/test/core/BaseTexture.js +++ b/test/core/BaseTexture.js @@ -14,4 +14,28 @@ expect(baseTexture.imageType).to.be.equals('png'); }); }); + + it('should remove Canvas BaseTexture from cache on destroy', function () + { + const canvas = document.createElement('canvas'); + const texture = PIXI.BaseTexture.fromCanvas(canvas); + const _pixiId = canvas._pixiId; + + expect(PIXI.utils.BaseTextureCache[_pixiId]).to.equal(texture); + texture.destroy(); + expect(PIXI.utils.BaseTextureCache[_pixiId]).to.equal(undefined); + }); + + it('should remove Image BaseTexture from cache on destroy', function () + { + const URL = 'foo.png'; + const NAME = 'bar'; + const image = new Image(); + + const texture = PIXI.Texture.fromLoader(image, URL, NAME); + + expect(PIXI.utils.BaseTextureCache[NAME]).to.equal(texture.baseTexture); + texture.destroy(true); + expect(PIXI.utils.BaseTextureCache[NAME]).to.equal(undefined); + }); }); diff --git a/test/core/Circle.js b/test/core/Circle.js index 183f98f..f621992 100644 --- a/test/core/Circle.js +++ b/test/core/Circle.js @@ -53,6 +53,10 @@ expect(circ1.contains(10, 16)).to.be.false; expect(circ1.contains(11, 15)).to.be.false; expect(circ1.contains(0, 0)).to.be.false; + + const circ2 = new PIXI.Circle(10, 10, 0); + + expect(circ2.contains(10, 10)).to.be.false; }); it('should return framing rectangle', function () diff --git a/test/core/Container.js b/test/core/Container.js index d11be33..cefde54 100644 --- a/test/core/Container.js +++ b/test/core/Container.js @@ -2,30 +2,36 @@ function testAddChild(fn) { - fn(function (container, obj) + return function () { - container.addChild(obj); - }); - fn(function (container, obj) - { - container.addChildAt(obj); - }); + fn(function (container, obj) + { + container.addChild(obj); + }); + fn(function (container, obj) + { + container.addChildAt(obj); + }); + }; } function testRemoveChild(fn) { - fn(function (container, obj) + return function () { - container.removeChild(obj); - }); - fn(function (container, obj) - { - container.removeChildAt(container.children.indexOf(obj)); - }); - fn(function (container, obj) - { - container.removeChildren(container.children.indexOf(obj), container.children.indexOf(obj) + 1); - }); + fn(function (container, obj) + { + container.removeChild(obj); + }); + fn(function (container, obj) + { + container.removeChildAt(container.children.indexOf(obj)); + }); + fn(function (container, obj) + { + container.removeChildren(container.children.indexOf(obj), container.children.indexOf(obj) + 1); + }); + }; } describe('PIXI.Container', function () diff --git a/test/core/Ellipse.js b/test/core/Ellipse.js index f2e8234..3f47442 100644 --- a/test/core/Ellipse.js +++ b/test/core/Ellipse.js @@ -56,6 +56,10 @@ expect(ellipse1.contains(10, 16)).to.be.false; expect(ellipse1.contains(11, 15)).to.be.false; expect(ellipse1.contains(0, 0)).to.be.false; + + const ellipse2 = new PIXI.Ellipse(10, 10, 0, 0); + + expect(ellipse2.contains(10, 10)).to.be.false; }); it('should return framing rectangle', function () diff --git a/test/core/Matrix.js b/test/core/Matrix.js index 9d2694b..76bd57c 100644 --- a/test/core/Matrix.js +++ b/test/core/Matrix.js @@ -107,10 +107,60 @@ expect(m1.ty).to.equal(m2.ty); }); + it('should prepend matrix', function () + { + const m1 = new PIXI.Matrix(); + const m2 = new PIXI.Matrix(); + + m2.set(2, 3, 4, 5, 100, 200); + m1.prepend(m2); + + expect(m1.a).to.equal(m2.a); + expect(m1.b).to.equal(m2.b); + expect(m1.c).to.equal(m2.c); + expect(m1.d).to.equal(m2.d); + expect(m1.tx).to.equal(m2.tx); + expect(m1.ty).to.equal(m2.ty); + + const m3 = new PIXI.Matrix(); + const m4 = new PIXI.Matrix(); + + m3.prepend(m4); + + expect(m3.a).to.equal(m4.a); + expect(m3.b).to.equal(m4.b); + expect(m3.c).to.equal(m4.c); + expect(m3.d).to.equal(m4.d); + expect(m3.tx).to.equal(m4.tx); + expect(m3.ty).to.equal(m4.ty); + }); + it('should get IDENTITY and TEMP_MATRIX', function () { expect(PIXI.Matrix.IDENTITY instanceof PIXI.Matrix).to.be.true; expect(PIXI.Matrix.TEMP_MATRIX instanceof PIXI.Matrix).to.be.true; }); -}); + it('should reset matrix to default when identity() is called', function () + { + const matrix = new PIXI.Matrix(); + + matrix.set(2, 3, 4, 5, 100, 200); + + expect(matrix.a).to.equal(2); + expect(matrix.b).to.equal(3); + expect(matrix.c).to.equal(4); + expect(matrix.d).to.equal(5); + expect(matrix.tx).to.equal(100); + expect(matrix.ty).to.equal(200); + + matrix.identity(); + + expect(matrix.a).to.equal(1); + expect(matrix.b).to.equal(0); + expect(matrix.c).to.equal(0); + expect(matrix.d).to.equal(1); + expect(matrix.tx).to.equal(0); + expect(matrix.ty).to.equal(0); + }); +}); diff --git a/test/core/Spritesheet.js b/test/core/Spritesheet.js index 283adf5..7601a0c 100644 --- a/test/core/Spritesheet.js +++ b/test/core/Spritesheet.js @@ -18,6 +18,7 @@ expect(textures[id]).to.be.an.instanceof(PIXI.Texture); expect(textures[id].width).to.equal(spritesheet.data.frames[id].frame.w / spritesheet.resolution); expect(textures[id].height).to.equal(spritesheet.data.frames[id].frame.h / spritesheet.resolution); + expect(textures[id].textureCacheId).to.equal(id); spritesheet.destroy(true); expect(spritesheet.textures).to.be.null; expect(spritesheet.baseTexture).to.be.null; diff --git a/test/core/Texture.js b/test/core/Texture.js index 8c4ef98..241ec3e 100644 --- a/test/core/Texture.js +++ b/test/core/Texture.js @@ -16,4 +16,20 @@ expect(PIXI.utils.TextureCache[URL]).to.equal(texture); expect(PIXI.utils.BaseTextureCache[URL]).to.equal(texture.baseTexture); }); + + it('should remove Texture from cache on destroy', function () + { + const NAME = 'foo'; + const NAME2 = 'bar'; + const texture = new PIXI.Texture(new PIXI.BaseTexture()); + + PIXI.Texture.addTextureToCache(texture, NAME); + PIXI.Texture.addTextureToCache(texture, NAME2); + expect(texture.textureCacheId).to.equal(NAME); + expect(PIXI.utils.TextureCache[NAME]).to.equal(texture); + expect(PIXI.utils.TextureCache[NAME2]).to.equal(texture); + texture.destroy(); + expect(PIXI.utils.TextureCache[NAME]).to.equal(undefined); + expect(PIXI.utils.TextureCache[NAME2]).to.equal(undefined); + }); }); diff --git a/test/core/Ticker.js b/test/core/Ticker.js new file mode 100644 index 0000000..188770f --- /dev/null +++ b/test/core/Ticker.js @@ -0,0 +1,322 @@ +'use strict'; + +const Ticker = PIXI.ticker.Ticker; +const shared = PIXI.ticker.shared; + +describe('PIXI.ticker.Ticker', function () +{ + before(function () + { + this.length = (ticker) => + { + ticker = ticker || shared; + + if (!ticker._head || !ticker._head.next) + { + return 0; + } + + let listener = ticker._head.next; + let i = 0; + + while (listener) + { + listener = listener.next; + i++; + } + + return i; + }; + }); + + it('should be available', function () + { + expect(Ticker).to.be.a.function; + expect(shared).to.be.an.instanceof(Ticker); + }); + + it('should create a new ticker and destroy it', function () + { + const ticker = new Ticker(); + + ticker.start(); + + const listener = sinon.spy(); + + expect(this.length(ticker)).to.equal(0); + + ticker.add(listener); + + expect(this.length(ticker)).to.equal(1); + + ticker.destroy(); + + expect(ticker._head).to.be.null; + expect(ticker.started).to.be.false; + expect(this.length(ticker)).to.equal(0); + }); + + it('should protect destroying shared ticker', function () + { + shared.destroy(); + expect(shared._head).to.not.be.null; + expect(shared.started).to.be.true; + }); + + it('should add and remove listener', function () + { + const listener = sinon.spy(); + const length = this.length(); + + shared.add(listener); + + expect(this.length()).to.equal(length + 1); + + shared.remove(listener); + + expect(this.length()).to.equal(length); + }); + + it('should update a listener', function () + { + const listener = sinon.spy(); + + shared.add(listener); + shared.update(); + + expect(listener.calledOnce).to.be.true; + + shared.remove(listener); + shared.update(); + + expect(listener.calledOnce).to.be.true; + }); + + it('should update a listener twice and remove once', function () + { + const listener = sinon.spy(); + const length = this.length(); + + shared.add(listener).add(listener); + shared.update(); + + expect(listener.calledTwice).to.be.true; + expect(this.length()).to.equal(length + 2); + + shared.remove(listener); + shared.update(); + + expect(listener.calledTwice).to.be.true; + expect(this.length()).to.equal(length); + }); + + it('should respect priority order', function () + { + const length = this.length(); + const listener1 = sinon.spy(); + const listener2 = sinon.spy(); + const listener3 = sinon.spy(); + const listener4 = sinon.spy(); + + shared.add(listener1, null, PIXI.UPDATE_PRIORITY.LOW) + .add(listener4, null, PIXI.UPDATE_PRIORITY.INTERACTION) + .add(listener3, null, PIXI.UPDATE_PRIORITY.HIGH) + .add(listener2, null, PIXI.UPDATE_PRIORITY.NORMAL); + + shared.update(); + + expect(this.length()).to.equal(length + 4); + + sinon.assert.callOrder(listener4, listener3, listener2, listener1); + + shared.remove(listener1) + .remove(listener2) + .remove(listener3) + .remove(listener4); + + expect(this.length()).to.equal(length); + }); + + it('should auto-remove once listeners', function () + { + const length = this.length(); + const listener = sinon.spy(); + + shared.addOnce(listener); + + shared.update(); + + expect(listener.calledOnce).to.be.true; + expect(this.length()).to.equal(length); + }); + + it('should call inserted item with a lower priority', function () + { + const length = this.length(); + const lowListener = sinon.spy(); + const highListener = sinon.spy(); + const mainListener = sinon.spy(() => + { + shared.add(highListener, null, PIXI.UPDATE_PRIORITY.HIGH); + shared.add(lowListener, null, PIXI.UPDATE_PRIORITY.LOW); + }); + + shared.add(mainListener, null, PIXI.UPDATE_PRIORITY.NORMAL); + + shared.update(); + + expect(this.length()).to.equal(length + 3); + + expect(mainListener.calledOnce).to.be.true; + expect(lowListener.calledOnce).to.be.true; + expect(highListener.calledOnce).to.be.false; + + shared.remove(mainListener) + .remove(highListener) + .remove(lowListener); + + expect(this.length()).to.equal(length); + }); + + it('should remove emit low-priority item during emit', function () + { + const length = this.length(); + const listener2 = sinon.spy(); + const listener1 = sinon.spy(() => + { + shared.add(listener2, null, PIXI.UPDATE_PRIORITY.LOW); + }); + + shared.add(listener1, null, PIXI.UPDATE_PRIORITY.NORMAL); + + shared.update(); + + expect(this.length()).to.equal(length + 2); + + expect(listener2.calledOnce).to.be.true; + expect(listener1.calledOnce).to.be.true; + + shared.remove(listener1) + .remove(listener2); + + expect(this.length()).to.equal(length); + }); + + it('should remove itself on emit after adding new item', function () + { + const length = this.length(); + const listener2 = sinon.spy(); + const listener1 = sinon.spy(() => + { + shared.add(listener2, null, PIXI.UPDATE_PRIORITY.LOW); + shared.remove(listener1); + + // listener is removed right away + expect(this.length()).to.equal(length + 1); + }); + + shared.add(listener1, null, PIXI.UPDATE_PRIORITY.NORMAL); + + shared.update(); + + expect(this.length()).to.equal(length + 1); + + expect(listener2.calledOnce).to.be.true; + expect(listener1.calledOnce).to.be.true; + + shared.remove(listener2); + + expect(this.length()).to.equal(length); + }); + + it('should remove itself before, still calling new item', function () + { + const length = this.length(); + const listener2 = sinon.spy(); + const listener1 = sinon.spy(() => + { + shared.remove(listener1); + shared.add(listener2, null, PIXI.UPDATE_PRIORITY.LOW); + + // listener is removed right away + expect(this.length()).to.equal(length + 1); + }); + + shared.add(listener1, null, PIXI.UPDATE_PRIORITY.NORMAL); + + shared.update(); + + expect(this.length()).to.equal(length + 1); + + expect(listener2.calledOnce).to.be.true; + expect(listener1.calledOnce).to.be.true; + + shared.remove(listener2); + + expect(this.length()).to.equal(length); + }); + + it('should remove items before and after current priority', function () + { + const length = this.length(); + const listener2 = sinon.spy(); + const listener3 = sinon.spy(); + const listener4 = sinon.spy(); + + shared.add(listener2, null, PIXI.UPDATE_PRIORITY.HIGH); + shared.add(listener3, null, PIXI.UPDATE_PRIORITY.LOW); + shared.add(listener4, null, PIXI.UPDATE_PRIORITY.LOW); + + const listener1 = sinon.spy(() => + { + shared.remove(listener2) + .remove(listener3); + + // listener is removed right away + expect(this.length()).to.equal(length + 2); + }); + + shared.add(listener1, null, PIXI.UPDATE_PRIORITY.NORMAL); + + shared.update(); + + expect(this.length()).to.equal(length + 2); + + expect(listener2.calledOnce).to.be.true; + expect(listener3.calledOnce).to.be.false; + expect(listener4.calledOnce).to.be.true; + expect(listener1.calledOnce).to.be.true; + + shared.update(); + + expect(listener2.calledOnce).to.be.true; + expect(listener3.calledOnce).to.be.false; + expect(listener4.calledTwice).to.be.true; + expect(listener1.calledTwice).to.be.true; + + shared.remove(listener1) + .remove(listener4); + + expect(this.length()).to.equal(length); + }); + + it('should destroy on listener', function (done) + { + const ticker = new Ticker(); + const listener2 = sinon.spy(); + const listener = sinon.spy(() => + { + ticker.destroy(); + setTimeout(() => + { + expect(listener2.called).to.be.false; + expect(listener.calledOnce).to.be.true; + done(); + }, 0); + }); + + ticker.add(listener); + ticker.add(listener2, null, PIXI.UPDATE_PRIORITY.LOW); + ticker.start(); + }); +}); diff --git a/test/core/TilingSprite.js b/test/core/TilingSprite.js index 2e449a7..7694792 100644 --- a/test/core/TilingSprite.js +++ b/test/core/TilingSprite.js @@ -24,4 +24,25 @@ expect(bounds.height).to.equal(600); }); }); + + it('checks if tilingSprite contains a point', function () + { + const texture = new PIXI.Texture(new PIXI.BaseTexture()); + const tilingSprite = new PIXI.extras.TilingSprite(texture, 200, 300); + + expect(tilingSprite.containsPoint(new PIXI.Point(1, 1))).to.equal(true); + expect(tilingSprite.containsPoint(new PIXI.Point(300, 400))).to.equal(false); + }); + + it('gets and sets height and width correctly', function () + { + const texture = new PIXI.Texture(new PIXI.BaseTexture()); + const tilingSprite = new PIXI.extras.TilingSprite(texture, 200, 300); + + tilingSprite.width = 400; + tilingSprite.height = 600; + + expect(tilingSprite.width).to.equal(400); + expect(tilingSprite.height).to.equal(600); + }); }); diff --git a/test/core/index.js b/test/core/index.js index 8530131..58dc30e 100755 --- a/test/core/index.js +++ b/test/core/index.js @@ -17,6 +17,7 @@ require('./util'); require('./Plane'); require('./Point'); +require('./Polygon'); require('./ObservablePoint'); require('./Matrix'); require('./Rectangle'); @@ -27,4 +28,5 @@ require('./WebGLRenderer'); require('./Ellipse'); require('./Texture'); +require('./Ticker'); require('./filters'); diff --git a/test/interaction/InteractionManager.js b/test/interaction/InteractionManager.js index 0e7e707..31385b9 100644 --- a/test/interaction/InteractionManager.js +++ b/test/interaction/InteractionManager.js @@ -4,6 +4,16 @@ describe('PIXI.interaction.InteractionManager', function () { + afterEach(function () + { + // if we made a MockPointer for the test, clean it up + if (this.pointer) + { + this.pointer.cleanUp(); + this.pointer = null; + } + }); + describe('event basics', function () { it('should call mousedown handler', function () @@ -11,7 +21,7 @@ const stage = new PIXI.Container(); const graphics = new PIXI.Graphics(); const eventSpy = sinon.spy(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); stage.addChild(graphics); graphics.beginFill(0xFFFFFF); @@ -29,7 +39,7 @@ const stage = new PIXI.Container(); const graphics = new PIXI.Graphics(); const eventSpy = sinon.spy(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); stage.addChild(graphics); graphics.beginFill(0xFFFFFF); @@ -47,7 +57,7 @@ const stage = new PIXI.Container(); const graphics = new PIXI.Graphics(); const eventSpy = sinon.spy(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); stage.addChild(graphics); graphics.beginFill(0xFFFFFF); @@ -66,7 +76,7 @@ const stage = new PIXI.Container(); const graphics = new PIXI.Graphics(); const eventSpy = sinon.spy(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); stage.addChild(graphics); graphics.beginFill(0xFFFFFF); @@ -84,7 +94,7 @@ const stage = new PIXI.Container(); const graphics = new PIXI.Graphics(); const eventSpy = sinon.spy(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); stage.addChild(graphics); graphics.beginFill(0xFFFFFF); @@ -286,7 +296,7 @@ const stage = new PIXI.Container(); const graphics = new PIXI.Graphics(); const clickSpy = sinon.spy(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); stage.addChild(graphics); graphics.beginFill(0xFFFFFF); @@ -304,7 +314,7 @@ const stage = new PIXI.Container(); const graphics = new PIXI.Graphics(); const clickSpy = sinon.spy(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); stage.addChild(graphics); graphics.beginFill(0xFFFFFF); @@ -316,6 +326,29 @@ expect(clickSpy).to.not.have.been.called; }); + + it('should not call handler when mousedown not received', function () + { + const stage = new PIXI.Container(); + const graphics = new PIXI.Graphics(); + const clickSpy = sinon.spy(); + const pointer = new MockPointer(stage); + + stage.addChild(graphics); + graphics.beginFill(0xFFFFFF); + graphics.drawRect(0, 0, 50, 50); + graphics.interactive = true; + graphics.on('click', clickSpy); + + pointer.mouseup(10, 10); + + expect(clickSpy, 'click should not happen on first mouseup').to.not.have.been.called; + + // test again, just because it was a bug that was reported + pointer.mouseup(20, 20); + + expect(clickSpy, 'click should not happen on second mouseup').to.not.have.been.called; + }); }); describe('onTap', function () @@ -325,7 +358,7 @@ const stage = new PIXI.Container(); const graphics = new PIXI.Graphics(); const clickSpy = sinon.spy(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); stage.addChild(graphics); graphics.beginFill(0xFFFFFF); @@ -343,7 +376,7 @@ const stage = new PIXI.Container(); const graphics = new PIXI.Graphics(); const clickSpy = sinon.spy(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); stage.addChild(graphics); graphics.beginFill(0xFFFFFF); @@ -359,32 +392,62 @@ describe('overlapping children', function () { - function getScene(callbackEventName) + function getScene(callbackEventName, splitParents) { const behindChild = new PIXI.Graphics(); const frontChild = new PIXI.Graphics(); const parent = new PIXI.Container(); - const behindChildCallback = sinon.spy(); - const frontChildCallback = sinon.spy(); - const parentCallback = sinon.spy(); + const behindChildCallback = sinon.spy(function behindSpy() { /* no op*/ }); + const frontChildCallback = sinon.spy(function frontSpy() { /* no op*/ }); + const parentCallback = sinon.spy(function parentSpy() { /* no op*/ }); + let behindParent; + let frontParent; + let behindParentCallback; + let frontParentCallback; behindChild.beginFill(0xFF); behindChild.drawRect(0, 0, 50, 50); behindChild.on(callbackEventName, behindChildCallback); + behindChild.name = 'behind'; frontChild.beginFill(0x00FF); frontChild.drawRect(0, 0, 50, 50); frontChild.on(callbackEventName, frontChildCallback); + frontChild.name = 'front'; - parent.addChild(behindChild, frontChild); + if (splitParents) + { + behindParent = new PIXI.Container(); + behindParent.name = 'behindParent'; + frontParent = new PIXI.Container(); + frontParent.name = 'frontParent'; + behindParentCallback = sinon.spy(function behindParentSpy() { /* no op*/ }); + frontParentCallback = sinon.spy(function frontParentSpy() { /* no op*/ }); + behindParent.on(callbackEventName, behindParentCallback); + frontParent.on(callbackEventName, frontParentCallback); + + parent.addChild(behindParent, frontParent); + behindParent.addChild(behindChild); + frontParent.addChild(frontChild); + + parent.name = 'parent'; + } + else + { + parent.addChild(behindChild, frontChild); + } parent.on(callbackEventName, parentCallback); return { behindChild, frontChild, + behindParent, + frontParent, parent, behindChildCallback, frontChildCallback, + behindParentCallback, + frontParentCallback, parentCallback, }; } @@ -396,7 +459,7 @@ it('should callback front child when clicking front child', function () { const stage = new PIXI.Container(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); const scene = getScene('click'); scene.behindChild.interactive = true; @@ -414,7 +477,7 @@ it('should callback front child when clicking overlap', function () { const stage = new PIXI.Container(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); const scene = getScene('click'); scene.behindChild.interactive = true; @@ -432,7 +495,7 @@ it('should callback behind child when clicking behind child', function () { const stage = new PIXI.Container(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); const scene = getScene('click'); scene.behindChild.interactive = true; @@ -446,6 +509,48 @@ expect(scene.behindChildCallback).to.have.been.calledOnce; expect(scene.parentCallback).to.not.have.been.called; }); + + it('should callback front child of different non-interactive parents when clicking overlap', function () + { + const stage = new PIXI.Container(); + const pointer = this.pointer = new MockPointer(stage); + const scene = getScene('click', true); + + scene.behindChild.interactive = true; + scene.behindChild.x = 25; + scene.frontChild.interactive = true; + + stage.addChild(scene.parent); + pointer.click(40, 10); + + expect(scene.behindChildCallback).to.not.have.been.called; + expect(scene.frontChildCallback).to.have.been.calledOnce; + expect(scene.parentCallback).to.not.have.been.called; + expect(scene.behindParentCallback).to.not.have.been.called; + expect(scene.frontParentCallback).to.not.have.been.called; + }); + + it('should callback front child of different interactive parents when clicking overlap', function () + { + const stage = new PIXI.Container(); + const pointer = this.pointer = new MockPointer(stage); + const scene = getScene('click', true); + + scene.behindChild.interactive = true; + scene.behindChild.x = 25; + scene.frontChild.interactive = true; + scene.behindParent.interactive = true; + scene.frontParent.interactive = true; + + stage.addChild(scene.parent); + pointer.click(40, 10); + + expect(scene.behindChildCallback).to.not.have.been.called; + expect(scene.frontChildCallback).to.have.been.calledOnce; + expect(scene.parentCallback).to.not.have.been.called; + expect(scene.behindParentCallback).to.not.have.been.called; + expect(scene.frontParentCallback).to.have.been.calledOnce; + }); }); describe('when front child is non-interactive', function () @@ -453,7 +558,7 @@ it('should not callback when clicking front child', function () { const stage = new PIXI.Container(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); const scene = getScene('click'); scene.behindChild.interactive = true; @@ -470,7 +575,7 @@ it('should callback behind child when clicking overlap', function () { const stage = new PIXI.Container(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); const scene = getScene('click'); scene.behindChild.interactive = true; @@ -487,7 +592,7 @@ it('should callback behind child when clicking behind child', function () { const stage = new PIXI.Container(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); const scene = getScene('click'); scene.behindChild.interactive = true; @@ -507,7 +612,7 @@ it('should callback front child when clicking front child', function () { const stage = new PIXI.Container(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); const scene = getScene('click'); scene.behindChild.x = 25; @@ -524,7 +629,7 @@ it('should callback front child when clicking overlap', function () { const stage = new PIXI.Container(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); const scene = getScene('click'); scene.behindChild.x = 25; @@ -541,7 +646,7 @@ it('should not callback when clicking behind child', function () { const stage = new PIXI.Container(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); const scene = getScene('click'); scene.behindChild.x = 25; @@ -564,7 +669,7 @@ it('should callback parent and front child when clicking front child', function () { const stage = new PIXI.Container(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); const scene = getScene('click'); scene.behindChild.interactive = true; @@ -583,7 +688,7 @@ it('should callback parent and front child when clicking overlap', function () { const stage = new PIXI.Container(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); const scene = getScene('click'); scene.behindChild.interactive = true; @@ -602,7 +707,7 @@ it('should callback parent and behind child when clicking behind child', function () { const stage = new PIXI.Container(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); const scene = getScene('click'); scene.behindChild.interactive = true; @@ -617,6 +722,50 @@ expect(scene.behindChildCallback).to.have.been.calledOnce; expect(scene.parentCallback).to.have.been.calledOnce; }); + + it('should callback front child of different non-interactive parents when clicking overlap', function () + { + const stage = new PIXI.Container(); + const pointer = this.pointer = new MockPointer(stage); + const scene = getScene('click', true); + + scene.behindChild.interactive = true; + scene.behindChild.x = 25; + scene.frontChild.interactive = true; + scene.parent.interactive = true; + + stage.addChild(scene.parent); + pointer.click(40, 10); + + expect(scene.behindChildCallback).to.not.have.been.called; + expect(scene.frontChildCallback).to.have.been.calledOnce; + expect(scene.parentCallback).to.have.been.calledOnce; + expect(scene.behindParentCallback).to.not.have.been.called; + expect(scene.frontParentCallback).to.not.have.been.called; + }); + + it('should callback front child of different interactive parents when clicking overlap', function () + { + const stage = new PIXI.Container(); + const pointer = this.pointer = new MockPointer(stage); + const scene = getScene('click', true); + + scene.behindChild.interactive = true; + scene.behindChild.x = 25; + scene.frontChild.interactive = true; + scene.parent.interactive = true; + scene.behindParent.interactive = true; + scene.frontParent.interactive = true; + + stage.addChild(scene.parent); + pointer.click(40, 10); + + expect(scene.behindChildCallback).to.not.have.been.called; + expect(scene.frontChildCallback).to.have.been.calledOnce; + expect(scene.parentCallback).to.have.been.calledOnce; + expect(scene.behindParentCallback).to.not.have.been.called; + expect(scene.frontParentCallback).to.have.been.calledOnce; + }); }); describe('when front child is non-interactive', function () @@ -624,7 +773,7 @@ it('should callback parent when clicking front child', function () { const stage = new PIXI.Container(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); const scene = getScene('click'); scene.behindChild.interactive = true; @@ -642,7 +791,7 @@ it('should callback parent and behind child when clicking overlap', function () { const stage = new PIXI.Container(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); const scene = getScene('click'); scene.behindChild.interactive = true; @@ -660,7 +809,7 @@ it('should callback parent and behind child when clicking behind child', function () { const stage = new PIXI.Container(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); const scene = getScene('click'); scene.behindChild.interactive = true; @@ -681,7 +830,7 @@ it('should callback parent and front child when clicking front child', function () { const stage = new PIXI.Container(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); const scene = getScene('click'); scene.behindChild.x = 25; @@ -699,7 +848,7 @@ it('should callback parent and front child when clicking overlap', function () { const stage = new PIXI.Container(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); const scene = getScene('click'); scene.behindChild.x = 25; @@ -717,7 +866,7 @@ it('should callback parent when clicking behind child', function () { const stage = new PIXI.Container(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); const scene = getScene('click'); scene.behindChild.x = 25; @@ -733,6 +882,42 @@ }); }); }); + + it('Semi-complicated nesting with overlap, should not call behind callback', function () + { + const stage = new PIXI.Container(); + const frontParent = new PIXI.Container(); + const frontChild = new PIXI.Graphics(); + const behindParent = new PIXI.Container(); + const subParent = new PIXI.Container(); + const behindChild = new PIXI.Graphics(); + const behindCallback = sinon.spy(function behindSpy() { /* no op*/ }); + const frontCallback = sinon.spy(function frontSpy() { /* no op*/ }); + + behindChild.beginFill(0xFF); + behindChild.drawRect(0, 0, 50, 50); + subParent.on('click', behindCallback); + + frontChild.beginFill(0x00FF); + frontChild.drawRect(0, 0, 50, 50); + frontParent.on('click', frontCallback); + const pointer = this.pointer = new MockPointer(stage); + + behindParent.x = 25; + subParent.interactive = true; + frontParent.interactive = true; + + behindParent.addChild(subParent); + subParent.addChild(behindChild); + stage.addChild(behindParent); + frontParent.addChild(frontChild); + stage.addChild(frontParent); + + pointer.click(40, 10); + + expect(behindCallback).to.not.have.been.called; + expect(frontCallback).to.have.been.calledOnce; + }); }); describe('cursor changes', function () @@ -741,7 +926,7 @@ { const stage = new PIXI.Container(); const graphics = new PIXI.Graphics(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); stage.addChild(graphics); graphics.beginFill(0xFFFFFF); @@ -759,7 +944,7 @@ { const stage = new PIXI.Container(); const graphics = new PIXI.Graphics(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); stage.addChild(graphics); graphics.beginFill(0xFFFFFF); @@ -778,7 +963,7 @@ { const stage = new PIXI.Container(); const graphics = new PIXI.Graphics(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); stage.addChild(graphics); graphics.beginFill(0xFFFFFF); @@ -797,7 +982,7 @@ { const stage = new PIXI.Container(); const graphics = new PIXI.Graphics(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); stage.addChild(graphics); graphics.beginFill(0xFFFFFF); @@ -818,7 +1003,7 @@ const graphics = new PIXI.Graphics(); const overSpy = sinon.spy(); const defaultSpy = sinon.spy(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); stage.addChild(graphics); graphics.beginFill(0xFFFFFF); @@ -840,7 +1025,7 @@ const stage = new PIXI.Container(); const graphics = new PIXI.Graphics(); const defaultSpy = sinon.spy(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); stage.addChild(graphics); graphics.beginFill(0xFFFFFF); @@ -859,7 +1044,7 @@ { const stage = new PIXI.Container(); const graphics = new PIXI.Graphics(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); stage.addChild(graphics); graphics.beginFill(0xFFFFFF); @@ -881,7 +1066,7 @@ { const stage = new PIXI.Container(); const graphics = new PIXI.Graphics(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); stage.addChild(graphics); graphics.beginFill(0xFFFFFF); @@ -932,7 +1117,7 @@ it('should stop hitTesting after first hit', function () { const scene = getScene(); - const pointer = new MockPointer(scene.stage); + const pointer = this.pointer = new MockPointer(scene.stage); const frontHitTest = sinon.spy(scene.frontChild, 'containsPoint'); const middleHitTest = sinon.spy(scene.middleChild, 'containsPoint'); const behindHitTest = sinon.spy(scene.behindChild, 'containsPoint'); @@ -954,7 +1139,7 @@ it('should stop hitTesting after first hit', function () { const scene = getScene(); - const pointer = new MockPointer(scene.stage); + const pointer = this.pointer = new MockPointer(scene.stage); const frontHitTest = sinon.spy(scene.frontChild, 'containsPoint'); const middleHitTest = sinon.spy(scene.middleChild, 'containsPoint'); const behindHitTest = sinon.spy(scene.behindChild, 'containsPoint'); @@ -971,4 +1156,127 @@ }); }); }); + + describe('pointer handling', function () + { + it('pointer event from mouse should use single mouse data', function () + { + const stage = new PIXI.Container(); + const graphics = new PIXI.Graphics(); + const pointer = this.pointer = new MockPointer(stage, 100, 100, true); + + stage.addChild(graphics); + graphics.beginFill(0xFFFFFF); + graphics.drawRect(0, 0, 50, 50); + graphics.interactive = true; + + pointer.mousemove(20, 10, true); + + expect(pointer.interaction.mouse.global.x).to.equal(20); + expect(pointer.interaction.mouse.global.y).to.equal(10); + }); + }); + + describe('data cleanup', function () + { + it('touchleave after touchout should not orphan data', 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, 42); + expect(pointer.interaction.activeInteractionData[42]).to.exist; + pointer.touchend(10, 10, 42); + expect(pointer.interaction.activeInteractionData[42]).to.be.undefined; + pointer.touchleave(10, 10, 42); + expect(pointer.interaction.activeInteractionData[42]).to.be.undefined; + }); + }); + + describe('hitTest()', function () + { + it('should return hit', 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.render(); + const hit = pointer.interaction.hitTest(new PIXI.Point(10, 10)); + + expect(hit).to.equal(graphics); + }); + + it('should return null if not hit', 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.render(); + const hit = pointer.interaction.hitTest(new PIXI.Point(60, 60)); + + expect(hit).to.be.null; + }); + + it('should return top thing that was hit', function () + { + const stage = new PIXI.Container(); + const graphics = new PIXI.Graphics(); + const behind = new PIXI.Graphics(); + const pointer = this.pointer = new MockPointer(stage); + + stage.addChild(behind); + stage.addChild(graphics); + graphics.beginFill(0xFFFFFF); + graphics.drawRect(0, 0, 50, 50); + graphics.interactive = true; + behind.beginFill(0xFFFFFF); + behind.drawRect(0, 0, 50, 50); + behind.interactive = true; + + pointer.render(); + const hit = pointer.interaction.hitTest(new PIXI.Point(10, 10)); + + expect(hit).to.equal(graphics); + }); + + it('should return hit when passing in root', function () + { + const stage = new PIXI.Container(); + const graphics = new PIXI.Graphics(); + const behind = new PIXI.Graphics(); + const pointer = this.pointer = new MockPointer(stage); + + stage.addChild(behind); + stage.addChild(graphics); + graphics.beginFill(0xFFFFFF); + graphics.drawRect(0, 0, 50, 50); + graphics.interactive = true; + behind.beginFill(0xFFFFFF); + behind.drawRect(0, 0, 50, 50); + behind.interactive = true; + + pointer.render(); + const hit = pointer.interaction.hitTest(new PIXI.Point(10, 10), behind); + + expect(hit).to.equal(behind); + }); + }); }); diff --git a/test/interaction/MockPointer.js b/test/interaction/MockPointer.js index 67878af..c9c6228 100644 --- a/test/interaction/MockPointer.js +++ b/test/interaction/MockPointer.js @@ -11,13 +11,43 @@ * @param {PIXI.Container} stage - The root of the scene tree * @param {number} [width=100] - Width of the renderer * @param {number} [height=100] - Height of the renderer + * @param {boolean} [ensurePointerEvents=false] - If we should make sure that PointerEvents are 'supported' */ - constructor(stage, width, height) + constructor(stage, width, height, ensurePointerEvents) { + // fake PointerEvent existing + if (ensurePointerEvents && !window.PointerEvent) + { + window.PointerEvent = class PointerEvent extends MouseEvent + { + //eslint-disable-next-line + constructor(type, opts) + { + super(type, opts); + this.pointerType = opts.pointerType; + } + }; + this.createdPointerEvent = true; + } + this.stage = stage; this.renderer = new PIXI.CanvasRenderer(width || 100, height || 100); this.renderer.sayHello = () => { /* empty */ }; this.interaction = this.renderer.plugins.interaction; + this.interaction.supportsTouchEvents = true; + PIXI.ticker.shared.remove(this.interaction.update, this.interaction); + } + + /** + * Cleans up after tests + */ + cleanup() + { + if (this.createdPointerEvent) + { + delete window.PointerEvent; + } + this.renderer.destroy(); } /** @@ -45,14 +75,29 @@ /** * @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) + mousemove(x, y, asPointer) { - const mouseEvent = new MouseEvent('mousemove', { - clientX: x, - clientY: y, - preventDefault: sinon.stub(), - }); + 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(), + }); + } this.setPosition(x, y); this.render(); @@ -97,9 +142,15 @@ */ mousedown(x, y) { + const mouseEvent = new MouseEvent('mousedown', { + clientX: x, + clientY: y, + preventDefault: sinon.stub(), + }); + this.setPosition(x, y); this.render(); - this.interaction.onMouseDown({ clientX: 0, clientY: 0, preventDefault: sinon.stub() }); + this.interaction.onPointerDown(mouseEvent); } /** @@ -108,47 +159,83 @@ */ mouseup(x, y) { - this.setPosition(x, y); - this.render(); - this.interaction.onMouseUp({ clientX: 0, clientY: 0, preventDefault: sinon.stub() }); - } - - /** - * @param {number} x - pointer x position - * @param {number} y - pointer y position - */ - tap(x, y) - { - this.touchstart(x, y); - this.touchend(x, y); - } - - /** - * @param {number} x - pointer x position - * @param {number} y - pointer y position - */ - touchstart(x, y) - { - this.setPosition(x, y); - this.render(); - this.interaction.onTouchStart({ + const mouseEvent = new MouseEvent('mouseup', { + clientX: x, + clientY: y, preventDefault: sinon.stub(), - changedTouches: [new Touch({ identifier: 0, target: this.renderer.view })], }); + + 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 */ - touchend(x, y) + tap(x, y, identifier) { + this.touchstart(x, y, identifier); + this.touchend(x, y, identifier); + } + + /** + * @param {number} x - pointer x position + * @param {number} y - pointer y position + * @param {number} [identifier] - pointer id + */ + touchstart(x, y, identifier) + { + const touchEvent = new TouchEvent('touchstart', { + preventDefault: sinon.stub(), + changedTouches: [ + new Touch({ identifier: identifier || 0, target: this.renderer.view }), + ], + }); + this.setPosition(x, y); this.render(); - this.interaction.onTouchEnd({ + this.interaction.onPointerDown(touchEvent); + } + + /** + * @param {number} x - pointer x position + * @param {number} y - pointer y position + * @param {number} [identifier] - pointer id + */ + touchend(x, y, identifier) + { + const touchEvent = new TouchEvent('touchend', { preventDefault: sinon.stub(), - changedTouches: [new Touch({ identifier: 0, target: this.renderer.view })], + changedTouches: [ + new Touch({ identifier: identifier || 0, target: 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 }), + ], + }); + + this.setPosition(x, y); + this.render(); + this.interaction.onPointerOut(touchEvent); } } diff --git a/test/loaders/spritesheetParser.js b/test/loaders/spritesheetParser.js index 79f8c15..4d54805 100644 --- a/test/loaders/spritesheetParser.js +++ b/test/loaders/spritesheetParser.js @@ -60,11 +60,46 @@ .that.is.an.instanceof(PIXI.Texture); }); + it('should build the image url', function () + { + function getResourcePath(url, image) + { + return PIXI.loaders.getResourcePath({ + url, + data: { meta: { image } }, + }); + } + + let result = getResourcePath('http://some.com/spritesheet.json', 'img.png'); + + expect(result).to.be.equals('http://some.com/img.png'); + + result = getResourcePath('http://some.com/some/dir/spritesheet.json', 'img.png'); + expect(result).to.be.equals('http://some.com/some/dir/img.png'); + + result = getResourcePath('http://some.com/some/dir/spritesheet.json', './img.png'); + expect(result).to.be.equals('http://some.com/some/dir/img.png'); + + result = getResourcePath('http://some.com/some/dir/spritesheet.json', '../img.png'); + expect(result).to.be.equals('http://some.com/some/img.png'); + + result = getResourcePath('/spritesheet.json', 'img.png'); + expect(result).to.be.equals('/img.png'); + + result = getResourcePath('/some/dir/spritesheet.json', 'img.png'); + expect(result).to.be.equals('/some/dir/img.png'); + + result = getResourcePath('/some/dir/spritesheet.json', './img.png'); + expect(result).to.be.equals('/some/dir/img.png'); + + result = getResourcePath('/some/dir/spritesheet.json', '../img.png'); + expect(result).to.be.equals('/some/img.png'); + }); + // TODO: Test that rectangles are created correctly. // TODO: Test that bathc processing works correctly. // TODO: Test that resolution processing works correctly. // TODO: Test that metadata is honored. - // TODO: Test data-url code paths. }); function createMockResource(type, data) diff --git a/.travis.yml b/.travis.yml index 3df8b22..14de334 100644 --- a/.travis.yml +++ b/.travis.yml @@ -110,3 +110,43 @@ on: all_branches: true condition: $TRAVIS_TAG + # Deploy config for latest release + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: dist + upload-dir: "release" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: docs + upload-dir: "release/docs" + on: + all_branches: true + condition: $TRAVIS_TAG + - provider: s3 + access_key_id: $S3_ACCESS_KEY_ID + secret_access_key: $S3_SECRET_ACCESS_KEY + bucket: "pixi.js" + skip_cleanup: true + acl: public_read + region: eu-west-1 + cache_control: "max-age=1209600" + local_dir: coverage + upload-dir: "release/coverage" + on: + all_branches: true + condition: $TRAVIS_TAG \ No newline at end of file diff --git a/README.md b/README.md index d26fadd..3b85f85 100644 --- a/README.md +++ b/README.md @@ -36,22 +36,14 @@ - Wiki: Other misc tutorials and resources are [on the Wiki](https://github.com/pixijs/pixi.js/wiki/Resources). ### Community ### -- Forums: Check out the [forum] (http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow] (http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. +- Forums: Check out the [forum](http://www.html5gamedevs.com/forum/15-pixijs/) and [Stackoverflow](http://stackoverflow.com/search?q=pixi.js), both friendly places to ask your pixi questions. - Inspiration: Check out the [gallery](http://www.pixijs.com/gallery) to see some of the amazing things people have created! - Chat: You can join us on [Gitter](https://gitter.im/pixijs/pixi.js) To chat about Pixi. We also now have a Slack channel. If you would like to join it please Send me an email (mat@goodboydigital.com) and I will invite you in. ### Setup ### -It's easy to get started with Pixi.js! Simply grab the pre-built versions from here: - -Release Branch - Nice and stable Pixi.js -- Unminified: [http://pixijs.download/release/pixi.js] -- Minified: [http://pixijs.download/release/pixi.min.js] - -Develop Branch - The bleeding edge version of Pixi.js -- Unminified: [http://pixijs.download/dev/pixi.js] -- Minified: [http://pixijs.download/dev/pixi.min.js] +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. @@ -70,10 +62,10 @@ #### CDN Install (via cdnjs) ```html - + ``` -_Note: `4.2.2` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ +_Note: `4.4.3` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ ### Demos ### @@ -130,7 +122,7 @@ // and the root stage PIXI.Container. var app = new PIXI.Application(); -// The application will create a canvas element for you that you +// The application will create a canvas element for you that you // can then insert into the DOM. document.body.appendChild(app.view); @@ -150,7 +142,7 @@ // Add the bunny to the scene we are building. app.stage.addChild(bunny); - + // Listen for frame updates app.ticker.add(function() { // each frame we spin the bunny around a bit diff --git a/bower.json b/bower.json deleted file mode 100644 index 41b668c..0000000 --- a/bower.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "pixi.js", - "main": "dist/pixi.min.js", - "ignore": [ - "**/.*", - "docs", - "node_modules", - "bower_components", - "test", - "scripts", - "bower.json", - "inch.json" - ], - "dependencies": { - }, - "devDependencies": { - } -} diff --git a/package.json b/package.json index d35cbb2..308469f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.3.5", + "version": "4.4.4", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ @@ -40,11 +40,11 @@ "lib": "babel src --out-dir lib -s", "predocs": "rimraf docs/**", "docs": "jsdoc -c scripts/jsdoc.conf.json -R README.md", - "publish:patch": "npm version patch --no-git-tag-version && npm publish", - "publish:minor": "npm version minor --no-git-tag-version && npm publish", - "publish:major": "npm version major --no-git-tag-version && npm publish", + "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", - "postpublish": "node scripts/release.js" + "postpublish": "git push && git push --tags" }, "files": [ "dist/", @@ -72,7 +72,6 @@ "electron": "^1.4.15", "eslint": "^3.5.0", "floss": "^2.0.1", - "gh-pages": "^0.11.0", "jaguarjs-jsdoc": "^1.0.1", "js-md5": "^0.4.1", "jsdoc": "^3.4.2", diff --git a/scripts/jsdoc.conf.json b/scripts/jsdoc.conf.json index 553e61c..3f52180 100644 --- a/scripts/jsdoc.conf.json +++ b/scripts/jsdoc.conf.json @@ -43,7 +43,7 @@ }, "markdown" : { "parser" : "gfm", - "hardwrap" : true + "hardwrap" : false }, "opts": { "encoding" : "utf8", diff --git a/scripts/release.js b/scripts/release.js deleted file mode 100644 index 156620c..0000000 --- a/scripts/release.js +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env node -'use strict'; - -// Publish script to push releases of the bin files -// the normally are gitignored -const ghpages = require('gh-pages'); -const path = require('path'); -const packageInfo = require(path.join(__dirname, '..', 'package.json')); - -const options = { - src: [ - 'dist/**/*', - 'lib/**/*', - 'src/**/*', - 'scripts/*', - 'scripts/renders/*', - 'test/**/*', - '*.json', - '*.md', - 'LICENSE', - '.eslintrc', - '.editorconfig', - '.travis.yml', - '.babelrc', - ], - dotfiles: true, - branch: 'release', - message: packageInfo.version, - logger: console.log.bind(console), -}; - -ghpages.publish(process.cwd(), options, (err) => -{ - if (err) - { - console.log(err); - process.exit(1); - - return; - } - - process.exit(0); -}); diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index f16bcb1..220427a 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -21,7 +21,7 @@ const DIV_HOOK_ZINDEX = 2; /** - * The Accessibility manager reacreates the ability to tab and and have content read by screen + * The Accessibility manager recreates the ability to tab and and have content read by screen * readers. This is very important as it can possibly help people with disabilities access pixi * content. * diff --git a/src/accessibility/index.js b/src/accessibility/index.js index 568cfb1..0ae108f 100644 --- a/src/accessibility/index.js +++ b/src/accessibility/index.js @@ -1,4 +1,9 @@ /** + * This namespace contains a renderer plugin for interaction accessibility for end-users + * with physical impairments which require screen-renders, keyboard navigation, etc. + * + * Do not instantiate this plugin directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. * @namespace PIXI.accessibility */ export { default as accessibleTarget } from './accessibleTarget'; diff --git a/src/core/Application.js b/src/core/Application.js index 5340578..2fcc729 100644 --- a/src/core/Application.js +++ b/src/core/Application.js @@ -1,6 +1,8 @@ import { autoDetectRenderer } from './autoDetectRenderer'; import Container from './display/Container'; import { shared, Ticker } from './ticker'; +import settings from './settings'; +import { UPDATE_PRIORITY } from './const'; /** * Convenience class to create a new PIXI application. @@ -22,28 +24,52 @@ */ export default class Application { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experience unexplained flickering try setting this to true. - * @param {boolean} [useSharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedTicker=false] - `true` to use PIXI.ticker.shared, `false` to create new ticker. + * @param {boolean} [options.sharedLoader=false] - `true` to use PIXI.loaders.shared, `false` to create new Loader. */ - constructor(width, height, options, noWebGL, useSharedTicker = false) + constructor(options, arg2, arg3, arg4, arg5) { + // Support for constructor(width, height, options, noWebGL, useSharedTicker) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + forceCanvas: !!arg4, + sharedTicker: !!arg5, + }, arg3); + } + + /** + * The default options, so we mixin functionality later. + * @member {object} + * @protected + */ + this._options = options = Object.assign({ + sharedTicker: false, + forceCanvas: false, + sharedLoader: false, + }, options); + /** * WebGL renderer if available, otherwise CanvasRenderer * @member {PIXI.WebGLRenderer|PIXI.CanvasRenderer} */ - this.renderer = autoDetectRenderer(width, height, options, noWebGL); + this.renderer = autoDetectRenderer(options); /** * The root display container that's rendered. @@ -63,7 +89,7 @@ * @member {PIXI.ticker.Ticker} * @default PIXI.ticker.shared */ - this.ticker = useSharedTicker ? shared : new Ticker(); + this.ticker = options.sharedTicker ? shared : new Ticker(); // Start the rendering this.start(); @@ -78,7 +104,7 @@ this._ticker = ticker; if (ticker) { - ticker.add(this.render, this); + ticker.add(this.render, this, UPDATE_PRIORITY.LOW); } } get ticker() // eslint-disable-line require-jsdoc @@ -136,13 +162,18 @@ */ destroy(removeView) { - this.stop(); + const oldTicker = this._ticker; + this.ticker = null; + oldTicker.destroy(); + this.stage.destroy(); this.stage = null; this.renderer.destroy(removeView); this.renderer = null; + + this._options = null; } } diff --git a/src/core/autoDetectRenderer.js b/src/core/autoDetectRenderer.js index 8960425..9ec0364 100644 --- a/src/core/autoDetectRenderer.js +++ b/src/core/autoDetectRenderer.js @@ -2,6 +2,7 @@ import CanvasRenderer from './renderers/canvas/CanvasRenderer'; import WebGLRenderer from './renderers/webgl/WebGLRenderer'; +// eslint-disable-next-line valid-jsdoc /** * This helper function will automatically detect which renderer you should be using. * WebGL is the preferred renderer as it is a lot faster. If webGL is not supported by @@ -9,24 +10,32 @@ * * @memberof PIXI * @function autoDetectRenderer - * @param {number} [width=800] - the width of the renderers view - * @param {number} [height=600] - the height of the renderers view * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the renderers view + * @param {number} [options.height=600] - the height of the renderers view * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, enable this if you * need to call toDataUrl on the webgl context * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer, retina would be 2 - * @param {boolean} [noWebGL=false] - prevents selection of WebGL renderer, even if such is present + * @param {boolean} [options.forceCanvas=false] - prevents selection of WebGL renderer, even if such is present * @return {PIXI.WebGLRenderer|PIXI.CanvasRenderer} Returns WebGL renderer if available, otherwise CanvasRenderer */ -export function autoDetectRenderer(width = 800, height = 600, options, noWebGL) +export function autoDetectRenderer(options, arg1, arg2, arg3) { - if (!noWebGL && utils.isWebGLSupported()) + // Backward-compatible support for noWebGL option + let forceCanvas = options && options.forceCanvas; + + if (arg3 !== undefined) { - return new WebGLRenderer(width, height, options); + forceCanvas = arg3; } - return new CanvasRenderer(width, height, options); + if (!forceCanvas && utils.isWebGLSupported()) + { + return new WebGLRenderer(options, arg1, arg2); + } + + return new CanvasRenderer(options, arg1, arg2); } diff --git a/src/core/const.js b/src/core/const.js index b9e99ae..2c68dee 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -341,3 +341,27 @@ LINEAR_VERTICAL: 0, LINEAR_HORIZONTAL: 1, }; + +/** + * Represents the update priorities used by internal PIXI classes when registered with + * the {@link PIXI.ticker.Ticker} object. Higher priority items are updated first and lower + * priority items, such as render, should go later. + * + * @static + * @constant + * @name UPDATE_PRIORITY + * @memberof PIXI + * @type {object} + * @property {number} INTERACTION=50 Highest priority, used for {@link PIXI.interaction.InteractionManager} + * @property {number} HIGH=25 High priority updating, {@link PIXI.VideoBaseTexture} and {@link PIXI.extras.AnimatedSprite} + * @property {number} NORMAL=0 Default priority for ticker events, see {@link PIXI.ticker.Ticker#add}. + * @property {number} LOW=-25 Low priority used for {@link PIXI.Application} rendering. + * @property {number} UTILITY=-50 Lowest priority used for {@link PIXI.prepare.BasePrepare} utility. + */ +export const UPDATE_PRIORITY = { + INTERACTION: 50, + HIGH: 25, + NORMAL: 0, + LOW: -25, + UTILITY: -50, +}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 4e6c77d..decb42a 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -13,7 +13,6 @@ * * @class * @extends EventEmitter - * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ export default class DisplayObject extends EventEmitter @@ -113,6 +112,29 @@ * @private */ this._mask = null; + + /** + * If the object has been destroyed via destroy(). If true, it should not be used. + * + * @member {boolean} + * @private + * @readonly + */ + this._destroyed = false; + + /** + * Fired when this DisplayObject is added to a Container. + * + * @event PIXI.DisplayObject#added + * @param {PIXI.Container} container - The container added to. + */ + + /** + * Fired when this DisplayObject is removed from a Container. + * + * @event PIXI.DisplayObject#removed + * @param {PIXI.Container} container - The container removed from. + */ } /** @@ -403,6 +425,8 @@ this.interactive = false; this.interactiveChildren = false; + + this._destroyed = true; } /** diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index aafdfe2..f787c4a 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -792,8 +792,8 @@ sprite.worldAlpha = this.worldAlpha * sprite.alpha; sprite.blendMode = this.blendMode; - sprite.texture._frame.width = rect.width; - sprite.texture._frame.height = rect.height; + sprite._texture._frame.width = rect.width; + sprite._texture._frame.height = rect.height; sprite.transform.worldTransform = this.transform.worldTransform; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index a24c775..b9f192d 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -19,11 +19,12 @@ */ export default class SystemRenderer extends EventEmitter { + // eslint-disable-next-line valid-jsdoc /** * @param {string} system - The name of the system this renderer is for. - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -37,27 +38,31 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(system, screenWidth, screenHeight, options) + constructor(system, options, arg2, arg3) { super(); sayHello(system); - // prepare options - if (options) + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') { - for (const i in settings.RENDER_OPTIONS) - { - if (typeof options[i] === 'undefined') - { - options[i] = settings.RENDER_OPTIONS[i]; - } - } + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); } - else - { - options = settings.RENDER_OPTIONS; - } + + // Add the default render options + options = Object.assign({}, settings.RENDER_OPTIONS, options); + + /** + * The supplied constructor options. + * + * @member {Object} + * @readOnly + */ + this.options = options; /** * The type of the renderer. @@ -75,7 +80,7 @@ * * @member {PIXI.Rectangle} */ - this.screen = new Rectangle(0, 0, screenWidth || 800, screenHeight || 600); + this.screen = new Rectangle(0, 0, options.width, options.height); /** * The canvas element that everything is drawn to @@ -279,6 +284,8 @@ this.blendModes = null; + this.options = null; + this.preserveDrawingBuffer = false; this.clearBeforeRender = false; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index e84e49c..691caf0 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -17,10 +17,11 @@ */ export default class CanvasRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -34,9 +35,9 @@ * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when rendering, * stopping pixel interpolation. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('Canvas', screenWidth, screenHeight, options); + super('Canvas', options, arg2, arg3); this.type = RENDERER_TYPE.CANVAS; @@ -96,7 +97,19 @@ this.context = null; this.renderingToScreen = false; - this.resize(screenWidth, screenHeight); + this.resize(this.options.width, this.options.height); + + /** + * Fired after rendering finishes. + * + * @event PIXI.CanvasRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.CanvasRenderer#prerender + */ } /** @@ -299,4 +312,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.CanvasRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.CanvasExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.CanvasPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.CanvasRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index e1cf7cd..f6fe123 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -31,11 +31,12 @@ */ export default class WebGLRenderer extends SystemRenderer { + // eslint-disable-next-line valid-jsdoc /** * - * @param {number} [screenWidth=800] - the width of the screen - * @param {number} [screenHeight=600] - the height of the screen * @param {object} [options] - The optional renderer parameters + * @param {number} [options.width=800] - the width of the screen + * @param {number} [options.height=600] - the height of the screen * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional * @param {boolean} [options.transparent=false] - If the render view is transparent, default false * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false @@ -55,9 +56,9 @@ * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. */ - constructor(screenWidth, screenHeight, options = {}) + constructor(options, arg2, arg3) { - super('WebGL', screenWidth, screenHeight, options); + super('WebGL', options, arg2, arg3); /** * The type of this renderer as a standardised const @@ -89,12 +90,6 @@ }; - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ this._backgroundColorRgba[3] = this.transparent ? 0 : 1; this.globalUniforms = new UniformGroup({ @@ -117,6 +112,13 @@ this.initPlugins(); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ if(options.context) { this.context.initFromContext(options.context); @@ -169,6 +171,25 @@ } return this; + + /** + * Fired after rendering finishes. + * + * @event PIXI.WebGLRenderer#postrender + */ + + /** + * Fired before rendering starts. + * + * @event PIXI.WebGLRenderer#prerender + */ + + /** + * Fired when the WebGL context is set. + * + * @event PIXI.WebGLRenderer#context + * @param {WebGLRenderingContext} gl - WebGL context. + */ } /** @@ -295,4 +316,25 @@ } } +/** + * Collection of installed plugins. These are included by default in PIXI, but can be excluded + * by creating a custom build. Consult the README for more information about creating custom + * builds and excluding plugins. + * @name PIXI.WebGLRenderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + +/** + * Adds a plugin to the renderer. + * + * @method PIXI.WebGLRenderer#registerPlugin + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + pluginTarget.mixin(WebGLRenderer); diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index 4eb6128..fa3d883 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -47,6 +47,14 @@ * @member {boolean} */ this.enabled = true; + + /** + * If enabled, pixi will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; } /** diff --git a/src/core/renderers/webgl/systems/ContextSystem.js b/src/core/renderers/webgl/systems/ContextSystem.js index ff958c7..8df85ea 100644 --- a/src/core/renderers/webgl/systems/ContextSystem.js +++ b/src/core/renderers/webgl/systems/ContextSystem.js @@ -75,7 +75,8 @@ */ createContext(canvas, options) { - var gl = canvas.getContext('webgl', options) || + var gl = //canvas.getContext('webgl2', options) || + canvas.getContext('webgl', options) || canvas.getContext('experimental-webgl', options); if (!gl) diff --git a/src/core/renderers/webgl/systems/FilterSystem.js b/src/core/renderers/webgl/systems/FilterSystem.js index f47335d..37e28d2 100644 --- a/src/core/renderers/webgl/systems/FilterSystem.js +++ b/src/core/renderers/webgl/systems/FilterSystem.js @@ -111,7 +111,7 @@ // TODO we should fit the rect around the transform.. } - else + else if (filters[0].autoFit) { sourceFrame.fit(filterData.stack[0].destinationFrame); } diff --git a/src/core/renderers/webgl/systems/shader/shader/mapType.js b/src/core/renderers/webgl/systems/shader/shader/mapType.js index 581dace..917538a 100644 --- a/src/core/renderers/webgl/systems/shader/shader/mapType.js +++ b/src/core/renderers/webgl/systems/shader/shader/mapType.js @@ -15,7 +15,7 @@ } } - return GL_TABLE[type]; + return GL_TABLE[type]; }; var GL_TABLE = null; diff --git a/src/core/settings.js b/src/core/settings.js index 18c5877..38ef45c 100644 --- a/src/core/settings.js +++ b/src/core/settings.js @@ -2,6 +2,15 @@ import canUploadSameBuffer from './utils/canUploadSameBuffer'; /** + * User's customizable globals for overriding the default PIXI settings, such + * as a renderer's default resolution, framerate, float percision, etc. + * @example + * // Use the native window resolution as the default resolution + * // will support high-density displays when rendering + * PIXI.settings.RESOLUTION = window.devicePixelRatio. + * + * // Disable interpolation when scaling, will make texture be pixelated + * PIXI.settings.SCALE_MODE = PIXI.SCALE_MODES.NEAREST; * @namespace PIXI.settings */ export default { @@ -101,6 +110,9 @@ * @property {boolean} clearBeforeRender=true * @property {boolean} preserveDrawingBuffer=false * @property {boolean} roundPixels=false + * @property {number} width=800 + * @property {number} height=600 + * @property {boolean} legacy=false */ RENDER_OPTIONS: { view: null, @@ -112,6 +124,9 @@ clearBeforeRender: true, preserveDrawingBuffer: false, roundPixels: false, + width: 800, + height: 600, + legacy: false, }, /** diff --git a/src/core/shader/Program.js b/src/core/shader/Program.js index c0b538b..07b4398 100644 --- a/src/core/shader/Program.js +++ b/src/core/shader/Program.js @@ -1,6 +1,6 @@ import extractUniformsFromSrc from './extractUniformsFromSrc'; import generateUniformsSync from './generateUniformsSync'; -import glCore from 'pixi-gl-core'; +import shaderUtils from '../renderers/webgl/systems/shader/shader'; import { ProgramCache } from '../utils'; import getTestContext from '../utils/getTestContext'; @@ -64,14 +64,17 @@ } else { - vertexSrc = glCore.shader.setPrecision(vertexSrc, 'mediump'); - fragmentSrc = glCore.shader.setPrecision(fragmentSrc, 'mediump'); + vertexSrc = shaderUtils.setPrecision(vertexSrc, 'mediump'); + fragmentSrc = shaderUtils.setPrecision(fragmentSrc, 'mediump'); - const program = glCore.shader.compileProgram(gl, vertexSrc, fragmentSrc); + const program = shaderUtils.compileProgram(gl, vertexSrc, fragmentSrc); + console.log("<<<<<<>>><<"); this.attributeData = this.getAttributeData(program, gl); this.uniformData = this.getUniformData(program, gl); + console.log(this.uniformData); + //gl.deleteProgram(program); } } @@ -95,13 +98,13 @@ for (let i = 0; i < totalAttributes; i++) { const attribData = gl.getActiveAttrib(program, i); - const type = glCore.shader.mapType(gl, attribData.type); + const type = shaderUtils.mapType(gl, attribData.type); /*eslint-disable */ const data = { type: type, name: attribData.name, - size: glCore.shader.mapSize(type), + size: shaderUtils.mapSize(type), location: 0, }; /*eslint-enable */ @@ -137,13 +140,14 @@ // TODO expose this as a prop? // const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); - const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); + //const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); for (let i = 0; i < totalUniforms; i++) { const uniformData = gl.getActiveUniform(program, i); const name = uniformData.name.replace(/\[.*?\]/, ''); - const type = glCore.shader.mapType(gl, uniformData.type); + const type = shaderUtils.mapType(gl, uniformData.type); + console.log('mapping ' + uniformData.type + ' to ' + type); // if (!name.match(maskRegex)) { @@ -151,7 +155,7 @@ uniforms[name] = { type: type, size: uniformData.size, - value: glCore.shader.defaultValue(type, uniformData.size), + value: shaderUtils.defaultValue(type, uniformData.size), }; /*eslint-enable */ } diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index e387e99..b57d095 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -147,12 +147,12 @@ // so if _width is 0 then width was not set.. if (this._width) { - this.scale.x = sign(this.scale.x) * this._width / this.texture.orig.width; + this.scale.x = sign(this.scale.x) * this._width / this._texture.orig.width; } if (this._height) { - this.scale.y = sign(this.scale.y) * this._height / this.texture.orig.height; + this.scale.y = sign(this.scale.y) * this._height / this._texture.orig.height; } } diff --git a/src/core/sprites/canvas/CanvasTinter.js b/src/core/sprites/canvas/CanvasTinter.js index 27e5e0c..fd9de5d 100644 --- a/src/core/sprites/canvas/CanvasTinter.js +++ b/src/core/sprites/canvas/CanvasTinter.js @@ -18,7 +18,7 @@ */ getTintedTexture: (sprite, color) => { - const texture = sprite.texture; + const texture = sprite._texture; color = CanvasTinter.roundColor(color); diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 5a4edda..8b9b797 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -344,6 +344,7 @@ const texture = this._texture; const style = this._style; + const padding = style.trim ? 0 : style.padding; texture.baseTexture.valid = true; texture.baseTexture.resolution = this.resolution; @@ -353,11 +354,11 @@ texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - texture.trim.x = -style.padding; - texture.trim.y = -style.padding; + texture.trim.x = -padding; + texture.trim.y = -padding; - texture.orig.width = texture._frame.width - (style.padding * 2); - texture.orig.height = texture._frame.height - (style.padding * 2); + texture.orig.width = texture._frame.width - (padding * 2); + texture.orig.height = texture._frame.height - (padding * 2); // call sprite onTextureUpdate to update scale if _width or _height were set this._onTextureUpdate(); diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index 853da3d..8a9c34b 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -114,6 +114,38 @@ this.cacheId = null; this.validate(); + + /** + * Fired when a not-immediately-available source finishes loading. + * + * @protected + * @event PIXI.BaseTexture#loaded + * @param {PIXI.BaseTexture} baseTexture - Resource loaded. + */ + + /** + * Fired when a not-immediately-available source fails to load. + * + * @protected + * @event PIXI.BaseTexture#error + * @param {PIXI.BaseTexture} baseTexture - Resource errored. + */ + + /** + * Fired when BaseTexture is updated. + * + * @protected + * @event PIXI.BaseTexture#update + * @param {PIXI.BaseTexture} baseTexture - Instance of texture being updated. + */ + + /** + * Fired when BaseTexture is destroyed. + * + * @protected + * @event PIXI.BaseTexture#dispose + * @param {PIXI.BaseTexture} baseTexture - Instance of texture being destroyed. + */ } setResource(resource) diff --git a/src/core/textures/Spritesheet.js b/src/core/textures/Spritesheet.js index d469c62..9b24f27 100644 --- a/src/core/textures/Spritesheet.js +++ b/src/core/textures/Spritesheet.js @@ -208,6 +208,7 @@ // lets also add the frame to pixi's global cache for fromFrame and fromImage functions TextureCache[i] = this.textures[i]; + this.textures[i].textureCacheId = i; } frameIndex++; diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 0a2c379..2213087 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -147,9 +147,9 @@ /** * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. * - * @event update - * @memberof PIXI.Texture# + * @event PIXI.Texture#update * @protected + * @param {PIXI.Texture} texture - Instance of texture being updated. */ this._updateID = 0; @@ -159,6 +159,15 @@ * @type {Object} */ this.transform = null; + + /** + * The id under which this Texture has been added to the texture cache. This is + * automatically set in certain cases, but may not always be accurate, particularly if + * the texture is in the cache under multiple ids. + * + * @member {string} + */ + this.textureCacheId = null; } /** @@ -225,11 +234,17 @@ this._uvs = null; this.trim = null; this.orig = null; + this.textureCacheId = null; this.valid = false; - this.off('dispose', this.dispose, this); - this.off('update', this.update, this); + for (const prop in TextureCache) + { + if (TextureCache[prop] === this) + { + delete TextureCache[prop]; + } + } } /** @@ -315,14 +330,15 @@ */ static fromLoader(source, imageUrl, name) { - // console.log('added from loader...') + // console.log('added from loader...') const resource = new ImageResource(source);//.from(imageUrl, crossorigin);// document.createElement('img'); - // console.log('base resource ' + resource.width); + // console.log('base resource ' + resource.width); const baseTexture = new BaseTexture(resource, settings.SCALE_MODE, getResolutionOfUrl(imageUrl)); + /// console.log('base width ' + baseTexture.width); const texture = new Texture(baseTexture); @@ -335,6 +351,7 @@ // lets also add the frame to pixi's global cache for fromFrame and fromImage fucntions BaseTextureCache[name] = baseTexture; TextureCache[name] = texture; + texture.textureCacheId = name; // also add references by url if they are different. if (name !== imageUrl) @@ -355,6 +372,10 @@ */ static addTextureToCache(texture, id) { + if (!texture.textureCacheId) + { + texture.textureCacheId = id; + } TextureCache[id] = texture; } @@ -495,6 +516,7 @@ */ Texture.EMPTY = new Texture(new BaseTexture()); removeAllHandlers(Texture.EMPTY); +removeAllHandlers(Texture.EMPTY.baseTexture); /** * A white texture of 10x10 size, used for graphics and other things @@ -505,3 +527,4 @@ */ Texture.WHITE = createWhiteTexture(); removeAllHandlers(Texture.WHITE); +removeAllHandlers(Texture.WHITE.baseTexture); diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index 59eb9fd..a9b9f51 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -1,6 +1,7 @@ import BaseTexture from './BaseTexture'; import { uid, BaseTextureCache } from '../utils'; -import * as ticker from '../ticker'; +import { shared } from '../ticker'; +import { UPDATE_PRIORITY } from '../const'; /** * A texture of a [playing] Video. @@ -125,7 +126,7 @@ if (!this._isAutoUpdating && this.autoUpdate) { - ticker.shared.add(this.update, this); + shared.add(this.update, this, UPDATE_PRIORITY.HIGH); this._isAutoUpdating = true; } } @@ -139,7 +140,7 @@ { if (this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); this._isAutoUpdating = false; } } @@ -187,7 +188,7 @@ { if (this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); } if (this.source && this.source._pixiId) @@ -281,12 +282,12 @@ if (!this._autoUpdate && this._isAutoUpdating) { - ticker.shared.remove(this.update, this); + shared.remove(this.update, this); this._isAutoUpdating = false; } else if (this._autoUpdate && !this._isAutoUpdating) { - ticker.shared.add(this.update, this); + shared.add(this.update, this, UPDATE_PRIORITY.HIGH); this._isAutoUpdating = true; } } diff --git a/src/core/ticker/Ticker.js b/src/core/ticker/Ticker.js index 17a1517..40df138 100644 --- a/src/core/ticker/Ticker.js +++ b/src/core/ticker/Ticker.js @@ -1,12 +1,10 @@ import settings from '../settings'; -import EventEmitter from 'eventemitter3'; - -// Internal event used by composed emitter -const TICK = 'tick'; +import { UPDATE_PRIORITY } from '../const'; +import TickerListener from './TickerListener'; /** * A Ticker class that runs an update loop that other objects listen to. - * This class is composed around an EventEmitter object to add listeners + * This class is composed around listeners * meant for execution on the next requested animation frame. * Animation frames are requested only when necessary, * e.g. When the ticker is started and the emitter has listeners. @@ -22,10 +20,11 @@ constructor() { /** - * Internal emitter used to fire 'tick' event + * The first listener. All new listeners added are chained on this. * @private + * @type {TickerListener} */ - this._emitter = new EventEmitter(); + this._head = new TickerListener(null, null, Infinity); /** * Internal current frame request ID @@ -131,7 +130,7 @@ // Invoke listeners now this.update(time); // Listener side effects may have modified ticker state. - if (this.started && this._requestId === null && this._emitter.listeners(TICK, true)) + if (this.started && this._requestId === null && this._head.next) { this._requestId = requestAnimationFrame(this._tick); } @@ -148,7 +147,7 @@ */ _requestIfNeeded() { - if (this._requestId === null && this._emitter.listeners(TICK, true)) + if (this._requestId === null && this._head.next) { // ensure callbacks get correct delta this.lastTime = performance.now(); @@ -193,35 +192,72 @@ } /** - * Calls {@link module:eventemitter3.EventEmitter#on} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. + * Register a handler for tick events. Calls continuously unless + * it is removed or the ticker is stopped. * * @param {Function} fn - The listener function to be added for updates * @param {Function} [context] - The listener context + * @param {number} [priority=PIXI.UPDATE_PRIORITY.NORMAL] - The priority for emitting * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - add(fn, context) + add(fn, context, priority = UPDATE_PRIORITY.NORMAL) { - this._emitter.on(TICK, fn, context); - - this._startIfPossible(); - - return this; + return this._addListener(new TickerListener(fn, context, priority)); } /** - * Calls {@link module:eventemitter3.EventEmitter#once} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. + * Add a handler for the tick event which is only execute once. * * @param {Function} fn - The listener function to be added for one update * @param {Function} [context] - The listener context + * @param {number} [priority=PIXI.UPDATE_PRIORITY.NORMAL] - The priority for emitting * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - addOnce(fn, context) + addOnce(fn, context, priority = UPDATE_PRIORITY.NORMAL) { - this._emitter.once(TICK, fn, context); + return this._addListener(new TickerListener(fn, context, priority, true)); + } + + /** + * Internally adds the event handler so that it can be sorted by priority. + * Priority allows certain handler (user, AnimatedSprite, Interaction) to be run + * before the rendering. + * + * @private + * @param {TickerListener} listener - Current listener being added. + * @returns {PIXI.ticker.Ticker} This instance of a ticker + */ + _addListener(listener) + { + // For attaching to head + let current = this._head.next; + let previous = this._head; + + // Add the first item + if (!current) + { + listener.connect(previous); + } + else + { + // Go from highest to lowest priority + while (current) + { + if (listener.priority >= current.priority) + { + listener.connect(previous); + break; + } + previous = current; + current = current.next; + } + + // Not yet connected + if (!listener.previous) + { + listener.connect(previous); + } + } this._startIfPossible(); @@ -229,19 +265,33 @@ } /** - * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. - * It checks if the emitter has listeners for 'tick' event. - * If it does, then it cancels the animation frame. + * Removes any handlers matching the function and context parameters. + * If no handlers are left after removing, then it cancels the animation frame. * - * @param {Function} [fn] - The listener function to be removed + * @param {Function} fn - The listener function to be removed * @param {Function} [context] - The listener context to be removed * @returns {PIXI.ticker.Ticker} This instance of a ticker */ remove(fn, context) { - this._emitter.off(TICK, fn, context); + let listener = this._head.next; - if (!this._emitter.listeners(TICK, true)) + while (listener) + { + // We found a match, lets remove it + // no break to delete all possible matches + // incase a listener was added 2+ times + if (listener.match(fn, context)) + { + listener = listener.destroy(); + } + else + { + listener = listener.next; + } + } + + if (!this._head.next) { this._cancelIfNeeded(); } @@ -276,6 +326,25 @@ } /** + * Destroy the ticker and don't use after this. Calling + * this method removes all references to internal events. + */ + destroy() + { + this.stop(); + + let listener = this._head.next; + + while (listener) + { + listener = listener.destroy(true); + } + + this._head.destroy(); + this._head = null; + } + + /** * Triggers an update. An update entails setting the * current {@link PIXI.ticker.Ticker#elapsedMS}, * the current {@link PIXI.ticker.Ticker#deltaTime}, @@ -320,8 +389,22 @@ this.deltaTime = elapsedMS * settings.TARGET_FPMS * this.speed; + // Cache a local reference, in-case ticker is destroyed + // during the emit, we can still check for head.next + const head = this._head; + // Invoke listeners added to internal emitter - this._emitter.emit(TICK, this.deltaTime); + let listener = head.next; + + while (listener) + { + listener = listener.emit(this.deltaTime); + } + + if (!head.next) + { + this._cancelIfNeeded(); + } } else { diff --git a/src/core/ticker/TickerListener.js b/src/core/ticker/TickerListener.js new file mode 100644 index 0000000..2bedb34 --- /dev/null +++ b/src/core/ticker/TickerListener.js @@ -0,0 +1,158 @@ +/** + * Internal class for handling the priority sorting of ticker handlers. + * + * @private + * @class + * @memberof PIXI.ticker + */ +export default class TickerListener +{ + /** + * Constructor + * + * @param {Function} fn - The listener function to be added for one update + * @param {Function} [context=null] - The listener context + * @param {number} [priority=0] - The priority for emitting + * @param {boolean} [once=false] - If the handler should fire once + */ + constructor(fn, context = null, priority = 0, once = false) + { + /** + * The handler function to execute. + * @member {Function} + */ + this.fn = fn; + + /** + * The calling to execute. + * @member {Function} + */ + this.context = context; + + /** + * The current priority. + * @member {number} + */ + this.priority = priority; + + /** + * If this should only execute once. + * @member {boolean} + */ + this.once = once; + + /** + * The next item in chain. + * @member {TickerListener} + */ + this.next = null; + + /** + * The previous item in chain. + * @member {TickerListener} + */ + this.previous = null; + + /** + * `true` if this listener has been destroyed already. + * @member {boolean} + * @private + */ + this._destroyed = false; + } + + /** + * Simple compare function to figure out if a function and context match. + * + * @param {Function} fn - The listener function to be added for one update + * @param {Function} context - The listener context + * @return {boolean} `true` if the listener match the arguments + */ + match(fn, context) + { + context = context || null; + + return this.fn === fn && this.context === context; + } + + /** + * Emit by calling the current function. + * @param {number} deltaTime - time since the last emit. + * @return {TickerListener} Next ticker + */ + emit(deltaTime) + { + if (this.context) + { + this.fn.call(this.context, deltaTime); + } + else + { + this.fn(deltaTime); + } + + if (this.once) + { + this.destroy(); + } + + const redirect = this.next; + + // Soft-destroying should remove + // the next reference + if (this._destroyed) + { + this.next = null; + } + + return redirect; + } + + /** + * Connect to the list. + * @param {TickerListener} previous - Input node, previous listener + */ + connect(previous) + { + this.previous = previous; + if (previous.next) + { + previous.next.previous = this; + } + this.next = previous.next; + previous.next = this; + } + + /** + * Destroy and don't use after this. + * @param {boolean} [hard = false] `true` to remove the `next` reference, this + * is considered a hard destroy. Soft destroy maintains the next reference. + * @return {TickerListener} The listener to redirect while emitting or removing. + */ + destroy(hard = false) + { + this._destroyed = true; + this.fn = null; + this.context = null; + + // Disconnect, hook up next and previous + if (this.previous) + { + this.previous.next = this.next; + } + + if (this.next) + { + this.next.previous = this.previous; + } + + // Redirect to the next item + const redirect = this.previous; + + // Remove references + this.next = hard ? null : redirect; + this.previous = null; + + return redirect; + } +} diff --git a/src/core/ticker/index.js b/src/core/ticker/index.js index 4b3017c..c3020d4 100644 --- a/src/core/ticker/index.js +++ b/src/core/ticker/index.js @@ -45,8 +45,25 @@ const shared = new Ticker(); shared.autoStart = true; +shared.destroy = () => +{ + // protect destroying shared ticker + // this is used by other internal systems + // like AnimatedSprite and InteractionManager +}; /** + * This namespace contains an API for interacting with PIXI's internal global update loop. + * + * This ticker is used for rendering, {@link PIXI.extras.AnimatedSprite AnimatedSprite}, + * {@link PIXI.interaction.InteractionManager InteractionManager} and many other time-based PIXI systems. + * @example + * const ticker = new PIXI.ticker.Ticker(); + * ticker.stop(); + * ticker.add((deltaTime) => { + * // do something every frame + * }); + * ticker.start(); * @namespace PIXI.ticker */ export { shared, Ticker }; diff --git a/src/core/utils/index.js b/src/core/utils/index.js index be44750..7441f7b 100644 --- a/src/core/utils/index.js +++ b/src/core/utils/index.js @@ -9,6 +9,21 @@ let saidHello = false; /** + * Generalized convenience utilities for PIXI. + * @example + * // Extend PIXI's internal Event Emitter. + * class MyEmitter extends PIXI.utils.EventEmitter { + * constructor() { + * super(); + * console.log("Emitter created!"); + * } + * } + * + * // Get info on current device + * console.log(PIXI.utils.isMobile); + * + * // Convert hex color to string + * console.log(PIXI.utils.hex2string(0xff00ff)); // returns: "#ff00ff" * @namespace PIXI.utils */ export { diff --git a/src/deprecation.js b/src/deprecation.js index b9c73b3..33daeec 100644 --- a/src/deprecation.js +++ b/src/deprecation.js @@ -918,6 +918,35 @@ }); /** + * @method + * @name PIXI.prepare.BasePrepare#register + * @see PIXI.prepare.BasePrepare#registerFindHook + * @deprecated since version 4.4.2 + * @param {Function} [addHook] - Function call that takes two parameters: `item:*, queue:Array` + * function must return `true` if it was able to add item to the queue. + * @param {Function} [uploadHook] - Function call that takes two parameters: `prepare:CanvasPrepare, item:*` and + * function must return `true` if it was able to handle upload of item. + * @return {PIXI.BasePrepare} Instance of plugin for chaining. + */ +prepare.BasePrepare.prototype.register = function register(addHook, uploadHook) +{ + warn('renderer.plugins.prepare.register is now deprecated, ' + + 'please use renderer.plugins.prepare.registerFindHook & renderer.plugins.prepare.registerUploadHook'); + + if (addHook) + { + this.registerFindHook(addHook); + } + + if (uploadHook) + { + this.registerUploadHook(uploadHook); + } + + return this; +}; + +/** * The number of graphics or textures to upload to the GPU. * * @name PIXI.prepare.canvas.UPLOADS_PER_FRAME @@ -969,67 +998,73 @@ }, }); -Object.defineProperties(loaders.Resource.prototype, { - isJson: { - get() - { - warn('The isJson property is deprecated, please use `resource.type === Resource.TYPE.JSON`.'); +if (loaders.Loader) +{ + const Resource = loaders.Resource; + const Loader = loaders.Loader; - return this.type === loaders.Loader.Resource.TYPE.JSON; + Object.defineProperties(Resource.prototype, { + isJson: { + get() + { + warn('The isJson property is deprecated, please use `resource.type === Resource.TYPE.JSON`.'); + + return this.type === Resource.TYPE.JSON; + }, }, - }, - isXml: { - get() - { - warn('The isXml property is deprecated, please use `resource.type === Resource.TYPE.XML`.'); + isXml: { + get() + { + warn('The isXml property is deprecated, please use `resource.type === Resource.TYPE.XML`.'); - return this.type === loaders.Loader.Resource.TYPE.XML; + return this.type === Resource.TYPE.XML; + }, }, - }, - isImage: { - get() - { - warn('The isImage property is deprecated, please use `resource.type === Resource.TYPE.IMAGE`.'); + isImage: { + get() + { + warn('The isImage property is deprecated, please use `resource.type === Resource.TYPE.IMAGE`.'); - return this.type === loaders.Loader.Resource.TYPE.IMAGE; + return this.type === Resource.TYPE.IMAGE; + }, }, - }, - isAudio: { - get() - { - warn('The isAudio property is deprecated, please use `resource.type === Resource.TYPE.AUDIO`.'); + isAudio: { + get() + { + warn('The isAudio property is deprecated, please use `resource.type === Resource.TYPE.AUDIO`.'); - return this.type === loaders.Loader.Resource.TYPE.AUDIO; + return this.type === Resource.TYPE.AUDIO; + }, }, - }, - isVideo: { - get() - { - warn('The isVideo property is deprecated, please use `resource.type === Resource.TYPE.VIDEO`.'); + isVideo: { + get() + { + warn('The isVideo property is deprecated, please use `resource.type === Resource.TYPE.VIDEO`.'); - return this.type === loaders.Loader.Resource.TYPE.VIDEO; + return this.type === Resource.TYPE.VIDEO; + }, }, - }, -}); + }); -Object.defineProperties(loaders.Loader.prototype, { - before: { - get() - { - warn('The before() method is deprecated, please use pre().'); + Object.defineProperties(Loader.prototype, { + before: { + get() + { + warn('The before() method is deprecated, please use pre().'); - return this.pre; + return this.pre; + }, }, - }, - after: { - get() - { - warn('The after() method is deprecated, please use use().'); + after: { + get() + { + warn('The after() method is deprecated, please use use().'); - return this.use; + return this.use; + }, }, - }, -}); + }); +} /** * @name PIXI.interaction.interactiveTarget#defaultCursor diff --git a/src/extract/canvas/CanvasExtract.js b/src/extract/canvas/CanvasExtract.js index 941ca05..3ac5aee 100644 --- a/src/extract/canvas/CanvasExtract.js +++ b/src/extract/canvas/CanvasExtract.js @@ -8,7 +8,7 @@ * An instance of this class is automatically created by default, and can be found at renderer.plugins.extract * * @class - * @memberof PIXI + * @memberof PIXI.extract */ export default class CanvasExtract { @@ -21,9 +21,9 @@ /** * Collection of methods for extracting data (image, pixels, etc.) from a display object or render texture * - * @member {PIXI.CanvasExtract} extract + * @member {PIXI.extract.CanvasExtract} extract * @memberof PIXI.CanvasRenderer# - * @see PIXI.CanvasExtract + * @see PIXI.extract.CanvasExtract */ renderer.extract = this; } diff --git a/src/extract/index.js b/src/extract/index.js index 0f2170d..e9478c1 100644 --- a/src/extract/index.js +++ b/src/extract/index.js @@ -1,2 +1,22 @@ +/** + * This namespace provides renderer-specific plugins for exporting content from a renderer. + * For instance, these plugins can be used for saving an Image, Canvas element or for exporting the raw image data (pixels). + * + * Do not instantiate these plugins directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. + * @example + * // Create a new app (will auto-add extract plugin to renderer) + * const app = new PIXI.Application(); + * + * // Draw a red circle + * const graphics = new PIXI.Graphics() + * .beginFill(0xFF0000) + * .drawCircle(0, 0, 50); + * + * // Render the graphics as an HTMLImageElement + * const image = app.renderer.plugins.extract.image(graphics); + * document.body.appendChild(image); + * @namespace PIXI.extract + */ export { default as webgl } from './webgl/WebGLExtract'; export { default as canvas } from './canvas/CanvasExtract'; diff --git a/src/extract/webgl/WebGLExtract.js b/src/extract/webgl/WebGLExtract.js index 60228bb..1c3dd30 100644 --- a/src/extract/webgl/WebGLExtract.js +++ b/src/extract/webgl/WebGLExtract.js @@ -9,7 +9,7 @@ * An instance of this class is automatically created by default, and can be found at renderer.plugins.extract * * @class - * @memberof PIXI + * @memberof PIXI.extract */ export default class WebGLExtract { @@ -22,9 +22,9 @@ /** * Collection of methods for extracting data (image, pixels, etc.) from a display object or render texture * - * @member {PIXI.WebGLExtract} extract + * @member {PIXI.extract.WebGLExtract} extract * @memberof PIXI.WebGLRenderer# - * @see PIXI.WebGLExtract + * @see PIXI.extract.WebGLExtract */ renderer.extract = this; } diff --git a/src/extras/AnimatedSprite.js b/src/extras/AnimatedSprite.js index 13b804f..5297778 100644 --- a/src/extras/AnimatedSprite.js +++ b/src/extras/AnimatedSprite.js @@ -137,7 +137,7 @@ this.playing = true; if (this._autoUpdate) { - core.ticker.shared.add(this.update, this); + core.ticker.shared.add(this.update, this, core.UPDATE_PRIORITY.HIGH); } } diff --git a/src/extras/TextureTransform.js b/src/extras/TextureTransform.js index 78e6e40..16536d2 100644 --- a/src/extras/TextureTransform.js +++ b/src/extras/TextureTransform.js @@ -65,8 +65,36 @@ } /** + * Multiplies uvs array to transform + * @param {Float32Array} uvs mesh uvs + * @param {Float32Array} [out=uvs] output + * @returns {Float32Array} output + */ + multiplyUvs(uvs, out) + { + if (out === undefined) + { + out = uvs; + } + + const mat = this.mapCoord; + + for (let i = 0; i < uvs.length; i += 2) + { + const x = uvs[i]; + const y = uvs[i + 1]; + + out[i] = (x * mat.a) + (y * mat.c) + mat.tx; + out[i + 1] = (x * mat.b) + (y * mat.d) + mat.ty; + } + + return out; + } + + /** * updates matrices if texture was changed * @param {boolean} forceUpdate if true, matrices will be updated any case + * @returns {boolean} whether or not it was updated */ update(forceUpdate) { @@ -74,13 +102,13 @@ if (!tex || !tex.valid) { - return; + return false; } if (!forceUpdate && this._lastTextureID === tex._updateID) { - return; + return false; } this._lastTextureID = tex._updateID; @@ -110,5 +138,7 @@ frame[3] = (tex._frame.y + tex._frame.height - margin + offset) / texBase.height; this.uClampOffset[0] = offset / texBase.realWidth; this.uClampOffset[1] = offset / texBase.realHeight; + + return true; } } diff --git a/src/extras/TilingSprite.js b/src/extras/TilingSprite.js index 1f6014f..a3f027f 100644 --- a/src/extras/TilingSprite.js +++ b/src/extras/TilingSprite.js @@ -180,9 +180,9 @@ const transform = this.worldTransform; const resolution = renderer.resolution; const baseTexture = texture.baseTexture; - const baseTextureResolution = texture.baseTexture.resolution; - const modX = (this.tilePosition.x / this.tileScale.x) % texture._frame.width; - const modY = (this.tilePosition.y / this.tileScale.y) % texture._frame.height; + const baseTextureResolution = baseTexture.resolution; + const modX = ((this.tilePosition.x / this.tileScale.x) % texture._frame.width) * baseTextureResolution; + const modY = ((this.tilePosition.y / this.tileScale.y) % texture._frame.height) * baseTextureResolution; // create a nice shiny pattern! // TODO this needs to be refreshed if texture changes.. diff --git a/src/extras/cacheAsBitmap.js b/src/extras/cacheAsBitmap.js index 5dd5640..aea06a8 100644 --- a/src/extras/cacheAsBitmap.js +++ b/src/extras/cacheAsBitmap.js @@ -227,7 +227,16 @@ this.transform._parentID = -1; // restore the transform of the cached sprite to avoid the nasty flicker.. - this.updateTransform(); + if (!this.parent) + { + this.parent = renderer._tempDisplayObjectParent; + this.updateTransform(); + this.parent = null; + } + else + { + this.updateTransform(); + } // map the hit test.. this.containsPoint = cachedSprite.containsPoint.bind(cachedSprite); @@ -314,7 +323,17 @@ cachedSprite._bounds = this._bounds; cachedSprite.alpha = cacheAlpha; - this.updateTransform(); + if (!this.parent) + { + this.parent = renderer._tempDisplayObjectParent; + this.updateTransform(); + this.parent = null; + } + else + { + this.updateTransform(); + } + this.updateTransform = this.displayObjectUpdateTransform; this._cacheData.sprite = cachedSprite; @@ -358,9 +377,12 @@ * Destroys the cached object. * * @private + * @param {object|boolean} [options] - Options parameter. A boolean will act as if all options + * have been set to that value. + * Used when destroying containers, see the Container.destroy method. */ -DisplayObject.prototype._cacheAsBitmapDestroy = function _cacheAsBitmapDestroy() +DisplayObject.prototype._cacheAsBitmapDestroy = function _cacheAsBitmapDestroy(options) { this.cacheAsBitmap = false; - this.destroy(); + this.destroy(options); }; diff --git a/src/extras/index.js b/src/extras/index.js index bbd8bbf..a34d7ec 100644 --- a/src/extras/index.js +++ b/src/extras/index.js @@ -1,7 +1,7 @@ /** + * Additional PIXI DisplayObjects for animation, tiling and bitmap text. * @namespace PIXI.extras */ -export { default as TextureTransform } from './TextureTransform'; export { default as AnimatedSprite } from './AnimatedSprite'; export { default as TilingSprite } from './TilingSprite'; export { default as TilingSpriteRenderer } from './webgl/TilingSpriteRenderer'; diff --git a/src/extras/webgl/TilingSpriteRenderer.js b/src/extras/webgl/TilingSpriteRenderer.js index aeec172..eee3b5c 100644 --- a/src/extras/webgl/TilingSpriteRenderer.js +++ b/src/extras/webgl/TilingSpriteRenderer.js @@ -11,7 +11,7 @@ * WebGL renderer plugin for tiling sprites * * @class - * @memberof PIXI + * @memberof PIXI.extras * @extends PIXI.ObjectRenderer */ export default class TilingSpriteRenderer extends core.ObjectRenderer @@ -118,7 +118,7 @@ tempMat.invert(); if (isSimple) { - tempMat.append(uv.mapCoord); + tempMat.prepend(uv.mapCoord); } else { diff --git a/src/filters/displacement/DisplacementFilter.js b/src/filters/displacement/DisplacementFilter.js index d5355c3..b5d030c 100644 --- a/src/filters/displacement/DisplacementFilter.js +++ b/src/filters/displacement/DisplacementFilter.js @@ -35,7 +35,7 @@ this.maskSprite = sprite; this.maskMatrix = maskMatrix; - this.uniforms.mapSampler = sprite.texture; + this.uniforms.mapSampler = sprite._texture; this.uniforms.filterMatrix = maskMatrix; this.uniforms.scale = { x: 1, y: 1 }; diff --git a/src/filters/index.js b/src/filters/index.js index f3e8862..7ceb50a 100644 --- a/src/filters/index.js +++ b/src/filters/index.js @@ -1,4 +1,21 @@ /** + * This namespace contains WebGL-only display filters that can be applied + * to DisplayObjects using the {@link PIXI.DisplayObject#filters filters} property. + * @example + * // Create a new application + * const app = new PIXI.Application(); + * + * // Draw a green rectangle + * const rect = new PIXI.Graphics() + * .beginFill(0x00ff00) + * .drawRect(40, 40, 200, 200); + * + * // Add a blur filter + * rect.filters = [new PIXI.filters.BlurFilter()]; + * + * // Display rectangle + * app.stage.addChild(rect); + * document.body.appendChild(app.view); * @namespace PIXI.filters */ export { default as FXAAFilter } from './fxaa/FXAAFilter'; diff --git a/src/index.js b/src/index.js index c553427..77da490 100644 --- a/src/index.js +++ b/src/index.js @@ -20,6 +20,14 @@ import { utils } from './core'; utils.mixins.performMixins(); +/** + * Alias for {@link PIXI.loaders.shared}. + * @name loader + * @memberof PIXI + * @type {PIXI.loader.Loader} + */ +const loader = loaders.shared || null; + export { accessibility, extract, @@ -30,18 +38,8 @@ mesh, particles, prepare, + loader, }; -/** - * A premade instance of the loader that can be used to load resources. - * - * @name loader - * @memberof PIXI - * @property {PIXI.loaders.Loader} - */ -const loader = loaders && loaders.Loader ? new loaders.Loader() : null; // check is there in case user excludes loader lib - -export { loader }; - // Always export pixi globally. global.PIXI = exports; // eslint-disable-line diff --git a/src/interaction/InteractionData.js b/src/interaction/InteractionData.js index 02d53a3..449f8cc 100644 --- a/src/interaction/InteractionData.js +++ b/src/interaction/InteractionData.js @@ -30,7 +30,10 @@ /** * When passed to an event handler, this will be the original DOM Event that was captured * - * @member {Event} + * @see https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent + * @see https://developer.mozilla.org/en-US/docs/Web/API/TouchEvent + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent + * @member {MouseEvent|TouchEvent|PointerEvent} */ this.originalEvent = null; } diff --git a/src/interaction/InteractionManager.js b/src/interaction/InteractionManager.js index 8baf13a..2d75c7c 100644 --- a/src/interaction/InteractionManager.js +++ b/src/interaction/InteractionManager.js @@ -13,6 +13,14 @@ const MOUSE_POINTER_ID = 'MOUSE'; +// helpers for hitTest() - only used inside hitTest() +const hitTestEvent = { + target: null, + data: { + global: null, + }, +}; + /** * The interaction manager deals with mouse, touch and pointer events. Any DisplayObject can be interactive * if its interactive parameter is set to true @@ -245,67 +253,60 @@ this.setTargetElement(this.renderer.view, this.renderer.resolution); /** - * Fired when a pointer device button (usually a mouse button) is pressed on the display + * Fired when a pointer device button (usually a mouse left-button) is pressed on the display * object. * - * @event mousedown - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mousedown + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device secondary button (usually a mouse right-button) is pressed * on the display object. * - * @event rightdown - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightdown + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** - * Fired when a pointer device button (usually a mouse button) is released over the display + * Fired when a pointer device button (usually a mouse left-button) is released over the display * object. * - * @event mouseup - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseup + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device secondary button (usually a mouse right-button) is released * over the display object. * - * @event rightup - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightup + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** - * Fired when a pointer device button (usually a mouse button) is pressed and released on + * Fired when a pointer device button (usually a mouse left-button) is pressed and released on * the display object. * - * @event click - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#click + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device secondary button (usually a mouse right-button) is pressed * and released on the display object. * - * @event rightclick - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightclick + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** - * Fired when a pointer device button (usually a mouse button) is released outside the + * Fired when a pointer device button (usually a mouse left-button) is released outside the * display object that initially registered a * [mousedown]{@link PIXI.interaction.InteractionManager#event:mousedown}. * - * @event mouseupoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** @@ -313,146 +314,362 @@ * outside the display object that initially registered a * [rightdown]{@link PIXI.interaction.InteractionManager#event:rightdown}. * - * @event rightupoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#rightupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device (usually a mouse) is moved while over the display object * - * @event mousemove - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mousemove + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device (usually a mouse) is moved onto the display object * - * @event mouseover - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseover + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device (usually a mouse) is moved off the display object * - * @event mouseout - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#mouseout + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is pressed on the display object. * - * @event pointerdown - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerdown + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is released over the display object. * - * @event pointerup - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerup + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when the operating system cancels a pointer event * - * @event pointercancel - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointercancel + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is pressed and released on the display object. * - * @event pointertap - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointertap + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device button is released outside the display object that initially * registered a [pointerdown]{@link PIXI.interaction.InteractionManager#event:pointerdown}. * - * @event pointerupoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device is moved while over the display object * - * @event pointermove - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointermove + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device is moved onto the display object * - * @event pointerover - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerover + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a pointer device is moved off the display object * - * @event pointerout - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#pointerout + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is placed on the display object. * - * @event touchstart - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchstart + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is removed from the display object. * - * @event touchend - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchend + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when the operating system cancels a touch * - * @event touchcancel - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchcancel + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is placed and removed from the display object. * - * @event tap - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#tap + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is removed outside of the display object that initially * registered a [touchstart]{@link PIXI.interaction.InteractionManager#event:touchstart}. * - * @event touchendoutside - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchendoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data */ /** * Fired when a touch point is moved along the display object. * - * @event touchmove - * @type {PIXI.interaction.InteractionData} - * @memberof PIXI.interaction.InteractionManager# + * @event PIXI.interaction.InteractionManager#touchmove + * @param {PIXI.interaction.InteractionData} event - Interaction data */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is pressed on the display. + * object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mousedown + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is pressed + * on the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightdown + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is released over the display + * object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseup + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is released + * over the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightup + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is pressed and released on + * the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#click + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is pressed + * and released on the display object. DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightclick + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button (usually a mouse left-button) is released outside the + * display object that initially registered a + * [mousedown]{@link PIXI.DisplayObject#event:mousedown}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device secondary button (usually a mouse right-button) is released + * outside the display object that initially registered a + * [rightdown]{@link PIXI.DisplayObject#event:rightdown}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#rightupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device (usually a mouse) is moved while over the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mousemove + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device (usually a mouse) is moved onto the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseover + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device (usually a mouse) is moved off the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#mouseout + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is pressed on the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerdown + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is released over the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerup + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when the operating system cancels a pointer event. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointercancel + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is pressed and released on the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointertap + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device button is released outside the display object that initially + * registered a [pointerdown]{@link PIXI.DisplayObject#event:pointerdown}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerupoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device is moved while over the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointermove + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device is moved onto the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerover + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a pointer device is moved off the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#pointerout + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is placed on the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchstart + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is removed from the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchend + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when the operating system cancels a touch. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchcancel + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is placed and removed from the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#tap + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is removed outside of the display object that initially + * registered a [touchstart]{@link PIXI.DisplayObject#event:touchstart}. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchendoutside + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + + /** + * Fired when a touch point is moved along the display object. + * DisplayObject's `interactive` property must be set to `true` to fire event. + * + * @event PIXI.DisplayObject#touchmove + * @param {PIXI.interaction.InteractionData} event - Interaction data + */ + } + + /** + * Hit tests a point against the display tree, returning the first interactive object that is hit. + * + * @param {PIXI.Point} globalPoint - A point to hit test with, in global space. + * @param {PIXI.Container} [root] - The root display object to start from. If omitted, defaults + * to the last rendered root of the associated renderer. + * @return {PIXI.DisplayObject} The hit display object, if any. + */ + hitTest(globalPoint, root) + { + // clear the target for our hit test + hitTestEvent.target = null; + // assign the global point + hitTestEvent.data.global = globalPoint; + // ensure safety of the root + if (!root) + { + root = this.renderer._lastObjectRendered; + } + // run the hit test + this.processInteractive(hitTestEvent, root, null, true); + // return our found object - it'll be null if we didn't hit anything + + return hitTestEvent.target; } /** @@ -487,7 +704,7 @@ return; } - core.ticker.shared.add(this.update, this); + core.ticker.shared.add(this.update, this, core.UPDATE_PRIORITY.INTERACTION); if (window.navigator.msPointerEnabled) { @@ -804,8 +1021,6 @@ } } - let keepHitTestingAfterChildren = hitTest; - // ** FREE TIP **! If an object is not interactive or has no buttons in it // (such as a game scene!) set interactiveChildren to false for that displayObject. // This will allow pixi to completely ignore and bypass checking the displayObjects children. @@ -818,7 +1033,9 @@ const child = children[i]; // time to get recursive.. if this function will return if something is hit.. - if (this.processInteractive(interactionEvent, child, func, hitTest, interactiveParent)) + const childHit = this.processInteractive(interactionEvent, child, func, hitTest, interactiveParent); + + if (childHit) { // its a good idea to check if a child has lost its parent. // this means it has been removed whilst looping so its best @@ -827,8 +1044,6 @@ continue; } - hit = true; - // we no longer need to hit test any more objects in this container as we we // now know the parent has been hit interactiveParent = false; @@ -838,36 +1053,41 @@ // This means we no longer need to hit test anything else. We still need to run // through all objects, but we don't need to perform any hit tests. - keepHitTestingAfterChildren = false; - - if (child.interactive) + if (childHit) { - hitTest = false; + if (interactionEvent.target) + { + hitTest = false; + } + hit = true; } - - // we can break now as we have hit an object. } } } - hitTest = keepHitTestingAfterChildren; - // no point running this if the item is not interactive or does not have an interactive parent. if (interactive) { // if we are hit testing (as in we have no hit any objects yet) // We also don't need to worry about hit testing if once of the displayObjects children - // has already been hit! - if (hitTest && !hit) + // has already been hit - but only if it was interactive, otherwise we need to keep + // looking for an interactive child, just in case we hit one + if (hitTest && !interactionEvent.target) { if (displayObject.hitArea) { displayObject.worldTransform.applyInverse(point, this._tempPoint); - hit = displayObject.hitArea.contains(this._tempPoint.x, this._tempPoint.y); + if (displayObject.hitArea.contains(this._tempPoint.x, this._tempPoint.y)) + { + hit = true; + } } else if (displayObject.containsPoint) { - hit = displayObject.containsPoint(point); + if (displayObject.containsPoint(point)) + { + hit = true; + } } } @@ -878,7 +1098,10 @@ interactionEvent.target = displayObject; } - func(interactionEvent, displayObject, hit); + if (func) + { + func(interactionEvent, displayObject, !!hit); + } } } @@ -914,7 +1137,7 @@ { const event = events[i]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -998,7 +1221,7 @@ { const event = events[i]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1098,7 +1321,7 @@ const test = isRightButton ? flags.RIGHT_DOWN : flags.LEFT_DOWN; - const isDown = trackingData !== undefined && (trackingData.flags | test); + const isDown = trackingData !== undefined && (trackingData.flags & test); if (hit) { @@ -1118,11 +1341,11 @@ { if (isRightButton) { - trackingData.rightDown = hit; + trackingData.rightDown = false; } else { - trackingData.leftDown = hit; + trackingData.leftDown = false; } } } @@ -1180,7 +1403,7 @@ { const event = events[i]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1255,7 +1478,7 @@ this.setCursorMode(null); } - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1268,6 +1491,12 @@ { this.emit('mouseout', interactionEvent); } + else + { + // we can get touchleave events after touchend, so we want to make sure we don't + // introduce memory leaks + this.releaseInteractionDataForPointerId(interactionData.identifier); + } } /** @@ -1344,7 +1573,7 @@ // Only mouse and pointer can call onPointerOver, so events will always be length 1 const event = events[0]; - const interactionData = this.getInteractionDataForPointerId(event.pointerId); + const interactionData = this.getInteractionDataForPointerId(event); const interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData); @@ -1366,12 +1595,14 @@ * Get InteractionData for a given pointerId. Store that data as well * * @private - * @param {number} pointerId - Identifier from a pointer event + * @param {PointerEvent} event - Normalized pointer event, output from normalizeToPointerData * @return {InteractionData} - Interaction data for the given pointer identifier */ - getInteractionDataForPointerId(pointerId) + getInteractionDataForPointerId(event) { - if (pointerId === MOUSE_POINTER_ID) + const pointerId = event.pointerId; + + if (pointerId === MOUSE_POINTER_ID || event.pointerType === 'mouse') { return this.mouse; } diff --git a/src/interaction/InteractionTrackingData.js b/src/interaction/InteractionTrackingData.js index 6a1f69e..8493b63 100644 --- a/src/interaction/InteractionTrackingData.js +++ b/src/interaction/InteractionTrackingData.js @@ -35,8 +35,10 @@ } /** + * Unique pointer id of the event + * * @readonly - * @type {number} Unique pointer id of the event + * @member {number} */ get pointerId() { diff --git a/src/interaction/index.js b/src/interaction/index.js index 56c5b09..72f7d71 100644 --- a/src/interaction/index.js +++ b/src/interaction/index.js @@ -1,4 +1,8 @@ /** + * This namespace contains a renderer plugin for handling mouse, pointer, and touch events. + * + * Do not instantiate this plugin directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. * @namespace PIXI.interaction */ export { default as InteractionData } from './InteractionData'; diff --git a/src/interaction/interactiveTarget.js b/src/interaction/interactiveTarget.js index d252fba..cebac40 100644 --- a/src/interaction/interactiveTarget.js +++ b/src/interaction/interactiveTarget.js @@ -2,7 +2,7 @@ * Default property values of interactive objects * Used by {@link PIXI.interaction.InteractionManager} to automatically give all DisplayObjects these properties * - * @mixin + * @private * @name interactiveTarget * @memberof PIXI.interaction * @example @@ -14,18 +14,28 @@ * ); */ export default { + /** - * Determines if the displayObject be clicked/touched + * Enable interaction events for the DisplayObject. Touch, pointer and mouse + * events will not be emitted unless `interactive` is set to `true`. * - * @inner {boolean} + * @example + * const sprite = new PIXI.Sprite(texture); + * sprite.interactive = true; + * sprite.on('tap', (event) => { + * //handle event + * }); + * @member {boolean} + * @memberof PIXI.DisplayObject# */ interactive: false, /** * Determines if the children to the displayObject can be clicked/touched - * Setting this to false allows pixi to bypass a recursive hitTest function + * Setting this to false allows pixi to bypass a recursive `hitTest` function * - * @inner {boolean} + * @member {boolean} + * @memberof PIXI.Container# */ interactiveChildren: true, @@ -33,7 +43,12 @@ * Interaction shape. Children will be hit first, then this shape will be checked. * Setting this will cause this shape to be checked in hit tests rather than the displayObject's bounds. * - * @inner {PIXI.Rectangle|PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.RoundedRectangle} + * @example + * const sprite = new PIXI.Sprite(texture); + * sprite.interactive = true; + * sprite.hitArea = new PIXI.Rectangle(0, 0, 100, 100); + * @member {PIXI.Rectangle|PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.RoundedRectangle} + * @memberof PIXI.DisplayObject# */ hitArea: null, @@ -41,8 +56,12 @@ * If enabled, the mouse cursor use the pointer behavior when hovered over the displayObject if it is interactive * Setting this changes the 'cursor' property to `'pointer'`. * + * @example + * const sprite = new PIXI.Sprite(texture); + * sprite.interactive = true; + * sprite.buttonMode = true; * @member {boolean} - * @memberof PIXI.interaction.interactiveTarget# + * @memberof PIXI.DisplayObject# */ get buttonMode() { @@ -64,9 +83,14 @@ * This defines what cursor mode is used when the mouse cursor * is hovered over the displayObject. * + * @example + * const sprite = new PIXI.Sprite(texture); + * sprite.interactive = true; + * sprite.cursor = 'wait'; * @see https://developer.mozilla.org/en/docs/Web/CSS/cursor * - * @inner {string} + * @member {string} + * @memberof PIXI.DisplayObject# */ cursor: null, @@ -74,7 +98,7 @@ * Internal set of all active pointers, by identifier * * @member {Map} - * @memberof PIXI.interaction.interactiveTarget# + * @memberof PIXI.DisplayObject# * @private */ get trackedPointers() @@ -87,7 +111,8 @@ /** * Map of all tracked pointers, by identifier. Use trackedPointers to access. * - * @private {Map} + * @private + * @type {Map} */ _trackedPointers: undefined, }; diff --git a/src/loaders/index.js b/src/loaders/index.js index 5189793..daa1fc4 100644 --- a/src/loaders/index.js +++ b/src/loaders/index.js @@ -1,9 +1,22 @@ +import Application from '../core/Application'; +import Loader from './loader'; + /** + * This namespace contains APIs which extends the {@link https://github.com/englercj/resource-loader resource-loader} module + * for loading assets, data, and other resources dynamically. + * @example + * const loader = new PIXI.loaders.Loader(); + * loader.add('bunny', 'data/bunny.png') + * .add('spaceship', 'assets/spritesheet.json'); + * loader.load((loader, resources) => { + * // resources.bunny + * // resources.spaceship + * }); * @namespace PIXI.loaders */ -export { default as Loader } from './loader'; +export { Loader }; export { default as bitmapFontParser, parse as parseBitmapFontData } from './bitmapFontParser'; -export { default as spritesheetParser } from './spritesheetParser'; +export { default as spritesheetParser, getResourcePath } from './spritesheetParser'; export { default as textureParser } from './textureParser'; /** @@ -13,3 +26,55 @@ * @memberof PIXI.loaders */ export { Resource } from 'resource-loader'; + +/** + * A premade instance of the loader that can be used to load resources. + * @name shared + * @memberof PIXI.loaders + * @type {PIXI.loaders.Loader} + */ +const shared = new Loader(); + +shared.destroy = () => +{ + // protect destroying shared loader +}; + +export { shared }; + +// Mixin the loader construction +const AppPrototype = Application.prototype; + +AppPrototype._loader = null; + +/** + * Loader instance to help with asset loading. + * @name PIXI.Application#loader + * @type {PIXI.loaders.Loader} + */ +Object.defineProperty(AppPrototype, 'loader', { + get() + { + if (!this._loader) + { + const sharedLoader = this._options.sharedLoader; + + this._loader = sharedLoader ? shared : new Loader(); + } + + return this._loader; + }, +}); + +// Override the destroy function +// making sure to destroy the current Loader +AppPrototype._parentDestroy = AppPrototype.destroy; +AppPrototype.destroy = function destroy() +{ + if (this._loader) + { + this._loader.destroy(); + this._loader = null; + } + this._parentDestroy(); +}; diff --git a/src/loaders/loader.js b/src/loaders/loader.js index 767a411..59b8000 100644 --- a/src/loaders/loader.js +++ b/src/loaders/loader.js @@ -90,6 +90,15 @@ { Loader._pixiMiddleware.push(fn); } + + /** + * Destroy the loader, removes references. + */ + destroy() + { + this.removeAllListeners(); + this.reset(); + } } // Copy EE3 prototype (mixin) diff --git a/src/loaders/spritesheetParser.js b/src/loaders/spritesheetParser.js index e57dfb2..eda9584 100644 --- a/src/loaders/spritesheetParser.js +++ b/src/loaders/spritesheetParser.js @@ -1,12 +1,11 @@ import { Resource } from 'resource-loader'; -import path from 'path'; +import url from 'url'; import { Spritesheet } from '../core'; export default function () { return function spritesheetParser(resource, next) { - let resourcePath; const imageResourceName = `${resource.name}_image`; // skip if no data, its not json, it isn't spritesheet data, or the image resource already exists @@ -28,15 +27,7 @@ parentResource: resource, }; - // Prepend url path unless the resource image is a data url - if (resource.isDataUrl) - { - resourcePath = resource.data.meta.image; - } - else - { - resourcePath = `${path.dirname(resource.url.replace(this.baseUrl, ''))}/${resource.data.meta.image}`; - } + const resourcePath = getResourcePath(resource, this.baseUrl); // load the image for this sheet this.add(imageResourceName, resourcePath, loadOptions, function onImageLoad(res) @@ -56,3 +47,14 @@ }); }; } + +export function getResourcePath(resource, baseUrl) +{ + // Prepend url path unless the resource image is a data url + if (resource.isDataUrl) + { + return resource.data.meta.image; + } + + return url.resolve(resource.url.replace(baseUrl, ''), resource.data.meta.image); +} diff --git a/src/mesh/canvas/CanvasMeshRenderer.js b/src/mesh/canvas/CanvasMeshRenderer.js index 297205f..f7899e1 100644 --- a/src/mesh/canvas/CanvasMeshRenderer.js +++ b/src/mesh/canvas/CanvasMeshRenderer.js @@ -134,12 +134,33 @@ const textureWidth = base.width; const textureHeight = base.height; - const u0 = uvs[index0] * base.width; - const u1 = uvs[index1] * base.width; - const u2 = uvs[index2] * base.width; - const v0 = uvs[index0 + 1] * base.height; - const v1 = uvs[index1 + 1] * base.height; - const v2 = uvs[index2 + 1] * base.height; + let u0; + let u1; + let u2; + let v0; + let v1; + let v2; + + if (mesh.uploadUvTransform) + { + const ut = mesh._uvTransform.mapCoord; + + u0 = ((uvs[index0] * ut.a) + (uvs[index0 + 1] * ut.c) + ut.tx) * base.width; + u1 = ((uvs[index1] * ut.a) + (uvs[index1 + 1] * ut.c) + ut.tx) * base.width; + u2 = ((uvs[index2] * ut.a) + (uvs[index2 + 1] * ut.c) + ut.tx) * base.width; + v0 = ((uvs[index0] * ut.b) + (uvs[index0 + 1] * ut.d) + ut.ty) * base.height; + v1 = ((uvs[index1] * ut.b) + (uvs[index1 + 1] * ut.d) + ut.ty) * base.height; + v2 = ((uvs[index2] * ut.b) + (uvs[index2 + 1] * ut.d) + ut.ty) * base.height; + } + else + { + u0 = uvs[index0] * base.width; + u1 = uvs[index1] * base.width; + u2 = uvs[index2] * base.width; + v0 = uvs[index0 + 1] * base.height; + v1 = uvs[index1 + 1] * base.height; + v2 = uvs[index2 + 1] * base.height; + } let x0 = vertices[index0]; let x1 = vertices[index1]; diff --git a/src/mesh/webgl/MeshRenderer.js b/src/mesh/webgl/MeshRenderer.js index 78d24c3..ab99fcd 100644 --- a/src/mesh/webgl/MeshRenderer.js +++ b/src/mesh/webgl/MeshRenderer.js @@ -2,6 +2,8 @@ const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; +const matrixIdentity = core.Matrix.IDENTITY; + /** * WebGL renderer plugin for tiling sprites * @@ -64,6 +66,7 @@ mesh.geometry.glVertexArrayObjects[this.CONTEXT_UID].draw(mesh.drawMode, mesh.size, mesh.start, mesh.geometry.instanceCount); } + /** * draws mesh * @param {PIXI.mesh.RawMesh} mesh mesh instance diff --git a/src/mesh/webgl/mesh.vert b/src/mesh/webgl/mesh.vert index a337aef..acc096c 100644 --- a/src/mesh/webgl/mesh.vert +++ b/src/mesh/webgl/mesh.vert @@ -1,8 +1,9 @@ attribute vec2 aVertexPosition; attribute vec2 aTextureCoord; -uniform mat3 translationMatrix; uniform mat3 projectionMatrix; +uniform mat3 translationMatrix; +uniform mat3 uTransform; varying vec2 vTextureCoord; @@ -10,5 +11,5 @@ { gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - vTextureCoord = aTextureCoord; + vTextureCoord = (uTransform * vec3(aTextureCoord, 1.0)).xy; } diff --git a/src/particles/ParticleContainer.js b/src/particles/ParticleContainer.js index 67796cf..63f7791 100644 --- a/src/particles/ParticleContainer.js +++ b/src/particles/ParticleContainer.js @@ -313,10 +313,10 @@ frame.y * resolution, frame.width * resolution, frame.height * resolution, - positionX * resolution, - positionY * resolution, - finalWidth * resolution, - finalHeight * resolution + positionX * renderer.resolution, + positionY * renderer.resolution, + finalWidth * renderer.resolution, + finalHeight * renderer.resolution ); } } diff --git a/src/prepare/BasePrepare.js b/src/prepare/BasePrepare.js index 09eb5e5..e45c4dc 100644 --- a/src/prepare/BasePrepare.js +++ b/src/prepare/BasePrepare.js @@ -18,6 +18,18 @@ * basic queuing functionality and is extended by {@link PIXI.prepare.WebGLPrepare} and {@link PIXI.prepare.CanvasPrepare} * to provide preparation capabilities specific to their respective renderers. * + * @example + * // Create a sprite + * const sprite = new PIXI.Sprite.fromImage('something.png'); + * + * // Load object into GPU + * app.renderer.plugins.prepare.upload(sprite, () => { + * + * //Texture(s) has been uploaded to GPU + * app.stage.addChild(sprite); + * + * }) + * * @abstract * @class * @memberof PIXI.prepare @@ -100,8 +112,16 @@ this.prepareItems(); }; - this.register(findText, drawText); - this.register(findTextStyle, calculateTextStyle); + // hooks to find the correct texture + this.registerFindHook(findText); + this.registerFindHook(findTextStyle); + this.registerFindHook(findMultipleBaseTextures); + this.registerFindHook(findBaseTexture); + this.registerFindHook(findTexture); + + // upload hooks + this.registerUploadHook(drawText); + this.registerUploadHook(calculateTextStyle); } /** @@ -138,7 +158,7 @@ if (!this.ticking) { this.ticking = true; - SharedTicker.addOnce(this.tick, this); + SharedTicker.addOnce(this.tick, this, core.UPDATE_PRIORITY.UTILITY); } } else if (done) @@ -172,13 +192,16 @@ const item = this.queue[0]; let uploaded = false; - for (let i = 0, len = this.uploadHooks.length; i < len; i++) + if (item && !item._destroyed) { - if (this.uploadHooks[i](this.uploadHookHelper, item)) + for (let i = 0, len = this.uploadHooks.length; i < len; i++) { - this.queue.shift(); - uploaded = true; - break; + if (this.uploadHooks[i](this.uploadHookHelper, item)) + { + this.queue.shift(); + uploaded = true; + break; + } } } @@ -205,26 +228,36 @@ else { // if we are not finished, on the next rAF do this again - SharedTicker.addOnce(this.tick, this); + SharedTicker.addOnce(this.tick, this, core.UPDATE_PRIORITY.UTILITY); } } /** - * Adds hooks for finding and uploading items. + * Adds hooks for finding items. * - * @param {Function} [addHook] - Function call that takes two parameters: `item:*, queue:Array` - function must return `true` if it was able to add item to the queue. - * @param {Function} [uploadHook] - Function call that takes two parameters: `prepare:CanvasPrepare, item:*` and - * function must return `true` if it was able to handle upload of item. - * @return {PIXI.CanvasPrepare} Instance of plugin for chaining. + * @param {Function} addHook - Function call that takes two parameters: `item:*, queue:Array` + * function must return `true` if it was able to add item to the queue. + * @return {PIXI.BasePrepare} Instance of plugin for chaining. */ - register(addHook, uploadHook) + registerFindHook(addHook) { if (addHook) { this.addHooks.push(addHook); } + return this; + } + + /** + * Adds hooks for uploading items. + * + * @param {Function} uploadHook - Function call that takes two parameters: `prepare:CanvasPrepare, item:*` and + * function must return `true` if it was able to handle upload of item. + * @return {PIXI.BasePrepare} Instance of plugin for chaining. + */ + registerUploadHook(uploadHook) + { if (uploadHook) { this.uploadHooks.push(uploadHook); @@ -287,6 +320,88 @@ } /** + * Built-in hook to find multiple textures from objects like AnimatedSprites. + * + * @private + * @param {PIXI.DisplayObject} item - Display object to check + * @param {Array<*>} queue - Collection of items to upload + * @return {boolean} if a PIXI.Texture object was found. + */ +function findMultipleBaseTextures(item, queue) +{ + let result = false; + + // Objects with mutliple textures + if (item && item._textures && item._textures.length) + { + for (let i = 0; i < item._textures.length; i++) + { + if (item._textures[i] instanceof core.Texture) + { + const baseTexture = item._textures[i].baseTexture; + + if (queue.indexOf(baseTexture) === -1) + { + queue.push(baseTexture); + result = true; + } + } + } + } + + return result; +} + +/** + * Built-in hook to find BaseTextures from Sprites. + * + * @private + * @param {PIXI.DisplayObject} item - Display object to check + * @param {Array<*>} queue - Collection of items to upload + * @return {boolean} if a PIXI.Texture object was found. + */ +function findBaseTexture(item, queue) +{ + // Objects with textures, like Sprites/Text + if (item instanceof core.BaseTexture) + { + if (queue.indexOf(item) === -1) + { + queue.push(item); + } + + return true; + } + + return false; +} + +/** + * Built-in hook to find textures from objects. + * + * @private + * @param {PIXI.DisplayObject} item - Display object to check + * @param {Array<*>} queue - Collection of items to upload + * @return {boolean} if a PIXI.Texture object was found. + */ +function findTexture(item, queue) +{ + if (item._texture && item._texture instanceof core.Texture) + { + const texture = item._texture.baseTexture; + + if (queue.indexOf(texture) === -1) + { + queue.push(texture); + } + + return true; + } + + return false; +} + +/** * Built-in hook to draw PIXI.Text to its texture. * * @private diff --git a/src/prepare/canvas/CanvasPrepare.js b/src/prepare/canvas/CanvasPrepare.js index 0224bb5..09c207a 100644 --- a/src/prepare/canvas/CanvasPrepare.js +++ b/src/prepare/canvas/CanvasPrepare.js @@ -43,7 +43,7 @@ this.ctx = this.canvas.getContext('2d'); // Add textures to upload - this.register(findBaseTextures, uploadBaseTextures); + this.registerUploadHook(uploadBaseTextures); } /** @@ -89,39 +89,4 @@ return false; } -/** - * Built-in hook to find textures from Sprites. - * - * @private - * @param {PIXI.DisplayObject} item -Display object to check - * @param {Array<*>} queue - Collection of items to upload - * @return {boolean} if a PIXI.Texture object was found. - */ -function findBaseTextures(item, queue) -{ - // Objects with textures, like Sprites/Text - if (item instanceof core.BaseTexture) - { - if (queue.indexOf(item) === -1) - { - queue.push(item); - } - - return true; - } - else if (item._texture && item._texture instanceof core.Texture) - { - const texture = item._texture.baseTexture; - - if (queue.indexOf(texture) === -1) - { - queue.push(texture); - } - - return true; - } - - return false; -} - core.CanvasRenderer.registerPlugin('prepare', CanvasPrepare); diff --git a/src/prepare/index.js b/src/prepare/index.js index f559c45..e7491ec 100644 --- a/src/prepare/index.js +++ b/src/prepare/index.js @@ -1,4 +1,29 @@ /** + * The prepare namespace provides renderer-specific plugins for pre-rendering DisplayObjects. These plugins are useful for + * asynchronously preparing assets, textures, graphics waiting to be displayed. + * + * Do not instantiate these plugins directly. It is available from the `renderer.plugins` property. + * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.WebGLRenderer#plugins}. + * @example + * // Create a new application + * const app = new PIXI.Application(); + * document.body.appendChild(app.view); + * + * // Don't start rendering right away + * app.stop(); + * + * // create a display object + * const rect = new PIXI.Graphics() + * .beginFill(0x00ff00) + * .drawRect(40, 40, 200, 200); + * + * // Add to the stage + * app.stage.addChild(rect); + * + * // Don't start rendering until the graphic is uploaded to the GPU + * app.renderer.plugins.prepare.upload(app.stage, () => { + * app.start(); + * }); * @namespace PIXI.prepare */ export { default as webgl } from './webgl/WebGLPrepare'; diff --git a/src/prepare/webgl/WebGLPrepare.js b/src/prepare/webgl/WebGLPrepare.js index eb7023f..9df3c3f 100644 --- a/src/prepare/webgl/WebGLPrepare.js +++ b/src/prepare/webgl/WebGLPrepare.js @@ -22,12 +22,11 @@ this.uploadHookHelper = this.renderer; // Add textures and graphics to upload - this.register(findBaseTextures, uploadBaseTextures) - .register(findGraphics, uploadGraphics); + this.registerFindHook(findGraphics); + this.registerUploadHook(uploadBaseTextures); + this.registerUploadHook(uploadGraphics); } - } - /** * Built-in hook to upload PIXI.Texture objects to the GPU. * @@ -80,41 +79,6 @@ } /** - * Built-in hook to find textures from Sprites. - * - * @private - * @param {PIXI.DisplayObject} item - Display object to check - * @param {Array<*>} queue - Collection of items to upload - * @return {boolean} if a PIXI.Texture object was found. - */ -function findBaseTextures(item, queue) -{ - // Objects with textures, like Sprites/Text - if (item instanceof core.BaseTexture) - { - if (queue.indexOf(item) === -1) - { - queue.push(item); - } - - return true; - } - else if (item._texture && item._texture instanceof core.Texture) - { - const texture = item._texture.baseTexture; - - if (queue.indexOf(texture) === -1) - { - queue.push(texture); - } - - return true; - } - - return false; -} - -/** * Built-in hook to find graphics. * * @private diff --git a/test/.eslintrc.json b/test/.eslintrc.json index e03220d..ac12110 100644 --- a/test/.eslintrc.json +++ b/test/.eslintrc.json @@ -12,7 +12,8 @@ "sinon": false, "expect": false, "assert": false, - "PIXI": false + "PIXI": false, + "PointerEvent": true }, "rules": { "func-names": 0, diff --git a/test/core/BaseTexture.js b/test/core/BaseTexture.js index 8b93478..bb65068 100644 --- a/test/core/BaseTexture.js +++ b/test/core/BaseTexture.js @@ -14,4 +14,28 @@ expect(baseTexture.imageType).to.be.equals('png'); }); }); + + it('should remove Canvas BaseTexture from cache on destroy', function () + { + const canvas = document.createElement('canvas'); + const texture = PIXI.BaseTexture.fromCanvas(canvas); + const _pixiId = canvas._pixiId; + + expect(PIXI.utils.BaseTextureCache[_pixiId]).to.equal(texture); + texture.destroy(); + expect(PIXI.utils.BaseTextureCache[_pixiId]).to.equal(undefined); + }); + + it('should remove Image BaseTexture from cache on destroy', function () + { + const URL = 'foo.png'; + const NAME = 'bar'; + const image = new Image(); + + const texture = PIXI.Texture.fromLoader(image, URL, NAME); + + expect(PIXI.utils.BaseTextureCache[NAME]).to.equal(texture.baseTexture); + texture.destroy(true); + expect(PIXI.utils.BaseTextureCache[NAME]).to.equal(undefined); + }); }); diff --git a/test/core/Circle.js b/test/core/Circle.js index 183f98f..f621992 100644 --- a/test/core/Circle.js +++ b/test/core/Circle.js @@ -53,6 +53,10 @@ expect(circ1.contains(10, 16)).to.be.false; expect(circ1.contains(11, 15)).to.be.false; expect(circ1.contains(0, 0)).to.be.false; + + const circ2 = new PIXI.Circle(10, 10, 0); + + expect(circ2.contains(10, 10)).to.be.false; }); it('should return framing rectangle', function () diff --git a/test/core/Container.js b/test/core/Container.js index d11be33..cefde54 100644 --- a/test/core/Container.js +++ b/test/core/Container.js @@ -2,30 +2,36 @@ function testAddChild(fn) { - fn(function (container, obj) + return function () { - container.addChild(obj); - }); - fn(function (container, obj) - { - container.addChildAt(obj); - }); + fn(function (container, obj) + { + container.addChild(obj); + }); + fn(function (container, obj) + { + container.addChildAt(obj); + }); + }; } function testRemoveChild(fn) { - fn(function (container, obj) + return function () { - container.removeChild(obj); - }); - fn(function (container, obj) - { - container.removeChildAt(container.children.indexOf(obj)); - }); - fn(function (container, obj) - { - container.removeChildren(container.children.indexOf(obj), container.children.indexOf(obj) + 1); - }); + fn(function (container, obj) + { + container.removeChild(obj); + }); + fn(function (container, obj) + { + container.removeChildAt(container.children.indexOf(obj)); + }); + fn(function (container, obj) + { + container.removeChildren(container.children.indexOf(obj), container.children.indexOf(obj) + 1); + }); + }; } describe('PIXI.Container', function () diff --git a/test/core/Ellipse.js b/test/core/Ellipse.js index f2e8234..3f47442 100644 --- a/test/core/Ellipse.js +++ b/test/core/Ellipse.js @@ -56,6 +56,10 @@ expect(ellipse1.contains(10, 16)).to.be.false; expect(ellipse1.contains(11, 15)).to.be.false; expect(ellipse1.contains(0, 0)).to.be.false; + + const ellipse2 = new PIXI.Ellipse(10, 10, 0, 0); + + expect(ellipse2.contains(10, 10)).to.be.false; }); it('should return framing rectangle', function () diff --git a/test/core/Matrix.js b/test/core/Matrix.js index 9d2694b..76bd57c 100644 --- a/test/core/Matrix.js +++ b/test/core/Matrix.js @@ -107,10 +107,60 @@ expect(m1.ty).to.equal(m2.ty); }); + it('should prepend matrix', function () + { + const m1 = new PIXI.Matrix(); + const m2 = new PIXI.Matrix(); + + m2.set(2, 3, 4, 5, 100, 200); + m1.prepend(m2); + + expect(m1.a).to.equal(m2.a); + expect(m1.b).to.equal(m2.b); + expect(m1.c).to.equal(m2.c); + expect(m1.d).to.equal(m2.d); + expect(m1.tx).to.equal(m2.tx); + expect(m1.ty).to.equal(m2.ty); + + const m3 = new PIXI.Matrix(); + const m4 = new PIXI.Matrix(); + + m3.prepend(m4); + + expect(m3.a).to.equal(m4.a); + expect(m3.b).to.equal(m4.b); + expect(m3.c).to.equal(m4.c); + expect(m3.d).to.equal(m4.d); + expect(m3.tx).to.equal(m4.tx); + expect(m3.ty).to.equal(m4.ty); + }); + it('should get IDENTITY and TEMP_MATRIX', function () { expect(PIXI.Matrix.IDENTITY instanceof PIXI.Matrix).to.be.true; expect(PIXI.Matrix.TEMP_MATRIX instanceof PIXI.Matrix).to.be.true; }); -}); + it('should reset matrix to default when identity() is called', function () + { + const matrix = new PIXI.Matrix(); + + matrix.set(2, 3, 4, 5, 100, 200); + + expect(matrix.a).to.equal(2); + expect(matrix.b).to.equal(3); + expect(matrix.c).to.equal(4); + expect(matrix.d).to.equal(5); + expect(matrix.tx).to.equal(100); + expect(matrix.ty).to.equal(200); + + matrix.identity(); + + expect(matrix.a).to.equal(1); + expect(matrix.b).to.equal(0); + expect(matrix.c).to.equal(0); + expect(matrix.d).to.equal(1); + expect(matrix.tx).to.equal(0); + expect(matrix.ty).to.equal(0); + }); +}); diff --git a/test/core/Spritesheet.js b/test/core/Spritesheet.js index 283adf5..7601a0c 100644 --- a/test/core/Spritesheet.js +++ b/test/core/Spritesheet.js @@ -18,6 +18,7 @@ expect(textures[id]).to.be.an.instanceof(PIXI.Texture); expect(textures[id].width).to.equal(spritesheet.data.frames[id].frame.w / spritesheet.resolution); expect(textures[id].height).to.equal(spritesheet.data.frames[id].frame.h / spritesheet.resolution); + expect(textures[id].textureCacheId).to.equal(id); spritesheet.destroy(true); expect(spritesheet.textures).to.be.null; expect(spritesheet.baseTexture).to.be.null; diff --git a/test/core/Texture.js b/test/core/Texture.js index 8c4ef98..241ec3e 100644 --- a/test/core/Texture.js +++ b/test/core/Texture.js @@ -16,4 +16,20 @@ expect(PIXI.utils.TextureCache[URL]).to.equal(texture); expect(PIXI.utils.BaseTextureCache[URL]).to.equal(texture.baseTexture); }); + + it('should remove Texture from cache on destroy', function () + { + const NAME = 'foo'; + const NAME2 = 'bar'; + const texture = new PIXI.Texture(new PIXI.BaseTexture()); + + PIXI.Texture.addTextureToCache(texture, NAME); + PIXI.Texture.addTextureToCache(texture, NAME2); + expect(texture.textureCacheId).to.equal(NAME); + expect(PIXI.utils.TextureCache[NAME]).to.equal(texture); + expect(PIXI.utils.TextureCache[NAME2]).to.equal(texture); + texture.destroy(); + expect(PIXI.utils.TextureCache[NAME]).to.equal(undefined); + expect(PIXI.utils.TextureCache[NAME2]).to.equal(undefined); + }); }); diff --git a/test/core/Ticker.js b/test/core/Ticker.js new file mode 100644 index 0000000..188770f --- /dev/null +++ b/test/core/Ticker.js @@ -0,0 +1,322 @@ +'use strict'; + +const Ticker = PIXI.ticker.Ticker; +const shared = PIXI.ticker.shared; + +describe('PIXI.ticker.Ticker', function () +{ + before(function () + { + this.length = (ticker) => + { + ticker = ticker || shared; + + if (!ticker._head || !ticker._head.next) + { + return 0; + } + + let listener = ticker._head.next; + let i = 0; + + while (listener) + { + listener = listener.next; + i++; + } + + return i; + }; + }); + + it('should be available', function () + { + expect(Ticker).to.be.a.function; + expect(shared).to.be.an.instanceof(Ticker); + }); + + it('should create a new ticker and destroy it', function () + { + const ticker = new Ticker(); + + ticker.start(); + + const listener = sinon.spy(); + + expect(this.length(ticker)).to.equal(0); + + ticker.add(listener); + + expect(this.length(ticker)).to.equal(1); + + ticker.destroy(); + + expect(ticker._head).to.be.null; + expect(ticker.started).to.be.false; + expect(this.length(ticker)).to.equal(0); + }); + + it('should protect destroying shared ticker', function () + { + shared.destroy(); + expect(shared._head).to.not.be.null; + expect(shared.started).to.be.true; + }); + + it('should add and remove listener', function () + { + const listener = sinon.spy(); + const length = this.length(); + + shared.add(listener); + + expect(this.length()).to.equal(length + 1); + + shared.remove(listener); + + expect(this.length()).to.equal(length); + }); + + it('should update a listener', function () + { + const listener = sinon.spy(); + + shared.add(listener); + shared.update(); + + expect(listener.calledOnce).to.be.true; + + shared.remove(listener); + shared.update(); + + expect(listener.calledOnce).to.be.true; + }); + + it('should update a listener twice and remove once', function () + { + const listener = sinon.spy(); + const length = this.length(); + + shared.add(listener).add(listener); + shared.update(); + + expect(listener.calledTwice).to.be.true; + expect(this.length()).to.equal(length + 2); + + shared.remove(listener); + shared.update(); + + expect(listener.calledTwice).to.be.true; + expect(this.length()).to.equal(length); + }); + + it('should respect priority order', function () + { + const length = this.length(); + const listener1 = sinon.spy(); + const listener2 = sinon.spy(); + const listener3 = sinon.spy(); + const listener4 = sinon.spy(); + + shared.add(listener1, null, PIXI.UPDATE_PRIORITY.LOW) + .add(listener4, null, PIXI.UPDATE_PRIORITY.INTERACTION) + .add(listener3, null, PIXI.UPDATE_PRIORITY.HIGH) + .add(listener2, null, PIXI.UPDATE_PRIORITY.NORMAL); + + shared.update(); + + expect(this.length()).to.equal(length + 4); + + sinon.assert.callOrder(listener4, listener3, listener2, listener1); + + shared.remove(listener1) + .remove(listener2) + .remove(listener3) + .remove(listener4); + + expect(this.length()).to.equal(length); + }); + + it('should auto-remove once listeners', function () + { + const length = this.length(); + const listener = sinon.spy(); + + shared.addOnce(listener); + + shared.update(); + + expect(listener.calledOnce).to.be.true; + expect(this.length()).to.equal(length); + }); + + it('should call inserted item with a lower priority', function () + { + const length = this.length(); + const lowListener = sinon.spy(); + const highListener = sinon.spy(); + const mainListener = sinon.spy(() => + { + shared.add(highListener, null, PIXI.UPDATE_PRIORITY.HIGH); + shared.add(lowListener, null, PIXI.UPDATE_PRIORITY.LOW); + }); + + shared.add(mainListener, null, PIXI.UPDATE_PRIORITY.NORMAL); + + shared.update(); + + expect(this.length()).to.equal(length + 3); + + expect(mainListener.calledOnce).to.be.true; + expect(lowListener.calledOnce).to.be.true; + expect(highListener.calledOnce).to.be.false; + + shared.remove(mainListener) + .remove(highListener) + .remove(lowListener); + + expect(this.length()).to.equal(length); + }); + + it('should remove emit low-priority item during emit', function () + { + const length = this.length(); + const listener2 = sinon.spy(); + const listener1 = sinon.spy(() => + { + shared.add(listener2, null, PIXI.UPDATE_PRIORITY.LOW); + }); + + shared.add(listener1, null, PIXI.UPDATE_PRIORITY.NORMAL); + + shared.update(); + + expect(this.length()).to.equal(length + 2); + + expect(listener2.calledOnce).to.be.true; + expect(listener1.calledOnce).to.be.true; + + shared.remove(listener1) + .remove(listener2); + + expect(this.length()).to.equal(length); + }); + + it('should remove itself on emit after adding new item', function () + { + const length = this.length(); + const listener2 = sinon.spy(); + const listener1 = sinon.spy(() => + { + shared.add(listener2, null, PIXI.UPDATE_PRIORITY.LOW); + shared.remove(listener1); + + // listener is removed right away + expect(this.length()).to.equal(length + 1); + }); + + shared.add(listener1, null, PIXI.UPDATE_PRIORITY.NORMAL); + + shared.update(); + + expect(this.length()).to.equal(length + 1); + + expect(listener2.calledOnce).to.be.true; + expect(listener1.calledOnce).to.be.true; + + shared.remove(listener2); + + expect(this.length()).to.equal(length); + }); + + it('should remove itself before, still calling new item', function () + { + const length = this.length(); + const listener2 = sinon.spy(); + const listener1 = sinon.spy(() => + { + shared.remove(listener1); + shared.add(listener2, null, PIXI.UPDATE_PRIORITY.LOW); + + // listener is removed right away + expect(this.length()).to.equal(length + 1); + }); + + shared.add(listener1, null, PIXI.UPDATE_PRIORITY.NORMAL); + + shared.update(); + + expect(this.length()).to.equal(length + 1); + + expect(listener2.calledOnce).to.be.true; + expect(listener1.calledOnce).to.be.true; + + shared.remove(listener2); + + expect(this.length()).to.equal(length); + }); + + it('should remove items before and after current priority', function () + { + const length = this.length(); + const listener2 = sinon.spy(); + const listener3 = sinon.spy(); + const listener4 = sinon.spy(); + + shared.add(listener2, null, PIXI.UPDATE_PRIORITY.HIGH); + shared.add(listener3, null, PIXI.UPDATE_PRIORITY.LOW); + shared.add(listener4, null, PIXI.UPDATE_PRIORITY.LOW); + + const listener1 = sinon.spy(() => + { + shared.remove(listener2) + .remove(listener3); + + // listener is removed right away + expect(this.length()).to.equal(length + 2); + }); + + shared.add(listener1, null, PIXI.UPDATE_PRIORITY.NORMAL); + + shared.update(); + + expect(this.length()).to.equal(length + 2); + + expect(listener2.calledOnce).to.be.true; + expect(listener3.calledOnce).to.be.false; + expect(listener4.calledOnce).to.be.true; + expect(listener1.calledOnce).to.be.true; + + shared.update(); + + expect(listener2.calledOnce).to.be.true; + expect(listener3.calledOnce).to.be.false; + expect(listener4.calledTwice).to.be.true; + expect(listener1.calledTwice).to.be.true; + + shared.remove(listener1) + .remove(listener4); + + expect(this.length()).to.equal(length); + }); + + it('should destroy on listener', function (done) + { + const ticker = new Ticker(); + const listener2 = sinon.spy(); + const listener = sinon.spy(() => + { + ticker.destroy(); + setTimeout(() => + { + expect(listener2.called).to.be.false; + expect(listener.calledOnce).to.be.true; + done(); + }, 0); + }); + + ticker.add(listener); + ticker.add(listener2, null, PIXI.UPDATE_PRIORITY.LOW); + ticker.start(); + }); +}); diff --git a/test/core/TilingSprite.js b/test/core/TilingSprite.js index 2e449a7..7694792 100644 --- a/test/core/TilingSprite.js +++ b/test/core/TilingSprite.js @@ -24,4 +24,25 @@ expect(bounds.height).to.equal(600); }); }); + + it('checks if tilingSprite contains a point', function () + { + const texture = new PIXI.Texture(new PIXI.BaseTexture()); + const tilingSprite = new PIXI.extras.TilingSprite(texture, 200, 300); + + expect(tilingSprite.containsPoint(new PIXI.Point(1, 1))).to.equal(true); + expect(tilingSprite.containsPoint(new PIXI.Point(300, 400))).to.equal(false); + }); + + it('gets and sets height and width correctly', function () + { + const texture = new PIXI.Texture(new PIXI.BaseTexture()); + const tilingSprite = new PIXI.extras.TilingSprite(texture, 200, 300); + + tilingSprite.width = 400; + tilingSprite.height = 600; + + expect(tilingSprite.width).to.equal(400); + expect(tilingSprite.height).to.equal(600); + }); }); diff --git a/test/core/index.js b/test/core/index.js index 8530131..58dc30e 100755 --- a/test/core/index.js +++ b/test/core/index.js @@ -17,6 +17,7 @@ require('./util'); require('./Plane'); require('./Point'); +require('./Polygon'); require('./ObservablePoint'); require('./Matrix'); require('./Rectangle'); @@ -27,4 +28,5 @@ require('./WebGLRenderer'); require('./Ellipse'); require('./Texture'); +require('./Ticker'); require('./filters'); diff --git a/test/interaction/InteractionManager.js b/test/interaction/InteractionManager.js index 0e7e707..31385b9 100644 --- a/test/interaction/InteractionManager.js +++ b/test/interaction/InteractionManager.js @@ -4,6 +4,16 @@ describe('PIXI.interaction.InteractionManager', function () { + afterEach(function () + { + // if we made a MockPointer for the test, clean it up + if (this.pointer) + { + this.pointer.cleanUp(); + this.pointer = null; + } + }); + describe('event basics', function () { it('should call mousedown handler', function () @@ -11,7 +21,7 @@ const stage = new PIXI.Container(); const graphics = new PIXI.Graphics(); const eventSpy = sinon.spy(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); stage.addChild(graphics); graphics.beginFill(0xFFFFFF); @@ -29,7 +39,7 @@ const stage = new PIXI.Container(); const graphics = new PIXI.Graphics(); const eventSpy = sinon.spy(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); stage.addChild(graphics); graphics.beginFill(0xFFFFFF); @@ -47,7 +57,7 @@ const stage = new PIXI.Container(); const graphics = new PIXI.Graphics(); const eventSpy = sinon.spy(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); stage.addChild(graphics); graphics.beginFill(0xFFFFFF); @@ -66,7 +76,7 @@ const stage = new PIXI.Container(); const graphics = new PIXI.Graphics(); const eventSpy = sinon.spy(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); stage.addChild(graphics); graphics.beginFill(0xFFFFFF); @@ -84,7 +94,7 @@ const stage = new PIXI.Container(); const graphics = new PIXI.Graphics(); const eventSpy = sinon.spy(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); stage.addChild(graphics); graphics.beginFill(0xFFFFFF); @@ -286,7 +296,7 @@ const stage = new PIXI.Container(); const graphics = new PIXI.Graphics(); const clickSpy = sinon.spy(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); stage.addChild(graphics); graphics.beginFill(0xFFFFFF); @@ -304,7 +314,7 @@ const stage = new PIXI.Container(); const graphics = new PIXI.Graphics(); const clickSpy = sinon.spy(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); stage.addChild(graphics); graphics.beginFill(0xFFFFFF); @@ -316,6 +326,29 @@ expect(clickSpy).to.not.have.been.called; }); + + it('should not call handler when mousedown not received', function () + { + const stage = new PIXI.Container(); + const graphics = new PIXI.Graphics(); + const clickSpy = sinon.spy(); + const pointer = new MockPointer(stage); + + stage.addChild(graphics); + graphics.beginFill(0xFFFFFF); + graphics.drawRect(0, 0, 50, 50); + graphics.interactive = true; + graphics.on('click', clickSpy); + + pointer.mouseup(10, 10); + + expect(clickSpy, 'click should not happen on first mouseup').to.not.have.been.called; + + // test again, just because it was a bug that was reported + pointer.mouseup(20, 20); + + expect(clickSpy, 'click should not happen on second mouseup').to.not.have.been.called; + }); }); describe('onTap', function () @@ -325,7 +358,7 @@ const stage = new PIXI.Container(); const graphics = new PIXI.Graphics(); const clickSpy = sinon.spy(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); stage.addChild(graphics); graphics.beginFill(0xFFFFFF); @@ -343,7 +376,7 @@ const stage = new PIXI.Container(); const graphics = new PIXI.Graphics(); const clickSpy = sinon.spy(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); stage.addChild(graphics); graphics.beginFill(0xFFFFFF); @@ -359,32 +392,62 @@ describe('overlapping children', function () { - function getScene(callbackEventName) + function getScene(callbackEventName, splitParents) { const behindChild = new PIXI.Graphics(); const frontChild = new PIXI.Graphics(); const parent = new PIXI.Container(); - const behindChildCallback = sinon.spy(); - const frontChildCallback = sinon.spy(); - const parentCallback = sinon.spy(); + const behindChildCallback = sinon.spy(function behindSpy() { /* no op*/ }); + const frontChildCallback = sinon.spy(function frontSpy() { /* no op*/ }); + const parentCallback = sinon.spy(function parentSpy() { /* no op*/ }); + let behindParent; + let frontParent; + let behindParentCallback; + let frontParentCallback; behindChild.beginFill(0xFF); behindChild.drawRect(0, 0, 50, 50); behindChild.on(callbackEventName, behindChildCallback); + behindChild.name = 'behind'; frontChild.beginFill(0x00FF); frontChild.drawRect(0, 0, 50, 50); frontChild.on(callbackEventName, frontChildCallback); + frontChild.name = 'front'; - parent.addChild(behindChild, frontChild); + if (splitParents) + { + behindParent = new PIXI.Container(); + behindParent.name = 'behindParent'; + frontParent = new PIXI.Container(); + frontParent.name = 'frontParent'; + behindParentCallback = sinon.spy(function behindParentSpy() { /* no op*/ }); + frontParentCallback = sinon.spy(function frontParentSpy() { /* no op*/ }); + behindParent.on(callbackEventName, behindParentCallback); + frontParent.on(callbackEventName, frontParentCallback); + + parent.addChild(behindParent, frontParent); + behindParent.addChild(behindChild); + frontParent.addChild(frontChild); + + parent.name = 'parent'; + } + else + { + parent.addChild(behindChild, frontChild); + } parent.on(callbackEventName, parentCallback); return { behindChild, frontChild, + behindParent, + frontParent, parent, behindChildCallback, frontChildCallback, + behindParentCallback, + frontParentCallback, parentCallback, }; } @@ -396,7 +459,7 @@ it('should callback front child when clicking front child', function () { const stage = new PIXI.Container(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); const scene = getScene('click'); scene.behindChild.interactive = true; @@ -414,7 +477,7 @@ it('should callback front child when clicking overlap', function () { const stage = new PIXI.Container(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); const scene = getScene('click'); scene.behindChild.interactive = true; @@ -432,7 +495,7 @@ it('should callback behind child when clicking behind child', function () { const stage = new PIXI.Container(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); const scene = getScene('click'); scene.behindChild.interactive = true; @@ -446,6 +509,48 @@ expect(scene.behindChildCallback).to.have.been.calledOnce; expect(scene.parentCallback).to.not.have.been.called; }); + + it('should callback front child of different non-interactive parents when clicking overlap', function () + { + const stage = new PIXI.Container(); + const pointer = this.pointer = new MockPointer(stage); + const scene = getScene('click', true); + + scene.behindChild.interactive = true; + scene.behindChild.x = 25; + scene.frontChild.interactive = true; + + stage.addChild(scene.parent); + pointer.click(40, 10); + + expect(scene.behindChildCallback).to.not.have.been.called; + expect(scene.frontChildCallback).to.have.been.calledOnce; + expect(scene.parentCallback).to.not.have.been.called; + expect(scene.behindParentCallback).to.not.have.been.called; + expect(scene.frontParentCallback).to.not.have.been.called; + }); + + it('should callback front child of different interactive parents when clicking overlap', function () + { + const stage = new PIXI.Container(); + const pointer = this.pointer = new MockPointer(stage); + const scene = getScene('click', true); + + scene.behindChild.interactive = true; + scene.behindChild.x = 25; + scene.frontChild.interactive = true; + scene.behindParent.interactive = true; + scene.frontParent.interactive = true; + + stage.addChild(scene.parent); + pointer.click(40, 10); + + expect(scene.behindChildCallback).to.not.have.been.called; + expect(scene.frontChildCallback).to.have.been.calledOnce; + expect(scene.parentCallback).to.not.have.been.called; + expect(scene.behindParentCallback).to.not.have.been.called; + expect(scene.frontParentCallback).to.have.been.calledOnce; + }); }); describe('when front child is non-interactive', function () @@ -453,7 +558,7 @@ it('should not callback when clicking front child', function () { const stage = new PIXI.Container(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); const scene = getScene('click'); scene.behindChild.interactive = true; @@ -470,7 +575,7 @@ it('should callback behind child when clicking overlap', function () { const stage = new PIXI.Container(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); const scene = getScene('click'); scene.behindChild.interactive = true; @@ -487,7 +592,7 @@ it('should callback behind child when clicking behind child', function () { const stage = new PIXI.Container(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); const scene = getScene('click'); scene.behindChild.interactive = true; @@ -507,7 +612,7 @@ it('should callback front child when clicking front child', function () { const stage = new PIXI.Container(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); const scene = getScene('click'); scene.behindChild.x = 25; @@ -524,7 +629,7 @@ it('should callback front child when clicking overlap', function () { const stage = new PIXI.Container(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); const scene = getScene('click'); scene.behindChild.x = 25; @@ -541,7 +646,7 @@ it('should not callback when clicking behind child', function () { const stage = new PIXI.Container(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); const scene = getScene('click'); scene.behindChild.x = 25; @@ -564,7 +669,7 @@ it('should callback parent and front child when clicking front child', function () { const stage = new PIXI.Container(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); const scene = getScene('click'); scene.behindChild.interactive = true; @@ -583,7 +688,7 @@ it('should callback parent and front child when clicking overlap', function () { const stage = new PIXI.Container(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); const scene = getScene('click'); scene.behindChild.interactive = true; @@ -602,7 +707,7 @@ it('should callback parent and behind child when clicking behind child', function () { const stage = new PIXI.Container(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); const scene = getScene('click'); scene.behindChild.interactive = true; @@ -617,6 +722,50 @@ expect(scene.behindChildCallback).to.have.been.calledOnce; expect(scene.parentCallback).to.have.been.calledOnce; }); + + it('should callback front child of different non-interactive parents when clicking overlap', function () + { + const stage = new PIXI.Container(); + const pointer = this.pointer = new MockPointer(stage); + const scene = getScene('click', true); + + scene.behindChild.interactive = true; + scene.behindChild.x = 25; + scene.frontChild.interactive = true; + scene.parent.interactive = true; + + stage.addChild(scene.parent); + pointer.click(40, 10); + + expect(scene.behindChildCallback).to.not.have.been.called; + expect(scene.frontChildCallback).to.have.been.calledOnce; + expect(scene.parentCallback).to.have.been.calledOnce; + expect(scene.behindParentCallback).to.not.have.been.called; + expect(scene.frontParentCallback).to.not.have.been.called; + }); + + it('should callback front child of different interactive parents when clicking overlap', function () + { + const stage = new PIXI.Container(); + const pointer = this.pointer = new MockPointer(stage); + const scene = getScene('click', true); + + scene.behindChild.interactive = true; + scene.behindChild.x = 25; + scene.frontChild.interactive = true; + scene.parent.interactive = true; + scene.behindParent.interactive = true; + scene.frontParent.interactive = true; + + stage.addChild(scene.parent); + pointer.click(40, 10); + + expect(scene.behindChildCallback).to.not.have.been.called; + expect(scene.frontChildCallback).to.have.been.calledOnce; + expect(scene.parentCallback).to.have.been.calledOnce; + expect(scene.behindParentCallback).to.not.have.been.called; + expect(scene.frontParentCallback).to.have.been.calledOnce; + }); }); describe('when front child is non-interactive', function () @@ -624,7 +773,7 @@ it('should callback parent when clicking front child', function () { const stage = new PIXI.Container(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); const scene = getScene('click'); scene.behindChild.interactive = true; @@ -642,7 +791,7 @@ it('should callback parent and behind child when clicking overlap', function () { const stage = new PIXI.Container(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); const scene = getScene('click'); scene.behindChild.interactive = true; @@ -660,7 +809,7 @@ it('should callback parent and behind child when clicking behind child', function () { const stage = new PIXI.Container(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); const scene = getScene('click'); scene.behindChild.interactive = true; @@ -681,7 +830,7 @@ it('should callback parent and front child when clicking front child', function () { const stage = new PIXI.Container(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); const scene = getScene('click'); scene.behindChild.x = 25; @@ -699,7 +848,7 @@ it('should callback parent and front child when clicking overlap', function () { const stage = new PIXI.Container(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); const scene = getScene('click'); scene.behindChild.x = 25; @@ -717,7 +866,7 @@ it('should callback parent when clicking behind child', function () { const stage = new PIXI.Container(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); const scene = getScene('click'); scene.behindChild.x = 25; @@ -733,6 +882,42 @@ }); }); }); + + it('Semi-complicated nesting with overlap, should not call behind callback', function () + { + const stage = new PIXI.Container(); + const frontParent = new PIXI.Container(); + const frontChild = new PIXI.Graphics(); + const behindParent = new PIXI.Container(); + const subParent = new PIXI.Container(); + const behindChild = new PIXI.Graphics(); + const behindCallback = sinon.spy(function behindSpy() { /* no op*/ }); + const frontCallback = sinon.spy(function frontSpy() { /* no op*/ }); + + behindChild.beginFill(0xFF); + behindChild.drawRect(0, 0, 50, 50); + subParent.on('click', behindCallback); + + frontChild.beginFill(0x00FF); + frontChild.drawRect(0, 0, 50, 50); + frontParent.on('click', frontCallback); + const pointer = this.pointer = new MockPointer(stage); + + behindParent.x = 25; + subParent.interactive = true; + frontParent.interactive = true; + + behindParent.addChild(subParent); + subParent.addChild(behindChild); + stage.addChild(behindParent); + frontParent.addChild(frontChild); + stage.addChild(frontParent); + + pointer.click(40, 10); + + expect(behindCallback).to.not.have.been.called; + expect(frontCallback).to.have.been.calledOnce; + }); }); describe('cursor changes', function () @@ -741,7 +926,7 @@ { const stage = new PIXI.Container(); const graphics = new PIXI.Graphics(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); stage.addChild(graphics); graphics.beginFill(0xFFFFFF); @@ -759,7 +944,7 @@ { const stage = new PIXI.Container(); const graphics = new PIXI.Graphics(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); stage.addChild(graphics); graphics.beginFill(0xFFFFFF); @@ -778,7 +963,7 @@ { const stage = new PIXI.Container(); const graphics = new PIXI.Graphics(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); stage.addChild(graphics); graphics.beginFill(0xFFFFFF); @@ -797,7 +982,7 @@ { const stage = new PIXI.Container(); const graphics = new PIXI.Graphics(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); stage.addChild(graphics); graphics.beginFill(0xFFFFFF); @@ -818,7 +1003,7 @@ const graphics = new PIXI.Graphics(); const overSpy = sinon.spy(); const defaultSpy = sinon.spy(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); stage.addChild(graphics); graphics.beginFill(0xFFFFFF); @@ -840,7 +1025,7 @@ const stage = new PIXI.Container(); const graphics = new PIXI.Graphics(); const defaultSpy = sinon.spy(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); stage.addChild(graphics); graphics.beginFill(0xFFFFFF); @@ -859,7 +1044,7 @@ { const stage = new PIXI.Container(); const graphics = new PIXI.Graphics(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); stage.addChild(graphics); graphics.beginFill(0xFFFFFF); @@ -881,7 +1066,7 @@ { const stage = new PIXI.Container(); const graphics = new PIXI.Graphics(); - const pointer = new MockPointer(stage); + const pointer = this.pointer = new MockPointer(stage); stage.addChild(graphics); graphics.beginFill(0xFFFFFF); @@ -932,7 +1117,7 @@ it('should stop hitTesting after first hit', function () { const scene = getScene(); - const pointer = new MockPointer(scene.stage); + const pointer = this.pointer = new MockPointer(scene.stage); const frontHitTest = sinon.spy(scene.frontChild, 'containsPoint'); const middleHitTest = sinon.spy(scene.middleChild, 'containsPoint'); const behindHitTest = sinon.spy(scene.behindChild, 'containsPoint'); @@ -954,7 +1139,7 @@ it('should stop hitTesting after first hit', function () { const scene = getScene(); - const pointer = new MockPointer(scene.stage); + const pointer = this.pointer = new MockPointer(scene.stage); const frontHitTest = sinon.spy(scene.frontChild, 'containsPoint'); const middleHitTest = sinon.spy(scene.middleChild, 'containsPoint'); const behindHitTest = sinon.spy(scene.behindChild, 'containsPoint'); @@ -971,4 +1156,127 @@ }); }); }); + + describe('pointer handling', function () + { + it('pointer event from mouse should use single mouse data', function () + { + const stage = new PIXI.Container(); + const graphics = new PIXI.Graphics(); + const pointer = this.pointer = new MockPointer(stage, 100, 100, true); + + stage.addChild(graphics); + graphics.beginFill(0xFFFFFF); + graphics.drawRect(0, 0, 50, 50); + graphics.interactive = true; + + pointer.mousemove(20, 10, true); + + expect(pointer.interaction.mouse.global.x).to.equal(20); + expect(pointer.interaction.mouse.global.y).to.equal(10); + }); + }); + + describe('data cleanup', function () + { + it('touchleave after touchout should not orphan data', 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, 42); + expect(pointer.interaction.activeInteractionData[42]).to.exist; + pointer.touchend(10, 10, 42); + expect(pointer.interaction.activeInteractionData[42]).to.be.undefined; + pointer.touchleave(10, 10, 42); + expect(pointer.interaction.activeInteractionData[42]).to.be.undefined; + }); + }); + + describe('hitTest()', function () + { + it('should return hit', 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.render(); + const hit = pointer.interaction.hitTest(new PIXI.Point(10, 10)); + + expect(hit).to.equal(graphics); + }); + + it('should return null if not hit', 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.render(); + const hit = pointer.interaction.hitTest(new PIXI.Point(60, 60)); + + expect(hit).to.be.null; + }); + + it('should return top thing that was hit', function () + { + const stage = new PIXI.Container(); + const graphics = new PIXI.Graphics(); + const behind = new PIXI.Graphics(); + const pointer = this.pointer = new MockPointer(stage); + + stage.addChild(behind); + stage.addChild(graphics); + graphics.beginFill(0xFFFFFF); + graphics.drawRect(0, 0, 50, 50); + graphics.interactive = true; + behind.beginFill(0xFFFFFF); + behind.drawRect(0, 0, 50, 50); + behind.interactive = true; + + pointer.render(); + const hit = pointer.interaction.hitTest(new PIXI.Point(10, 10)); + + expect(hit).to.equal(graphics); + }); + + it('should return hit when passing in root', function () + { + const stage = new PIXI.Container(); + const graphics = new PIXI.Graphics(); + const behind = new PIXI.Graphics(); + const pointer = this.pointer = new MockPointer(stage); + + stage.addChild(behind); + stage.addChild(graphics); + graphics.beginFill(0xFFFFFF); + graphics.drawRect(0, 0, 50, 50); + graphics.interactive = true; + behind.beginFill(0xFFFFFF); + behind.drawRect(0, 0, 50, 50); + behind.interactive = true; + + pointer.render(); + const hit = pointer.interaction.hitTest(new PIXI.Point(10, 10), behind); + + expect(hit).to.equal(behind); + }); + }); }); diff --git a/test/interaction/MockPointer.js b/test/interaction/MockPointer.js index 67878af..c9c6228 100644 --- a/test/interaction/MockPointer.js +++ b/test/interaction/MockPointer.js @@ -11,13 +11,43 @@ * @param {PIXI.Container} stage - The root of the scene tree * @param {number} [width=100] - Width of the renderer * @param {number} [height=100] - Height of the renderer + * @param {boolean} [ensurePointerEvents=false] - If we should make sure that PointerEvents are 'supported' */ - constructor(stage, width, height) + constructor(stage, width, height, ensurePointerEvents) { + // fake PointerEvent existing + if (ensurePointerEvents && !window.PointerEvent) + { + window.PointerEvent = class PointerEvent extends MouseEvent + { + //eslint-disable-next-line + constructor(type, opts) + { + super(type, opts); + this.pointerType = opts.pointerType; + } + }; + this.createdPointerEvent = true; + } + this.stage = stage; this.renderer = new PIXI.CanvasRenderer(width || 100, height || 100); this.renderer.sayHello = () => { /* empty */ }; this.interaction = this.renderer.plugins.interaction; + this.interaction.supportsTouchEvents = true; + PIXI.ticker.shared.remove(this.interaction.update, this.interaction); + } + + /** + * Cleans up after tests + */ + cleanup() + { + if (this.createdPointerEvent) + { + delete window.PointerEvent; + } + this.renderer.destroy(); } /** @@ -45,14 +75,29 @@ /** * @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) + mousemove(x, y, asPointer) { - const mouseEvent = new MouseEvent('mousemove', { - clientX: x, - clientY: y, - preventDefault: sinon.stub(), - }); + 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(), + }); + } this.setPosition(x, y); this.render(); @@ -97,9 +142,15 @@ */ mousedown(x, y) { + const mouseEvent = new MouseEvent('mousedown', { + clientX: x, + clientY: y, + preventDefault: sinon.stub(), + }); + this.setPosition(x, y); this.render(); - this.interaction.onMouseDown({ clientX: 0, clientY: 0, preventDefault: sinon.stub() }); + this.interaction.onPointerDown(mouseEvent); } /** @@ -108,47 +159,83 @@ */ mouseup(x, y) { - this.setPosition(x, y); - this.render(); - this.interaction.onMouseUp({ clientX: 0, clientY: 0, preventDefault: sinon.stub() }); - } - - /** - * @param {number} x - pointer x position - * @param {number} y - pointer y position - */ - tap(x, y) - { - this.touchstart(x, y); - this.touchend(x, y); - } - - /** - * @param {number} x - pointer x position - * @param {number} y - pointer y position - */ - touchstart(x, y) - { - this.setPosition(x, y); - this.render(); - this.interaction.onTouchStart({ + const mouseEvent = new MouseEvent('mouseup', { + clientX: x, + clientY: y, preventDefault: sinon.stub(), - changedTouches: [new Touch({ identifier: 0, target: this.renderer.view })], }); + + 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 */ - touchend(x, y) + tap(x, y, identifier) { + this.touchstart(x, y, identifier); + this.touchend(x, y, identifier); + } + + /** + * @param {number} x - pointer x position + * @param {number} y - pointer y position + * @param {number} [identifier] - pointer id + */ + touchstart(x, y, identifier) + { + const touchEvent = new TouchEvent('touchstart', { + preventDefault: sinon.stub(), + changedTouches: [ + new Touch({ identifier: identifier || 0, target: this.renderer.view }), + ], + }); + this.setPosition(x, y); this.render(); - this.interaction.onTouchEnd({ + this.interaction.onPointerDown(touchEvent); + } + + /** + * @param {number} x - pointer x position + * @param {number} y - pointer y position + * @param {number} [identifier] - pointer id + */ + touchend(x, y, identifier) + { + const touchEvent = new TouchEvent('touchend', { preventDefault: sinon.stub(), - changedTouches: [new Touch({ identifier: 0, target: this.renderer.view })], + changedTouches: [ + new Touch({ identifier: identifier || 0, target: 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 }), + ], + }); + + this.setPosition(x, y); + this.render(); + this.interaction.onPointerOut(touchEvent); } } diff --git a/test/loaders/spritesheetParser.js b/test/loaders/spritesheetParser.js index 79f8c15..4d54805 100644 --- a/test/loaders/spritesheetParser.js +++ b/test/loaders/spritesheetParser.js @@ -60,11 +60,46 @@ .that.is.an.instanceof(PIXI.Texture); }); + it('should build the image url', function () + { + function getResourcePath(url, image) + { + return PIXI.loaders.getResourcePath({ + url, + data: { meta: { image } }, + }); + } + + let result = getResourcePath('http://some.com/spritesheet.json', 'img.png'); + + expect(result).to.be.equals('http://some.com/img.png'); + + result = getResourcePath('http://some.com/some/dir/spritesheet.json', 'img.png'); + expect(result).to.be.equals('http://some.com/some/dir/img.png'); + + result = getResourcePath('http://some.com/some/dir/spritesheet.json', './img.png'); + expect(result).to.be.equals('http://some.com/some/dir/img.png'); + + result = getResourcePath('http://some.com/some/dir/spritesheet.json', '../img.png'); + expect(result).to.be.equals('http://some.com/some/img.png'); + + result = getResourcePath('/spritesheet.json', 'img.png'); + expect(result).to.be.equals('/img.png'); + + result = getResourcePath('/some/dir/spritesheet.json', 'img.png'); + expect(result).to.be.equals('/some/dir/img.png'); + + result = getResourcePath('/some/dir/spritesheet.json', './img.png'); + expect(result).to.be.equals('/some/dir/img.png'); + + result = getResourcePath('/some/dir/spritesheet.json', '../img.png'); + expect(result).to.be.equals('/some/img.png'); + }); + // TODO: Test that rectangles are created correctly. // TODO: Test that bathc processing works correctly. // TODO: Test that resolution processing works correctly. // TODO: Test that metadata is honored. - // TODO: Test data-url code paths. }); function createMockResource(type, data) diff --git a/test/prepare/BasePrepare.js b/test/prepare/BasePrepare.js index 03cf2fa..26dc8ad 100644 --- a/test/prepare/BasePrepare.js +++ b/test/prepare/BasePrepare.js @@ -10,7 +10,7 @@ expect(prep.renderer).to.equal(renderer); expect(prep.uploadHookHelper).to.be.null; expect(prep.queue).to.be.empty; - expect(prep.addHooks).to.have.lengthOf(2); + expect(prep.addHooks).to.have.lengthOf(5); expect(prep.uploadHooks).to.have.lengthOf(2); expect(prep.completes).to.be.empty; @@ -23,10 +23,11 @@ function uploadHook() { /* empty */ } const prep = new PIXI.prepare.BasePrepare(); - prep.register(addHook, uploadHook); + prep.registerFindHook(addHook); + prep.registerUploadHook(uploadHook); expect(prep.addHooks).to.contain(addHook); - expect(prep.addHooks).to.have.lengthOf(3); + expect(prep.addHooks).to.have.lengthOf(6); expect(prep.uploadHooks).to.contain(uploadHook); expect(prep.uploadHooks).to.have.lengthOf(3); @@ -58,7 +59,8 @@ }); const complete = sinon.spy(function () { /* empty */ }); - prep.register(addHook, uploadHook); + prep.registerFindHook(addHook); + prep.registerUploadHook(uploadHook); prep.upload(uploadItem, complete); expect(prep.queue).to.contain(uploadItem); @@ -82,7 +84,7 @@ } const complete = sinon.spy(function () { /* empty */ }); - prep.register(addHook); + prep.registerFindHook(addHook); prep.upload({}, complete); expect(complete.calledOnce).to.be.true; @@ -106,7 +108,8 @@ }); const complete = sinon.spy(function () { /* empty */ }); - prep.register(addHook, uploadHook); + prep.registerFindHook(addHook); + prep.registerUploadHook(uploadHook); prep.upload({}, complete); expect(prep.queue).to.have.lengthOf(1); @@ -121,6 +124,41 @@ prep.destroy(); }); + it('should remove destroyed items from queue', function () + { + const prep = new PIXI.prepare.BasePrepare(); + + const addHook = sinon.spy(function (item, queue) + { + queue.push(item); + + return true; + }); + const uploadHook = sinon.spy(function () + { + return false; + }); + const complete = sinon.spy(function () { /* empty */ }); + + prep.registerFindHook(addHook); + prep.registerUploadHook(uploadHook); + const item = {}; + + prep.upload(item, complete); + + expect(prep.queue).to.have.lengthOf(1); + + item._destroyed = true; + prep.prepareItems(); + + expect(prep.queue).to.be.empty; + expect(addHook.calledOnce).to.be.true; + expect(uploadHook.called).to.be.false; + expect(complete.calledOnce).to.be.true; + + prep.destroy(); + }); + it('should attach to SharedTicker', function (done) { const prep = new PIXI.prepare.BasePrepare(); @@ -147,7 +185,8 @@ done(); } - prep.register(addHook, uploadHook); + prep.registerFindHook(addHook); + prep.registerUploadHook(uploadHook); prep.upload({}, complete); expect(prep.queue).to.have.lengthOf(1);