import { CE, createSvgIcon } from "@utils/html"; import { ScreenshotAction } from "./screenshot-action"; import { TouchControlAction } from "./touch-control-action"; import { BxEvent } from "@utils/bx-event"; import { BxIcon } from "@utils/bx-icon"; import type { BaseGameBarAction } from "./base-action"; import { STATES } from "@utils/global"; import { MicrophoneAction } from "./microphone-action"; import { GlobalPref } from "@/enums/pref-keys"; import { TrueAchievementsAction } from "./true-achievements-action"; import { SpeakerAction } from "./speaker-action"; import { RendererAction } from "./renderer-action"; import { BxLogger } from "@/utils/bx-logger"; import { GameBarPosition, TouchControllerMode } from "@/enums/pref-values"; import { BxEventBus } from "@/utils/bx-event-bus"; import { getGlobalPref } from "@/utils/pref-utils"; export class GameBar { private static instance: GameBar | null | undefined; public static getInstance(): typeof GameBar['instance'] { if (typeof GameBar.instance === 'undefined') { if (getGlobalPref(GlobalPref.GAME_BAR_POSITION) !== GameBarPosition.OFF) { GameBar.instance = new GameBar(); } else { GameBar.instance = null; } } return GameBar.instance; } private readonly LOG_TAG = 'GameBar'; private static readonly VISIBLE_DURATION = 2000; private $gameBar: HTMLElement; private $container: HTMLElement; private timeoutId: number | null = null; private actions: BaseGameBarAction[] = []; private constructor() { BxLogger.info(this.LOG_TAG, 'constructor()'); let $container; const position = getGlobalPref(GlobalPref.GAME_BAR_POSITION); const $gameBar = CE('div', { id: 'bx-game-bar', class: 'bx-gone', 'data-position': position }, $container = CE('div', { class: 'bx-game-bar-container bx-offscreen' }), createSvgIcon(position === 'bottom-left' ? BxIcon.CARET_RIGHT : BxIcon.CARET_LEFT), ); this.actions = [ new ScreenshotAction(), ...(STATES.userAgent.capabilities.touch && (getGlobalPref(GlobalPref.TOUCH_CONTROLLER_MODE) !== TouchControllerMode.OFF) ? [new TouchControlAction()] : []), new SpeakerAction(), new RendererAction(), new MicrophoneAction(), new TrueAchievementsAction(), ]; // Reverse the action list if Game Bar's position is on the right side if (position === 'bottom-right') { this.actions.reverse(); } // Render actions for (const action of this.actions) { $container.appendChild(action.render()); } // Toggle game bar when clicking on the game bar box $gameBar.addEventListener('click', e => { if (e.target !== $gameBar) { return; } $container.classList.contains('bx-show') ? this.hideBar() : this.showBar(); }); // Hide game bar after clicking on an action BxEventBus.Stream.on('gameBar.activated', this.hideBar); $container.addEventListener('pointerover', this.clearHideTimeout); $container.addEventListener('pointerout', this.beginHideTimeout); // Add animation when hiding game bar $container.addEventListener('transitionend', e => { $container.classList.replace('bx-hide', 'bx-offscreen'); }); document.documentElement.appendChild($gameBar); this.$gameBar = $gameBar; this.$container = $container; // Enable/disable Game Bar when playing/pausing position !== GameBarPosition.OFF && window.addEventListener(BxEvent.XCLOUD_POLLING_MODE_CHANGED, ((e: Event) => { // Toggle Game bar if (STATES.isPlaying) { window.BX_STREAM_SETTINGS.xCloudPollingMode !== 'none' ? this.disable() : this.enable(); } }).bind(this)); } private beginHideTimeout = () => { this.clearHideTimeout(); this.timeoutId = window.setTimeout(() => { this.timeoutId = null; this.hideBar(); }, GameBar.VISIBLE_DURATION); } private clearHideTimeout = () => { this.timeoutId && clearTimeout(this.timeoutId); this.timeoutId = null; } enable() { this.$gameBar.classList.remove('bx-gone'); } disable() { this.hideBar(); this.$gameBar.classList.add('bx-gone'); } showBar() { this.$container.classList.remove('bx-offscreen', 'bx-hide' , 'bx-gone'); this.$container.classList.add('bx-show'); this.beginHideTimeout(); } hideBar = () => { this.clearHideTimeout(); this.$container.classList.replace('bx-show', 'bx-hide'); } // Reset all states reset() { for (const action of this.actions) { action.reset(); } } }