Implement apps v2 custom window frame.

The default apps v2 container is pretty basic. We want to provide something that looks prettier and also implements some functionality specific to our use-case:

* When connected to a host, a disconnect icon is added to the window controls (it's therefore no longer needed in the tool-bar).
* When connected to a host, maximize == full-screen.
* In full-screen mode, the window controls are still accessible, but are auto-hidden near the top-left corner (but not obscuring it, since it's often a hot-spot on the server).
* For touch-screen devices with no concept of hover, clicking the "stub" will also reveal the controls.

There should be no change to the v1 UX, but I don't plan on landing this CL before the M36 branch point, just in case.

BUG=134213

Review URL: https://codereview.chromium.org/265393005

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@270342 0039d316-1c4b-4281-b951-d872f2087c98
parent 79e70d0f
...@@ -206,6 +206,7 @@ ...@@ -206,6 +206,7 @@
'webapp/manifest.json.jinja2', 'webapp/manifest.json.jinja2',
'webapp/paired_client_manager.js', 'webapp/paired_client_manager.js',
'webapp/remoting.js', 'webapp/remoting.js',
'webapp/window_frame.js',
], ],
}, },
'actions': [ 'actions': [
......
...@@ -90,6 +90,7 @@ ...@@ -90,6 +90,7 @@
'webapp/menu_button.js', 'webapp/menu_button.js',
'webapp/ui_mode.js', 'webapp/ui_mode.js',
'webapp/toolbar.js', 'webapp/toolbar.js',
'webapp/window_frame.js',
], ],
# UI files for controlling the local machine as a host. # UI files for controlling the local machine as a host.
'remoting_webapp_js_ui_host_control_files': [ 'remoting_webapp_js_ui_host_control_files': [
...@@ -169,10 +170,15 @@ ...@@ -169,10 +170,15 @@
'remoting_webapp_resource_files': [ 'remoting_webapp_resource_files': [
'resources/disclosure_arrow_down.webp', 'resources/disclosure_arrow_down.webp',
'resources/disclosure_arrow_right.webp', 'resources/disclosure_arrow_right.webp',
'resources/drag.webp',
'resources/host_setup_instructions.webp', 'resources/host_setup_instructions.webp',
'resources/icon_close.webp',
'resources/icon_cross.webp', 'resources/icon_cross.webp',
'resources/icon_disconnect.webp',
'resources/icon_help.webp', 'resources/icon_help.webp',
'resources/icon_host.webp', 'resources/icon_host.webp',
'resources/icon_maximize_restore.webp',
'resources/icon_minimize.webp',
'resources/icon_pencil.webp', 'resources/icon_pencil.webp',
'resources/icon_warning.webp', 'resources/icon_warning.webp',
'resources/infographic_my_computers.webp', 'resources/infographic_my_computers.webp',
...@@ -188,6 +194,7 @@ ...@@ -188,6 +194,7 @@
'webapp/scale-to-fit.webp', 'webapp/scale-to-fit.webp',
'webapp/spinner.gif', 'webapp/spinner.gif',
'webapp/toolbar.css', 'webapp/toolbar.css',
'webapp/window_frame.css',
], ],
'remoting_webapp_files': [ 'remoting_webapp_files': [
...@@ -225,6 +232,7 @@ ...@@ -225,6 +232,7 @@
'webapp/html/ui_header.html', 'webapp/html/ui_header.html',
'webapp/html/ui_it2me.html', 'webapp/html/ui_it2me.html',
'webapp/html/ui_me2me.html', 'webapp/html/ui_me2me.html',
'webapp/html/window_frame.html',
], ],
}, },
......
...@@ -552,9 +552,24 @@ For information about privacy, please see the Google Privacy Policy (http://goo. ...@@ -552,9 +552,24 @@ For information about privacy, please see the Google Privacy Policy (http://goo.
<message desc="Footer text displayed at the host after an access code has been generated, but before a client connects." name="IDS_FOOTER_WAITING"> <message desc="Footer text displayed at the host after an access code has been generated, but before a client connects." name="IDS_FOOTER_WAITING">
waiting for connection… waiting for connection…
</message> </message>
<message desc="Menu option for toggle full-screen mode. Equivalent to using the Wrench menu to do the same thing." name="IDS_FULL_SCREEN" formatter_data="android_java"> <message desc="Menu option for toggle full-screen mode." name="IDS_FULL_SCREEN" formatter_data="android_java">
Full screen Full screen
</message> </message>
<message desc="Icon to leave full-screen mode." name="IDS_EXIT_FULL_SCREEN">
Exit full screen
</message>
<message desc="Tool-tip for the window's close icon." name="IDS_CLOSE_WINDOW">
Close window
</message>
<message desc="Tool-tip for the window's maximize icon." name="IDS_MAXIMIZE_WINDOW">
Maximize window
</message>
<message desc="Tool-tip for the window's minimize icon." name="IDS_MINIMIZE_WINDOW">
Minimize window
</message>
<message desc="Tool-tip for the window's restore icon." name="IDS_RESTORE_WINDOW">
Restore window
</message>
<message desc="Button displayed underneath explanatory text for app features. Clicking causes the text, infographic and the button itself to be replaced by the actual UI for that feature." name="IDS_GET_STARTED"> <message desc="Button displayed underneath explanatory text for app features. Clicking causes the text, infographic and the button itself to be replaced by the actual UI for that feature." name="IDS_GET_STARTED">
Get started Get started
</message> </message>
......
...@@ -8,7 +8,8 @@ var kNewWindowId = 'new-window'; ...@@ -8,7 +8,8 @@ var kNewWindowId = 'new-window';
function createWindow() { function createWindow() {
chrome.app.window.create('main.html', { chrome.app.window.create('main.html', {
'width': 800, 'width': 800,
'height': 600 'height': 600,
'frame': 'none'
}); });
}; };
......
...@@ -176,28 +176,29 @@ remoting.ClientSession.prototype.updateScrollbarVisibility = function() { ...@@ -176,28 +176,29 @@ remoting.ClientSession.prototype.updateScrollbarVisibility = function() {
if (!this.shrinkToFit_) { if (!this.shrinkToFit_) {
// Determine whether or not horizontal or vertical scrollbars are // Determine whether or not horizontal or vertical scrollbars are
// required, taking into account their width. // required, taking into account their width.
needsVerticalScroll = window.innerHeight < this.plugin_.desktopHeight; var clientArea = this.getClientArea_();
needsHorizontalScroll = window.innerWidth < this.plugin_.desktopWidth; needsVerticalScroll = clientArea.height < this.plugin_.desktopHeight;
needsHorizontalScroll = clientArea.width < this.plugin_.desktopWidth;
var kScrollBarWidth = 16; var kScrollBarWidth = 16;
if (needsHorizontalScroll && !needsVerticalScroll) { if (needsHorizontalScroll && !needsVerticalScroll) {
needsVerticalScroll = needsVerticalScroll =
window.innerHeight - kScrollBarWidth < this.plugin_.desktopHeight; clientArea.height - kScrollBarWidth < this.plugin_.desktopHeight;
} else if (!needsHorizontalScroll && needsVerticalScroll) { } else if (!needsHorizontalScroll && needsVerticalScroll) {
needsHorizontalScroll = needsHorizontalScroll =
window.innerWidth - kScrollBarWidth < this.plugin_.desktopWidth; clientArea.width - kScrollBarWidth < this.plugin_.desktopWidth;
} }
} }
var htmlNode = /** @type {HTMLElement} */ (document.documentElement); var scroller = document.getElementById('scroller');
if (needsHorizontalScroll) { if (needsHorizontalScroll) {
htmlNode.classList.remove('no-horizontal-scroll'); scroller.classList.remove('no-horizontal-scroll');
} else { } else {
htmlNode.classList.add('no-horizontal-scroll'); scroller.classList.add('no-horizontal-scroll');
} }
if (needsVerticalScroll) { if (needsVerticalScroll) {
htmlNode.classList.remove('no-vertical-scroll'); scroller.classList.remove('no-vertical-scroll');
} else { } else {
htmlNode.classList.add('no-vertical-scroll'); scroller.classList.add('no-vertical-scroll');
} }
}; };
...@@ -568,6 +569,9 @@ remoting.ClientSession.prototype.removePlugin = function() { ...@@ -568,6 +569,9 @@ remoting.ClientSession.prototype.removePlugin = function() {
function() { function() {
remoting.fullscreen.removeListener(listener); remoting.fullscreen.removeListener(listener);
}); });
if (remoting.windowFrame) {
remoting.windowFrame.setConnected(false);
}
// Remove mediasource-rendering class from video-contained - this will also // Remove mediasource-rendering class from video-contained - this will also
// hide the <video> element. // hide the <video> element.
...@@ -766,9 +770,10 @@ remoting.ClientSession.prototype.onSetScreenMode_ = function(event) { ...@@ -766,9 +770,10 @@ remoting.ClientSession.prototype.onSetScreenMode_ = function(event) {
remoting.ClientSession.prototype.setScreenMode_ = remoting.ClientSession.prototype.setScreenMode_ =
function(shrinkToFit, resizeToClient) { function(shrinkToFit, resizeToClient) {
if (resizeToClient && !this.resizeToClient_) { if (resizeToClient && !this.resizeToClient_) {
this.plugin_.notifyClientResolution(window.innerWidth, var clientArea = this.getClientArea_();
window.innerHeight, this.plugin_.notifyClientResolution(clientArea.width,
window.devicePixelRatio); clientArea.height,
window.devicePixelRatio);
} }
// If enabling shrink, reset bump-scroll offsets. // If enabling shrink, reset bump-scroll offsets.
...@@ -953,13 +958,18 @@ remoting.ClientSession.prototype.onConnectionStatusUpdate_ = ...@@ -953,13 +958,18 @@ remoting.ClientSession.prototype.onConnectionStatusUpdate_ =
this.setFocusHandlers_(); this.setFocusHandlers_();
this.onDesktopSizeChanged_(); this.onDesktopSizeChanged_();
if (this.resizeToClient_) { if (this.resizeToClient_) {
this.plugin_.notifyClientResolution(window.innerWidth, var clientArea = this.getClientArea_();
window.innerHeight, this.plugin_.notifyClientResolution(clientArea.width,
window.devicePixelRatio); clientArea.height,
window.devicePixelRatio);
} }
// Start listening for full-screen related events. // Activate full-screen related UX.
remoting.fullscreen.addListener(this.callOnFullScreenChanged_); remoting.fullscreen.addListener(this.callOnFullScreenChanged_);
remoting.fullscreen.syncWithMaximize(true); remoting.fullscreen.syncWithMaximize(true);
if (remoting.windowFrame) {
remoting.windowFrame.setConnected(true);
}
} else if (status == remoting.ClientSession.State.FAILED) { } else if (status == remoting.ClientSession.State.FAILED) {
switch (error) { switch (error) {
case remoting.ClientSession.ConnectionError.HOST_IS_OFFLINE: case remoting.ClientSession.ConnectionError.HOST_IS_OFFLINE:
...@@ -1019,9 +1029,10 @@ remoting.ClientSession.prototype.onSetCapabilities_ = function(capabilities) { ...@@ -1019,9 +1029,10 @@ remoting.ClientSession.prototype.onSetCapabilities_ = function(capabilities) {
this.capabilities_ = capabilities; this.capabilities_ = capabilities;
if (this.hasCapability_( if (this.hasCapability_(
remoting.ClientSession.Capability.SEND_INITIAL_RESOLUTION)) { remoting.ClientSession.Capability.SEND_INITIAL_RESOLUTION)) {
this.plugin_.notifyClientResolution(window.innerWidth, var clientArea = this.getClientArea_();
window.innerHeight, this.plugin_.notifyClientResolution(clientArea.width,
window.devicePixelRatio); clientArea.height,
window.devicePixelRatio);
} }
}; };
...@@ -1080,10 +1091,11 @@ remoting.ClientSession.prototype.onResize = function() { ...@@ -1080,10 +1091,11 @@ remoting.ClientSession.prototype.onResize = function() {
remoting.ClientSession.Capability.RATE_LIMIT_RESIZE_REQUESTS)) { remoting.ClientSession.Capability.RATE_LIMIT_RESIZE_REQUESTS)) {
kResizeRateLimitMs = 250; kResizeRateLimitMs = 250;
} }
var clientArea = this.getClientArea_();
this.notifyClientResolutionTimer_ = window.setTimeout( this.notifyClientResolutionTimer_ = window.setTimeout(
this.plugin_.notifyClientResolution.bind(this.plugin_, this.plugin_.notifyClientResolution.bind(this.plugin_,
window.innerWidth, clientArea.width,
window.innerHeight, clientArea.height,
window.devicePixelRatio), window.devicePixelRatio),
kResizeRateLimitMs); kResizeRateLimitMs);
} }
...@@ -1148,8 +1160,7 @@ remoting.ClientSession.prototype.updateDimensions = function() { ...@@ -1148,8 +1160,7 @@ remoting.ClientSession.prototype.updateDimensions = function() {
return; return;
} }
var windowWidth = window.innerWidth; var clientArea = this.getClientArea_();
var windowHeight = window.innerHeight;
var desktopWidth = this.plugin_.desktopWidth; var desktopWidth = this.plugin_.desktopWidth;
var desktopHeight = this.plugin_.desktopHeight; var desktopHeight = this.plugin_.desktopHeight;
...@@ -1174,8 +1185,9 @@ remoting.ClientSession.prototype.updateDimensions = function() { ...@@ -1174,8 +1185,9 @@ remoting.ClientSession.prototype.updateDimensions = function() {
if (this.shrinkToFit_) { if (this.shrinkToFit_) {
// Reduce the scale, if necessary, to fit the whole desktop in the window. // Reduce the scale, if necessary, to fit the whole desktop in the window.
var scaleFitWidth = Math.min(scale, 1.0 * windowWidth / desktopWidth); var scaleFitWidth = Math.min(scale, 1.0 * clientArea.width / desktopWidth);
var scaleFitHeight = Math.min(scale, 1.0 * windowHeight / desktopHeight); var scaleFitHeight =
Math.min(scale, 1.0 * clientArea.height / desktopHeight);
scale = Math.min(scaleFitHeight, scaleFitWidth); scale = Math.min(scaleFitHeight, scaleFitWidth);
// If we're running full-screen then try to handle common side-by-side // If we're running full-screen then try to handle common side-by-side
...@@ -1334,12 +1346,13 @@ remoting.ClientSession.prototype.scroll_ = function(dx, dy) { ...@@ -1334,12 +1346,13 @@ remoting.ClientSession.prototype.scroll_ = function(dx, dy) {
}; };
var stopX = { stop: false }; var stopX = { stop: false };
var clientArea = this.getClientArea_();
style.marginLeft = adjustMargin(style.marginLeft, dx, style.marginLeft = adjustMargin(style.marginLeft, dx,
window.innerWidth, plugin.clientWidth, stopX); clientArea.width, plugin.clientWidth, stopX);
var stopY = { stop: false }; var stopY = { stop: false };
style.marginTop = adjustMargin(style.marginTop, dy, style.marginTop = adjustMargin(
window.innerHeight, plugin.clientHeight, stopY); style.marginTop, dy, clientArea.height, plugin.clientHeight, stopY);
return stopX.stop && stopY.stop; return stopX.stop && stopY.stop;
}; };
...@@ -1396,8 +1409,9 @@ remoting.ClientSession.prototype.onMouseMove_ = function(event) { ...@@ -1396,8 +1409,9 @@ remoting.ClientSession.prototype.onMouseMove_ = function(event) {
return 0; return 0;
}; };
var dx = computeDelta(event.x, window.innerWidth); var clientArea = this.getClientArea_();
var dy = computeDelta(event.y, window.innerHeight); var dx = computeDelta(event.x, clientArea.width);
var dy = computeDelta(event.y, clientArea.height);
if (dx != 0 || dy != 0) { if (dx != 0 || dy != 0) {
/** @type {remoting.ClientSession} */ /** @type {remoting.ClientSession} */
...@@ -1475,3 +1489,15 @@ remoting.ClientSession.prototype.createGnubbyAuthHandler_ = function() { ...@@ -1475,3 +1489,15 @@ remoting.ClientSession.prototype.createGnubbyAuthHandler_ = function() {
this.sendGnubbyAuthMessage({'type': 'control', 'option': 'auth-v1'}); this.sendGnubbyAuthMessage({'type': 'control', 'option': 'auth-v1'});
} }
}; };
/**
* @return {{width: number, height: number}} The height of the window's client
* area. This differs between apps v1 and apps v2 due to the custom window
* borders used by the latter.
* @private
*/
remoting.ClientSession.prototype.getClientArea_ = function() {
return remoting.windowFrame ?
remoting.windowFrame.getClientArea() :
{ 'width': window.innerWidth, 'height': window.innerHeight };
}
\ No newline at end of file
...@@ -101,6 +101,12 @@ function onLoad() { ...@@ -101,6 +101,12 @@ function onLoad() {
remoting.init(); remoting.init();
window.addEventListener('resize', remoting.onResize, false); window.addEventListener('resize', remoting.onResize, false);
// When a window goes full-screen, a resize event is triggered, but the
// Fullscreen.isActive call is not guaranteed to return true until the
// full-screen event is triggered. In apps v2, the size of the window's
// client area is calculated differently in full-screen mode, so register
// for both events.
remoting.fullscreen.addListener(remoting.onResize);
if (!remoting.isAppsV2) { if (!remoting.isAppsV2) {
window.addEventListener('beforeunload', remoting.promptClose, false); window.addEventListener('beforeunload', remoting.promptClose, false);
window.addEventListener('unload', remoting.disconnect, false); window.addEventListener('unload', remoting.disconnect, false);
......
...@@ -102,6 +102,7 @@ remoting.FullscreenAppsV2.prototype.syncWithMaximize = function(sync) { ...@@ -102,6 +102,7 @@ remoting.FullscreenAppsV2.prototype.syncWithMaximize = function(sync) {
remoting.FullscreenAppsV2.prototype.onFullscreened_ = function() { remoting.FullscreenAppsV2.prototype.onFullscreened_ = function() {
this.notifyCallbacksOnRestore_ = true; this.notifyCallbacksOnRestore_ = true;
this.eventSource_.raiseEvent(this.kEventName_, true); this.eventSource_.raiseEvent(this.kEventName_, true);
document.body.classList.add('fullscreen');
}; };
remoting.FullscreenAppsV2.prototype.onMaximized_ = function() { remoting.FullscreenAppsV2.prototype.onMaximized_ = function() {
...@@ -111,6 +112,7 @@ remoting.FullscreenAppsV2.prototype.onMaximized_ = function() { ...@@ -111,6 +112,7 @@ remoting.FullscreenAppsV2.prototype.onMaximized_ = function() {
}; };
remoting.FullscreenAppsV2.prototype.onRestored_ = function() { remoting.FullscreenAppsV2.prototype.onRestored_ = function() {
document.body.classList.remove('fullscreen');
if (this.hookingWindowEvents_) { if (this.hookingWindowEvents_) {
this.activate(false); this.activate(false);
} }
......
...@@ -5,7 +5,7 @@ Use of this source code is governed by a BSD-style license that can be ...@@ -5,7 +5,7 @@ Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file. found in the LICENSE file.
--> -->
<html class="scrollable full-height"> <html class="full-height">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<link rel="icon" type="image/png" href="chromoting16.webp"> <link rel="icon" type="image/png" href="chromoting16.webp">
...@@ -14,13 +14,16 @@ found in the LICENSE file. ...@@ -14,13 +14,16 @@ found in the LICENSE file.
<link rel="stylesheet" href="main.css"> <link rel="stylesheet" href="main.css">
<link rel="stylesheet" href="menu_button.css"> <link rel="stylesheet" href="menu_button.css">
<link rel="stylesheet" href="toolbar.css"> <link rel="stylesheet" href="toolbar.css">
<link rel="stylesheet" href="window_frame.css">
<meta-include type="javascript"/> <meta-include type="javascript"/>
<title i18n-content="PRODUCT_NAME"></title> <title i18n-content="PRODUCT_NAME"></title>
</head> </head>
<body class="full-height"> <body class="full-height inner-border-for-apps-v2">
<meta-include src="webapp/html/window_frame.html"/>
<!-- loading-mode is initially visible, but becomes hidden as soon as an <!-- loading-mode is initially visible, but becomes hidden as soon as an
AppMode is selected by remoting.init. All other divs are initially AppMode is selected by remoting.init. All other divs are initially
...@@ -34,62 +37,65 @@ found in the LICENSE file. ...@@ -34,62 +37,65 @@ found in the LICENSE file.
<iframe id="wcs-sandbox" src="wcs_sandbox.html" hidden></iframe> <iframe id="wcs-sandbox" src="wcs_sandbox.html" hidden></iframe>
<div class="inset" data-ui-mode="home" hidden> <div id="scroller">
<div class="inset" data-ui-mode="home" hidden>
<meta-include src="webapp/html/ui_header.html"/> <meta-include src="webapp/html/ui_header.html"/>
<meta-include src="webapp/html/butterbar.html"/> <meta-include src="webapp/html/butterbar.html"/>
<meta-include src="webapp/html/ui_it2me.html"/> <meta-include src="webapp/html/ui_it2me.html"/>
<meta-include src="webapp/html/ui_me2me.html"/> <meta-include src="webapp/html/ui_me2me.html"/>
</div> </div> <!-- inset -->
<meta-include src="webapp/html/dialog_auth.html"/> <meta-include src="webapp/html/dialog_auth.html"/>
<div class="dialog-screen" <div class="dialog-screen"
data-ui-mode="home.host home.client home.history home.confirm-host-delete home.host-setup home.token-refresh-failed home.manage-pairings" data-ui-mode="home.host home.client home.history home.confirm-host-delete home.host-setup home.token-refresh-failed home.manage-pairings"
hidden></div> hidden></div>
<div class="dialog-container" <div class="dialog-container"
data-ui-mode="home.host home.client home.history home.confirm-host-delete home.host-install home.host-setup home.token-refresh-failed home.manage-pairings" data-ui-mode="home.host home.client home.history home.confirm-host-delete home.host-install home.host-setup home.token-refresh-failed home.manage-pairings"
hidden> hidden>
<meta-include src="webapp/html/dialog_token_refresh_failed.html"/> <meta-include src="webapp/html/dialog_token_refresh_failed.html"/>
<meta-include src="webapp/html/dialog_host_setup.html"/> <meta-include src="webapp/html/dialog_host_setup.html"/>
<meta-include src="webapp/html/dialog_host_install.html"/> <meta-include src="webapp/html/dialog_host_install.html"/>
<meta-include src="webapp/html/dialog_host.html"/> <meta-include src="webapp/html/dialog_host.html"/>
<div id="client-dialog" <div id="client-dialog"
class="kd-modaldialog" class="kd-modaldialog"
data-ui-mode="home.client"> data-ui-mode="home.client">
<meta-include src="webapp/html/dialog_client_unconnected.html"/> <meta-include src="webapp/html/dialog_client_unconnected.html"/>
<meta-include src="webapp/html/dialog_client_connecting.html"/> <meta-include src="webapp/html/dialog_client_connecting.html"/>
<meta-include src="webapp/html/dialog_client_host_needs_upgrade.html"/> <meta-include src="webapp/html/dialog_client_host_needs_upgrade.html"/>
<meta-include src="webapp/html/dialog_client_pin_prompt.html"/> <meta-include src="webapp/html/dialog_client_pin_prompt.html"/>
<meta-include src="webapp/html/dialog_client_third_party_auth.html"/> <meta-include src="webapp/html/dialog_client_third_party_auth.html"/>
<meta-include src="webapp/html/dialog_client_connect_failed.html"/> <meta-include src="webapp/html/dialog_client_connect_failed.html"/>
<meta-include src="webapp/html/dialog_client_session_finished.html"/> <meta-include src="webapp/html/dialog_client_session_finished.html"/>
</div> </div>
<meta-include src="webapp/html/dialog_connection_history.html"/> <meta-include src="webapp/html/dialog_connection_history.html"/>
<meta-include src="webapp/html/dialog_confirm_host_delete.html"/> <meta-include src="webapp/html/dialog_confirm_host_delete.html"/>
<meta-include src="webapp/html/dialog_manage_pairings.html"/> <meta-include src="webapp/html/dialog_manage_pairings.html"/>
</div> <!-- dialog-container --> </div> <!-- dialog-container -->
<div id="session-mode" <div id="session-mode"
data-ui-mode="in-session home.client" data-ui-mode="in-session home.client"
class="full-height" class="full-height"
hidden> hidden>
<meta-include src="webapp/html/toolbar.html"/> <meta-include src="webapp/html/toolbar.html"/>
<meta-include src="webapp/html/client_plugin.html"/> <meta-include src="webapp/html/client_plugin.html"/>
</div> </div> <!-- session-mode -->
<div id="statistics" dir="ltr" class="selectable" hidden>
</div>
<div id="statistics" dir="ltr" class="selectable" hidden> </div> <!-- scroller ->
</div>
</body> </body>
</html> </html>
...@@ -23,7 +23,8 @@ found in the LICENSE file. ...@@ -23,7 +23,8 @@ found in the LICENSE file.
<button id="toolbar-disconnect" <button id="toolbar-disconnect"
type="button" type="button"
i18n-content="DISCONNECT_MYSELF_BUTTON"> i18n-content="DISCONNECT_MYSELF_BUTTON"
class="apps-v1-only">
</button> </button>
<span class="menu-button" id="send-keys-menu"> <span class="menu-button" id="send-keys-menu">
...@@ -47,9 +48,12 @@ found in the LICENSE file. ...@@ -47,9 +48,12 @@ found in the LICENSE file.
<ul> <ul>
<li id="screen-resize-to-client" <li id="screen-resize-to-client"
i18n-content="RESIZE_TO_CLIENT"></li> i18n-content="RESIZE_TO_CLIENT"></li>
<li id="screen-shrink-to-fit" i18n-content="SHRINK_TO_FIT"></li> <li id="screen-shrink-to-fit"
<li class="menu-separator"></li> i18n-content="SHRINK_TO_FIT"></li>
<li id="toggle-full-screen" i18n-content="FULL_SCREEN"></li> <li class="menu-separator apps-v1-only"></li>
<li id="toggle-full-screen"
i18n-content="FULL_SCREEN"
class="apps-v1-only"></li>
</ul> </ul>
</span> </span>
......
<!--
Copyright 2014 The Chromium Authors. All rights reserved.
Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file.
-->
<div id="title-bar" class="title-bar apps-v2-only">
<span class="window-title">&nbsp;</span>
<span class="window-controls-hover-target">
<div class="window-controls">
<span i18n-title="DISCONNECT_MYSELF_BUTTON"
class="window-control window-disconnect">
<img src="icon_disconnect.webp">
</span>
<span i18n-title="MINIMIZE_WINDOW"
class="window-control window-minimize">
<img src="icon_minimize.webp">
</span>
<span i18n-title="MAXIMIZE_WINDOW"
class="window-control window-maximize-restore">
<img src="icon_maximize_restore.webp">
</span>
<span i18n-title="CLOSE_WINDOW"
class="window-control window-close">
<img src="icon_close.webp">
</span>
</div>
<div class="window-controls-stub">&nbsp;</div>
</span>
</div>
...@@ -287,6 +287,7 @@ var AppWindow = function() { ...@@ -287,6 +287,7 @@ var AppWindow = function() {
AppWindow.prototype.close = function() {}; AppWindow.prototype.close = function() {};
AppWindow.prototype.drawAttention = function() {}; AppWindow.prototype.drawAttention = function() {};
AppWindow.prototype.maximize = function() {};
AppWindow.prototype.minimize = function() {}; AppWindow.prototype.minimize = function() {};
AppWindow.prototype.restore = function() {}; AppWindow.prototype.restore = function() {};
AppWindow.prototype.fullscreen = function() {}; AppWindow.prototype.fullscreen = function() {};
......
...@@ -20,6 +20,7 @@ tfoot, thead, tr, th, td, button { ...@@ -20,6 +20,7 @@ tfoot, thead, tr, th, td, button {
.inset { .inset {
padding: 20px 20px 0 20px; padding: 20px 20px 0 20px;
position: relative;
} }
body { body {
...@@ -30,6 +31,7 @@ body { ...@@ -30,6 +31,7 @@ body {
direction: __MSG_@@bidi_dir__; direction: __MSG_@@bidi_dir__;
} }
/* /*
* The "app-v2" class is added to the <html> node by remoting.init if it's * The "app-v2" class is added to the <html> node by remoting.init if it's
* running as a V2 app. * running as a V2 app.
...@@ -621,7 +623,7 @@ button { ...@@ -621,7 +623,7 @@ button {
} }
.dialog-screen { .dialog-screen {
position: fixed; position: absolute;
top: 0; top: 0;
left: 0; left: 0;
width: 100%; width: 100%;
......
...@@ -69,6 +69,8 @@ remoting.init = function() { ...@@ -69,6 +69,8 @@ remoting.init = function() {
if (remoting.isAppsV2) { if (remoting.isAppsV2) {
remoting.identity = new remoting.Identity(consentRequired_); remoting.identity = new remoting.Identity(consentRequired_);
remoting.fullscreen = new remoting.FullscreenAppsV2(); remoting.fullscreen = new remoting.FullscreenAppsV2();
remoting.windowFrame = new remoting.WindowFrame(
document.getElementById('title-bar'));
} else { } else {
remoting.oauth2 = new remoting.OAuth2(); remoting.oauth2 = new remoting.OAuth2();
if (!remoting.oauth2.isAuthenticated()) { if (!remoting.oauth2.isAuthenticated()) {
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
*/ */
.toolbar-container { .toolbar-container {
position: fixed; position: absolute;
top: -48px; top: -48px;
width: 640px; width: 640px;
-webkit-transition: top 0.15s ease; -webkit-transition: top 0.15s ease;
......
/* Copyright 2014 The Chromium Authors. All rights reserved.
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
html.apps-v2,
html.apps-v2 body {
height: 100%;
width: 100%;
}
html.apps-v2 body:not(.fullscreen) {
border: 1px solid gray; /* This is the window border. */
}
html.apps-v2 .title-bar {
border-bottom: 1px solid gray;
z-index: 100;
}
.window-title,
.window-controls-hover-target {
height: 32px;
line-height: 32px;
font-size: 16px;
background-color: #c4c4c4;
}
.title-bar .window-title {
padding-__MSG_@@bidi_start_edge__: 12px;
width: 100%;
display: inline-block;
-webkit-app-region: drag;
}
.window-controls-hover-target {
-webkit-app-region: no-drag;
position: fixed;
top: 1px;
__MSG_@@bidi_end_edge__: 1px;
}
.window-controls-hover-target {
display: table;
}
.window-controls-hover-target > div:first-child {
display: table-row;
}
.window-control {
height: 32px;
width: 32px;
text-align: center;
display: inline-block;
border-__MSG_@@bidi_start_edge__: 1px solid rgba(0, 0, 0, 0.2);
}
.window-control:hover {
background-color: #d5d5d5;
}
.window-control:active {
background-color: #a6a6a6;
}
.window-control > img {
margin-bottom: -2px;
}
.window-controls-stub {
display: none;
-webkit-column-span: all;
line-height: 3px;
background: url("drag.webp");
border-top: 1px solid rgba(0, 0, 0, 0.2);
}
#scroller {
height: 100%;
width: 100%;
overflow: auto;
position: relative;
}
html.apps-v2 #scroller {
height: calc(100% - 32px); /** Allow space for the title-bar */
}
/* Add an etched border to the window controls, title bar and stub */
.title-bar,
.window-control,
.window-controls-stub {
position: relative;
}
.title-bar:after,
.window-control:after,
.window-controls-stub:after {
content: "";
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
border-left: 1px solid rgba(255, 255, 255, 0.2);
border-top: 1px solid rgba(255, 255, 255, 0.2);
pointer-events: none;
}
/* When connected to a host, the Disconnect button is displayed. */
body:not(.connected) .window-disconnect {
display: none;
}
/*
* When in full-screen mode, significant changes are made:
* - The scroll-bars are removed.
* - The window controls have a border (so the left-border of the first button
* is not needed).
* - The title-bar (and its bottom border) are not displayed.
* - The stub is visible.
* - The window controls gain transition effects for position and opacity and
* auto-hide behind the top edge of the screen.
* - A border is added to the window controls to ensure they stand out against
* any desktop.
* - The window border is removed.
*/
html.apps-v2 body.fullscreen #scroller {
height: 100%;
overflow: hidden;
}
body.fullscreen .window-controls-hover-target {
border: 1px solid #a6a6a6;
}
body.fullscreen .window-control:first-child {
border-__MSG_@@bidi_start_edge__: none;
}
body.fullscreen .window-title {
display: none;
}
body.fullscreen .title-bar {
border-bottom: none;
}
body.fullscreen .window-controls-stub {
display: table-cell;
}
body.fullscreen .window-controls-hover-target {
transition-property: opacity, box-shadow, top;
transition-duration: 0.3s;
opacity: 0.7;
top: -33px;
__MSG_@@bidi_end_edge__: 8px;
}
body.fullscreen .window-controls-hover-target:hover,
body.fullscreen .window-controls-hover-target.opened {
top: -4px;
opacity: 1.0;
box-shadow: 1px 1px 10px rgba(0, 0, 0, 0.5);
}
.fullscreen .window-controls-hover-target.opened .window-controls-stub {
background-color: #a6a6a6;
}
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @fileoverview
* Apps v2 custom title bar implementation
*/
'use strict';
/** @suppress {duplicate} */
var remoting = remoting || {};
/**
* @param {HTMLElement} titleBar The root node of the title-bar DOM hierarchy.
* @constructor
*/
remoting.WindowFrame = function(titleBar) {
/**
* @type {boolean}
* @private
*/
this.isConnected_ = false;
/**
* @type {HTMLElement}
* @private
*/
this.titleBar_ = titleBar;
/**
* @type {HTMLElement}
* @private
*/
this.hoverTarget_ = /** @type {HTMLElement} */
(titleBar.querySelector('.window-controls-hover-target'));
base.debug.assert(this.hoverTarget_ != null);
/**
* @type {HTMLElement}
* @private
*/
this.maximizeRestoreControl_ = /** @type {HTMLElement} */
(titleBar.querySelector('.window-maximize-restore'));
base.debug.assert(this.maximizeRestoreControl_ != null);
/**
* @type {Array.<{cls:string, fn: function()}>}
*/
var handlers = [
{ cls: 'window-disconnect', fn: this.disconnectSession_.bind(this) },
{ cls: 'window-maximize-restore',
fn: this.maximizeOrRestoreWindow_.bind(this) },
{ cls: 'window-minimize', fn: this.minimizeWindow_.bind(this) },
{ cls: 'window-close', fn: window.close.bind(window) },
{ cls: 'window-controls-stub', fn: this.toggleWindowControls_.bind(this) }
];
for (var i = 0; i < handlers.length; ++i) {
var element = titleBar.querySelector('.' + handlers[i].cls);
base.debug.assert(element != null);
element.addEventListener('click', handlers[i].fn, false);
}
// Ensure that tool-tips are always correct.
this.updateMaximizeOrRestoreIconTitle_();
chrome.app.window.current().onMaximized.addListener(
this.updateMaximizeOrRestoreIconTitle_.bind(this));
chrome.app.window.current().onRestored.addListener(
this.updateMaximizeOrRestoreIconTitle_.bind(this));
chrome.app.window.current().onFullscreened.addListener(
this.updateMaximizeOrRestoreIconTitle_.bind(this));
};
/**
* @param {boolean} isConnected True if there is a connection active.
*/
remoting.WindowFrame.prototype.setConnected = function(isConnected) {
this.isConnected_ = isConnected;
if (this.isConnected_) {
document.body.classList.add('connected');
} else {
document.body.classList.remove('connected');
}
this.updateMaximizeOrRestoreIconTitle_();
};
/**
* @return {{width: number, height: number}} The size of the window, ignoring
* the title-bar and window borders, if visible.
*/
remoting.WindowFrame.prototype.getClientArea = function() {
if (chrome.app.window.current().isFullscreen()) {
return { 'height': window.innerHeight, 'width': window.innerWidth };
} else {
var kBorderWidth = 1;
var titleHeight = this.titleBar_.clientHeight;
return {
'height': window.innerHeight - titleHeight - 2 * kBorderWidth,
'width': window.innerWidth - 2 * kBorderWidth
};
}
};
/**
* @private
*/
remoting.WindowFrame.prototype.disconnectSession_ = function() {
// When the user disconnects, exit full-screen mode. This should not be
// necessary, as we do the same thing in client_session.js when the plugin
// is removed. However, there seems to be a bug in chrome.AppWindow.restore
// that causes it to get stuck in full-screen mode without this.
if (chrome.app.window.current().isFullscreen()) {
chrome.app.window.current().restore();
chrome.app.window.current().restore();
}
remoting.disconnect();
};
/**
* @private
*/
remoting.WindowFrame.prototype.maximizeOrRestoreWindow_ = function() {
/** @type {boolean} */
var restore =
chrome.app.window.current().isFullscreen() ||
chrome.app.window.current().isMaximized();
if (restore) {
// Restore twice: once to exit full-screen and once to exit maximized.
// If the app is not full-screen, or went full-screen without first
// being maximized, then the second restore has no effect.
chrome.app.window.current().restore();
chrome.app.window.current().restore();
} else {
chrome.app.window.current().maximize();
}
};
/**
* @private
*/
remoting.WindowFrame.prototype.minimizeWindow_ = function() {
chrome.app.window.current().minimize();
};
/**
* @private
*/
remoting.WindowFrame.prototype.toggleWindowControls_ = function() {
this.hoverTarget_.classList.toggle('opened');
};
/**
* Update the tool-top for the maximize/full-screen/restore icon to reflect
* its current behaviour.
*
* @private
*/
remoting.WindowFrame.prototype.updateMaximizeOrRestoreIconTitle_ = function() {
/** @type {string} */
var tag = '';
if (chrome.app.window.current().isFullscreen()) {
tag = /*i18n-content*/'EXIT_FULL_SCREEN';
} else if (chrome.app.window.current().isMaximized()) {
tag = /*i18n-content*/'RESTORE_WINDOW';
} else if (this.isConnected_) {
tag = /*i18n-content*/'FULL_SCREEN';
} else {
tag = /*i18n-content*/'MAXIMIZE_WINDOW';
}
this.maximizeRestoreControl_.title = l10n.getTranslationOrError(tag);
};
/** @type {remoting.WindowFrame} */
remoting.windowFrame = null;
\ No newline at end of file
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment