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
5 changes: 5 additions & 0 deletions accessibility/keyboardui.js
Original file line number Diff line number Diff line change
Expand Up @@ -626,6 +626,11 @@ function getShortcuts() {
keys: `F`,
category: translate('shortcut_category_gizmos'),
},
{
label: translate('shortcut_toggle_hud'),
keys: `O`,
category: translate('shortcut_category_gizmos'),
},
{
label: translate('shortcut_quick_colour'),
keys: `C`,
Expand Down
3 changes: 3 additions & 0 deletions locale/de.js
Original file line number Diff line number Diff line change
Expand Up @@ -1274,6 +1274,9 @@ export default {
shortcut_lock_transform: 'Transformation auf Achse einschränken',
shortcut_transform_3d: 'In 3D transformieren',
shortcut_focus_camera: 'Kamera auf Objekt ausrichten',
shortcut_toggle_hud: 'Bildschirmsteuerung ein-/ausblenden', // machine
hud_hidden: 'Bildschirmsteuerung ausgeblendet', // machine
hud_shown: 'Bildschirmsteuerung eingeblendet', // machine
shortcut_quick_colour: 'Schnellfarbauswahl im Farbwähler',
shortcut_delete_object: 'Objekt löschen',

Expand Down
3 changes: 3 additions & 0 deletions locale/en.js
Original file line number Diff line number Diff line change
Expand Up @@ -1397,6 +1397,9 @@ export default {
shortcut_lock_transform: 'Lock transform to axis',
shortcut_transform_3d: 'Transform in 3D',
shortcut_focus_camera: 'Focus camera on object',
shortcut_toggle_hud: 'Show/hide on-screen controls',
hud_hidden: 'On-screen controls hidden',
hud_shown: 'On-screen controls shown',
shortcut_quick_colour: 'Quick use colour in colour picker',
shortcut_delete_object: 'Delete object',

Expand Down
3 changes: 3 additions & 0 deletions locale/es.js
Original file line number Diff line number Diff line change
Expand Up @@ -1342,6 +1342,9 @@ export default {
shortcut_lock_transform: 'Bloquear transformación en eje',
shortcut_transform_3d: 'Transformar en 3D',
shortcut_focus_camera: 'Enfocar cámara en objeto',
shortcut_toggle_hud: 'Mostrar/ocultar controles en pantalla', // machine
hud_hidden: 'Controles en pantalla ocultos', // machine
hud_shown: 'Controles en pantalla visibles', // machine
shortcut_quick_colour: 'Usar color rápido en el selector de color',
shortcut_delete_object: 'Eliminar objeto',

Expand Down
3 changes: 3 additions & 0 deletions locale/fr.js
Original file line number Diff line number Diff line change
Expand Up @@ -1281,6 +1281,9 @@ export default {
shortcut_lock_transform: 'Verrouiller la transformation sur un axe',
shortcut_transform_3d: 'Transformer en 3D',
shortcut_focus_camera: "Centrer la caméra sur l'objet",
shortcut_toggle_hud: "Afficher/masquer les commandes à l'écran", // machine
hud_hidden: "Commandes à l'écran masquées", // machine
hud_shown: "Commandes à l'écran affichées", // machine
shortcut_quick_colour: 'Utiliser rapidement une couleur dans le sélecteur',
shortcut_delete_object: "Supprimer l'objet",

Expand Down
3 changes: 3 additions & 0 deletions locale/it.js
Original file line number Diff line number Diff line change
Expand Up @@ -1283,6 +1283,9 @@ export default {
shortcut_lock_transform: "Blocca trasformazione sull'asse",
shortcut_transform_3d: 'Trasforma in 3D',
shortcut_focus_camera: "Centra la telecamera sull'oggetto",
shortcut_toggle_hud: 'Mostra/nascondi i comandi a schermo', // machine
hud_hidden: 'Comandi a schermo nascosti', // machine
hud_shown: 'Comandi a schermo visibili', // machine
shortcut_quick_colour: 'Uso rapido del colore nel selettore colori',
shortcut_delete_object: 'Elimina oggetto',

Expand Down
3 changes: 3 additions & 0 deletions locale/pl.js
Original file line number Diff line number Diff line change
Expand Up @@ -1274,6 +1274,9 @@ export default {
shortcut_lock_transform: 'Zablokuj transformację na osi',
shortcut_transform_3d: 'Transformuj w 3D',
shortcut_focus_camera: 'Skieruj kamerę na obiekt',
shortcut_toggle_hud: 'Pokaż/ukryj sterowanie ekranowe', // machine
hud_hidden: 'Sterowanie ekranowe ukryte', // machine
hud_shown: 'Sterowanie ekranowe widoczne', // machine
shortcut_quick_colour: 'Szybkie użycie koloru w selektorze kolorów',
shortcut_delete_object: 'Usuń obiekt',

Expand Down
3 changes: 3 additions & 0 deletions locale/pt.js
Original file line number Diff line number Diff line change
Expand Up @@ -1285,6 +1285,9 @@ export default {
shortcut_lock_transform: 'Bloquear transformação no eixo',
shortcut_transform_3d: 'Transformar em 3D',
shortcut_focus_camera: 'Focar câmara no objeto',
shortcut_toggle_hud: 'Mostrar/ocultar controlos no ecrã', // machine
hud_hidden: 'Controlos no ecrã ocultos', // machine
hud_shown: 'Controlos no ecrã visíveis', // machine
shortcut_quick_colour: 'Uso rápido de cor no seletor de cores',
shortcut_delete_object: 'Eliminar objeto',

Expand Down
3 changes: 3 additions & 0 deletions locale/sv.js
Original file line number Diff line number Diff line change
Expand Up @@ -1260,6 +1260,9 @@ export default {
shortcut_lock_transform: 'Lås transformering till axel',
shortcut_transform_3d: 'Transformera i 3D',
shortcut_focus_camera: 'Rikta kameran mot objekt',
shortcut_toggle_hud: 'Visa/dölj skärmkontroller', // machine
hud_hidden: 'Skärmkontroller dolda', // machine
hud_shown: 'Skärmkontroller visas', // machine
shortcut_quick_colour: 'Snabb färganvändning i färgväljaren',
shortcut_delete_object: 'Ta bort objekt',

Expand Down
160 changes: 144 additions & 16 deletions tests/gizmo-mobile-hud.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,34 @@ function findControl(flock, name) {
return hud?.getDescendants(false, (c) => c.name === name)[0] ?? null;
}

const COLLAPSED_KEY = 'flock-gizmo-hud-collapsed';

// A dispatched PointerEvent has no active pointer behind it, so the slider's
// canvas.setPointerCapture() throws NotFoundError here even though it is fine
// in a real browser. Stub it for the dispatch — it is incidental to what these
// tests assert.
function withoutPointerCapture(canvas, fn) {
const real = canvas.setPointerCapture;
canvas.setPointerCapture = () => {};
try {
fn();
} finally {
canvas.setPointerCapture = real;
}
}

export function runGizmoMobileHudTests(flock) {
describe('ui/gizmo-mobile-hud @gizmomobilehud', function () {
let stop;
let moves;
let axisChanges;

// The collapsed preference persists in localStorage, so without this a
// single collapse test would silently invalidate every later assertion.
beforeEach(function () {
localStorage.removeItem(COLLAPSED_KEY);
});

function make(overrides = {}) {
moves = [];
axisChanges = [];
Expand All @@ -34,6 +56,7 @@ export function runGizmoMobileHudTests(flock) {
stop = null;
flock.controlsTexture = undefined;
flock._joystickSource = undefined;
localStorage.removeItem(COLLAPSED_KEY);
});

describe('guard clause', function () {
Expand Down Expand Up @@ -207,14 +230,16 @@ export function runGizmoMobileHudTests(flock) {
make({ initialAxis: 'x', getValues: () => ({ x: 0, y: 0, z: 0 }) });
const canvas = flock.canvas;
const rect = canvas.getBoundingClientRect();
canvas.dispatchEvent(
new PointerEvent('pointerdown', {
pointerId: 2,
clientX: rect.left + rect.width / 4 + 30,
clientY: rect.bottom - 10,
})
);
canvas.dispatchEvent(new PointerEvent('pointerup', { pointerId: 2 }));
withoutPointerCapture(canvas, () => {
canvas.dispatchEvent(
new PointerEvent('pointerdown', {
pointerId: 2,
clientX: rect.left + rect.width / 4 + 30,
clientY: rect.bottom - 10,
})
);
canvas.dispatchEvent(new PointerEvent('pointerup', { pointerId: 2 }));
});
expect(moves.length).to.equal(1);
expect(moves[0][0]).to.be.above(0);
});
Expand All @@ -223,14 +248,16 @@ export function runGizmoMobileHudTests(flock) {
make({ initialAxis: 'x', getValues: () => ({ x: 0, y: 0, z: 0 }) });
const canvas = flock.canvas;
const rect = canvas.getBoundingClientRect();
canvas.dispatchEvent(
new PointerEvent('pointerdown', {
pointerId: 3,
clientX: rect.left + rect.width / 4 - 30,
clientY: rect.bottom - 10,
})
);
canvas.dispatchEvent(new PointerEvent('pointerup', { pointerId: 3 }));
withoutPointerCapture(canvas, () => {
canvas.dispatchEvent(
new PointerEvent('pointerdown', {
pointerId: 3,
clientX: rect.left + rect.width / 4 - 30,
clientY: rect.bottom - 10,
})
);
canvas.dispatchEvent(new PointerEvent('pointerup', { pointerId: 3 }));
});
expect(moves.length).to.equal(1);
expect(moves[0][0]).to.be.below(0);
});
Expand All @@ -249,5 +276,106 @@ export function runGizmoMobileHudTests(flock) {
expect(moves).to.deep.equal([]);
});
});

describe('collapse handle', function () {
it('creates the handle in slider mode', function () {
make();
expect(findControl(flock, 'gizmo-hud-toggle')).to.exist;
});

it('creates the handle in arrows mode', function () {
make({ mode: 'arrows' });
expect(findControl(flock, 'gizmo-hud-toggle')).to.exist;
});

it('starts expanded by default', function () {
make();
expect(stop.isCollapsed()).to.equal(false);
expect(findControl(flock, 'gizmoHudContainer').isVisible).to.equal(true);
});

it('tapping the handle hides the strip but keeps the handle visible', function () {
make();
const handle = findControl(flock, 'gizmo-hud-toggle');
handle.onPointerUpObservable.notifyObservers();
expect(findControl(flock, 'gizmoHudContainer').isVisible).to.equal(false);
expect(handle.isVisible).to.equal(true);
});

it('tapping the handle again restores the strip', function () {
make();
const handle = findControl(flock, 'gizmo-hud-toggle');
handle.onPointerUpObservable.notifyObservers();
handle.onPointerUpObservable.notifyObservers();
expect(findControl(flock, 'gizmoHudContainer').isVisible).to.equal(true);
});

it('stop.toggleCollapsed() reports and applies the new state', function () {
make();
expect(stop.toggleCollapsed()).to.equal(true);
expect(stop.isCollapsed()).to.equal(true);
expect(stop.toggleCollapsed()).to.equal(false);
});

it('persists the collapsed state to localStorage', function () {
make();
stop.toggleCollapsed();
expect(localStorage.getItem(COLLAPSED_KEY)).to.equal('1');
});

it('a HUD built while the preference is set starts collapsed', function () {
localStorage.setItem(COLLAPSED_KEY, '1');
make();
expect(stop.isCollapsed()).to.equal(true);
expect(findControl(flock, 'gizmoHudContainer').isVisible).to.equal(false);
});

it('does not claim slider touches while collapsed', function () {
// The slider hit-tests canvas geometry rather than using GUI
// hit-testing, so hiding the strip alone would still swallow the
// camera drag in that corner.
make({ initialAxis: 'x', getValues: () => ({ x: 0, y: 0, z: 0 }) });
stop.toggleCollapsed();
const canvas = flock.canvas;
const rect = canvas.getBoundingClientRect();
withoutPointerCapture(canvas, () => {
canvas.dispatchEvent(
new PointerEvent('pointerdown', {
pointerId: 5,
clientX: rect.left + rect.width / 4 + 30,
clientY: rect.bottom - 10,
})
);
canvas.dispatchEvent(new PointerEvent('pointerup', { pointerId: 5 }));
});
expect(moves).to.deep.equal([]);
});

it('claims slider touches again after expanding', function () {
make({ initialAxis: 'x', getValues: () => ({ x: 0, y: 0, z: 0 }) });
stop.toggleCollapsed();
stop.toggleCollapsed();
const canvas = flock.canvas;
const rect = canvas.getBoundingClientRect();
withoutPointerCapture(canvas, () => {
canvas.dispatchEvent(
new PointerEvent('pointerdown', {
pointerId: 6,
clientX: rect.left + rect.width / 4 + 30,
clientY: rect.bottom - 10,
})
);
canvas.dispatchEvent(new PointerEvent('pointerup', { pointerId: 6 }));
});
expect(moves.length).to.equal(1);
});

it('toggleCollapsed after stop() does not throw', function () {
make();
stop();
expect(() => stop.toggleCollapsed()).to.not.throw();
stop = null;
});
});
});
}
Loading