From 100c5e612c8e2806fc8c5560bd416c3975871589 Mon Sep 17 00:00:00 2001 From: Michael Mainguy Date: Thu, 13 Nov 2025 06:57:35 -0600 Subject: [PATCH] Move exit XR button to toolbox class MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Refactored exit XR button creation from rigplatform to toolbox for better organization and UI cohesion. - Add setXR() methods to DiagramManager, DiagramMenuManager, and Toolbox to pass WebXRDefaultExperience after initialization - Create setupXRButton() in Toolbox class that creates button when entering XR - Position button at bottom-right of toolbox (x: 0.5, y: -0.35, z: 0) - Use Y-axis rotation (Math.PI) for correct orientation within toolbox coordinate system - Scale button to 0.2 for appropriate size - Remove button creation code from rigplatform Exit button now moves with toolbox and is logically grouped with other UI elements. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- src/controllers/rigplatform.ts | 21 +------------- src/diagram/diagramManager.ts | 10 ++++++- src/diagram/diagramMenuManager.ts | 6 +++- src/toolbox/toolbox.ts | 37 +++++++++++++++++++++++- src/util/functions/groundMeshObserver.ts | 3 ++ 5 files changed, 54 insertions(+), 23 deletions(-) diff --git a/src/controllers/rigplatform.ts b/src/controllers/rigplatform.ts index 9a258ec..2575891 100644 --- a/src/controllers/rigplatform.ts +++ b/src/controllers/rigplatform.ts @@ -8,7 +8,6 @@ import {DefaultScene} from "../defaultScene"; import {ControllerEvent} from "./types/controllerEvent"; import {ControllerEventType} from "./types/controllerEventType"; import {controllerObservable} from "./controllers"; -import {Button} from "../objects/Button"; const RIGHT = "right"; const LEFT = "left"; @@ -45,25 +44,7 @@ export class Rigplatform { this._xr = xr; this.rigMesh = buildRig(xr); - this._xr.baseExperience.onStateChangedObservable.add((state) => { - if (state == 2) { - const button = Button.CreateButton("exitXr", "exitXr", this._scene, {}); - button.transform.position.z = 1; - button.transform.rotation.y = Math.PI; - button.transform.position.y = 1.2; - button.transform.scaling = new Vector3(.1, .1, .1); - button.transform.parent = this.rigMesh; - button.onPointerObservable.add((evt) => { - console.log(evt); - console.log(evt.sourceEvent.type); - if (evt.sourceEvent.type == 'pointerdown') { - xr.baseExperience.exitXRAsync(); - } - }); - - } - - }); + // Exit XR button is now created in toolbox class this._fixRotation(); this._initializeControllers(); diff --git a/src/diagram/diagramManager.ts b/src/diagram/diagramManager.ts index 291665d..7cbb393 100644 --- a/src/diagram/diagramManager.ts +++ b/src/diagram/diagramManager.ts @@ -1,4 +1,4 @@ -import {AbstractActionManager, AbstractMesh, ActionManager, Observable, Scene} from "@babylonjs/core"; +import {AbstractActionManager, AbstractMesh, ActionManager, Observable, Scene, WebXRDefaultExperience} from "@babylonjs/core"; import {DiagramEntity, DiagramEntityType, DiagramEvent, DiagramEventType} from "./types/diagramEntity"; import log from "loglevel"; @@ -29,6 +29,14 @@ export class DiagramManager { private _moving: number = 10; private _i: number = 0; + public get diagramMenuManager(): DiagramMenuManager { + return this._diagramMenuManager; + } + + public setXR(xr: WebXRDefaultExperience): void { + this._diagramMenuManager.setXR(xr); + } + constructor(readyObservable: Observable) { this._me = getMe(); this._scene = DefaultScene.Scene; diff --git a/src/diagram/diagramMenuManager.ts b/src/diagram/diagramMenuManager.ts index 2dc491f..e690bda 100644 --- a/src/diagram/diagramMenuManager.ts +++ b/src/diagram/diagramMenuManager.ts @@ -1,5 +1,5 @@ import {DiagramEntityType, DiagramEvent, DiagramEventType} from "./types/diagramEntity"; -import {AbstractMesh, ActionEvent, Observable, Scene, Vector3, WebXRInputSource} from "@babylonjs/core"; +import {AbstractMesh, ActionEvent, Observable, Scene, Vector3, WebXRDefaultExperience, WebXRInputSource} from "@babylonjs/core"; import {InputTextView} from "../information/inputTextView"; import {DefaultScene} from "../defaultScene"; import log from "loglevel"; @@ -127,4 +127,8 @@ export class DiagramMenuManager { private notifyAll(event: DiagramEvent) { this._notifier.notifyObservers(event, DiagramEventObserverMask.ALL); } + + public setXR(xr: WebXRDefaultExperience): void { + this.toolbox.setXR(xr); + } } \ No newline at end of file diff --git a/src/toolbox/toolbox.ts b/src/toolbox/toolbox.ts index 921eae2..0d8a191 100644 --- a/src/toolbox/toolbox.ts +++ b/src/toolbox/toolbox.ts @@ -1,8 +1,9 @@ -import {AbstractMesh, Color3, InstancedMesh, Node, Observable, Scene, TransformNode, Vector3} from "@babylonjs/core"; +import {AbstractMesh, Color3, InstancedMesh, Node, Observable, Scene, TransformNode, Vector3, WebXRDefaultExperience} from "@babylonjs/core"; import {buildColor} from "./functions/buildColor"; import log from "loglevel"; import {Handle} from "../objects/handle"; import {DefaultScene} from "../defaultScene"; +import {Button} from "../objects/Button"; const colors: string[] = [ "#222222", "#8b4513", "#006400", "#778899", @@ -18,6 +19,7 @@ export class Toolbox { private readonly _logger = log.getLogger('Toolbox'); private readonly _handle: Handle; private readonly _scene: Scene; + private _xr?: WebXRDefaultExperience; constructor(readyObservable: Observable) { this._scene = DefaultScene.Scene; @@ -31,6 +33,11 @@ export class Toolbox { }); Toolbox._instance = this; } + + public setXR(xr: WebXRDefaultExperience): void { + this._xr = xr; + this.setupXRButton(); + } private index = 0; private colorPicker: TransformNode; private changing = false; @@ -128,5 +135,33 @@ export class Toolbox { } } + + private setupXRButton() { + if (!this._xr) { + this._logger.warn('XR not available, exit XR button will not be created'); + return; + } + + this._xr.baseExperience.onStateChangedObservable.add((state) => { + if (state == 2) { // WebXRState.IN_XR + const button = Button.CreateButton("exitXr", "exitXr", this._scene, {}); + + // Position button at bottom-right of toolbox, matching handle size and orientation + button.transform.position.x = 0.5; // Right side + button.transform.position.y = -0.35; // Below color grid + button.transform.position.z = 0; // Coplanar with toolbox + button.transform.rotation.y = Math.PI; // Flip 180° on local x-axis to face correctly + button.transform.scaling = new Vector3(.2, .2, .2); // Match handle height + button.transform.parent = this._toolboxBaseNode; + + button.onPointerObservable.add((evt) => { + this._logger.debug(evt); + if (evt.sourceEvent.type == 'pointerdown') { + this._xr.baseExperience.exitXRAsync(); + } + }); + } + }); + } } diff --git a/src/util/functions/groundMeshObserver.ts b/src/util/functions/groundMeshObserver.ts index 91b6c47..9b5636e 100644 --- a/src/util/functions/groundMeshObserver.ts +++ b/src/util/functions/groundMeshObserver.ts @@ -79,6 +79,9 @@ export async function groundMeshObserver(ground: AbstractMesh, rig.turnSnap = parseFloat(config.snapTurnSnap); const webController = new WebController(ground.getScene(), rig, diagramManager); + // Set XR on diagram manager so toolbox can create exit button + diagramManager.setXR(xr); + } function positionComponentsRelativeToCamera(scene: Scene, diagramManager: DiagramManager) {