Implement Phase 7: Add Label Rendering Mode controls to VR config panel

Complete the final phase of VR configuration panel implementation by adding
Label Rendering Mode selection controls. This allows users to configure how
diagram labels are rendered in the VR environment.

Changes:
- Add radio-style buttons for 4 label rendering modes:
  - Fixed: Static orientation
  - Billboard: Always faces camera (default)
  - Dynamic: Coming soon (disabled)
  - Distance-based: Coming soon (disabled)
- Implement disabled styling for future modes (gray background, 50% opacity)
- Wire up to appConfigInstance.setLabelRenderingMode()
- Update UI when config changes from external sources (2D modal)
- Add updateLabelModeButtonStates() for visual state management

This completes all 7 phases of VRCONFIGPLAN.md implementation.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Michael Mainguy 2025-11-18 15:05:13 -06:00
parent 3002181160
commit 5091ca0bab

View File

@ -17,7 +17,7 @@ import {
Vector3 Vector3
} from "@babylonjs/core"; } from "@babylonjs/core";
import {appConfigInstance} from "../util/appConfig"; import {appConfigInstance} from "../util/appConfig";
import {AppConfigType} from "../util/appConfigType"; import {AppConfigType, LabelRenderingMode} from "../util/appConfigType";
import log from "loglevel"; import log from "loglevel";
import {DefaultScene} from "../defaultScene"; import {DefaultScene} from "../defaultScene";
import {Handle} from "../objects/handle"; import {Handle} from "../objects/handle";
@ -69,6 +69,9 @@ export class VRConfigPanel {
private _snapTurnToggle: Button; private _snapTurnToggle: Button;
private _snapTurnButtons: Map<number, Button> = new Map(); private _snapTurnButtons: Map<number, Button> = new Map();
// Label Rendering Mode UI controls
private _labelModeButtons: Map<LabelRenderingMode, Button> = new Map();
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');
@ -252,6 +255,7 @@ export class VRConfigPanel {
// Section 5: Label Rendering Mode // Section 5: Label Rendering Mode
this._labelModeContent = this.createSectionContainer("Label Rendering Mode"); this._labelModeContent = this.createSectionContainer("Label Rendering Mode");
this.buildLabelModeControls();
} }
/** /**
@ -698,6 +702,91 @@ export class VRConfigPanel {
}); });
} }
/**
* Build Label Rendering Mode controls
*/
private buildLabelModeControls(): void {
const currentMode = appConfigInstance.current.labelRenderingMode || 'billboard';
// Create vertical container for mode buttons
const modesContainer = new StackPanel("labelModesContainer");
modesContainer.isVertical = true;
modesContainer.width = "100%";
modesContainer.adaptHeightToChildren = true;
this._labelModeContent.addControl(modesContainer);
// Define label rendering modes
const modes: Array<{ value: LabelRenderingMode, label: string, disabled: boolean }> = [
{ value: 'fixed', label: 'Fixed', disabled: false },
{ value: 'billboard', label: 'Billboard (Always Face Camera)', disabled: false },
{ value: 'dynamic', label: 'Dynamic (Coming Soon)', disabled: true },
{ value: 'distance', label: 'Distance-based (Coming Soon)', disabled: true }
];
// Create button for each mode
modes.forEach((mode) => {
const isSelected = currentMode === mode.value;
const btn = Button.CreateSimpleButton(`labelMode_${mode.value}`, mode.label);
btn.width = "100%";
btn.height = "70px";
btn.fontSize = 42;
btn.color = "white";
btn.paddingBottom = "10px";
btn.thickness = 0;
btn.cornerRadius = 8;
// Set initial appearance
if (mode.disabled) {
btn.background = "#222222";
btn.alpha = 0.5;
btn.color = "#888888";
} else if (isSelected) {
btn.background = "#4A9EFF";
btn.fontWeight = "bold";
} else {
btn.background = "#333333";
}
// Click handler (only for enabled modes)
if (!mode.disabled) {
btn.onPointerClickObservable.add(() => {
appConfigInstance.setLabelRenderingMode(mode.value);
this.updateLabelModeButtonStates(mode.value);
});
}
this._labelModeButtons.set(mode.value, btn);
modesContainer.addControl(btn);
});
}
/**
* Update Label Rendering Mode button visual states
*/
private updateLabelModeButtonStates(selectedValue: LabelRenderingMode): void {
this._labelModeButtons.forEach((btn, value) => {
const isSelected = selectedValue === value;
const isDisabled = value === 'dynamic' || value === 'distance';
if (isDisabled) {
// Keep disabled appearance
btn.background = "#222222";
btn.alpha = 0.5;
btn.color = "#888888";
btn.fontWeight = "normal";
} else if (isSelected) {
btn.background = "#4A9EFF";
btn.fontWeight = "bold";
btn.color = "white";
} else {
btn.background = "#333333";
btn.fontWeight = "normal";
btn.color = "white";
}
});
}
/** /**
* Set up parenting to platform for world movement tracking * Set up parenting to platform for world movement tracking
*/ */
@ -755,7 +844,9 @@ export class VRConfigPanel {
this.updateSnapTurnButtonStates(config.turnSnap); this.updateSnapTurnButtonStates(config.turnSnap);
} }
// Phase 7 will add: // Update Label Rendering Mode UI
// - Label rendering mode UI update if (this._labelModeButtons.size > 0 && config.labelRenderingMode) {
this.updateLabelModeButtonStates(config.labelRenderingMode);
}
} }
} }