From 00db29cf4783c511f12941732944b15c7e3376c8 Mon Sep 17 00:00:00 2001 From: Erik Onarheim Date: Fri, 16 Feb 2024 23:04:19 -0600 Subject: [PATCH] fix: ScreenAppender Logger (#2935) Closes: #2886 Fixes an issue spotted by @jyoung4242 where `ScreenAppenders` did not work properly. They are now also more configurable! https://github.com/excaliburjs/Excalibur/assets/612071/9f971648-db2f-4958-bcb8-606c00c234ce ```typescript const logger = Logger.getInstance(); const screenAppender = new ScreenAppender({ engine: game, color: Color.Black, xPos: 0 }); logger.addAppender(screenAppender); setInterval(() => { logger.info('test', performance.now()); }, 100); ``` --- CHANGELOG.md | 26 ++++++ src/engine/Util/Log.ts | 79 +++++++++++++----- src/spec/ScreenAppenderSpec.ts | 46 ++++++++++ .../ScreenAppenderSpec/screen-log-linux.png | Bin 0 -> 1571 bytes .../images/ScreenAppenderSpec/screen-log.png | Bin 0 -> 1176 bytes src/spec/util/TestUtils.ts | 22 +++++ 6 files changed, 154 insertions(+), 19 deletions(-) create mode 100644 src/spec/ScreenAppenderSpec.ts create mode 100644 src/spec/images/ScreenAppenderSpec/screen-log-linux.png create mode 100644 src/spec/images/ScreenAppenderSpec/screen-log.png 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 0000000000000000000000000000000000000000..01d73da1b81202c988a59d075a56b8e65379794e GIT binary patch literal 1571 zcmdUv`7_%I7{)`SjU%(DL98Rw= z1aou6dPz0xTR{&=J8}Mzk5ptxUM>zG*0A;p2m}$hVKF}O!6HWBGhbh|d)=779z8J> z>NA&R^)tgxReX7)kX1k}KxA3mmo+=Od=1au9$_an>Hf||beQdU{g8&w2L#LI5Qvcj z)1MGlq;eg#8`C*F6Sdx=Q%*`t?MXm|vr-$x2PB%!f!z{rV8n){*%T9Zssld6xK8(^cMqWDm<|Vn+!-Vo@)y01bo>fpxG&BX8KjAaI$ zO}P&Eba>;oau|}B2cH!udXjfQ)uo%Ejcxm1llhRC@RLNNCi&Ca@w2qf=@LbIP(5Tu zcFi)MXuP3k-EH&`9R!J40^zga2W_sz@bg&khWUmqQHO}$Fd=FYt%+rD_bG!u2C7Oh z%Sp7geC9OfnbvL#i&w~esN=vpzK{{{C7#tj5zKKUU=L5v-O%V`cmpOB_S5^TpLlx$ z1_>|$LMRU?2E1DCTKoxTSCz1{MEi|PGW3Z40CKRLdP(zZHu4x0y6HgKrI-Pas}_>LI6}Y4n!OfN+Yi*YZgt{%4*C)}W3?U;UK~Sm)3K6yP~2Q=%-!M) z5-hAlh0cxE3nE=AT`p-H=<=Xq6|y~)?G{~ZjmPB@EOk$=RaA0eFe*0f4{)Jc=)Ed? z^4hU57ZC}U3=%%mx*og5VC3?W2fzbOh~D( zU6qk*)F?tvLvD^GMvM;hWUr zW|o>bbM+bOaX-FNO}ynU3DE0Z^K!ffdbg>0h2LqXy}f(UF3fXCu+pVGNjqlrPA{$Y zYkKOZqg)kE8Rxf9C0o5V&E5v}XEpU>g>vm{d$Sd$1M9OTA~m9t|W34KfE(s)=?rER;C+ES4mU6^_du;xVHk2h7`RXwY76oDjl$! zAEaKb61K(JLg{t0>6T647j((;XTervtkmJ(^mfM~Ygoy`1{nH%%}i5h&0`Vty)@f` zt#lkK$%z4@#5U?rQW8*c|3N$v&tXeaDqopSI%j?hx?`0q%#K|c8v1D_m!4=`ChuqP zi%@=24T}Uz)}dKpgmZ3rO6o^!3|)~Wa8FgkyKk^c@&&GhLl5QN;%!tMsfGn5GDeu= hei!=k5dW8T{((U1DGx^{wx%0C9D<|66N9~vyJ{cb8sTqZ5{4o)Ir87rn} zb>)^wnJkj5e0T2d+}iJTdv})K|9xZ5_T=a1=2V}X^ZcH%@q6hD$3>YBib^#)Hd)MO zQFN1IPH%91pj@EC!WZ_iXQK0g-~{#pb(VF{-X=X^D-ir4#>9T>ovGV9#{CWJ52z+6 zvfP_`T{Wjzv0%r__3PeRJ+KUIUHo>J;{Q0VpgSc>TRbO(9muWq;rJn05vqP-g88kb z>m@Sx?$`VKB{Nu)qkyqu%A5_NKX`2#m_Fn^ur3g{nEx+VVqVLEn_7;S{?BDOHyi$xtu(B-ZT8W;Nf+-LPyx@CVTv2s$sI5GL3B=gY{aIr87&pcksN))vD&@Gi;P+ zdVe{kg71x2)0U}0+Pl?aqIbVuImd4GOqHby`i-00j-M*(*>kpVc~(-goaw?(M!P3A z-C@sbZ)h}k@D^>-Q9bzPW>TM#(A(hXZ@Z?fk5Kay3=I=3i3U3*e7mPPWCqBj3mEk`fKV)I)mo+{#eGc}UkA|Bpa z6Kl9}(H=NXZ^mpUe^R3pWW2|DRPpn^RwN1?)f(t`toH~-_L(taZBdXwfWokFE5JR zxAgDYQ%SXk+AF`O7Cd-8^@_36UADt*ODA6ow^jXGH!U#e{I!!s?HVif^^a<-e6GDT zb*E=`WNg#INxzP8mF{o4x@X!|R{_;h_v}coR~t62-dp>2*^+zn9aX<-T7(46Z(4Kq zQix})XYJI+ocoJEO*p(Jeedn1!f#h@Ua(}{k~oo;ebGC-UU|Jb8o1=--lDfFm$T

BL(p$b{&6VW}nVU@mgTjF@zi8{XD_fVxy%u=8 zQhTL-{d?!-_a}S43ac*tRr}Ri;;7@xzU6*fmant@S{U3u>DP`w>!o8o@6J@%+^`sE zISB6iHEZqrmF8#LI%O6?1dbVay?XuHeSWYy$XS~`n7$@!tn5F%f6^}Ff9{Piee0Io zdwacM%9ZxBir<9mt^TgN{NCzs-1phtAWe&1nx{HWy>k3%yysoX|BDY?^#9ZARQ-2> mkV}(A@&QR1%fUg 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; + } }