From bda9751bd91b5e57fcef37e96a3804532559e337 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sat, 20 Dec 2014 21:30:14 -0800 Subject: [PATCH 1/9] GUAC-901: Add touch module and guacPan directive for handling panning gestures. --- .../webapp/app/touch/directives/guacPan.js | 192 ++++++++++++++++++ .../src/main/webapp/app/touch/touchModule.js | 26 +++ 2 files changed, 218 insertions(+) create mode 100644 guacamole/src/main/webapp/app/touch/directives/guacPan.js create mode 100644 guacamole/src/main/webapp/app/touch/touchModule.js diff --git a/guacamole/src/main/webapp/app/touch/directives/guacPan.js b/guacamole/src/main/webapp/app/touch/directives/guacPan.js new file mode 100644 index 000000000..b5797bc43 --- /dev/null +++ b/guacamole/src/main/webapp/app/touch/directives/guacPan.js @@ -0,0 +1,192 @@ +/* + * Copyright (C) 2014 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/** + * A directive which allows handling of panning gestures on a particular + * element. + */ +angular.module('touch').directive('guacPan', [function guacPan() { + + return { + restrict: 'A', + scope: { + + /** + * Called as during a panning gesture as the user's finger is + * placed upon the element, moves, and is lifted from the element. + * + * @event + * @param {Boolean} inProgress + * Whether the gesture is currently in progress. This will + * always be true except when the gesture has ended, at which + * point one final call will occur with this parameter set to + * false. + * + * @param {Number} startX + * The X location at which the panning gesture began. + * + * @param {Number} startY + * The Y location at which the panning gesture began. + * + * @param {Number} currentX + * The current X location of the user's finger. + * + * @param {Number} currentY + * The current Y location of the user's finger. + * + * @param {Number} deltaX + * The difference in X location relative to the start of the + * gesture. + * + * @param {Number} deltaY + * The difference in Y location relative to the start of the + * gesture. + */ + guacPan : '=' + + }, + + link: function guacPan($scope, $element) { + + /** + * The element which will register the panning gesture. + * + * @type Element + */ + var element = $element[0]; + + /** + * Whether a drag gesture is in progress. + * + * @type Boolean + */ + var inProgress = false; + + /** + * The starting X location of the drag gesture. + * + * @type Number + */ + var startX = null; + + /** + * The starting Y location of the drag gesture. + * + * @type Number + */ + var startY = null; + + /** + * The current X location of the drag gesture. + * + * @type Number + */ + var currentX = null; + + /** + * The current Y location of the drag gesture. + * + * @type Number + */ + var currentY = null; + + /** + * The change in X relative to drag start. + * + * @type Number + */ + var deltaX = 0; + + /** + * The change in X relative to drag start. + * + * @type Number + */ + var deltaY = 0; + + // When there is exactly one touch, monitor the change in location + element.addEventListener("touchmove", function(e) { + if (e.touches.length === 1) { + + e.preventDefault(); + e.stopPropagation(); + + // Get touch location + var x = e.touches[0].clientX; + var y = e.touches[0].clientY; + + // Init start location and deltas if gesture is starting + if (!startX || !startY) { + startX = currentX = x; + startY = currentY = y; + deltaX = 0; + deltaY = 0; + inProgress = true; + } + + // Update deltas if gesture is in progress + else if (inProgress) { + deltaX = x - currentX; + deltaY = y - currentY; + currentX = x; + currentY = y; + } + + // Signal start/change in panning gesture + if (inProgress && $scope.guacPan) { + $scope.$apply(function panChanged() { + $scope.guacPan(true, startX, startY, currentX, currentY, deltaX, deltaY); + }); + } + + } + }, false); + + // Reset monitoring and fire end event when done + element.addEventListener("touchend", function(e) { + + if (startX && startY && e.touches.length === 0) { + + e.preventDefault(); + e.stopPropagation(); + + // Signal end of panning gesture + if (inProgress && $scope.guacPan) { + $scope.$apply(function panComplete() { + $scope.guacPan(true, startX, startY, currentX, currentY, deltaX, deltaY); + }); + } + + startX = currentX = null; + startY = currentY = null; + deltaX = 0; + deltaY = 0; + inProgress = false; + + } + + }, false); + + } + + }; +}]); diff --git a/guacamole/src/main/webapp/app/touch/touchModule.js b/guacamole/src/main/webapp/app/touch/touchModule.js new file mode 100644 index 000000000..3a86c333d --- /dev/null +++ b/guacamole/src/main/webapp/app/touch/touchModule.js @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2014 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/** + * Module for handling common touch gestures, like panning or pinch-to-zoom. + */ +angular.module('touch', []); From 2623ba3196a9a29c65886600fb11c5fc97e8377a Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sat, 20 Dec 2014 22:55:39 -0800 Subject: [PATCH 2/9] GUAC-901: Add touch module and guacTouchDrag directive for handling drag gestures. --- .../{guacPan.js => guacTouchDrag.js} | 43 ++++++++++--------- 1 file changed, 23 insertions(+), 20 deletions(-) rename guacamole/src/main/webapp/app/touch/directives/{guacPan.js => guacTouchDrag.js} (77%) diff --git a/guacamole/src/main/webapp/app/touch/directives/guacPan.js b/guacamole/src/main/webapp/app/touch/directives/guacTouchDrag.js similarity index 77% rename from guacamole/src/main/webapp/app/touch/directives/guacPan.js rename to guacamole/src/main/webapp/app/touch/directives/guacTouchDrag.js index b5797bc43..c372714c8 100644 --- a/guacamole/src/main/webapp/app/touch/directives/guacPan.js +++ b/guacamole/src/main/webapp/app/touch/directives/guacTouchDrag.js @@ -21,18 +21,17 @@ */ /** - * A directive which allows handling of panning gestures on a particular - * element. + * A directive which allows handling of drag gestures on a particular element. */ -angular.module('touch').directive('guacPan', [function guacPan() { +angular.module('touch').directive('guacTouchDrag', [function guacTouchDrag() { return { restrict: 'A', scope: { /** - * Called as during a panning gesture as the user's finger is - * placed upon the element, moves, and is lifted from the element. + * Called during a drag gesture as the user's finger is placed upon + * the element, moves, and is lifted from the element. * * @event * @param {Boolean} inProgress @@ -42,10 +41,10 @@ angular.module('touch').directive('guacPan', [function guacPan() { * false. * * @param {Number} startX - * The X location at which the panning gesture began. + * The X location at which the drag gesture began. * * @param {Number} startY - * The Y location at which the panning gesture began. + * The Y location at which the drag gesture began. * * @param {Number} currentX * The current X location of the user's finger. @@ -60,15 +59,19 @@ angular.module('touch').directive('guacPan', [function guacPan() { * @param {Number} deltaY * The difference in Y location relative to the start of the * gesture. + * + * @return {Boolean} + * false if the default action of the touch event should be + * prevented, any other value otherwise. */ - guacPan : '=' + guacTouchDrag : '=' }, - link: function guacPan($scope, $element) { + link: function guacTouchDrag($scope, $element) { /** - * The element which will register the panning gesture. + * The element which will register the drag gesture. * * @type Element */ @@ -127,7 +130,6 @@ angular.module('touch').directive('guacPan', [function guacPan() { element.addEventListener("touchmove", function(e) { if (e.touches.length === 1) { - e.preventDefault(); e.stopPropagation(); // Get touch location @@ -151,10 +153,11 @@ angular.module('touch').directive('guacPan', [function guacPan() { currentY = y; } - // Signal start/change in panning gesture - if (inProgress && $scope.guacPan) { - $scope.$apply(function panChanged() { - $scope.guacPan(true, startX, startY, currentX, currentY, deltaX, deltaY); + // Signal start/change in drag gesture + if (inProgress && $scope.guacTouchDrag) { + $scope.$apply(function dragChanged() { + if ($scope.guacTouchDrag(true, startX, startY, currentX, currentY, deltaX, deltaY) === false) + e.preventDefault(); }); } @@ -166,13 +169,13 @@ angular.module('touch').directive('guacPan', [function guacPan() { if (startX && startY && e.touches.length === 0) { - e.preventDefault(); e.stopPropagation(); - // Signal end of panning gesture - if (inProgress && $scope.guacPan) { - $scope.$apply(function panComplete() { - $scope.guacPan(true, startX, startY, currentX, currentY, deltaX, deltaY); + // Signal end of drag gesture + if (inProgress && $scope.guacTouchDrag) { + $scope.$apply(function dragComplete() { + if ($scope.guacTouchDrag(true, startX, startY, currentX, currentY, deltaX, deltaY === false)) + e.preventDefault(); }); } From 2f74f5025c1bab140a5d6b6472683390f004bd4b Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sat, 20 Dec 2014 22:57:02 -0800 Subject: [PATCH 3/9] GUAC-901: Allow client display to be scrolled via properties. --- .../webapp/app/client/directives/guacClient.js | 12 ++++++++++++ .../webapp/app/client/types/ClientProperties.js | 16 ++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/guacamole/src/main/webapp/app/client/directives/guacClient.js b/guacamole/src/main/webapp/app/client/directives/guacClient.js index 8c330805a..498fd5ac0 100644 --- a/guacamole/src/main/webapp/app/client/directives/guacClient.js +++ b/guacamole/src/main/webapp/app/client/directives/guacClient.js @@ -318,6 +318,18 @@ angular.module('client').directive('guacClient', [function guacClient() { client.setClipboard(data); }); + /* + * SCROLLING + */ + + $scope.$watch('clientProperties.scrollLeft', function scrollLeftChanged(newValue, oldValue) { + main.scrollLeft += newValue - oldValue; + }); + + $scope.$watch('clientProperties.scrollTop', function scrollTopChanged(newValue, oldValue) { + main.scrollTop += newValue - oldValue; + }); + /* * CONNECT / RECONNECT */ diff --git a/guacamole/src/main/webapp/app/client/types/ClientProperties.js b/guacamole/src/main/webapp/app/client/types/ClientProperties.js index 7c08b360e..529022cb0 100644 --- a/guacamole/src/main/webapp/app/client/types/ClientProperties.js +++ b/guacamole/src/main/webapp/app/client/types/ClientProperties.js @@ -83,6 +83,22 @@ angular.module('client').factory('ClientProperties', [function defineClientPrope */ this.emulateAbsoluteMouse = template.emulateAbsoluteMouse || true; + /** + * The relative Y coordinate of the scroll offset of the display within + * the client element. + * + * @type Number + */ + this.scrollTop = template.scrollTop || 0; + + /** + * The relative X coordinate of the scroll offset of the display within + * the client element. + * + * @type Number + */ + this.scrollLeft = template.scrollLeft || 0; + }; return ClientProperties; From 8ec09dcb2b41da75fd8602db8123f52a0a25101f Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sat, 20 Dec 2014 23:08:40 -0800 Subject: [PATCH 4/9] GUAC-901: Show/hide menu depending on swipe. Pan display via drag gestures. Fix missing input method IDs. --- .../main/webapp/app/client/clientModule.js | 2 +- .../client/controllers/clientController.js | 55 +++++++++++++++++++ .../main/webapp/app/client/styles/display.css | 1 + .../webapp/app/client/templates/client.html | 10 ++-- 4 files changed, 62 insertions(+), 6 deletions(-) diff --git a/guacamole/src/main/webapp/app/client/clientModule.js b/guacamole/src/main/webapp/app/client/clientModule.js index 38eee4bbb..cb66904a6 100644 --- a/guacamole/src/main/webapp/app/client/clientModule.js +++ b/guacamole/src/main/webapp/app/client/clientModule.js @@ -23,4 +23,4 @@ /** * The module for code used to connect to a connection or balancing group. */ -angular.module('client', ['auth', 'history', 'osk', 'rest', 'textInput']); +angular.module('client', ['auth', 'history', 'osk', 'rest', 'textInput', 'touch']); diff --git a/guacamole/src/main/webapp/app/client/controllers/clientController.js b/guacamole/src/main/webapp/app/client/controllers/clientController.js index e0efc341c..139da3054 100644 --- a/guacamole/src/main/webapp/app/client/controllers/clientController.js +++ b/guacamole/src/main/webapp/app/client/controllers/clientController.js @@ -26,6 +26,30 @@ angular.module('home').controller('clientController', ['$scope', '$routeParams', '$injector', function clientController($scope, $routeParams, $injector) { + /** + * The minimum number of pixels a drag gesture must move to result in the + * menu being shown or hidden. + * + * @type Number + */ + var MENU_DRAG_DELTA = 64; + + /** + * The maximum X location of the start of a drag gesture for that gesture + * to potentially show the menu. + * + * @type Number + */ + var MENU_DRAG_MARGIN = 64; + + /** + * When showing or hiding the menu via a drag gesture, the maximum number + * of pixels the touch can move vertically and still affect the menu. + * + * @type Number + */ + var MENU_DRAG_VERTICAL_TOLERANCE = 10; + /* * In order to open the guacamole menu, we need to hit ctrl-alt-shift. There are * several possible keysysms for each key. @@ -201,6 +225,37 @@ angular.module('home').controller('clientController', ['$scope', '$routeParams', return true; } + // Hide menu when the user swipes from the right + $scope.menuDrag = function menuDrag(inProgress, startX, startY, currentX, currentY, deltaX, deltaY) { + + if (Math.abs(currentY - startY) < MENU_DRAG_VERTICAL_TOLERANCE + && startX - currentX >= MENU_DRAG_DELTA) + $scope.menuShown = false; + + }; + + // Update menu or client based on dragging gestures + $scope.clientDrag = function clientDrag(inProgress, startX, startY, currentX, currentY, deltaX, deltaY) { + + // Show menu if the user swipes from the left + if (startX <= MENU_DRAG_MARGIN) { + + if (Math.abs(currentY - startY) < MENU_DRAG_VERTICAL_TOLERANCE + && currentX - startX >= MENU_DRAG_DELTA) + $scope.menuShown = true; + + } + + // Scroll display if absolute mouse is in use + else if ($scope.clientProperties.emulateAbsoluteMouse) { + $scope.clientProperties.scrollLeft -= deltaX; + $scope.clientProperties.scrollTop -= deltaY; + } + + return false; + + }; + // Show/hide UI elements depending on input method $scope.$watch('inputMethod', function setInputMethod(inputMethod) { diff --git a/guacamole/src/main/webapp/app/client/styles/display.css b/guacamole/src/main/webapp/app/client/styles/display.css index a606514e4..41b635915 100644 --- a/guacamole/src/main/webapp/app/client/styles/display.css +++ b/guacamole/src/main/webapp/app/client/styles/display.css @@ -35,6 +35,7 @@ div.main { width: 100%; height: 100%; position: relative; + font-size: 0px; } .resize-sensor { diff --git a/guacamole/src/main/webapp/app/client/templates/client.html b/guacamole/src/main/webapp/app/client/templates/client.html index 4528040d2..ed8caa5f1 100644 --- a/guacamole/src/main/webapp/app/client/templates/client.html +++ b/guacamole/src/main/webapp/app/client/templates/client.html @@ -24,7 +24,7 @@
-
+
-