Changed simulated mouse to simulated touchpad. Temporary lack of tap support.
[guacamole-common-js.git] / src / main / resources / mouse.js
1
2 /*
3  *  Guacamole - Clientless Remote Desktop
4  *  Copyright (C) 2010  Michael Jumper
5  *
6  *  This program is free software: you can redistribute it and/or modify
7  *  it under the terms of the GNU Affero General Public License as published by
8  *  the Free Software Foundation, either version 3 of the License, or
9  *  (at your option) any later version.
10  *
11  *  This program is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *  GNU Affero General Public License for more details.
15  *
16  *  You should have received a copy of the GNU Affero General Public License
17  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  */
19
20 // Guacamole namespace
21 var Guacamole = Guacamole || {};
22
23 /**
24  * Provides cross-browser mouse events for a given element. The events of
25  * the given element are automatically populated with handlers that translate
26  * mouse events into a non-browser-specific event provided by the
27  * Guacamole.Mouse instance.
28  * 
29  * Touch event support is planned, but currently only in testing (translate
30  * touch events into mouse events).
31  * 
32  * @constructor
33  * @param {Element} element The Element to use to provide mouse events.
34  */
35 Guacamole.Mouse = function(element) {
36
37     /**
38      * Reference to this Guacamole.Mouse.
39      * @private
40      */
41     var guac_mouse = this;
42
43     /**
44      * The current mouse state. The properties of this state are updated when
45      * mouse events fire. This state object is also passed in as a parameter to
46      * the handler of any mouse events.
47      * 
48      * @type Guacamole.Mouse.State
49      */
50     this.currentState = new Guacamole.Mouse.State(
51         0, 0, 
52         false, false, false, false, false
53     );
54
55     /**
56      * Fired whenever the user presses a mouse button down over the element
57      * associated with this Guacamole.Mouse.
58      * 
59      * @event
60      * @param {Guacamole.Mouse.State} state The current mouse state.
61      */
62         this.onmousedown = null;
63
64     /**
65      * Fired whenever the user releases a mouse button down over the element
66      * associated with this Guacamole.Mouse.
67      * 
68      * @event
69      * @param {Guacamole.Mouse.State} state The current mouse state.
70      */
71         this.onmouseup = null;
72
73     /**
74      * Fired whenever the user moves the mouse over the element associated with
75      * this Guacamole.Mouse.
76      * 
77      * @event
78      * @param {Guacamole.Mouse.State} state The current mouse state.
79      */
80         this.onmousemove = null;
81
82     function moveMouse(pageX, pageY) {
83
84         guac_mouse.currentState.x = pageX - element.offsetLeft;
85         guac_mouse.currentState.y = pageY - element.offsetTop;
86
87         // This is all JUST so we can get the mouse position within the element
88         var parent = element.offsetParent;
89         while (parent) {
90             if (parent.offsetLeft && parent.offsetTop) {
91                 guac_mouse.currentState.x -= parent.offsetLeft;
92                 guac_mouse.currentState.y -= parent.offsetTop;
93             }
94             parent = parent.offsetParent;
95         }
96
97         if (guac_mouse.onmousemove)
98             guac_mouse.onmousemove(guac_mouse.currentState);
99
100     }
101
102
103     // Block context menu so right-click gets sent properly
104     element.oncontextmenu = function(e) {
105         return false;
106     };
107
108     element.onmousemove = function(e) {
109
110         e.stopPropagation();
111
112         moveMouse(e.pageX, e.pageY);
113
114     };
115
116     var last_touch_x = 0;
117     var last_touch_y = 0;
118
119     element.ontouchend = function(e) {
120         
121         e.stopPropagation();
122         e.preventDefault();
123
124         // TODO: Handle tap-to-click.
125
126     };
127
128     element.ontouchstart = function(e) {
129
130         e.stopPropagation();
131         e.preventDefault();
132
133         // Record initial touch location and time for single-touch movement
134         // and tap gestures
135         if (e.touches.length == 1) {
136
137             var starting_touch = e.touches[0];
138             last_touch_x = starting_touch.pageX;
139             last_touch_y = starting_touch.pageY;
140
141             // TODO: Record time (for sake of tap-to-click)
142
143         }
144
145     };
146
147     element.ontouchmove = function(e) {
148
149         e.stopPropagation();
150         e.preventDefault();
151
152         // Handle single-touch movement gesture (touchpad mouse move)
153         if (e.touches.length == 1) {
154
155             // Get change in touch location
156             var touch = e.touches[0];
157             var delta_x = touch.pageX - last_touch_x;
158             var delta_y = touch.pageY - last_touch_y;
159
160             // Update mouse location
161             guac_mouse.currentState.x += delta_x;
162             guac_mouse.currentState.y += delta_y;
163
164             // FIXME: Prevent mouse from leaving screen
165
166             // Fire movement event, if defined
167             if (guac_mouse.onmousemove)
168                 guac_mouse.onmousemove(guac_mouse.currentState);
169
170             // Update touch location
171             last_touch_x = touch.pageX;
172             last_touch_y = touch.pageY;
173
174         }
175
176     };
177
178
179     element.onmousedown = function(e) {
180
181         e.stopPropagation();
182
183         switch (e.button) {
184             case 0:
185                 guac_mouse.currentState.left = true;
186                 break;
187             case 1:
188                 guac_mouse.currentState.middle = true;
189                 break;
190             case 2:
191                 guac_mouse.currentState.right = true;
192                 break;
193         }
194
195         if (guac_mouse.onmousedown)
196             guac_mouse.onmousedown(guac_mouse.currentState);
197
198     };
199
200
201     element.onmouseup = function(e) {
202
203         e.stopPropagation();
204
205         switch (e.button) {
206             case 0:
207                 guac_mouse.currentState.left = false;
208                 break;
209             case 1:
210                 guac_mouse.currentState.middle = false;
211                 break;
212             case 2:
213                 guac_mouse.currentState.right = false;
214                 break;
215         }
216
217         if (guac_mouse.onmouseup)
218             guac_mouse.onmouseup(guac_mouse.currentState);
219
220     };
221
222     element.onmouseout = function(e) {
223
224         e.stopPropagation();
225
226         // Release all buttons
227         if (guac_mouse.currentState.left
228             || guac_mouse.currentState.middle
229             || guac_mouse.currentState.right) {
230
231             guac_mouse.currentState.left = false;
232             guac_mouse.currentState.middle = false;
233             guac_mouse.currentState.right = false;
234
235             if (guac_mouse.onmouseup)
236                 guac_mouse.onmouseup(guac_mouse.currentState);
237         }
238
239     };
240
241     // Override selection on mouse event element.
242     element.onselectstart = function() {
243         return false;
244     };
245
246     // Scroll wheel support
247     function handleScroll(e) {
248
249         var delta = 0;
250         if (e.detail)
251             delta = e.detail;
252         else if (e.wheelDelta)
253             delta = -event.wheelDelta;
254
255         // Up
256         if (delta < 0) {
257             if (guac_mouse.onmousedown) {
258                 guac_mouse.currentState.up = true;
259                 guac_mouse.onmousedown(guac_mouse.currentState);
260             }
261
262             if (guac_mouse.onmouseup) {
263                 guac_mouse.currentState.up = false;
264                 guac_mouse.onmouseup(guac_mouse.currentState);
265             }
266         }
267
268         // Down
269         if (delta > 0) {
270             if (guac_mouse.onmousedown) {
271                 guac_mouse.currentState.down = true;
272                 guac_mouse.onmousedown(guac_mouse.currentState);
273             }
274
275             if (guac_mouse.onmouseup) {
276                 guac_mouse.currentState.down = false;
277                 guac_mouse.onmouseup(guac_mouse.currentState);
278             }
279         }
280
281         if (e.preventDefault)
282             e.preventDefault();
283
284         e.returnValue = false;
285     }
286
287     element.addEventListener('DOMMouseScroll', handleScroll, false);
288
289     element.onmousewheel = function(e) {
290         handleScroll(e);
291     };
292
293 };
294
295 /**
296  * Simple container for properties describing the state of a mouse.
297  * 
298  * @constructor
299  * @param {Number} x The X position of the mouse pointer in pixels.
300  * @param {Number} y The Y position of the mouse pointer in pixels.
301  * @param {Boolean} left Whether the left mouse button is pressed. 
302  * @param {Boolean} middle Whether the middle mouse button is pressed. 
303  * @param {Boolean} right Whether the right mouse button is pressed. 
304  * @param {Boolean} up Whether the up mouse button is pressed (the fourth
305  *                     button, usually part of a scroll wheel). 
306  * @param {Boolean} down Whether the down mouse button is pressed (the fifth
307  *                       button, usually part of a scroll wheel). 
308  */
309 Guacamole.Mouse.State = function(x, y, left, middle, right, up, down) {
310
311     /**
312      * The current X position of the mouse pointer.
313      * @type Number
314      */
315     this.x = x;
316
317     /**
318      * The current Y position of the mouse pointer.
319      * @type Number
320      */
321     this.y = y;
322
323     /**
324      * Whether the left mouse button is currently pressed.
325      * @type Boolean
326      */
327     this.left = left;
328
329     /**
330      * Whether the middle mouse button is currently pressed.
331      * @type Boolean
332      */
333     this.middle = middle
334
335     /**
336      * Whether the right mouse button is currently pressed.
337      * @type Boolean
338      */
339     this.right = right;
340
341     /**
342      * Whether the up mouse button is currently pressed. This is the fourth
343      * mouse button, associated with upward scrolling of the mouse scroll
344      * wheel.
345      * @type Boolean
346      */
347     this.up = up;
348
349     /**
350      * Whether the down mouse button is currently pressed. This is the fifth 
351      * mouse button, associated with downward scrolling of the mouse scroll
352      * wheel.
353      * @type Boolean
354      */
355     this.down = down;
356     
357 };
358