Skip to content

Commit ead9f36

Browse files
authored
Merge pull request #7286 from adridavid/touch-events
Add support for touch and hold gesture
2 parents 464d2cd + 5ad60e2 commit ead9f36

File tree

3 files changed

+138
-13
lines changed

3 files changed

+138
-13
lines changed

CHANGES.md

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ Change Log
1212
##### Additions :tada:
1313
* `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)
1414
* 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)
15+
* 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)
1516

1617
##### Fixes :wrench:
1718
* Fixed the value for `BlendFunction.ONE_MINUS_CONSTANT_COLOR`. [#7624](https://github.com/AnalyticalGraphicsInc/cesium/pull/7624)

Source/Core/ScreenSpaceEventHandler.js

+54-13
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,14 @@ define([
133133
return (getTimestamp() - screenSpaceEventHandler._lastSeenTouchEvent) > ScreenSpaceEventHandler.mouseEmulationIgnoreMilliseconds;
134134
}
135135

136+
function checkPixelTolerance(startPosition, endPosition, pixelTolerance) {
137+
var xDiff = startPosition.x - endPosition.x;
138+
var yDiff = startPosition.y - endPosition.y;
139+
var totalPixels = Math.sqrt(xDiff * xDiff + yDiff * yDiff);
140+
141+
return totalPixels < pixelTolerance;
142+
}
143+
136144
function handleMouseDown(screenSpaceEventHandler, event) {
137145
if (!canProcessMouseEvent(screenSpaceEventHandler)) {
138146
return;
@@ -193,11 +201,7 @@ define([
193201

194202
if (defined(clickAction)) {
195203
var startPosition = screenSpaceEventHandler._primaryStartPosition;
196-
var xDiff = startPosition.x - position.x;
197-
var yDiff = startPosition.y - position.y;
198-
var totalPixels = Math.sqrt(xDiff * xDiff + yDiff * yDiff);
199-
200-
if (totalPixels < screenSpaceEventHandler._clickPixelTolerance) {
204+
if (checkPixelTolerance(startPosition, position, screenSpaceEventHandler._clickPixelTolerance)) {
201205
Cartesian2.clone(position, mouseClickEvent.position);
202206

203207
clickAction(mouseClickEvent);
@@ -398,11 +402,13 @@ define([
398402
var touchClickEvent = {
399403
position : new Cartesian2()
400404
};
405+
var touchHoldEvent = {
406+
position : new Cartesian2()
407+
};
401408

402409
function fireTouchEvents(screenSpaceEventHandler, event) {
403410
var modifier = getModifier(event);
404411
var positions = screenSpaceEventHandler._positions;
405-
var previousPositions = screenSpaceEventHandler._previousPositions;
406412
var numberOfTouches = positions.length;
407413
var action;
408414
var clickAction;
@@ -411,6 +417,12 @@ define([
411417
if (numberOfTouches !== 1 && screenSpaceEventHandler._buttonDown[MouseButton.LEFT]) {
412418
// transitioning from single touch, trigger UP and might trigger CLICK
413419
screenSpaceEventHandler._buttonDown[MouseButton.LEFT] = false;
420+
421+
if(defined(screenSpaceEventHandler._touchHoldTimer)) {
422+
clearTimeout(screenSpaceEventHandler._touchHoldTimer);
423+
screenSpaceEventHandler._touchHoldTimer = undefined;
424+
}
425+
414426
action = screenSpaceEventHandler.getInputAction(ScreenSpaceEventType.LEFT_UP, modifier);
415427

416428
if (defined(action)) {
@@ -419,25 +431,23 @@ define([
419431
action(touchEndEvent);
420432
}
421433

422-
if (numberOfTouches === 0) {
434+
if (numberOfTouches === 0 && !screenSpaceEventHandler._isTouchHolding) {
423435
// releasing single touch, check for CLICK
424436
clickAction = screenSpaceEventHandler.getInputAction(ScreenSpaceEventType.LEFT_CLICK, modifier);
425437

426438
if (defined(clickAction)) {
427439
var startPosition = screenSpaceEventHandler._primaryStartPosition;
428-
var endPosition = previousPositions.values[0];
429-
var xDiff = startPosition.x - endPosition.x;
430-
var yDiff = startPosition.y - endPosition.y;
431-
var totalPixels = Math.sqrt(xDiff * xDiff + yDiff * yDiff);
432-
433-
if (totalPixels < screenSpaceEventHandler._clickPixelTolerance) {
440+
var endPosition = screenSpaceEventHandler._previousPositions.values[0];
441+
if(checkPixelTolerance(startPosition, endPosition, screenSpaceEventHandler._clickPixelTolerance)) {
434442
Cartesian2.clone(screenSpaceEventHandler._primaryPosition, touchClickEvent.position);
435443

436444
clickAction(touchClickEvent);
437445
}
438446
}
439447
}
440448

449+
screenSpaceEventHandler._isTouchHolding = false;
450+
441451
// Otherwise don't trigger CLICK, because we are adding more touches.
442452
}
443453

@@ -469,6 +479,25 @@ define([
469479
action(touchStartEvent);
470480
}
471481

482+
screenSpaceEventHandler._touchHoldTimer = setTimeout(function() {
483+
if(!screenSpaceEventHandler.isDestroyed()) {
484+
screenSpaceEventHandler._touchHoldTimer = undefined;
485+
screenSpaceEventHandler._isTouchHolding = true;
486+
487+
clickAction = screenSpaceEventHandler.getInputAction(ScreenSpaceEventType.RIGHT_CLICK, modifier);
488+
489+
if (defined(clickAction)) {
490+
var startPosition = screenSpaceEventHandler._primaryStartPosition;
491+
var endPosition = screenSpaceEventHandler._previousPositions.values[0];
492+
if(checkPixelTolerance(startPosition, endPosition, screenSpaceEventHandler._holdPixelTolerance)) {
493+
Cartesian2.clone(screenSpaceEventHandler._primaryPosition, touchHoldEvent.position);
494+
495+
clickAction(touchHoldEvent);
496+
}
497+
}
498+
}
499+
}, ScreenSpaceEventHandler.touchHoldDelayMilliseconds);
500+
472501
event.preventDefault();
473502
}
474503

@@ -669,6 +698,7 @@ define([
669698
RIGHT: false
670699
};
671700
this._isPinching = false;
701+
this._isTouchHolding = false;
672702
this._lastSeenTouchEvent = -ScreenSpaceEventHandler.mouseEmulationIgnoreMilliseconds;
673703

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

681711
this._removalFunctions = [];
682712

713+
this._touchHoldTimer = undefined;
714+
683715
// TODO: Revisit when doing mobile development. May need to be configurable
684716
// or determined based on the platform?
685717
this._clickPixelTolerance = 5;
718+
this._holdPixelTolerance = 25;
686719

687720
this._element = defaultValue(element, document);
688721

@@ -799,5 +832,13 @@ define([
799832
*/
800833
ScreenSpaceEventHandler.mouseEmulationIgnoreMilliseconds = 800;
801834

835+
/**
836+
* The amount of time, in milliseconds, before a touch on the screen becomes a
837+
* touch and hold.
838+
* @type {Number}
839+
* @default 1500
840+
*/
841+
ScreenSpaceEventHandler.touchHoldDelayMilliseconds = 1500;
842+
802843
return ScreenSpaceEventHandler;
803844
});

Specs/Core/ScreenSpaceEventHandlerSpec.js

+83
Original file line numberDiff line numberDiff line change
@@ -1207,6 +1207,89 @@ defineSuite([
12071207
expect(action).not.toHaveBeenCalled();
12081208
});
12091209

1210+
it('handles touch and hold gesture', function() {
1211+
jasmine.clock().install();
1212+
1213+
var delay = ScreenSpaceEventHandler.touchHoldDelayMilliseconds;
1214+
1215+
var eventType = ScreenSpaceEventType.RIGHT_CLICK;
1216+
1217+
var action = createCloningSpy('action');
1218+
handler.setInputAction(action, eventType);
1219+
1220+
expect(handler.getInputAction(eventType)).toEqual(action);
1221+
1222+
// start, then end
1223+
function simulateInput(timeout) {
1224+
var touchStartPosition = {
1225+
clientX : 1,
1226+
clientY : 2
1227+
};
1228+
var touchEndPosition = {
1229+
clientX : 1,
1230+
clientY : 2
1231+
};
1232+
1233+
if (usePointerEvents) {
1234+
DomEventSimulator.firePointerDown(element, combine({
1235+
pointerType : 'touch',
1236+
pointerId : 1
1237+
}, touchStartPosition));
1238+
jasmine.clock().tick(timeout);
1239+
DomEventSimulator.firePointerUp(element, combine({
1240+
pointerType : 'touch',
1241+
pointerId : 1
1242+
}, touchEndPosition));
1243+
} else {
1244+
DomEventSimulator.fireTouchStart(element, {
1245+
changedTouches : [combine({
1246+
identifier : 0
1247+
}, touchStartPosition)]
1248+
});
1249+
jasmine.clock().tick(timeout);
1250+
DomEventSimulator.fireTouchEnd(element, {
1251+
changedTouches : [combine({
1252+
identifier : 0
1253+
}, touchEndPosition)]
1254+
});
1255+
}
1256+
}
1257+
1258+
simulateInput(delay + 1);
1259+
1260+
expect(action.calls.count()).toEqual(1);
1261+
expect(action).toHaveBeenCalledWith({
1262+
position : new Cartesian2(1, 2)
1263+
});
1264+
1265+
// Should not be fired if hold delay is less than touchHoldDelayMilliseconds.
1266+
action.calls.reset();
1267+
1268+
simulateInput(delay - 1);
1269+
1270+
expect(action).not.toHaveBeenCalled();
1271+
1272+
// Should not be fired after removal.
1273+
action.calls.reset();
1274+
1275+
handler.removeInputAction(eventType);
1276+
1277+
simulateInput(delay + 1);
1278+
1279+
expect(action).not.toHaveBeenCalled();
1280+
1281+
// Should not fire click action if touch and hold is triggered.
1282+
eventType = ScreenSpaceEventType.LEFT_CLICK;
1283+
1284+
handler.setInputAction(action, eventType);
1285+
1286+
simulateInput(delay + 1);
1287+
1288+
expect(action).not.toHaveBeenCalled();
1289+
1290+
jasmine.clock().uninstall();
1291+
});
1292+
12101293
it('treats touch cancel as touch end for touch clicks', function() {
12111294
var eventType = ScreenSpaceEventType.LEFT_CLICK;
12121295

0 commit comments

Comments
 (0)