source: OpenRLabs-Git/web2py/applications/rlabs/static/js/guacamole-common-js/modules/Mouse.js

main
Last change on this file was 42bd667, checked in by David Fuertes <dfuertes@…>, 4 years ago

Historial Limpio

  • Property mode set to 100644
File size: 33.0 KB
Line 
1/*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements.  See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership.  The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License.  You may obtain a copy of the License at
9 *
10 *   http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied.  See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19
20var Guacamole = Guacamole || {};
21
22/**
23 * Provides cross-browser mouse events for a given element. The events of
24 * the given element are automatically populated with handlers that translate
25 * mouse events into a non-browser-specific event provided by the
26 * Guacamole.Mouse instance.
27 *
28 * @constructor
29 * @param {Element} element The Element to use to provide mouse events.
30 */
31Guacamole.Mouse = function(element) {
32
33    /**
34     * Reference to this Guacamole.Mouse.
35     * @private
36     */
37    var guac_mouse = this;
38
39    /**
40     * The number of mousemove events to require before re-enabling mouse
41     * event handling after receiving a touch event.
42     */
43    this.touchMouseThreshold = 3;
44
45    /**
46     * The minimum amount of pixels scrolled required for a single scroll button
47     * click.
48     */
49    this.scrollThreshold = 53;
50
51    /**
52     * The number of pixels to scroll per line.
53     */
54    this.PIXELS_PER_LINE = 18;
55
56    /**
57     * The number of pixels to scroll per page.
58     */
59    this.PIXELS_PER_PAGE = this.PIXELS_PER_LINE * 16;
60
61    /**
62     * The current mouse state. The properties of this state are updated when
63     * mouse events fire. This state object is also passed in as a parameter to
64     * the handler of any mouse events.
65     *
66     * @type {Guacamole.Mouse.State}
67     */
68    this.currentState = new Guacamole.Mouse.State(
69        0, 0,
70        false, false, false, false, false
71    );
72
73    /**
74     * Fired whenever the user presses a mouse button down over the element
75     * associated with this Guacamole.Mouse.
76     *
77     * @event
78     * @param {Guacamole.Mouse.State} state The current mouse state.
79     */
80        this.onmousedown = null;
81
82    /**
83     * Fired whenever the user releases a mouse button down over the element
84     * associated with this Guacamole.Mouse.
85     *
86     * @event
87     * @param {Guacamole.Mouse.State} state The current mouse state.
88     */
89        this.onmouseup = null;
90
91    /**
92     * Fired whenever the user moves the mouse over the element associated with
93     * this Guacamole.Mouse.
94     *
95     * @event
96     * @param {Guacamole.Mouse.State} state The current mouse state.
97     */
98        this.onmousemove = null;
99
100    /**
101     * Fired whenever the mouse leaves the boundaries of the element associated
102     * with this Guacamole.Mouse.
103     *
104     * @event
105     */
106        this.onmouseout = null;
107
108    /**
109     * Counter of mouse events to ignore. This decremented by mousemove, and
110     * while non-zero, mouse events will have no effect.
111     * @private
112     */
113    var ignore_mouse = 0;
114
115    /**
116     * Cumulative scroll delta amount. This value is accumulated through scroll
117     * events and results in scroll button clicks if it exceeds a certain
118     * threshold.
119     *
120     * @private
121     */
122    var scroll_delta = 0;
123
124    function cancelEvent(e) {
125        e.stopPropagation();
126        if (e.preventDefault) e.preventDefault();
127        e.returnValue = false;
128    }
129
130    // Block context menu so right-click gets sent properly
131    element.addEventListener("contextmenu", function(e) {
132        cancelEvent(e);
133    }, false);
134
135    element.addEventListener("mousemove", function(e) {
136
137        cancelEvent(e);
138
139        // If ignoring events, decrement counter
140        if (ignore_mouse) {
141            ignore_mouse--;
142            return;
143        }
144
145        guac_mouse.currentState.fromClientPosition(element, e.clientX, e.clientY);
146
147        if (guac_mouse.onmousemove)
148            guac_mouse.onmousemove(guac_mouse.currentState);
149
150    }, false);
151
152    element.addEventListener("mousedown", function(e) {
153
154        cancelEvent(e);
155
156        // Do not handle if ignoring events
157        if (ignore_mouse)
158            return;
159
160        switch (e.button) {
161            case 0:
162                guac_mouse.currentState.left = true;
163                break;
164            case 1:
165                guac_mouse.currentState.middle = true;
166                break;
167            case 2:
168                guac_mouse.currentState.right = true;
169                break;
170        }
171
172        if (guac_mouse.onmousedown)
173            guac_mouse.onmousedown(guac_mouse.currentState);
174
175    }, false);
176
177    element.addEventListener("mouseup", function(e) {
178
179        cancelEvent(e);
180
181        // Do not handle if ignoring events
182        if (ignore_mouse)
183            return;
184
185        switch (e.button) {
186            case 0:
187                guac_mouse.currentState.left = false;
188                break;
189            case 1:
190                guac_mouse.currentState.middle = false;
191                break;
192            case 2:
193                guac_mouse.currentState.right = false;
194                break;
195        }
196
197        if (guac_mouse.onmouseup)
198            guac_mouse.onmouseup(guac_mouse.currentState);
199
200    }, false);
201
202    element.addEventListener("mouseout", function(e) {
203
204        // Get parent of the element the mouse pointer is leaving
205        if (!e) e = window.event;
206
207        // Check that mouseout is due to actually LEAVING the element
208        var target = e.relatedTarget || e.toElement;
209        while (target) {
210            if (target === element)
211                return;
212            target = target.parentNode;
213        }
214
215        cancelEvent(e);
216
217        // Release all buttons
218        if (guac_mouse.currentState.left
219            || guac_mouse.currentState.middle
220            || guac_mouse.currentState.right) {
221
222            guac_mouse.currentState.left = false;
223            guac_mouse.currentState.middle = false;
224            guac_mouse.currentState.right = false;
225
226            if (guac_mouse.onmouseup)
227                guac_mouse.onmouseup(guac_mouse.currentState);
228        }
229
230        // Fire onmouseout event
231        if (guac_mouse.onmouseout)
232            guac_mouse.onmouseout();
233
234    }, false);
235
236    // Override selection on mouse event element.
237    element.addEventListener("selectstart", function(e) {
238        cancelEvent(e);
239    }, false);
240
241    // Ignore all pending mouse events when touch events are the apparent source
242    function ignorePendingMouseEvents() { ignore_mouse = guac_mouse.touchMouseThreshold; }
243
244    element.addEventListener("touchmove",  ignorePendingMouseEvents, false);
245    element.addEventListener("touchstart", ignorePendingMouseEvents, false);
246    element.addEventListener("touchend",   ignorePendingMouseEvents, false);
247
248    // Scroll wheel support
249    function mousewheel_handler(e) {
250
251        // Determine approximate scroll amount (in pixels)
252        var delta = e.deltaY || -e.wheelDeltaY || -e.wheelDelta;
253
254        // If successfully retrieved scroll amount, convert to pixels if not
255        // already in pixels
256        if (delta) {
257
258            // Convert to pixels if delta was lines
259            if (e.deltaMode === 1)
260                delta = e.deltaY * guac_mouse.PIXELS_PER_LINE;
261
262            // Convert to pixels if delta was pages
263            else if (e.deltaMode === 2)
264                delta = e.deltaY * guac_mouse.PIXELS_PER_PAGE;
265
266        }
267
268        // Otherwise, assume legacy mousewheel event and line scrolling
269        else
270            delta = e.detail * guac_mouse.PIXELS_PER_LINE;
271       
272        // Update overall delta
273        scroll_delta += delta;
274
275        // Up
276        if (scroll_delta <= -guac_mouse.scrollThreshold) {
277
278            // Repeatedly click the up button until insufficient delta remains
279            do {
280
281                if (guac_mouse.onmousedown) {
282                    guac_mouse.currentState.up = true;
283                    guac_mouse.onmousedown(guac_mouse.currentState);
284                }
285
286                if (guac_mouse.onmouseup) {
287                    guac_mouse.currentState.up = false;
288                    guac_mouse.onmouseup(guac_mouse.currentState);
289                }
290
291                scroll_delta += guac_mouse.scrollThreshold;
292
293            } while (scroll_delta <= -guac_mouse.scrollThreshold);
294
295            // Reset delta
296            scroll_delta = 0;
297
298        }
299
300        // Down
301        if (scroll_delta >= guac_mouse.scrollThreshold) {
302
303            // Repeatedly click the down button until insufficient delta remains
304            do {
305
306                if (guac_mouse.onmousedown) {
307                    guac_mouse.currentState.down = true;
308                    guac_mouse.onmousedown(guac_mouse.currentState);
309                }
310
311                if (guac_mouse.onmouseup) {
312                    guac_mouse.currentState.down = false;
313                    guac_mouse.onmouseup(guac_mouse.currentState);
314                }
315
316                scroll_delta -= guac_mouse.scrollThreshold;
317
318            } while (scroll_delta >= guac_mouse.scrollThreshold);
319
320            // Reset delta
321            scroll_delta = 0;
322
323        }
324
325        cancelEvent(e);
326
327    }
328
329    element.addEventListener('DOMMouseScroll', mousewheel_handler, false);
330    element.addEventListener('mousewheel',     mousewheel_handler, false);
331    element.addEventListener('wheel',          mousewheel_handler, false);
332
333    /**
334     * Whether the browser supports CSS3 cursor styling, including hotspot
335     * coordinates.
336     *
337     * @private
338     * @type {Boolean}
339     */
340    var CSS3_CURSOR_SUPPORTED = (function() {
341
342        var div = document.createElement("div");
343
344        // If no cursor property at all, then no support
345        if (!("cursor" in div.style))
346            return false;
347
348        try {
349            // Apply simple 1x1 PNG
350            div.style.cursor = "url(data:image/png;base64,"
351                             + "iVBORw0KGgoAAAANSUhEUgAAAAEAAAAB"
352                             + "AQMAAAAl21bKAAAAA1BMVEX///+nxBvI"
353                             + "AAAACklEQVQI12NgAAAAAgAB4iG8MwAA"
354                             + "AABJRU5ErkJggg==) 0 0, auto";
355        }
356        catch (e) {
357            return false;
358        }
359
360        // Verify cursor property is set to URL with hotspot
361        return /\burl\([^()]*\)\s+0\s+0\b/.test(div.style.cursor || "");
362
363    })();
364
365    /**
366     * Changes the local mouse cursor to the given canvas, having the given
367     * hotspot coordinates. This affects styling of the element backing this
368     * Guacamole.Mouse only, and may fail depending on browser support for
369     * setting the mouse cursor.
370     *
371     * If setting the local cursor is desired, it is up to the implementation
372     * to do something else, such as use the software cursor built into
373     * Guacamole.Display, if the local cursor cannot be set.
374     *
375     * @param {HTMLCanvasElement} canvas The cursor image.
376     * @param {Number} x The X-coordinate of the cursor hotspot.
377     * @param {Number} y The Y-coordinate of the cursor hotspot.
378     * @return {Boolean} true if the cursor was successfully set, false if the
379     *                   cursor could not be set for any reason.
380     */
381    this.setCursor = function(canvas, x, y) {
382
383        // Attempt to set via CSS3 cursor styling
384        if (CSS3_CURSOR_SUPPORTED) {
385            var dataURL = canvas.toDataURL('image/png');
386            element.style.cursor = "url(" + dataURL + ") " + x + " " + y + ", auto";
387            return true;
388        }
389
390        // Otherwise, setting cursor failed
391        return false;
392
393    };
394
395};
396
397/**
398 * Simple container for properties describing the state of a mouse.
399 *
400 * @constructor
401 * @param {Number} x The X position of the mouse pointer in pixels.
402 * @param {Number} y The Y position of the mouse pointer in pixels.
403 * @param {Boolean} left Whether the left mouse button is pressed.
404 * @param {Boolean} middle Whether the middle mouse button is pressed.
405 * @param {Boolean} right Whether the right mouse button is pressed.
406 * @param {Boolean} up Whether the up mouse button is pressed (the fourth
407 *                     button, usually part of a scroll wheel).
408 * @param {Boolean} down Whether the down mouse button is pressed (the fifth
409 *                       button, usually part of a scroll wheel).
410 */
411Guacamole.Mouse.State = function(x, y, left, middle, right, up, down) {
412
413    /**
414     * Reference to this Guacamole.Mouse.State.
415     * @private
416     */
417    var guac_state = this;
418
419    /**
420     * The current X position of the mouse pointer.
421     * @type {Number}
422     */
423    this.x = x;
424
425    /**
426     * The current Y position of the mouse pointer.
427     * @type {Number}
428     */
429    this.y = y;
430
431    /**
432     * Whether the left mouse button is currently pressed.
433     * @type {Boolean}
434     */
435    this.left = left;
436
437    /**
438     * Whether the middle mouse button is currently pressed.
439     * @type {Boolean}
440     */
441    this.middle = middle;
442
443    /**
444     * Whether the right mouse button is currently pressed.
445     * @type {Boolean}
446     */
447    this.right = right;
448
449    /**
450     * Whether the up mouse button is currently pressed. This is the fourth
451     * mouse button, associated with upward scrolling of the mouse scroll
452     * wheel.
453     * @type {Boolean}
454     */
455    this.up = up;
456
457    /**
458     * Whether the down mouse button is currently pressed. This is the fifth
459     * mouse button, associated with downward scrolling of the mouse scroll
460     * wheel.
461     * @type {Boolean}
462     */
463    this.down = down;
464
465    /**
466     * Updates the position represented within this state object by the given
467     * element and clientX/clientY coordinates (commonly available within event
468     * objects). Position is translated from clientX/clientY (relative to
469     * viewport) to element-relative coordinates.
470     *
471     * @param {Element} element The element the coordinates should be relative
472     *                          to.
473     * @param {Number} clientX The X coordinate to translate, viewport-relative.
474     * @param {Number} clientY The Y coordinate to translate, viewport-relative.
475     */
476    this.fromClientPosition = function(element, clientX, clientY) {
477   
478        guac_state.x = clientX - element.offsetLeft;
479        guac_state.y = clientY - element.offsetTop;
480
481        // This is all JUST so we can get the mouse position within the element
482        var parent = element.offsetParent;
483        while (parent && !(parent === document.body)) {
484            guac_state.x -= parent.offsetLeft - parent.scrollLeft;
485            guac_state.y -= parent.offsetTop  - parent.scrollTop;
486
487            parent = parent.offsetParent;
488        }
489
490        // Element ultimately depends on positioning within document body,
491        // take document scroll into account.
492        if (parent) {
493            var documentScrollLeft = document.body.scrollLeft || document.documentElement.scrollLeft;
494            var documentScrollTop = document.body.scrollTop || document.documentElement.scrollTop;
495
496            guac_state.x -= parent.offsetLeft - documentScrollLeft;
497            guac_state.y -= parent.offsetTop  - documentScrollTop;
498        }
499
500    };
501
502};
503
504/**
505 * Provides cross-browser relative touch event translation for a given element.
506 *
507 * Touch events are translated into mouse events as if the touches occurred
508 * on a touchpad (drag to push the mouse pointer, tap to click).
509 *
510 * @constructor
511 * @param {Element} element The Element to use to provide touch events.
512 */
513Guacamole.Mouse.Touchpad = function(element) {
514
515    /**
516     * Reference to this Guacamole.Mouse.Touchpad.
517     * @private
518     */
519    var guac_touchpad = this;
520
521    /**
522     * The distance a two-finger touch must move per scrollwheel event, in
523     * pixels.
524     */
525    this.scrollThreshold = 20 * (window.devicePixelRatio || 1);
526
527    /**
528     * The maximum number of milliseconds to wait for a touch to end for the
529     * gesture to be considered a click.
530     */
531    this.clickTimingThreshold = 250;
532
533    /**
534     * The maximum number of pixels to allow a touch to move for the gesture to
535     * be considered a click.
536     */
537    this.clickMoveThreshold = 10 * (window.devicePixelRatio || 1);
538
539    /**
540     * The current mouse state. The properties of this state are updated when
541     * mouse events fire. This state object is also passed in as a parameter to
542     * the handler of any mouse events.
543     *
544     * @type {Guacamole.Mouse.State}
545     */
546    this.currentState = new Guacamole.Mouse.State(
547        0, 0,
548        false, false, false, false, false
549    );
550
551    /**
552     * Fired whenever a mouse button is effectively pressed. This can happen
553     * as part of a "click" gesture initiated by the user by tapping one
554     * or more fingers over the touchpad element, as part of a "scroll"
555     * gesture initiated by dragging two fingers up or down, etc.
556     *
557     * @event
558     * @param {Guacamole.Mouse.State} state The current mouse state.
559     */
560        this.onmousedown = null;
561
562    /**
563     * Fired whenever a mouse button is effectively released. This can happen
564     * as part of a "click" gesture initiated by the user by tapping one
565     * or more fingers over the touchpad element, as part of a "scroll"
566     * gesture initiated by dragging two fingers up or down, etc.
567     *
568     * @event
569     * @param {Guacamole.Mouse.State} state The current mouse state.
570     */
571        this.onmouseup = null;
572
573    /**
574     * Fired whenever the user moves the mouse by dragging their finger over
575     * the touchpad element.
576     *
577     * @event
578     * @param {Guacamole.Mouse.State} state The current mouse state.
579     */
580        this.onmousemove = null;
581
582    var touch_count = 0;
583    var last_touch_x = 0;
584    var last_touch_y = 0;
585    var last_touch_time = 0;
586    var pixels_moved = 0;
587
588    var touch_buttons = {
589        1: "left",
590        2: "right",
591        3: "middle"
592    };
593
594    var gesture_in_progress = false;
595    var click_release_timeout = null;
596
597    element.addEventListener("touchend", function(e) {
598       
599        e.preventDefault();
600           
601        // If we're handling a gesture AND this is the last touch
602        if (gesture_in_progress && e.touches.length === 0) {
603           
604            var time = new Date().getTime();
605
606            // Get corresponding mouse button
607            var button = touch_buttons[touch_count];
608
609            // If mouse already down, release anad clear timeout
610            if (guac_touchpad.currentState[button]) {
611
612                // Fire button up event
613                guac_touchpad.currentState[button] = false;
614                if (guac_touchpad.onmouseup)
615                    guac_touchpad.onmouseup(guac_touchpad.currentState);
616
617                // Clear timeout, if set
618                if (click_release_timeout) {
619                    window.clearTimeout(click_release_timeout);
620                    click_release_timeout = null;
621                }
622
623            }
624
625            // If single tap detected (based on time and distance)
626            if (time - last_touch_time <= guac_touchpad.clickTimingThreshold
627                    && pixels_moved < guac_touchpad.clickMoveThreshold) {
628
629                // Fire button down event
630                guac_touchpad.currentState[button] = true;
631                if (guac_touchpad.onmousedown)
632                    guac_touchpad.onmousedown(guac_touchpad.currentState);
633
634                // Delay mouse up - mouse up should be canceled if
635                // touchstart within timeout.
636                click_release_timeout = window.setTimeout(function() {
637                   
638                    // Fire button up event
639                    guac_touchpad.currentState[button] = false;
640                    if (guac_touchpad.onmouseup)
641                        guac_touchpad.onmouseup(guac_touchpad.currentState);
642                   
643                    // Gesture now over
644                    gesture_in_progress = false;
645
646                }, guac_touchpad.clickTimingThreshold);
647
648            }
649
650            // If we're not waiting to see if this is a click, stop gesture
651            if (!click_release_timeout)
652                gesture_in_progress = false;
653
654        }
655
656    }, false);
657
658    element.addEventListener("touchstart", function(e) {
659
660        e.preventDefault();
661
662        // Track number of touches, but no more than three
663        touch_count = Math.min(e.touches.length, 3);
664
665        // Clear timeout, if set
666        if (click_release_timeout) {
667            window.clearTimeout(click_release_timeout);
668            click_release_timeout = null;
669        }
670
671        // Record initial touch location and time for touch movement
672        // and tap gestures
673        if (!gesture_in_progress) {
674
675            // Stop mouse events while touching
676            gesture_in_progress = true;
677
678            // Record touch location and time
679            var starting_touch = e.touches[0];
680            last_touch_x = starting_touch.clientX;
681            last_touch_y = starting_touch.clientY;
682            last_touch_time = new Date().getTime();
683            pixels_moved = 0;
684
685        }
686
687    }, false);
688
689    element.addEventListener("touchmove", function(e) {
690
691        e.preventDefault();
692
693        // Get change in touch location
694        var touch = e.touches[0];
695        var delta_x = touch.clientX - last_touch_x;
696        var delta_y = touch.clientY - last_touch_y;
697
698        // Track pixels moved
699        pixels_moved += Math.abs(delta_x) + Math.abs(delta_y);
700
701        // If only one touch involved, this is mouse move
702        if (touch_count === 1) {
703
704            // Calculate average velocity in Manhatten pixels per millisecond
705            var velocity = pixels_moved / (new Date().getTime() - last_touch_time);
706
707            // Scale mouse movement relative to velocity
708            var scale = 1 + velocity;
709
710            // Update mouse location
711            guac_touchpad.currentState.x += delta_x*scale;
712            guac_touchpad.currentState.y += delta_y*scale;
713
714            // Prevent mouse from leaving screen
715
716            if (guac_touchpad.currentState.x < 0)
717                guac_touchpad.currentState.x = 0;
718            else if (guac_touchpad.currentState.x >= element.offsetWidth)
719                guac_touchpad.currentState.x = element.offsetWidth - 1;
720
721            if (guac_touchpad.currentState.y < 0)
722                guac_touchpad.currentState.y = 0;
723            else if (guac_touchpad.currentState.y >= element.offsetHeight)
724                guac_touchpad.currentState.y = element.offsetHeight - 1;
725
726            // Fire movement event, if defined
727            if (guac_touchpad.onmousemove)
728                guac_touchpad.onmousemove(guac_touchpad.currentState);
729
730            // Update touch location
731            last_touch_x = touch.clientX;
732            last_touch_y = touch.clientY;
733
734        }
735
736        // Interpret two-finger swipe as scrollwheel
737        else if (touch_count === 2) {
738
739            // If change in location passes threshold for scroll
740            if (Math.abs(delta_y) >= guac_touchpad.scrollThreshold) {
741
742                // Decide button based on Y movement direction
743                var button;
744                if (delta_y > 0) button = "down";
745                else             button = "up";
746
747                // Fire button down event
748                guac_touchpad.currentState[button] = true;
749                if (guac_touchpad.onmousedown)
750                    guac_touchpad.onmousedown(guac_touchpad.currentState);
751
752                // Fire button up event
753                guac_touchpad.currentState[button] = false;
754                if (guac_touchpad.onmouseup)
755                    guac_touchpad.onmouseup(guac_touchpad.currentState);
756
757                // Only update touch location after a scroll has been
758                // detected
759                last_touch_x = touch.clientX;
760                last_touch_y = touch.clientY;
761
762            }
763
764        }
765
766    }, false);
767
768};
769
770/**
771 * Provides cross-browser absolute touch event translation for a given element.
772 *
773 * Touch events are translated into mouse events as if the touches occurred
774 * on a touchscreen (tapping anywhere on the screen clicks at that point,
775 * long-press to right-click).
776 *
777 * @constructor
778 * @param {Element} element The Element to use to provide touch events.
779 */
780Guacamole.Mouse.Touchscreen = function(element) {
781
782    /**
783     * Reference to this Guacamole.Mouse.Touchscreen.
784     * @private
785     */
786    var guac_touchscreen = this;
787
788    /**
789     * Whether a gesture is known to be in progress. If false, touch events
790     * will be ignored.
791     *
792     * @private
793     */
794    var gesture_in_progress = false;
795
796    /**
797     * The start X location of a gesture.
798     * @private
799     */
800    var gesture_start_x = null;
801
802    /**
803     * The start Y location of a gesture.
804     * @private
805     */
806    var gesture_start_y = null;
807
808    /**
809     * The timeout associated with the delayed, cancellable click release.
810     *
811     * @private
812     */
813    var click_release_timeout = null;
814
815    /**
816     * The timeout associated with long-press for right click.
817     *
818     * @private
819     */
820    var long_press_timeout = null;
821
822    /**
823     * The distance a two-finger touch must move per scrollwheel event, in
824     * pixels.
825     */
826    this.scrollThreshold = 20 * (window.devicePixelRatio || 1);
827
828    /**
829     * The maximum number of milliseconds to wait for a touch to end for the
830     * gesture to be considered a click.
831     */
832    this.clickTimingThreshold = 250;
833
834    /**
835     * The maximum number of pixels to allow a touch to move for the gesture to
836     * be considered a click.
837     */
838    this.clickMoveThreshold = 16 * (window.devicePixelRatio || 1);
839
840    /**
841     * The amount of time a press must be held for long press to be
842     * detected.
843     */
844    this.longPressThreshold = 500;
845
846    /**
847     * The current mouse state. The properties of this state are updated when
848     * mouse events fire. This state object is also passed in as a parameter to
849     * the handler of any mouse events.
850     *
851     * @type {Guacamole.Mouse.State}
852     */
853    this.currentState = new Guacamole.Mouse.State(
854        0, 0,
855        false, false, false, false, false
856    );
857
858    /**
859     * Fired whenever a mouse button is effectively pressed. This can happen
860     * as part of a "mousedown" gesture initiated by the user by pressing one
861     * finger over the touchscreen element, as part of a "scroll" gesture
862     * initiated by dragging two fingers up or down, etc.
863     *
864     * @event
865     * @param {Guacamole.Mouse.State} state The current mouse state.
866     */
867        this.onmousedown = null;
868
869    /**
870     * Fired whenever a mouse button is effectively released. This can happen
871     * as part of a "mouseup" gesture initiated by the user by removing the
872     * finger pressed against the touchscreen element, or as part of a "scroll"
873     * gesture initiated by dragging two fingers up or down, etc.
874     *
875     * @event
876     * @param {Guacamole.Mouse.State} state The current mouse state.
877     */
878        this.onmouseup = null;
879
880    /**
881     * Fired whenever the user moves the mouse by dragging their finger over
882     * the touchscreen element. Note that unlike Guacamole.Mouse.Touchpad,
883     * dragging a finger over the touchscreen element will always cause
884     * the mouse button to be effectively down, as if clicking-and-dragging.
885     *
886     * @event
887     * @param {Guacamole.Mouse.State} state The current mouse state.
888     */
889        this.onmousemove = null;
890
891    /**
892     * Presses the given mouse button, if it isn't already pressed. Valid
893     * button values are "left", "middle", "right", "up", and "down".
894     *
895     * @private
896     * @param {String} button The mouse button to press.
897     */
898    function press_button(button) {
899        if (!guac_touchscreen.currentState[button]) {
900            guac_touchscreen.currentState[button] = true;
901            if (guac_touchscreen.onmousedown)
902                guac_touchscreen.onmousedown(guac_touchscreen.currentState);
903        }
904    }
905
906    /**
907     * Releases the given mouse button, if it isn't already released. Valid
908     * button values are "left", "middle", "right", "up", and "down".
909     *
910     * @private
911     * @param {String} button The mouse button to release.
912     */
913    function release_button(button) {
914        if (guac_touchscreen.currentState[button]) {
915            guac_touchscreen.currentState[button] = false;
916            if (guac_touchscreen.onmouseup)
917                guac_touchscreen.onmouseup(guac_touchscreen.currentState);
918        }
919    }
920
921    /**
922     * Clicks (presses and releases) the given mouse button. Valid button
923     * values are "left", "middle", "right", "up", and "down".
924     *
925     * @private
926     * @param {String} button The mouse button to click.
927     */
928    function click_button(button) {
929        press_button(button);
930        release_button(button);
931    }
932
933    /**
934     * Moves the mouse to the given coordinates. These coordinates must be
935     * relative to the browser window, as they will be translated based on
936     * the touch event target's location within the browser window.
937     *
938     * @private
939     * @param {Number} x The X coordinate of the mouse pointer.
940     * @param {Number} y The Y coordinate of the mouse pointer.
941     */
942    function move_mouse(x, y) {
943        guac_touchscreen.currentState.fromClientPosition(element, x, y);
944        if (guac_touchscreen.onmousemove)
945            guac_touchscreen.onmousemove(guac_touchscreen.currentState);
946    }
947
948    /**
949     * Returns whether the given touch event exceeds the movement threshold for
950     * clicking, based on where the touch gesture began.
951     *
952     * @private
953     * @param {TouchEvent} e The touch event to check.
954     * @return {Boolean} true if the movement threshold is exceeded, false
955     *                   otherwise.
956     */
957    function finger_moved(e) {
958        var touch = e.touches[0] || e.changedTouches[0];
959        var delta_x = touch.clientX - gesture_start_x;
960        var delta_y = touch.clientY - gesture_start_y;
961        return Math.sqrt(delta_x*delta_x + delta_y*delta_y) >= guac_touchscreen.clickMoveThreshold;
962    }
963
964    /**
965     * Begins a new gesture at the location of the first touch in the given
966     * touch event.
967     *
968     * @private
969     * @param {TouchEvent} e The touch event beginning this new gesture.
970     */
971    function begin_gesture(e) {
972        var touch = e.touches[0];
973        gesture_in_progress = true;
974        gesture_start_x = touch.clientX;
975        gesture_start_y = touch.clientY;
976    }
977
978    /**
979     * End the current gesture entirely. Wait for all touches to be done before
980     * resuming gesture detection.
981     *
982     * @private
983     */
984    function end_gesture() {
985        window.clearTimeout(click_release_timeout);
986        window.clearTimeout(long_press_timeout);
987        gesture_in_progress = false;
988    }
989
990    element.addEventListener("touchend", function(e) {
991
992        // Do not handle if no gesture
993        if (!gesture_in_progress)
994            return;
995
996        // Ignore if more than one touch
997        if (e.touches.length !== 0 || e.changedTouches.length !== 1) {
998            end_gesture();
999            return;
1000        }
1001
1002        // Long-press, if any, is over
1003        window.clearTimeout(long_press_timeout);
1004
1005        // Always release mouse button if pressed
1006        release_button("left");
1007
1008        // If finger hasn't moved enough to cancel the click
1009        if (!finger_moved(e)) {
1010
1011            e.preventDefault();
1012
1013            // If not yet pressed, press and start delay release
1014            if (!guac_touchscreen.currentState.left) {
1015
1016                var touch = e.changedTouches[0];
1017                move_mouse(touch.clientX, touch.clientY);
1018                press_button("left");
1019
1020                // Release button after a delay, if not canceled
1021                click_release_timeout = window.setTimeout(function() {
1022                    release_button("left");
1023                    end_gesture();
1024                }, guac_touchscreen.clickTimingThreshold);
1025
1026            }
1027
1028        } // end if finger not moved
1029
1030    }, false);
1031
1032    element.addEventListener("touchstart", function(e) {
1033
1034        // Ignore if more than one touch
1035        if (e.touches.length !== 1) {
1036            end_gesture();
1037            return;
1038        }
1039
1040        e.preventDefault();
1041
1042        // New touch begins a new gesture
1043        begin_gesture(e);
1044
1045        // Keep button pressed if tap after left click
1046        window.clearTimeout(click_release_timeout);
1047
1048        // Click right button if this turns into a long-press
1049        long_press_timeout = window.setTimeout(function() {
1050            var touch = e.touches[0];
1051            move_mouse(touch.clientX, touch.clientY);
1052            click_button("right");
1053            end_gesture();
1054        }, guac_touchscreen.longPressThreshold);
1055
1056    }, false);
1057
1058    element.addEventListener("touchmove", function(e) {
1059
1060        // Do not handle if no gesture
1061        if (!gesture_in_progress)
1062            return;
1063
1064        // Cancel long press if finger moved
1065        if (finger_moved(e))
1066            window.clearTimeout(long_press_timeout);
1067
1068        // Ignore if more than one touch
1069        if (e.touches.length !== 1) {
1070            end_gesture();
1071            return;
1072        }
1073
1074        // Update mouse position if dragging
1075        if (guac_touchscreen.currentState.left) {
1076
1077            e.preventDefault();
1078
1079            // Update state
1080            var touch = e.touches[0];
1081            move_mouse(touch.clientX, touch.clientY);
1082
1083        }
1084
1085    }, false);
1086
1087};
Note: See TracBrowser for help on using the repository browser.