From 4c903a6d503ecd147729c8e5d1b15bd0e22b5c5b Mon Sep 17 00:00:00 2001 From: Ray Chen Date: Fri, 8 Jul 2016 00:21:52 -0700 Subject: [PATCH] Refactor and move actions into menu --- src/DataImage.js | 5 +- src/Modes.js | 11 +- src/infospot/Infospot.js | 16 +- src/viewer/Viewer.js | 165 ++++++---- src/widget/Widget.js | 637 +++++++++++++++++++++++++++++++++------ 5 files changed, 673 insertions(+), 161 deletions(-) diff --git a/src/DataImage.js b/src/DataImage.js index 2cb81982..5b6c326f 100644 --- a/src/DataImage.js +++ b/src/DataImage.js @@ -19,7 +19,10 @@ VideoPause: 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48IURPQ1RZUEUgc3ZnIFBVQkxJQyAiLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4iICJodHRwOi8vd3d3LnczLm9yZy9HcmFwaGljcy9TVkcvMS4xL0RURC9zdmcxMS5kdGQiPjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmVyc2lvbj0iMS4xIiB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCI+PHBhdGggc3R5bGU9ImZpbGw6I2ZmZiIgZD0iTTE0LDE5LjE0SDE4VjUuMTRIMTRNNiwxOS4xNEgxMFY1LjE0SDZWMTkuMTRaIiAvPjwvc3ZnPg==', Cardboard: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNHB4IiBoZWlnaHQ9IjI0cHgiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0iI0ZGRkZGRiI+CiAgICA8cGF0aCBkPSJNMjAuNzQgNkgzLjIxQzIuNTUgNiAyIDYuNTcgMiA3LjI4djEwLjQ0YzAgLjcuNTUgMS4yOCAxLjIzIDEuMjhoNC43OWMuNTIgMCAuOTYtLjMzIDEuMTQtLjc5bDEuNC0zLjQ4Yy4yMy0uNTkuNzktMS4wMSAxLjQ0LTEuMDFzMS4yMS40MiAxLjQ1IDEuMDFsMS4zOSAzLjQ4Yy4xOS40Ni42My43OSAxLjExLjc5aDQuNzljLjcxIDAgMS4yNi0uNTcgMS4yNi0xLjI4VjcuMjhjMC0uNy0uNTUtMS4yOC0xLjI2LTEuMjh6TTcuNSAxNC42MmMtMS4xNyAwLTIuMTMtLjk1LTIuMTMtMi4xMiAwLTEuMTcuOTYtMi4xMyAyLjEzLTIuMTMgMS4xOCAwIDIuMTIuOTYgMi4xMiAyLjEzcy0uOTUgMi4xMi0yLjEyIDIuMTJ6bTkgMGMtMS4xNyAwLTIuMTMtLjk1LTIuMTMtMi4xMiAwLTEuMTcuOTYtMi4xMyAyLjEzLTIuMTNzMi4xMi45NiAyLjEyIDIuMTMtLjk1IDIuMTItMi4xMiAyLjEyeiIvPgogICAgPHBhdGggZmlsbD0ibm9uZSIgZD0iTTAgMGgyNHYyNEgwVjB6Ii8+Cjwvc3ZnPgo=', WhiteTile: 'data:image/png;base64,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', - Reticle: 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48IURPQ1RZUEUgc3ZnIFBVQkxJQyAiLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4iICJodHRwOi8vd3d3LnczLm9yZy9HcmFwaGljcy9TVkcvMS4xL0RURC9zdmcxMS5kdGQiPjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmVyc2lvbj0iMS4xIiB3aWR0aD0iNjQiIGhlaWdodD0iNjQiIHZpZXdCb3g9IjAgMCAyNCAyNCI+PHBhdGggZmlsbD0iI2ZmZiIgZD0iTTEyLDIwQTgsOCAwIDAsMSA0LDEyQTgsOCAwIDAsMSAxMiw0QTgsOCAwIDAsMSAyMCwxMkE4LDggMCAwLDEgMTIsMjBNMTIsMkExMCwxMCAwIDAsMCAyLDEyQTEwLDEwIDAgMCwwIDEyLDIyQTEwLDEwIDAgMCwwIDIyLDEyQTEwLDEwIDAgMCwwIDEyLDJaIiAvPjwvc3ZnPg==' + Reticle: 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48IURPQ1RZUEUgc3ZnIFBVQkxJQyAiLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4iICJodHRwOi8vd3d3LnczLm9yZy9HcmFwaGljcy9TVkcvMS4xL0RURC9zdmcxMS5kdGQiPjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmVyc2lvbj0iMS4xIiB3aWR0aD0iNjQiIGhlaWdodD0iNjQiIHZpZXdCb3g9IjAgMCAyNCAyNCI+PHBhdGggZmlsbD0iI2ZmZiIgZD0iTTEyLDIwQTgsOCAwIDAsMSA0LDEyQTgsOCAwIDAsMSAxMiw0QTgsOCAwIDAsMSAyMCwxMkE4LDggMCAwLDEgMTIsMjBNMTIsMkExMCwxMCAwIDAsMCAyLDEyQTEwLDEwIDAgMCwwIDEyLDIyQTEwLDEwIDAgMCwwIDIyLDEyQTEwLDEwIDAgMCwwIDEyLDJaIiAvPjwvc3ZnPg==', + Setting: 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48IURPQ1RZUEUgc3ZnIFBVQkxJQyAiLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4iICJodHRwOi8vd3d3LnczLm9yZy9HcmFwaGljcy9TVkcvMS4xL0RURC9zdmcxMS5kdGQiPjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmVyc2lvbj0iMS4xIiB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCI+PHBhdGggZmlsbD0iI2ZmZiIgZD0iTTEyLDE1LjVBMy41LDMuNSAwIDAsMSA4LjUsMTJBMy41LDMuNSAwIDAsMSAxMiw4LjVBMy41LDMuNSAwIDAsMSAxNS41LDEyQTMuNSwzLjUgMCAwLDEgMTIsMTUuNU0xOS40MywxMi45N0MxOS40NywxMi42NSAxOS41LDEyLjMzIDE5LjUsMTJDMTkuNSwxMS42NyAxOS40NywxMS4zNCAxOS40MywxMUwyMS41NCw5LjM3QzIxLjczLDkuMjIgMjEuNzgsOC45NSAyMS42Niw4LjczTDE5LjY2LDUuMjdDMTkuNTQsNS4wNSAxOS4yNyw0Ljk2IDE5LjA1LDUuMDVMMTYuNTYsNi4wNUMxNi4wNCw1LjY2IDE1LjUsNS4zMiAxNC44Nyw1LjA3TDE0LjUsMi40MkMxNC40NiwyLjE4IDE0LjI1LDIgMTQsMkgxMEM5Ljc1LDIgOS41NCwyLjE4IDkuNSwyLjQyTDkuMTMsNS4wN0M4LjUsNS4zMiA3Ljk2LDUuNjYgNy40NCw2LjA1TDQuOTUsNS4wNUM0LjczLDQuOTYgNC40Niw1LjA1IDQuMzQsNS4yN0wyLjM0LDguNzNDMi4yMSw4Ljk1IDIuMjcsOS4yMiAyLjQ2LDkuMzdMNC41NywxMUM0LjUzLDExLjM0IDQuNSwxMS42NyA0LjUsMTJDNC41LDEyLjMzIDQuNTMsMTIuNjUgNC41NywxMi45N0wyLjQ2LDE0LjYzQzIuMjcsMTQuNzggMi4yMSwxNS4wNSAyLjM0LDE1LjI3TDQuMzQsMTguNzNDNC40NiwxOC45NSA0LjczLDE5LjAzIDQuOTUsMTguOTVMNy40NCwxNy45NEM3Ljk2LDE4LjM0IDguNSwxOC42OCA5LjEzLDE4LjkzTDkuNSwyMS41OEM5LjU0LDIxLjgyIDkuNzUsMjIgMTAsMjJIMTRDMTQuMjUsMjIgMTQuNDYsMjEuODIgMTQuNSwyMS41OEwxNC44NywxOC45M0MxNS41LDE4LjY3IDE2LjA0LDE4LjM0IDE2LjU2LDE3Ljk0TDE5LjA1LDE4Ljk1QzE5LjI3LDE5LjAzIDE5LjU0LDE4Ljk1IDE5LjY2LDE4LjczTDIxLjY2LDE1LjI3QzIxLjc4LDE1LjA1IDIxLjczLDE0Ljc4IDIxLjU0LDE0LjYzTDE5LjQzLDEyLjk3WiIgLz48L3N2Zz4=', + ChevronRight: 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48IURPQ1RZUEUgc3ZnIFBVQkxJQyAiLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4iICJodHRwOi8vd3d3LnczLm9yZy9HcmFwaGljcy9TVkcvMS4xL0RURC9zdmcxMS5kdGQiPjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmVyc2lvbj0iMS4xIiB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCI+PHBhdGggZD0iTTguNTksMTYuNThMMTMuMTcsMTJMOC41OSw3LjQxTDEwLDZMMTYsMTJMMTAsMThMOC41OSwxNi41OFoiIC8+PC9zdmc+', + Check: 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48IURPQ1RZUEUgc3ZnIFBVQkxJQyAiLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4iICJodHRwOi8vd3d3LnczLm9yZy9HcmFwaGljcy9TVkcvMS4xL0RURC9zdmcxMS5kdGQiPjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmVyc2lvbj0iMS4xIiB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCI+PHBhdGggZD0iTTIxLDdMOSwxOUwzLjUsMTMuNUw0LjkxLDEyLjA5TDksMTYuMTdMMTkuNTksNS41OUwyMSw3WiIgLz48L3N2Zz4=' }; diff --git a/src/Modes.js b/src/Modes.js index f72a8a57..5c086e5a 100644 --- a/src/Modes.js +++ b/src/Modes.js @@ -9,14 +9,17 @@ */ PANOLENS.Modes = { - /** Current viewer state is unknown */ + /** Unknown */ UNKNOWN: 0, - /** Current viewer state is normal */ + /** Normal */ NORMAL: 1, - /** Current viewer state is in vr mode*/ - VR: 2 + /** Google Cardboard*/ + CARDBOARD: 2, + + /** Stereoscopic **/ + STEREO: 3 }; diff --git a/src/infospot/Infospot.js b/src/infospot/Infospot.js index 5f0f6036..670a105a 100644 --- a/src/infospot/Infospot.js +++ b/src/infospot/Infospot.js @@ -97,7 +97,7 @@ this.addEventListener( 'hover', this.onHover ); this.addEventListener( 'hoverenter', this.onHoverStart ); this.addEventListener( 'hoverleave', this.onHoverEnd ); - this.addEventListener( 'VR-toggle', this.onToggleVR ); + this.addEventListener( 'panolens-dual-eye-effect', this.onDualEyeEffect ); this.addEventListener( 'panolens-container', this.setContainer.bind( this ) ); this.addEventListener( 'dismiss', this.onDismiss ); this.addEventListener( 'panolens-infospot-focus', this.setFocusMethod ); @@ -201,7 +201,7 @@ if ( !this.getContainer() ) { return; } - var cursorStyle = this.cursorStyle || ( this.mode === PANOLENS.Modes.VR ? 'default' : 'pointer' ); + var cursorStyle = this.cursorStyle || ( this.mode === PANOLENS.Modes.NORMAL ? 'pointer' : 'default' ); this.isHovering = true; this.container.style.cursor = cursorStyle; @@ -215,7 +215,7 @@ if ( this.element ) { - if ( this.mode === PANOLENS.Modes.VR ) { + if ( this.mode === PANOLENS.Modes.CARDBOARD ||this.mode === PANOLENS.Modes.STEREO ) { this.element.style.display = 'none'; this.element.left && ( this.element.left.style.display = 'block' ); @@ -274,11 +274,11 @@ }; /** - * On VR toggle handler + * On dual eye effect handler * Creates duplicate left and right element - * @param {object} event - VR toggle event + * @param {object} event - panolens-dual-eye-effect event */ - PANOLENS.Infospot.prototype.onToggleVR = function ( event ) { + PANOLENS.Infospot.prototype.onDualEyeEffect = function ( event ) { if ( !this.getContainer() ) { return; } @@ -304,7 +304,7 @@ } - if ( this.mode === PANOLENS.Modes.VR ) { + if ( this.mode === PANOLENS.Modes.CARDBOARD || this.mode === PANOLENS.Modes.STEREO ) { element.left.style.display = element.style.display; element.right.style.display = element.style.display; @@ -350,7 +350,7 @@ left = x - width - container.offsetLeft; top = y - height - delta; - if ( this.mode === PANOLENS.Modes.VR && element.left && element.right ) { + if ( ( this.mode === PANOLENS.Modes.CARDBOARD || this.mode === PANOLENS.Modes.STEREO ) && element.left && element.right ) { left = container.clientWidth / 4 - width; top = container.clientHeight / 2 - height - delta; diff --git a/src/viewer/Viewer.js b/src/viewer/Viewer.js index 026fcaf3..9544ea8b 100644 --- a/src/viewer/Viewer.js +++ b/src/viewer/Viewer.js @@ -11,7 +11,7 @@ * @param {THREE.Camera} [options.camera=THREE.PerspectiveCamera] - A THREE.Camera to view the scene * @param {THREE.WebGLRenderer} [options.renderer=THREE.WebGLRenderer] - A THREE.WebGLRenderer to render canvas * @param {boolean} [options.controlBar=true] - Show/hide control bar on the bottom of the container - * @param {array} [options.controlButtons=[]] - Button names to mount on controlBar if controlBar exists, Defaults to ['fullscreen', 'navigation', 'vr', 'video'] + * @param {array} [options.controlButtons=[]] - Button names to mount on controlBar if controlBar exists, Defaults to ['fullscreen', 'setting', 'video'] * @param {boolean} [options.autoHideControlBar=false] - Auto hide control bar when click on non-active area * @param {boolean} [options.autoHideInfospot=true] - Auto hide infospots when click on non-active area * @param {boolean} [options.horizontalView=false] - Allow only horizontal camera control @@ -38,7 +38,7 @@ options = options || {}; options.controlBar = options.controlBar !== undefined ? options.controlBar : true; - options.controlButtons = options.controlButtons || [ 'fullscreen', 'navigation', 'vr', 'video' ]; + options.controlButtons = options.controlButtons || [ 'fullscreen', 'setting', 'video' ]; options.autoHideControlBar = options.autoHideControlBar !== undefined ? options.autoHideControlBar : false; options.autoHideInfospot = options.autoHideInfospot !== undefined ? options.autoHideInfospot : true; options.horizontalView = options.horizontalView !== undefined ? options.horizontalView : false; @@ -83,7 +83,6 @@ this.camera = options.camera || new THREE.PerspectiveCamera( this.options.cameraFov, this.container.clientWidth / this.container.clientHeight, 1, 10000 ); this.scene = options.scene || new THREE.Scene(); this.renderer = options.renderer || new THREE.WebGLRenderer( { alpha: true, antialias: true } ); - this.effect; this.reticle = {}; this.tempEnableReticle = this.options.enableReticle; @@ -93,7 +92,11 @@ this.OrbitControls; this.DeviceOrientationControls; + this.CardboardEffect; + this.StereoEffect; + this.controls; + this.effect; this.panorama; this.widget; @@ -154,13 +157,20 @@ } - // Cardboard effect - this.effect = new THREE.CardboardEffect( this.renderer ); - this.effect.setSize( this.container.clientWidth, this.container.clientHeight ); - + // Controls this.controls = [ this.OrbitControls, this.DeviceOrientationControls ]; this.control = this.OrbitControls; + // Cardboard effect + this.CardboardEffect = new THREE.CardboardEffect( this.renderer ); + this.CardboardEffect.setSize( this.container.clientWidth, this.container.clientHeight ); + + // Stereo effect + this.StereoEffect = new THREE.StereoEffect( this.renderer ); + this.StereoEffect.setSize( this.container.clientWidth, this.container.clientHeight ); + + this.effect = this.CardboardEffect; + // Add default hidden reticle this.addReticle(); @@ -332,102 +342,129 @@ }; /** - * Toggle VR effect mode and broadcast event to infospot descendants - * @fires PANOLENS.Viewer#VR-toggle - * @fires PANOLENS.Infospot#VR-toggle + * Dispatch event to all descendants + * @param {object} event - Event to be passed along */ - PANOLENS.Viewer.prototype.toggleVR = function () { + PANOLENS.Viewer.prototype.dispatchEventToChildren = function ( event ) { - var event; + this.scene.traverse( function ( object ) { - if ( this.effect ) { + if ( object.dispatchEvent ) { - if ( this.mode !== PANOLENS.Modes.VR ) { + object.dispatchEvent( event ); - this.mode = PANOLENS.Modes.VR; + } + + }); - } else { + }; - this.mode = PANOLENS.Modes.NORMAL; + /** + * Disable additional rendering effect + */ + PANOLENS.Viewer.prototype.disableEffect = function () { - } - } + if ( this.mode === PANOLENS.Modes.NORMAL ) { return; } - event = { type: 'VR-toggle', mode: this.mode }; + this.mode = PANOLENS.Modes.NORMAL; + this.disableReticleControl(); /** - * Toggle vr event + * Dual eye effect event * @type {object} - * @event PANOLENS.Viewer#VR-toggle - * @event PANOLENS.Infospot#VR-toggle + * @event PANOLENS.Viewer#panolens-dual-eye-effect + * @event PANOLENS.Infospot#panolens-dual-eye-effect * @property {PANOLENS.Modes} mode - Current display mode */ - this.dispatchEvent( event ); - this.scene.traverse( function ( object ) { + this.dispatchEventToChildren( { type: 'panolens-dual-eye-effect', mode: this.mode } ); - if ( object.dispatchEvent ) { - - object.dispatchEvent( event ); + this.renderer.setSize( this.container.clientWidth, this.container.clientHeight ); + this.render(); + }; - } + /** + * Enable rendering effect + * @param {PANOLENS.Modes} mode - Modes for effects + */ + PANOLENS.Viewer.prototype.enableEffect = function ( mode ) { - }); + if ( this.mode === mode ) { return; } - if ( this.mode === PANOLENS.Modes.VR ) { + var fov = this.camera.fov; - this.enableVR(); + this.mode = mode; - } else { + switch( mode ) { - this.disableVR(); + case PANOLENS.Modes.CARDBOARD: - } + this.effect = this.CardboardEffect; + this.enableReticleControl(); - }; + break; - /** - * Enable VR effect - */ - PANOLENS.Viewer.prototype.enableVR = function () { + case PANOLENS.Modes.STEREO: - if ( this.effect && this.mode === PANOLENS.Modes.VR ) { + this.effect = this.StereoEffect; + this.enableReticleControl(); + + break; - this.tempEnableReticle = true; + default: - // Register reticle event and unregister mouse event - this.unregisterMouseAndTouchEvents(); + return; - this.reticle.show(); - this.registerReticleEvent(); + } - this.updateReticleEvent( this.mode ); - + /** + * Dual eye effect event + * @type {object} + * @event PANOLENS.Viewer#panolens-dual-eye-effect + * @event PANOLENS.Infospot#panolens-dual-eye-effect + * @property {PANOLENS.Modes} mode - Current display mode + */ + this.dispatchEventToChildren( { type: 'panolens-dual-eye-effect', mode: mode } ); - } + // Force effect stereo camera to update by refreshing fov + this.camera.fov = fov + 10e-3; + this.effect.setSize( this.container.clientWidth, this.container.clientHeight ); + this.render(); + this.camera.fov = fov; }; /** - * Disable VR effect + * Enable reticle control */ - PANOLENS.Viewer.prototype.disableVR = function () { + PANOLENS.Viewer.prototype.enableReticleControl = function () { + + this.tempEnableReticle = true; - if ( this.effect && this.mode === PANOLENS.Modes.NORMAL ) { + // Register reticle event and unregister mouse event + this.unregisterMouseAndTouchEvents(); + this.reticle.show(); + this.registerReticleEvent(); + this.updateReticleEvent(); - this.tempEnableReticle = false; + }; - // Register mouse event and unregister reticle event - if ( !this.options.enableReticle ) { + /** + * Disable reticle control + */ + PANOLENS.Viewer.prototype.disableReticleControl = function () { - this.reticle.hide(); - this.unregisterReticleEvent(); - this.registerMouseAndTouchEvents(); + this.tempEnableReticle = false; - } else { + // Register mouse event and unregister reticle event + if ( !this.options.enableReticle ) { - this.updateReticleEvent( this.mode ); + this.reticle.hide(); + this.unregisterReticleEvent(); + this.registerMouseAndTouchEvents(); - } + } else { + + this.updateReticleEvent(); } @@ -910,7 +947,7 @@ // Update reticle if ( this.options.enableReticle || this.tempEnableReticle ) { - this.updateReticleEvent( this.mode ); + this.updateReticleEvent(); } @@ -1312,7 +1349,7 @@ */ PANOLENS.Viewer.prototype.render = function () { - if ( this.mode === PANOLENS.Modes.VR ) { + if ( this.mode === PANOLENS.Modes.CARDBOARD || this.mode === PANOLENS.Modes.STEREO ) { this.effect.render( this.scene, this.camera ); @@ -1412,7 +1449,7 @@ /** * Update reticle event */ - PANOLENS.Viewer.prototype.updateReticleEvent = function ( mode ) { + PANOLENS.Viewer.prototype.updateReticleEvent = function () { var centerX, centerY; diff --git a/src/widget/Widget.js b/src/widget/Widget.js index 4354d080..49bdd351 100644 --- a/src/widget/Widget.js +++ b/src/widget/Widget.js @@ -9,13 +9,21 @@ THREE.EventDispatcher.call( this ); + this.DEFAULT_TRANSITION = 'all 0.27s ease'; + this.MAX_ZINDEX = 10e5; + this.TOUCH_DETECTED = 'ontouchstart' in window; + this.container = container; this.barElement; this.fullscreenElement; - this.navigationElement; - this.vrElement; this.videoElement; + this.settingElement; + + this.mainMenu; + + this.activeMainItem; + this.activeSubMenu; } @@ -34,15 +42,21 @@ return; } - var scope = this, bar, styleTranslate, styleOpacity; + var scope = this, bar, styleTranslate, styleOpacity, gradientStyle; + + gradientStyle = 'linear-gradient(bottom, rgba(0,0,0,0.2), rgba(0,0,0,0))'; bar = document.createElement( 'div' ); bar.style.width = '100%'; bar.style.height = '44px'; bar.style.float = 'left'; bar.style.transform = bar.style.webkitTransform = bar.style.msTransform = 'translateY(-100%)'; - bar.style.background = 'rgba( 0, 0, 0, 0.3 )'; - bar.style.transition = 'all 0.5s ease'; + bar.style.background = '-webkit-' + gradientStyle; + bar.style.background = '-moz-' + gradientStyle; + bar.style.background = '-o-' + gradientStyle; + bar.style.background = '-ms-' + gradientStyle; + bar.style.background = gradientStyle; + bar.style.transition = this.DEFAULT_TRANSITION; bar.isHidden = false; bar.toggle = function () { bar.isHidden = !bar.isHidden; @@ -52,6 +66,11 @@ bar.style.opacity = styleOpacity; }; + // Menu + var menu = this.createDefaultMenu(); + this.mainMenu = this.createMainMenu( menu ); + bar.appendChild( this.mainMenu ); + // Dispose bar.dispose = function () { @@ -63,19 +82,11 @@ } - if ( scope.navigationElement ) { + if ( scope.settingElement ) { - bar.removeChild( scope.navigationElement ); - scope.navigationElement.dispose(); - scope.navigationElement = null; - - } - - if ( scope.vrElement ) { - - bar.removeChild( scope.vrElement ); - scope.vrElement.dispose(); - scope.vrElement = null; + bar.removeChild( scope.settingElement ); + scope.settingElement.dispose(); + scope.settingElement = null; } @@ -90,6 +101,30 @@ }; this.container.appendChild( bar ); + this.container.addEventListener( 'click', function () { + + // Hide main menu + if ( scope.mainMenu && scope.mainMenu.visible ) { + + scope.mainMenu.hide(); + + } + + // Hide active sub menu + if ( scope.activeSubMenu && scope.activeSubMenu.visible ) { + + scope.activeSubMenu.hide(); + + } + + if ( scope.mainMenu._width && scope.mainMenu._height ) { + + scope.mainMenu.changeSize( scope.mainMenu._width, scope.mainMenu._height ); + scope.mainMenu.unslideAll(); + + } + + }, false ); // Event listener this.addEventListener( 'control-bar-toggle', bar.toggle ); @@ -98,42 +133,133 @@ }; + /** + * Create default menu + */ + PANOLENS.Widget.prototype.createDefaultMenu = function () { + + var scope = this, handler; + + handler = function ( method, data ) { + + return function () { + + scope.dispatchEvent( { + + type: 'panolens-viewer-handler', + method: method, + data: data + + } ); + + } + + }; + + return [ + + { + title: 'Control', + subMenu: [ + { + title: this.TOUCH_DETECTED ? 'Touch' : 'Mouse', + handler: handler( 'enableControl', 0 ) + }, + { + title: 'Sensor', + handler: handler( 'enableControl', 1 ) + } + ] + }, + + { + title: 'Mode', + subMenu: [ + { + title: 'Normal', + handler: handler( 'disableEffect' ) + }, + { + title: 'Cardboard', + handler: handler( 'enableEffect', PANOLENS.Modes.CARDBOARD ) + }, + { + title: 'Stereoscopic', + handler: handler( 'enableEffect', PANOLENS.Modes.STEREO ) + } + ] + } + + ]; + + }; + /** * Add buttons on top of control bar * @param {string} name - The control button name to be created */ PANOLENS.Widget.prototype.addControlButton = function ( name ) { - this.fullscreenElement = name === 'fullscreen' ? this.createFullscreenButton() : this.fullscreenElement; - this.navigationElement = name === 'navigation' ? this.createCameraControlButton() : this.navigationElement; - this.vrElement = name === 'vr' ? this.createVRButton() : this.vrElement; - this.videoElement = name === 'video' ? this.createVideoControl() : this.videoElement; + var element; + + switch( name ) { + + case 'fullscreen': + + element = this.createFullscreenButton(); + this.fullscreenElement = element; + + break; + + case 'setting': + + element = this.createSettingButton(); + this.settingElement = element; + + break; - // Add Control Items - this.fullscreenElement && this.barElement.appendChild( this.fullscreenElement ); - this.navigationElement && this.barElement.appendChild( this.navigationElement ); - this.vrElement && this.barElement.appendChild( this.vrElement ); - this.videoElement && this.barElement.appendChild( this.videoElement ); + case 'video': + + element = this.createVideoControl(); + this.videoElement = element; + + break; + + default: + + return; + + } + + this.barElement.appendChild( element ); }; /** - * Create VR button - * @return {HTMLSpanElement} - The dom element icon for VR effect - * @fires PANOLENS.Widget#panolens-viewer-handler + * Create Setting button to toggle menu */ - PANOLENS.Widget.prototype.createVRButton = function () { + PANOLENS.Widget.prototype.createSettingButton = function () { var scope = this, item; - function onTap () { + function onTap ( event ) { - /** - * Viewer handler event - * @type {object} - * @property {string} method - 'toggleVR' function call on PANOLENS.Viewer - */ - scope.dispatchEvent( { type: 'panolens-viewer-handler', method: 'toggleVR' } ); + event.preventDefault(); + event.stopPropagation(); + + scope.mainMenu.toggle(); + + if ( this.activated ) { + + this.style.transform = 'rotate3d(0,0,0,0)'; + + } else { + + this.style.transform = 'rotate3d(0,0,1,90deg)'; + + } + + this.activated = !this.activated; } @@ -141,17 +267,21 @@ style : { - backgroundImage : 'url("' + PANOLENS.DataImage.Cardboard + '")' + backgroundImage : 'url("' + PANOLENS.DataImage.Setting + '")', + webkitTransition : this.DEFAULT_TRANSITION, + transition : this.DEFAULT_TRANSITION }, - onTap : onTap + onTap: onTap } ); + item.activated = false; + return item; - } + }; /** * Create Fullscreen button @@ -242,50 +372,6 @@ }; - /** - * Create camera control button - * @return {HTMLSpanElement} - The dom element icon for camera navigation - * @fires PANOLENS.Widget#panolens-viewer-handler - */ - PANOLENS.Widget.prototype.createCameraControlButton = function () { - - var scope = this, item; - - function onTap(){ - - /** - * Viewer handler event - * @type {object} - * @property {string} method - 'toggleNextControl' function call on PANOLENS.Viewer - */ - scope.dispatchEvent( { type: 'panolens-viewer-handler', method: 'toggleNextControl' } ); - - this.controlName = ( this.controlName === 'orbit' ) ? 'device-orientation' : 'orbit'; - - this.style.backgroundImage = 'url("' + ( this.controlName === 'orbit' - ? PANOLENS.DataImage.Gyro - : PANOLENS.DataImage.Orbit ) + '")'; - - } - - item = this.createCustomItem( { - - style: { - - backgroundImage: 'url("' + PANOLENS.DataImage.Gyro + '")' - - }, - - onTap : onTap - - } ); - - item.controlName = 'orbit'; - - return item; - - }; - /** * Create video control container * @return {HTMLSpanElement} - The dom element icon for video control @@ -550,6 +636,388 @@ }; + /** + * Create menu item + * @param {string} title - Title to display + * @return {HTMLDomElement} - An anchor tag element + */ + PANOLENS.Widget.prototype.createMenuItem = function ( title ) { + + var scope = this, item = document.createElement( 'a' ); + item.textContent = title; + item.style.display = 'block'; + item.style.padding = '10px'; + item.style.textDecoration = 'none'; + item.style.transition = this.DEFAULT_TRANSITION; + + item.slide = function ( right ) { + + this.style.transform = 'translateX(' + ( right ? '' : '-' ) + '100%)'; + + }; + + item.unslide = function () { + + this.style.transform = 'translateX(0)'; + + }; + + item.setIcon = function ( url ) { + + if ( this.icon ) { + + this.icon.style.backgroundImage = 'url(' + url + ')'; + + } + + }; + + item.setSelectionTitle = function ( title ) { + + if ( this.selection ) { + + this.selection.textContent = title; + + } + + }; + + item.addSelection = function ( name ) { + + var selection = document.createElement( 'span' ); + selection.style.float = 'right'; + + this.selection = selection; + this.setSelectionTitle( name ); + this.appendChild( selection ); + + return this; + + }; + + item.addIcon = function ( url, left, flip ) { + + url = url || PANOLENS.DataImage.ChevronRight; + left = left || false; + flip = flip || false; + + var element = document.createElement( 'span' ); + element.style.float = left ? 'left' : 'right'; + element.style.width = '17px'; + element.style.height = '17px'; + element.style[ 'margin' + ( left ? 'Right' : 'Left' ) ] = '12px'; + element.style.backgroundSize = 'cover'; + + if ( flip ) { + + element.style.transform = 'rotateZ(180deg)'; + + } + + this.icon = element; + this.setIcon( url ); + this.appendChild( element ); + + return this; + + }; + + item.addSubMenu = function ( title, items ) { + + this.subMenu = scope.createSubMenu( title, items ); + + return this; + + }; + + item.addEventListener( 'mouseenter', function () { + + this.style.backgroundColor = '#e0e0e0'; + + }, false ); + + item.addEventListener( 'mouseleave', function () { + + this.style.backgroundColor = '#fafafa'; + + }, false ); + + return item; + + }; + + /** + * Create menu item header + * @param {string} title - Title to display + * @return {HTMLDomElement} - An anchor tag element + */ + PANOLENS.Widget.prototype.createMenuItemHeader = function ( title ) { + + var header = this.createMenuItem( title ); + + header.style.borderBottom = '1px solid #333'; + header.style.paddingBottom = '15px'; + + return header; + + }; + + /** + * Create main menu + * @param {array} menus - Menu array list + * @return {HTMLDomElement} - A span element + */ + PANOLENS.Widget.prototype.createMainMenu = function ( menus ) { + + var scope = this, menu = this.createMenu(), subMenu; + + menu.style.width = '200px'; + + function onTap ( event ) { + + event.preventDefault(); + event.stopPropagation(); + + var mainMenu = scope.mainMenu, subMenu = this.subMenu; + + function onNextTick () { + + mainMenu.changeSize( subMenu.clientWidth, subMenu.clientHeight ); + subMenu.show(); + subMenu.unslideAll(); + + } + + mainMenu._width = mainMenu.clientWidth; + mainMenu._height = mainMenu.clientHeight; + mainMenu.hide(); + mainMenu.slideAll(); + mainMenu.style.width = mainMenu.clientWidth + 'px'; + mainMenu.style.height = mainMenu.clientHeight + 'px'; + mainMenu.parentElement.appendChild( subMenu ); + + scope.activeMainItem = this; + scope.activeSubMenu = subMenu; + + window.requestAnimationFrame( onNextTick ); + + }; + + for ( var i = 0; i < menus.length; i++ ) { + + var item = menu.addItem( menus[ i ].title ); + + item.addIcon() + .addEventListener( 'click', onTap, false ); + + if ( menus[ i ].subMenu && menus[ i ].subMenu.length > 0 ) { + + var title = menus[ i ].subMenu[ 0 ].title; + + item.addSelection( title ) + .addSubMenu( menus[ i ].title, menus[ i ].subMenu ); + + } + + } + + return menu; + + }; + + /** + * Create sub menu + * @param {string} title - Sub menu title + * @param {array} items - Item array list + * @return {HTMLDomElement} - A span element + */ + PANOLENS.Widget.prototype.createSubMenu = function ( title, items ) { + + var scope = this, menu, subMenu = this.createMenu(); + + subMenu.items = items; + subMenu.activeItem; + + function onTap ( event ) { + + event.preventDefault(); + event.stopPropagation(); + + menu = scope.mainMenu; + menu.changeSize( menu._width, menu._height ); + menu.unslideAll(); + menu.show(); + subMenu.slideAll( true ); + subMenu.hide(); + + if ( this.type !== 'header' ) { + + subMenu.setActiveItem( this ); + scope.activeMainItem.setSelectionTitle( this.textContent ); + + this.handler && this.handler(); + + } + + } + + subMenu.addHeader( title ).addIcon( undefined, true, true ).addEventListener( 'click', onTap, false ); + + for ( var i = 0; i < items.length; i++ ) { + + var item = subMenu.addItem( items[ i ].title ); + + item.handler = items[ i ].handler; + item.addIcon( ' ', true ); + item.addEventListener( 'click', onTap, false ); + + if ( !subMenu.activeItem ) { + + subMenu.setActiveItem( item ); + + } + + } + + subMenu.slideAll( true ); + + return subMenu; + + }; + + /** + * Create general menu + * @return {HTMLDomElement} - A span element + */ + PANOLENS.Widget.prototype.createMenu = function () { + + var scope = this, menu = document.createElement( 'span' ), style; + + style = menu.style; + + style.padding = '5px 0'; + style.position = 'fixed'; + style.bottom = '100%'; + style.right = '14px'; + style.backgroundColor = '#fafafa'; + style.fontFamily = 'Helvetica Neue'; + style.fontSize = '14px'; + style.fontWeight = '200'; + style.visibility = 'hidden'; + style.opacity = 0; + style.boxShadow = '0 0 12pt rgba(0,0,0,0.25)'; + style.borderRadius = '2px'; + style.overflow = 'hidden'; + style.willChange = 'width, height, opacity'; + style.transition = this.DEFAULT_TRANSITION; + + menu.visible = false; + + menu.changeSize = function ( width, height ) { + + this.style.width = width + 'px'; + this.style.height = height + 'px'; + + }; + + menu.show = function () { + + this.style.opacity = 1; + this.style.visibility = 'visible'; + this.visible = true; + + }; + + menu.hide = function () { + + this.style.opacity = 0; + this.style.visibility = 'hidden'; + this.visible = false; + + }; + + menu.toggle = function () { + + if ( this.visible ) { + + this.hide(); + + } else { + + this.show(); + + } + + }; + + menu.slideAll = function ( right ) { + + for ( var i = 0; i < menu.children.length; i++ ){ + + if ( menu.children[ i ].slide ) { + + menu.children[ i ].slide( right ); + + } + + } + + }; + + menu.unslideAll = function () { + + for ( var i = 0; i < menu.children.length; i++ ){ + + if ( menu.children[ i ].unslide ) { + + menu.children[ i ].unslide(); + + } + + } + + }; + + menu.addHeader = function ( title ) { + + var header = scope.createMenuItemHeader( title ); + header.type = 'header'; + + this.appendChild( header ); + + return header; + + }; + + menu.addItem = function ( title ) { + + var item = scope.createMenuItem( title ); + item.type = 'item'; + + this.appendChild( item ); + + return item; + + }; + + menu.setActiveItem = function ( item ) { + + if ( this.activeItem ) { + + this.activeItem.setIcon( ' ' ); + + } + + item.setIcon( PANOLENS.DataImage.Check ); + + this.activeItem = item; + + }; + + return menu; + + }; + /** * Create custom item element * @return {HTMLSpanElement} - The dom element icon @@ -571,6 +1039,7 @@ item.style.webkitUserSelect = item.style.MozUserSelect = item.style.userSelect = 'none'; + item.style.position = 'relative'; // White glow on icon item.addEventListener( touchEnabled ? 'touchstart' : 'mouseenter', function() {