• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

excaliburjs / Excalibur / 14804036802

02 May 2025 09:58PM UTC coverage: 5.927% (-83.4%) from 89.28%
14804036802

Pull #3404

github

web-flow
Merge 5c103d7f8 into 0f2ccaeb2
Pull Request #3404: feat: added Graph module to Math

234 of 8383 branches covered (2.79%)

229 of 246 new or added lines in 1 file covered. (93.09%)

13145 existing lines in 208 files now uncovered.

934 of 15759 relevant lines covered (5.93%)

4.72 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

2.89
/src/engine/Screen.ts
1
import { vec, Vector } from './Math/vector';
2
import { Logger } from './Util/Log';
3
import { Camera } from './Camera';
4
import { BrowserEvents } from './Util/Browser';
5
import { BoundingBox } from './Collision/Index';
6
import { ExcaliburGraphicsContext } from './Graphics/Context/ExcaliburGraphicsContext';
7
import { getPosition } from './Util/Util';
8
import { ExcaliburGraphicsContextWebGL } from './Graphics/Context/ExcaliburGraphicsContextWebGL';
9
import { ExcaliburGraphicsContext2DCanvas } from './Graphics/Context/ExcaliburGraphicsContext2DCanvas';
10
import { EventEmitter } from './EventEmitter';
11

12
/**
13
 * Enum representing the different display modes available to Excalibur.
14
 */
15
export enum DisplayMode {
1✔
16
  /**
17
   * Default, use a specified resolution for the game. Like 800x600 pixels for example.
18
   */
19
  Fixed = 'Fixed',
1✔
20

21
  /**
22
   * Fit the aspect ratio given by the game resolution within the container at all times will fill any gaps with canvas.
23
   * The displayed area outside the aspect ratio is not guaranteed to be on the screen, only the {@apilink Screen.contentArea}
24
   * is guaranteed to be on screen.
25
   */
26
  FitContainerAndFill = 'FitContainerAndFill',
1✔
27

28
  /**
29
   * Fit the aspect ratio given by the game resolution the screen at all times will fill the screen.
30
   * This displayed area outside the aspect ratio is not guaranteed to be on the screen, only the {@apilink Screen.contentArea}
31
   * is guaranteed to be on screen.
32
   */
33
  FitScreenAndFill = 'FitScreenAndFill',
1✔
34

35
  /**
36
   * Fit the viewport to the parent element maintaining aspect ratio given by the game resolution, but zooms in to avoid the black bars
37
   * (letterbox) that would otherwise be present in {@apilink FitContainer}.
38
   *
39
   * **warning** This will clip some drawable area from the user because of the zoom,
40
   * use {@apilink Screen.contentArea} to know the safe to draw area.
41
   */
42
  FitContainerAndZoom = 'FitContainerAndZoom',
1✔
43

44
  /**
45
   * Fit the viewport to the device screen maintaining aspect ratio given by the game resolution, but zooms in to avoid the black bars
46
   * (letterbox) that would otherwise be present in {@apilink FitScreen}.
47
   *
48
   * **warning** This will clip some drawable area from the user because of the zoom,
49
   * use {@apilink Screen.contentArea} to know the safe to draw area.
50
   */
51
  FitScreenAndZoom = 'FitScreenAndZoom',
1✔
52

53
  /**
54
   * Fit to screen using as much space as possible while maintaining aspect ratio and resolution.
55
   * This is not the same as {@apilink Screen.enterFullscreen} but behaves in a similar way maintaining aspect ratio.
56
   *
57
   * You may want to center your game here is an example
58
   * ```html
59
   * <!-- html -->
60
   * <body>
61
   * <main>
62
   *   <canvas id="game"></canvas>
63
   * </main>
64
   * </body>
65
   * ```
66
   *
67
   * ```css
68
   * // css
69
   * main {
70
   *   display: flex;
71
   *   align-items: center;
72
   *   justify-content: center;
73
   *   height: 100%;
74
   *   width: 100%;
75
   * }
76
   * ```
77
   */
78
  FitScreen = 'FitScreen',
1✔
79

80
  /**
81
   * Fill the entire screen's css width/height for the game resolution dynamically. This means the resolution of the game will
82
   * change dynamically as the window is resized. This is not the same as {@apilink Screen.enterFullscreen}
83
   */
84
  FillScreen = 'FillScreen',
1✔
85

86
  /**
87
   * Fit to parent element width/height using as much space as possible while maintaining aspect ratio and resolution.
88
   */
89
  FitContainer = 'FitContainer',
1✔
90

91
  /**
92
   * Use the parent DOM container's css width/height for the game resolution dynamically
93
   */
94
  FillContainer = 'FillContainer'
1✔
95
}
96

97
/**
98
 * Convenience class for quick resolutions
99
 * Mostly sourced from https://emulation.gametechwiki.com/index.php/Resolution
100
 */
101
export class Resolution {
102
  /* istanbul ignore next */
103
  public static get SVGA(): Resolution {
104
    return { width: 800, height: 600 };
105
  }
106

107
  /* istanbul ignore next */
108
  public static get Standard(): Resolution {
109
    return { width: 1920, height: 1080 };
110
  }
111

112
  /* istanbul ignore next */
113
  public static get Atari2600(): Resolution {
114
    return { width: 160, height: 192 };
115
  }
116

117
  /* istanbul ignore next */
118
  public static get GameBoy(): Resolution {
119
    return { width: 160, height: 144 };
120
  }
121

122
  /* istanbul ignore next */
123
  public static get GameBoyAdvance(): Resolution {
124
    return { width: 240, height: 160 };
125
  }
126

127
  /* istanbul ignore next */
128
  public static get NintendoDS(): Resolution {
129
    return { width: 256, height: 192 };
130
  }
131

132
  /* istanbul ignore next */
133
  public static get NES(): Resolution {
134
    return { width: 256, height: 224 };
135
  }
136

137
  /* istanbul ignore next */
138
  public static get SNES(): Resolution {
139
    return { width: 256, height: 244 };
140
  }
141
}
142

143
export type ViewportUnit = 'pixel' | 'percent';
144

145
export interface Resolution {
146
  width: number;
147
  height: number;
148
}
149

150
export interface ViewportDimension {
151
  widthUnit?: ViewportUnit;
152
  heightUnit?: ViewportUnit;
153
  width: number;
154
  height: number;
155
}
156

157
export interface ScreenOptions {
158
  /**
159
   * Canvas element to build a screen on
160
   */
161
  canvas: HTMLCanvasElement;
162

163
  /**
164
   * Graphics context for the screen
165
   */
166
  context: ExcaliburGraphicsContext;
167

168
  /**
169
   * Browser abstraction
170
   */
171
  browser: BrowserEvents;
172
  /**
173
   * Optionally set antialiasing, defaults to true. If set to true, images will be smoothed
174
   */
175
  antialiasing?: boolean;
176

177
  /**
178
   * Optionally set the image rendering CSS hint on the canvas element, default is auto
179
   */
180
  canvasImageRendering?: 'auto' | 'pixelated';
181
  /**
182
   * Optionally override the pixel ratio to use for the screen, otherwise calculated automatically from the browser
183
   */
184
  pixelRatio?: number;
185
  /**
186
   * Optionally specify the actual pixel resolution in width/height pixels (also known as logical resolution), by default the
187
   * resolution will be the same as the viewport. Resolution will be overridden by {@apilink DisplayMode.FillContainer} and
188
   * {@apilink DisplayMode.FillScreen}.
189
   */
190
  resolution?: Resolution;
191
  /**
192
   * Visual viewport size in css pixel, if resolution is not specified it will be the same as the viewport
193
   */
194
  viewport: ViewportDimension;
195
  /**
196
   * Set the display mode of the screen, by default DisplayMode.Fixed.
197
   */
198
  displayMode?: DisplayMode;
199
}
200

201
/**
202
 * Fires when the screen resizes, useful if you have logic that needs to be aware of resolution/viewport constraints
203
 */
204
export interface ScreenResizeEvent {
205
  /**
206
   * Current viewport in css pixels of the screen
207
   */
208
  viewport: ViewportDimension;
209
  /**
210
   * Current resolution in world pixels of the screen
211
   */
212
  resolution: Resolution;
213
}
214

215
/**
216
 * Fires when the pixel ratio changes, useful to know if you've moved to a hidpi screen or back
217
 */
218
export interface PixelRatioChangeEvent {
219
  /**
220
   * Current pixel ratio of the screen
221
   */
222
  pixelRatio: number;
223
}
224

225
/**
226
 * Fires when the browser fullscreen api is successfully engaged or disengaged
227
 */
228
export interface FullScreenChangeEvent {
229
  /**
230
   * Current fullscreen state
231
   */
232
  fullscreen: boolean;
233
}
234

235
/**
236
 * Built in events supported by all entities
237
 */
238
export type ScreenEvents = {
239
  /**
240
   * Fires when the screen resizes, useful if you have logic that needs to be aware of resolution/viewport constraints
241
   */
242
  resize: ScreenResizeEvent;
243
  /**
244
   * Fires when the pixel ratio changes, useful to know if you've moved to a hidpi screen or back
245
   */
246
  pixelratio: PixelRatioChangeEvent;
247
  /**
248
   * Fires when the browser fullscreen api is successfully engaged or disengaged
249
   */
250
  fullscreen: FullScreenChangeEvent;
251
};
252

253
export const ScreenEvents = {
1✔
254
  ScreenResize: 'resize',
255
  PixelRatioChange: 'pixelratio',
256
  FullScreenChange: 'fullscreen'
257
} as const;
258

259
/**
260
 * The Screen handles all aspects of interacting with the screen for Excalibur.
261
 */
262
export class Screen {
263
  public graphicsContext: ExcaliburGraphicsContext;
264
  /**
265
   * Listen to screen events {@apilink ScreenEvents}
266
   */
UNCOV
267
  public events = new EventEmitter<ScreenEvents>();
×
268
  private _canvas: HTMLCanvasElement;
UNCOV
269
  private _antialiasing: boolean = true;
×
UNCOV
270
  private _canvasImageRendering: 'auto' | 'pixelated' = 'auto';
×
271
  private _contentResolution: Resolution;
272
  private _browser: BrowserEvents;
273
  private _camera: Camera;
274
  private _resolution: Resolution;
UNCOV
275
  private _resolutionStack: Resolution[] = [];
×
276
  private _viewport: ViewportDimension;
UNCOV
277
  private _viewportStack: ViewportDimension[] = [];
×
UNCOV
278
  private _pixelRatioOverride: number | null = null;
×
279
  private _displayMode: DisplayMode;
UNCOV
280
  private _isFullscreen = false;
×
281
  private _mediaQueryList: MediaQueryList;
UNCOV
282
  private _isDisposed = false;
×
UNCOV
283
  private _logger = Logger.getInstance();
×
284
  private _resizeObserver: ResizeObserver;
285

286
  constructor(options: ScreenOptions) {
UNCOV
287
    this.viewport = options.viewport;
×
UNCOV
288
    this.resolution = options.resolution ?? { ...this.viewport };
×
UNCOV
289
    this._contentResolution = this.resolution;
×
UNCOV
290
    this._displayMode = options.displayMode ?? DisplayMode.Fixed;
×
UNCOV
291
    this._canvas = options.canvas;
×
UNCOV
292
    this.graphicsContext = options.context;
×
UNCOV
293
    this._antialiasing = options.antialiasing ?? this._antialiasing;
×
UNCOV
294
    this._canvasImageRendering = options.canvasImageRendering ?? this._canvasImageRendering;
×
UNCOV
295
    this._browser = options.browser;
×
UNCOV
296
    this._pixelRatioOverride = options.pixelRatio;
×
297

UNCOV
298
    this._applyDisplayMode();
×
299

UNCOV
300
    this._listenForPixelRatio();
×
301

UNCOV
302
    this._canvas.addEventListener('fullscreenchange', this._fullscreenChangeHandler);
×
UNCOV
303
    this.applyResolutionAndViewport();
×
304
  }
305

306
  private _listenForPixelRatio() {
UNCOV
307
    if (this._mediaQueryList && !this._mediaQueryList.addEventListener) {
×
308
      // Safari <=13.1 workaround, remove any existing handlers
309
      this._mediaQueryList.removeListener(this._pixelRatioChangeHandler);
×
310
    }
UNCOV
311
    this._mediaQueryList = this._browser.window.nativeComponent.matchMedia(`(resolution: ${window.devicePixelRatio}dppx)`);
×
312

313
    // Safari <=13.1 workaround
UNCOV
314
    if (this._mediaQueryList.addEventListener) {
×
UNCOV
315
      this._mediaQueryList.addEventListener('change', this._pixelRatioChangeHandler, { once: true });
×
316
    } else {
317
      this._mediaQueryList.addListener(this._pixelRatioChangeHandler);
×
318
    }
319
  }
320

321
  public dispose(): void {
UNCOV
322
    if (!this._isDisposed) {
×
323
      // Clean up handlers
UNCOV
324
      this._isDisposed = true;
×
UNCOV
325
      this.events.clear();
×
UNCOV
326
      this._browser.window.off('resize', this._resizeHandler);
×
UNCOV
327
      this._browser.window.clear();
×
UNCOV
328
      if (this._resizeObserver) {
×
329
        this._resizeObserver.disconnect();
×
330
      }
UNCOV
331
      this.parent.removeEventListener('resize', this._resizeHandler);
×
332
      // Safari <=13.1 workaround
UNCOV
333
      if (this._mediaQueryList.removeEventListener) {
×
UNCOV
334
        this._mediaQueryList.removeEventListener('change', this._pixelRatioChangeHandler);
×
335
      } else {
336
        this._mediaQueryList.removeListener(this._pixelRatioChangeHandler);
×
337
      }
UNCOV
338
      this._canvas.removeEventListener('fullscreenchange', this._fullscreenChangeHandler);
×
UNCOV
339
      this._canvas = null;
×
340
    }
341
  }
342

UNCOV
343
  private _fullscreenChangeHandler = () => {
×
UNCOV
344
    if (this._isDisposed) {
×
345
      return;
×
346
    }
UNCOV
347
    this._isFullscreen = !this._isFullscreen;
×
UNCOV
348
    this._logger.debug('Fullscreen Change', this._isFullscreen);
×
UNCOV
349
    this.events.emit('fullscreen', {
×
350
      fullscreen: this.isFullscreen
351
    } satisfies FullScreenChangeEvent);
352
  };
353

UNCOV
354
  private _pixelRatioChangeHandler = () => {
×
355
    if (this._isDisposed) {
×
356
      return;
×
357
    }
358
    this._logger.debug('Pixel Ratio Change', window.devicePixelRatio);
×
359
    this._listenForPixelRatio();
×
360
    this._devicePixelRatio = this._calculateDevicePixelRatio();
×
361
    this.applyResolutionAndViewport();
×
362

363
    this.events.emit('pixelratio', {
×
364
      pixelRatio: this.pixelRatio
365
    } satisfies PixelRatioChangeEvent);
366
  };
367

UNCOV
368
  private _resizeHandler = () => {
×
UNCOV
369
    if (this._isDisposed) {
×
UNCOV
370
      return;
×
371
    }
UNCOV
372
    const parent = this.parent;
×
UNCOV
373
    this._logger.debug('View port resized');
×
UNCOV
374
    this._setResolutionAndViewportByDisplayMode(parent);
×
UNCOV
375
    this.applyResolutionAndViewport();
×
376

377
    // Emit resize event
UNCOV
378
    this.events.emit('resize', {
×
379
      resolution: this.resolution,
380
      viewport: this.viewport
381
    } satisfies ScreenResizeEvent);
382
  };
383

384
  private _calculateDevicePixelRatio() {
UNCOV
385
    if (window.devicePixelRatio < 1) {
×
386
      return 1;
×
387
    }
388

UNCOV
389
    const devicePixelRatio = window.devicePixelRatio || 1;
×
390

UNCOV
391
    return devicePixelRatio;
×
392
  }
393

394
  // Asking the window.devicePixelRatio is expensive we do it once
UNCOV
395
  private _devicePixelRatio = this._calculateDevicePixelRatio();
×
396

397
  /**
398
   * Returns the computed pixel ratio, first using any override, then the device pixel ratio
399
   */
400
  public get pixelRatio(): number {
UNCOV
401
    if (this._pixelRatioOverride) {
×
UNCOV
402
      return this._pixelRatioOverride;
×
403
    }
404

UNCOV
405
    return this._devicePixelRatio;
×
406
  }
407

408
  /**
409
   * This calculates the ratio between excalibur pixels and the HTML pixels.
410
   *
411
   * This is useful for scaling HTML UI so that it matches your game.
412
   */
413
  public get worldToPagePixelRatio(): number {
UNCOV
414
    if (this._canvas) {
×
UNCOV
415
      const pageOrigin = this.worldToPageCoordinates(Vector.Zero);
×
UNCOV
416
      const pageDistance = this.worldToPageCoordinates(vec(1, 0)).sub(pageOrigin);
×
UNCOV
417
      const pixelConversion = pageDistance.x;
×
UNCOV
418
      return pixelConversion;
×
419
    } else {
420
      return 1;
×
421
    }
422
  }
423

424
  /**
425
   * Get or set the pixel ratio override
426
   *
427
   * You will need to call applyResolutionAndViewport() affect change on the screen
428
   */
429
  public get pixelRatioOverride(): number | undefined {
UNCOV
430
    return this._pixelRatioOverride;
×
431
  }
432

433
  public set pixelRatioOverride(value: number | undefined) {
UNCOV
434
    this._pixelRatioOverride = value;
×
435
  }
436

437
  public get isHiDpi() {
UNCOV
438
    return this.pixelRatio !== 1;
×
439
  }
440

441
  public get displayMode(): DisplayMode {
UNCOV
442
    return this._displayMode;
×
443
  }
444

445
  public get canvas(): HTMLCanvasElement {
UNCOV
446
    return this._canvas;
×
447
  }
448

449
  public get parent(): HTMLElement | Window {
UNCOV
450
    switch (this.displayMode) {
×
451
      case DisplayMode.FillContainer:
×
452
      case DisplayMode.FitContainer:
453
      case DisplayMode.FitContainerAndFill:
454
      case DisplayMode.FitContainerAndZoom:
UNCOV
455
        return this.canvas.parentElement || document.body;
×
456
      default:
UNCOV
457
        return window;
×
458
    }
459
  }
460

461
  public get resolution(): Resolution {
UNCOV
462
    return this._resolution;
×
463
  }
464

465
  public set resolution(resolution: Resolution) {
UNCOV
466
    this._resolution = resolution;
×
467
  }
468

469
  /**
470
   * Returns screen dimensions in pixels or percentage
471
   */
472
  public get viewport(): ViewportDimension {
UNCOV
473
    if (this._viewport) {
×
UNCOV
474
      return this._viewport;
×
475
    }
476
    return this._resolution;
×
477
  }
478

479
  public set viewport(viewport: ViewportDimension) {
UNCOV
480
    this._viewport = viewport;
×
481
  }
482

483
  public get aspectRatio() {
UNCOV
484
    return this._resolution.width / this._resolution.height;
×
485
  }
486

487
  public get scaledWidth() {
UNCOV
488
    return this._resolution.width * this.pixelRatio;
×
489
  }
490

491
  public get scaledHeight() {
UNCOV
492
    return this._resolution.height * this.pixelRatio;
×
493
  }
494

495
  public setCurrentCamera(camera: Camera) {
UNCOV
496
    this._camera = camera;
×
497
  }
498

499
  public pushResolutionAndViewport() {
UNCOV
500
    this._resolutionStack.push(this.resolution);
×
UNCOV
501
    this._viewportStack.push(this.viewport);
×
502

UNCOV
503
    this.resolution = { ...this.resolution };
×
UNCOV
504
    this.viewport = { ...this.viewport };
×
505
  }
506

507
  public peekViewport(): ViewportDimension {
508
    return this._viewportStack[this._viewportStack.length - 1];
×
509
  }
510

511
  public peekResolution(): Resolution {
UNCOV
512
    return this._resolutionStack[this._resolutionStack.length - 1];
×
513
  }
514

515
  public popResolutionAndViewport() {
UNCOV
516
    if (this._resolutionStack.length && this._viewportStack.length) {
×
UNCOV
517
      this.resolution = this._resolutionStack.pop();
×
UNCOV
518
      this.viewport = this._viewportStack.pop();
×
519
    }
520
  }
521

522
  public applyResolutionAndViewport() {
UNCOV
523
    if (this.graphicsContext instanceof ExcaliburGraphicsContextWebGL) {
×
UNCOV
524
      const scaledResolutionSupported = this.graphicsContext.checkIfResolutionSupported({
×
525
        width: this.scaledWidth,
526
        height: this.scaledHeight
527
      });
UNCOV
528
      if (!scaledResolutionSupported) {
×
UNCOV
529
        this._logger.warnOnce(
×
530
          `The currently configured resolution (${this.resolution.width}x${this.resolution.height}) and pixel ratio (${this.pixelRatio})` +
531
            ' are too large for the platform WebGL implementation, this may work but cause WebGL rendering to behave oddly.' +
532
            ' Try reducing the resolution or disabling Hi DPI scaling to avoid this' +
533
            ' (read more here https://excaliburjs.com/docs/screens#understanding-viewport--resolution).'
534
        );
535

536
        // Attempt to recover if the user hasn't configured a specific ratio for up scaling
UNCOV
537
        if (!this.pixelRatioOverride) {
×
UNCOV
538
          let currentPixelRatio = Math.max(1, this.pixelRatio - 0.5);
×
UNCOV
539
          let newResolutionSupported = false;
×
UNCOV
540
          while (currentPixelRatio > 1 && !newResolutionSupported) {
×
UNCOV
541
            currentPixelRatio = Math.max(1, currentPixelRatio - 0.5);
×
UNCOV
542
            const width = this._resolution.width * currentPixelRatio;
×
UNCOV
543
            const height = this._resolution.height * currentPixelRatio;
×
UNCOV
544
            newResolutionSupported = this.graphicsContext.checkIfResolutionSupported({ width, height });
×
545
          }
UNCOV
546
          this.pixelRatioOverride = currentPixelRatio;
×
UNCOV
547
          this._logger.warnOnce(
×
548
            'Scaled resolution too big attempted recovery!' +
549
              ` Pixel ratio was automatically reduced to (${this.pixelRatio}) to avoid 4k texture limit.` +
550
              ' Setting `ex.Engine({pixelRatio: ...}) will override any automatic recalculation, do so at your own risk.` ' +
551
              ' (read more here https://excaliburjs.com/docs/screens#understanding-viewport--resolution).'
552
          );
553
        }
554
      }
555
    }
556

UNCOV
557
    this._canvas.width = this.scaledWidth;
×
UNCOV
558
    this._canvas.height = this.scaledHeight;
×
559

UNCOV
560
    if (this._canvasImageRendering === 'auto') {
×
UNCOV
561
      this._canvas.style.imageRendering = 'auto';
×
562
    } else {
UNCOV
563
      this._canvas.style.imageRendering = 'pixelated';
×
564
      // Fall back to 'crisp-edges' if 'pixelated' is not supported
565
      // Currently for firefox https://developer.mozilla.org/en-US/docs/Web/CSS/image-rendering
UNCOV
566
      if (this._canvas.style.imageRendering === '') {
×
UNCOV
567
        this._canvas.style.imageRendering = 'crisp-edges';
×
568
      }
569
    }
570

UNCOV
571
    const widthUnit = this.viewport.widthUnit === 'percent' ? '%' : 'px';
×
UNCOV
572
    const heightUnit = this.viewport.heightUnit === 'percent' ? '%' : 'px';
×
573

UNCOV
574
    this._canvas.style.width = this.viewport.width + widthUnit;
×
UNCOV
575
    this._canvas.style.height = this.viewport.height + heightUnit;
×
576

577
    // After messing with the canvas width/height the graphics context is invalidated and needs to have some properties reset
UNCOV
578
    this.graphicsContext.updateViewport(this.resolution);
×
UNCOV
579
    this.graphicsContext.resetTransform();
×
UNCOV
580
    this.graphicsContext.smoothing = this._antialiasing;
×
UNCOV
581
    if (this.graphicsContext instanceof ExcaliburGraphicsContext2DCanvas) {
×
UNCOV
582
      this.graphicsContext.scale(this.pixelRatio, this.pixelRatio);
×
583
    }
584
    // Add the excalibur world pixel to page pixel
UNCOV
585
    document.documentElement.style.setProperty('--ex-pixel-ratio', this.worldToPagePixelRatio.toString());
×
586
  }
587

588
  /**
589
   * Get or set screen antialiasing,
590
   *
591
   * If true smoothing is applied
592
   */
593
  public get antialiasing() {
UNCOV
594
    return this._antialiasing;
×
595
  }
596

597
  /**
598
   * Get or set screen antialiasing
599
   */
600
  public set antialiasing(isSmooth: boolean) {
UNCOV
601
    this._antialiasing = isSmooth;
×
UNCOV
602
    this.graphicsContext.smoothing = this._antialiasing;
×
603
  }
604

605
  /**
606
   * Returns true if excalibur is fullscreen using the browser fullscreen api
607
   * @deprecated use isFullscreen()
608
   */
609
  public get isFullScreen() {
UNCOV
610
    return this._isFullscreen;
×
611
  }
612

613
  /**
614
   * Returns true if excalibur is fullscreen using the browser fullscreen api
615
   */
616
  public get isFullscreen() {
UNCOV
617
    return this._isFullscreen;
×
618
  }
619

620
  /**
621
   * Requests to go fullscreen using the browser fullscreen api, requires user interaction to be successful.
622
   * For example, wire this to a user click handler.
623
   *
624
   * Optionally specify a target element id to go fullscreen, by default the game canvas is used
625
   * @param elementId
626
   * @deprecated use enterFullscreen(...)
627
   */
628
  public goFullScreen(elementId?: string): Promise<void> {
629
    return this.enterFullscreen(elementId);
×
630
  }
631

632
  /**
633
   * Requests to enter fullscreen using the browser fullscreen api, requires user interaction to be successful.
634
   * For example, wire this to a user click handler.
635
   *
636
   * Optionally specify a target element id to go fullscreen, by default the game canvas is used
637
   * @param elementId
638
   */
639
  public enterFullscreen(elementId?: string): Promise<void> {
UNCOV
640
    if (elementId) {
×
UNCOV
641
      const maybeElement = document.getElementById(elementId);
×
642
      // workaround for safari partial support
UNCOV
643
      if (maybeElement?.requestFullscreen || (maybeElement as any)?.webkitRequestFullscreen) {
×
UNCOV
644
        if (!maybeElement.getAttribute('ex-fullscreen-listener')) {
×
UNCOV
645
          maybeElement.setAttribute('ex-fullscreen-listener', 'true');
×
UNCOV
646
          maybeElement.addEventListener('fullscreenchange', this._fullscreenChangeHandler);
×
647
        }
UNCOV
648
        if (maybeElement.requestFullscreen) {
×
UNCOV
649
          return maybeElement.requestFullscreen() ?? Promise.resolve();
×
650
        } else if ((maybeElement as any).webkitRequestFullscreen) {
×
651
          return (maybeElement as any).webkitRequestFullscreen() ?? Promise.resolve();
×
652
        }
653
      }
654
    }
UNCOV
655
    if (this._canvas?.requestFullscreen) {
×
UNCOV
656
      return this._canvas?.requestFullscreen() ?? Promise.resolve();
×
657
    } else if ((this._canvas as any).webkitRequestFullscreen) {
×
658
      return (this._canvas as any).webkitRequestFullscreen() ?? Promise.resolve();
×
659
    }
660
    this._logger.warnOnce('Could not go fullscreen, is this an iPhone? Currently Apple does not support fullscreen on iPhones');
×
661
    return Promise.resolve();
×
662
  }
663

664
  /**
665
   * Requests to exit fullscreen using the browser fullscreen api
666
   * @deprecated use exitFullscreen()
667
   */
668
  public exitFullScreen(): Promise<void> {
669
    return this.exitFullscreen();
×
670
  }
671

672
  public exitFullscreen(): Promise<void> {
673
    return document.exitFullscreen();
×
674
  }
675

676
  private _viewportToPixels(viewport: ViewportDimension) {
UNCOV
677
    return {
×
678
      width: viewport.widthUnit === 'percent' ? this.canvas.offsetWidth : viewport.width,
×
679
      height: viewport.heightUnit === 'percent' ? this.canvas.offsetHeight : viewport.height
×
680
    } satisfies ViewportDimension;
681
  }
682

683
  /**
684
   * Takes a coordinate in normal html page space, for example from a pointer move event, and translates it to
685
   * Excalibur screen space.
686
   *
687
   * Excalibur screen space starts at the top left (0, 0) corner of the viewport, and extends to the
688
   * bottom right corner (resolutionX, resolutionY). When using *AndFill suffixed display modes screen space
689
   * (0, 0) is the top left of the safe content area bounding box not the viewport.
690
   * @param point
691
   */
692
  public pageToScreenCoordinates(point: Vector): Vector {
UNCOV
693
    let newX = point.x;
×
UNCOV
694
    let newY = point.y;
×
695

UNCOV
696
    if (!this._isFullscreen) {
×
UNCOV
697
      newX -= getPosition(this._canvas).x;
×
UNCOV
698
      newY -= getPosition(this._canvas).y;
×
699
    }
700

UNCOV
701
    const viewport = this._viewportToPixels(this.viewport);
×
702

703
    // if fullscreen api on it centers with black bars
704
    // we need to adjust the screen to world coordinates in this case
UNCOV
705
    if (this._isFullscreen) {
×
UNCOV
706
      if (window.innerWidth / this.aspectRatio < window.innerHeight) {
×
UNCOV
707
        const screenHeight = window.innerWidth / this.aspectRatio;
×
UNCOV
708
        const screenMarginY = (window.innerHeight - screenHeight) / 2;
×
UNCOV
709
        newY = ((newY - screenMarginY) / screenHeight) * viewport.height;
×
UNCOV
710
        newX = (newX / window.innerWidth) * viewport.width;
×
711
      } else {
UNCOV
712
        const screenWidth = window.innerHeight * this.aspectRatio;
×
UNCOV
713
        const screenMarginX = (window.innerWidth - screenWidth) / 2;
×
UNCOV
714
        newX = ((newX - screenMarginX) / screenWidth) * viewport.width;
×
UNCOV
715
        newY = (newY / window.innerHeight) * viewport.height;
×
716
      }
717
    }
718

UNCOV
719
    newX = (newX / viewport.width) * this.resolution.width;
×
UNCOV
720
    newY = (newY / viewport.height) * this.resolution.height;
×
721

722
    // offset by content area
UNCOV
723
    newX = newX - this.contentArea.left;
×
UNCOV
724
    newY = newY - this.contentArea.top;
×
725

UNCOV
726
    return new Vector(newX, newY);
×
727
  }
728

729
  /**
730
   * Takes a coordinate in Excalibur screen space, and translates it to normal html page space. For example,
731
   * this is where html elements might live if you want to position them relative to Excalibur.
732
   *
733
   * Excalibur screen space starts at the top left (0, 0) corner of the viewport, and extends to the
734
   * bottom right corner (resolutionX, resolutionY)
735
   * @param point
736
   */
737
  public screenToPageCoordinates(point: Vector): Vector {
UNCOV
738
    let newX = point.x;
×
UNCOV
739
    let newY = point.y;
×
740

741
    // no need to offset by content area, drawing is already offset by this
742

UNCOV
743
    const viewport = this._viewportToPixels(this.viewport);
×
744

UNCOV
745
    newX = (newX / this.resolution.width) * viewport.width;
×
UNCOV
746
    newY = (newY / this.resolution.height) * viewport.height;
×
747

UNCOV
748
    if (this._isFullscreen) {
×
UNCOV
749
      if (window.innerWidth / this.aspectRatio < window.innerHeight) {
×
UNCOV
750
        const screenHeight = window.innerWidth / this.aspectRatio;
×
UNCOV
751
        const screenMarginY = (window.innerHeight - screenHeight) / 2;
×
UNCOV
752
        newY = (newY / viewport.height) * screenHeight + screenMarginY;
×
UNCOV
753
        newX = (newX / viewport.width) * window.innerWidth;
×
754
      } else {
UNCOV
755
        const screenWidth = window.innerHeight * this.aspectRatio;
×
UNCOV
756
        const screenMarginX = (window.innerWidth - screenWidth) / 2;
×
UNCOV
757
        newX = (newX / viewport.width) * screenWidth + screenMarginX;
×
UNCOV
758
        newY = (newY / viewport.height) * window.innerHeight;
×
759
      }
760
    }
761

UNCOV
762
    if (!this._isFullscreen) {
×
UNCOV
763
      newX += getPosition(this._canvas).x;
×
UNCOV
764
      newY += getPosition(this._canvas).y;
×
765
    }
766

UNCOV
767
    return new Vector(newX, newY);
×
768
  }
769

770
  /**
771
   * Takes a coordinate in Excalibur screen space, and translates it to Excalibur world space.
772
   *
773
   * World space is where {@apilink Entity | `entities`} in Excalibur live by default {@apilink CoordPlane.World}
774
   * and extends infinitely out relative from the {@apilink Camera}.
775
   * @param point  Screen coordinate to convert
776
   */
777
  public screenToWorldCoordinates(point: Vector): Vector {
778
    // offset by content area
UNCOV
779
    point = point.add(vec(this.contentArea.left, this.contentArea.top));
×
780

781
    // the only difference between screen & world is the camera transform
UNCOV
782
    if (this._camera) {
×
UNCOV
783
      return this._camera.inverse.multiply(point);
×
784
    }
UNCOV
785
    return point.sub(vec(this.resolution.width / 2, this.resolution.height / 2));
×
786
  }
787

788
  /**
789
   * Takes a coordinate in Excalibur world space, and translates it to Excalibur screen space.
790
   *
791
   * Screen space is where {@apilink ScreenElement | `screen elements`} and {@apilink Entity | `entities`} with {@apilink CoordPlane.Screen} live.
792
   * @param point  World coordinate to convert
793
   */
794
  public worldToScreenCoordinates(point: Vector): Vector {
UNCOV
795
    if (this._camera) {
×
UNCOV
796
      return this._camera.transform.multiply(point);
×
797
    }
UNCOV
798
    return point.add(vec(this.resolution.width / 2, this.resolution.height / 2));
×
799
  }
800

801
  public pageToWorldCoordinates(point: Vector): Vector {
UNCOV
802
    const screen = this.pageToScreenCoordinates(point);
×
UNCOV
803
    return this.screenToWorldCoordinates(screen);
×
804
  }
805

806
  public worldToPageCoordinates(point: Vector): Vector {
UNCOV
807
    const screen = this.worldToScreenCoordinates(point);
×
UNCOV
808
    return this.screenToPageCoordinates(screen);
×
809
  }
810

811
  /**
812
   * Returns a BoundingBox of the top left corner of the screen
813
   * and the bottom right corner of the screen.
814
   *
815
   * World bounds are in world coordinates, useful for culling objects offscreen that are in world space
816
   */
817
  public getWorldBounds(): BoundingBox {
UNCOV
818
    const bounds = BoundingBox.fromDimension(this.resolution.width, this.resolution.height, Vector.Half)
×
819
      .scale(vec(1 / this._camera.zoom, 1 / this._camera.zoom))
820
      .rotate(this._camera.rotation)
821
      .translate(this._camera.drawPos);
UNCOV
822
    return bounds;
×
823
  }
824

825
  /**
826
   * Returns a BoundingBox of the top left corner of the screen and the bottom right corner of the screen.
827
   *
828
   * Screen bounds are in screen coordinates, useful for culling objects offscreen that are in screen space
829
   */
830
  public getScreenBounds(): BoundingBox {
831
    const bounds = BoundingBox.fromDimension(this.resolution.width, this.resolution.height, Vector.Zero, Vector.Zero);
×
832
    return bounds;
×
833
  }
834

835
  /**
836
   * The width of the game canvas in pixels (physical width component of the
837
   * resolution of the canvas element)
838
   */
839
  public get canvasWidth(): number {
UNCOV
840
    return this.canvas.width;
×
841
  }
842

843
  /**
844
   * Returns half width of the game canvas in pixels (half physical width component)
845
   */
846
  public get halfCanvasWidth(): number {
UNCOV
847
    return this.canvas.width / 2;
×
848
  }
849

850
  /**
851
   * The height of the game canvas in pixels, (physical height component of
852
   * the resolution of the canvas element)
853
   */
854
  public get canvasHeight(): number {
UNCOV
855
    return this.canvas.height;
×
856
  }
857

858
  /**
859
   * Returns half height of the game canvas in pixels (half physical height component)
860
   */
861
  public get halfCanvasHeight(): number {
UNCOV
862
    return this.canvas.height / 2;
×
863
  }
864

865
  /**
866
   * Returns the width of the engine's visible drawing surface in pixels including zoom and device pixel ratio.
867
   */
868
  public get drawWidth(): number {
UNCOV
869
    if (this._camera) {
×
UNCOV
870
      return this.resolution.width / this._camera.zoom;
×
871
    }
UNCOV
872
    return this.resolution.width;
×
873
  }
874

875
  /**
876
   * Returns the width of the engine's visible drawing surface in pixels including zoom and device pixel ratio.
877
   */
878
  public get width(): number {
879
    if (this._camera) {
×
880
      return this.resolution.width / this._camera.zoom;
×
881
    }
882
    return this.resolution.width;
×
883
  }
884

885
  /**
886
   * Returns half the width of the engine's visible drawing surface in pixels including zoom and device pixel ratio.
887
   */
888
  public get halfDrawWidth(): number {
UNCOV
889
    return this.drawWidth / 2;
×
890
  }
891

892
  /**
893
   * Returns the height of the engine's visible drawing surface in pixels including zoom and device pixel ratio.
894
   */
895
  public get drawHeight(): number {
UNCOV
896
    if (this._camera) {
×
UNCOV
897
      return this.resolution.height / this._camera.zoom;
×
898
    }
UNCOV
899
    return this.resolution.height;
×
900
  }
901

902
  public get height(): number {
903
    if (this._camera) {
×
904
      return this.resolution.height / this._camera.zoom;
×
905
    }
906
    return this.resolution.height;
×
907
  }
908

909
  /**
910
   * Returns half the height of the engine's visible drawing surface in pixels including zoom and device pixel ratio.
911
   */
912
  public get halfDrawHeight(): number {
UNCOV
913
    return this.drawHeight / 2;
×
914
  }
915

916
  /**
917
   * Returns screen center coordinates including zoom and device pixel ratio.
918
   */
919
  public get center(): Vector {
UNCOV
920
    return vec(this.halfDrawWidth, this.halfDrawHeight);
×
921
  }
922

923
  /**
924
   * Returns the content area in screen space where it is safe to place content
925
   */
926
  public get contentArea(): BoundingBox {
UNCOV
927
    return this._contentArea;
×
928
  }
929

930
  /**
931
   * Returns the unsafe area in screen space, this is the full screen and some space may not be onscreen.
932
   */
933
  public get unsafeArea(): BoundingBox {
UNCOV
934
    return this._unsafeArea;
×
935
  }
936

UNCOV
937
  private _contentArea: BoundingBox = new BoundingBox();
×
UNCOV
938
  private _unsafeArea: BoundingBox = new BoundingBox();
×
939

940
  private _computeFit() {
UNCOV
941
    document.body.style.margin = '0px';
×
UNCOV
942
    document.body.style.overflow = 'hidden';
×
UNCOV
943
    const aspect = this.aspectRatio;
×
UNCOV
944
    let adjustedWidth = 0;
×
UNCOV
945
    let adjustedHeight = 0;
×
UNCOV
946
    if (window.innerWidth / aspect < window.innerHeight) {
×
UNCOV
947
      adjustedWidth = window.innerWidth;
×
UNCOV
948
      adjustedHeight = window.innerWidth / aspect;
×
949
    } else {
UNCOV
950
      adjustedWidth = window.innerHeight * aspect;
×
UNCOV
951
      adjustedHeight = window.innerHeight;
×
952
    }
953

UNCOV
954
    this.viewport = {
×
955
      width: adjustedWidth,
956
      height: adjustedHeight
957
    };
UNCOV
958
    this._contentArea = BoundingBox.fromDimension(this.resolution.width, this.resolution.height, Vector.Zero);
×
UNCOV
959
    this._unsafeArea = BoundingBox.fromDimension(this.resolution.width, this.resolution.height, Vector.Zero);
×
UNCOV
960
    this.events.emit('resize', {
×
961
      resolution: this.resolution,
962
      viewport: this.viewport
963
    } satisfies ScreenResizeEvent);
964
  }
965

966
  private _computeFitScreenAndFill() {
UNCOV
967
    document.body.style.margin = '0px';
×
UNCOV
968
    document.body.style.overflow = 'hidden';
×
UNCOV
969
    const vw = window.innerWidth;
×
UNCOV
970
    const vh = window.innerHeight;
×
UNCOV
971
    this._computeFitAndFill(vw, vh);
×
UNCOV
972
    this.events.emit('resize', {
×
973
      resolution: this.resolution,
974
      viewport: this.viewport
975
    } satisfies ScreenResizeEvent);
976
  }
977

978
  private _computeFitContainerAndFill() {
UNCOV
979
    this.canvas.style.width = '100%';
×
UNCOV
980
    this.canvas.style.height = '100%';
×
981

UNCOV
982
    this._computeFitAndFill(this.canvas.offsetWidth, this.canvas.offsetHeight, {
×
983
      width: 100,
984
      widthUnit: 'percent',
985
      height: 100,
986
      heightUnit: 'percent'
987
    });
UNCOV
988
    this.events.emit('resize', {
×
989
      resolution: this.resolution,
990
      viewport: this.viewport
991
    } satisfies ScreenResizeEvent);
992
  }
993

994
  private _computeFitAndFill(vw: number, vh: number, viewport?: ViewportDimension) {
UNCOV
995
    this.viewport = viewport ?? {
×
996
      width: vw,
997
      height: vh
998
    };
999
    // if the current screen aspectRatio is less than the original aspectRatio
UNCOV
1000
    if (vw / vh <= this._contentResolution.width / this._contentResolution.height) {
×
1001
      // compute new resolution to match the original aspect ratio
UNCOV
1002
      this.resolution = {
×
1003
        width: (vw * this._contentResolution.width) / vw,
1004
        height: (((vw * this._contentResolution.width) / vw) * vh) / vw
1005
      };
UNCOV
1006
      const clip = (this.resolution.height - this._contentResolution.height) / 2;
×
UNCOV
1007
      this._contentArea = new BoundingBox({
×
1008
        top: clip,
1009
        left: 0,
1010
        right: this._contentResolution.width,
1011
        bottom: this.resolution.height - clip
1012
      });
UNCOV
1013
      this._unsafeArea = new BoundingBox({
×
1014
        top: -clip,
1015
        left: 0,
1016
        right: this._contentResolution.width,
1017
        bottom: this.resolution.height + clip
1018
      });
1019
    } else {
UNCOV
1020
      this.resolution = {
×
1021
        width: (((vh * this._contentResolution.height) / vh) * vw) / vh,
1022
        height: (vh * this._contentResolution.height) / vh
1023
      };
UNCOV
1024
      const clip = (this.resolution.width - this._contentResolution.width) / 2;
×
UNCOV
1025
      this._contentArea = new BoundingBox({
×
1026
        top: 0,
1027
        left: clip,
1028
        right: this.resolution.width - clip,
1029
        bottom: this._contentResolution.height
1030
      });
UNCOV
1031
      this._unsafeArea = new BoundingBox({
×
1032
        top: 0,
1033
        left: -clip,
1034
        right: this.resolution.width + clip,
1035
        bottom: this._contentResolution.height
1036
      });
1037
    }
1038
  }
1039

1040
  private _computeFitScreenAndZoom() {
UNCOV
1041
    document.body.style.margin = '0px';
×
UNCOV
1042
    document.body.style.overflow = 'hidden';
×
UNCOV
1043
    this.canvas.style.position = 'absolute';
×
1044

UNCOV
1045
    const vw = window.innerWidth;
×
UNCOV
1046
    const vh = window.innerHeight;
×
1047

UNCOV
1048
    this._computeFitAndZoom(vw, vh);
×
UNCOV
1049
    this.events.emit('resize', {
×
1050
      resolution: this.resolution,
1051
      viewport: this.viewport
1052
    } satisfies ScreenResizeEvent);
1053
  }
1054

1055
  private _computeFitContainerAndZoom() {
UNCOV
1056
    this.canvas.style.width = '100%';
×
UNCOV
1057
    this.canvas.style.height = '100%';
×
UNCOV
1058
    this.canvas.style.position = 'relative';
×
UNCOV
1059
    const parent = this.canvas.parentElement;
×
UNCOV
1060
    parent.style.overflow = 'hidden';
×
UNCOV
1061
    const { offsetWidth: vw, offsetHeight: vh } = this.canvas;
×
1062

UNCOV
1063
    this._computeFitAndZoom(vw, vh);
×
UNCOV
1064
    this.events.emit('resize', {
×
1065
      resolution: this.resolution,
1066
      viewport: this.viewport
1067
    } satisfies ScreenResizeEvent);
1068
  }
1069

1070
  private _computeFitAndZoom(vw: number, vh: number) {
UNCOV
1071
    const aspect = this.aspectRatio;
×
UNCOV
1072
    let adjustedWidth = 0;
×
UNCOV
1073
    let adjustedHeight = 0;
×
UNCOV
1074
    if (vw / aspect < vh) {
×
UNCOV
1075
      adjustedWidth = vw;
×
UNCOV
1076
      adjustedHeight = vw / aspect;
×
1077
    } else {
UNCOV
1078
      adjustedWidth = vh * aspect;
×
UNCOV
1079
      adjustedHeight = vh;
×
1080
    }
1081

UNCOV
1082
    const scaleX = vw / adjustedWidth;
×
UNCOV
1083
    const scaleY = vh / adjustedHeight;
×
1084

UNCOV
1085
    const maxScaleFactor = Math.max(scaleX, scaleY);
×
1086

UNCOV
1087
    const zoomedWidth = adjustedWidth * maxScaleFactor;
×
UNCOV
1088
    const zoomedHeight = adjustedHeight * maxScaleFactor;
×
1089

1090
    // Center zoomed dimension if bigger than the screen
UNCOV
1091
    if (zoomedWidth > vw) {
×
UNCOV
1092
      this.canvas.style.left = -(zoomedWidth - vw) / 2 + 'px';
×
1093
    } else {
UNCOV
1094
      this.canvas.style.left = '';
×
1095
    }
1096

UNCOV
1097
    if (zoomedHeight > vh) {
×
UNCOV
1098
      this.canvas.style.top = -(zoomedHeight - vh) / 2 + 'px';
×
1099
    } else {
UNCOV
1100
      this.canvas.style.top = '';
×
1101
    }
1102

UNCOV
1103
    this.viewport = {
×
1104
      width: zoomedWidth,
1105
      height: zoomedHeight
1106
    };
1107

UNCOV
1108
    const bounds = BoundingBox.fromDimension(this.viewport.width, this.viewport.height, Vector.Zero);
×
1109
    // return safe area
UNCOV
1110
    if (this.viewport.width > vw) {
×
UNCOV
1111
      const clip = ((this.viewport.width - vw) / this.viewport.width) * this.resolution.width;
×
UNCOV
1112
      bounds.top = 0;
×
UNCOV
1113
      bounds.left = clip / 2;
×
UNCOV
1114
      bounds.right = this.resolution.width - clip / 2;
×
UNCOV
1115
      bounds.bottom = this.resolution.height;
×
1116
    }
1117

UNCOV
1118
    if (this.viewport.height > vh) {
×
UNCOV
1119
      const clip = ((this.viewport.height - vh) / this.viewport.height) * this.resolution.height;
×
UNCOV
1120
      bounds.top = clip / 2;
×
UNCOV
1121
      bounds.left = 0;
×
UNCOV
1122
      bounds.bottom = this.resolution.height - clip / 2;
×
UNCOV
1123
      bounds.right = this.resolution.width;
×
1124
    }
UNCOV
1125
    this._contentArea = bounds;
×
1126
  }
1127

1128
  private _computeFitContainer() {
UNCOV
1129
    const aspect = this.aspectRatio;
×
UNCOV
1130
    let adjustedWidth = 0;
×
UNCOV
1131
    let adjustedHeight = 0;
×
UNCOV
1132
    let widthUnit: ViewportUnit = 'pixel';
×
UNCOV
1133
    let heightUnit: ViewportUnit = 'pixel';
×
UNCOV
1134
    const parent = this.canvas.parentElement;
×
UNCOV
1135
    if (parent.clientWidth / aspect < parent.clientHeight) {
×
UNCOV
1136
      this.canvas.style.width = '100%';
×
UNCOV
1137
      adjustedWidth = 100;
×
UNCOV
1138
      widthUnit = 'percent';
×
UNCOV
1139
      adjustedHeight = this.canvas.offsetWidth / aspect;
×
1140
    } else {
UNCOV
1141
      this.canvas.style.height = '100%';
×
UNCOV
1142
      adjustedHeight = 100;
×
UNCOV
1143
      heightUnit = 'percent';
×
UNCOV
1144
      adjustedWidth = this.canvas.offsetHeight * aspect;
×
1145
    }
1146

UNCOV
1147
    this.viewport = {
×
1148
      width: adjustedWidth,
1149
      widthUnit,
1150
      height: adjustedHeight,
1151
      heightUnit
1152
    };
UNCOV
1153
    this._contentArea = BoundingBox.fromDimension(this.resolution.width, this.resolution.height, Vector.Zero);
×
UNCOV
1154
    this.events.emit('resize', {
×
1155
      resolution: this.resolution,
1156
      viewport: this.viewport
1157
    } satisfies ScreenResizeEvent);
1158
  }
1159

1160
  private _applyDisplayMode() {
UNCOV
1161
    this._setResolutionAndViewportByDisplayMode(this.parent);
×
1162

1163
    // watch resizing
UNCOV
1164
    if (this.parent instanceof Window) {
×
UNCOV
1165
      this._browser.window.on('resize', this._resizeHandler);
×
1166
    } else {
UNCOV
1167
      this._resizeObserver = new ResizeObserver(() => {
×
1168
        this._resizeHandler();
×
1169
      });
UNCOV
1170
      this._resizeObserver.observe(this.parent);
×
1171
    }
UNCOV
1172
    this.parent.addEventListener('resize', this._resizeHandler);
×
1173
  }
1174

1175
  /**
1176
   * Sets the resolution and viewport based on the selected display mode.
1177
   */
1178
  private _setResolutionAndViewportByDisplayMode(parent: HTMLElement | Window) {
UNCOV
1179
    if (this.displayMode === DisplayMode.FillContainer) {
×
1180
      this.canvas.style.width = '100%';
×
1181
      this.canvas.style.height = '100%';
×
1182
      this.viewport = {
×
1183
        width: 100,
1184
        widthUnit: 'percent',
1185
        height: 100,
1186
        heightUnit: 'percent'
1187
      };
1188
      this.resolution = {
×
1189
        width: this.canvas.offsetWidth,
1190
        height: this.canvas.offsetHeight
1191
      };
1192
    }
1193

UNCOV
1194
    if (this.displayMode === DisplayMode.FillScreen) {
×
UNCOV
1195
      document.body.style.margin = '0px';
×
UNCOV
1196
      document.body.style.overflow = 'hidden';
×
UNCOV
1197
      this.resolution = {
×
1198
        width: (<Window>parent).innerWidth,
1199
        height: (<Window>parent).innerHeight
1200
      };
1201

UNCOV
1202
      this.viewport = this.resolution;
×
1203
    }
1204

UNCOV
1205
    this._contentArea = BoundingBox.fromDimension(this.resolution.width, this.resolution.height, Vector.Zero);
×
1206

UNCOV
1207
    if (this.displayMode === DisplayMode.FitScreen) {
×
UNCOV
1208
      this._computeFit();
×
1209
    }
1210

UNCOV
1211
    if (this.displayMode === DisplayMode.FitContainer) {
×
UNCOV
1212
      this._computeFitContainer();
×
1213
    }
1214

UNCOV
1215
    if (this.displayMode === DisplayMode.FitScreenAndFill) {
×
UNCOV
1216
      this._computeFitScreenAndFill();
×
1217
    }
1218

UNCOV
1219
    if (this.displayMode === DisplayMode.FitContainerAndFill) {
×
UNCOV
1220
      this._computeFitContainerAndFill();
×
1221
    }
1222

UNCOV
1223
    if (this.displayMode === DisplayMode.FitScreenAndZoom) {
×
UNCOV
1224
      this._computeFitScreenAndZoom();
×
1225
    }
1226

UNCOV
1227
    if (this.displayMode === DisplayMode.FitContainerAndZoom) {
×
UNCOV
1228
      this._computeFitContainerAndZoom();
×
1229
    }
1230
  }
1231
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc