Skip to content

Commit

Permalink
fix: [#1417] [#1431] Loader positioning, allow customization, clean-u…
Browse files Browse the repository at this point in the history
…p html (#1507)

Closes #1417
Closes #1431

## Changes:

- Adds an id to the play button root element `#excalibur-play-root`
- Allows modification of all loader positioned elements
   - Logo
   - Play button
   - Loading bar
- Additionally allows the loading bar color to be set
  • Loading branch information
eonarheim authored May 23, 2020
1 parent 6b44d34 commit c5d710a
Show file tree
Hide file tree
Showing 10 changed files with 302 additions and 38 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ This project adheres to [Semantic Versioning](http://semver.org/).
- Add color blind mode simulation and correction in debug object.
([#390](https://github.com/excaliburjs/Excalibur/issues/390))
- Add `LimitCameraBoundsStrategy`, which always keeps the camera locked to within the given bounds. ([#1498](https://github.com/excaliburjs/Excalibur/issues/1498))
- Add mechanisms to manipulate the `Loader` screen. ([#1417](https://github.com/excaliburjs/Excalibur/issues/1417))
- Logo position `Loader.logoPosition`
- Play button position `Loader.playButtonPosition`
- Loading bar position `Loader.loadingBarPosition`
- Loading bar color `Loader.loadingBarColor` by default is white, but can be any excalibur `ex.Color`

### Changed

Expand All @@ -24,6 +29,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).

### Fixed

- Fixed Loader play button markup and styles are now cleaned up after clicked ([#1431](https://github.com/excaliburjs/Excalibur/issues/1431))
- Fixed Excalibur crashing when embedded within a cross-origin IFrame ([#1151](https://github.com/excaliburjs/Excalibur/issues/1151))
- Fixed issue when loading images from a base64 strings that would crash the loader ([#1543](https://github.com/excaliburjs/Excalibur/issues/1543))

Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
"sandbox": "serve ./sandbox -l 3001 -n",
"legacy-visual": "npm run sandbox:copy && npm run sandbox:build",
"test": "karma start",
"test:watch": "karma start --auto-watch --single-run=false",
"coveralls": "coveralls < coverage/lcov.info",
"apidocs": "git submodule update && node apidocs.js",
"pretty": "prettier --write \"{src,sandbox/src,sandbox/tests}/**/*.{ts,js,json,css,md}\" --config prettier.config.js",
Expand Down
158 changes: 120 additions & 38 deletions src/engine/Loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import * as DrawUtil from './Util/DrawUtil';

import logoImg from './Loader.logo.png';
import loaderCss from './Loader.css';
import { Vector } from './Algebra';

/**
* Pre-loading assets
Expand Down Expand Up @@ -93,7 +94,31 @@ export class Loader extends Class implements CanLoad {
public logo = logoImg;
public logoWidth = 468;
public logoHeight = 118;
public backgroundColor = '#176BAA';
/**
* Positions the top left corner of the logo image
* If not set, the loader automatically positions the logo
*/
public logoPosition: Vector | null;
/**
* Positions the top left corner of the play button.
* If not set, the loader automatically positions the play button
*/
public playButtonPosition: Vector | null;
/**
* Positions the top left corner of the loading bar
* If not set, the loader automatically positions the loading bar
*/
public loadingBarPosition: Vector | null;

/**
* Gets or sets the color of the loading bar, default is [[Color.White]]
*/
public loadingBarColor: Color = Color.White;

/**
* Gets or sets the background color of the loader as a hex string
*/
public backgroundColor: string = '#176BAA';

protected _imageElement: HTMLImageElement;
protected get _image() {
Expand All @@ -106,6 +131,12 @@ export class Loader extends Class implements CanLoad {
}

public suppressPlayButton: boolean = false;
public get playButtonRootElement(): HTMLElement | null {
return this._playButtonRootElement;
}
public get playButtonElement(): HTMLButtonElement | null {
return this._playButtonElement;
}
protected _playButtonRootElement: HTMLElement;
protected _playButtonElement: HTMLButtonElement;
protected _styleBlock: HTMLStyleElement;
Expand All @@ -114,6 +145,7 @@ export class Loader extends Class implements CanLoad {
protected get _playButton() {
if (!this._playButtonRootElement) {
this._playButtonRootElement = document.createElement('div');
this._playButtonRootElement.id = 'excalibur-play-root';
this._playButtonRootElement.style.position = 'absolute';
document.body.appendChild(this._playButtonRootElement);
}
Expand Down Expand Up @@ -216,60 +248,61 @@ export class Loader extends Class implements CanLoad {
this._playButton.style.display = 'none';
}

/**
* Clean up generated elements for the loader
*/
public dispose() {
if (this._playButtonRootElement.parentElement) {
this._playButtonRootElement.removeChild(this._playButtonElement);
document.body.removeChild(this._playButtonRootElement);
document.head.removeChild(this._styleBlock);
this._playButtonRootElement = null;
this._playButtonElement = null;
this._styleBlock = null;
}
}

/**
* Begin loading all of the supplied resources, returning a promise
* that resolves when loading of all is complete
*/
public load(): Promise<any> {
const complete = new Promise<any>();
const me = this;
if (this._resourceList.length === 0) {
me.showPlayButton().then(() => {
this.showPlayButton().then(() => {
// Unlock audio context in chrome after user gesture
// https://github.com/excaliburjs/Excalibur/issues/262
// https://github.com/excaliburjs/Excalibur/issues/1031
WebAudio.unlock().then(() => {
me.hidePlayButton();
me.oncomplete.call(me);
this.hidePlayButton();
this.oncomplete.call(this);
complete.resolve();
this.dispose();
});
});
return complete;
}

const progressArray = new Array<any>(this._resourceList.length);
const progressChunks = this._resourceList.length;

this._resourceList.forEach((r, i) => {
this._resourceList.forEach((resource) => {
if (this._engine) {
r.wireEngine(this._engine);
resource.wireEngine(this._engine);
}
r.onprogress = function(e) {
const total = <number>e.total;
const loaded = <number>e.loaded;
progressArray[i] = { loaded: (loaded / total) * (100 / progressChunks), total: 100 };

const progressResult: any = progressArray.reduce(
function(accum, next) {
return { loaded: accum.loaded + next.loaded, total: 100 };
},
{ loaded: 0, total: 100 }
);

me.onprogress.call(me, progressResult);
resource.onprogress = (e: ProgressEvent) => {
this.updateResourceProgress(e.loaded, e.total);
};
r.oncomplete = r.onerror = function() {
me._numLoaded++;
if (me._numLoaded === me._resourceCount) {
resource.oncomplete = resource.onerror = () => {
this.markResourceComplete();
if (this.isLoaded()) {
setTimeout(() => {
me.showPlayButton().then(() => {
this.showPlayButton().then(() => {
// Unlock audio context in chrome after user gesture
// https://github.com/excaliburjs/Excalibur/issues/262
// https://github.com/excaliburjs/Excalibur/issues/1031
WebAudio.unlock().then(() => {
me.hidePlayButton();
me.oncomplete.call(me);
this.hidePlayButton();
this.oncomplete.call(this);
complete.resolve();
this.dispose();
});
});
}, 200); // short delay in showing the button for aesthetics
Expand All @@ -281,7 +314,7 @@ export class Loader extends Class implements CanLoad {
if (!list[index]) {
return;
}
list[index].load().then(function() {
list[index].load().then(function () {
loadNext(list, index + 1);
});
}
Expand All @@ -290,6 +323,25 @@ export class Loader extends Class implements CanLoad {
return complete;
}

public updateResourceProgress(loadedBytes: number, totalBytes: number) {
const chunkSize = 100 / this._resourceCount;
const resourceProgress = loadedBytes / totalBytes;
// This only works if we load 1 resource at a time
const totalProgress = resourceProgress * chunkSize + this.progress * 100;
this.onprogress({ loaded: totalProgress, total: 100 });
}

public markResourceComplete() {
this._numLoaded++;
}

/**
* Returns the progess of the loader as a number between [0, 1] inclusive.
*/
public get progress(): number {
return this._resourceCount > 0 ? this._numLoaded / this._resourceCount : 1;
}

/**
* Loader draw function. Draws the default Excalibur loading screen.
* Override `logo`, `logoWidth`, `logoHeight` and `backgroundColor` properties
Expand All @@ -304,35 +356,65 @@ export class Loader extends Class implements CanLoad {
const top = ctx.canvas.offsetTop;
const buttonWidth = this._playButton.clientWidth;
const buttonHeight = this._playButton.clientHeight;
this._playButtonRootElement.style.left = `${left + canvasWidth / 2 - buttonWidth / 2}px`;
this._playButtonRootElement.style.top = `${top + canvasHeight / 2 - buttonHeight / 2 + 100}px`;
if (this.playButtonPosition) {
this._playButtonRootElement.style.left = `${this.playButtonPosition.x}px`;
this._playButtonRootElement.style.top = `${this.playButtonPosition.y}px`;
} else {
this._playButtonRootElement.style.left = `${left + canvasWidth / 2 - buttonWidth / 2}px`;
this._playButtonRootElement.style.top = `${top + canvasHeight / 2 - buttonHeight / 2 + 100}px`;
}
}

ctx.fillStyle = this.backgroundColor;
ctx.fillRect(0, 0, canvasWidth, canvasHeight);

const y = canvasHeight / 2;
let logoY = canvasHeight / 2;
const width = Math.min(this.logoWidth, canvasWidth * 0.75);
const x = canvasWidth / 2 - width / 2;
let logoX = canvasWidth / 2 - width / 2;

if (this.logoPosition) {
logoX = this.logoPosition.x;
logoY = this.logoPosition.y;
}

const imageHeight = Math.floor(width * (this.logoHeight / this.logoWidth)); // OG height/width factor
const oldAntialias = this._engine.getAntialiasing();
this._engine.setAntialiasing(true);
ctx.drawImage(this._image, 0, 0, this.logoWidth, this.logoHeight, x, y - imageHeight - 20, width, imageHeight);
if (!this.logoPosition) {
ctx.drawImage(this._image, 0, 0, this.logoWidth, this.logoHeight, logoX, logoY - imageHeight - 20, width, imageHeight);
} else {
ctx.drawImage(this._image, 0, 0, this.logoWidth, this.logoHeight, logoX, logoY, width, imageHeight);
}

// loading box
if (!this.suppressPlayButton && this._playButtonShown) {
this._engine.setAntialiasing(oldAntialias);
return;
}

let loadingX = logoX;
let loadingY = logoY;
if (this.loadingBarPosition) {
loadingX = this.loadingBarPosition.x;
loadingY = this.loadingBarPosition.y;
}

ctx.lineWidth = 2;
DrawUtil.roundRect(ctx, x, y, width, 20, 10);
const progress = width * (this._numLoaded / this._resourceCount);
DrawUtil.roundRect(ctx, loadingX, loadingY, width, 20, 10, this.loadingBarColor);
const progress = width * this.progress;
const margin = 5;
const progressWidth = progress - margin * 2;
const height = 20 - margin * 2;
DrawUtil.roundRect(ctx, x + margin, y + margin, progressWidth > 0 ? progressWidth : 0, height, 5, null, Color.White);
DrawUtil.roundRect(
ctx,
loadingX + margin,
loadingY + margin,
progressWidth > 10 ? progressWidth : 10,
height,
5,
null,
this.loadingBarColor
);
this._engine.setAntialiasing(oldAntialias);
}

Expand Down
Loading

0 comments on commit c5d710a

Please sign in to comment.