diff --git a/index.js b/index.js index dcb1224..e4f8fd0 100644 --- a/index.js +++ b/index.js @@ -11,12 +11,15 @@ XrClientProvider.setXrClient(new XrClient()); export default { _init() { this._nativeFactory = new configuration.nativeFactory(configuration.nativeMapping); + this._spatialLogger = new configuration.spatialLogger(); }, bootstrap(app) { this._init(); this._app = this._nativeFactory.createApp(app); + this._app.SpatialLogger = this._spatialLogger; + XrClientProvider.getXrClient().setNativeApp(this._app); }, diff --git a/src/configuration.js b/src/configuration.js index 054e16b..f24af17 100644 --- a/src/configuration.js +++ b/src/configuration.js @@ -3,9 +3,11 @@ import nativeComponentMapping from './platform/lumin-runtime/component-mapping.js'; import { PlatformFactory } from './platform/lumin-runtime/platform-factory.js'; import { MessageSeverity } from './util/logger.js'; +import SpatialLogger from './platform/lumin-runtime/utilities/spatial-logger.js'; export default { + logMinMessageSeverity: MessageSeverity.error, nativeMapping: nativeComponentMapping, nativeFactory: PlatformFactory, - logMinMessageSeverity: MessageSeverity.error + spatialLogger: SpatialLogger }; diff --git a/src/platform/lumin-runtime/mxs-base-app.js b/src/platform/lumin-runtime/mxs-base-app.js index 3136dfb..702a8a5 100644 --- a/src/platform/lumin-runtime/mxs-base-app.js +++ b/src/platform/lumin-runtime/mxs-base-app.js @@ -26,7 +26,11 @@ export class MxsBaseApp { return this._onAppStartData; } - onAppStart(args) { + set SpatialLogger(spatialLogger) { + this._spatialLogger = spatialLogger; + } + + onAppStart(args, app) { this._onAppStartData = { uri: args.getUri(), isInternetConnected: typeof this.isInternetConnected === 'function' ? this.isInternetConnected() : undefined, @@ -35,6 +39,14 @@ export class MxsBaseApp { isShareableApp: typeof this.isShareableApp === 'function' ? this.isShareableApp() : undefined }; + const spatialLoggerProps = this._app.props.spatialLogger; + if (this._spatialLogger && spatialLoggerProps) { + const loggerPrism = executor.callNativeFunction(app, 'requestNewPrism', spatialLoggerProps.size); + executor.callNativeFunction(app, 'positionPrismRelativeToCamera', loggerPrism, spatialLoggerProps.position); + executor.callNativeFunction(app, 'orientPrismRelativeToCamera', loggerPrism, spatialLoggerProps.orientation); + this._spatialLogger.init(loggerPrism); + } + const container = { controller: { getRoot: () => ({ addChild: (child) => logInfo('App container - adding child (scene)') }) diff --git a/src/platform/lumin-runtime/mxs-immersive-app.js b/src/platform/lumin-runtime/mxs-immersive-app.js index a9afab8..046d561 100644 --- a/src/platform/lumin-runtime/mxs-immersive-app.js +++ b/src/platform/lumin-runtime/mxs-immersive-app.js @@ -16,12 +16,16 @@ export class MxsImmersiveApp extends ImmersiveApp { }; } + set SpatialLogger(spatialLogger) { + return this._baseApp.SpatialLogger = spatialLogger; + } + init() { return 0; } onAppStart(arg) { - this._baseApp.onAppStart(arg); + this._baseApp.onAppStart(arg, this); } onAppPause() { diff --git a/src/platform/lumin-runtime/mxs-landscape-app.js b/src/platform/lumin-runtime/mxs-landscape-app.js index d56f698..3520011 100644 --- a/src/platform/lumin-runtime/mxs-landscape-app.js +++ b/src/platform/lumin-runtime/mxs-landscape-app.js @@ -13,12 +13,16 @@ export class MxsLandscapeApp extends LandscapeApp { return this._baseApp.OnAppStartData; } + set SpatialLogger(spatialLogger) { + return this._baseApp.SpatialLogger = spatialLogger; + } + init() { return 0; } onAppStart(arg) { - this._baseApp.onAppStart(arg); + this._baseApp.onAppStart(arg, this); } updateLoop(delta) { diff --git a/src/platform/lumin-runtime/platform-factory.js b/src/platform/lumin-runtime/platform-factory.js index 9a18772..419a0fc 100644 --- a/src/platform/lumin-runtime/platform-factory.js +++ b/src/platform/lumin-runtime/platform-factory.js @@ -566,6 +566,49 @@ export class PlatformFactory extends NativeFactory { } } + _createConfirmDialogNode (prism, title, message) { + const uiDialog = ui.UiDialog.CreateScrolling(prism, title, message, ui.DialogType.kSingleAction, ui.DialogLayout.kStandard); + const callbackId = uiDialog.onConfirmSub((eventData) => { + uiDialog.onConfirmUnsub(callbackId); + prism.deleteNode(uiDialog); + }); + return uiDialog; + } + + showSpatialMessage (prism, title, message) { + prism.getRootNode().addChild(this._createConfirmDialogNode(prism, title, message)); + } + + _showErrorOnElementAction (container, title, message) { + logError(message); + + if (container === undefined || container.controller === undefined) { + return; + } + + if (typeof container.controller.getPrism !== 'function') { + return; + } + + const prism = container.controller.getPrism(); + if (prism === undefined) { + return; + } + this.showSpatialMessage(prism, title, message); + } + + showErrorOnCreateElement (type, properties, container, error) { + this._showErrorOnElementAction(container, `Creating ${type} has failed`, error.message); + } + + showErrorOnUpdateElement (type, properties, container, error) { + this._showErrorOnElementAction(container, `Updating ${type} has failed`, error.message); + } + + showErrorOnRemoveElement (type, properties, container, error) { + this._showErrorOnElementAction(container, `Removing ${type} has failed`, error.message); + } + _validateAppType (type) { if (type !== undefined && this._appConstructors[type] === undefined) { throw new TypeError(`Invalid argument: Unknown app type: ${type}`); diff --git a/src/platform/lumin-runtime/utilities/spatial-logger.js b/src/platform/lumin-runtime/utilities/spatial-logger.js new file mode 100644 index 0000000..e6cffb3 --- /dev/null +++ b/src/platform/lumin-runtime/utilities/spatial-logger.js @@ -0,0 +1,100 @@ +import { ui } from 'lumin'; +import { logError } from '../../../util/logger.js'; + +const { UiText, UiListView, UiListViewItem, Alignment, HorizontalTextAlignment } = ui; + +const COLOR_RED = [1, 0, 0, 1]; +const COLOR_YELLOW = [1, 1, 0, 1]; +const COLOR_WHITE = [1, 1, 1, 1]; +const COLOR_GRAY = [0.5, 0.5, 0.5, 0.5]; + +const LOG_MAX_ITEM_COUNT = 100; +const LOG_DEFAULT_ITEM_PADDING = [0.002, 0.002, 0.002, 0.002]; +const LOG_ITEM_TEXT_SIZE = 0.02; +const LOG_TITLE_HEIGHT = 0.07; +const LOG_TITLE_SIZE = 0.05; +const LOG_TITLE = 'console'; + +function _createColoredTextNode(prism, message, color) { + const uiText = UiText.Create(prism, message); + uiText.setTextColor(color); + uiText.setTextSize(LOG_ITEM_TEXT_SIZE); + uiText.setTextAlignment(HorizontalTextAlignment.kLeft); + // uiText.setBoundsSize([prism.getSize()[0], LOG_ITEM_TEXT_SIZE*2], true); + return uiText; +} + +function _createLogListView (prism) { + const [width, height] = prism.getSize(); + const uiListView = UiListView.Create(prism, width, height - LOG_TITLE_HEIGHT); + uiListView.setDefaultItemPadding(LOG_DEFAULT_ITEM_PADDING); + const x = parseFloat(width) / 2 - 0.002; + const y = parseFloat(height) / 2; + uiListView.setLocalPosition([-x, y - LOG_TITLE_HEIGHT, 0]); + return uiListView; +} + +function _createLogFrame (prism) { + const [width, height] = prism.getSize(); + const x = parseFloat(width) / 2 - 0.001; + const y = parseFloat(height) / 2; + const line = prism.createLineNode(); + line.setColor(COLOR_GRAY); + line.addPoints([-x, y, 0]); + line.addPoints([ x, y, 0]); + line.addPoints([ x, -y, 0]); + line.addPoints([-x, -y, 0]); + line.addPoints([-x, y, 0]); + return line; +} + +function _createTitle (prism) { + const uiText = UiText.Create(prism, LOG_TITLE); + uiText.setTextSize(LOG_TITLE_SIZE); + const [, height] = prism.getSize(); + const y = parseFloat(height) / 2; + uiText.setLocalPosition([-0.07, y - LOG_TITLE_HEIGHT, 0]); + return uiText; +} + +export default class SpatialLogger { + init (prism) { + if (prism === undefined) { + return; + } + + this._prism = prism; + const rootNode = this._prism.getRootNode(); + rootNode.addChild(_createTitle(this._prism)); + rootNode.addChild(_createLogFrame(this._prism)); + + this._logView = _createLogListView(this._prism); + rootNode.addChild(this._logView); + } + + _log(message, color) { + if (this._prism === undefined) { + return; + } + + if (this._logView.getItemCount() >= LOG_MAX_ITEM_COUNT) { + this._prism.deleteNode(this._logView.removeItem(0)); + } + + const uiListViewItem = UiListViewItem.Create(this._prism); + uiListViewItem.addChild(_createColoredTextNode(this._prism, message, color)); + this._logView.addItem(uiListViewItem); + } + + logInfo(message) { + this._log(message, COLOR_WHITE); + } + + logWarning (message) { + this._log(message, COLOR_YELLOW); + } + + logError (message) { + this._log(message, COLOR_RED); + } +} diff --git a/src/react-magic-script/magic-script-renderer.js b/src/react-magic-script/magic-script-renderer.js index ecfb5e2..3175485 100644 --- a/src/react-magic-script/magic-script-renderer.js +++ b/src/react-magic-script/magic-script-renderer.js @@ -36,7 +36,13 @@ const UPDATE_SIGNAL = {}; // hostContext: HostContext, // internalInstanceHandle: Object function createInstance(type, props, rootContainerInstance, hostContext, internalInstanceHandle) { - return mxs._nativeFactory.createElement(type, rootContainerInstance, props); + let instance; + try { + instance = mxs._nativeFactory.createElement(type, rootContainerInstance, props); + } catch (error) { + mxs._nativeFactory.showErrorOnCreateElement(type, props, rootContainerInstance, error); + } + return instance; } // Function: This function is used to create separate text nodes if the target allows only creating text in separate text nodes @@ -221,7 +227,11 @@ function commitMount(instance, type, newProps, internalInstanceHandle) { // newProps: Props, // internalInstanceHandle: Object function commitUpdate(instance, updatePayload, type, oldProps, newProps, internalInstanceHandle) { - mxs._nativeFactory.updateElement(type, instance, oldProps, newProps); + try { + mxs._nativeFactory.updateElement(type, instance, oldProps, newProps); + } catch (error) { + mxs._nativeFactory.showErrorOnUpdateElement(type, props, rootContainerInstance, error); + } } // Function: insertBefore @@ -259,7 +269,11 @@ function insertInContainerBefore(container, child, beforeChild) { // parentInstance: Instance, // child: Instance | TextInstance function removeChild(parentInstance, child) { - mxs._nativeFactory.removeChildElement(parentInstance, child); + try { + mxs._nativeFactory.removeChildElement(parentInstance, child); + } catch (error) { + mxs._nativeFactory.showErrorOnRemoveElement(type, props, rootContainerInstance, error); + } } // Function: removeChildFromContainer