Fix jsdoc, add missing documentation.
[guacamole-common-js.git] / src / main / resources / mouse.js
index d23c8f7..9dc2f22 100644 (file)
  *
  * ***** END LICENSE BLOCK ***** */
 
-// Guacamole namespace
+/**
+ * Namespace for all Guacamole JavaScript objects.
+ * @namespace
+ */
 var Guacamole = Guacamole || {};
 
 /**
@@ -56,6 +59,12 @@ Guacamole.Mouse = function(element) {
     var guac_mouse = this;
 
     /**
+     * The number of mousemove events to require before re-enabling mouse
+     * event handling after receiving a touch event.
+     */
+    this.touchMouseThreshold = 3;
+
+    /**
      * The current mouse state. The properties of this state are updated when
      * mouse events fire. This state object is also passed in as a parameter to
      * the handler of any mouse events.
@@ -95,37 +104,11 @@ Guacamole.Mouse = function(element) {
        this.onmousemove = null;
 
     /**
-     * Zero-delay timeout set when mouse events are fired, and canceled when
-     * touch events are detected, in order to prevent touch events registering
-     * as mouse events (some browsers will do this).
-     */
-    var deferred_mouse_event = null;
-
-    /**
-     * Flag which, when set to true, will cause all mouse events to be
-     * ignored. Used to temporarily ignore events when generated by
-     * touch events, and not by a mouse.
-     */
-    var ignore_mouse = false;
-
-    /**
-     * Forces all mouse events to be ignored until the event queue is flushed.
+     * Counter of mouse events to ignore. This decremented by mousemove, and
+     * while non-zero, mouse events will have no effect.
+     * @private
      */
-    function ignorePendingMouseEvents() {
-
-        // Cancel deferred event
-        if (deferred_mouse_event) {
-            window.clearTimeout(deferred_mouse_event);
-            deferred_mouse_event = null;
-        }
-
-        // Ignore all other events until end of event loop
-        ignore_mouse = true;
-        window.setTimeout(function() {
-            ignore_mouse = false;
-        }, 0);
-
-    }
+    var ignore_mouse = 0;
 
     function cancelEvent(e) {
         e.stopPropagation();
@@ -133,36 +116,6 @@ Guacamole.Mouse = function(element) {
         e.returnValue = false;
     }
 
-    function moveMouse(clientX, clientY) {
-
-        guac_mouse.currentState.x = clientX - element.offsetLeft;
-        guac_mouse.currentState.y = clientY - element.offsetTop;
-
-        // This is all JUST so we can get the mouse position within the element
-        var parent = element.offsetParent;
-        while (parent && !(parent === document.body)) {
-            guac_mouse.currentState.x -= parent.offsetLeft - parent.scrollLeft;
-            guac_mouse.currentState.y -= parent.offsetTop  - parent.scrollTop;
-
-            parent = parent.offsetParent;
-        }
-
-        // Offset by document scroll amount
-        var documentScrollLeft = document.body.scrollLeft || document.documentElement.scrollLeft;
-        var documentScrollTop = document.body.scrollTop || document.documentElement.scrollTop;
-
-        guac_mouse.currentState.x -= parent.offsetLeft - documentScrollLeft;
-        guac_mouse.currentState.y -= parent.offsetTop  - documentScrollTop;
-
-        if (guac_mouse.onmousemove)
-            deferred_mouse_event = window.setTimeout(function() {
-                guac_mouse.onmousemove(guac_mouse.currentState);
-                deferred_mouse_event = null;
-            }, 0);
-
-    }
-
-
     // Block context menu so right-click gets sent properly
     element.addEventListener("contextmenu", function(e) {
         cancelEvent(e);
@@ -172,14 +125,16 @@ Guacamole.Mouse = function(element) {
 
         cancelEvent(e);
 
-        // If artificial event detected, ignore currently pending events
-        if (deferred_mouse_event)
-            ignorePendingMouseEvents();
-
-        if (ignore_mouse)
+        // If ignoring events, decrement counter
+        if (ignore_mouse) {
+            ignore_mouse--;
             return;
+        }
+
+        guac_mouse.currentState.fromClientPosition(element, e.clientX, e.clientY);
 
-        moveMouse(e.clientX, e.clientY);
+        if (guac_mouse.onmousemove)
+            guac_mouse.onmousemove(guac_mouse.currentState);
 
     }, false);
 
@@ -187,10 +142,7 @@ Guacamole.Mouse = function(element) {
 
         cancelEvent(e);
 
-        // If artificial event detected, ignore currently pending events
-        if (deferred_mouse_event)
-            ignorePendingMouseEvents();
-
+        // Do not handle if ignoring events
         if (ignore_mouse)
             return;
 
@@ -207,10 +159,7 @@ Guacamole.Mouse = function(element) {
         }
 
         if (guac_mouse.onmousedown)
-            deferred_mouse_event = window.setTimeout(function() {
-                guac_mouse.onmousedown(guac_mouse.currentState);
-                deferred_mouse_event = null;
-            }, 0);
+            guac_mouse.onmousedown(guac_mouse.currentState);
 
     }, false);
 
@@ -218,10 +167,7 @@ Guacamole.Mouse = function(element) {
 
         cancelEvent(e);
 
-        // If artificial event detected, ignore currently pending events
-        if (deferred_mouse_event)
-            ignorePendingMouseEvents();
-
+        // Do not handle if ignoring events
         if (ignore_mouse)
             return;
 
@@ -238,10 +184,7 @@ Guacamole.Mouse = function(element) {
         }
 
         if (guac_mouse.onmouseup)
-            deferred_mouse_event = window.setTimeout(function() {
-                guac_mouse.onmouseup(guac_mouse.currentState);
-                deferred_mouse_event = null;
-            }, 0);
+            guac_mouse.onmouseup(guac_mouse.currentState);
 
     }, false);
 
@@ -280,8 +223,9 @@ Guacamole.Mouse = function(element) {
         cancelEvent(e);
     }, false);
 
-
     // Ignore all pending mouse events when touch events are the apparent source
+    function ignorePendingMouseEvents() { ignore_mouse = guac_mouse.touchMouseThreshold; }
+
     element.addEventListener("touchmove",  ignorePendingMouseEvents, false);
     element.addEventListener("touchstart", ignorePendingMouseEvents, false);
     element.addEventListener("touchend",   ignorePendingMouseEvents, false);
@@ -700,8 +644,7 @@ Guacamole.Mouse.Touchscreen = function(element) {
 
         // Update state
         guac_touchscreen.currentState.left = true;
-        guac_touchscreen.currentState.x = touch.clientX;
-        guac_touchscreen.currentState.y = touch.clientY;
+        guac_touchscreen.currentState.fromClientPosition(element, touch.clientX, touch.clientY);
 
         // Fire press event, if defined
         if (guac_touchscreen.onmousedown)
@@ -723,8 +666,7 @@ Guacamole.Mouse.Touchscreen = function(element) {
         var touch = e.touches[0];
 
         // Update state
-        guac_touchscreen.currentState.x = touch.clientX;
-        guac_touchscreen.currentState.y = touch.clientY;
+        guac_touchscreen.currentState.fromClientPosition(element, touch.clientX, touch.clientY);
 
         // Fire movement event, if defined
         if (guac_touchscreen.onmousemove)
@@ -751,6 +693,12 @@ Guacamole.Mouse.Touchscreen = function(element) {
 Guacamole.Mouse.State = function(x, y, left, middle, right, up, down) {
 
     /**
+     * Reference to this Guacamole.Mouse.State.
+     * @private
+     */
+    var guac_state = this;
+
+    /**
      * The current X position of the mouse pointer.
      * @type Number
      */
@@ -795,6 +743,43 @@ Guacamole.Mouse.State = function(x, y, left, middle, right, up, down) {
      * @type Boolean
      */
     this.down = down;
+
+    /**
+     * Updates the position represented within this state object by the given
+     * element and clientX/clientY coordinates (commonly available within event
+     * objects). Position is translated from clientX/clientY (relative to
+     * viewport) to element-relative coordinates.
+     * 
+     * @param {Element} element The element the coordinates should be relative
+     *                          to.
+     * @param {Number} clientX The X coordinate to translate, viewport-relative.
+     * @param {Number} clientY The Y coordinate to translate, viewport-relative.
+     */
+    this.fromClientPosition = function(element, clientX, clientY) {
     
+        guac_state.x = clientX - element.offsetLeft;
+        guac_state.y = clientY - element.offsetTop;
+
+        // This is all JUST so we can get the mouse position within the element
+        var parent = element.offsetParent;
+        while (parent && !(parent === document.body)) {
+            guac_state.x -= parent.offsetLeft - parent.scrollLeft;
+            guac_state.y -= parent.offsetTop  - parent.scrollTop;
+
+            parent = parent.offsetParent;
+        }
+
+        // Element ultimately depends on positioning within document body,
+        // take document scroll into account. 
+        if (parent) {
+            var documentScrollLeft = document.body.scrollLeft || document.documentElement.scrollLeft;
+            var documentScrollTop = document.body.scrollTop || document.documentElement.scrollTop;
+
+            guac_state.x -= parent.offsetLeft - documentScrollLeft;
+            guac_state.y -= parent.offsetTop  - documentScrollTop;
+        }
+
+    };
+
 };