From 2a8b91e45e9781b7f74bdb26f29e4e019a883909 Mon Sep 17 00:00:00 2001 From: humbletim Date: Fri, 16 Jun 2017 16:07:50 -0400 Subject: [PATCH] cleanup pass --- .../marketplace/camera-move/_debug.js | 38 +- .../marketplace/camera-move/_json-persist.js | 14 +- .../marketplace/camera-move/_less-utils.js | 56 +- .../marketplace/camera-move/_tooltips.js | 284 +++--- .../camera-move/app-camera-move.js | 240 +++-- .../marketplace/camera-move/app.html | 849 ++++++++++-------- .../marketplace/camera-move/app.js | 290 +++--- .../marketplace/camera-move/avatar-updater.js | 27 +- .../marketplace/camera-move/hifi-jquery-ui.js | 134 ++- .../camera-move/modules/EnumMeta.js | 4 +- .../marketplace/camera-move/modules/_utils.js | 170 +--- .../camera-move/modules/config-utils.js | 37 +- .../custom-settings-app/CustomSettingsApp.js | 115 +-- .../browser/BridgedSettings.js | 8 +- .../browser/JQuerySettings.js | 25 +- .../camera-move/modules/movement-utils.js | 136 +-- .../modules/movement-utils.mapping.json | 2 +- 17 files changed, 1222 insertions(+), 1207 deletions(-) diff --git a/unpublishedScripts/marketplace/camera-move/_debug.js b/unpublishedScripts/marketplace/camera-move/_debug.js index ecec259663..d8c05d646e 100644 --- a/unpublishedScripts/marketplace/camera-move/_debug.js +++ b/unpublishedScripts/marketplace/camera-move/_debug.js @@ -2,6 +2,8 @@ /* eslint-disable comma-dangle, no-empty */ /* global EventBridge: true, PARAMS, signal, assert, log, debugPrint, bridgedSettings, _utils, jquerySettings, */ + +// helper functions for debugging and testing the UI in an external web brower var _debug = { handleUncaughtException: function onerror(message, fileName, lineNumber, colNumber, err) { if (message === onerror.lastMessage) { @@ -25,7 +27,7 @@ var _debug = { } }, loadScriptNodes: function loadScriptNodes(selector) { - // scripts are loaded this way to ensure refreshing the client script refreshes dependencies too + // scripts are loaded this way to ensure that when the client script refreshes, so are the app's dependencies [].forEach.call(document.querySelectorAll(selector), function(script) { script.parentNode.removeChild(script); if (script.src) { @@ -35,7 +37,8 @@ var _debug = { document.write(script.outerHTML); }); }, - // TESTING MOCK (allows the UI to be tested using a normal web browser, outside of Interface + + // TESTING MOCKs openEventBridgeMock: function openEventBridgeMock(onEventBridgeOpened) { var updatedValues = openEventBridgeMock.updatedValues = {}; // emulate EventBridge's API @@ -47,12 +50,10 @@ var _debug = { onEventBridgeOpened(EventBridge); assert(!bridgedSettings.onUnhandledMessage); bridgedSettings.onUnhandledMessage = function(msg) { - if (1||!msg || msg.method !== 'valueUpdated') { - log('bridgedSettings.onUnhandledMessage', msg); - } + log('bridgedSettings.onUnhandledMessage', msg); return true; }; - // manually trigger bootstrapping responses + // manually trigger initial bootstrapping responses (that the client script would normally send) bridgedSettings.handleExtraParams({uuid: PARAMS.uuid, ns: PARAMS.ns, extraParams: { mock: true, appVersion: 'browsermock', @@ -61,8 +62,8 @@ var _debug = { toolbar: true, browser: true, desktop: true, - tablet: false, - hmd: false, + tablet: /tablet/.test(location) || /android|ipad|iphone/i.test(navigator.userAgent), + hmd: /hmd/.test(location), }, } }); bridgedSettings.setValue('ui-show-advanced-options', true); @@ -72,13 +73,13 @@ var _debug = { console.log.apply(console, ['[mock] ' + msg].concat([].slice.call(arguments,1))); } - // generate mock data in response to outgoing web events + // generate mock data in response to outgoing web page events function onEmitWebEvent(message) { try { - var obj = JSON.parse(message); + var obj = JSON.parse(message); } catch (e) {} if (!obj) { - // message isn't JSON or doesn't expect a reply so just log it and bail early + // message isn't JSON so just log it and bail early log('consuming non-callback web event', message); return; } @@ -92,20 +93,21 @@ var _debug = { var key = obj.params[0]; var node = jquerySettings.findNodeByKey(key, true); // log('Settings.getValue.findNodeByKey', key, node); - var type = node && (node.dataset.type || node.type || node.attributes['type']); + var type = node && (node.dataset.hifiType || node.dataset.type || node.type); switch (type) { - case 'checkbox': { - obj.result = /tooltip/i.test(key) || PARAMS.tooltiptest ? true : Math.random() > 0.5; + case 'hifiButton': + case 'hifiCheckbox': { + obj.result = /tooltip|advanced-options/i.test(key) || PARAMS.tooltiptest ? true : Math.random() > 0.5; } break; - case 'radio-group': { + case 'hifiRadioGroup': { var radios = $(node).find('input[type=radio]').toArray(); while (Math.random() < 0.9) { - radios.push(radios.shift()); + radios.push(radios.shift()); } obj.result = radios[0].value; } break; - case 'spinner': - case 'number': { + case 'hifiSpinner': + case 'hifiSlider': { var step = node.step || 1, precision = (1/step).toString().length - 1; var magnitude = node.max || (precision >=1 ? Math.pow(10, precision-1) : 10); obj.result = parseFloat((Math.random() * magnitude).toFixed(precision||1)); diff --git a/unpublishedScripts/marketplace/camera-move/_json-persist.js b/unpublishedScripts/marketplace/camera-move/_json-persist.js index be253ff16b..5915842d1b 100644 --- a/unpublishedScripts/marketplace/camera-move/_json-persist.js +++ b/unpublishedScripts/marketplace/camera-move/_json-persist.js @@ -21,10 +21,10 @@ var SettingsJSON = (function() { function encodeNodes(resolver) { return resolver.getAllNodes().reduce((function(out, input, i) { - debugPrint('input['+i+']', input.id); + //debugPrint('input['+i+']', input.id); var id = input.id, key = resolver.getKey(id); - debugPrint('toJSON', id, key, input.id); + //debugPrint('toJSON', id, key, input.id); setPath(out, key.split('/'), resolver.getValue(key)); return out; }).bind(this), {}); @@ -35,7 +35,7 @@ var SettingsJSON = (function() { obj = path.reduce(function(obj, subkey) { return obj[subkey] = obj[subkey] || {}; }, obj); - debugPrint('setPath', key, Object.keys(obj)); + //debugPrint('setPath', key, Object.keys(obj)); obj[key] = value; } @@ -81,23 +81,24 @@ var SettingsJSON = (function() { var HTML = document.getElementById('POPUP').innerHTML .replace(/\bxx-script\b/g, 'script') .replace('JSON', JSON.stringify(tmp, 0, 2).replace(/\n/g, '
')); - if (arguments[3] === '_overlayWebWindow') { + if (/WebWindowEx/.test(navigator.userAgent) ) { bridgedSettings.sendEvent({ method: 'overlayWebWindow', + userAgent: navigator.userAgent, options: { title: 'app-camera-move-export' + (title ? '::'+title : ''), content: HTML, }, }); } else { - // make the browser address bar less ugly by putting spaces and friedly name as a "URL footer" + // append a footer to the data URI so it displays cleaner in the built-in browser window that opens var footer = '<\!-- #' + HTML.substr(0,256).replace(/./g,' ') + (title || 'Camera Move Settings'); window.open("data:text/html;escape," + encodeURIComponent(HTML) + footer,"app-camera-move-export"); } } function applyJSON(resolver, name, tmp) { - assert('version' in tmp && 'settings' in tmp, 'invalid settings record: ' + JSON.stringify(tmp)); + assert(tmp && 'version' in tmp && 'settings' in tmp, 'invalid settings record: ' + JSON.stringify(tmp)); var settings = rollupPaths(tmp.settings); for (var p in settings) { if (/^[.]/.test(p)) { @@ -118,6 +119,7 @@ var SettingsJSON = (function() { return; } try { + log('parsing json', json); json = JSON.parse(json); } catch (e) { throw new Error('Could not parse pasted JSON: ' + e + '\n\n' + (json||'').replace(/").children(0).text(hash); - // LESS needs some help to recompile inline styles, so a fresh copy of the source nodes is swapped-in + + // patch less with absolute line number reporting + options.errorReporting && options.errorReporting.$patch && options.errorReporting.$patch(); + + // to recompile inline styles (in response to onresize or when developing), + // a fresh copy of the source nodes gets swapped-in var newNodes = lessManager.elements.clone().appendTo(document.body); - // less.modifyVars && less.modifyVars(true, globalVars); // note: refresh(reload, modifyVars, clearFileCache) less.refresh(false, globalVars).then(function(result) { - log('less.refresh completed OK', result); + debugPrint('less.refresh completed OK', result); })['catch'](function(err) { log('less ERROR:', err); }); diff --git a/unpublishedScripts/marketplace/camera-move/_tooltips.js b/unpublishedScripts/marketplace/camera-move/_tooltips.js index c1df716d24..94206b2ef9 100644 --- a/unpublishedScripts/marketplace/camera-move/_tooltips.js +++ b/unpublishedScripts/marketplace/camera-move/_tooltips.js @@ -5,143 +5,165 @@ // ---------------------------------------------------------------------------- // manage jquery-tooltipster hover tooltips var TooltipManager = (function(global) { + Object.assign(TooltipManager, { + BASECONFIG: { + theme: ['tooltipster-noir'], + side: ['right','top','bottom', 'left'], + updateAnimation: 'scale', + delay: [750, 1000], + distance: { right: 24, left: 8, top: 8, bottom: 8 }, + contentAsHTML: true, + }, + }); + function TooltipManager(options) { - this.options = options; - assert(options.elements, 'TooltipManager constructor expects options.elements'); - assert(options.tooltips, 'TooltipManager constructor expects options.tooltips'); - var TOOLTIPS = this.TOOLTIPS = {}; - $(options.tooltips).find('[for]').each(function() { - var id = $(this).attr('for'); - TOOLTIPS[id] = $(this).html(); - }); - - var _self = this; - options.elements.each(function() { - var element = $($(this).closest('.tooltip-target').get(0) || this), - input = element.is('input, button') ? element : element.find('input, button'), - parent = element.is('.row, button') ? element : element.parent(), - id = element.attr('for') || input.prop('id'), - tip = TOOLTIPS[id] || element.prop('title'); - - var tooltipSide = element.data('tooltipSide'); - - debugPrint('binding tooltip', tooltipSide, element[0].nodeName, id || element[0], tip); - if (!tip) { - return log('missing tooltip:', this.nodeName, id || this.id || this.name || $(this).text()); - } - if (element.is('.tooltipstered')) { - log('already tooltipstered!?', this.id, this.name, id); - return; - } - var instance = element.tooltipster({ - theme: ['tooltipster-noir'], - side: tooltipSide || ( - input.is('button') ? 'top' : - input.closest('.slider').get(0) || input.closest('.column').get(0) ? ['top','bottom'] : - ['right','top','bottom', 'left'] - ), - content: tip, - updateAnimation: 'scale', + assert(options.elements && options.tooltips, 'TooltipManager constructor expects .elements and .tooltips'); + Object.assign(this, { + instances: [], + options: options, + config: Object.assign({}, TooltipManager.BASECONFIG, { trigger: !options.testmode ? 'hover' : 'click', - distance: element.is('.slider.row') ? -20 : undefined,// element - delay: [500, 1000], - contentAsHTML: true, interactive: options.testmode, minWidth: options.viewport && options.viewport.min.width, maxWidth: options.viewport && options.viewport.max.width, - }).tooltipster('instance'); - - instance.on('close', function(event) { - if (options.keepopen === element) { - debugPrint(event.type, 'canceling close keepopen === element', id); - event.stop(); - options.keepopen = null; - } - }); - instance.on('before', function(event) { - debugPrint(event.type, 'before', event); - !options.testmode && _self.closeAll(); - !options.enabled && event.stop(); - return; - }); - parent.find(':focusable, input, [tabindex], button, .control') - .add(parent) - .add(input.closest(':focusable, input, [tabindex]')) - .on({ - click: function(evt) { - if (input.is('button')) { - return setTimeout(instance.close.bind(instance,null),50); - } - options.keepopen = element; 0&&instance.open(); - }, - focus: instance.open.bind(instance, null), - blur: function(evt) { - instance.close(); _self.openFocusedTooltip(); - }, - }); - }); - Object.assign(this, { - openFocusedTooltip: function() { - if (!this.options.enabled) { - return; - } - setTimeout(function() { - if (!document.activeElement || document.activeElement === document.body || - !$(document.activeElement).closest('section')) { - return; - } - var tip = $([]) - .add($(document.activeElement)) - .add($(document.activeElement).find('.tooltipstered')) - .add($(document.activeElement).closest('.tooltipstered')) - .filter('.tooltipstered'); - if (tip.is('.tooltipstered')) { - // log('opening focused tooltip', tip.length, tip[0].id); - tip.tooltipster('open'); - } - },1); - }, - rapidClose: function(instance, reopen) { - if (!instance.status().open) { - return; - } - instance.elementTooltip() && $(instance.elementTooltip()).hide(); - instance.close(function() { - reopen && instance.open(); - }); - }, - openAll: function() { - $('.tooltipstered').tooltipster('open'); - }, - closeAll: function() { - $.tooltipster.instances().forEach(function(instance) { - this.rapidClose(instance); - }.bind(this)); - }, - updateViewport: function(viewport) { - var options = { - minWidth: viewport.min.width, - maxWidth: viewport.max.width, - }; - $.tooltipster.setDefaults(options); - log('updating tooltipster options', JSON.stringify(options)); - $.tooltipster.instances().forEach(function(instance) { - instance.option('minWidth', options.minWidth); - instance.option('maxWidth', options.maxWidth); - this.rapidClose(instance, instance.status().open); - }.bind(this)); - }, - enable: function() { - this.options.enabled = true; - if (this.options.testmode) { - this.openAll(); - } - }, - disable: function() { - this.options.enabled = false; - this.closeAll(); - }, + }), }); + options.enabled && this.initialize(); } + + TooltipManager.prototype = { + constructor: TooltipManager, + initialize: function() { + var options = this.options, + _config = this.config, + _self = this, + candidates = $(options.elements); + + candidates.add($('button')).each(function() { + var id = this.id, + input = $(this), + tip = options.tooltips[id] || options.tooltips[input.data('for')]; + + var alreadyTipped = input.is('.tooltipstered') || input.closest('.tooltipstered').get(0); + if (alreadyTipped || !tip) { + return !tip && _debugPrint('!tooltippable -- missing tooltip for ' + (id || input.data('for') || input.text())); + } + var config = Object.assign({ content: tip }, _config); + + function mergeConfig() { + var attr = $(this).attr('data-tooltipster'), + object = $(this).data('tooltipster'); + typeof object === 'object' && Object.assign(config, object); + attr && Object.assign(config, JSON.parse(attr)); + } + try { + input.parents(':data(tooltipster),[data-tooltipster]').each(mergeConfig); + input.each(mergeConfig); // prioritize own settings + } catch(e) { + console.error('error extracting tooltipster data:' + [e, id]); + } + + var target = $(input.closest('.tooltip-target').get(0) || + (input.is('input') && input) || null); + + assert(target && target[0] && tip); + debugPrint('binding tooltip', config, target[0].nodeName, id || target[0]); + var instance = target.tooltipster(config) + .tooltipster('instance'); + + instance.on('close', function(event) { + if (options.keepopen === target) { + debugPrint(event.type, 'canceling close keepopen === target', id); + event.stop(); + options.keepopen = null; + } + }); + instance.on('before', function(event) { + debugPrint(event.type, 'before', event); + !options.testmode && _self.closeAll(); + !options.enabled && event.stop(); + }); + target.find(':focusable, input, [tabindex], button, .control') + .add(target).add(input) + .add(input.closest(':focusable, input, [tabindex]')) + .on({ + click: function(evt) { + if (input.is('button')) { + return setTimeout(instance.close.bind(instance,null),50); + } + options.keepopen = target; + }, + focus: instance.open.bind(instance, null), + blur: function(evt) { + instance.close(); _self.openFocusedTooltip(); + }, + }); + _self.instances.push(instance); + }); + return this.instances; + }, + openFocusedTooltip: function() { + if (!this.options.enabled) { + return; + } + setTimeout(function() { + if (!document.activeElement || document.activeElement === document.body || + !$(document.activeElement).closest('section')) { + return; + } + var tip = $([]) + .add($(document.activeElement)) + .add($(document.activeElement).find('.tooltipstered')) + .add($(document.activeElement).closest('.tooltipstered')) + .filter('.tooltipstered'); + if (tip.is('.tooltipstered')) { + // log('opening focused tooltip', tip.length, tip[0].id); + tip.tooltipster('open'); + } + }, 1); + }, + rapidClose: function(instance, reopen) { + if (!instance.status().open) { + return; + } + instance.elementTooltip() && $(instance.elementTooltip()).hide(); + instance.close(function() { + reopen && instance.open(); + }); + }, + openAll: function() { + $('.tooltipstered').tooltipster('open'); + }, + closeAll: function() { + $.tooltipster.instances().forEach(function(instance) { + this.rapidClose(instance); + }.bind(this)); + }, + updateViewport: function(viewport) { + var options = { + minWidth: viewport.min.width, + maxWidth: viewport.max.width, + }; + Object.assign(this.config, options); + $.tooltipster.setDefaults(options); + debugPrint('updating tooltipster options', JSON.stringify(options)); + $.tooltipster.instances().forEach(function(instance) { + instance.option('minWidth', options.minWidth); + instance.option('maxWidth', options.maxWidth); + this.rapidClose(instance, instance.status().open); + }.bind(this)); + }, + enable: function() { + this.options.enabled = true; + if (this.options.testmode) { + this.openAll(); + } + }, + disable: function() { + this.options.enabled = false; + this.closeAll(); + }, + };// prototype + return TooltipManager; })(this); diff --git a/unpublishedScripts/marketplace/camera-move/app-camera-move.js b/unpublishedScripts/marketplace/camera-move/app-camera-move.js index 7ec38d6fab..e24a3903b2 100644 --- a/unpublishedScripts/marketplace/camera-move/app-camera-move.js +++ b/unpublishedScripts/marketplace/camera-move/app-camera-move.js @@ -12,7 +12,7 @@ /* eslint-disable comma-dangle, no-empty */ "use strict"; -var VERSION = '0.0.0d', +var VERSION = '0.0.1', NAMESPACE = 'app-camera-move', APP_HTML_URL = Script.resolvePath('app.html'), BUTTON_CONFIG = { @@ -23,10 +23,7 @@ var VERSION = '0.0.0d', var MINIMAL_CURSOR_SCALE = 0.5, FILENAME = Script.resolvePath(''), - WANT_DEBUG = ( - false || (FILENAME.match(/[&#?]debug[=](\w+)/)||[])[1] || - Settings.getValue(NAMESPACE + '/debug') - ), + WANT_DEBUG = Settings.getValue(NAMESPACE + '/debug', false) EPSILON = 1e-6; function log() { @@ -39,8 +36,8 @@ var require = Script.require, overlayDebugOutput = function(){}; if (WANT_DEBUG) { - log('WANT_DEBUG is true; instrumenting debug rigging', WANT_DEBUG); - _instrumentDebugValues(); + log('WANT_DEBUG is true; instrumenting debug support', WANT_DEBUG); + _instrumentDebug(); } var _utils = require('./modules/_utils.js'), @@ -58,6 +55,7 @@ var cameraControls, eventMapper, cameraConfig, applicationConfig; var DEFAULTS = { 'namespace': NAMESPACE, 'debug': WANT_DEBUG, + 'jitter-test': false, 'camera-move-enabled': false, 'thread-update-mode': movementUtils.CameraControls.SCRIPT_UPDATE, 'fps': 90, @@ -83,30 +81,21 @@ var DEFAULTS = { 'ui-enable-tooltips': true, 'ui-show-advanced-options': false, - - // 'toggle-key': DEFAULT_TOGGLE_KEY, }; // map setting names to/from corresponding Menu and API properties var APPLICATION_SETTINGS = { - 'Avatar/pitchSpeed': 'pitchSpeed' in MyAvatar && { - object: [ MyAvatar, 'pitchSpeed' ] - }, - 'Avatar/yawSpeed': 'yawSpeed' in MyAvatar && { - object: [ MyAvatar, 'yawSpeed' ] - }, 'Avatar/Enable Avatar Collisions': { menu: 'Avatar > Enable Avatar Collisions', object: [ MyAvatar, 'collisionsEnabled' ], }, 'Avatar/Draw Mesh': { menu: 'Developer > Draw Mesh', - // object: [ MyAvatar, 'shouldRenderLocally' ], // shouldRenderLocally seems to be broken... object: [ MyAvatar, 'getEnableMeshVisible', 'setEnableMeshVisible' ], }, - 'Avatar/useSnapTurn': { - object: [ MyAvatar, 'getSnapTurn', 'setSnapTurn' ], - }, + 'Avatar/Show My Eye Vectors': { menu: 'Developer > Show My Eye Vectors' }, + 'Avatar/Show Other Eye Vectors': { menu: 'Developer > Show Other Eye Vectors' }, + 'Avatar/useSnapTurn': { object: [ MyAvatar, 'getSnapTurn', 'setSnapTurn' ] }, 'Avatar/lookAtSnappingEnabled': 'lookAtSnappingEnabled' in MyAvatar && { menu: 'Developer > Enable LookAt Snapping', object: [ MyAvatar, 'lookAtSnappingEnabled' ] @@ -119,17 +108,6 @@ var APPLICATION_SETTINGS = { cameraControls.setEnabled(!!nv); }, }, - // 'toggle-key': { - // get: function() { try { - // return JSON.parse(cameraConfig.getValue('toggle-key')); - // } catch (e) { - // return DEFAULT_TOGGLE_KEY; - // } }, - // set: function(nv) { - // assert(typeof nv === 'object', 'new toggle-key is not an object: ' + nv); - // cameraConfig.setValue('toggle-key', JSON.parse(JSON.stringify(nv))); - // }, - // }, }; var DEBUG_INFO = { @@ -143,7 +121,6 @@ var DEBUG_INFO = { supportsPitchSpeed: 'pitchSpeed' in MyAvatar, supportsYawSpeed: 'yawSpeed' in MyAvatar, supportsLookAtSnappingEnabled: 'lookAtSnappingEnabled' in MyAvatar, - supportsDensity: 'density' in MyAvatar, }, Reticle: { supportsScale: 'scale' in Reticle, @@ -160,7 +137,7 @@ var globalState = { }, }, - // batch updates to MyAvatar/Camera properties (submitting together seems to help reduce timeslice jitter) + // batch updates to MyAvatar/Camera properties (submitting together seems to help reduce jitter) pendingChanges: _utils.DeferredUpdater.createGroup({ Camera: Camera, MyAvatar: MyAvatar, @@ -169,9 +146,7 @@ var globalState = { // current input controls' effective velocities currentVelocities: new movementUtils.VelocityTracker({ translation: Vec3.ZERO, - step_translation: Vec3.ZERO, // eslint-disable-line camelcase rotation: Vec3.ZERO, - step_rotation: Vec3.ZERO, // eslint-disable-line camelcase zoom: Vec3.ZERO, }), }; @@ -187,8 +162,7 @@ function main() { button = null; }); - // track both runtime state (applicationConfig) and settings state (cameraConfig) - // (this is necessary because Interface does not yet consistently keep config Menus, APIs and Settings in sync) + // track runtime state (applicationConfig) and Settings state (cameraConfig) applicationConfig = new configUtils.ApplicationConfig({ namespace: DEFAULTS.namespace, config: APPLICATION_SETTINGS, @@ -204,12 +178,12 @@ function main() { toggleKey = JSON.parse(cameraConfig.getValue('toggle-key')); } catch (e) {} } - // set up a monitor to observe configuration changes between the two sources + // monitor configuration changes / keep tablet app up-to-date var MONITOR_INTERVAL_MS = 1000; _startConfigationMonitor(applicationConfig, cameraConfig, MONITOR_INTERVAL_MS); // ---------------------------------------------------------------------------- - // set up the tablet webview app + // set up the tablet app log('APP_HTML_URL', APP_HTML_URL); var settingsApp = new CustomSettingsApp({ namespace: cameraConfig.namespace, @@ -219,19 +193,20 @@ function main() { tablet: tablet, extraParams: Object.assign({ toggleKey: toggleKey, - }, _utils.getSystemMetadata(), DEBUG_INFO), + }, getSystemMetadata(), DEBUG_INFO), debug: WANT_DEBUG > 1, }); Script.scriptEnding.connect(settingsApp, 'cleanup'); settingsApp.valueUpdated.connect(function(key, value, oldValue, origin) { - log('[settingsApp.valueUpdated @ ' + origin + ']', key + ' = ' + JSON.stringify(value) + ' (was: ' + JSON.stringify(oldValue) + ')'); + log('settingsApp.valueUpdated: '+ key + ' = ' + JSON.stringify(value) + ' (was: ' + JSON.stringify(oldValue) + ')'); if (/tablet/i.test(origin)) { - log('cameraConfig applying immediate setting', key, value); - // apply relevant settings immediately when changed from the app UI + // apply relevant settings immediately if changed from the tablet UI + log('applying immediate setting', key, value); applicationConfig.applyValue(key, value, origin); } }); + // process custom eventbridge messages settingsApp.onUnhandledMessage = function(msg) { switch (msg.method) { case 'window.close': { @@ -250,9 +225,6 @@ function main() { }, 500); } break; case 'reset': { - // if (!Window.confirm('Reset all camera move settings to system defaults?')) { - // return; - // } var resetValues = {}; Object.keys(DEFAULTS).reduce(function(out, key) { var resolved = cameraConfig.resolve(key); @@ -264,16 +236,13 @@ function main() { out[resolved] = resolved in out ? out[resolved] : applicationConfig.getValue(key); return out; }, resetValues); - log('resetValues', JSON.stringify(resetValues, 0, 2)); + log('restting to system defaults:', JSON.stringify(resetValues, 0, 2)); for (var p in resetValues) { var value = resetValues[p]; applicationConfig.applyValue(p, value, 'reset'); cameraConfig.setValue(p, value); } } break; - case 'overlayWebWindow': { - _utils._overlayWebWindow(msg.options); - } break; default: { log('onUnhandledMessage', JSON.stringify(msg,0,2)); } break; @@ -281,11 +250,10 @@ function main() { }; // ---------------------------------------------------------------------------- - // set up the keyboard/mouse/controller/meta input state manager + // set up the keyboard/mouse/controller input state manager eventMapper = new movementUtils.MovementEventMapper({ namespace: DEFAULTS.namespace, mouseSmooth: cameraConfig.getValue('enable-mouse-smooth'), - xexcludeNames: [ 'Keyboard.C', 'Keyboard.E', 'Actions.TranslateY' ], mouseMultiplier: cameraConfig.getValue('mouse-multiplier'), keyboardMultiplier: cameraConfig.getValue('keyboard-multiplier'), eventFilter: function eventFilter(from, event, defaultFilter) { @@ -293,6 +261,7 @@ function main() { driveKeyName = event.driveKeyName; if (!result || !driveKeyName) { if (from === 'Keyboard.RightMouseButton') { + // let the app know when the user is mouse looking settingsApp.syncValue('Keyboard.RightMouseButton', event.actionValue, 'eventFilter'); } return 0; @@ -302,14 +271,14 @@ function main() { } if (from === 'Actions.Pitch') { result *= cameraConfig.getFloat('rotation-x-speed'); - } - if (from === 'Actions.Yaw') { + } else if (from === 'Actions.Yaw') { result *= cameraConfig.getFloat('rotation-y-speed'); } return result; }, }); Script.scriptEnding.connect(eventMapper, 'disable'); + // keep track of these changes live so the controller mapping can be kept in sync applicationConfig.register({ 'enable-mouse-smooth': { object: [ eventMapper.options, 'mouseSmooth' ] }, 'keyboard-multiplier': { object: [ eventMapper.options, 'keyboardMultiplier' ] }, @@ -347,33 +316,41 @@ function main() { Script.scriptEnding.connect(spacebar, 'disconnect'); // ---------------------------------------------------------------------------- - // set up ESC for reset drive key states + // set up ESC for resetting all drive key states Script.scriptEnding.connect(new _utils.KeyListener({ text: 'ESC', onKeyPressEvent: function(event) { if (cameraControls.enabled) { - log('ESC pressed -- resetting drive keys values:', JSON.stringify({ + log('ESC pressed -- resetting drive keys:', JSON.stringify({ virtualDriveKeys: eventMapper.states, movementState: eventMapper.getState(), }, 0, 2)); eventMapper.states.reset(); + MyAvatar.velocity = Vec3.ZERO; + MyAvatar.angularVelocity = Vec3.ZERO; } }, }), 'disconnect'); - // set up the tablet button to toggle the app UI display + // set up the tablet button to toggle the UI display button.clicked.connect(settingsApp, function(enable) { - Object.assign(this.extraParams, _utils.getSystemMetadata()); + Object.assign(this.extraParams, getSystemMetadata()); + button.editProperties({ text: '(opening)' + BUTTON_CONFIG.text, isActive: true }); this.toggle(enable); }); settingsApp.isActiveChanged.connect(function(isActive) { updateButtonText(); + if (Overlays.getOverlayType(HMD.tabletScreenID)) { + var fromMode = Overlays.getProperty(HMD.tabletScreenID, 'inputMode'), + inputMode = isActive ? "Mouse" : "Touch"; + log('switching HMD.tabletScreenID from inputMode', fromMode, 'to', inputMode); + Overlays.editOverlay(HMD.tabletScreenID, { inputMode: inputMode }); + } }); cameraControls.modeChanged.connect(onCameraModeChanged); - var fpsTimeout = 0; function updateButtonText() { var lines = [ settingsApp.isActive ? '(app open)' : '', @@ -382,6 +359,7 @@ function main() { button && button.editProperties({ text: lines.join('\n') }); } + var fpsTimeout = 0; cameraControls.enabledChanged.connect(function(enabled) { log('enabledChanged', enabled); button && button.editProperties({ isActive: enabled }); @@ -396,10 +374,14 @@ function main() { eventMapper.disable(); avatarUpdater._resetMyAvatarMotor({ MyAvatar: MyAvatar }); updateButtonText(); + if (settingsApp.isActive) { + settingsApp.syncValue('Keyboard.RightMouseButton', false, 'cameraControls.disabled'); + } } overlayDebugOutput.overlayID && Overlays.editOverlay(overlayDebugOutput.overlayID, { visible: enabled }); }); + // when certain settings change we need to reset the drive systems var resetIfChanged = [ 'minimal-cursor', 'drive-mode', 'fps', 'thread-update-mode', 'mouse-multiplier', 'keyboard-multiplier', @@ -408,28 +390,14 @@ function main() { cameraConfig.valueUpdated.connect(function(key, value, oldValue, origin) { var triggerReset = !!~resetIfChanged.indexOf(key); - log('[cameraConfig.valueUpdated @ ' + origin + ']', - key + ' = ' + JSON.stringify(value), '(was:' + JSON.stringify(oldValue) + ')', + log('cameraConfig.valueUpdated: ' + key + ' = ' + JSON.stringify(value), '(was:' + JSON.stringify(oldValue) + ')', 'triggerReset: ' + triggerReset); if (/tablet/i.test(origin)) { - log('cameraConfig applying immediate setting', key, value); - // apply relevant settings immediately when changed from the app UI + log('applying immediate setting', key, value); applicationConfig.applyValue(key, value, origin); - log(JSON.stringify(cameraConfig.getValue(key))); - } - 0&&debugPrint('//cameraConfig.valueUpdated', JSON.stringify({ - key: key, - cameraConfig: cameraConfig.getValue(key), - applicationConfig: applicationConfig.getValue(key), - value: value, - triggerReset: triggerReset, - },0,2)); - - if (triggerReset) { - log('KEYBOARD multiplier', eventMapper.options.keyboardMultiplier); - cameraControls.reset(); } + triggerReset && cameraControls.reset(); }); if (cameraConfig.getValue('camera-move-enabled')) { @@ -440,7 +408,7 @@ function main() { } // main() function onCameraControlsEnabled() { - log('onCameraControlsEnabled!!!! ----------------------------------------------'); + log('onCameraControlsEnabled'); globalState.previousValues.reset(); globalState.currentVelocities.reset(); globalState.pendingChanges.reset(); @@ -450,11 +418,10 @@ function onCameraControlsEnabled() { } log('cameraConfig', JSON.stringify({ cameraConfig: getCameraMovementSettings(), - //DEFAULTS: DEFAULTS })); } -// reset values based on the selected Camera.mode (to help keep the visual display/orientation more reasonable) +// reset orientation-related values when the Camera.mode changes function onCameraModeChanged(mode, oldMode) { globalState.pendingChanges.reset(); globalState.previousValues.reset(); @@ -486,6 +453,7 @@ function getCameraMovementSettings() { return { epsilon: EPSILON, debug: cameraConfig.getValue('debug'), + jitterTest: cameraConfig.getValue('jitter-test'), driveMode: cameraConfig.getValue('drive-mode'), threadMode: cameraConfig.getValue('thread-update-mode'), fps: cameraConfig.getValue('fps'), @@ -504,7 +472,7 @@ function getCameraMovementSettings() { zoom: _getEasingGroup(cameraConfig, 'zoom'), }; - // extract an easing group (translation, rotation, or zoom) from cameraConfig + // extract a single easing group (translation, rotation, or zoom) from cameraConfig function _getEasingGroup(cameraConfig, group) { var multiplier = 1.0; if (group === 'zoom') { @@ -525,11 +493,8 @@ function getCameraMovementSettings() { } } -// ---------------------------------------------------------------------------- - -// ---------------------------------------------------------------------------- +// monitor and sync Application state -> Settings values function _startConfigationMonitor(applicationConfig, cameraConfig, interval) { - // monitor and sync Application state -> Settings values return Script.setInterval(function monitor() { var settingNames = Object.keys(applicationConfig.config); settingNames.forEach(function(key) { @@ -546,6 +511,23 @@ function _startConfigationMonitor(applicationConfig, cameraConfig, interval) { } // ---------------------------------------------------------------------------- +// DEBUG overlay support (enable by setting app-camera-move/debug = true in settings +// ---------------------------------------------------------------------------- +function _instrumentDebug() { + debugPrint = log; + var cacheBuster = '?' + new Date().getTime().toString(36); + require = Script.require(Script.resolvePath('./modules/_utils.js') + cacheBuster).makeDebugRequire(Script.resolvePath('.')); + APP_HTML_URL += cacheBuster; + overlayDebugOutput = _createOverlayDebugOutput({ + lineHeight: 12, + font: { size: 12 }, + width: 250, height: 800 }); + // auto-disable camera move mode when debugging + Script.scriptEnding.connect(function() { + cameraConfig && cameraConfig.setValue('camera-move-enabled', false); + }); +} + function _fixedPrecisionStringifiyFilter(key, value, object) { if (typeof value === 'object' && value && 'w' in value) { return Quat.safeEulerAngles(value); @@ -554,6 +536,7 @@ function _fixedPrecisionStringifiyFilter(key, value, object) { } return value; } + function _createOverlayDebugOutput(options) { options = require('./modules/_utils.js').assign({ x: 0, y: 0, width: 500, height: 800, visible: false @@ -578,21 +561,18 @@ function _createOverlayDebugOutput(options) { } } function onMessageReceived(channel, message, ssend, local) { - if (!local || channel !== _debugChannel) { - return; + if (local && channel === _debugChannel) { + overlayDebugOutput(JSON.parse(message)); } - overlayDebugOutput(JSON.parse(message)); } - return overlayDebugOutput; } - // ---------------------------------------------------------------------------- _patchCameraModeSetting(); function _patchCameraModeSetting() { - // FIXME: looks like the Camera API suffered a regression where setting Camera.mode = 'first person' or 'third person' - // no longer works; the only reliable way to set it now seems to be jury-rigging the Menu items... + // FIXME: looks like the Camera API suffered a regression where Camera.mode = 'first person' or 'third person' + // no longer works from the API; setting via Menu items still seems to work though. Camera.$setModeString = Camera.$setModeString || function(mode) { // 'independent' => "Independent Mode", 'first person' => 'First Person', etc. var cameraMenuItem = (mode+'') @@ -605,50 +585,46 @@ function _patchCameraModeSetting() { Menu.setIsOptionChecked(cameraMenuItem, true); }; } -// ---------------------------------------------------------------------------- -function _instrumentDebugValues() { - debugPrint = log; - var cacheBuster = '?' + new Date().getTime().toString(36); - require = Script.require(Script.resolvePath('./modules/_utils.js') + cacheBuster).makeDebugRequire(Script.resolvePath('.')); - APP_HTML_URL += cacheBuster; - overlayDebugOutput = _createOverlayDebugOutput({ - lineHeight: 12, - font: { size: 12 }, - width: 250, height: 800 }); - // auto-disable camera move mode when debugging - Script.scriptEnding.connect(function() { - cameraConfig && cameraConfig.setValue('camera-move-enabled', false); - }); +function getSystemMetadata() { + var tablet = Tablet.getTablet('com.highfidelity.interface.tablet.system'); + return { + mode: { + hmd: HMD.active, + desktop: !HMD.active, + toolbar: Uuid.isNull(HMD.tabletID), + tablet: !Uuid.isNull(HMD.tabletID), + }, + tablet: { + toolbarMode: tablet.toolbarMode, + desktopScale: Settings.getValue('desktopTabletScale'), + hmdScale: Settings.getValue('hmdTabletScale'), + }, + window: { + width: Window.innerWidth, + height: Window.innerHeight, + }, + desktop: { + width: Desktop.width, + height: Desktop.height, + }, + }; } -// Show fatal (unhandled) exceptions in a BSOD popup -Script.unhandledException.connect(function onUnhandledException(error) { - log('UNHANDLED EXCEPTION!!', error, error && error.stack); - try { - cameraControls.disable(); - } catch (e) {} - Script.unhandledException.disconnect(onUnhandledException); - if (WANT_DEBUG) { - // show blue screen of death with the error details - new _utils.BSOD({ - error: error, - buttons: [ 'Abort', 'Retry', 'Fail' ], - debugInfo: DEBUG_INFO, - }, function(error, button) { - log('BSOD.result', error, button); - if (button === 'Abort') { - Script.stop(); - } else if (button === 'Retry') { - _utils.reloadClientScript(FILENAME); - } - }); - } else { - // use a simple alert to display just the error message - Window.alert('app-camera-move error: ' + error.message); - Script.stop(); - } -}); // ---------------------------------------------------------------------------- - main(); + +if (typeof module !== 'object') { + // if uncaught exceptions occur, show the first in an alert with option to stop the script + Script.unhandledException.connect(function onUnhandledException(error) { + Script.unhandledException.disconnect(onUnhandledException); + log('UNHANDLED EXCEPTION', error, error && error.stack); + try { + cameraControls.disable(); + } catch (e) {} + // show the error message and first two stack entries + var trace = _utils.normalizeStackTrace(error); + var message = [ error ].concat(trace.split('\n').slice(0,2)).concat('stop script?').join('\n'); + Window.confirm('app-camera-move error: ' + message.substr(0,256)) && Script.stop(); + }); +} diff --git a/unpublishedScripts/marketplace/camera-move/app.html b/unpublishedScripts/marketplace/camera-move/app.html index a86813421d..6e8b856024 100644 --- a/unpublishedScripts/marketplace/camera-move/app.html +++ b/unpublishedScripts/marketplace/camera-move/app.html @@ -3,6 +3,8 @@ Camera Move + + - - -
- -

-            
-
- +

- - - - + @@ -570,10 +550,12 @@ try { - - - - +