From 04b399e27d3eaa9ba55d46c05c1d3760dc22d1e6 Mon Sep 17 00:00:00 2001 From: Samuel Mannehed Date: Wed, 14 Sep 2016 13:09:12 +0200 Subject: [PATCH] Allow moving the controlbar handle This also adds emulation of Element.setCapture() as only Firefox and Internet Explorer/Edge currently supports it. --- app/styles/base.css | 3 +- app/ui.js | 113 ++++++++++++++++++++++++++++++++++++++++++-- app/webutil.js | 93 ++++++++++++++++++++++++++++++++++++ core/util.js | 10 ++-- 4 files changed, 212 insertions(+), 7 deletions(-) diff --git a/app/styles/base.css b/app/styles/base.css index 78f97dff..8fbcb282 100644 --- a/app/styles/base.css +++ b/app/styles/base.css @@ -178,7 +178,8 @@ input[type=button]:active, select:active { #noVNC_control_bar_handle { position: absolute; right: -15px; - top: 10%; + top: 0; + transform: translateY(35px); width: 50px; height: 50px; z-index: -2; diff --git a/app/ui.js b/app/ui.js index 0c7cf1ec..cdf20a96 100644 --- a/app/ui.js +++ b/app/ui.js @@ -43,6 +43,10 @@ var UI; hideKeyboardTimeout: null, controlbarTimeout: null, + controlbarGrabbed: false, + controlbarDrag: false, + controlbarMouseDownClientY: 0, + controlbarMouseDownOffsetY: 0, keyboardVisible: false, isTouchDevice: false, @@ -198,13 +202,19 @@ var UI; document.getElementById("noVNC_control_bar") .addEventListener('keypress', UI.activateControlbar); - document.getElementById("noVNC_control_bar_handle") - .addEventListener('click', UI.toggleControlbar); - document.getElementById("noVNC_view_drag_button") .addEventListener('click', UI.toggleViewDrag); document.getElementById("noVNC_send_ctrl_alt_del_button") .addEventListener('click', UI.sendCtrlAltDel); + + document.getElementById("noVNC_control_bar_handle") + .addEventListener('mousedown', UI.controlbarHandleMouseDown); + document.getElementById("noVNC_control_bar_handle") + .addEventListener('mouseup', UI.controlbarHandleMouseUp); + document.getElementById("noVNC_control_bar_handle") + .addEventListener('mousemove', UI.dragControlbarHandle); + // resize events aren't available for elements + window.addEventListener('resize', UI.updateControlbarHandle); }, addTouchSpecificHandlers: function() { @@ -235,6 +245,13 @@ var UI; document.getElementById("noVNC_control_bar") .addEventListener('input', UI.activateControlbar); + document.getElementById("noVNC_control_bar_handle") + .addEventListener('touchstart', UI.controlbarHandleMouseDown); + document.getElementById("noVNC_control_bar_handle") + .addEventListener('touchend', UI.controlbarHandleMouseUp); + document.getElementById("noVNC_control_bar_handle") + .addEventListener('touchmove', UI.dragControlbarHandle); + window.addEventListener('load', UI.keyboardinputReset); }, @@ -497,6 +514,96 @@ var UI; } }, + dragControlbarHandle: function (e) { + if (!UI.controlbarGrabbed) return; + + var ptr = Util.getPointerEvent(e); + + if (!UI.controlbarDrag) { + // The goal is to trigger on a certain physical width, the + // devicePixelRatio brings us a bit closer but is not optimal. + var dragThreshold = 10 * (window.devicePixelRatio || 1); + var dragDistance = Math.abs(ptr.clientY - UI.controlbarMouseDownClientY); + + if (dragDistance < dragThreshold) return; + + UI.controlbarDrag = true; + } + + var eventY = ptr.clientY - UI.controlbarMouseDownOffsetY; + + UI.moveControlbarHandle(eventY); + + e.preventDefault(); + e.stopPropagation(); + }, + + // Move the handle but don't allow any position outside the bounds + moveControlbarHandle: function (posY) { + var handle = document.getElementById("noVNC_control_bar_handle"); + var handleHeight = Util.getPosition(handle).height; + var controlbar = document.getElementById("noVNC_control_bar"); + var controlbarBounds = Util.getPosition(controlbar); + var controlbarTop = controlbarBounds.y; + var controlbarBottom = controlbarBounds.y + controlbarBounds.height; + var margin = 10; + + var viewportY = posY; + + // Refuse coordinates outside the control bar + if (viewportY < controlbarTop + margin) { + viewportY = controlbarTop + margin; + } else if (viewportY > controlbarBottom - handleHeight - margin) { + viewportY = controlbarBottom - handleHeight - margin; + } + + // Corner case: control bar too small for stable position + if (controlbarBounds.height < (handleHeight + margin * 2)) { + viewportY = controlbarTop + (controlbarBounds.height - handleHeight) / 2; + } + + var relativeY = viewportY - controlbarTop; + handle.style.transform = "translateY(" + relativeY + "px)"; + }, + + updateControlbarHandle: function () { + var handle = document.getElementById("noVNC_control_bar_handle"); + var pos = Util.getPosition(handle); + UI.moveControlbarHandle(pos.y); + }, + + controlbarHandleMouseUp: function(e) { + if ((e.type == "mouseup") && (e.button != 0)) + return; + + // mouseup and mousedown on the same place toggles the controlbar + if (UI.controlbarGrabbed && !UI.controlbarDrag) { + UI.toggleControlbar(); + e.preventDefault(); + e.stopPropagation(); + } + UI.controlbarGrabbed = false; + }, + + controlbarHandleMouseDown: function(e) { + if ((e.type == "mousedown") && (e.button != 0)) + return; + + var ptr = Util.getPointerEvent(e); + + var handle = document.getElementById("noVNC_control_bar_handle"); + var bounds = handle.getBoundingClientRect(); + + WebUtil.setCapture(handle); + UI.controlbarGrabbed = true; + UI.controlbarDrag = false; + + UI.controlbarMouseDownClientY = ptr.clientY; + UI.controlbarMouseDownOffsetY = ptr.clientY - bounds.top; + e.preventDefault(); + e.stopPropagation(); + }, + /* ------^------- * /VISUAL * ============== diff --git a/app/webutil.js b/app/webutil.js index 7f234dbf..38bb3967 100644 --- a/app/webutil.js +++ b/app/webutil.js @@ -278,6 +278,99 @@ WebUtil.injectParamIfMissing = function (path, param, value) { } }; +// Emulate Element.setCapture() when not supported + +WebUtil._captureRecursion = false; +WebUtil._captureProxy = function (e) { + // Recursion protection as we'll see our own event + if (WebUtil._captureRecursion) return; + + // Clone the event as we cannot dispatch an already dispatched event + var newEv = new e.constructor(e.type, e); + + WebUtil._captureRecursion = true; + WebUtil._captureElem.dispatchEvent(newEv); + WebUtil._captureRecursion = false; + + // Implicitly release the capture on button release + if ((e.type === "mouseup") || (e.type === "touchend")) { + WebUtil.releaseCapture(); + } +}; + +WebUtil.setCapture = function (elem) { + if (elem.setCapture) { + + elem.setCapture(); + + // IE releases capture on 'click' events which might not trigger + elem.addEventListener('mouseup', WebUtil.releaseCapture); + elem.addEventListener('touchend', WebUtil.releaseCapture); + + } else { + // Safari on iOS 9 has a broken constructor for TouchEvent. + // We are fine in this case however, since Safari seems to + // have some sort of implicit setCapture magic anyway. + if (window.TouchEvent !== undefined) { + try { + new TouchEvent("touchstart"); + } catch (TypeError) { + return; + } + } + + var captureElem = document.getElementById("noVNC_mouse_capture_elem"); + + if (captureElem === null) { + captureElem = document.createElement("div"); + captureElem.id = "noVNC_mouse_capture_elem"; + captureElem.style.position = "fixed"; + captureElem.style.top = "0px"; + captureElem.style.left = "0px"; + captureElem.style.width = "100%"; + captureElem.style.height = "100%"; + captureElem.style.zIndex = 10000; + captureElem.style.display = "none"; + document.body.appendChild(captureElem); + + captureElem.addEventListener('mousemove', WebUtil._captureProxy); + captureElem.addEventListener('mouseup', WebUtil._captureProxy); + + captureElem.addEventListener('touchmove', WebUtil._captureProxy); + captureElem.addEventListener('touchend', WebUtil._captureProxy); + } + + WebUtil._captureElem = elem; + captureElem.style.display = null; + + // We listen to events on window in order to keep tracking if it + // happens to leave the viewport + window.addEventListener('mousemove', WebUtil._captureProxy); + window.addEventListener('mouseup', WebUtil._captureProxy); + + window.addEventListener('touchmove', WebUtil._captureProxy); + window.addEventListener('touchend', WebUtil._captureProxy); + } +}; + +WebUtil.releaseCapture = function () { + if (document.releaseCapture) { + + document.releaseCapture(); + + } else { + var captureElem = document.getElementById("noVNC_mouse_capture_elem"); + WebUtil._captureElem = null; + captureElem.style.display = "none"; + + window.removeEventListener('mousemove', WebUtil._captureProxy); + window.removeEventListener('mouseup', WebUtil._captureProxy); + + window.removeEventListener('touchmove', WebUtil._captureProxy); + window.removeEventListener('touchend', WebUtil._captureProxy); + } +}; + // Dynamically load scripts without using document.write() // Reference: http://unixpapa.com/js/dyna.html // diff --git a/core/util.js b/core/util.js index d6a01932..3cc1e876 100644 --- a/core/util.js +++ b/core/util.js @@ -204,14 +204,18 @@ Util.getPosition = function(obj) { 'width': objPosition.width, 'height': objPosition.height}; }; +Util.getPointerEvent = function (e) { + var evt; + evt = (e ? e : window.event); + evt = (evt.changedTouches ? evt.changedTouches[0] : evt.touches ? evt.touches[0] : evt); + return evt; +}; // Get mouse event position in DOM element Util.getEventPosition = function (e, obj, scale) { "use strict"; var evt, docX, docY, pos; - //if (!e) evt = window.event; - evt = (e ? e : window.event); - evt = (evt.changedTouches ? evt.changedTouches[0] : evt.touches ? evt.touches[0] : evt); + evt = Util.getPointerEvent(e); if (evt.pageX || evt.pageY) { docX = evt.pageX; docY = evt.pageY;