diff --git a/index.html b/index.html
index ebe4ad8..78e532c 100644
--- a/index.html
+++ b/index.html
@@ -17,6 +17,8 @@
+
+
diff --git a/lib/jquery.event.move.js b/lib/jquery.event.move.js
new file mode 100644
index 0000000..a69242e
--- /dev/null
+++ b/lib/jquery.event.move.js
@@ -0,0 +1,580 @@
+// jquery.event.move
+//
+// 1.3.1
+//
+// Stephen Band
+//
+// Triggers 'movestart', 'move' and 'moveend' events after
+// mousemoves following a mousedown cross a distance threshold,
+// similar to the native 'dragstart', 'drag' and 'dragend' events.
+// Move events are throttled to animation frames. Move event objects
+// have the properties:
+//
+// pageX:
+// pageY: Page coordinates of pointer.
+// startX:
+// startY: Page coordinates of pointer at movestart.
+// distX:
+// distY: Distance the pointer has moved since movestart.
+// deltaX:
+// deltaY: Distance the finger has moved since last event.
+// velocityX:
+// velocityY: Average velocity over last few events.
+
+
+(function (module) {
+ if (typeof define === 'function' && define.amd) {
+ // AMD. Register as an anonymous module.
+ define(['jquery'], module);
+ } else {
+ // Browser globals
+ module(jQuery);
+ }
+})(function(jQuery, undefined){
+
+ var // Number of pixels a pressed pointer travels before movestart
+ // event is fired.
+ threshold = 6,
+
+ add = jQuery.event.add,
+
+ remove = jQuery.event.remove,
+
+ // Just sugar, so we can have arguments in the same order as
+ // add and remove.
+ trigger = function(node, type, data) {
+ jQuery.event.trigger(type, data, node);
+ },
+
+ // Shim for requestAnimationFrame, falling back to timer. See:
+ // see http://paulirish.com/2011/requestanimationframe-for-smart-animating/
+ requestFrame = (function(){
+ return (
+ window.requestAnimationFrame ||
+ window.webkitRequestAnimationFrame ||
+ window.mozRequestAnimationFrame ||
+ window.oRequestAnimationFrame ||
+ window.msRequestAnimationFrame ||
+ function(fn, element){
+ return window.setTimeout(function(){
+ fn();
+ }, 25);
+ }
+ );
+ })(),
+
+ ignoreTags = {
+ textarea: true,
+ input: true,
+ select: true,
+ button: true
+ },
+
+ mouseevents = {
+ move: 'mousemove',
+ cancel: 'mouseup dragstart',
+ end: 'mouseup'
+ },
+
+ touchevents = {
+ move: 'touchmove',
+ cancel: 'touchend',
+ end: 'touchend'
+ };
+
+
+ // Constructors
+
+ function Timer(fn){
+ var callback = fn,
+ active = false,
+ running = false;
+
+ function trigger(time) {
+ if (active){
+ callback();
+ requestFrame(trigger);
+ running = true;
+ active = false;
+ }
+ else {
+ running = false;
+ }
+ }
+
+ this.kick = function(fn) {
+ active = true;
+ if (!running) { trigger(); }
+ };
+
+ this.end = function(fn) {
+ var cb = callback;
+
+ if (!fn) { return; }
+
+ // If the timer is not running, simply call the end callback.
+ if (!running) {
+ fn();
+ }
+ // If the timer is running, and has been kicked lately, then
+ // queue up the current callback and the end callback, otherwise
+ // just the end callback.
+ else {
+ callback = active ?
+ function(){ cb(); fn(); } :
+ fn ;
+
+ active = true;
+ }
+ };
+ }
+
+
+ // Functions
+
+ function returnTrue() {
+ return true;
+ }
+
+ function returnFalse() {
+ return false;
+ }
+
+ function preventDefault(e) {
+ e.preventDefault();
+ }
+
+ function preventIgnoreTags(e) {
+ // Don't prevent interaction with form elements.
+ if (ignoreTags[ e.target.tagName.toLowerCase() ]) { return; }
+
+ e.preventDefault();
+ }
+
+ function isLeftButton(e) {
+ // Ignore mousedowns on any button other than the left (or primary)
+ // mouse button, or when a modifier key is pressed.
+ return (e.which === 1 && !e.ctrlKey && !e.altKey);
+ }
+
+ function identifiedTouch(touchList, id) {
+ var i, l;
+
+ if (touchList.identifiedTouch) {
+ return touchList.identifiedTouch(id);
+ }
+
+ // touchList.identifiedTouch() does not exist in
+ // webkit yet… we must do the search ourselves...
+
+ i = -1;
+ l = touchList.length;
+
+ while (++i < l) {
+ if (touchList[i].identifier === id) {
+ return touchList[i];
+ }
+ }
+ }
+
+ function changedTouch(e, event) {
+ var touch = identifiedTouch(e.changedTouches, event.identifier);
+
+ // This isn't the touch you're looking for.
+ if (!touch) { return; }
+
+ // Chrome Android (at least) includes touches that have not
+ // changed in e.changedTouches. That's a bit annoying. Check
+ // that this touch has changed.
+ if (touch.pageX === event.pageX && touch.pageY === event.pageY) { return; }
+
+ return touch;
+ }
+
+
+ // Handlers that decide when the first movestart is triggered
+
+ function mousedown(e){
+ var data;
+
+ if (!isLeftButton(e)) { return; }
+
+ data = {
+ target: e.target,
+ startX: e.pageX,
+ startY: e.pageY,
+ timeStamp: e.timeStamp
+ };
+
+ add(document, mouseevents.move, mousemove, data);
+ add(document, mouseevents.cancel, mouseend, data);
+ }
+
+ function mousemove(e){
+ var data = e.data;
+
+ checkThreshold(e, data, e, removeMouse);
+ }
+
+ function mouseend(e) {
+ removeMouse();
+ }
+
+ function removeMouse() {
+ remove(document, mouseevents.move, mousemove);
+ remove(document, mouseevents.cancel, mouseend);
+ }
+
+ function touchstart(e) {
+ var touch, template;
+
+ // Don't get in the way of interaction with form elements.
+ if (ignoreTags[ e.target.tagName.toLowerCase() ]) { return; }
+
+ touch = e.changedTouches[0];
+
+ // iOS live updates the touch objects whereas Android gives us copies.
+ // That means we can't trust the touchstart object to stay the same,
+ // so we must copy the data. This object acts as a template for
+ // movestart, move and moveend event objects.
+ template = {
+ target: touch.target,
+ startX: touch.pageX,
+ startY: touch.pageY,
+ timeStamp: e.timeStamp,
+ identifier: touch.identifier
+ };
+
+ // Use the touch identifier as a namespace, so that we can later
+ // remove handlers pertaining only to this touch.
+ add(document, touchevents.move + '.' + touch.identifier, touchmove, template);
+ add(document, touchevents.cancel + '.' + touch.identifier, touchend, template);
+ }
+
+ function touchmove(e){
+ var data = e.data,
+ touch = changedTouch(e, data);
+
+ if (!touch) { return; }
+
+ checkThreshold(e, data, touch, removeTouch);
+ }
+
+ function touchend(e) {
+ var template = e.data,
+ touch = identifiedTouch(e.changedTouches, template.identifier);
+
+ if (!touch) { return; }
+
+ removeTouch(template.identifier);
+ }
+
+ function removeTouch(identifier) {
+ remove(document, '.' + identifier, touchmove);
+ remove(document, '.' + identifier, touchend);
+ }
+
+
+ // Logic for deciding when to trigger a movestart.
+
+ function checkThreshold(e, template, touch, fn) {
+ var distX = touch.pageX - template.startX,
+ distY = touch.pageY - template.startY;
+
+ // Do nothing if the threshold has not been crossed.
+ if ((distX * distX) + (distY * distY) < (threshold * threshold)) { return; }
+
+ triggerStart(e, template, touch, distX, distY, fn);
+ }
+
+ function handled() {
+ // this._handled should return false once, and after return true.
+ this._handled = returnTrue;
+ return false;
+ }
+
+ function flagAsHandled(e) {
+ e._handled();
+ }
+
+ function triggerStart(e, template, touch, distX, distY, fn) {
+ var node = template.target,
+ touches, time;
+
+ touches = e.targetTouches;
+ time = e.timeStamp - template.timeStamp;
+
+ // Create a movestart object with some special properties that
+ // are passed only to the movestart handlers.
+ template.type = 'movestart';
+ template.distX = distX;
+ template.distY = distY;
+ template.deltaX = distX;
+ template.deltaY = distY;
+ template.pageX = touch.pageX;
+ template.pageY = touch.pageY;
+ template.velocityX = distX / time;
+ template.velocityY = distY / time;
+ template.targetTouches = touches;
+ template.finger = touches ?
+ touches.length :
+ 1 ;
+
+ // The _handled method is fired to tell the default movestart
+ // handler that one of the move events is bound.
+ template._handled = handled;
+
+ // Pass the touchmove event so it can be prevented if or when
+ // movestart is handled.
+ template._preventTouchmoveDefault = function() {
+ e.preventDefault();
+ };
+
+ // Trigger the movestart event.
+ trigger(template.target, template);
+
+ // Unbind handlers that tracked the touch or mouse up till now.
+ fn(template.identifier);
+ }
+
+
+ // Handlers that control what happens following a movestart
+
+ function activeMousemove(e) {
+ var event = e.data.event,
+ timer = e.data.timer;
+
+ updateEvent(event, e, e.timeStamp, timer);
+ }
+
+ function activeMouseend(e) {
+ var event = e.data.event,
+ timer = e.data.timer;
+
+ removeActiveMouse();
+
+ endEvent(event, timer, function() {
+ // Unbind the click suppressor, waiting until after mouseup
+ // has been handled.
+ setTimeout(function(){
+ remove(event.target, 'click', returnFalse);
+ }, 0);
+ });
+ }
+
+ function removeActiveMouse(event) {
+ remove(document, mouseevents.move, activeMousemove);
+ remove(document, mouseevents.end, activeMouseend);
+ }
+
+ function activeTouchmove(e) {
+ var event = e.data.event,
+ timer = e.data.timer,
+ touch = changedTouch(e, event);
+
+ if (!touch) { return; }
+
+ // Stop the interface from gesturing
+ e.preventDefault();
+
+ event.targetTouches = e.targetTouches;
+ updateEvent(event, touch, e.timeStamp, timer);
+ }
+
+ function activeTouchend(e) {
+ var event = e.data.event,
+ timer = e.data.timer,
+ touch = identifiedTouch(e.changedTouches, event.identifier);
+
+ // This isn't the touch you're looking for.
+ if (!touch) { return; }
+
+ removeActiveTouch(event);
+ endEvent(event, timer);
+ }
+
+ function removeActiveTouch(event) {
+ remove(document, '.' + event.identifier, activeTouchmove);
+ remove(document, '.' + event.identifier, activeTouchend);
+ }
+
+
+ // Logic for triggering move and moveend events
+
+ function updateEvent(event, touch, timeStamp, timer) {
+ var time = timeStamp - event.timeStamp;
+
+ event.type = 'move';
+ event.distX = touch.pageX - event.startX;
+ event.distY = touch.pageY - event.startY;
+ event.deltaX = touch.pageX - event.pageX;
+ event.deltaY = touch.pageY - event.pageY;
+
+ // Average the velocity of the last few events using a decay
+ // curve to even out spurious jumps in values.
+ event.velocityX = 0.3 * event.velocityX + 0.7 * event.deltaX / time;
+ event.velocityY = 0.3 * event.velocityY + 0.7 * event.deltaY / time;
+ event.pageX = touch.pageX;
+ event.pageY = touch.pageY;
+
+ timer.kick();
+ }
+
+ function endEvent(event, timer, fn) {
+ timer.end(function(){
+ event.type = 'moveend';
+
+ trigger(event.target, event);
+
+ return fn && fn();
+ });
+ }
+
+
+ // jQuery special event definition
+
+ function setup(data, namespaces, eventHandle) {
+ // Stop the node from being dragged
+ //add(this, 'dragstart.move drag.move', preventDefault);
+
+ // Prevent text selection and touch interface scrolling
+ //add(this, 'mousedown.move', preventIgnoreTags);
+
+ // Tell movestart default handler that we've handled this
+ add(this, 'movestart.move', flagAsHandled);
+
+ // Don't bind to the DOM. For speed.
+ return true;
+ }
+
+ function teardown(namespaces) {
+ remove(this, 'dragstart drag', preventDefault);
+ remove(this, 'mousedown touchstart', preventIgnoreTags);
+ remove(this, 'movestart', flagAsHandled);
+
+ // Don't bind to the DOM. For speed.
+ return true;
+ }
+
+ function addMethod(handleObj) {
+ // We're not interested in preventing defaults for handlers that
+ // come from internal move or moveend bindings
+ if (handleObj.namespace === "move" || handleObj.namespace === "moveend") {
+ return;
+ }
+
+ // Stop the node from being dragged
+ add(this, 'dragstart.' + handleObj.guid + ' drag.' + handleObj.guid, preventDefault, undefined, handleObj.selector);
+
+ // Prevent text selection and touch interface scrolling
+ add(this, 'mousedown.' + handleObj.guid, preventIgnoreTags, undefined, handleObj.selector);
+ }
+
+ function removeMethod(handleObj) {
+ if (handleObj.namespace === "move" || handleObj.namespace === "moveend") {
+ return;
+ }
+
+ remove(this, 'dragstart.' + handleObj.guid + ' drag.' + handleObj.guid);
+ remove(this, 'mousedown.' + handleObj.guid);
+ }
+
+ jQuery.event.special.movestart = {
+ setup: setup,
+ teardown: teardown,
+ add: addMethod,
+ remove: removeMethod,
+
+ _default: function(e) {
+ var template, data;
+
+ // If no move events were bound to any ancestors of this
+ // target, high tail it out of here.
+ if (!e._handled()) { return; }
+
+ template = {
+ target: e.target,
+ startX: e.startX,
+ startY: e.startY,
+ pageX: e.pageX,
+ pageY: e.pageY,
+ distX: e.distX,
+ distY: e.distY,
+ deltaX: e.deltaX,
+ deltaY: e.deltaY,
+ velocityX: e.velocityX,
+ velocityY: e.velocityY,
+ timeStamp: e.timeStamp,
+ identifier: e.identifier,
+ targetTouches: e.targetTouches,
+ finger: e.finger
+ };
+
+ data = {
+ event: template,
+ timer: new Timer(function(time){
+ trigger(e.target, template);
+ })
+ };
+
+ if (e.identifier === undefined) {
+ // We're dealing with a mouse
+ // Stop clicks from propagating during a move
+ add(e.target, 'click', returnFalse);
+ add(document, mouseevents.move, activeMousemove, data);
+ add(document, mouseevents.end, activeMouseend, data);
+ }
+ else {
+ // We're dealing with a touch. Stop touchmove doing
+ // anything defaulty.
+ e._preventTouchmoveDefault();
+ add(document, touchevents.move + '.' + e.identifier, activeTouchmove, data);
+ add(document, touchevents.end + '.' + e.identifier, activeTouchend, data);
+ }
+ }
+ };
+
+ jQuery.event.special.move = {
+ setup: function() {
+ // Bind a noop to movestart. Why? It's the movestart
+ // setup that decides whether other move events are fired.
+ add(this, 'movestart.move', jQuery.noop);
+ },
+
+ teardown: function() {
+ remove(this, 'movestart.move', jQuery.noop);
+ }
+ };
+
+ jQuery.event.special.moveend = {
+ setup: function() {
+ // Bind a noop to movestart. Why? It's the movestart
+ // setup that decides whether other move events are fired.
+ add(this, 'movestart.moveend', jQuery.noop);
+ },
+
+ teardown: function() {
+ remove(this, 'movestart.moveend', jQuery.noop);
+ }
+ };
+
+ add(document, 'mousedown.move', mousedown);
+ add(document, 'touchstart.move', touchstart);
+
+ // Make jQuery copy touch event properties over to the jQuery event
+ // object, if they are not already listed. But only do the ones we
+ // really need. IE7/8 do not have Array#indexOf(), but nor do they
+ // have touch events, so let's assume we can ignore them.
+ if (typeof Array.prototype.indexOf === 'function') {
+ (function(jQuery, undefined){
+ var props = ["changedTouches", "targetTouches"],
+ l = props.length;
+
+ while (l--) {
+ if (jQuery.event.props.indexOf(props[l]) === -1) {
+ jQuery.event.props.push(props[l]);
+ }
+ }
+ })(jQuery);
+ };
+});
diff --git a/lib/jquery.event.swipe.js b/lib/jquery.event.swipe.js
new file mode 100644
index 0000000..d078cff
--- /dev/null
+++ b/lib/jquery.event.swipe.js
@@ -0,0 +1,130 @@
+// jQuery.event.swipe
+// 0.5
+// Stephen Band
+
+// Dependencies
+// jQuery.event.move 1.2
+
+// One of swipeleft, swiperight, swipeup or swipedown is triggered on
+// moveend, when the move has covered a threshold ratio of the dimension
+// of the target node, or has gone really fast. Threshold and velocity
+// sensitivity changed with:
+//
+// jQuery.event.special.swipe.settings.threshold
+// jQuery.event.special.swipe.settings.sensitivity
+
+(function (module) {
+ if (typeof define === 'function' && define.amd) {
+ // AMD. Register as an anonymous module.
+ define(['jquery'], module);
+ } else {
+ // Browser globals
+ module(jQuery);
+ }
+})(function(jQuery, undefined){
+ var add = jQuery.event.add,
+
+ remove = jQuery.event.remove,
+
+ // Just sugar, so we can have arguments in the same order as
+ // add and remove.
+ trigger = function(node, type, data) {
+ jQuery.event.trigger(type, data, node);
+ },
+
+ settings = {
+ // Ratio of distance over target finger must travel to be
+ // considered a swipe.
+ threshold: 0.4,
+ // Faster fingers can travel shorter distances to be considered
+ // swipes. 'sensitivity' controls how much. Bigger is shorter.
+ sensitivity: 6
+ };
+
+ function moveend(e) {
+ var w, h, event;
+
+ w = e.target.offsetWidth;
+ h = e.target.offsetHeight;
+
+ // Copy over some useful properties from the move event
+ event = {
+ distX: e.distX,
+ distY: e.distY,
+ velocityX: e.velocityX,
+ velocityY: e.velocityY,
+ finger: e.finger
+ };
+
+ // Find out which of the four directions was swiped
+ if (e.distX > e.distY) {
+ if (e.distX > -e.distY) {
+ if (e.distX/w > settings.threshold || e.velocityX * e.distX/w * settings.sensitivity > 1) {
+ event.type = 'swiperight';
+ trigger(e.currentTarget, event);
+ }
+ }
+ else {
+ if (-e.distY/h > settings.threshold || e.velocityY * e.distY/w * settings.sensitivity > 1) {
+ event.type = 'swipeup';
+ trigger(e.currentTarget, event);
+ }
+ }
+ }
+ else {
+ if (e.distX > -e.distY) {
+ if (e.distY/h > settings.threshold || e.velocityY * e.distY/w * settings.sensitivity > 1) {
+ event.type = 'swipedown';
+ trigger(e.currentTarget, event);
+ }
+ }
+ else {
+ if (-e.distX/w > settings.threshold || e.velocityX * e.distX/w * settings.sensitivity > 1) {
+ event.type = 'swipeleft';
+ trigger(e.currentTarget, event);
+ }
+ }
+ }
+ }
+
+ function getData(node) {
+ var data = jQuery.data(node, 'event_swipe');
+
+ if (!data) {
+ data = { count: 0 };
+ jQuery.data(node, 'event_swipe', data);
+ }
+
+ return data;
+ }
+
+ jQuery.event.special.swipe =
+ jQuery.event.special.swipeleft =
+ jQuery.event.special.swiperight =
+ jQuery.event.special.swipeup =
+ jQuery.event.special.swipedown = {
+ setup: function( data, namespaces, eventHandle ) {
+ var data = getData(this);
+
+ // If another swipe event is already setup, don't setup again.
+ if (data.count++ > 0) { return; }
+
+ add(this, 'moveend', moveend);
+
+ return true;
+ },
+
+ teardown: function() {
+ var data = getData(this);
+
+ // If another swipe event is still setup, don't teardown.
+ if (--data.count > 0) { return; }
+
+ remove(this, 'moveend', moveend);
+
+ return true;
+ },
+
+ settings: settings
+ };
+});
\ No newline at end of file
diff --git a/script/engine.js b/script/engine.js
index 0455750..a94294b 100644
--- a/script/engine.js
+++ b/script/engine.js
@@ -104,7 +104,14 @@ var Engine = {
// Register keypress handlers
$('body').off('keydown').keydown(Engine.keyDown);
$('body').off('keyup').keyup(Engine.keyUp);
-
+
+ // Register swipe handlers
+ swipeElement = $('#outerSlider');
+ swipeElement.on('swipeleft', Engine.swipeLeft);
+ swipeElement.on('swiperight', Engine.swipeRight);
+ swipeElement.on('swipeup', Engine.swipeUp);
+ swipeElement.on('swipedown', Engine.swipeDown);
+
Notifications.init();
Events.init();
Room.init();
@@ -542,6 +549,30 @@ var Engine = {
Engine.activeModule.keyUp(e);
}
return false;
+ },
+
+ swipeLeft: function(e) {
+ if(Engine.activeModule.swipeLeft) {
+ Engine.activeModule.swipeLeft(e);
+ }
+ },
+
+ swipeRight: function(e) {
+ if(Engine.activeModule.swipeRight) {
+ Engine.activeModule.swipeRight(e);
+ }
+ },
+
+ swipeUp: function(e) {
+ if(Engine.activeModule.swipeUp) {
+ Engine.activeModule.swipeUp(e);
+ }
+ },
+
+ swipeDown: function(e) {
+ if(Engine.activeModule.swipeDown) {
+ Engine.activeModule.swipeDown(e);
+ }
}
};
diff --git a/script/world.js b/script/world.js
index 7ad8167..46f1a5b 100644
--- a/script/world.js
+++ b/script/world.js
@@ -316,7 +316,23 @@ var World = {
break;
}
},
-
+
+ swipeLeft: function(e) {
+ World.moveWest();
+ },
+
+ swipeRight: function(e) {
+ World.moveEast();
+ },
+
+ swipeUp: function(e) {
+ World.moveNorth();
+ },
+
+ swipeDown: function(e) {
+ World.moveSouth();
+ },
+
click: function(event) {
var map = $('#map'),
// measure clicks relative to the centre of the current location