diff --git a/CHANGELOG.md b/CHANGELOG.md index b22e446ba..cf0ad6716 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -281,6 +281,32 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Fixed +- Fixed issue with Log ScreenAppender utility where it was not positioned correctly, you can now deeply configure it! + ```typescript + export interface ScreenAppenderOptions { + engine: Engine; + /** + * Optionally set the width of the overlay canvas + */ + width?: number; + /** + * Optionally set the height of the overlay canvas + */ + height?: number; + /** + * Adjust the text offset from the left side of the screen + */ + xPos?: number; + /** + * Provide a text color + */ + color?: Color; + /** + * Optionally set the CSS zindex of the overlay canvas + */ + zIndex?: number; + } + ``` - Fixed errant warning about resolution when using `pixelRatio` on low res games to upscale - Fixes an issue where a collider that was part of a contact that was deleted did not fire a collision end event, this was unexpected - Fixes an issue where you may want to have composite colliders behave as constituent colliders for the purposes of start/end collision events. A new property is added to physics config, the current behavior is the default which is `'together'`, this means the whole composite collider is treated as 1 collider for onCollisionStart/onCollisionEnd. Now you can configure a `separate` which will fire onCollisionStart/onCollisionEnd for every separate collider included in the composite (useful if you are building levels or things with gaps that you need to disambiguate). You can also configure this on a per composite level to mix and match `CompositeCollider.compositeStrategy` diff --git a/src/engine/Util/Log.ts b/src/engine/Util/Log.ts index 53db937b7..4febbb0cd 100644 --- a/src/engine/Util/Log.ts +++ b/src/engine/Util/Log.ts @@ -1,4 +1,9 @@ /* eslint-disable no-console */ + +import { Engine } from '../Engine'; +import { vec } from '../Math/vector'; +import { Color } from '../Color'; + /** * Logging level that Excalibur will tag */ @@ -230,28 +235,64 @@ export class ConsoleAppender implements Appender { } } +export interface ScreenAppenderOptions { + engine: Engine; + /** + * Optionally set the width of the overlay canvas + */ + width?: number; + /** + * Optionally set the height of the overlay canvas + */ + height?: number; + /** + * Adjust the text offset from the left side of the screen + */ + xPos?: number; + /** + * Provide a text color + */ + color?: Color; + /** + * Optionally set the CSS zindex of the overlay canvas + */ + zIndex?: number; +} + /** * On-screen (canvas) appender */ export class ScreenAppender implements Appender { - // @todo Clean this up private _messages: string[] = []; - private _canvas: HTMLCanvasElement; + public canvas: HTMLCanvasElement; private _ctx: CanvasRenderingContext2D; + private _pos = 10; + private _color = Color.Black; + private _options: ScreenAppenderOptions; + constructor(options: ScreenAppenderOptions) { + this._options = options; + this.canvas = document.createElement('canvas'); + this._ctx = this.canvas.getContext('2d')!; + this.canvas.style.position = 'absolute'; + this.canvas.style.zIndex = options.zIndex?.toString() ?? '99'; + document.body.appendChild(this.canvas); + this._positionScreenAppenderCanvas(); + options.engine.screen.events.on('resize', () => { + this._positionScreenAppenderCanvas(); + }); + } - /** - * @param width Width of the screen appender in pixels - * @param height Height of the screen appender in pixels - */ - constructor(width?: number, height?: number) { - this._canvas = document.createElement('canvas'); - this._canvas.width = width || window.innerWidth; - this._canvas.height = height || window.innerHeight; - this._canvas.style.position = 'absolute'; - // eslint-disable-next-line - this._ctx = this._canvas.getContext('2d'); // eslint-disable-line - document.body.appendChild(this._canvas); + private _positionScreenAppenderCanvas() { + const options = this._options; + this.canvas.width = options.width ?? options.engine.screen.resolution.width; + this.canvas.height = options.height ?? options.engine.screen.resolution.height; + this.canvas.style.position = 'absolute'; + const pagePos = options.engine.screen.screenToPageCoordinates(vec(0, 0)); + this.canvas.style.left = pagePos.x + 'px'; + this.canvas.style.top = pagePos.y + 'px'; + this._pos = options.xPos ?? this._pos; + this._color = options.color ?? this._color; } /** @@ -262,17 +303,17 @@ export class ScreenAppender implements Appender { public log(level: LogLevel, args: any[]): void { const message = args.join(','); - this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height); + this._ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); this._messages.unshift('[' + LogLevel[level] + '] : ' + message); let pos = 10; - let opacity = 1.0; + + this._messages = this._messages.slice(0, 1000); for (let i = 0; i < this._messages.length; i++) { - this._ctx.fillStyle = 'rgba(255,255,255,' + opacity.toFixed(2) + ')'; - this._ctx.fillText(this._messages[i], 200, pos); + this._ctx.fillStyle = this._color.toRGBA(); + this._ctx.fillText(this._messages[i], this._pos, pos); pos += 10; - opacity = opacity > 0 ? opacity - 0.05 : 0; } } } diff --git a/src/spec/ScreenAppenderSpec.ts b/src/spec/ScreenAppenderSpec.ts new file mode 100644 index 000000000..45b57a954 --- /dev/null +++ b/src/spec/ScreenAppenderSpec.ts @@ -0,0 +1,46 @@ +import * as ex from '@excalibur'; +import { TestUtils } from './util/TestUtils'; +import { ExcaliburAsyncMatchers } from 'excalibur-jasmine'; + +describe('A ScreenAppender', () => { + beforeAll(() => { + jasmine.addAsyncMatchers(ExcaliburAsyncMatchers); + }); + + it('exists', () => { + expect(ex.ScreenAppender).toBeDefined(); + }); + + it('can be constructed', () => { + const engine = TestUtils.engine({width: 100, height: 100}); + const sut = new ex.ScreenAppender({engine}); + expect(sut).toBeDefined(); + engine.dispose(); + }); + + it('can be configured to log', async () => { + const engine = TestUtils.engine({width: 100, height: 100}); + const sut = new ex.ScreenAppender({ + engine, + color: ex.Color.Yellow, + xPos: 0 + }); + + sut.log(ex.LogLevel.Info, ['this is a log']); + sut.log(ex.LogLevel.Info, ['this is a log']); + sut.log(ex.LogLevel.Info, ['this is a log']); + sut.log(ex.LogLevel.Info, ['this is a log']); + sut.log(ex.LogLevel.Info, ['this is a log']); + sut.log(ex.LogLevel.Info, ['this is a log']); + + await TestUtils.runOnWindows(async () => { + await expectAsync(sut.canvas).toEqualImage('src/spec/images/ScreenAppenderSpec/screen-log.png'); + }); + + // FIXME CI chokes on this one for some reason + // await TestUtils.runOnLinux(async () => { + // await expectAsync(sut.canvas).toEqualImage('src/spec/images/ScreenAppenderSpec/screen-log-linux.png'); + // }); + engine.dispose(); + }); +}); \ No newline at end of file diff --git a/src/spec/images/ScreenAppenderSpec/screen-log-linux.png b/src/spec/images/ScreenAppenderSpec/screen-log-linux.png new file mode 100644 index 000000000..01d73da1b Binary files /dev/null and b/src/spec/images/ScreenAppenderSpec/screen-log-linux.png differ diff --git a/src/spec/images/ScreenAppenderSpec/screen-log.png b/src/spec/images/ScreenAppenderSpec/screen-log.png new file mode 100644 index 000000000..1e4c997c0 Binary files /dev/null and b/src/spec/images/ScreenAppenderSpec/screen-log.png differ diff --git a/src/spec/util/TestUtils.ts b/src/spec/util/TestUtils.ts index 968895bbd..ad54fd2ef 100644 --- a/src/spec/util/TestUtils.ts +++ b/src/spec/util/TestUtils.ts @@ -82,4 +82,26 @@ export namespace TestUtils { ctx.drawImage(source, 0, 0); return canvas; } + + /** + * + */ + export async function runOnWindows(ctx: () => Promise): Promise { + if (navigator.platform === 'Win32' || navigator.platform === 'Win64') { + await ctx(); + return true; + } + return false; + } + + /** + * + */ + export async function runOnLinux(ctx: () => Promise): Promise { + if (navigator.platform.includes('Linux')) { + await ctx(); + return true; + } + return false; + } }