Implement Phase 2 UI layout and toolbox integration for VR config panel
Phase 2: Add UI layout structure to VRConfigPanel - Create 5 section containers with titles (Location Snap, Rotation Snap, Fly Mode, Snap Turn, Label Rendering Mode) - Add visual separators between sections using Rectangle components - Style with proper padding and spacing for VR readability (60px titles, blue #4A9EFF) - Store section content containers as private properties for Phase 3-7 controls Toolbox Integration (Phase 8 partial): - Instantiate VRConfigPanel in DiagramMenuManager constructor - Add "Config" button to toolbox (bottom-left, opposite Exit VR button) - Wire up click handler to toggle panel visibility - Add B-button positioning logic to reposition panel with other UI elements - Pass DiagramMenuManager reference to Toolbox.setXR() for panel access The panel now has complete skeleton structure and can be tested in VR: - Click "Config" button on toolbox to show/hide panel - Grab handle to reposition and test ergonomics - Press B-button to auto-lower panel if too high - 2m x 1.5m panel size optimized for VR viewing distance 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
aa41895675
commit
be311e6dc8
@ -12,12 +12,14 @@ import {GroupMenu} from "../menus/groupMenu";
|
|||||||
import {ControllerEvent} from "../controllers/types/controllerEvent";
|
import {ControllerEvent} from "../controllers/types/controllerEvent";
|
||||||
import {ControllerEventType} from "../controllers/types/controllerEventType";
|
import {ControllerEventType} from "../controllers/types/controllerEventType";
|
||||||
import {ResizeGizmo} from "../gizmos/ResizeGizmo";
|
import {ResizeGizmo} from "../gizmos/ResizeGizmo";
|
||||||
|
import {VRConfigPanel} from "../menus/vrConfigPanel";
|
||||||
|
|
||||||
|
|
||||||
export class DiagramMenuManager {
|
export class DiagramMenuManager {
|
||||||
public readonly toolbox: Toolbox;
|
public readonly toolbox: Toolbox;
|
||||||
private readonly _notifier: Observable<DiagramEvent>;
|
private readonly _notifier: Observable<DiagramEvent>;
|
||||||
private readonly _inputTextView: InputTextView;
|
private readonly _inputTextView: InputTextView;
|
||||||
|
private readonly _vrConfigPanel: VRConfigPanel;
|
||||||
private _groupMenu: GroupMenu;
|
private _groupMenu: GroupMenu;
|
||||||
private readonly _scene: Scene;
|
private readonly _scene: Scene;
|
||||||
private _logger = log.getLogger('DiagramMenuManager');
|
private _logger = log.getLogger('DiagramMenuManager');
|
||||||
@ -29,6 +31,7 @@ export class DiagramMenuManager {
|
|||||||
this._scene = DefaultScene.Scene;
|
this._scene = DefaultScene.Scene;
|
||||||
this._notifier = notifier;
|
this._notifier = notifier;
|
||||||
this._inputTextView = new InputTextView(controllerObservable);
|
this._inputTextView = new InputTextView(controllerObservable);
|
||||||
|
this._vrConfigPanel = new VRConfigPanel(this._scene);
|
||||||
//this.configMenu = new ConfigMenu(config);
|
//this.configMenu = new ConfigMenu(config);
|
||||||
|
|
||||||
this._inputTextView.onTextObservable.add((evt) => {
|
this._inputTextView.onTextObservable.add((evt) => {
|
||||||
@ -62,10 +65,11 @@ export class DiagramMenuManager {
|
|||||||
if (inputY > (cameraPos.y - .2)) {
|
if (inputY > (cameraPos.y - .2)) {
|
||||||
this._inputTextView.handleMesh.position.y = localCamera.y - .2;
|
this._inputTextView.handleMesh.position.y = localCamera.y - .2;
|
||||||
}
|
}
|
||||||
const configY = this._inputTextView.handleMesh.absolutePosition.y;
|
|
||||||
/*if (configY > (cameraPos.y - .2)) {
|
const configY = this._vrConfigPanel.handleMesh.absolutePosition.y;
|
||||||
this.configMenu.handleTransformNode.position.y = localCamera.y - .2;
|
if (configY > (cameraPos.y - .2)) {
|
||||||
}*/
|
this._vrConfigPanel.handleMesh.position.y = localCamera.y - .2;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -159,6 +163,16 @@ export class DiagramMenuManager {
|
|||||||
|
|
||||||
public setXR(xr: WebXRDefaultExperience): void {
|
public setXR(xr: WebXRDefaultExperience): void {
|
||||||
this._xr = xr;
|
this._xr = xr;
|
||||||
this.toolbox.setXR(xr);
|
this.toolbox.setXR(xr, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public toggleVRConfigPanel(): void {
|
||||||
|
// Toggle visibility of VR config panel
|
||||||
|
const isEnabled = this._vrConfigPanel.handleMesh.isEnabled(false);
|
||||||
|
if (isEnabled) {
|
||||||
|
this._vrConfigPanel.hide();
|
||||||
|
} else {
|
||||||
|
this._vrConfigPanel.show();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,5 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
AdvancedDynamicTexture,
|
AdvancedDynamicTexture,
|
||||||
|
Control,
|
||||||
|
Rectangle,
|
||||||
StackPanel,
|
StackPanel,
|
||||||
TextBlock
|
TextBlock
|
||||||
} from "@babylonjs/gui";
|
} from "@babylonjs/gui";
|
||||||
@ -41,6 +43,13 @@ export class VRConfigPanel {
|
|||||||
private _configObserver: Observer<AppConfigType>;
|
private _configObserver: Observer<AppConfigType>;
|
||||||
private _mainContainer: StackPanel;
|
private _mainContainer: StackPanel;
|
||||||
|
|
||||||
|
// Section content containers (filled in Phases 3-7)
|
||||||
|
private _locationSnapContent: StackPanel;
|
||||||
|
private _rotationSnapContent: StackPanel;
|
||||||
|
private _flyModeContent: StackPanel;
|
||||||
|
private _snapTurnContent: StackPanel;
|
||||||
|
private _labelModeContent: StackPanel;
|
||||||
|
|
||||||
constructor(scene: Scene) {
|
constructor(scene: Scene) {
|
||||||
this._scene = scene || DefaultScene.Scene;
|
this._scene = scene || DefaultScene.Scene;
|
||||||
this._logger.debug('VRConfigPanel constructor called');
|
this._logger.debug('VRConfigPanel constructor called');
|
||||||
@ -188,12 +197,89 @@ export class VRConfigPanel {
|
|||||||
title.paddingBottom = "40px";
|
title.paddingBottom = "40px";
|
||||||
this._mainContainer.addControl(title);
|
this._mainContainer.addControl(title);
|
||||||
|
|
||||||
|
// Build configuration sections
|
||||||
|
this.buildConfigSections();
|
||||||
|
|
||||||
// Parent handle to platform when available
|
// Parent handle to platform when available
|
||||||
this.setupPlatformParenting();
|
this.setupPlatformParenting();
|
||||||
|
|
||||||
this._logger.debug('VR config panel built successfully');
|
this._logger.debug('VR config panel built successfully');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build all configuration sections with layout structure
|
||||||
|
*/
|
||||||
|
private buildConfigSections(): void {
|
||||||
|
// Section 1: Location Snap
|
||||||
|
this._locationSnapContent = this.createSectionContainer("Location Snap");
|
||||||
|
this.addSeparator();
|
||||||
|
|
||||||
|
// Section 2: Rotation Snap
|
||||||
|
this._rotationSnapContent = this.createSectionContainer("Rotation Snap");
|
||||||
|
this.addSeparator();
|
||||||
|
|
||||||
|
// Section 3: Fly Mode
|
||||||
|
this._flyModeContent = this.createSectionContainer("Fly Mode");
|
||||||
|
this.addSeparator();
|
||||||
|
|
||||||
|
// Section 4: Snap Turn
|
||||||
|
this._snapTurnContent = this.createSectionContainer("Snap Turn");
|
||||||
|
this.addSeparator();
|
||||||
|
|
||||||
|
// Section 5: Label Rendering Mode
|
||||||
|
this._labelModeContent = this.createSectionContainer("Label Rendering Mode");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a section container with title
|
||||||
|
*/
|
||||||
|
private createSectionContainer(sectionTitle: string): StackPanel {
|
||||||
|
// Create section container
|
||||||
|
const section = new StackPanel(`section_${sectionTitle.replace(/\s+/g, '_')}`);
|
||||||
|
section.isVertical = true;
|
||||||
|
section.width = "100%";
|
||||||
|
section.height = "auto";
|
||||||
|
section.paddingTop = "20px";
|
||||||
|
section.paddingBottom = "20px";
|
||||||
|
this._mainContainer.addControl(section);
|
||||||
|
|
||||||
|
// Add section title
|
||||||
|
const titleText = new TextBlock(`title_${sectionTitle.replace(/\s+/g, '_')}`, sectionTitle);
|
||||||
|
titleText.height = "60px";
|
||||||
|
titleText.fontSize = 60;
|
||||||
|
titleText.color = "#4A9EFF"; // Bright blue for section titles
|
||||||
|
titleText.textHorizontalAlignment = TextBlock.HORIZONTAL_ALIGNMENT_LEFT;
|
||||||
|
titleText.paddingLeft = "20px";
|
||||||
|
titleText.paddingBottom = "10px";
|
||||||
|
section.addControl(titleText);
|
||||||
|
|
||||||
|
// Create content container for controls (to be filled in subsequent phases)
|
||||||
|
const contentContainer = new StackPanel(`content_${sectionTitle.replace(/\s+/g, '_')}`);
|
||||||
|
contentContainer.isVertical = true;
|
||||||
|
contentContainer.width = "100%";
|
||||||
|
contentContainer.height = "auto";
|
||||||
|
contentContainer.paddingLeft = "40px";
|
||||||
|
contentContainer.paddingRight = "40px";
|
||||||
|
section.addControl(contentContainer);
|
||||||
|
|
||||||
|
return contentContainer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a visual separator line between sections
|
||||||
|
*/
|
||||||
|
private addSeparator(): void {
|
||||||
|
const separator = new Rectangle(`separator_${Date.now()}`);
|
||||||
|
separator.height = "2px";
|
||||||
|
separator.width = "90%";
|
||||||
|
separator.thickness = 0;
|
||||||
|
separator.background = "#444444"; // Dark gray separator
|
||||||
|
separator.horizontalAlignment = Control.HORIZONTAL_ALIGNMENT_CENTER;
|
||||||
|
separator.paddingTop = "10px";
|
||||||
|
separator.paddingBottom = "10px";
|
||||||
|
this._mainContainer.addControl(separator);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set up parenting to platform for world movement tracking
|
* Set up parenting to platform for world movement tracking
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -30,6 +30,7 @@ export class Toolbox {
|
|||||||
private readonly _scene: Scene;
|
private readonly _scene: Scene;
|
||||||
private _xr?: WebXRDefaultExperience;
|
private _xr?: WebXRDefaultExperience;
|
||||||
private _renderModeDisplay?: Button;
|
private _renderModeDisplay?: Button;
|
||||||
|
private _diagramMenuManager?: any; // Import would create circular dependency
|
||||||
|
|
||||||
constructor(readyObservable: Observable<boolean>) {
|
constructor(readyObservable: Observable<boolean>) {
|
||||||
this._scene = DefaultScene.Scene;
|
this._scene = DefaultScene.Scene;
|
||||||
@ -48,8 +49,9 @@ export class Toolbox {
|
|||||||
Toolbox._instance = this;
|
Toolbox._instance = this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public setXR(xr: WebXRDefaultExperience): void {
|
public setXR(xr: WebXRDefaultExperience, diagramMenuManager?: any): void {
|
||||||
this._xr = xr;
|
this._xr = xr;
|
||||||
|
this._diagramMenuManager = diagramMenuManager;
|
||||||
this.setupXRButton();
|
this.setupXRButton();
|
||||||
}
|
}
|
||||||
private index = 0;
|
private index = 0;
|
||||||
@ -176,6 +178,26 @@ export class Toolbox {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Create config button next to exit button
|
||||||
|
if (this._diagramMenuManager) {
|
||||||
|
const configButton = Button.CreateButton("config", "config", this._scene, {});
|
||||||
|
|
||||||
|
// Position button at bottom-left of toolbox, opposite the exit button
|
||||||
|
configButton.transform.position.x = -0.5; // Left side
|
||||||
|
configButton.transform.position.y = -0.35; // Below color grid (same as exit)
|
||||||
|
configButton.transform.position.z = 0; // Coplanar with toolbox
|
||||||
|
configButton.transform.rotation.y = Math.PI; // Flip 180° to face correctly
|
||||||
|
configButton.transform.scaling = new Vector3(.2, .2, .2); // Match exit button size
|
||||||
|
configButton.transform.parent = this._toolboxBaseNode;
|
||||||
|
|
||||||
|
configButton.onPointerObservable.add((evt) => {
|
||||||
|
this._logger.debug('Config button clicked', evt);
|
||||||
|
if (evt.sourceEvent.type == 'pointerdown') {
|
||||||
|
this._diagramMenuManager.toggleVRConfigPanel();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Create rendering mode button that cycles through modes
|
// Create rendering mode button that cycles through modes
|
||||||
this.createRenderModeButton();
|
this.createRenderModeButton();
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user