Partial conversion to new UI paradigm and manager.

This commit is contained in:
Michael Jumper
2012-11-11 23:52:34 -08:00
parent efd93fcad5
commit 38dfc3128a
5 changed files with 491 additions and 131 deletions

View File

@@ -40,11 +40,16 @@
</div>
</div>
<textarea id="eventTarget"></textarea>
<!-- On-screen magnifier -->
<div id="magnifier">
<textarea id="eventTarget"></textarea>
<canvas id="magnifier-display"></canvas>
<div id="magnifier-background">
<div id="magnifier">
<canvas id="magnifier-display"></canvas>
</div>
</div>
<div id="pan-overlay"></div>
<!-- On-screen keyboard -->
<div id="keyboardContainer"></div>
@@ -78,6 +83,8 @@
<script type="text/javascript" src="scripts/session.js"></script>
<script type="text/javascript" src="scripts/history.js"></script>
<script type="text/javascript" src="scripts/interface.js"></script>
<script type="text/javascript" src="scripts/guac-ui.js"></script>
<script type="text/javascript" src="scripts/client-ui.js"></script>
<!-- Init -->
<script type="text/javascript"> /* <![CDATA[ */

View File

@@ -0,0 +1,246 @@
/**
* Client UI namespace.
* @namespace
*/
GuacUI.Client = GuacUI.Client || {};
GuacUI.Client = {
/**
* Collection of all Guacamole client UI states.
*/
"states": {
/**
* The normal default Guacamole client UI mode
*/
"FULL_INTERACTIVE" : 0,
/**
* Same as FULL_INTERACTIVE except with visible on-screen keyboard.
*/
"OSK" : 1,
/**
* No on-screen keyboard, but a visible magnifier.
*/
"MAGNIFIER" : 2,
/**
* Arrows and a draggable view.
*/
"PAN" : 3,
/**
* Same as PAN, but with visible native OSK.
*/
"PAN_TYPING" : 4
},
/* Constants */
"LONG_PRESS_DETECT_TIMEOUT" : 800, /* milliseconds */
"LONG_PRESS_MOVEMENT_THRESHOLD" : 10, /* pixels */
"KEYBOARD_AUTO_RESIZE_INTERVAL" : 30, /* milliseconds */
/* UI Components */
"viewport" : document.getElementById("viewportClone"),
"display" : document.getElementById("display"),
"logo" : document.getElementById("status-logo"),
"eventTarget" : document.getElementById("eventTarget"),
"buttons": {
"reconnect" : document.getElementById("reconnect")
},
"containers": {
"state" : document.getElementById("statusDialog"),
"keyboard" : document.getElementById("keyboardContainer"),
"magnifier" : document.getElementById("magnifier")
},
"pan_overlay" : document.getElementById("pan-overlay"),
"magnifier_background" : document.getElementById("magnifier-background"),
"magnifier" : document.getElementById("magnifier-display"),
"state" : document.getElementById("statusText"),
"client" : null,
"sessionState" : new GuacamoleSessionState()
};
GuacUI.Client.magnifier_background.onclick = function() {
GuacUI.StateManager.setState(GuacUI.Client.states.INTERACTIVE);
};
GuacUI.Client.containers.magnifier.addEventListener("click", function(e) {
GuacUI.StateManager.setState(GuacUI.Client.states.PAN_TYPING);
e.stopPropagation();
}, true);
/**
* Component which displays a magnified (100% zoomed) client display.
*
* @constructor
* @augments GuacUI.DraggableComponent
*/
GuacUI.Client.Magnifier = function(magnifier) {
/**
* Reference to this magnifier.
* @private
*/
var guac_magnifier = this;
/*
* Call parent constructor.
*/
GuacUI.DraggableComponent.apply(this, [magnifier]);
// Ensure transformations on display originate at 0,0
GuacamoleUI.containers.magnifier.style.transformOrigin =
GuacamoleUI.containers.magnifier.style.webkitTransformOrigin =
GuacamoleUI.containers.magnifier.style.MozTransformOrigin =
GuacamoleUI.containers.magnifier.style.OTransformOrigin =
GuacamoleUI.containers.magnifier.style.msTransformOrigin =
"0 0";
var magnifier_display = GuacUI.Client.magnifier;
var magnifier_context = magnifier_display.getContext("2d");
this.onmove = function(x, y) {
var width = magnifier.offsetWidth;
var height = magnifier.offsetHeight;
// Update contents relative to new position
var clip_x = x
/ (window.innerWidth - width) * (GuacamoleUI.client.getWidth() - width);
var clip_y = y
/ (window.innerHeight - height) * (GuacamoleUI.client.getHeight() - height);
magnifier_display.style.WebkitTransform =
magnifier_display.style.MozTransform =
magnifier_display.style.OTransform =
magnifier_display.style.msTransform =
magnifier_display.style.transform = "translate("
+ (-clip_x) + "px, " + (-clip_y) + "px)";
GuacamoleUI.eventTarget.style.left = clip_x + "px";
GuacamoleUI.eventTarget.style.top = clip_y + "px";
};
this.show = function() {
// Copy displayed image
magnifier_display.width = GuacamoleUI.client.getWidth();
magnifier_display.height = GuacamoleUI.client.getHeight();
magnifier_context.drawImage(GuacamoleUI.client.flatten(), 0, 0);
// Show magnifier container
GuacUI.Client.magnifier_background.style.display = "block";
};
this.hide = function() {
// Hide magnifier container
GuacUI.Client.magnifier_background.style.display = "none";
};
};
/*
* We inherit from GuacUI.DraggableComponent.
*/
GuacUI.Client.Magnifier.prototype = new GuacUI.DraggableComponent();
GuacUI.StateManager.registerComponent(
new GuacUI.Client.Magnifier(GuacUI.Client.containers.magnifier),
GuacUI.Client.states.MAGNIFIER
);
/*
* Zoomed Display
*/
GuacUI.Client.ZoomedDisplay = function(client) {
var old_scale = null;
this.show = function() {
old_scale = GuacamoleUI.client.getScale();
GuacamoleUI.client.scale(1.0);
};
this.hide = function() {
GuacamoleUI.client.scale(old_scale);
};
};
GuacUI.Client.ZoomedDisplay.prototype = new GuacUI.Component();
GuacUI.StateManager.registerComponent(
new GuacUI.Client.ZoomedDisplay(),
GuacUI.Client.states.PAN,
GuacUI.Client.states.PAN_TYPING
);
/*
* Pan UI
*/
GuacUI.Client.PanOverlay = function(client) {
this.show = function() {
GuacUI.Client.pan_overlay.style.display = "block";
};
this.hide = function() {
GuacUI.Client.pan_overlay.style.display = "none";
};
};
GuacUI.Client.PanOverlay.prototype = new GuacUI.Component();
GuacUI.StateManager.registerComponent(
new GuacUI.Client.PanOverlay(),
GuacUI.Client.states.PAN,
GuacUI.Client.states.PAN_TYPING
);
/*
* Native Keyboard
*/
GuacUI.Client.NativeKeyboard = function() {
this.show = function() {
GuacamoleUI.eventTarget.focus();
};
this.hide = function() {
GuacamoleUI.eventTarget.blur();
};
};
GuacUI.StateManager.registerComponent(
new GuacUI.Client.NativeKeyboard(),
GuacUI.Client.states.PAN_TYPING
);
GuacUI.Client.eventTarget.onblur = function() {
GuacUI.StateManager.setState(GuacUI.Client.states.INTERACTIVE);
};
/*
* Set initial state
*/
GuacUI.StateManager.setState(GuacUI.Client.states.INTERACTIVE);

View File

@@ -0,0 +1,208 @@
/*
* Guacamole - Clientless Remote Desktop
* Copyright (C) 2010 Michael Jumper
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
var GuacUI = GuacUI || {};
/**
* Central registry of all components for all states.
*/
GuacUI.StateManager = new (function() {
/**
* The current state.
*/
var current_state = null;
/**
* Array of arrays of components, indexed by the states they are in.
*/
var components = [];
/**
* Registers the given component with this state manager, to be shown
* during the given states.
*
* @param {GuacUI.Component} component The component to register.
* @param {Number} [...] The list of states this component should be
* visible during.
*/
this.registerComponent = function(component) {
// For each state specified, add the given component
for (var i=1; i<arguments.length; i++) {
// Get specified state
var state = arguments[i];
// Get array of components in that state
var component_array = components[state];
if (!component_array)
component_array = components[state] = [];
// Add component
component_array.push(component);
}
};
function allComponents(components, name) {
// Invoke given function on all components in array
for (var i=0; i<components.length; i++)
components[i][name]();
}
/**
* Sets the current visible state.
*/
this.setState = function(state) {
// Hide components in current state
if (current_state && components[current_state])
allComponents(components[current_state], "hide");
// Show all components in new state
current_state = state;
if (components[state])
allComponents(components[state], "show");
};
})();
/**
* Abstract component which can be registered with GuacUI and shown or hidden
* dynamically based on interface mode.
*
* @constructor
*/
GuacUI.Component = function() {
/**
* Called whenever this component needs to be shown and activated.
* @event
*/
this.onshow = null;
/**
* Called whenever this component needs to be hidden and deactivated.
* @event
*/
this.onhide = null;
};
/**
* A Guacamole UI component which can be repositioned by dragging.
*
* @constructor
* @augments GuacUI.Component
*/
GuacUI.DraggableComponent = function(element) {
var draggable_component = this;
var position_x = 0;
var position_y = 0;
var start_x = 0;
var start_y = 0;
/*
* Record drag start when finger hits element
*/
if (element)
element.addEventListener("touchstart", function(e) {
if (e.touches.length == 1) {
start_x = e.touches[0].screenX;
start_y = e.touches[0].screenY;
}
e.stopPropagation();
}, true);
/*
* Update position based on last touch
*/
if (element)
element.addEventListener("touchmove", function(e) {
if (e.touches.length == 1) {
var new_x = e.touches[0].screenX;
var new_y = e.touches[0].screenY;
position_x += new_x - start_x;
position_y += new_y - start_y;
start_x = new_x;
start_y = new_y;
// Move magnifier to new position
draggable_component.move(position_x, position_y);
}
e.preventDefault();
e.stopPropagation();
}, true);
if (element)
element.addEventListener("touchend", function(e) {
e.stopPropagation();
}, true);
/**
* Moves this component to the specified location relative to its normal
* position.
*
* @param {Number} x The X coordinate in pixels.
* @param {Number} y The Y coordinate in pixels.
*/
this.move = function(x, y) {
element.style.WebkitTransform =
element.style.MozTransform =
element.style.OTransform =
element.style.msTransform =
element.style.transform = "translate("
+ x + "px, " + y + "px)";
if (draggable_component.onmove)
draggable_component.onmove(x, y);
};
/**
* Trigered whenever this element is moved.
*
* @event
* @param {Number} x The new X coordinate.
* @param {Number} y The new Y coordinate.
*/
this.onmove = null;
};

View File

@@ -114,124 +114,6 @@ GuacamoleUI.toggleKeyboard = function() {
};
GuacamoleUI.Magnifier = new (function() {
var guac_magnifier = this;
var position_x = 0;
var position_y = 0;
var start_x = 0;
var start_y = 0;
GuacamoleUI.containers.magnifier.addEventListener("touchstart", function(e) {
if (e.touches.length == 1) {
start_x = e.touches[0].screenX;
start_y = e.touches[0].screenY;
}
}, false);
GuacamoleUI.containers.magnifier.addEventListener("touchmove", function(e) {
if (e.touches.length == 1) {
var width = GuacamoleUI.containers.magnifier.offsetWidth;
var height = GuacamoleUI.containers.magnifier.offsetHeight;
var new_x = e.touches[0].screenX;
var new_y = e.touches[0].screenY;
position_x += new_x - start_x;
position_y += new_y - start_y;
if (position_x < 0) position_x = 0;
else if (position_x > window.innerWidth - width)
position_x = window.innerWidth - width;
if (position_y < 0) position_y = 0;
else if (position_y > window.innerHeight - height)
position_y = window.innerHeight - height;
start_x = new_x;
start_y = new_y;
// Move magnifier to new position
guac_magnifier.move(position_x, position_y);
// Update contents relative to new position
var clip_x = position_x
/ (window.innerWidth - width) * (GuacamoleUI.client.getWidth() - width);
var clip_y = position_y
/ (window.innerHeight - height) * (GuacamoleUI.client.getHeight() - height);
GuacamoleUI.magnifier.style.WebkitTransform =
GuacamoleUI.magnifier.style.MozTransform =
GuacamoleUI.magnifier.style.OTransform =
GuacamoleUI.magnifier.style.msTransform =
GuacamoleUI.magnifier.style.transform = "translate("
+ (-clip_x) + "px, " + (-clip_y) + "px)";
}
e.preventDefault();
}, false);
GuacamoleUI.containers.magnifier.addEventListener("click", function(e) {
GuacamoleUI.eventTarget.focus();
}, false);
this.move = function(x, y) {
GuacamoleUI.containers.magnifier.style.WebkitTransform =
GuacamoleUI.containers.magnifier.style.MozTransform =
GuacamoleUI.containers.magnifier.style.OTransform =
GuacamoleUI.containers.magnifier.style.msTransform =
GuacamoleUI.containers.magnifier.style.transform = "translate("
+ x + "px, " + y + "px)";
};
this.update = function() {
GuacamoleUI.magnifierContext.drawImage(GuacamoleUI.client.flatten(),
0, 0);
};
this.show = function(x, y) {
// Get client dimensions
var width = GuacamoleUI.client.getWidth();
var height = GuacamoleUI.client.getHeight();
// Resize to fit client
GuacamoleUI.magnifier.width = width;
GuacamoleUI.magnifier.height = height;
// Ensure transformations on display originate at 0,0
GuacamoleUI.containers.magnifier.style.transformOrigin =
GuacamoleUI.containers.magnifier.style.webkitTransformOrigin =
GuacamoleUI.containers.magnifier.style.MozTransformOrigin =
GuacamoleUI.containers.magnifier.style.OTransformOrigin =
GuacamoleUI.containers.magnifier.style.msTransformOrigin =
"0 0";
// Show magnifier
GuacamoleUI.containers.magnifier.style.display = "block";
guac_magnifier.update();
};
this.hide = function() {
GuacamoleUI.containers.magnifier.style.display = "none";
};
})();
// If Node.classList is supported, implement addClass/removeClass using that
if (Node.classList) {
@@ -678,7 +560,7 @@ GuacamoleUI.attach = function(guac) {
longPressTimeout = window.setTimeout(function() {
longPressTimeout = null;
GuacamoleUI.Magnifier.show();
GuacUI.StateManager.setState(GuacUI.Client.states.MAGNIFIER);
}, GuacamoleUI.LONG_PRESS_DETECT_TIMEOUT);
}
@@ -692,9 +574,6 @@ GuacamoleUI.attach = function(guac) {
// Detect long-press at bottom of screen
GuacamoleUI.display.addEventListener('touchstart', function(e) {
// Close magnifier
GuacamoleUI.Magnifier.hide();
// Record touch location
if (e.touches.length == 1) {
var touch = e.touches[0];

View File

@@ -42,9 +42,11 @@ img {
}
#eventTarget {
position: absolute;
width: 100%;
height: 100%;
position: fixed;
left: 0;
top: 0;
width: 1px;
height: 1px;
opacity: 0;
}
@@ -170,13 +172,22 @@ div#display > * {
margin-right: auto;
}
div#magnifier {
div#magnifier-background {
display: none;
position: absolute;
left: 0;
right: 0;
top: 0;
width: 100%;
height: 100%;
z-index: 1;
overflow: hidden;
}
div#magnifier {
position: absolute;
left: 0;
top: 0;
box-shadow: 2px 2px 10px rgba(0, 0, 0, 0.75);
width: 50%;
@@ -185,6 +196,15 @@ div#magnifier {
}
div#pan-overlay {
display: none;
width: 100%;
height: 100%;
position: absolute;
left: 0;
top: 0;
}
/* Viewport Clone */
div#viewportClone {