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 {ControllerEventType} from "../controllers/types/controllerEventType";
|
||||
import {ResizeGizmo} from "../gizmos/ResizeGizmo";
|
||||
import {VRConfigPanel} from "../menus/vrConfigPanel";
|
||||
|
||||
|
||||
export class DiagramMenuManager {
|
||||
public readonly toolbox: Toolbox;
|
||||
private readonly _notifier: Observable<DiagramEvent>;
|
||||
private readonly _inputTextView: InputTextView;
|
||||
private readonly _vrConfigPanel: VRConfigPanel;
|
||||
private _groupMenu: GroupMenu;
|
||||
private readonly _scene: Scene;
|
||||
private _logger = log.getLogger('DiagramMenuManager');
|
||||
@ -29,6 +31,7 @@ export class DiagramMenuManager {
|
||||
this._scene = DefaultScene.Scene;
|
||||
this._notifier = notifier;
|
||||
this._inputTextView = new InputTextView(controllerObservable);
|
||||
this._vrConfigPanel = new VRConfigPanel(this._scene);
|
||||
//this.configMenu = new ConfigMenu(config);
|
||||
|
||||
this._inputTextView.onTextObservable.add((evt) => {
|
||||
@ -62,10 +65,11 @@ export class DiagramMenuManager {
|
||||
if (inputY > (cameraPos.y - .2)) {
|
||||
this._inputTextView.handleMesh.position.y = localCamera.y - .2;
|
||||
}
|
||||
const configY = this._inputTextView.handleMesh.absolutePosition.y;
|
||||
/*if (configY > (cameraPos.y - .2)) {
|
||||
this.configMenu.handleTransformNode.position.y = localCamera.y - .2;
|
||||
}*/
|
||||
|
||||
const configY = this._vrConfigPanel.handleMesh.absolutePosition.y;
|
||||
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 {
|
||||
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 {
|
||||
AdvancedDynamicTexture,
|
||||
Control,
|
||||
Rectangle,
|
||||
StackPanel,
|
||||
TextBlock
|
||||
} from "@babylonjs/gui";
|
||||
@ -41,6 +43,13 @@ export class VRConfigPanel {
|
||||
private _configObserver: Observer<AppConfigType>;
|
||||
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) {
|
||||
this._scene = scene || DefaultScene.Scene;
|
||||
this._logger.debug('VRConfigPanel constructor called');
|
||||
@ -188,12 +197,89 @@ export class VRConfigPanel {
|
||||
title.paddingBottom = "40px";
|
||||
this._mainContainer.addControl(title);
|
||||
|
||||
// Build configuration sections
|
||||
this.buildConfigSections();
|
||||
|
||||
// Parent handle to platform when available
|
||||
this.setupPlatformParenting();
|
||||
|
||||
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
|
||||
*/
|
||||
|
||||
@ -30,6 +30,7 @@ export class Toolbox {
|
||||
private readonly _scene: Scene;
|
||||
private _xr?: WebXRDefaultExperience;
|
||||
private _renderModeDisplay?: Button;
|
||||
private _diagramMenuManager?: any; // Import would create circular dependency
|
||||
|
||||
constructor(readyObservable: Observable<boolean>) {
|
||||
this._scene = DefaultScene.Scene;
|
||||
@ -48,8 +49,9 @@ export class Toolbox {
|
||||
Toolbox._instance = this;
|
||||
}
|
||||
|
||||
public setXR(xr: WebXRDefaultExperience): void {
|
||||
public setXR(xr: WebXRDefaultExperience, diagramMenuManager?: any): void {
|
||||
this._xr = xr;
|
||||
this._diagramMenuManager = diagramMenuManager;
|
||||
this.setupXRButton();
|
||||
}
|
||||
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
|
||||
this.createRenderModeButton();
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user