Skip to content

Commit 8a61d96

Browse files
Open feat[client]: Supports show/hide control plane
1 parent 052a024 commit 8a61d96

File tree

7 files changed

+150
-49
lines changed

7 files changed

+150
-49
lines changed

deps/cloudxr/webxr_client/helpers/react/utils.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ export type ControlPanelPosition = 'left' | 'center' | 'right';
2424
/** React UI options (e.g. in-XR control panel position). */
2525
export interface ReactUIConfig {
2626
controlPanelPosition?: ControlPanelPosition;
27+
/** When true, the control panel is hidden at immersive XR enter (small “show control panel” control only). */
28+
panelHiddenAtStart?: boolean;
2729
}
2830

2931
const CONTROL_PANEL_POSITIONS: readonly ControlPanelPosition[] = ['left', 'center', 'right'];

deps/cloudxr/webxr_client/helpers/utils.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -155,9 +155,6 @@ export interface CloudXRConfig {
155155
/** Optional device profile identifier used by the examples UI */
156156
deviceProfileId?: string;
157157

158-
/** Application identifier string for the CloudXR session */
159-
app: string;
160-
161158
/** Type of server being connected to */
162159
serverType: string;
163160

deps/cloudxr/webxr_client/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545
"style-loader": "^3.3.3",
4646
"ts-loader": "^9.5.1",
4747
"typescript": "^5.8.2",
48-
"webpack-dev-server": "^5.2.1",
49-
"webpack-cli": "^6.0.1"
48+
"webpack-cli": "^6.0.1",
49+
"webpack-dev-server": "^5.2.1"
5050
}
5151
}

deps/cloudxr/webxr_client/src/App.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -772,6 +772,8 @@ function App() {
772772
onStartTeleop={handleStartTeleop}
773773
onDisconnect={handleDisconnect}
774774
onResetTeleop={handleResetTeleop}
775+
isXRMode={isXRMode}
776+
panelHiddenAtStart={config.panelHiddenAtStart ?? false}
775777
serverAddress={serverAddress || config.serverIP}
776778
sessionStatus={sessionStatus}
777779
playLabel={

deps/cloudxr/webxr_client/src/CloudXR2DUI.tsx

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -115,8 +115,8 @@ export class CloudXR2DUI {
115115
private serverTypeSelect!: HTMLSelectElement;
116116
/** Dropdown to select device profile */
117117
private deviceProfileSelect!: HTMLSelectElement;
118-
/** Dropdown to select application type */
119-
private appSelect!: HTMLSelectElement;
118+
/** Whether the control panel starts hidden when immersive XR begins */
119+
private panelHiddenAtStartSelect!: HTMLSelectElement;
120120
/** Dropdown to select reference space for XR tracking */
121121
private referenceSpaceSelect!: HTMLSelectElement;
122122
/** Input for XR reference space X offset (cm) */
@@ -235,7 +235,7 @@ export class CloudXR2DUI {
235235
this.getElement<HTMLSelectElement>('useQuestColorWorkaround');
236236
this.serverTypeSelect = this.getElement<HTMLSelectElement>('serverType');
237237
this.deviceProfileSelect = this.getElement<HTMLSelectElement>('deviceProfile');
238-
this.appSelect = this.getElement<HTMLSelectElement>('app');
238+
this.panelHiddenAtStartSelect = this.getElement<HTMLSelectElement>('panelHiddenAtStart');
239239
this.referenceSpaceSelect = this.getElement<HTMLSelectElement>('referenceSpace');
240240
this.xrOffsetXInput = this.getElement<HTMLInputElement>('xrOffsetX');
241241
this.xrOffsetYInput = this.getElement<HTMLInputElement>('xrOffsetY');
@@ -291,8 +291,8 @@ export class CloudXR2DUI {
291291
codec: 'av1',
292292
immersiveMode: 'ar',
293293
deviceProfileId: 'custom',
294-
app: 'generic',
295294
serverType: 'manual',
295+
panelHiddenAtStart: false,
296296
proxyUrl: '',
297297
referenceSpaceType: 'auto',
298298
controlPanelPosition: 'center',
@@ -326,7 +326,7 @@ export class CloudXR2DUI {
326326
enableLocalStorage(this.useQuestColorWorkaroundSelect, 'useQuestColorWorkaround');
327327
enableLocalStorage(this.immersiveSelect, 'immersiveMode');
328328
enableLocalStorage(this.deviceProfileSelect, 'deviceProfile');
329-
enableLocalStorage(this.appSelect, 'app');
329+
enableLocalStorage(this.panelHiddenAtStartSelect, 'panelHiddenAtStart');
330330
enableLocalStorage(this.referenceSpaceSelect, 'referenceSpace');
331331
enableLocalStorage(this.xrOffsetXInput, 'xrOffsetX');
332332
enableLocalStorage(this.xrOffsetYInput, 'xrOffsetY');
@@ -417,7 +417,7 @@ export class CloudXR2DUI {
417417
addListener(this.enableTexSubImage2DSelect, 'change', onProfileLinkedChange);
418418
addListener(this.useQuestColorWorkaroundSelect, 'change', onProfileLinkedChange);
419419
addListener(this.immersiveSelect, 'change', updateConfig);
420-
addListener(this.appSelect, 'change', updateConfig);
420+
addListener(this.panelHiddenAtStartSelect, 'change', updateConfig);
421421
addListener(this.referenceSpaceSelect, 'change', updateConfig);
422422
addListener(this.xrOffsetXInput, 'input', updateConfig);
423423
addListener(this.xrOffsetXInput, 'change', updateConfig);
@@ -572,7 +572,6 @@ export class CloudXR2DUI {
572572
immersiveMode:
573573
(this.immersiveSelect.value as 'ar' | 'vr') || this.getDefaultConfiguration().immersiveMode,
574574
deviceProfileId: resolveDeviceProfileId(this.deviceProfileSelect.value),
575-
app: this.appSelect.value || this.getDefaultConfiguration().app,
576575
serverType: this.serverTypeSelect.value || this.getDefaultConfiguration().serverType,
577576
proxyUrl: this.proxyUrlInput.value || this.getDefaultConfiguration().proxyUrl,
578577
referenceSpaceType:
@@ -606,6 +605,7 @@ export class CloudXR2DUI {
606605
return !isNaN(v) ? v : undefined;
607606
})(),
608607
hideControllerModel: this.controllerModelVisibilitySelect.value === 'hide',
608+
panelHiddenAtStart: this.panelHiddenAtStartSelect.value === 'true',
609609
};
610610

611611
this.currentConfiguration = newConfiguration;

deps/cloudxr/webxr_client/src/CloudXRUI.tsx

Lines changed: 125 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
* - Configurable position and rotation in world space for flexible UI placement
2828
* - Draggable handle bar for repositioning the UI in 3D space
2929
* - Face-camera rotation for optimal viewing angle (Y-axis only)
30+
* - Panel depth: full control panel, compact (when "minimize on play" and teleop active), or hidden
31+
* (semi-transparent Show + slim drag handle).
3032
*
3133
* The UI is positioned in 3D space and designed for VR/AR interaction with
3234
* visual feedback and clear button labeling. All interactions are passed
@@ -66,6 +68,10 @@ interface CloudXRUIProps {
6668
streamingFpsText?: ReadonlySignal<string>;
6769
/** Computed signal for pose-to-render latency text - updates without React re-render */
6870
poseToRenderText?: ReadonlySignal<string>;
71+
/** From settings: hide control panel when immersive XR begins. */
72+
panelHiddenAtStart?: boolean;
73+
/** Immersive XR active; used to apply panelHiddenAtStart on session enter. */
74+
isXRMode?: boolean;
6975
}
7076

7177
// Reusable objects for face-camera rotation (avoid allocations in render loop)
@@ -96,6 +102,8 @@ export default function CloudXR3DUI({
96102
renderFpsText,
97103
streamingFpsText,
98104
poseToRenderText,
105+
panelHiddenAtStart = false,
106+
isXRMode = false,
99107
}: CloudXRUIProps) {
100108
const MINIMIZE_ON_PLAY_KEY = 'cxr.isaac.minimizeOnPlay';
101109

@@ -112,6 +120,17 @@ export default function CloudXR3DUI({
112120
}
113121
});
114122

123+
/** Control panel hidden: small Show control (see settings to hide control panel on XR enter). */
124+
const [panelHidden, setPanelHidden] = useState(false);
125+
const prevXRMode = useRef(false);
126+
127+
useEffect(() => {
128+
if (isXRMode && !prevXRMode.current) {
129+
setPanelHidden(panelHiddenAtStart);
130+
}
131+
prevXRMode.current = isXRMode;
132+
}, [isXRMode, panelHiddenAtStart]);
133+
115134
// Keep localStorage in sync when the user toggles the option.
116135
useEffect(() => {
117136
try {
@@ -125,7 +144,10 @@ export default function CloudXR3DUI({
125144
}
126145
}, [position[0], position[1], position[2]]);
127146

128-
const isMinimized = minimizeOnPlay && playInProgress;
147+
const isCompact = minimizeOnPlay && playInProgress;
148+
const isMinimizedLayout = isCompact || panelHidden;
149+
const handleWidth = panelHidden ? 0.12 : isCompact ? 0.28 : 1.0;
150+
const handleY = panelHidden ? -0.065 : isCompact ? -0.15 : -0.42;
129151

130152
// Face-camera rotation: smoothly rotate UI to face the user (Y-axis only)
131153
useFrame((state, dt) => {
@@ -165,40 +187,63 @@ export default function CloudXR3DUI({
165187
>
166188
<mesh
167189
ref={handleRef}
168-
position={[0, isMinimized ? -0.15 : -0.42, 0.01]}
190+
position={[0, handleY, 0.01]}
169191
onPointerEnter={() => {
170192
const mat = handleRef.current?.material as MeshStandardMaterial | undefined;
171193
if (mat) {
172194
mat.color.copy(HANDLE_COLOR_HOVER);
173-
mat.opacity = 0.9;
195+
mat.opacity = panelHidden ? 0.55 : 0.9;
174196
}
175197
}}
176198
onPointerLeave={() => {
177199
const mat = handleRef.current?.material as MeshStandardMaterial | undefined;
178200
if (mat) {
179201
mat.color.copy(HANDLE_COLOR_DEFAULT);
180-
mat.opacity = 0.6;
202+
mat.opacity = panelHidden ? 0.35 : 0.6;
181203
}
182204
}}
183205
>
184-
<boxGeometry args={[isMinimized ? 0.28 : 1.0, 0.05, 0.02]} />
185-
<meshStandardMaterial color="#666666" transparent opacity={0.6} roughness={0.5} />
206+
<boxGeometry args={[handleWidth, panelHidden ? 0.035 : 0.05, 0.02]} />
207+
<meshStandardMaterial
208+
color="#666666"
209+
transparent
210+
opacity={panelHidden ? 0.35 : 0.6}
211+
roughness={0.5}
212+
/>
186213
</mesh>
187214
</Handle>
188215

189216
<Container
190217
pixelSize={0.001}
191-
width={isMinimized ? 520 : 2000}
192-
height={isMinimized ? 320 : 1400}
218+
width={panelHidden ? 128 : isCompact ? 520 : 2000}
219+
height={panelHidden ? 128 : isCompact ? 320 : 1400}
193220
alignItems="center"
194221
justifyContent="center"
195222
pointerEvents="auto"
196-
padding={isMinimized ? 24 : 40}
197-
sizeX={isMinimized ? 0.87 : 3.33}
198-
sizeY={isMinimized ? 0.53 : 2.33}
223+
padding={panelHidden ? 0 : isCompact ? 24 : 40}
224+
sizeX={panelHidden ? 0.2 : isCompact ? 0.87 : 3.33}
225+
sizeY={panelHidden ? 0.2 : isCompact ? 0.53 : 2.33}
199226
flexDirection="column"
200227
>
201-
{isMinimized ? (
228+
{panelHidden ? (
229+
<Button
230+
{...xrButton('show-panel', () => setPanelHidden(false))}
231+
variant="default"
232+
width={112}
233+
height={112}
234+
borderRadius={56}
235+
backgroundColor="rgba(90, 130, 210, 0.42)"
236+
hover={{
237+
backgroundColor: 'rgba(90, 130, 210, 0.72)',
238+
borderColor: 'rgba(255, 255, 255, 0.6)',
239+
borderWidth: 2,
240+
}}
241+
>
242+
<Text fontSize={26} color="rgba(255, 255, 255, 0.95)" fontWeight="bold">
243+
Show
244+
</Text>
245+
</Button>
246+
) : isCompact ? (
202247
<Container
203248
width="100%"
204249
flexDirection="column"
@@ -230,26 +275,51 @@ export default function CloudXR3DUI({
230275
</Text>
231276
</Container>
232277
</Button>
233-
<Button
234-
{...xrButton('reset-min', onResetTeleop)}
235-
variant="default"
236-
width={400}
237-
height={80}
238-
borderRadius={24}
239-
backgroundColor="rgba(220, 220, 220, 0.9)"
240-
hover={{
241-
backgroundColor: 'rgba(100, 150, 255, 1)',
242-
borderColor: 'white',
243-
borderWidth: 2,
244-
}}
278+
<Container
279+
flexDirection="row"
280+
gap={14}
281+
alignItems="center"
282+
justifyContent="center"
283+
width="100%"
245284
>
246-
<Container flexDirection="row" alignItems="center" gap={8}>
247-
<Image src="./arrow-uturn-left.svg" width={40} height={40} />
248-
<Text fontSize={36} color="black" fontWeight="medium">
249-
Reset
285+
<Button
286+
{...xrButton('reset-min', onResetTeleop)}
287+
variant="default"
288+
width={292}
289+
height={80}
290+
borderRadius={24}
291+
backgroundColor="rgba(220, 220, 220, 0.9)"
292+
hover={{
293+
backgroundColor: 'rgba(100, 150, 255, 1)',
294+
borderColor: 'white',
295+
borderWidth: 2,
296+
}}
297+
>
298+
<Container flexDirection="row" alignItems="center" gap={8}>
299+
<Image src="./arrow-uturn-left.svg" width={40} height={40} />
300+
<Text fontSize={36} color="black" fontWeight="medium">
301+
Reset
302+
</Text>
303+
</Container>
304+
</Button>
305+
<Button
306+
{...xrButton('hide-panel-compact', () => setPanelHidden(true))}
307+
variant="default"
308+
width={94}
309+
height={80}
310+
borderRadius={20}
311+
backgroundColor="rgba(70, 75, 90, 0.55)"
312+
hover={{
313+
backgroundColor: 'rgba(90, 95, 115, 0.85)',
314+
borderColor: 'rgba(255, 255, 255, 0.5)',
315+
borderWidth: 2,
316+
}}
317+
>
318+
<Text fontSize={26} color="rgba(255, 255, 255, 0.92)" fontWeight="medium">
319+
Hide
250320
</Text>
251-
</Container>
252-
</Button>
321+
</Button>
322+
</Container>
253323
</Container>
254324
) : (
255325
<Container
@@ -422,9 +492,10 @@ export default function CloudXR3DUI({
422492
)}
423493
</Container>
424494
<Text fontSize={30} color="rgba(220, 220, 220, 1)">
425-
Minimize on play
495+
Minimize on play (compact controls)
426496
</Text>
427497
</Container>
498+
428499
</Container>
429500

430501
{/* Right Column - Controls */}
@@ -565,7 +636,12 @@ export default function CloudXR3DUI({
565636
</Container>
566637

567638
{/* Bottom Row */}
568-
<Container flexDirection="row" justifyContent="center">
639+
<Container
640+
flexDirection="row"
641+
justifyContent="center"
642+
alignItems="center"
643+
gap={18}
644+
>
569645
<Button
570646
{...xrButton('disconnect', onDisconnect)}
571647
variant="destructive"
@@ -586,6 +662,23 @@ export default function CloudXR3DUI({
586662
</Text>
587663
</Container>
588664
</Button>
665+
<Button
666+
{...xrButton('hide-panel-full', () => setPanelHidden(true))}
667+
variant="default"
668+
width={100}
669+
height={90}
670+
borderRadius={22}
671+
backgroundColor="rgba(70, 75, 90, 0.55)"
672+
hover={{
673+
backgroundColor: 'rgba(90, 95, 115, 0.88)',
674+
borderColor: 'rgba(255, 255, 255, 0.5)',
675+
borderWidth: 2,
676+
}}
677+
>
678+
<Text fontSize={28} color="rgba(255, 255, 255, 0.92)" fontWeight="medium">
679+
Hide
680+
</Text>
681+
</Button>
589682
</Container>
590683
</Container>
591684
</Container>

deps/cloudxr/webxr_client/src/index.html

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -524,11 +524,6 @@ <h2 class="settings-title">Settings</h2>
524524
</div>
525525
</div>
526526

527-
<label for="app" class="input-label">Application</label>
528-
<select id="app" name="app" class="ui-input" aria-label="Select application">
529-
<option value="generic">Generic Client</option>
530-
<!-- TODO: Add other applications here -->
531-
</select>
532527
<label for="immersive" class="input-label">Immersive Mode</label>
533528
<select id="immersive" name="immersive" class="ui-input" aria-label="Select immersive mode">
534529
<option value="ar" selected>AR Immersive</option>
@@ -579,6 +574,18 @@ <h3 class="debug-title">Debug Settings</h3>
579574
</div>
580575
</div>
581576

577+
<div class="config-section">
578+
<label for="panelHiddenAtStart" class="input-label">Control panel when entering XR</label>
579+
<select id="panelHiddenAtStart" name="panelHiddenAtStart" class="ui-input config-input"
580+
aria-label="Show or hide control panel when entering immersive XR">
581+
<option value="false" selected>Show control panel</option>
582+
<option value="true">Hide control panel</option>
583+
</select>
584+
<div class="config-text">
585+
If you hide the control panel, only a small control stays visible so you can show it again.
586+
</div>
587+
</div>
588+
582589
<div class="config-section">
583590
<label class="input-label">Proxy Configuration</label>
584591
<label for="proxyUrl" class="input-label">Proxy URL</label>

0 commit comments

Comments
 (0)