Skip to content

Commit

Permalink
fix/feat: Screen resizing artifacts + expose screen events! (#2826)
Browse files Browse the repository at this point in the history
- Fixed issue where play button was hidden when going fullscreen mode
- Fixed issue where screen resizing caused artifacts on the loading screen
- Added new `ex.Screen.events`
  - `screen.events.on('resize', (evt) => )` Will emit when the screen is resized
  - `screen.events.on('fullscreen', (evt) => )` Will emit when the screen is changed into browser fullscreen mode
  - `screen.events.on('pixelratio', (evt) => )` Will emit when the screen's pixel ratio changes (moving from a hidpi screen to a non, or vice versa)
  • Loading branch information
eonarheim authored Dec 1, 2023
1 parent 22a3971 commit e4edab3
Show file tree
Hide file tree
Showing 6 changed files with 135 additions and 9 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,15 @@ This project adheres to [Semantic Versioning](http://semver.org/).
### Added

- Added `ex.Engine.version` to report the current excalibur version build string
- Added new `ex.Screen.events`
- `screen.events.on('resize', (evt) => )` Will emit when the screen is resized
- `screen.events.on('fullscreen', (evt) => )` Will emit when the screen is changed into browser fullscreen mode
- `screen.events.on('pixelratio', (evt) => )` Will emit when the screen's pixel ratio changes (moving from a hidpi screen to a non, or vice versa)

### Fixed

- Fixed issue where play button was hidden when going fullscreen mode
- Fix issue where screen resizing caused artifacts on the loading screen
- Fix bug in `useCanvas2DFallback()` where `antialiasing` settings could be lost
- Fix bug in `useCanvas2DFallback()` where opacity was not respected in `save
- Fixed typo in trigger event signature `entertrigger` should have been `enter`
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
"core:bundle:esm": "webpack --progress --config webpack.config.js --mode production --env output=esm",
"core:watch": "npm run core:bundle -- --watch",
"lint": "npm run eslint && npm run eslint:spec",
"lint:fix": "npm run eslint --fix && npm run eslint:spec --fix",
"lint:fix": "npm run eslint -- --fix && npm run eslint:spec -- --fix",
"eslint": "eslint \"src/engine/**/*.ts\"",
"eslint:sandbox": "eslint \"sandbox/**/*.ts\"",
"eslint:spec": "eslint \"src/spec/**/*.ts\"",
Expand Down
13 changes: 11 additions & 2 deletions sandbox/src/game.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ var game = new ex.Engine({
height: 600 / 2,
viewport: { width: 800, height: 600 },
canvasElementId: 'game',
pixelRatio: 1,
suppressPlayButton: true,
// pixelRatio: 1,
// suppressPlayButton: true,
pointerScope: ex.PointerScope.Canvas,
displayMode: ex.DisplayMode.FitScreenAndFill,
snapToPixel: false,
Expand All @@ -58,6 +58,15 @@ var game = new ex.Engine({
}
});
game.setAntialiasing(false);
game.screen.events.on('fullscreen', (evt) => {
console.log('fullscreen', evt);
});
game.screen.events.on('resize', (evt) => {
console.log('resize', evt);
});
game.screen.events.on('pixelratio', (evt) => {
console.log('pixelratio', evt);
});
game.currentScene.onPreDraw = (ctx: ex.ExcaliburGraphicsContext) => {
ctx.save();
ctx.z = 99;
Expand Down
37 changes: 33 additions & 4 deletions src/engine/Loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { clamp } from './Math/util';
import { Sound } from './Resources/Sound/Sound';
import { Future } from './Util/Future';
import { EventEmitter, EventKey, Handler, Subscription } from './EventEmitter';
import { ScreenResizeEvent } from './Screen';

export type LoaderEvents = {
// Add event types here
Expand Down Expand Up @@ -148,6 +149,13 @@ export class Loader implements Loadable<Loadable<any>[]> {
return this._imageElement;
}

private _getScreenParent() {
if (this._engine) {
return this._engine.screen.canvas.parentElement;
}
return document.body;
}

public suppressPlayButton: boolean = false;
public get playButtonRootElement(): HTMLElement | null {
return this._playButtonRootElement;
Expand All @@ -169,7 +177,10 @@ export class Loader implements Loadable<Loadable<any>[]> {
this._playButtonRootElement = document.createElement('div');
this._playButtonRootElement.id = 'excalibur-play-root';
this._playButtonRootElement.style.position = 'absolute';
document.body.appendChild(this._playButtonRootElement);

// attach play button to canvas parent, this is important for fullscreen
const parent = this._getScreenParent();
parent.appendChild(this._playButtonRootElement);
}
if (!this._styleBlock) {
this._styleBlock = document.createElement('style');
Expand Down Expand Up @@ -212,10 +223,23 @@ export class Loader implements Loadable<Loadable<any>[]> {
}
}

private _loaderResizeHandler = (evt: ScreenResizeEvent) => {
// Configure resolution for loader, it expects resolution === viewport
this._engine.screen.resolution = this._engine.screen.viewport;
this._engine.screen.applyResolutionAndViewport();

this.canvas.width = evt.viewport.width;
this.canvas.height = evt.viewport.height;
this.canvas.flagDirty();
};

public wireEngine(engine: Engine) {
this._engine = engine;
this.canvas.width = this._engine.canvas.width;
this.canvas.height = this._engine.canvas.height;
// wire once
if (!this._engine) {
this._engine = engine;
this.canvas.width = this._engine.canvas.width;
this.canvas.height = this._engine.canvas.height;
}
}

/**
Expand Down Expand Up @@ -328,6 +352,8 @@ export class Loader implements Loadable<Loadable<any>[]> {
* that resolves when loading of all is complete AND the user has clicked the "Play button"
*/
public async load(): Promise<Loadable<any>[]> {
this._engine.screen.events.on('resize', this._loaderResizeHandler);

await this._image?.decode(); // decode logo if it exists
this.canvas.flagDirty();

Expand Down Expand Up @@ -359,6 +385,9 @@ export class Loader implements Loadable<Loadable<any>[]> {
// See: https://github.com/excaliburjs/Excalibur/issues/1031
await WebAudio.unlock();

// unload loader resize watcher
this._engine.screen.events.off('resize', this._loaderResizeHandler);

return (this.data = this._resourceList);
}

Expand Down
81 changes: 80 additions & 1 deletion src/engine/Screen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { ExcaliburGraphicsContext } from './Graphics/Context/ExcaliburGraphicsCo
import { getPosition } from './Util/Util';
import { ExcaliburGraphicsContextWebGL } from './Graphics/Context/ExcaliburGraphicsContextWebGL';
import { ExcaliburGraphicsContext2DCanvas } from './Graphics/Context/ExcaliburGraphicsContext2DCanvas';
import { EventEmitter } from './EventEmitter';

/**
* Enum representing the different display modes available to Excalibur.
Expand Down Expand Up @@ -183,11 +184,73 @@ export interface ScreenOptions {
displayMode?: DisplayMode;
}

/**
* Fires when the screen resizes, useful if you have logic that needs to be aware of resolution/viewport constraints
*/
export interface ScreenResizeEvent {
/**
* Current viewport in css pixels of the screen
*/
viewport: ScreenDimension;
/**
* Current resolution in world pixels of the screen
*/
resolution: ScreenDimension;
}

/**
* Fires when the pixel ratio changes, useful to know if you've moved to a hidpi screen or back
*/
export interface PixelRatioChangeEvent {
/**
* Current pixel ratio of the screen
*/
pixelRatio: number;
}

/**
* Fires when the browser fullscreen api is successfully engaged or disengaged
*/
export interface FullScreenChangeEvent {
/**
* Current fullscreen state
*/
fullscreen: boolean;
}

/**
* Built in events supported by all entities
*/
export type ScreenEvents = {
/**
* Fires when the screen resizes, useful if you have logic that needs to be aware of resolution/viewport constraints
*/
'resize': ScreenResizeEvent;
/**
* Fires when the pixel ratio changes, useful to know if you've moved to a hidpi screen or back
*/
'pixelratio': PixelRatioChangeEvent;
/**
* Fires when the browser fullscreen api is successfully engaged or disengaged
*/
'fullscreen': FullScreenChangeEvent;
};

export const ScreenEvents = {
ScreenResize: 'resize',
PixelRatioChange: 'pixelratio',
FullScreenChange: 'fullscreen'
} as const;

/**
* The Screen handles all aspects of interacting with the screen for Excalibur.
*/
export class Screen {
public graphicsContext: ExcaliburGraphicsContext;
/**
* Listen to screen events [[ScreenEvents]]
*/
public events = new EventEmitter<ScreenEvents>();
private _canvas: HTMLCanvasElement;
private _antialiasing: boolean = true;
private _contentResolution: ScreenDimension;
Expand Down Expand Up @@ -261,20 +324,31 @@ export class Screen {
private _fullscreenChangeHandler = () => {
this._isFullScreen = !this._isFullScreen;
this._logger.debug('Fullscreen Change', this._isFullScreen);
this.events.emit('fullscreen', {
fullscreen: this.isFullScreen
} satisfies FullScreenChangeEvent);
};

private _pixelRatioChangeHandler = () => {
this._logger.debug('Pixel Ratio Change', window.devicePixelRatio);
this._listenForPixelRatio();
this._devicePixelRatio = this._calculateDevicePixelRatio();
this.applyResolutionAndViewport();
this.events.emit('pixelratio', {
pixelRatio: this.pixelRatio
} satisfies PixelRatioChangeEvent);
};

private _resizeHandler = () => {
const parent = this.parent;
this._logger.debug('View port resized');
this._setResolutionAndViewportByDisplayMode(parent);
this.applyResolutionAndViewport();
// Emit resize event
this.events.emit('resize', {
resolution: this.resolution,
viewport: this.viewport
} satisfies ScreenResizeEvent);
};

private _calculateDevicePixelRatio() {
Expand Down Expand Up @@ -447,7 +521,12 @@ export class Screen {
if (elementId) {
const maybeElement = document.getElementById(elementId);
if (maybeElement) {
return maybeElement.requestFullscreen();
if (!maybeElement.getAttribute('ex-fullscreen-listener')) {
maybeElement.setAttribute('ex-fullscreen-listener', 'true');
maybeElement.addEventListener('fullscreenchange', this._fullscreenChangeHandler);
}
const fullscreenPromise = maybeElement.requestFullscreen();
return fullscreenPromise;
}
}
return this._canvas.requestFullscreen();
Expand Down
5 changes: 4 additions & 1 deletion src/spec/ScreenSpec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -448,12 +448,15 @@ describe('A Screen', () => {
viewport: { width: 800, height: 600 }
});

const fakeElement = jasmine.createSpyObj('element', ['requestFullscreen']);
const fakeElement = jasmine.createSpyObj('element', ['requestFullscreen', 'getAttribute', 'setAttribute', 'addEventListener']);
spyOn(document, 'getElementById').and.returnValue(fakeElement);

sut.goFullScreen('some-id');

expect(document.getElementById).toHaveBeenCalledWith('some-id');
expect(fakeElement.getAttribute).toHaveBeenCalledWith('ex-fullscreen-listener');
expect(fakeElement.setAttribute).toHaveBeenCalledWith('ex-fullscreen-listener', 'true');
expect(fakeElement.addEventListener).toHaveBeenCalled();
expect(fakeElement.requestFullscreen).toHaveBeenCalled();
});

Expand Down

0 comments on commit e4edab3

Please sign in to comment.