Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions samples/ui/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@
createPlayerPanel() {
// Panel 2: Interactive Media Player (Top-Center)
const panel = new xb.SpatialPanel({
width: 1.5,
width: 1.3,
height: 1.25,
backgroundColor: '#00000000',
});
Expand All @@ -189,11 +189,11 @@
this.add(panel);
const grid = panel.addGrid();
// Space for orbiter
grid.addRow({weight: 0.25});
grid.addRow({weight: 0.0});
// player row
const playerRow = grid.addRow({weight: 0.75});
const playerRow = grid.addRow({weight: 1.0});
const playerPanel = playerRow.addPanel({
width: 1.0,
width: 1.2,
height: 1.2,
backgroundColor: '#21252baa',
});
Expand Down Expand Up @@ -221,7 +221,9 @@
.addIconButton({text: 'skip_next', fontSize: 0.4});
controlsRow.addCol({weight: 0.2});
}
const orbiter = playerGrid.addOrbiter();
const orbiter = playerGrid.addOrbiter({
orbiterScale: 0.1,
});
orbiter.addExitButton();
panel.updateLayouts();
}
Expand Down
112 changes: 109 additions & 3 deletions src/ui/layouts/Orbiter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,119 @@ import {Grid, GridOptions} from './Grid.js';
* as an exit button or settings icon. It typically "orbits" or remains
* attached to a corner of its parent panel, outside the main content area.
*/
export type OrbiterOptions = GridOptions;

export type OrbiterPosition =
| 'top-right'
| 'top-left'
| 'bottom-right'
| 'bottom-left'
| 'top'
| 'bottom'
| 'left'
| 'right';

export type OrbiterOptions = GridOptions & {
orbiterPosition?: OrbiterPosition;
orbiterScale?: number;
offset?: number;
elevation?: number;
};

export class Orbiter extends Grid {
orbiterPosition: OrbiterPosition;
orbiterScale: number;
offset: number;
elevation: number;

// These values are based on Material Design guidelines: https://developer.android.com/design/ui/xr/guides/spatial-ui
private static readonly BASE_OFFSET = 0.02; // put the orbiter outside of the parent panel's "draggable region" by default
private static readonly BASE_ELEVATION = 0.02; // put the orbiter at 15dp above the parent panel by default
private static readonly MAX_OUTWARD = 0.05; // avoid the orbiter being too far away from the parent panel

constructor(options: OrbiterOptions = {}) {
const {
orbiterPosition = 'top-left',
orbiterScale = 0.2,
offset = 0.0,
elevation = 0.0,
...gridOptions
} = options;

super(gridOptions);

this.orbiterPosition = orbiterPosition;
this.orbiterScale = orbiterScale;
this.offset = offset;
this.elevation = elevation;
}

init() {
super.init();
this.scale.set(this.orbiterScale, this.orbiterScale, 1.0);
this._place();
}

private _place() {
const hx = this.rangeX * 0.5;
const hy = this.rangeY * 0.5;

const rightEdge = +hx;
const leftEdge = -hx;
const topEdge = +hy;
const bottomEdge = -hy;

// Clamp edge spacing so the orbiter stays within the recommended range:
// edgeDelta == -orbiterScale / 2 corresponds to the 50% overlap boundary.
const edgeDelta = Math.max(
-this.orbiterScale / 2,
Math.min(Orbiter.MAX_OUTWARD, Orbiter.BASE_OFFSET + this.offset)
);

// Clamp elevation so the orbiter remains in front of the parent panel and doesn’t float excessively.
const zDelta = Math.max(
0,
Math.min(Orbiter.MAX_OUTWARD, Orbiter.BASE_ELEVATION + this.elevation)
);

let x = 0.0;
let y = 0.0;

switch (this.orbiterPosition) {
case 'top':
x = 0.0;
y = topEdge + this.orbiterScale / 2 + edgeDelta;
break;
case 'bottom':
x = 0.0;
y = bottomEdge - this.orbiterScale / 2 - edgeDelta;
break;
case 'right':
x = rightEdge + this.orbiterScale / 2 + edgeDelta;
y = 0.0;
break;
case 'left':
x = leftEdge - this.orbiterScale / 2 - edgeDelta;
y = 0.0;
break;
case 'top-right':
x = rightEdge - this.orbiterScale / 2;
y = topEdge + this.orbiterScale / 2 + edgeDelta;
break;
case 'top-left':
x = leftEdge + this.orbiterScale / 2;
y = topEdge + this.orbiterScale / 2 + edgeDelta;
break;
case 'bottom-right':
x = rightEdge - this.orbiterScale / 2;
y = bottomEdge - this.orbiterScale / 2 - edgeDelta;
break;
case 'bottom-left':
x = leftEdge + this.orbiterScale / 2;
y = bottomEdge - this.orbiterScale / 2 - edgeDelta;
break;
}

this.position.set(-0.45 * this.rangeX, 0.7 * this.rangeY, this.position.z);
this.scale.set(0.2, 0.2, 1.0);
const z = this.position.z + zDelta;
this.position.set(x, y, z);
}
}