From bdcde9a4bb9d573c14529ee524263b2a07a4fb4a Mon Sep 17 00:00:00 2001 From: Valery Koultchitzky <83373303+Valery-a@users.noreply.github.com> Date: Thu, 17 Oct 2024 14:31:01 +0300 Subject: [PATCH] fix: add a way to disable VR button (important for android users) (#209) --- src/optionsGuiScheme.tsx | 22 +++++---- src/optionsStorage.ts | 1 + src/styles.css | 3 +- src/vr.ts | 100 +++++++++++++++++++++++++++++++++++++-- 4 files changed, 112 insertions(+), 14 deletions(-) diff --git a/src/optionsGuiScheme.tsx b/src/optionsGuiScheme.tsx index 015013d15..1e3851205 100644 --- a/src/optionsGuiScheme.tsx +++ b/src/optionsGuiScheme.tsx @@ -1,8 +1,7 @@ -import { useState } from 'react' +import { useRef, useState } from 'react' import { useSnapshot } from 'valtio' import { openURL } from 'prismarine-viewer/viewer/lib/simpleUtils' import { noCase } from 'change-case' -import { titleCase } from 'title-case' import { loadedGameState, miscUiState, openOptionsMenu, showModal } from './globalState' import { AppOptions, options } from './optionsStorage' import Button from './react/Button' @@ -14,7 +13,6 @@ import { completeTexturePackInstall, getResourcePackNames, resourcePackState, un import { downloadPacketsReplay, packetsReplaceSessionState } from './packetsReplay' import { showOptionsModal } from './react/SelectOption' - export const guiOptionsScheme: { [t in OptionsGroupType]: Array<{ [K in keyof AppOptions]?: Partial> } & { custom? }> } = { @@ -368,15 +366,21 @@ export const guiOptionsScheme: { } // { ignoreSilentSwitch: {} }, ], + VR: [ { custom () { - return <> - VR currently has basic support -
- - }, - } + return ( + <> + + VR currently has basic support + +
+ + ) + }, + vrSupport: {} + }, ], advanced: [ { diff --git a/src/optionsStorage.ts b/src/optionsStorage.ts index d5a9e5aa8..c7ab7b24a 100644 --- a/src/optionsStorage.ts +++ b/src/optionsStorage.ts @@ -74,6 +74,7 @@ const defaultOptions = { chatSelect: true, autoJump: 'auto' as 'auto' | 'always' | 'never', autoParkour: false, + vrSupport: true, // doesn't directly affect the VR mode, should only disable the button which is annoying to android users // advanced bot options autoRespawn: false, diff --git a/src/styles.css b/src/styles.css index 43eb9acbc..817fae8ca 100644 --- a/src/styles.css +++ b/src/styles.css @@ -70,8 +70,9 @@ body { #VRButton { background: rgba(0, 0, 0, 0.3) !important; opacity: 0.7 !important; - position: fixed !important; + position: static !important; bottom: 60px !important; + z-index: unset !important; } .dirt-bg { diff --git a/src/vr.ts b/src/vr.ts index f78cdb460..dd97560ec 100644 --- a/src/vr.ts +++ b/src/vr.ts @@ -3,17 +3,103 @@ import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js' import { XRControllerModelFactory } from 'three/examples/jsm/webxr/XRControllerModelFactory.js' import { buttonMap as standardButtonsMap } from 'contro-max/build/gamepad' import * as THREE from 'three' +import { subscribeKey } from 'valtio/utils' +import { subscribe } from 'valtio' import { activeModalStack, hideModal } from './globalState' +import { watchUnloadForCleanup } from './gameUnload' +import { options } from './optionsStorage' export async function initVR () { + options.vrSupport = true const { renderer } = viewer if (!('xr' in navigator)) return - const isSupported = await navigator.xr?.isSessionSupported('immersive-vr') && !!XRSession.prototype.updateRenderState // e.g. android webview doesn't support updateRenderState + + const isSupported = await checkVRSupport() if (!isSupported) return - // VR - document.body.appendChild(VRButton.createButton(renderer)) - renderer.xr.enabled = true + enableVr(renderer) + + const vrButtonContainer = createVrButtonContainer(renderer) + const updateVrButtons = () => { + vrButtonContainer.hidden = !options.vrSupport || activeModalStack.length !== 0 + } + + const unsubWatchSetting = subscribeKey(options, 'vrSupport', updateVrButtons) + const unsubWatchModals = subscribe(activeModalStack, updateVrButtons) + + function enableVr (renderer) { + renderer.xr.enabled = true + } + + function disableVr () { + renderer.xr.enabled = false + viewer.cameraObjectOverride = undefined + viewer.scene.remove(user) + vrButtonContainer.hidden = true + unsubWatchSetting() + unsubWatchModals() + } + + function createVrButtonContainer (renderer) { + const container = document.createElement('div') + const vrButton = VRButton.createButton(renderer) + styleContainer(container) + + const closeButton = createCloseButton(container) + + container.appendChild(vrButton) + container.appendChild(closeButton) + document.body.appendChild(container) + + return container + } + + function styleContainer (container: HTMLElement) { + typedAssign(container.style, { + position: 'absolute', + bottom: '80px', + left: '0', + right: '0', + display: 'flex', + justifyContent: 'center', + zIndex: '8', + gap: '8px', + }) + } + + function createCloseButton (container: HTMLElement) { + const closeButton = document.createElement('button') + closeButton.textContent = 'X' + typedAssign(closeButton.style, { + padding: '0 12px', + color: 'white', + fontSize: '14px', + lineHeight: '20px', + cursor: 'pointer', + background: 'transparent', + border: '1px solid rgb(255, 255, 255)', + borderRadius: '4px', + opacity: '0.7', + }) + + closeButton.addEventListener('click', () => { + container.hidden = true + options.vrSupport = false + }) + + return closeButton + } + + + async function checkVRSupport () { + try { + const supported = await navigator.xr?.isSessionSupported('immersive-vr') + return supported && !!XRSession.prototype.updateRenderState + } catch (err) { + console.error('Error checking if VR is supported', err) + return false + } + } // hack for vr camera const user = new THREE.Group() @@ -129,6 +215,8 @@ export async function initVR () { renderer.xr.addEventListener('sessionend', () => { viewer.cameraObjectOverride = undefined }) + + watchUnloadForCleanup(disableVr) } const xrStandardRightButtonsMap = [ @@ -169,3 +257,7 @@ const remapAxes = (axesRight, axesLeft) => { axesRight[3] ] } + +function typedAssign> (target: T, source: Partial) { + Object.assign(target, source) +}