Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions resources.json
Original file line number Diff line number Diff line change
Expand Up @@ -645,13 +645,13 @@
"Model"
],
"dependencies": [
"projects/enchantmentengine/ee-development-test-suite/public/avatars/Adam/1a8a75bafb18c29e94be4f6e0df4503932791c732fd5b85f5991993ffef3199e.ktx2",
"projects/enchantmentengine/ee-development-test-suite/public/avatars/Adam/1f68862e8529230781b98f1825852cb37039ab137a796da4e1a54368ab308693.ktx2",
"projects/enchantmentengine/ee-development-test-suite/public/avatars/Adam/3ebba2b690b91a31064fec6d6ca9e1558f4f56ddb804294c4663017e2a2ae8fc.bin",
"projects/enchantmentengine/ee-development-test-suite/public/avatars/Adam/5cbedd9382f99d1be408692e8d3b4172edb4aa5edcd9d90bca57f7c6b7db4e2b.bin",
"projects/enchantmentengine/ee-development-test-suite/public/avatars/Adam/9613d363e55e1af7cfb0dfb020bae9e417ccb11f6051da379ac96aa35cba59a0.ktx2",
"projects/enchantmentengine/ee-development-test-suite/public/avatars/Adam/ffd667f5c4c57f61a582c47337035d704ae616104df6ee505b3c715455c4c11c.ktx2",
"projects/enchantmentengine/ee-development-test-suite/public/avatars/Adam/Adam_data.bin",
"projects/enchantmentengine/ee-development-test-suite/public/avatars/Adam/ffd667f5c4c57f61a582c47337035d704ae616104df6ee505b3c715455c4c11c.ktx2"
"projects/enchantmentengine/ee-development-test-suite/public/avatars/Adam/9613d363e55e1af7cfb0dfb020bae9e417ccb11f6051da379ac96aa35cba59a0.ktx2",
"projects/enchantmentengine/ee-development-test-suite/public/avatars/Adam/5cbedd9382f99d1be408692e8d3b4172edb4aa5edcd9d90bca57f7c6b7db4e2b.bin",
"projects/enchantmentengine/ee-development-test-suite/public/avatars/Adam/3ebba2b690b91a31064fec6d6ca9e1558f4f56ddb804294c4663017e2a2ae8fc.bin",
"projects/enchantmentengine/ee-development-test-suite/public/avatars/Adam/1f68862e8529230781b98f1825852cb37039ab137a796da4e1a54368ab308693.ktx2",
"projects/enchantmentengine/ee-development-test-suite/public/avatars/Adam/1a8a75bafb18c29e94be4f6e0df4503932791c732fd5b85f5991993ffef3199e.ktx2"
],
"name": "Adam.gltf",
"thumbnailKey": "projects/enchantmentengine/ee-development-test-suite/public/thumbnails/public_avatars_Adam.gltf-thumbnail.png",
Expand Down
218 changes: 198 additions & 20 deletions src/devtool/EmulatorDevtools.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useHookstate, useImmediateEffect, useMutableState } from '@ir-engine/hyperflux'
import { endXRSession, requestXRSession } from '@ir-engine/spatial/src/xr/XRSessionFunctions'
import Button from '@ir-engine/ui/src/primitives/tailwind/Button'
import React from 'react'
import React, { useEffect, useRef, useState } from 'react'

import EmulatedDevice from './js/emulatedDevice'
import { EmulatorSettings, emulatorStates } from './js/emulatorStates'
Expand All @@ -15,15 +15,8 @@ import { WebXREventDispatcher } from '@ir-engine/spatial/tests/webxr/emulator/We
import { POLYFILL_ACTIONS } from '@ir-engine/spatial/tests/webxr/emulator/actions'

export async function overrideXR(args: { mode: 'immersive-vr' | 'immersive-ar' }) {
// inject the webxr polyfill from the webxr emulator source - this is a script added by the bot
// globalThis.WebXRPolyfillInjection()

const { CustomWebXRPolyfill } = await import('@ir-engine/spatial/tests/webxr/emulator/CustomWebXRPolyfill')
new CustomWebXRPolyfill()
// override session supported request, it hangs indefinitely for some reason
;(navigator as any).xr.isSessionSupported = () => {
return true
}

const deviceDefinition = {
id: 'Oculus Quest',
Expand Down Expand Up @@ -74,7 +67,6 @@ const setup = async (mode: 'immersive-vr' | 'immersive-ar') => {

return device
}

export const EmulatorDevtools = (props: { mode: 'immersive-vr' | 'immersive-ar' }) => {
const xrState = useMutableState(XRState)
const xrActive = xrState.sessionActive.value && !xrState.requestingSession.value
Expand All @@ -86,6 +78,24 @@ export const EmulatorDevtools = (props: { mode: 'immersive-vr' | 'immersive-ar'
})
}, [])

// Panel state
const [panelPosition, setPanelPosition] = useState({ x: window.innerWidth - 720, y: 20 })
const [panelSize, setPanelSize] = useState({ width: 700, height: 900 })
const [isDragging, setIsDragging] = useState(false)
const [isResizing, setIsResizing] = useState(false)
const [dragOffset, setDragOffset] = useState({ x: 0, y: 0 })
const [resizeDirection, setResizeDirection] = useState('')
const [isVisible, setIsVisible] = useState(false)
const [isMinimized, setIsMinimized] = useState(false)

const panelRef = useRef<HTMLDivElement>(null)
const resizeHandleRef = useRef<HTMLDivElement>(null)

// Refs for performance optimization
const dragPosRef = useRef(panelPosition)
const sizeRef = useRef(panelSize)
const rafRef = useRef<number | null>(null)

const toggleXR = async () => {
if (xrActive) {
endXRSession()
Expand All @@ -104,24 +114,192 @@ export const EmulatorDevtools = (props: { mode: 'immersive-vr' | 'immersive-ar'
}
}

const handleClosePanel = () => setIsVisible(false)

const handleMinimizePanel = () => {
setIsMinimized(!isMinimized)
setPanelSize((prev) => ({
...prev,
height: isMinimized ? 600 : 50
}))
}

// Mouse event handlers for dragging
const handleMouseDown = (e: React.MouseEvent) => {
const target = e.target as HTMLElement
const isHeader = target.closest('.floating-panel-header')
const isPanel = target === panelRef.current
const isResizeHandle = target.closest('.resize-handle')

if (isHeader || isPanel) {
e.preventDefault()
setIsDragging(true)
const rect = panelRef.current?.getBoundingClientRect()
if (rect) {
setDragOffset({
x: e.clientX - rect.left,
y: e.clientY - rect.top
})
}
}
}

const updatePanelPosition = (x: number, y: number) => {
dragPosRef.current = { x, y }
if (rafRef.current === null) {
rafRef.current = requestAnimationFrame(() => {
setPanelPosition({ ...dragPosRef.current })
rafRef.current = null
})
}
}

const updatePanelSize = (width: number, height: number) => {
sizeRef.current = { width, height }
if (rafRef.current === null) {
rafRef.current = requestAnimationFrame(() => {
setPanelSize({ ...sizeRef.current })
rafRef.current = null
})
}
}

const handleMouseMove = (e: MouseEvent) => {
if (isDragging) {
e.preventDefault()
const newX = e.clientX - dragOffset.x
const newY = e.clientY - dragOffset.y

const maxX = window.innerWidth - panelSize.width
const maxY = window.innerHeight - panelSize.height

updatePanelPosition(Math.max(0, Math.min(newX, maxX)), Math.max(0, Math.min(newY, maxY)))
} else if (isResizing) {
e.preventDefault()
let width = sizeRef.current.width
let height = sizeRef.current.height

if (resizeDirection.includes('e')) {
width = e.clientX - panelPosition.x
}
if (resizeDirection.includes('s')) {
height = e.clientY - panelPosition.y
}

width = Math.max(300, Math.min(width, window.innerWidth - panelPosition.x))
height = Math.max(400, Math.min(height, window.innerHeight - panelPosition.y))

updatePanelSize(width, height)
}
}

const handleMouseUp = () => {
setIsDragging(false)
setIsResizing(false)
setResizeDirection('')
if (rafRef.current !== null) {
cancelAnimationFrame(rafRef.current)
rafRef.current = null
}
}

const handleResizeMouseDown = (direction: string) => (e: React.MouseEvent) => {
e.preventDefault()
e.stopPropagation()
setIsResizing(true)
setResizeDirection(direction)
}

useEffect(() => {
document.addEventListener('mousemove', handleMouseMove)
document.addEventListener('mouseup', handleMouseUp)
return () => {
document.removeEventListener('mousemove', handleMouseMove)
document.removeEventListener('mouseup', handleMouseUp)
}
}, [isDragging, isResizing, dragOffset, panelPosition, panelSize, resizeDirection])

if (!isVisible) {
return (
<>
<style type="text/css">{devtoolCSS.toString()}</style>
<button className="show-panel-btn" onClick={() => setIsVisible(true)}>
Show XR Devtool
</button>
</>
)
}

return (
<>
<style type="text/css">{devtoolCSS.toString()}</style>

<div
id="devtools"
className="flex-no-wrap m-0 flex h-full h-full select-none flex-col overflow-hidden overflow-hidden bg-gray-900 text-xs text-gray-900"
ref={panelRef}
className={`floating-devtool-panel ${isMinimized ? 'minimized' : ''}`}
style={{
left: panelPosition.x,
top: panelPosition.y,
width: panelSize.width,
height: panelSize.height,
cursor: isDragging ? 'grabbing' : 'grab'
}}
onMouseDown={handleMouseDown}
>
<div className="flex-no-wrap z-50 flex h-10 select-none flex-row bg-gray-800 text-xs text-gray-900">
<Button className="my-1 ml-auto mr-6 px-10" onClick={toggleXR} disabled={xrState.requestingSession.value}>
{(xrActive ? 'Exit ' : 'Enter ') + (props.mode === 'immersive-ar' ? 'AR' : 'VR')}
</Button>
{props.mode === 'immersive-ar' && (
<Button className="my-1 ml-auto mr-6 px-10" onClick={togglePlacement} disabled={!xrActive}>
Place Scene
<div className="floating-panel-header" style={{ cursor: isDragging ? 'grabbing' : 'grab' }}>
<div className="text-sm font-medium text-white">XR Devtool Panel</div>
<div className="flex gap-2">
<button
className="panel-control-btn bg-gray-600 text-white hover:bg-gray-500"
onClick={handleMinimizePanel}
title={isMinimized ? 'Maximize' : 'Minimize'}
>
{isMinimized ? '□' : '−'}
</button>
<Button
className="bg-gray-600 px-3 py-1 text-xs text-white hover:bg-gray-500"
onClick={toggleXR}
disabled={xrState.requestingSession.value}
>
{(xrActive ? 'Exit ' : 'Enter ') + (props.mode === 'immersive-ar' ? 'AR' : 'VR')}
</Button>
)}
{props.mode === 'immersive-ar' && (
<Button
className="bg-gray-600 px-3 py-1 text-xs text-white hover:bg-gray-500"
onClick={togglePlacement}
disabled={!xrActive}
>
Place Scene
</Button>
)}
<button
className="panel-control-btn bg-red-600 text-white hover:bg-red-700"
onClick={handleClosePanel}
title="Close"
>
×
</button>
</div>
</div>
{deviceState.value && <Devtool device={deviceState.value} />}

<div className="floating-panel-content" style={{ opacity: isMinimized ? 0 : 1 }}>
<div className="floating-panel-scroll">
{deviceState.value ? (
<Devtool device={deviceState.value} />
) : (
<div className="flex h-full items-center justify-center text-gray-400">
<div className="text-center">
<div className="mx-auto mb-2 h-8 w-8 animate-spin rounded-full border-b-2 border-gray-400"></div>
<div>Initializing XR Devtool...</div>
</div>
</div>
)}
</div>
</div>

{!isMinimized && (
<div ref={resizeHandleRef} className="resize-handle" onMouseDown={handleResizeMouseDown('se')} />
)}
</div>
</>
)
Expand Down
Loading