Skip to content

Commit

Permalink
Cancel prompt (google#3717)
Browse files Browse the repository at this point in the history
* added test

* added automatic to change source enum

* fixed tests

* added interact-stopped event and fixed spelling
  • Loading branch information
elalish authored Aug 19, 2022
1 parent 4d7d498 commit 47d5ae2
Show file tree
Hide file tree
Showing 7 changed files with 107 additions and 75 deletions.
19 changes: 11 additions & 8 deletions packages/model-viewer/src/features/controls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -573,12 +573,15 @@ export const ControlsMixin = <T extends Constructor<ModelViewerElementBase>>(
};

const moveTouches = () => {
// cancel interaction if user interacts
if (this[$controls].isUserChange) {
// cancel interaction if something else moves the camera
const {changeSource} = this[$controls];
if (changeSource !== ChangeSource.AUTOMATIC) {
for (const fingerElement of this[$fingerAnimatedContainers]) {
fingerElement.style.opacity = '0';
}
dispatchTouches('pointercancel');
this.dispatchEvent(new CustomEvent<CameraChangeDetails>(
'interact-stopped', {detail: {source: changeSource}}));
return;
}

Expand All @@ -593,6 +596,8 @@ export const ControlsMixin = <T extends Constructor<ModelViewerElementBase>>(
requestAnimationFrame(moveTouches);
} else {
dispatchTouches('pointerup');
this.dispatchEvent(new CustomEvent<CameraChangeDetails>(
'interact-stopped', {detail: {source: changeSource}}));
document.removeEventListener('visibilitychange', onVisibilityChange);
}
};
Expand Down Expand Up @@ -627,7 +632,7 @@ export const ControlsMixin = <T extends Constructor<ModelViewerElementBase>>(
style[1] = phi;
this[$maintainThetaPhi] = false;
}
controls.isUserChange = false;
controls.changeSource = ChangeSource.NONE;
controls.setOrbit(style[0], style[1], style[2]);
}

Expand Down Expand Up @@ -667,7 +672,7 @@ export const ControlsMixin = <T extends Constructor<ModelViewerElementBase>>(
if (!this[$renderer].arRenderer.isPresenting) {
this[$scene].setTarget(x, y, z);
}
this[$controls].isUserChange = false;
this[$controls].changeSource = ChangeSource.NONE;
this[$renderer].arRenderer.updateTarget();
}

Expand Down Expand Up @@ -708,7 +713,7 @@ export const ControlsMixin = <T extends Constructor<ModelViewerElementBase>>(
this[$promptAnimatedContainer].style.transform =
`translateX(${xOffset}px)`;

controls.isUserChange = false;
controls.changeSource = ChangeSource.AUTOMATIC;
controls.adjustOrbit(deltaTheta, 0, 0);

this[$lastPromptOffset] = offset;
Expand All @@ -717,9 +722,7 @@ export const ControlsMixin = <T extends Constructor<ModelViewerElementBase>>(

controls.update(time, delta);
if (scene.updateTarget(delta)) {
const source = controls.isUserChange ? ChangeSource.USER_INTERACTION :
ChangeSource.NONE;
this[$onChange]({type: 'change', source});
this[$onChange]({type: 'change', source: controls.changeSource});
}
}

Expand Down
29 changes: 29 additions & 0 deletions packages/model-viewer/src/test/features/controls-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -684,17 +684,46 @@ suite('Controls', () => {
expect(newTarget.z).to.be.eq(target.z, 'Z');
});

test('camera-orbit cancels synthetic interaction', async () => {
const orbit = element.getCameraOrbit();
element.interact(50, finger);
await rafPasses();
await rafPasses();

let changeSource = ChangeSource.AUTOMATIC;
element.addEventListener<any>(
'interact-stopped', ({detail: {source}}) => {
changeSource = source;
});
element.cameraOrbit = 'auto auto 70%';
await timePasses(50);
await rafPasses();

expect(changeSource).to.be.eq(ChangeSource.NONE);
const newOrbit = element.getCameraOrbit();
expect(newOrbit.theta).to.be.not.closeTo(orbit.theta, 0.001, 'theta');
expect(newOrbit.phi).to.be.not.closeTo(orbit.phi, 0.001, 'phi');
expect(newOrbit.radius)
.to.be.not.closeTo(orbit.radius, 0.001, 'radius');
});

test('user interaction cancels synthetic interaction', async () => {
const orbit = element.getCameraOrbit();
element.interact(50, finger);
await rafPasses();
await rafPasses();

let changeSource = ChangeSource.AUTOMATIC;
element.addEventListener<any>(
'interact-stopped', ({detail: {source}}) => {
changeSource = source;
});
dispatchSyntheticEvent(
element[$userInputElement], 'keydown', {key: 'PageDown'});
await timePasses(50);
await rafPasses();

expect(changeSource).to.be.eq(ChangeSource.USER_INTERACTION);
const newOrbit = element.getCameraOrbit();
expect(newOrbit.theta).to.be.not.closeTo(orbit.theta, 0.001, 'theta');
expect(newOrbit.phi).to.be.not.closeTo(orbit.phi, 0.001, 'phi');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,6 @@ const HALF_PI = Math.PI / 2.0;
const QUARTER_PI = HALF_PI / 2.0;
const THREE_QUARTERS_PI = HALF_PI + QUARTER_PI;

const USER_INTERACTION_CHANGE_SOURCE = 'user-interaction';
const DEFAULT_INTERACTION_CHANGE_SOURCE = 'none';

/**
* Settle controls by performing 50 frames worth of updates
*/
Expand Down Expand Up @@ -304,7 +301,7 @@ suite('SmoothControls', () => {
settleControls(controls);

expect(didCall).to.be.true;
expect(changeSource).to.equal(USER_INTERACTION_CHANGE_SOURCE);
expect(changeSource).to.equal(ChangeSource.USER_INTERACTION);
});

test('dispatches "change" on direct orbit change', () => {
Expand All @@ -320,15 +317,15 @@ suite('SmoothControls', () => {
settleControls(controls);

expect(didCall).to.be.true;
expect(changeSource).to.equal(DEFAULT_INTERACTION_CHANGE_SOURCE);
expect(changeSource).to.equal(ChangeSource.NONE);
});

test('sends "user-interaction" multiple times', () => {
const expectedSources = [
USER_INTERACTION_CHANGE_SOURCE,
USER_INTERACTION_CHANGE_SOURCE,
USER_INTERACTION_CHANGE_SOURCE,
USER_INTERACTION_CHANGE_SOURCE,
ChangeSource.USER_INTERACTION,
ChangeSource.USER_INTERACTION,
ChangeSource.USER_INTERACTION,
ChangeSource.USER_INTERACTION,
];
let changeSource: Array<string> = [];

Expand All @@ -346,11 +343,11 @@ suite('SmoothControls', () => {

test('does not send "user-interaction" after setOrbit', () => {
const expectedSources = [
USER_INTERACTION_CHANGE_SOURCE,
USER_INTERACTION_CHANGE_SOURCE,
USER_INTERACTION_CHANGE_SOURCE,
DEFAULT_INTERACTION_CHANGE_SOURCE,
DEFAULT_INTERACTION_CHANGE_SOURCE,
ChangeSource.USER_INTERACTION,
ChangeSource.USER_INTERACTION,
ChangeSource.USER_INTERACTION,
ChangeSource.NONE,
ChangeSource.NONE,
];
let changeSource: Array<string> = [];

Expand All @@ -363,7 +360,7 @@ suite('SmoothControls', () => {
controls.update(performance.now(), ONE_FRAME_DELTA);
controls.update(performance.now(), ONE_FRAME_DELTA);

controls.isUserChange = false;
controls.changeSource = ChangeSource.NONE;
controls.setOrbit(3, 3, 3);

controls.update(performance.now(), ONE_FRAME_DELTA);
Expand Down
78 changes: 38 additions & 40 deletions packages/model-viewer/src/three-components/SmoothControls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ const KEYBOARD_ORBIT_INCREMENT = Math.PI / 8;
const ZOOM_SENSITIVITY = 0.04;

// The move size on pan key event
const PANKEYINCREMENT = 10;
const PAN_KEY_INCREMENT = 10;

export const KeyCode = {
PAGE_UP: 33,
Expand All @@ -83,11 +83,12 @@ export const KeyCode = {
DOWN: 40
};

export type ChangeSource = 'user-interaction'|'none';
export type ChangeSource = 'user-interaction'|'none'|'automatic';

export const ChangeSource: {[index: string]: ChangeSource} = {
USER_INTERACTION: 'user-interaction',
NONE: 'none'
NONE: 'none',
AUTOMATIC: 'automatic'
};

/**
Expand Down Expand Up @@ -126,7 +127,7 @@ export interface PointerChangeEvent extends ThreeEvent {
*/
export class SmoothControls extends EventDispatcher {
public sensitivity = 1;
public isUserChange = false;
public changeSource = ChangeSource.NONE;

private _interactionEnabled: boolean = false;
private _options: SmoothControlsOptions;
Expand Down Expand Up @@ -477,10 +478,7 @@ export class SmoothControls extends EventDispatcher {
}

private dispatchChange() {
const source =
this.isUserChange ? ChangeSource.USER_INTERACTION : ChangeSource.NONE;

this.dispatchEvent({type: 'change', source});
this.dispatchEvent({type: 'change', source: this.changeSource});
}

private moveCamera() {
Expand Down Expand Up @@ -562,7 +560,7 @@ export class SmoothControls extends EventDispatcher {
const dxMag = Math.abs(dx);
const dyMag = Math.abs(dy);
// If motion is mostly vertical, assume scrolling is the intent.
if (this.isUserChange &&
if (this.changeSource === ChangeSource.USER_INTERACTION &&
((touchAction === 'pan-y' && dyMag > dxMag) ||
(touchAction === 'pan-x' && dxMag > dyMag))) {
this.touchMode = null;
Expand Down Expand Up @@ -698,10 +696,12 @@ export class SmoothControls extends EventDispatcher {
this.isUserPointing = false;

if (event.pointerType === 'touch') {
this.isUserChange = !event.altKey; // set by interact() in controls.ts
this.changeSource = event.altKey ? // set by interact() in controls.ts
ChangeSource.AUTOMATIC :
ChangeSource.USER_INTERACTION;
this.onTouchChange(event);
} else {
this.isUserChange = true;
this.changeSource = ChangeSource.USER_INTERACTION;
this.onMouseDown(event);
}
};
Expand All @@ -723,12 +723,14 @@ export class SmoothControls extends EventDispatcher {
pointer.clientY = event.clientY;

if (event.pointerType === 'touch') {
this.isUserChange = !event.altKey; // set by interact() in controls.ts
this.changeSource = event.altKey ? // set by interact() in controls.ts
ChangeSource.AUTOMATIC :
ChangeSource.USER_INTERACTION;
if (this.touchMode !== null) {
this.touchMode(dx, dy);
}
} else {
this.isUserChange = true;
this.changeSource = ChangeSource.USER_INTERACTION;
if (this.panPerPixel > 0) {
this.movePan(dx, dy);
} else {
Expand Down Expand Up @@ -810,7 +812,7 @@ export class SmoothControls extends EventDispatcher {
}

private onWheel = (event: Event) => {
this.isUserChange = true;
this.changeSource = ChangeSource.USER_INTERACTION;

const deltaZoom = (event as WheelEvent).deltaY *
((event as WheelEvent).deltaMode == 1 ? 18 : 1) * ZOOM_SENSITIVITY / 30;
Expand All @@ -823,23 +825,27 @@ export class SmoothControls extends EventDispatcher {
// We track if the key is actually one we respond to, so as not to
// accidentally clobber unrelated key inputs when the <model-viewer> has
// focus.
const {isUserChange} = this;
this.isUserChange = true;
const {changeSource} = this;
this.changeSource = ChangeSource.USER_INTERACTION;

(event.shiftKey && this.enablePan) ?
this.panKeyCodeHandler(event, isUserChange) :
this.orbitZoomKeyCodeHandler(event, isUserChange)
};
const relevantKey = (event.shiftKey && this.enablePan) ?
this.panKeyCodeHandler(event) :
this.orbitZoomKeyCodeHandler(event);

if (relevantKey) {
event.preventDefault();
} else {
this.changeSource = changeSource;
}
};

/**
* Handles the orbit and Zoom key presses
* Uses constants for the increment.
* @param event The keyboard event for the .key value
* @param isUserChange Is this a user initiated event
* @returns boolean to indicate if the key event has been handled
*/
private orbitZoomKeyCodeHandler(event: KeyboardEvent, isUserChange: boolean) {
private orbitZoomKeyCodeHandler(event: KeyboardEvent) {
let relevantKey = true;
switch (event.key) {
case 'PageUp':
Expand All @@ -862,49 +868,41 @@ export class SmoothControls extends EventDispatcher {
break;
default:
relevantKey = false;
this.isUserChange = isUserChange;
break;
}

if (relevantKey) {
event.preventDefault();
}
return relevantKey;
}

/**
* Handles the Pan key presses
* Uses constants for the increment.
* @param event The keyboard event for the .key value
* @param isUserChange Is this a user initiated event
* @returns boolean to indicate if the key event has been handled
*/
private panKeyCodeHandler(event: KeyboardEvent, isUserChange: boolean) {
private panKeyCodeHandler(event: KeyboardEvent) {
this.initializePan();
let relevantKey = true;
switch (event.key) {
case 'ArrowUp':
this.movePan(
0, -1 * PANKEYINCREMENT); // This is the negative one so that the
// model appears to move as the arrow
// direction rather than the view moving
0,
-1 * PAN_KEY_INCREMENT); // This is the negative one so that the
// model appears to move as the arrow
// direction rather than the view moving
break;
case 'ArrowDown':
this.movePan(0, PANKEYINCREMENT);
this.movePan(0, PAN_KEY_INCREMENT);
break;
case 'ArrowLeft':
this.movePan(-1 * PANKEYINCREMENT, 0);
this.movePan(-1 * PAN_KEY_INCREMENT, 0);
break;
case 'ArrowRight':
this.movePan(PANKEYINCREMENT, 0);
this.movePan(PAN_KEY_INCREMENT, 0);
break;
default:
relevantKey = false;
this.isUserChange = isUserChange;
break;
}

if (relevantKey) {
event.preventDefault();
}
return relevantKey;
}
}
Loading

0 comments on commit 47d5ae2

Please sign in to comment.