Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for touch and hold gesture #7286

Merged
merged 11 commits into from
Mar 20, 2019
1 change: 1 addition & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ Change Log
##### Additions :tada:
* `Resource.fetchImage` now has a `flipY` option to vertically flip an image during fetch & decode. It is only valid when `ImageBitmapOptions` is supported by the browser. [#7579](https://github.com/AnalyticalGraphicsInc/cesium/pull/7579)
* Added `backFaceCulling` and `normalShading` options to `PointCloudShading`. Both options are only applicable for point clouds containing normals. [#7399](https://github.com/AnalyticalGraphicsInc/cesium/pull/7399)
* Added support for touch and hold gesture. The touch and hold delay can be customized by updating `ScreenSpaceEventHandler.touchHoldDelayMilliseconds`. [#7286](https://github.com/AnalyticalGraphicsInc/cesium/pull/7286)

##### Fixes :wrench:
* Fixed the value for `BlendFunction.ONE_MINUS_CONSTANT_COLOR`. [#7624](https://github.com/AnalyticalGraphicsInc/cesium/pull/7624)
Expand Down
67 changes: 54 additions & 13 deletions Source/Core/ScreenSpaceEventHandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,14 @@ define([
return (getTimestamp() - screenSpaceEventHandler._lastSeenTouchEvent) > ScreenSpaceEventHandler.mouseEmulationIgnoreMilliseconds;
}

function checkPixelTolerance(startPosition, endPosition, pixelTolerance) {
var xDiff = startPosition.x - endPosition.x;
var yDiff = startPosition.y - endPosition.y;
var totalPixels = Math.sqrt(xDiff * xDiff + yDiff * yDiff);

return totalPixels < pixelTolerance;
}

function handleMouseDown(screenSpaceEventHandler, event) {
if (!canProcessMouseEvent(screenSpaceEventHandler)) {
return;
Expand Down Expand Up @@ -193,11 +201,7 @@ define([

if (defined(clickAction)) {
var startPosition = screenSpaceEventHandler._primaryStartPosition;
var xDiff = startPosition.x - position.x;
var yDiff = startPosition.y - position.y;
var totalPixels = Math.sqrt(xDiff * xDiff + yDiff * yDiff);

if (totalPixels < screenSpaceEventHandler._clickPixelTolerance) {
if (checkPixelTolerance(startPosition, position, screenSpaceEventHandler._clickPixelTolerance)) {
Cartesian2.clone(position, mouseClickEvent.position);

clickAction(mouseClickEvent);
Expand Down Expand Up @@ -398,11 +402,13 @@ define([
var touchClickEvent = {
position : new Cartesian2()
};
var touchHoldEvent = {
position : new Cartesian2()
};

function fireTouchEvents(screenSpaceEventHandler, event) {
var modifier = getModifier(event);
var positions = screenSpaceEventHandler._positions;
var previousPositions = screenSpaceEventHandler._previousPositions;
var numberOfTouches = positions.length;
var action;
var clickAction;
Expand All @@ -411,6 +417,12 @@ define([
if (numberOfTouches !== 1 && screenSpaceEventHandler._buttonDown[MouseButton.LEFT]) {
// transitioning from single touch, trigger UP and might trigger CLICK
screenSpaceEventHandler._buttonDown[MouseButton.LEFT] = false;

if(defined(screenSpaceEventHandler._touchHoldTimer)) {
clearTimeout(screenSpaceEventHandler._touchHoldTimer);
screenSpaceEventHandler._touchHoldTimer = undefined;
}

action = screenSpaceEventHandler.getInputAction(ScreenSpaceEventType.LEFT_UP, modifier);

if (defined(action)) {
Expand All @@ -419,25 +431,23 @@ define([
action(touchEndEvent);
}

if (numberOfTouches === 0) {
if (numberOfTouches === 0 && !screenSpaceEventHandler._isTouchHolding) {
// releasing single touch, check for CLICK
clickAction = screenSpaceEventHandler.getInputAction(ScreenSpaceEventType.LEFT_CLICK, modifier);

if (defined(clickAction)) {
var startPosition = screenSpaceEventHandler._primaryStartPosition;
var endPosition = previousPositions.values[0];
var xDiff = startPosition.x - endPosition.x;
var yDiff = startPosition.y - endPosition.y;
var totalPixels = Math.sqrt(xDiff * xDiff + yDiff * yDiff);

if (totalPixels < screenSpaceEventHandler._clickPixelTolerance) {
var endPosition = screenSpaceEventHandler._previousPositions.values[0];
if(checkPixelTolerance(startPosition, endPosition, screenSpaceEventHandler._clickPixelTolerance)) {
Cartesian2.clone(screenSpaceEventHandler._primaryPosition, touchClickEvent.position);

clickAction(touchClickEvent);
}
}
}

screenSpaceEventHandler._isTouchHolding = false;

// Otherwise don't trigger CLICK, because we are adding more touches.
}

Expand Down Expand Up @@ -469,6 +479,25 @@ define([
action(touchStartEvent);
}

screenSpaceEventHandler._touchHoldTimer = setTimeout(function() {
if(!screenSpaceEventHandler.isDestroyed()) {
screenSpaceEventHandler._touchHoldTimer = undefined;
screenSpaceEventHandler._isTouchHolding = true;

clickAction = screenSpaceEventHandler.getInputAction(ScreenSpaceEventType.RIGHT_CLICK, modifier);

if (defined(clickAction)) {
var startPosition = screenSpaceEventHandler._primaryStartPosition;
var endPosition = screenSpaceEventHandler._previousPositions.values[0];
if(checkPixelTolerance(startPosition, endPosition, screenSpaceEventHandler._holdPixelTolerance)) {
Cartesian2.clone(screenSpaceEventHandler._primaryPosition, touchHoldEvent.position);

clickAction(touchHoldEvent);
}
}
}
}, ScreenSpaceEventHandler.touchHoldDelayMilliseconds);

event.preventDefault();
}

Expand Down Expand Up @@ -669,6 +698,7 @@ define([
RIGHT: false
};
this._isPinching = false;
this._isTouchHolding = false;
this._lastSeenTouchEvent = -ScreenSpaceEventHandler.mouseEmulationIgnoreMilliseconds;

this._primaryStartPosition = new Cartesian2();
Expand All @@ -680,9 +710,12 @@ define([

this._removalFunctions = [];

this._touchHoldTimer = undefined;

// TODO: Revisit when doing mobile development. May need to be configurable
// or determined based on the platform?
this._clickPixelTolerance = 5;
this._holdPixelTolerance = 25;

this._element = defaultValue(element, document);

Expand Down Expand Up @@ -799,5 +832,13 @@ define([
*/
ScreenSpaceEventHandler.mouseEmulationIgnoreMilliseconds = 800;

/**
* The amount of time, in milliseconds, before a touch on the screen becomes a
* touch and hold.
* @type {Number}
* @default 1500
*/
ScreenSpaceEventHandler.touchHoldDelayMilliseconds = 1500;

return ScreenSpaceEventHandler;
});
83 changes: 83 additions & 0 deletions Specs/Core/ScreenSpaceEventHandlerSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -1207,6 +1207,89 @@ defineSuite([
expect(action).not.toHaveBeenCalled();
});

it('handles touch and hold gesture', function() {
jasmine.clock().install();

var delay = ScreenSpaceEventHandler.touchHoldDelayMilliseconds;

var eventType = ScreenSpaceEventType.RIGHT_CLICK;

var action = createCloningSpy('action');
handler.setInputAction(action, eventType);

expect(handler.getInputAction(eventType)).toEqual(action);

// start, then end
function simulateInput(timeout) {
var touchStartPosition = {
clientX : 1,
clientY : 2
};
var touchEndPosition = {
clientX : 1,
clientY : 2
};

if (usePointerEvents) {
DomEventSimulator.firePointerDown(element, combine({
pointerType : 'touch',
pointerId : 1
}, touchStartPosition));
jasmine.clock().tick(timeout);
DomEventSimulator.firePointerUp(element, combine({
pointerType : 'touch',
pointerId : 1
}, touchEndPosition));
} else {
DomEventSimulator.fireTouchStart(element, {
changedTouches : [combine({
identifier : 0
}, touchStartPosition)]
});
jasmine.clock().tick(timeout);
DomEventSimulator.fireTouchEnd(element, {
changedTouches : [combine({
identifier : 0
}, touchEndPosition)]
});
}
}

simulateInput(delay + 1);

expect(action.calls.count()).toEqual(1);
expect(action).toHaveBeenCalledWith({
position : new Cartesian2(1, 2)
});

// Should not be fired if hold delay is less than touchHoldDelayMilliseconds.
action.calls.reset();

simulateInput(delay - 1);

expect(action).not.toHaveBeenCalled();

// Should not be fired after removal.
action.calls.reset();

handler.removeInputAction(eventType);

simulateInput(delay + 1);

expect(action).not.toHaveBeenCalled();

// Should not fire click action if touch and hold is triggered.
eventType = ScreenSpaceEventType.LEFT_CLICK;

handler.setInputAction(action, eventType);

simulateInput(delay + 1);

expect(action).not.toHaveBeenCalled();

jasmine.clock().uninstall();
});

it('treats touch cancel as touch end for touch clicks', function() {
var eventType = ScreenSpaceEventType.LEFT_CLICK;

Expand Down