314 lines
9.9 KiB
JavaScript
314 lines
9.9 KiB
JavaScript
|
//>>built
|
||
|
define("dojox/geo/charting/TouchInteractionSupport", ["dojo/_base/lang","dojo/_base/declare","dojo/_base/event", "dojo/_base/connect","dojo/_base/window"],
|
||
|
function(lang,declare,event,connect,win) {
|
||
|
|
||
|
return declare("dojox.geo.charting.TouchInteractionSupport",null, {
|
||
|
// summary:
|
||
|
// class to handle touch interactions on a dojox.geo.charting.Map widget
|
||
|
// tags:
|
||
|
// private
|
||
|
|
||
|
_map : null,
|
||
|
_centerTouchLocation : null,
|
||
|
_touchMoveListener: null,
|
||
|
_touchEndListener: null,
|
||
|
_touchEndTapListener: null,
|
||
|
_touchStartListener: null,
|
||
|
_initialFingerSpacing: null,
|
||
|
_initialScale: null,
|
||
|
_tapCount: null,
|
||
|
_tapThreshold: null,
|
||
|
_lastTap: null,
|
||
|
_doubleTapPerformed:false,
|
||
|
_oneFingerTouch:false,
|
||
|
_tapCancel:false,
|
||
|
|
||
|
constructor : function(/* dojox.geo.charting.Map */map,options) {
|
||
|
// summary:
|
||
|
// Constructs a new _TouchInteractionSupport instance
|
||
|
// map: dojox.geo.charting.Map
|
||
|
// the Map widget this class provides touch navigation for.
|
||
|
this._map = map;
|
||
|
this._centerTouchLocation = {x: 0,y: 0};
|
||
|
|
||
|
this._tapCount = 0;
|
||
|
this._lastTap = {x: 0,y: 0};
|
||
|
this._tapThreshold = 100; // square distance in pixels
|
||
|
},
|
||
|
|
||
|
connect: function() {
|
||
|
// summary:
|
||
|
// install touch listeners
|
||
|
_touchStartListener = this._map.surface.connect("touchstart", this, this._touchStartHandler);
|
||
|
},
|
||
|
|
||
|
disconnect: function() {
|
||
|
// summary:
|
||
|
// disconnects any installed listeners. Must be called only when disposing of this instance
|
||
|
if (this._touchStartListener) {
|
||
|
connect.disconnect(this._touchStartListener);
|
||
|
this._touchStartListener = null;
|
||
|
}
|
||
|
},
|
||
|
|
||
|
_getTouchBarycenter: function(touchEvent) {
|
||
|
// summary:
|
||
|
// returns the midpoint of the two first fingers (or the first finger location if only one)
|
||
|
// touchEvent: a touch event
|
||
|
// returns: dojox.gfx.Point
|
||
|
// the midpoint
|
||
|
// tags:
|
||
|
// private
|
||
|
var touches = touchEvent.touches;
|
||
|
var firstTouch = touches[0];
|
||
|
var secondTouch = null;
|
||
|
if (touches.length > 1) {
|
||
|
secondTouch = touches[1];
|
||
|
} else {
|
||
|
secondTouch = touches[0];
|
||
|
}
|
||
|
var containerBounds = this._map._getContainerBounds();
|
||
|
var middleX = (firstTouch.pageX + secondTouch.pageX) / 2.0 - containerBounds.x;
|
||
|
var middleY = (firstTouch.pageY + secondTouch.pageY) / 2.0 - containerBounds.y;
|
||
|
return {x: middleX,y: middleY};
|
||
|
},
|
||
|
|
||
|
_getFingerSpacing: function(touchEvent) {
|
||
|
// summary:
|
||
|
// computes the distance between the first two fingers
|
||
|
// touchEvent: a touch event
|
||
|
// returns: float
|
||
|
// a distance. -1 if less that 2 fingers
|
||
|
// tags:
|
||
|
// private
|
||
|
var touches = touchEvent.touches;
|
||
|
var spacing = -1;
|
||
|
if (touches.length >= 2) {
|
||
|
var dx = (touches[1].pageX - touches[0].pageX);
|
||
|
var dy = (touches[1].pageY - touches[0].pageY);
|
||
|
spacing = Math.sqrt(dx*dx + dy*dy);
|
||
|
}
|
||
|
return spacing;
|
||
|
},
|
||
|
|
||
|
_isDoubleTap: function(touchEvent) {
|
||
|
// summary:
|
||
|
// checks whether the specified touchStart event is a double tap
|
||
|
// (i.e. follows closely a previous touchStart at approximately the same location)
|
||
|
// touchEvent: a touch event
|
||
|
// returns: boolean
|
||
|
// true if this event is considered a double tap
|
||
|
// tags:
|
||
|
// private
|
||
|
var isDoubleTap = false;
|
||
|
var touches = touchEvent.touches;
|
||
|
if ((this._tapCount > 0) && touches.length == 1) {
|
||
|
// test distance from last tap
|
||
|
var dx = (touches[0].pageX - this._lastTap.x);
|
||
|
var dy = (touches[0].pageY - this._lastTap.y);
|
||
|
var distance = dx*dx + dy*dy;
|
||
|
if (distance < this._tapThreshold) {
|
||
|
isDoubleTap = true;
|
||
|
} else {
|
||
|
this._tapCount = 0;
|
||
|
}
|
||
|
}
|
||
|
this._tapCount++;
|
||
|
this._lastTap.x = touches[0].pageX;
|
||
|
this._lastTap.y = touches[0].pageY;
|
||
|
setTimeout(lang.hitch(this,function() {
|
||
|
this._tapCount = 0;}),300);
|
||
|
return isDoubleTap;
|
||
|
},
|
||
|
|
||
|
_doubleTapHandler: function(touchEvent) {
|
||
|
// summary:
|
||
|
// action performed on the map when a double tap was triggered
|
||
|
// touchEvent: a touch event
|
||
|
// tags:
|
||
|
// private
|
||
|
var feature = this._getFeatureFromTouchEvent(touchEvent);
|
||
|
if (feature) {
|
||
|
this._map.fitToMapArea(feature._bbox, 15, true);
|
||
|
} else {
|
||
|
// perform a basic 2x zoom on touch
|
||
|
var touches = touchEvent.touches;
|
||
|
var containerBounds = this._map._getContainerBounds();
|
||
|
var offX = touches[0].pageX - containerBounds.x;
|
||
|
var offY = touches[0].pageY - containerBounds.y;
|
||
|
// clicked map point before zooming
|
||
|
var mapPoint = this._map.screenCoordsToMapCoords(offX,offY);
|
||
|
// zoom increment power
|
||
|
this._map.setMapCenterAndScale(mapPoint.x, mapPoint.y,this._map.getMapScale()*2,true);
|
||
|
}
|
||
|
},
|
||
|
|
||
|
_getFeatureFromTouchEvent: function(touchEvent) {
|
||
|
// summary:
|
||
|
// utility function to return the feature located at this touch event location
|
||
|
// touchEvent: a touch event
|
||
|
// returns: dojox.geo.charting.Feature
|
||
|
// the feature found if any, null otherwise.
|
||
|
// tags:
|
||
|
// private
|
||
|
var feature = null;
|
||
|
if (touchEvent.gfxTarget && touchEvent.gfxTarget.getParent) {
|
||
|
feature = this._map.mapObj.features[touchEvent.gfxTarget.getParent().id];
|
||
|
}
|
||
|
return feature;
|
||
|
},
|
||
|
|
||
|
_touchStartHandler: function(touchEvent){
|
||
|
// summary:
|
||
|
// action performed on the map when a touch start was triggered
|
||
|
// touchEvent: a touch event
|
||
|
// tags:
|
||
|
// private
|
||
|
event.stop(touchEvent);
|
||
|
this._oneFingerTouch = (touchEvent.touches.length == 1);
|
||
|
this._tapCancel = !this._oneFingerTouch;
|
||
|
// test double tap
|
||
|
this._doubleTapPerformed = false;
|
||
|
if (this._isDoubleTap(touchEvent)) {
|
||
|
//console.log("double tap recognized");
|
||
|
this._doubleTapHandler(touchEvent);
|
||
|
this._doubleTapPerformed = true;
|
||
|
return;
|
||
|
}
|
||
|
// compute map midpoint between fingers
|
||
|
var middlePoint = this._getTouchBarycenter(touchEvent);
|
||
|
var mapPoint = this._map.screenCoordsToMapCoords(middlePoint.x,middlePoint.y);
|
||
|
this._centerTouchLocation.x = mapPoint.x;
|
||
|
this._centerTouchLocation.y = mapPoint.y;
|
||
|
// store initial finger spacing to compute zoom later
|
||
|
this._initialFingerSpacing = this._getFingerSpacing(touchEvent);
|
||
|
// store initial map scale
|
||
|
this._initialScale = this._map.getMapScale();
|
||
|
// install touch move and up listeners (if not done by other fingers before)
|
||
|
if (!this._touchMoveListener)
|
||
|
this._touchMoveListener = connect.connect(win.global,"touchmove",this,this._touchMoveHandler);
|
||
|
if (!this._touchEndTapListener)
|
||
|
this._touchEndTapListener = this._map.surface.connect("touchend", this, this._touchEndTapHandler);
|
||
|
if (!this._touchEndListener)
|
||
|
this._touchEndListener = connect.connect(win.global,"touchend",this, this._touchEndHandler);
|
||
|
},
|
||
|
|
||
|
_touchEndTapHandler: function(touchEvent) {
|
||
|
// summary:
|
||
|
// action performed on the map when a tap was triggered
|
||
|
// touchEvent: a touch event
|
||
|
// tags:
|
||
|
// private
|
||
|
var touches = touchEvent.touches;
|
||
|
if (touches.length == 0) {
|
||
|
|
||
|
// test potential tap ?
|
||
|
if (this._oneFingerTouch && !this._tapCancel) {
|
||
|
this._oneFingerTouch = false;
|
||
|
setTimeout(lang.hitch(this,function() {
|
||
|
// wait to check if double tap
|
||
|
// perform test for single tap
|
||
|
//console.log("double tap was performed ? " + this._doubleTapPerformed);
|
||
|
if (!this._doubleTapPerformed) {
|
||
|
// test distance from last tap
|
||
|
var dx = (touchEvent.changedTouches[0].pageX - this._lastTap.x);
|
||
|
var dy = (touchEvent.changedTouches[0].pageY - this._lastTap.y);
|
||
|
var distance = dx*dx + dy*dy;
|
||
|
if (distance < this._tapThreshold) {
|
||
|
// single tap ok
|
||
|
this._singleTapHandler(touchEvent);
|
||
|
}
|
||
|
}
|
||
|
}),350);
|
||
|
}
|
||
|
this._tapCancel = false;
|
||
|
|
||
|
}
|
||
|
},
|
||
|
|
||
|
_touchEndHandler: function(touchEvent) {
|
||
|
// summary:
|
||
|
// action performed on the map when a touch end was triggered
|
||
|
// touchEvent: a touch event
|
||
|
// tags:
|
||
|
// private
|
||
|
event.stop(touchEvent);
|
||
|
var touches = touchEvent.touches;
|
||
|
if (touches.length == 0) {
|
||
|
// disconnect listeners only when all fingers are up
|
||
|
if (this._touchMoveListener) {
|
||
|
connect.disconnect(this._touchMoveListener);
|
||
|
this._touchMoveListener = null;
|
||
|
}
|
||
|
if (this._touchEndListener) {
|
||
|
connect.disconnect(this._touchEndListener);
|
||
|
this._touchEndListener = null;
|
||
|
}
|
||
|
} else {
|
||
|
// recompute touch center
|
||
|
var middlePoint = this._getTouchBarycenter(touchEvent);
|
||
|
var mapPoint = this._map.screenCoordsToMapCoords(middlePoint.x,middlePoint.y);
|
||
|
this._centerTouchLocation.x = mapPoint.x;
|
||
|
this._centerTouchLocation.y = mapPoint.y;
|
||
|
}
|
||
|
},
|
||
|
|
||
|
_singleTapHandler: function(touchEvent) {
|
||
|
// summary:
|
||
|
// action performed on the map when a single tap was triggered
|
||
|
// touchEvent: a touch event
|
||
|
// tags:
|
||
|
// private
|
||
|
var feature = this._getFeatureFromTouchEvent(touchEvent);
|
||
|
if (feature) {
|
||
|
// call feature handler
|
||
|
feature._onclickHandler(touchEvent);
|
||
|
} else {
|
||
|
// unselect all
|
||
|
for (var name in this._map.mapObj.features){
|
||
|
this._map.mapObj.features[name].select(false);
|
||
|
}
|
||
|
this._map.onFeatureClick(null);
|
||
|
}
|
||
|
},
|
||
|
|
||
|
_touchMoveHandler: function(touchEvent){
|
||
|
// summary:
|
||
|
// action performed on the map when a touch move was triggered
|
||
|
// touchEvent: a touch event
|
||
|
// tags:
|
||
|
// private
|
||
|
|
||
|
// prevent browser interaction
|
||
|
event.stop(touchEvent);
|
||
|
|
||
|
// cancel tap if moved too far from first touch location
|
||
|
if (!this._tapCancel) {
|
||
|
var dx = (touchEvent.touches[0].pageX - this._lastTap.x),
|
||
|
dy = (touchEvent.touches[0].pageY - this._lastTap.y);
|
||
|
var distance = dx*dx + dy*dy;
|
||
|
if (distance > this._tapThreshold) {
|
||
|
this._tapCancel = true;
|
||
|
}
|
||
|
}
|
||
|
var middlePoint = this._getTouchBarycenter(touchEvent);
|
||
|
// compute map offset
|
||
|
var mapPoint = this._map.screenCoordsToMapCoords(middlePoint.x,middlePoint.y),
|
||
|
mapOffsetX = mapPoint.x - this._centerTouchLocation.x,
|
||
|
mapOffsetY = mapPoint.y - this._centerTouchLocation.y;
|
||
|
// compute scale factor
|
||
|
var scaleFactor = 1;
|
||
|
var touches = touchEvent.touches;
|
||
|
if (touches.length >= 2) {
|
||
|
var fingerSpacing = this._getFingerSpacing(touchEvent);
|
||
|
scaleFactor = fingerSpacing / this._initialFingerSpacing;
|
||
|
// scale map
|
||
|
this._map.setMapScale(this._initialScale*scaleFactor);
|
||
|
}
|
||
|
// adjust map center on barycentre
|
||
|
var currentMapCenter = this._map.getMapCenter();
|
||
|
this._map.setMapCenter(currentMapCenter.x - mapOffsetX, currentMapCenter.y - mapOffsetY);
|
||
|
}
|
||
|
});
|
||
|
});
|