@needle-tools/engine
Version:
Needle Engine is a web-based runtime for 3D apps. It runs on your machine for development with great integrations into editors like Unity or Blender - and can be deployed onto any device! It is flexible, extensible and networking and XR are built-in.
114 lines (101 loc) • 4.24 kB
text/typescript
import { isDevEnvironment } from "./debug/index.js";
import { Context } from "./engine_setup.js";
export enum ApplicationEvents {
Visible = "application-visible",
Hidden = "application-hidden",
MuteChanged = "application-mutechanged",
}
let userInteractionRegistered = false;
const userInteractionCallbacks: Function[] = [];
/**
* Invoked when the user interacts with the page (click, touch, keypress, etc) allowing to play media / audio / start XR
* @internal
*/
export function internalOnUserInputRegistered() {
if (userInteractionRegistered) return;
if (isDevEnvironment()) console.debug("User interaction registered: audio can now be played");
userInteractionRegistered = true;
const copy = [...userInteractionCallbacks];
userInteractionCallbacks.length = 0;
copy.forEach(cb => cb());
}
document.addEventListener('mousedown', internalOnUserInputRegistered);
document.addEventListener('pointerup', internalOnUserInputRegistered);
document.addEventListener('click', internalOnUserInputRegistered);
document.addEventListener('dragstart', internalOnUserInputRegistered);
document.addEventListener('touchend', internalOnUserInputRegistered);
document.addEventListener('keydown', internalOnUserInputRegistered);
/**
* The Application class can be used to mute audio globally, and to check if the application (canvas) is currently visible (it's tab is active and not minimized).
*/
export class Application extends EventTarget {
public static get userInteractionRegistered(): boolean {
return userInteractionRegistered;
}
/** @deprecated use Application.registerWaitForInteraction instead */
public static readonly registerWaitForAllowAudio = Application.registerWaitForInteraction;
/**
* Register a callback that will be called when the user interacts with the page (click, touch, keypress, etc).
* If the user has already interacted with the page, the callback will be called immediately.
* This can be used to wait for user interaction before playing audio, for example.
*/
public static registerWaitForInteraction(cb: Function) {
if (cb !== null) {
if (userInteractionRegistered) {
cb();
return;
}
if (userInteractionCallbacks.indexOf(cb) === -1)
userInteractionCallbacks.push(cb);
}
}
/**
* Unregister a callback that was previously registered with registerWaitForInteraction.
*/
public static unregisterWaitForInteraction(cb: Function) {
const index = userInteractionCallbacks.indexOf(cb);
if (index !== -1) {
userInteractionCallbacks.splice(index, 1);
}
}
private _mute: boolean = false;
/** audio muted? */
get muted() { return this._mute; }
/** set global audio mute */
set muted(value) {
if (value === this._mute) return;
this._mute = value;
this.dispatchEvent(new Event(ApplicationEvents.MuteChanged));
}
private readonly context: Context;
/** @returns true if the document is focused */
public get hasFocus(): boolean {
return document.hasFocus();
}
/**
* @returns true if the application is currently visible (it's tab is active and not minimized)
*/
public get isVisible(): boolean {
return this._isVisible;
}
private _isVisible: boolean = true;
/** @internal */
constructor(context: Context) {
super();
this.context = context;
window.addEventListener("visibilitychange", this.onVisiblityChanged.bind(this), false);
}
private onVisiblityChanged(evt) {
// console.log(evt.target.visibilityState)
switch (evt.target.visibilityState) {
case "hidden":
this._isVisible = false;
this.dispatchEvent(new Event(ApplicationEvents.Hidden));
break;
case "visible":
this._isVisible = true;
this.dispatchEvent(new Event(ApplicationEvents.Visible));
break;
}
}
}