blob: 1d363bf4f1a3a2326baa0090b98e60a1de821e9b [file] [log] [blame]
// Copyright 2016 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.
var vrShellUi = (function() {
'use strict';
let scene = new ui.Scene();
let sceneManager;
let uiRootElement = document.querySelector('#ui');
let uiStyle = window.getComputedStyle(uiRootElement);
let scaleFactor = uiStyle.getPropertyValue('--scaleFactor');
function getStyleFloat(style, property) {
let value = parseFloat(style.getPropertyValue(property));
return isNaN(value) ? 0 : value;
}
class ContentQuad {
constructor() {
/** @const */ var SCREEN_HEIGHT = 1.6;
/** @const */ var SCREEN_DISTANCE = 2.0;
let element = new api.UiElement(0, 0, 0, 0);
element.setIsContentQuad(false);
element.setVisible(false);
element.setSize(SCREEN_HEIGHT * 16 / 9, SCREEN_HEIGHT);
element.setTranslation(0, 0, -SCREEN_DISTANCE);
this.elementId = scene.addElement(element);
}
show(visible) {
let update = new api.UiElementUpdate();
update.setVisible(visible);
scene.updateElement(this.elementId, update);
}
getElementId() {
return this.elementId;
}
};
class DomUiElement {
constructor(domId) {
let domElement = document.querySelector(domId);
// Pull copy rectangle from the position of the element.
let rect = domElement.getBoundingClientRect();
let pixelX = Math.floor(rect.left);
let pixelY = Math.floor(rect.top);
let pixelWidth = Math.ceil(rect.right) - pixelX;
let pixelHeight = Math.ceil(rect.bottom) - pixelY;
let element = new api.UiElement(pixelX, pixelY, pixelWidth, pixelHeight);
element.setSize(scaleFactor * pixelWidth / 1000,
scaleFactor * pixelHeight / 1000);
// Pull additional custom properties from CSS.
let style = window.getComputedStyle(domElement);
element.setTranslation(
getStyleFloat(style, '--tranX'),
getStyleFloat(style, '--tranY'),
getStyleFloat(style, '--tranZ'));
this.uiElementId = scene.addElement(element);
this.uiAnimationId = -1;
this.domElement = domElement;
}
};
class RoundButton extends DomUiElement {
constructor(domId, callback) {
super(domId);
let button = this.domElement.querySelector('.button');
button.addEventListener('mouseenter', this.onMouseEnter.bind(this));
button.addEventListener('mouseleave', this.onMouseLeave.bind(this));
button.addEventListener('click', callback);
}
configure(buttonOpacity, captionOpacity, distanceForward) {
let button = this.domElement.querySelector('.button');
let caption = this.domElement.querySelector('.caption');
button.style.opacity = buttonOpacity;
caption.style.opacity = captionOpacity;
let anim = new api.Animation(this.uiElementId, 150);
anim.setTranslation(0, 0, distanceForward);
if (this.uiAnimationId >= 0) {
scene.removeAnimation(this.uiAnimationId);
}
this.uiAnimationId = scene.addAnimation(anim);
scene.flush();
}
onMouseEnter() {
this.configure(1, 1, 0.015);
}
onMouseLeave() {
this.configure(0.8, 0, 0);
}
};
class Controls {
constructor(contentQuadId) {
this.buttons = [];
let descriptors = [
['#back', function() {
api.doAction(api.Action.HISTORY_BACK);
}],
['#reload', function() {
api.doAction(api.Action.RELOAD);
}],
['#forward', function() {
api.doAction(api.Action.HISTORY_FORWARD);
}],
];
/** @const */ var BUTTON_SPACING = 0.136;
let startPosition = -BUTTON_SPACING * (descriptors.length / 2.0 - 0.5);
for (let i = 0; i < descriptors.length; i++) {
// Use an invisible parent to simplify Z-axis movement on hover.
let position = new api.UiElement(0, 0, 0, 0);
position.setVisible(false);
position.setTranslation(startPosition + i * BUTTON_SPACING, -0.68, -1);
let id = scene.addElement(position);
let domId = descriptors[i][0];
let callback = descriptors[i][1];
let element = new RoundButton(domId, callback);
this.buttons.push(element);
let update = new api.UiElementUpdate();
update.setParentId(id);
update.setVisible(false);
scene.updateElement(element.uiElementId, update);
}
this.reloadUiButton = new DomUiElement('#reload-ui-button');
this.reloadUiButton.domElement.addEventListener('click', function() {
scene.purge();
api.doAction(api.Action.RELOAD_UI);
});
let update = new api.UiElementUpdate();
update.setParentId(contentQuadId);
update.setVisible(false);
update.setScale(2.2, 2.2, 1);
update.setTranslation(0, -0.6, 0.3);
update.setAnchoring(api.XAnchoring.XNONE, api.YAnchoring.YBOTTOM);
scene.updateElement(this.reloadUiButton.uiElementId, update);
}
show(visible) {
this.enabled = visible;
this.configure();
}
setReloadUiEnabled(enabled) {
this.reloadUiEnabled = enabled;
this.configure();
}
configure() {
for (let i = 0; i < this.buttons.length; i++) {
let update = new api.UiElementUpdate();
update.setVisible(this.enabled);
scene.updateElement(this.buttons[i].uiElementId, update);
}
let update = new api.UiElementUpdate();
update.setVisible(this.enabled && this.reloadUiEnabled);
scene.updateElement(this.reloadUiButton.uiElementId, update);
}
};
class SecureOriginWarnings {
constructor() {
/** @const */ var DISTANCE = 0.7;
/** @const */ var ANGLE_UP = 16.3 * Math.PI / 180.0;
// Permanent WebVR security warning. This warning is shown near the top of
// the field of view.
this.webVrSecureWarning = new DomUiElement('#webvr-not-secure-permanent');
let update = new api.UiElementUpdate();
update.setScale(DISTANCE, DISTANCE, 1);
update.setTranslation(0, DISTANCE * Math.sin(ANGLE_UP),
-DISTANCE * Math.cos(ANGLE_UP));
update.setRotation(1.0, 0.0, 0.0, ANGLE_UP);
update.setHitTestable(false);
update.setVisible(false);
update.setLockToFieldOfView(true);
scene.updateElement(this.webVrSecureWarning.uiElementId, update);
// Temporary WebVR security warning. This warning is shown in the center
// of the field of view, for a limited period of time.
this.transientWarning = new DomUiElement(
'#webvr-not-secure-transient');
update = new api.UiElementUpdate();
update.setScale(DISTANCE, DISTANCE, 1);
update.setTranslation(0, 0, -DISTANCE);
update.setHitTestable(false);
update.setVisible(false);
update.setLockToFieldOfView(true);
scene.updateElement(this.transientWarning.uiElementId, update);
}
show(visible) {
this.enabled = visible;
this.updateState();
}
setSecureOrigin(secure) {
this.isSecureOrigin = secure;
this.updateState();
}
updateState() {
/** @const */ var TRANSIENT_TIMEOUT_MS = 30000;
let visible = (this.enabled && !this.isSecureOrigin);
if (this.secureOriginTimer) {
clearInterval(this.secureOriginTimer);
this.secureOriginTimer = null;
}
if (visible) {
this.secureOriginTimer = setTimeout(
this.onTransientTimer.bind(this), TRANSIENT_TIMEOUT_MS);
}
this.showOrHideWarnings(visible);
}
showOrHideWarnings(visible) {
let update = new api.UiElementUpdate();
update.setVisible(visible);
scene.updateElement(this.webVrSecureWarning.uiElementId, update);
update = new api.UiElementUpdate();
update.setVisible(visible);
scene.updateElement(this.transientWarning.uiElementId, update);
}
onTransientTimer() {
let update = new api.UiElementUpdate();
update.setVisible(false);
scene.updateElement(this.transientWarning.uiElementId, update);
this.secureOriginTimer = null;
scene.flush();
}
};
class Omnibox {
constructor(contentQuadId) {
this.setSecureOrigin(false);
this.domUiElement = new DomUiElement('#omni');
let update = new api.UiElementUpdate();
update.setVisible(false);
scene.updateElement(this.domUiElement.uiElementId, update);
}
show(visible) {
let update = new api.UiElementUpdate();
update.setVisible(visible);
scene.updateElement(this.domUiElement.uiElementId, update);
}
setLoading(loading) {
this.domUiElement.domElement.className = loading ? 'loading' : 'idle';
}
setURL(host, path) {
let omnibox = this.domUiElement.domElement;
omnibox.querySelector('#domain').innerHTML = host;
omnibox.querySelector('#path').innerHTML = path;
}
setSecureOrigin(secure) {
document.querySelector('#omni-secure-icon').style.display =
(secure ? 'block' : 'none');
document.querySelector('#omni-insecure-icon').style.display =
(!secure ? 'block' : 'none');
}
};
class SceneManager {
constructor() {
this.mode = api.Mode.UNKNOWN;
this.contentQuad = new ContentQuad();
let contentId = this.contentQuad.getElementId();
this.controls = new Controls(contentId);
this.secureOriginWarnings = new SecureOriginWarnings();
this.omnibox = new Omnibox(contentId);
}
setMode(mode) {
this.mode = mode;
this.contentQuad.show(mode == api.Mode.STANDARD);
this.controls.show(mode == api.Mode.STANDARD);
this.omnibox.show(mode == api.Mode.STANDARD);
this.secureOriginWarnings.show(mode == api.Mode.WEB_VR);
}
setSecureOrigin(secure) {
this.secureOriginWarnings.setSecureOrigin(secure);
this.omnibox.setSecureOrigin(secure);
}
setReloadUiEnabled(enabled) {
this.controls.setReloadUiEnabled(enabled);
}
};
function initialize() {
sceneManager = new SceneManager();
scene.flush();
api.domLoaded();
}
function command(dict) {
if ('mode' in dict) {
sceneManager.setMode(dict['mode']);
}
if ('secureOrigin' in dict) {
sceneManager.setSecureOrigin(dict['secureOrigin']);
}
if ('enableReloadUi' in dict) {
sceneManager.setReloadUiEnabled(dict['enableReloadUi']);
}
if ('url' in dict) {
let url = dict['url'];
sceneManager.omnibox.setURL(url['host'], url['path']);
}
if ('loading' in dict) {
sceneManager.omnibox.setLoading(dict['loading']);
}
scene.flush();
}
return {
initialize: initialize,
command: command,
};
})();
document.addEventListener('DOMContentLoaded', vrShellUi.initialize);