You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

viewport.ts 24KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914
  1. /*
  2. * MARTINS.js
  3. * GPU-accelerated Augmented Reality for the web
  4. * Copyright (C) 2022-2024 Alexandre Martins <alemartf(at)gmail.com>
  5. *
  6. * This program is free software: you can redistribute it and/or modify
  7. * it under the terms of the GNU Lesser General Public License as published
  8. * by the Free Software Foundation, either version 3 of the License, or
  9. * (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU Lesser General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU Lesser General Public License
  17. * along with this program. If not, see <https://www.gnu.org/licenses/>.
  18. *
  19. * viewport.ts
  20. * Viewport
  21. */
  22. import Speedy from 'speedy-vision';
  23. import { SpeedySize } from 'speedy-vision/types/core/speedy-size';
  24. import { SpeedyPromise } from 'speedy-vision/types/core/speedy-promise';
  25. import { Nullable } from '../utils/utils';
  26. import { Resolution } from '../utils/resolution';
  27. import { Utils } from '../utils/utils';
  28. import { IllegalArgumentError, IllegalOperationError, NotSupportedError, AccessDeniedError } from '../utils/errors';
  29. import { HUD, HUDContainer } from './hud';
  30. import { AREvent, AREventTarget, AREventListener } from '../utils/ar-events';
  31. /** Viewport container */
  32. export type ViewportContainer = HTMLDivElement;
  33. /** We admit that the size of the drawing buffer of the background canvas of the viewport may change over time */
  34. type ViewportSizeGetter = () => SpeedySize;
  35. /** All possible event types emitted by a Viewport */
  36. type ViewportEventType = 'resize';
  37. /** An event emitted by a Viewport */
  38. class ViewportEvent extends AREvent<ViewportEventType> { }
  39. /** Viewport event target */
  40. class ViewportEventTarget extends AREventTarget<ViewportEventType> { }
  41. /** Viewport style (immersive mode) */
  42. type ViewportStyle = 'best-fit' | 'stretch' | 'inline';
  43. /**
  44. * Viewport constructor settings
  45. */
  46. export interface ViewportSettings
  47. {
  48. /** Viewport container */
  49. container: Nullable<ViewportContainer>;
  50. /** HUD container */
  51. hudContainer?: Nullable<HUDContainer>;
  52. /** Resolution of the canvas on which the virtual scene will be drawn */
  53. resolution?: Resolution;
  54. /** Viewport style */
  55. style?: ViewportStyle;
  56. /** An existing <canvas> on which the virtual scene will be drawn */
  57. canvas?: Nullable<HTMLCanvasElement>;
  58. }
  59. /** Default viewport constructor settings */
  60. const DEFAULT_VIEWPORT_SETTINGS: Readonly<Required<ViewportSettings>> = {
  61. container: null,
  62. hudContainer: null,
  63. resolution: 'lg',
  64. style: 'best-fit',
  65. canvas: null,
  66. };
  67. /** Base z-index of the children of the viewport container */
  68. const BASE_ZINDEX = 0;
  69. /** Z-index of the background canvas */
  70. const BACKGROUND_ZINDEX = BASE_ZINDEX + 0;
  71. /** Z-index of the foreground canvas */
  72. const FOREGROUND_ZINDEX = BASE_ZINDEX + 1;
  73. /** Z-index of the HUD */
  74. const HUD_ZINDEX = BASE_ZINDEX + 2;
  75. /**
  76. * Helper class to work with the containers of the viewport
  77. */
  78. class ViewportContainers
  79. {
  80. /** The viewport container */
  81. private readonly _container: ViewportContainer;
  82. /** A direct child of the viewport container */
  83. private readonly _subContainer: HTMLDivElement;
  84. /**
  85. * Constructor
  86. * @param container viewport container
  87. */
  88. constructor(container: Nullable<ViewportContainer>)
  89. {
  90. // validate
  91. if(container == null)
  92. throw new IllegalArgumentError('Unspecified viewport container');
  93. else if(!(container instanceof HTMLElement))
  94. throw new IllegalArgumentError('Invalid viewport container');
  95. // store the viewport container
  96. this._container = container;
  97. // create the sub-container
  98. this._subContainer = document.createElement('div') as HTMLDivElement;
  99. container.appendChild(this._subContainer);
  100. }
  101. /**
  102. * The viewport container
  103. */
  104. get container(): ViewportContainer
  105. {
  106. return this._container;
  107. }
  108. /**
  109. * The sub-container
  110. */
  111. get subContainer(): HTMLDivElement
  112. {
  113. return this._subContainer;
  114. }
  115. /**
  116. * Initialize
  117. */
  118. init(): void
  119. {
  120. this._container.style.touchAction = 'none';
  121. this._container.style.backgroundColor = 'black';
  122. }
  123. /**
  124. * Release
  125. */
  126. release(): void
  127. {
  128. this._container.style.backgroundColor = 'initial';
  129. this._container.style.touchAction = 'auto';
  130. }
  131. }
  132. /**
  133. * Helper class to work with the canvases of the viewport
  134. */
  135. class ViewportCanvases
  136. {
  137. /** A canvas used to render the physical scene */
  138. private readonly _backgroundCanvas: HTMLCanvasElement;
  139. /** A canvas used to render the virtual scene */
  140. private readonly _foregroundCanvas: HTMLCanvasElement;
  141. /** Original CSS of the foreground canvas */
  142. private readonly _originalCSSTextOfForegroundCanvas: string;
  143. /**
  144. * Constructor
  145. * @param parent container for the canvases
  146. * @param initialSize initial size of the canvases
  147. * @param fgCanvas optional existing foreground canvas
  148. */
  149. constructor(parent: HTMLElement, initialSize: SpeedySize, fgCanvas: Nullable<HTMLCanvasElement> = null)
  150. {
  151. if(fgCanvas !== null && !(fgCanvas instanceof HTMLCanvasElement))
  152. throw new IllegalArgumentError('Not a canvas: ' + fgCanvas);
  153. this._originalCSSTextOfForegroundCanvas = fgCanvas ? fgCanvas.style.cssText : '';
  154. this._foregroundCanvas = this._styleCanvas(
  155. fgCanvas || this._createCanvas(initialSize),
  156. FOREGROUND_ZINDEX
  157. );
  158. this._backgroundCanvas = this._styleCanvas(
  159. this._createCanvas(initialSize),
  160. BACKGROUND_ZINDEX
  161. );
  162. parent.appendChild(this._backgroundCanvas);
  163. parent.appendChild(this._foregroundCanvas);
  164. }
  165. /**
  166. * The background canvas
  167. */
  168. get backgroundCanvas(): HTMLCanvasElement
  169. {
  170. return this._backgroundCanvas;
  171. }
  172. /**
  173. * The foreground canvas
  174. */
  175. get foregroundCanvas(): HTMLCanvasElement
  176. {
  177. return this._foregroundCanvas;
  178. }
  179. /**
  180. * Initialize
  181. */
  182. init(): void
  183. {
  184. }
  185. /**
  186. * Release
  187. */
  188. release(): void
  189. {
  190. this._backgroundCanvas.style.cssText = '';
  191. this._foregroundCanvas.style.cssText = this._originalCSSTextOfForegroundCanvas;
  192. }
  193. /**
  194. * Create a canvas
  195. * @param size size of the drawing buffer
  196. * @returns a new canvas
  197. */
  198. private _createCanvas(size: SpeedySize): HTMLCanvasElement
  199. {
  200. const canvas = document.createElement('canvas') as HTMLCanvasElement;
  201. canvas.width = size.width;
  202. canvas.height = size.height;
  203. return canvas;
  204. }
  205. /**
  206. * Add suitable CSS rules to a canvas
  207. * @param canvas
  208. * @param zIndex
  209. * @returns canvas
  210. */
  211. private _styleCanvas(canvas: HTMLCanvasElement, zIndex: number): HTMLCanvasElement
  212. {
  213. canvas.style.position = 'absolute';
  214. canvas.style.left = '0px';
  215. canvas.style.top = '0px';
  216. canvas.style.width = '100%';
  217. canvas.style.height = '100%';
  218. canvas.style.zIndex = String(zIndex);
  219. return canvas;
  220. }
  221. }
  222. /**
  223. * Fullscreen utilities
  224. */
  225. class ViewportFullscreenHelper
  226. {
  227. /**
  228. * Constructor
  229. * @param _container the container which will be put in fullscreen
  230. */
  231. constructor(private readonly _container: HTMLElement)
  232. {
  233. }
  234. /**
  235. * Make a request to the user agent so that the viewport container is
  236. * displayed in fullscreen mode. The container must be a compatible element[1]
  237. * and the user must interact with the page in order to comply with browser
  238. * policies[2]. In case of error, the returned promise is rejected.
  239. * [1] https://developer.mozilla.org/en-US/docs/Web/API/Element/requestFullscreen#compatible_elements
  240. * [2] https://developer.mozilla.org/en-US/docs/Web/API/Element/requestFullscreen#security
  241. * @returns promise
  242. */
  243. request(): SpeedyPromise<void>
  244. {
  245. const container = this._container;
  246. // fallback for older WebKit versions
  247. if(container.requestFullscreen === undefined) {
  248. if((container as any).webkitRequestFullscreen === undefined)
  249. return Speedy.Promise.reject(new NotSupportedError());
  250. else if(!(document as any).webkitFullscreenEnabled)
  251. return Speedy.Promise.reject(new AccessDeniedError());
  252. // webkitRequestFullscreen() does not return a value
  253. (container as any).webkitRequestFullscreen();
  254. return new Speedy.Promise<void>((resolve, reject) => {
  255. setTimeout(() => {
  256. if(container === (document as any).webkitFullscreenElement) {
  257. Utils.log('Entering fullscreen mode...');
  258. resolve();
  259. }
  260. else
  261. reject(new TypeError());
  262. }, 100);
  263. });
  264. }
  265. // check if the fullscreen mode is available
  266. if(!document.fullscreenEnabled)
  267. return Speedy.Promise.reject(new AccessDeniedError());
  268. // request fullscreen
  269. return new Speedy.Promise<void>((resolve, reject) => {
  270. container.requestFullscreen({
  271. navigationUI: 'hide'
  272. }).then(() => {
  273. Utils.log('Entering fullscreen mode...');
  274. resolve();
  275. }, reject);
  276. });
  277. }
  278. /**
  279. * Exit fullscreen mode
  280. * @returns promise
  281. */
  282. exit(): SpeedyPromise<void>
  283. {
  284. // fallback for older WebKit versions
  285. if(document.exitFullscreen === undefined) {
  286. const doc = document as any;
  287. if(doc.webkitExitFullscreen === undefined)
  288. return Speedy.Promise.reject(new NotSupportedError());
  289. else if(doc.webkitFullscreenElement === null)
  290. return Speedy.Promise.reject(new IllegalOperationError('Not in fullscreen mode'));
  291. // webkitExitFullscreen() does not return a value
  292. doc.webkitExitFullscreen();
  293. return new Speedy.Promise<void>((resolve, reject) => {
  294. setTimeout(() => {
  295. if(doc.webkitFullscreenElement === null) {
  296. Utils.log('Exiting fullscreen mode...');
  297. resolve();
  298. }
  299. else
  300. reject(new TypeError());
  301. }, 100);
  302. });
  303. }
  304. // error if not in fullscreen mode
  305. if(document.fullscreenElement === null)
  306. return Speedy.Promise.reject(new IllegalOperationError('Not in fullscreen mode'));
  307. // exit fullscreen
  308. return new Speedy.Promise<void>((resolve, reject) => {
  309. document.exitFullscreen().then(() => {
  310. Utils.log('Exiting fullscreen mode...');
  311. resolve();
  312. }, reject);
  313. });
  314. }
  315. /**
  316. * Is the fullscreen mode available in this platform?
  317. * @returns true if the fullscreen mode is available in this platform
  318. */
  319. isAvailable(): boolean
  320. {
  321. return document.fullscreenEnabled ||
  322. !!((document as any).webkitFullscreenEnabled);
  323. }
  324. /**
  325. * Is the container currently being displayed in fullscreen mode?
  326. * @returns true if the container is currently being displayed in fullscreen mode
  327. */
  328. isActivated(): boolean
  329. {
  330. if(document.fullscreenElement !== undefined)
  331. return document.fullscreenElement === this._container;
  332. else if((document as any).webkitFullscreenElement !== undefined)
  333. return (document as any).webkitFullscreenElement === this._container;
  334. else
  335. return false;
  336. }
  337. }
  338. /**
  339. * Helper class to resize the viewport
  340. */
  341. class ViewportResizer
  342. {
  343. /** the viewport to be resized */
  344. private readonly _viewport: Viewport;
  345. /** is this viewport subject to being resized? */
  346. private _active: boolean;
  347. /** bound resize method */
  348. private readonly _resize: () => void;
  349. /** resize strategy */
  350. private _resizeStrategy: ViewportResizeStrategy;
  351. /**
  352. * Constructor
  353. * @param viewport the viewport to be resized
  354. */
  355. constructor(viewport: Viewport)
  356. {
  357. this._viewport = viewport;
  358. this._active = false;
  359. this._resize = this._onResize.bind(this);
  360. this._resizeStrategy = new InlineResizeStrategy();
  361. // initial setup
  362. // (the size is yet unknown)
  363. this._resize();
  364. }
  365. /**
  366. * Initialize
  367. */
  368. init(): void
  369. {
  370. // Configure the resize listener. We want the viewport
  371. // to adjust itself if the phone/screen is resized or
  372. // changes orientation
  373. let timeout: Nullable<ReturnType<typeof setTimeout>> = null;
  374. const onWindowResize = () => {
  375. if(!this._active) {
  376. window.removeEventListener('resize', onWindowResize);
  377. return;
  378. }
  379. if(timeout !== null)
  380. clearTimeout(timeout);
  381. timeout = setTimeout(() => {
  382. timeout = null;
  383. this._resize();
  384. }, 50);
  385. };
  386. window.addEventListener('resize', onWindowResize);
  387. // handle changes of orientation
  388. // (is this needed? we already listen to resize events)
  389. if(screen.orientation !== undefined)
  390. screen.orientation.addEventListener('change', this._resize);
  391. else
  392. window.addEventListener('orientationchange', this._resize); // deprecated
  393. // setup event listener & finish!
  394. this._viewport.addEventListener('resize', this._resize);
  395. this._active = true;
  396. // trigger a resize to setup the sizes / the CSS
  397. this.resize();
  398. }
  399. /**
  400. * Release
  401. */
  402. release(): void
  403. {
  404. this._resizeStrategy.clear(this._viewport);
  405. this._active = false;
  406. this._viewport.removeEventListener('resize', this._resize);
  407. if(screen.orientation !== undefined)
  408. screen.orientation.removeEventListener('change', this._resize);
  409. else
  410. window.removeEventListener('orientationchange', this._resize); // deprecated
  411. }
  412. /**
  413. * Trigger a resize event
  414. */
  415. resize(): void
  416. {
  417. const event = new ViewportEvent('resize');
  418. this._viewport.dispatchEvent(event);
  419. }
  420. /**
  421. * Change the resize strategy
  422. * @param strategy new strategy
  423. */
  424. setStrategy(strategy: ViewportResizeStrategy): void
  425. {
  426. this._resizeStrategy.clear(this._viewport);
  427. this._resizeStrategy = strategy;
  428. this.resize();
  429. }
  430. /**
  431. * Resize callback
  432. */
  433. private _onResize(): void
  434. {
  435. const viewport = this._viewport;
  436. // Resize the drawing buffer of the foreground canvas, so that it
  437. // matches the desired resolution, as well as the aspect ratio of the
  438. // background canvas
  439. const foregroundCanvas = viewport.canvas;
  440. const virtualSize = viewport.virtualSize;
  441. foregroundCanvas.width = virtualSize.width;
  442. foregroundCanvas.height = virtualSize.height;
  443. // Resize the drawing buffer of the background canvas
  444. const backgroundCanvas = viewport._backgroundCanvas;
  445. const realSize = viewport._realSize;
  446. backgroundCanvas.width = realSize.width;
  447. backgroundCanvas.height = realSize.height;
  448. // Call strategy
  449. this._resizeStrategy.resize(viewport);
  450. }
  451. }
  452. /**
  453. * Resize strategies
  454. */
  455. abstract class ViewportResizeStrategy
  456. {
  457. /**
  458. * Resize the viewport
  459. * @param viewport
  460. */
  461. abstract resize(viewport: Viewport): void;
  462. /**
  463. * Clear CSS rules
  464. * @param viewport
  465. */
  466. clear(viewport: Viewport): void
  467. {
  468. viewport.container.style.cssText = '';
  469. viewport._subContainer.style.cssText = '';
  470. }
  471. }
  472. /**
  473. * Inline viewport: it follows the typical flow of a web page
  474. */
  475. class InlineResizeStrategy extends ViewportResizeStrategy
  476. {
  477. resize(viewport: Viewport): void
  478. {
  479. const container = viewport.container;
  480. const subContainer = viewport._subContainer;
  481. const virtualSize = viewport.virtualSize;
  482. container.style.position = 'relative';
  483. container.style.left = '0px';
  484. container.style.top = '0px';
  485. container.style.width = virtualSize.width + 'px';
  486. container.style.height = virtualSize.height + 'px';
  487. subContainer.style.position = 'absolute';
  488. subContainer.style.left = '0px';
  489. subContainer.style.top = '0px';
  490. subContainer.style.width = '100%';
  491. subContainer.style.height = '100%';
  492. }
  493. }
  494. /**
  495. * Immersive viewport: it occupies the entire page
  496. */
  497. abstract class ImmersiveResizeStrategy extends ViewportResizeStrategy
  498. {
  499. resize(viewport: Viewport): void
  500. {
  501. const CONTAINER_ZINDEX = 1000000000;
  502. const container = viewport.container;
  503. container.style.position = 'fixed';
  504. container.style.left = '0px';
  505. container.style.top = '0px';
  506. container.style.width = '100vw';
  507. container.style.height = '100vh';
  508. container.style.zIndex = String(CONTAINER_ZINDEX);
  509. }
  510. }
  511. /**
  512. * Immersive viewport with best-fit style: it occupies the entire page and
  513. * preserves the aspect ratio of the media
  514. */
  515. class BestFitResizeStrategy extends ImmersiveResizeStrategy
  516. {
  517. resize(viewport: Viewport): void
  518. {
  519. const subContainer = viewport._subContainer;
  520. const windowAspectRatio = window.innerWidth / window.innerHeight;
  521. const viewportAspectRatio = viewport._realSize.width / viewport._realSize.height;
  522. let width = 1, height = 1;
  523. if(viewportAspectRatio <= windowAspectRatio) {
  524. height = window.innerHeight;
  525. width = (height * viewportAspectRatio) | 0;
  526. }
  527. else {
  528. width = window.innerWidth;
  529. height = (width / viewportAspectRatio) | 0;
  530. }
  531. subContainer.style.position = 'absolute';
  532. subContainer.style.left = `calc(50% - ${(width+1) >>> 1}px)`;
  533. subContainer.style.top = `calc(50% - ${(height+1) >>> 1}px)`;
  534. subContainer.style.width = width + 'px';
  535. subContainer.style.height = height + 'px';
  536. super.resize(viewport);
  537. }
  538. }
  539. /**
  540. * Immersive viewport with stretch style: it occupies the entire page and
  541. * fully stretches the media
  542. */
  543. class StretchResizeStrategy extends ImmersiveResizeStrategy
  544. {
  545. resize(viewport: Viewport): void
  546. {
  547. const subContainer = viewport._subContainer;
  548. subContainer.style.position = 'absolute';
  549. subContainer.style.left = '0px';
  550. subContainer.style.top = '0px';
  551. subContainer.style.width = window.innerWidth + 'px';
  552. subContainer.style.height = window.innerHeight + 'px';
  553. super.resize(viewport);
  554. }
  555. }
  556. /**
  557. * Viewport
  558. */
  559. export class Viewport extends ViewportEventTarget
  560. {
  561. /** Viewport resolution (controls the size of the drawing buffer of the foreground canvas) */
  562. private readonly _resolution: Resolution;
  563. /** The containers */
  564. private readonly _containers: ViewportContainers;
  565. /** An overlay displayed in front of the augmented scene */
  566. protected readonly _hud: HUD;
  567. /** Viewport style */
  568. protected _style: Nullable<ViewportStyle>;
  569. /** The canvases of the viewport */
  570. private readonly _canvases: ViewportCanvases;
  571. /** Fullscreen utilities */
  572. private readonly _fullscreen: ViewportFullscreenHelper;
  573. /** Resize helper */
  574. private readonly _resizer: ViewportResizer;
  575. /** The current size of the underlying SpeedyMedia */
  576. private _mediaSize: ViewportSizeGetter;
  577. /**
  578. * Constructor
  579. * @param viewportSettings
  580. */
  581. constructor(viewportSettings: ViewportSettings)
  582. {
  583. const settings = Object.assign({}, DEFAULT_VIEWPORT_SETTINGS, viewportSettings);
  584. super();
  585. const guessedAspectRatio = window.innerWidth / window.innerHeight;
  586. const initialSize = Utils.resolution(settings.resolution, guessedAspectRatio);
  587. this._mediaSize = () => initialSize;
  588. this._resolution = settings.resolution;
  589. this._style = null;
  590. this._containers = new ViewportContainers(settings.container);
  591. this._hud = new HUD(this._subContainer, settings.hudContainer);
  592. this._canvases = new ViewportCanvases(this._subContainer, initialSize, settings.canvas);
  593. this._fullscreen = new ViewportFullscreenHelper(this.container);
  594. this._resizer = new ViewportResizer(this);
  595. this.style = settings.style;
  596. }
  597. /**
  598. * Viewport container
  599. */
  600. get container(): ViewportContainer
  601. {
  602. return this._containers.container;
  603. }
  604. /**
  605. * Viewport style
  606. */
  607. get style(): ViewportStyle
  608. {
  609. if(this._style === null)
  610. throw new IllegalOperationError();
  611. return this._style;
  612. }
  613. /**
  614. * Set viewport style
  615. */
  616. set style(value: ViewportStyle)
  617. {
  618. // nothing to do
  619. if(value === this._style)
  620. return;
  621. // change style
  622. switch(value) {
  623. case 'best-fit':
  624. this._resizer.setStrategy(new BestFitResizeStrategy());
  625. break;
  626. case 'stretch':
  627. this._resizer.setStrategy(new StretchResizeStrategy());
  628. break;
  629. case 'inline':
  630. this._resizer.setStrategy(new InlineResizeStrategy());
  631. break;
  632. default:
  633. throw new IllegalArgumentError('Invalid viewport style: ' + value);
  634. }
  635. this._style = value;
  636. // note: the viewport style is independent of the session mode!
  637. }
  638. /**
  639. * HUD
  640. */
  641. get hud(): HUD
  642. {
  643. return this._hud;
  644. }
  645. /**
  646. * Resolution of the virtual scene
  647. */
  648. get resolution(): Resolution
  649. {
  650. return this._resolution;
  651. }
  652. /**
  653. * Size in pixels of the drawing buffer of the canvas
  654. * on which the virtual scene will be drawn
  655. */
  656. get virtualSize(): SpeedySize
  657. {
  658. const size = this._realSize;
  659. const aspectRatio = size.width / size.height;
  660. return Utils.resolution(this._resolution, aspectRatio);
  661. }
  662. /**
  663. * Is the viewport currently being displayed in fullscreen mode?
  664. */
  665. get fullscreen(): boolean
  666. {
  667. return this._fullscreen.isActivated();
  668. }
  669. /**
  670. * Is the fullscreen mode available in this platform?
  671. */
  672. get fullscreenAvailable(): boolean
  673. {
  674. return this._fullscreen.isAvailable();
  675. }
  676. /**
  677. * The canvas on which the virtual scene will be drawn
  678. */
  679. get canvas(): HTMLCanvasElement
  680. {
  681. return this._canvases.foregroundCanvas;
  682. }
  683. /**
  684. * The canvas on which the physical scene will be drawn
  685. * @internal
  686. */
  687. get _backgroundCanvas(): HTMLCanvasElement
  688. {
  689. return this._canvases.backgroundCanvas;
  690. }
  691. /**
  692. * Size of the drawing buffer of the background canvas, in pixels
  693. * @internal
  694. */
  695. get _realSize(): SpeedySize
  696. {
  697. return this._mediaSize();
  698. }
  699. /**
  700. * Sub-container of the viewport container
  701. * @internal
  702. */
  703. get _subContainer(): HTMLDivElement
  704. {
  705. return this._containers.subContainer;
  706. }
  707. /**
  708. * Request fullscreen mode
  709. * @returns promise
  710. */
  711. requestFullscreen(): SpeedyPromise<void>
  712. {
  713. return this._fullscreen.request();
  714. }
  715. /**
  716. * Exit fullscreen mode
  717. * @returns promise
  718. */
  719. exitFullscreen(): SpeedyPromise<void>
  720. {
  721. return this._fullscreen.exit();
  722. }
  723. /**
  724. * Initialize the viewport (when the session starts)
  725. * @internal
  726. */
  727. _init(getMediaSize: ViewportSizeGetter): void
  728. {
  729. this._mediaSize = getMediaSize;
  730. this._containers.init();
  731. this._hud._init(HUD_ZINDEX);
  732. this._canvases.init();
  733. this._resizer.init();
  734. }
  735. /**
  736. * Release the viewport (when the session ends)
  737. * @internal
  738. */
  739. _release(): void
  740. {
  741. this._resizer.release();
  742. this._canvases.release();
  743. this._hud._release();
  744. this._containers.release();
  745. }
  746. }