diff --git a/BUILD_WIN.md b/BUILD_WIN.md index 965cb70ec6..48530b9351 100644 --- a/BUILD_WIN.md +++ b/BUILD_WIN.md @@ -33,7 +33,7 @@ If you do not wish to use the Python installation bundled with Visual Studio, yo Download and install the latest version of CMake 3.9. -Download the file named win64-x64 Installer from the [CMake Website](https://cmake.org/download/). You can access the installer on this [3.9 Version page](https://cmake.org/files/v3.9/). During installation, make sure to check "Add CMake to system PATH for all users" when prompted. +Download the file named win64-x64 Installer from the [CMake Website](https://cmake.org/download/). You can access the installer on this [3.14 Version page](https://cmake.org/files/v3.14/). During installation, make sure to check "Add CMake to system PATH for all users" when prompted. ### Step 3. Create VCPKG environment variable In the next step, you will use CMake to build High Fidelity. By default, the CMake process builds dependency files in Windows' `%TEMP%` directory, which is periodically cleared by the operating system. To prevent you from having to re-build the dependencies in the event that Windows clears that directory, we recommend that you create a `HIFI_VCPKG_BASE` environment variable linked to a directory somewhere on your machine. That directory will contain all dependency files until you manually remove them. diff --git a/CMakeLists.txt b/CMakeLists.txt index 1f6cffb7c1..9876a0d7ef 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -139,6 +139,7 @@ option(BUILD_MANUAL_TESTS "Build manual tests" ${BUILD_MANUAL_TESTS_OPTION}) option(BUILD_TOOLS "Build tools" ${BUILD_TOOLS_OPTION}) option(BUILD_INSTALLER "Build installer" ${BUILD_INSTALLER_OPTION}) option(USE_GLES "Use OpenGL ES" ${GLES_OPTION}) +option(USE_KHR_ROBUSTNESS "Use KHR_robustness" OFF) option(DISABLE_QML "Disable QML" ${DISABLE_QML_OPTION}) option(DISABLE_KTX_CACHE "Disable KTX Cache" OFF) option( @@ -149,6 +150,10 @@ option( set(PLATFORM_QT_GL OpenGL) +if (USE_KHR_ROBUSTNESS) + add_definitions(-DUSE_KHR_ROBUSTNESS) +endif() + if (USE_GLES) add_definitions(-DUSE_GLES) add_definitions(-DGPU_POINTER_STORAGE_SHARED) diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index 709c5810e2..6d339852d5 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -127,7 +127,8 @@ void DomainGatekeeper::processConnectRequestPacket(QSharedPointer()->putLocalPortIntoSharedMemory(DOMAIN_SERVER_LOCAL_PORT_SMEM_KEY, this, localSockAddr.getPort()); + }); // register as the packet receiver for the types we want PacketReceiver& packetReceiver = nodeList->getPacketReceiver(); diff --git a/domain-server/src/NodeConnectionData.cpp b/domain-server/src/NodeConnectionData.cpp index b4aaacd749..9703db39c8 100644 --- a/domain-server/src/NodeConnectionData.cpp +++ b/domain-server/src/NodeConnectionData.cpp @@ -36,6 +36,9 @@ NodeConnectionData NodeConnectionData::fromDataStream(QDataStream& dataStream, c // now the machine fingerprint dataStream >> newHeader.machineFingerprint; + // and the operating system type + dataStream >> newHeader.SystemInfo; + dataStream >> newHeader.connectReason; dataStream >> newHeader.previousConnectionUpTime; diff --git a/domain-server/src/NodeConnectionData.h b/domain-server/src/NodeConnectionData.h index 23eceb0dca..3833f5afd3 100644 --- a/domain-server/src/NodeConnectionData.h +++ b/domain-server/src/NodeConnectionData.h @@ -31,9 +31,9 @@ public: QString placeName; QString hardwareAddress; QUuid machineFingerprint; + QString SystemInfo; quint32 connectReason; quint64 previousConnectionUpTime; - QByteArray protocolVersion; }; diff --git a/interface/resources/qml/AnimStats.qml b/interface/resources/qml/AnimStats.qml index d06a0484cb..70f2e37927 100644 --- a/interface/resources/qml/AnimStats.qml +++ b/interface/resources/qml/AnimStats.qml @@ -53,6 +53,9 @@ Item { StatText { text: root.recenterText } + StatText { + text: root.overrideJointText + } StatText { text: "Anim Vars:--------------------------------------------------------------------------------" } @@ -98,6 +101,9 @@ Item { StatText { text: root.sittingText } + StatText { + text: root.flowText + } StatText { text: "State Machines:---------------------------------------------------------------------------" } @@ -131,6 +137,9 @@ Item { StatText { text: root.walkingText } + StatText { + text: root.networkGraphText + } StatText { text: "Alpha Values:--------------------------------------------------------------------------" } diff --git a/interface/resources/qml/InteractiveWindow.qml b/interface/resources/qml/InteractiveWindow.qml index da1e9ec2bf..df3475ea7b 100644 --- a/interface/resources/qml/InteractiveWindow.qml +++ b/interface/resources/qml/InteractiveWindow.qml @@ -58,6 +58,7 @@ Windows.Window { } QmlSurface.load(source, contentHolder, function(newObject) { dynamicContent = newObject; + updateInteractiveWindowSizeForMode(); if (dynamicContent && dynamicContent.anchors) { dynamicContent.anchors.fill = contentHolder; } @@ -81,10 +82,12 @@ Windows.Window { } function updateInteractiveWindowSizeForMode() { - if (presentationMode === Desktop.PresentationMode.VIRTUAL) { - width = interactiveWindowSize.width; - height = interactiveWindowSize.height; - } else if (presentationMode === Desktop.PresentationMode.NATIVE && nativeWindow) { + root.width = interactiveWindowSize.width; + root.height = interactiveWindowSize.height; + contentHolder.width = interactiveWindowSize.width; + contentHolder.height = interactiveWindowSize.height; + + if (presentationMode === Desktop.PresentationMode.NATIVE && nativeWindow) { nativeWindow.width = interactiveWindowSize.width; nativeWindow.height = interactiveWindowSize.height; } @@ -134,6 +137,9 @@ Windows.Window { Window { id: root; + width: interactiveWindowSize.width + height: interactiveWindowSize.height + Rectangle { color: hifi.colors.baseGray anchors.fill: parent diff --git a/interface/resources/qml/controls/qmldir b/interface/resources/qml/controls/qmldir new file mode 100644 index 0000000000..6d7cf458cd --- /dev/null +++ b/interface/resources/qml/controls/qmldir @@ -0,0 +1,25 @@ +module controls +Button 1.0 Button.qml +ButtonAwesome 1.0 ButtonAwesome.qml +CheckBox 1.0 CheckBox.qml +ComboBox 1.0 ComboBox.qml +FlickableWebViewCore 1.0 FlickableWebViewCore.qml +FontAwesome 1.0 FontAwesome.qml +Player 1.0 Player.qml +RadioButton 1.0 RadioButton.qml +Slider 1.0 Slider.qml +Spacer 1.0 Spacer.qml +SpinBox 1.0 SpinBox.qml +TabletWebButton 1.0 TabletWebButton.qml +TabletWebScreen 1.0 TabletWebScreen.qml +TabletWebView 1.0 TabletWebView.qml +Text 1.0 Text.qml +TextAndSlider 1.0 TextAndSlider.qml +TextAndSpinBox 1.0 TextAndSpinBox.qml +TextArea 1.0 TextArea.qml +TextEdit 1.0 TextEdit.qml +TextField 1.0 TextField.qml +TextHeader 1.0 TextHeader.qml +TextInput 1.0 TextInput.qml +TextInputAndButton 1.0 TextInputAndButton.qml +WebView 1.0 WebView.qml \ No newline at end of file diff --git a/interface/resources/qml/hifi/dialogs/qmldir b/interface/resources/qml/hifi/dialogs/qmldir new file mode 100644 index 0000000000..4075738e33 --- /dev/null +++ b/interface/resources/qml/hifi/dialogs/qmldir @@ -0,0 +1,18 @@ +module dialogs +AboutDialog 1.0 AboutDialog.qml +AdvancedPreferencesDialog 1.0 AdvancedPreferencesDialog.qml +AudioBuffers 1.0 AudioBuffers.qml +AvatarPreferencesDialog 1.0 AvatarPreferencesDialog.qml +GeneralPreferencesDialog 1.0 GeneralPreferencesDialog.qml +LodPreferencesDialog 1.0 LodPreferencesDialog.qml +ModelBrowserDialog 1.0 ModelBrowserDialog.qml +NetworkingPreferencesDialog 1.0 NetworkingPreferencesDialog.qml +RunningScripts 1.0 RunningScripts.qml +TabletAboutDialog 1.0 TabletAboutDialog.qml +TabletAssetServer 1.0 TabletAssetServer.qml +TabletDCDialog 1.0 TabletDCDialog.qml +TabletDebugWindow 1.0 TabletDebugWindow.qml +TabletEntityStatistics 1.0 TabletEntityStatistics.qml +TabletEntityStatisticsItem 1.0 TabletEntityStatisticsItem.qml +TabletLODTools 1.0 TabletLODTools.qml +TabletRunningScripts 1.0 TabletRunningScripts.qml \ No newline at end of file diff --git a/interface/resources/qml/hifi/models/PSFListModel.qml b/interface/resources/qml/hifi/models/PSFListModel.qml index b9006bc57c..6dce3f185e 100644 --- a/interface/resources/qml/hifi/models/PSFListModel.qml +++ b/interface/resources/qml/hifi/models/PSFListModel.qml @@ -75,21 +75,31 @@ ListModel { // 1: equivalent to paging when reaching end (and not before). // 0: don't getNextPage on scroll at all here. The application code will do it. property real pageAhead: 2.0; - function needsEarlyYFetch() { - return flickable - && !flickable.atYBeginning - && (flickable.contentY - flickable.originY) >= (flickable.contentHeight - (pageAhead * flickable.height)); + function onContentXChanged() { + if (flickable && + !flickable.atXBeginning && + (flickable.contentX - flickable.originX) >= (flickable.contentWidth - (pageAhead * flickable.width))) { + getNextPage(); + } } - function needsEarlyXFetch() { - return flickable - && !flickable.atXBeginning - && (flickable.contentX - flickable.originX) >= (flickable.contentWidth - (pageAhead * flickable.width)); + function onContentYChanged() { + if (flickable && + !flickable.atYBeginning && + (flickable.contentY - flickable.originY) >= (flickable.contentHeight - (pageAhead * flickable.height))) { + getNextPage(); + } } - function getNextPageIfHorizontalScroll() { - if (needsEarlyXFetch()) { getNextPage(); } + function onWidthChanged() { + if (flickable && + (flickable.contentX - flickable.originX) >= (flickable.contentWidth - (pageAhead * flickable.width))) { + getNextPage(); + } } - function getNextPageIfVerticalScroll() { - if (needsEarlyYFetch()) { getNextPage(); } + function onHeightChanged() { + if (flickable && + (flickable.contentY - flickable.originY) >= (flickable.contentHeight - (pageAhead * flickable.height))) { + getNextPage(); + } } function needsMoreHorizontalResults() { return flickable @@ -118,8 +128,10 @@ ListModel { initialized = true; if (flickable && pageAhead > 0.0) { // Pun: Scrollers are usually one direction or another, such that only one of the following will actually fire. - flickable.contentXChanged.connect(getNextPageIfHorizontalScroll); - flickable.contentYChanged.connect(getNextPageIfVerticalScroll); + flickable.contentXChanged.connect(onContentXChanged); + flickable.contentYChanged.connect(onContentYChanged); + flickable.widthChanged.connect(onWidthChanged); + flickable.heightChanged.connect(onHeightChanged); flickable.contentWidthChanged.connect(getNextPageIfNotEnoughHorizontalResults); flickable.contentHeightChanged.connect(getNextPageIfNotEnoughVerticalResults); } diff --git a/interface/resources/qml/hifi/models/qmldir b/interface/resources/qml/hifi/models/qmldir new file mode 100644 index 0000000000..d4c78941e7 --- /dev/null +++ b/interface/resources/qml/hifi/models/qmldir @@ -0,0 +1,3 @@ +module hifiModels +PSFListModel 1.0 PSFListModel.qml +S3Model 1.0 S3Model.qml diff --git a/interface/resources/qml/hifi/simplifiedUI/avatarApp/AvatarApp.qml b/interface/resources/qml/hifi/simplifiedUI/avatarApp/AvatarApp.qml index ef9a3cbe24..d52dd3f3d7 100644 --- a/interface/resources/qml/hifi/simplifiedUI/avatarApp/AvatarApp.qml +++ b/interface/resources/qml/hifi/simplifiedUI/avatarApp/AvatarApp.qml @@ -10,6 +10,7 @@ import QtQuick 2.10 import "../simplifiedConstants" as SimplifiedConstants +import "../simplifiedControls" as SimplifiedControls import "./components" as AvatarAppComponents import stylesUit 1.0 as HifiStylesUit import TabletScriptingInterface 1.0 @@ -245,6 +246,10 @@ Rectangle { verticalAlignment: Text.AlignVCenter horizontalAlignment: Text.AlignHCenter } + + SimplifiedControls.VerticalScrollBar { + parent: inventoryContentsList + } } diff --git a/interface/resources/qml/hifi/simplifiedUI/avatarApp/components/DisplayNameHeader.qml b/interface/resources/qml/hifi/simplifiedUI/avatarApp/components/DisplayNameHeader.qml index a6be398e53..becbd8ca61 100644 --- a/interface/resources/qml/hifi/simplifiedUI/avatarApp/components/DisplayNameHeader.qml +++ b/interface/resources/qml/hifi/simplifiedUI/avatarApp/components/DisplayNameHeader.qml @@ -94,6 +94,7 @@ Item { text: MyAvatar.sessionDisplayName === "" ? MyAvatar.displayName : MyAvatar.sessionDisplayName maximumLength: 256 clip: true + selectByMouse: true anchors.fill: parent onEditingFinished: { if (MyAvatar.displayName !== text) { diff --git a/interface/resources/qml/hifi/simplifiedUI/settingsApp/SettingsApp.qml b/interface/resources/qml/hifi/simplifiedUI/settingsApp/SettingsApp.qml index 60703a8062..1f48d1d753 100644 --- a/interface/resources/qml/hifi/simplifiedUI/settingsApp/SettingsApp.qml +++ b/interface/resources/qml/hifi/simplifiedUI/settingsApp/SettingsApp.qml @@ -9,12 +9,15 @@ // import QtQuick 2.10 +import QtQuick.Controls 2.3 import "../simplifiedConstants" as SimplifiedConstants +import "../simplifiedControls" as SimplifiedControls import stylesUit 1.0 as HifiStylesUit import "./audio" as AudioSettings import "./general" as GeneralSettings import "./vr" as VrSettings import "./dev" as DevSettings +import "./about" as AboutSettings Rectangle { property string activeTabView: "generalTabView" @@ -74,6 +77,10 @@ Rectangle { tabTitle: "VR" tabViewName: "vrTabView" } + ListElement { + tabTitle: "About" + tabViewName: "aboutTabView" + } ListElement { tabTitle: "Dev" tabViewName: "devTabView" @@ -101,7 +108,7 @@ Rectangle { delegate: Item { visible: model.tabTitle !== "Dev" || (model.tabTitle === "Dev" && root.developerModeEnabled) - width: tabTitleText.paintedWidth + 64 + width: tabTitleText.paintedWidth + 32 height: parent.height HifiStylesUit.GraphikRegular { @@ -129,9 +136,7 @@ Rectangle { id: tabViewContainers anchors.top: tabContainer.bottom anchors.left: parent.left - anchors.leftMargin: 26 anchors.right: parent.right - anchors.rightMargin: 26 anchors.bottom: parent.bottom @@ -162,24 +167,28 @@ Rectangle { visible: activeTabView === "devTabView" anchors.fill: parent } - } - Image { - source: { - if (root.activeTabView === "generalTabView") { - "images/accent1.svg" - } else if (root.activeTabView === "audioTabView") { - "images/accent2.svg" - } else if (root.activeTabView === "vrTabView") { - "images/accent3.svg" - } else { - "images/accent3.svg" + AboutSettings.About { + id: aboutTabViewContainer + visible: activeTabView === "aboutTabView" + anchors.fill: parent + } + + SimplifiedControls.VerticalScrollBar { + parent: { + if (activeTabView === "generalTabView") { + generalTabViewContainer + } else if (activeTabView === "audioTabView") { + audioTabViewContainer + } else if (activeTabView === "vrTabView") { + vrTabViewContainer + } else if (activeTabView === "devTabView") { + devTabViewContainer + } else if (activeTabView === "aboutTabView") { + aboutTabViewContainer + } } } - anchors.right: parent.right - anchors.top: tabContainer.bottom - width: 106 - height: 200 } diff --git a/interface/resources/qml/hifi/simplifiedUI/settingsApp/about/About.qml b/interface/resources/qml/hifi/simplifiedUI/settingsApp/about/About.qml new file mode 100644 index 0000000000..76ab762a6b --- /dev/null +++ b/interface/resources/qml/hifi/simplifiedUI/settingsApp/about/About.qml @@ -0,0 +1,218 @@ +// +// About.qml +// +// Created by Zach Fox on 2019-06-18 +// Copyright 2019 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.10 +import QtQuick.Controls 2.3 +import "../../simplifiedConstants" as SimplifiedConstants +import "../../simplifiedControls" as SimplifiedControls +import stylesUit 1.0 as HifiStylesUit +import QtQuick.Layouts 1.3 + +Flickable { + id: root + contentWidth: parent.width + contentHeight: aboutColumnLayout.height + clip: true + + onVisibleChanged: { + if (visible) { + root.contentX = 0; + root.contentY = 0; + } + } + + + SimplifiedConstants.SimplifiedConstants { + id: simplifiedUI + } + + + ColumnLayout { + id: aboutColumnLayout + anchors.left: parent.left + anchors.leftMargin: 26 + anchors.right: parent.right + anchors.rightMargin: 26 + anchors.top: parent.top + spacing: 0 + + Image { + source: "images/logo.png" + Layout.alignment: Qt.AlignHCenter + Layout.topMargin: 16 + Layout.preferredWidth: 200 + Layout.preferredHeight: 150 + fillMode: Image.PreserveAspectFit + mipmap: true + } + + ColumnLayout { + id: platformInfoContainer + Layout.preferredWidth: parent.width + Layout.bottomMargin: 24 + spacing: 0 + + HifiStylesUit.GraphikSemiBold { + text: "Version " + Window.checkVersion() + Layout.alignment: Qt.AlignHCenter + Layout.maximumWidth: parent.width + height: paintedHeight + size: 16 + color: simplifiedUI.colors.text.white + wrapMode: Text.Wrap + } + + HifiStylesUit.GraphikSemiBold { + text: "Platform Info" + Layout.maximumWidth: parent.width + Layout.topMargin: 8 + Layout.bottomMargin: 8 + height: paintedHeight + size: 22 + color: simplifiedUI.colors.text.white + wrapMode: Text.Wrap + } + + HifiStylesUit.GraphikRegular { + text: "Computer Vendor/Model:" + Layout.maximumWidth: parent.width + height: paintedHeight + size: 16 + color: simplifiedUI.colors.text.white + wrapMode: Text.Wrap + + Component.onCompleted: { + var computer = JSON.parse(PlatformInfo.getComputer()); + var computerVendor = computer.vendor; + if (computerVendor.length === 0) { + computerVendor = "Unknown"; + } + var computerModel = computer.model; + if (computerModel.length === 0) { + computerModel = "Unknown"; + } + + text = "Computer Vendor/Model: " + computerVendor + "/" + computerModel; + } + } + + HifiStylesUit.GraphikRegular { + text: "Profiled Platform Tier: " + PlatformInfo.getTierProfiled() + Layout.maximumWidth: parent.width + height: paintedHeight + size: 16 + color: simplifiedUI.colors.text.white + wrapMode: Text.Wrap + } + + HifiStylesUit.GraphikRegular { + text: "OS Type: " + PlatformInfo.getOperatingSystemType() + Layout.maximumWidth: parent.width + height: paintedHeight + size: 16 + color: simplifiedUI.colors.text.white + wrapMode: Text.Wrap + } + + HifiStylesUit.GraphikRegular { + text: "CPU: " + PlatformInfo.getCPUBrand() + Layout.maximumWidth: parent.width + height: paintedHeight + size: 16 + color: simplifiedUI.colors.text.white + wrapMode: Text.Wrap + } + + HifiStylesUit.GraphikRegular { + text: "# CPUs: " + PlatformInfo.getNumCPUs() + Layout.maximumWidth: parent.width + height: paintedHeight + size: 16 + color: simplifiedUI.colors.text.white + wrapMode: Text.Wrap + } + + HifiStylesUit.GraphikRegular { + text: "# CPU Cores: " + PlatformInfo.getNumLogicalCores() + Layout.maximumWidth: parent.width + height: paintedHeight + size: 16 + color: simplifiedUI.colors.text.white + wrapMode: Text.Wrap + } + + HifiStylesUit.GraphikRegular { + text: "RAM: " + PlatformInfo.getTotalSystemMemoryMB() + " MB" + Layout.maximumWidth: parent.width + height: paintedHeight + size: 16 + color: simplifiedUI.colors.text.white + wrapMode: Text.Wrap + } + + HifiStylesUit.GraphikRegular { + text: "GPU: " + PlatformInfo.getGraphicsCardType() + Layout.maximumWidth: parent.width + height: paintedHeight + size: 16 + color: simplifiedUI.colors.text.white + wrapMode: Text.Wrap + } + + HifiStylesUit.GraphikRegular { + text: "VR Hand Controllers: " + (PlatformInfo.hasRiftControllers() ? "Rift" : (PlatformInfo.hasViveControllers() ? "Vive" : "None")) + Layout.maximumWidth: parent.width + height: paintedHeight + size: 16 + color: simplifiedUI.colors.text.white + wrapMode: Text.Wrap + } + + SimplifiedControls.Button { + Layout.topMargin: 8 + width: 200 + height: 32 + text: "Copy to Clipboard" + + onClicked: { + Window.copyToClipboard(root.buildPlatformInfoTextToCopy()); + } + } + } + } + + function buildPlatformInfoTextToCopy() { + var textToCopy = "**About Interface**\n"; + textToCopy += "Interface Version: " + Window.checkVersion() + "\n"; + textToCopy += "\n**Platform Info**\n"; + + var computer = JSON.parse(PlatformInfo.getComputer()); + var computerVendor = computer.vendor; + if (computerVendor.length === 0) { + computerVendor = "Unknown"; + } + var computerModel = computer.model; + if (computerModel.length === 0) { + computerModel = "Unknown"; + } + + textToCopy += "Computer Vendor/Model: " + computerVendor + "/" + computerModel + "\n"; + textToCopy += "Profiled Platform Tier: " + PlatformInfo.getTierProfiled() + "\n"; + textToCopy += "OS Type: " + PlatformInfo.getOperatingSystemType() + "\n"; + textToCopy += "CPU: " + PlatformInfo.getCPUBrand() + "\n"; + textToCopy += "# CPUs: " + PlatformInfo.getNumCPUs() + "\n"; + textToCopy += "# CPU Cores: " + PlatformInfo.getNumLogicalCores() + "\n"; + textToCopy += "RAM: " + PlatformInfo.getTotalSystemMemoryMB() + " MB\n"; + textToCopy += "GPU: " + PlatformInfo.getGraphicsCardType() + "\n"; + textToCopy += "VR Hand Controllers: " + (PlatformInfo.hasRiftControllers() ? "Rift" : (PlatformInfo.hasViveControllers() ? "Vive" : "None")); + + return textToCopy; + } +} diff --git a/interface/resources/qml/hifi/simplifiedUI/settingsApp/about/images/logo.png b/interface/resources/qml/hifi/simplifiedUI/settingsApp/about/images/logo.png new file mode 100644 index 0000000000..d480da86dd Binary files /dev/null and b/interface/resources/qml/hifi/simplifiedUI/settingsApp/about/images/logo.png differ diff --git a/interface/resources/qml/hifi/simplifiedUI/settingsApp/audio/Audio.qml b/interface/resources/qml/hifi/simplifiedUI/settingsApp/audio/Audio.qml index 4dc5e973fc..68a8fa49da 100644 --- a/interface/resources/qml/hifi/simplifiedUI/settingsApp/audio/Audio.qml +++ b/interface/resources/qml/hifi/simplifiedUI/settingsApp/audio/Audio.qml @@ -19,8 +19,6 @@ Flickable { id: root contentWidth: parent.width contentHeight: audioColumnLayout.height - topMargin: 24 - bottomMargin: 24 clip: true function changePeakValuesEnabled(enabled) { @@ -33,7 +31,7 @@ Flickable { AudioScriptingInterface.devices.input.peakValuesEnabled = visible; if (visible) { root.contentX = 0; - root.contentY = -root.topMargin; + root.contentY = 0; AudioScriptingInterface.devices.input.peakValuesEnabledChanged.connect(changePeakValuesEnabled); } else { AudioScriptingInterface.devices.input.peakValuesEnabledChanged.disconnect(changePeakValuesEnabled); @@ -45,16 +43,35 @@ Flickable { id: simplifiedUI } + + Image { + id: accent + source: "../images/accent2.svg" + anchors.left: parent.left + anchors.top: parent.top + width: 83 + height: 156 + transform: Scale { + xScale: -1 + origin.x: accent.width / 2 + origin.y: accent.height / 2 + } + } + + ColumnLayout { id: audioColumnLayout anchors.left: parent.left + anchors.leftMargin: 26 anchors.right: parent.right + anchors.rightMargin: 26 anchors.top: parent.top spacing: simplifiedUI.margins.settings.spacingBetweenSettings ColumnLayout { id: volumeControlsContainer Layout.preferredWidth: parent.width + Layout.topMargin: 24 spacing: 0 HifiStylesUit.GraphikSemiBold { @@ -289,6 +306,7 @@ Flickable { ColumnLayout { id: outputDeviceContainer Layout.preferredWidth: parent.width + Layout.bottomMargin: 24 spacing: 0 HifiStylesUit.GraphikSemiBold { diff --git a/interface/resources/qml/hifi/simplifiedUI/settingsApp/dev/Dev.qml b/interface/resources/qml/hifi/simplifiedUI/settingsApp/dev/Dev.qml index a8c0a8f158..fe623e5d78 100644 --- a/interface/resources/qml/hifi/simplifiedUI/settingsApp/dev/Dev.qml +++ b/interface/resources/qml/hifi/simplifiedUI/settingsApp/dev/Dev.qml @@ -19,14 +19,12 @@ Flickable { id: root contentWidth: parent.width contentHeight: devColumnLayout.height - topMargin: 24 - bottomMargin: 24 clip: true onVisibleChanged: { if (visible) { root.contentX = 0; - root.contentY = -root.topMargin; + root.contentY = 0; } } @@ -35,16 +33,36 @@ Flickable { id: simplifiedUI } + + Image { + id: accent + source: "../images/accent3.svg" + anchors.left: parent.left + anchors.top: parent.top + width: 83 + height: 156 + transform: Scale { + xScale: -1 + origin.x: accent.width / 2 + origin.y: accent.height / 2 + } + } + + ColumnLayout { id: devColumnLayout anchors.left: parent.left + anchors.leftMargin: 26 anchors.right: parent.right + anchors.rightMargin: 26 anchors.top: parent.top spacing: simplifiedUI.margins.settings.spacingBetweenSettings ColumnLayout { id: uiControlsContainer Layout.preferredWidth: parent.width + Layout.topMargin: 24 + Layout.bottomMargin: 24 spacing: 0 HifiStylesUit.GraphikSemiBold { diff --git a/interface/resources/qml/hifi/simplifiedUI/settingsApp/general/General.qml b/interface/resources/qml/hifi/simplifiedUI/settingsApp/general/General.qml index f56d0f33bd..af7e9ba4ae 100644 --- a/interface/resources/qml/hifi/simplifiedUI/settingsApp/general/General.qml +++ b/interface/resources/qml/hifi/simplifiedUI/settingsApp/general/General.qml @@ -9,6 +9,7 @@ // import QtQuick 2.10 +import QtQuick.Controls 2.3 import "../../simplifiedConstants" as SimplifiedConstants import "../../simplifiedControls" as SimplifiedControls import stylesUit 1.0 as HifiStylesUit @@ -20,8 +21,6 @@ Flickable { id: root contentWidth: parent.width contentHeight: generalColumnLayout.height - topMargin: 24 - bottomMargin: 24 clip: true onAvatarNametagModeChanged: { @@ -31,7 +30,7 @@ Flickable { onVisibleChanged: { if (visible) { root.contentX = 0; - root.contentY = -root.topMargin; + root.contentY = 0; } } @@ -39,16 +38,35 @@ Flickable { id: simplifiedUI } + + Image { + id: accent + source: "../images/accent1.svg" + anchors.left: parent.left + anchors.top: parent.top + width: 83 + height: 156 + transform: Scale { + xScale: -1 + origin.x: accent.width / 2 + origin.y: accent.height / 2 + } + } + + ColumnLayout { id: generalColumnLayout anchors.left: parent.left + anchors.leftMargin: 26 anchors.right: parent.right + anchors.rightMargin: 26 anchors.top: parent.top spacing: simplifiedUI.margins.settings.spacingBetweenSettings ColumnLayout { id: avatarNameTagsContainer Layout.preferredWidth: parent.width + Layout.topMargin: 24 spacing: 0 HifiStylesUit.GraphikSemiBold { @@ -193,29 +211,36 @@ Flickable { } } - HifiStylesUit.GraphikRegular { - id: logoutText - text: (AccountServices.username === "Unknown user" ? "Log In" : "Logout " + AccountServices.username) - wrapMode: Text.Wrap - width: paintedWidth - height: paintedHeight - size: 14 - color: simplifiedUI.colors.text.lightBlue + ColumnLayout { + id: logoutContainer + Layout.preferredWidth: parent.width + Layout.bottomMargin: 24 + spacing: 0 - MouseArea { - anchors.fill: parent - hoverEnabled: true - onEntered: { - parent.color = simplifiedUI.colors.text.lightBlueHover; - } - onExited: { - parent.color = simplifiedUI.colors.text.lightBlue; - } - onClicked: { - if (Account.loggedIn) { - AccountServices.logOut(); - } else { - DialogsManager.showLoginDialog(); + HifiStylesUit.GraphikRegular { + id: logoutText + text: (AccountServices.username === "Unknown user" ? "Log In" : "Logout " + AccountServices.username) + wrapMode: Text.Wrap + width: paintedWidth + height: paintedHeight + size: 14 + color: simplifiedUI.colors.text.lightBlue + + MouseArea { + anchors.fill: parent + hoverEnabled: true + onEntered: { + parent.color = simplifiedUI.colors.text.lightBlueHover; + } + onExited: { + parent.color = simplifiedUI.colors.text.lightBlue; + } + onClicked: { + if (Account.loggedIn) { + AccountServices.logOut(); + } else { + DialogsManager.showLoginDialog(); + } } } } diff --git a/interface/resources/qml/hifi/simplifiedUI/settingsApp/vr/VR.qml b/interface/resources/qml/hifi/simplifiedUI/settingsApp/vr/VR.qml index 9f5ed3ff8f..c1dc3888e2 100644 --- a/interface/resources/qml/hifi/simplifiedUI/settingsApp/vr/VR.qml +++ b/interface/resources/qml/hifi/simplifiedUI/settingsApp/vr/VR.qml @@ -19,8 +19,6 @@ Flickable { id: root contentWidth: parent.width contentHeight: vrColumnLayout.height - topMargin: 24 - bottomMargin: 24 clip: true function changePeakValuesEnabled(enabled) { @@ -33,7 +31,7 @@ Flickable { AudioScriptingInterface.devices.input.peakValuesEnabled = visible; if (visible) { root.contentX = 0; - root.contentY = -root.topMargin; + root.contentY = 0; AudioScriptingInterface.devices.input.peakValuesEnabledChanged.connect(changePeakValuesEnabled); } else { AudioScriptingInterface.devices.input.peakValuesEnabledChanged.disconnect(changePeakValuesEnabled); @@ -45,16 +43,35 @@ Flickable { id: simplifiedUI } + + Image { + id: accent + source: "../images/accent3.svg" + anchors.left: parent.left + anchors.top: parent.top + width: 83 + height: 156 + transform: Scale { + xScale: -1 + origin.x: accent.width / 2 + origin.y: accent.height / 2 + } + } + + ColumnLayout { id: vrColumnLayout anchors.left: parent.left + anchors.leftMargin: 26 anchors.right: parent.right + anchors.rightMargin: 26 anchors.top: parent.top spacing: simplifiedUI.margins.settings.spacingBetweenSettings ColumnLayout { id: controlsContainer Layout.preferredWidth: parent.width + Layout.topMargin: 24 spacing: 0 HifiStylesUit.GraphikSemiBold { @@ -278,6 +295,7 @@ Flickable { ColumnLayout { id: outputDeviceContainer Layout.preferredWidth: parent.width + Layout.bottomMargin: 24 spacing: 0 HifiStylesUit.GraphikSemiBold { diff --git a/interface/resources/qml/hifi/simplifiedUI/simplifiedConstants/SimplifiedConstants.qml b/interface/resources/qml/hifi/simplifiedUI/simplifiedConstants/SimplifiedConstants.qml index 5aa94d798e..fb09a7ae1d 100644 --- a/interface/resources/qml/hifi/simplifiedUI/simplifiedConstants/SimplifiedConstants.qml +++ b/interface/resources/qml/hifi/simplifiedUI/simplifiedConstants/SimplifiedConstants.qml @@ -144,6 +144,10 @@ QtObject { readonly property color hover: "#FFFFFF" readonly property color focus: "#FFFFFF" } + readonly property QtObject scrollBar: QtObject { + readonly property color background: "#474747" + readonly property color contentItem: "#0198CB" + } } readonly property color darkSeparator: "#595959" @@ -219,6 +223,10 @@ QtObject { readonly property QtObject textField: QtObject { readonly property int editPencilPadding: 6 } + readonly property QtObject scrollBar: QtObject { + readonly property int backgroundWidth: 9 + readonly property int contentItemWidth: 7 + } } } diff --git a/interface/resources/qml/hifi/simplifiedUI/simplifiedControls/VerticalScrollBar.qml b/interface/resources/qml/hifi/simplifiedUI/simplifiedControls/VerticalScrollBar.qml new file mode 100644 index 0000000000..ab3c0a3f72 --- /dev/null +++ b/interface/resources/qml/hifi/simplifiedUI/simplifiedControls/VerticalScrollBar.qml @@ -0,0 +1,51 @@ +// +// VerticalScrollBar.qml +// +// Created by Zach Fox on 2019-06-17 +// Copyright 2019 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.10 +import QtQuick.Controls 2.3 +import "../simplifiedConstants" as SimplifiedConstants + +ScrollBar { + SimplifiedConstants.SimplifiedConstants { + id: simplifiedUI + } + + orientation: Qt.Vertical + policy: ScrollBar.AlwaysOn + anchors.top: parent.top + anchors.topMargin: 4 + anchors.right: parent.right + anchors.rightMargin: 4 + anchors.bottom: parent.bottom + anchors.bottomMargin: 4 + width: simplifiedUI.sizes.controls.scrollBar.backgroundWidth + visible: parent.contentHeight > parent.parent.height + position: parent.contentY / parent.contentHeight + size: parent.parent.height / parent.contentHeight + minimumSize: 0.1 + background: Rectangle { + color: simplifiedUI.colors.controls.scrollBar.background + anchors.fill: parent + } + contentItem: Rectangle { + width: simplifiedUI.sizes.controls.scrollBar.contentItemWidth + color: simplifiedUI.colors.controls.scrollBar.contentItem + anchors { + horizontalCenter: parent.horizontalCenter + topMargin: 1 + bottomMargin: 1 + } + } + onPositionChanged: { + if (pressed) { + parent.contentY = position * parent.contentHeight; + } + } +} \ No newline at end of file diff --git a/interface/resources/qml/hifi/toolbars/qmldir b/interface/resources/qml/hifi/toolbars/qmldir new file mode 100644 index 0000000000..a27cdc0a4f --- /dev/null +++ b/interface/resources/qml/hifi/toolbars/qmldir @@ -0,0 +1,4 @@ +module toolbars +StateImage 1.0 StateImage.qml +Toolbar 1.0 Toolbar.qml +ToolbarButton 1.0 ToolbarButton.qml \ No newline at end of file diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index f9470782bf..b50ad8f595 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2775,7 +2775,6 @@ void Application::cleanupBeforeQuit() { // destroy Audio so it and its threads have a chance to go down safely // this must happen after QML, as there are unexplained audio crashes originating in qtwebengine - QMetaObject::invokeMethod(DependencyManager::get().data(), "stop"); DependencyManager::destroy(); DependencyManager::destroy(); @@ -2935,8 +2934,10 @@ void Application::initializeGL() { #if !defined(DISABLE_QML) QStringList chromiumFlags; + // HACK: re-expose mic and camera to prevent crash on domain-change in chromium's media::FakeAudioInputStream::ReadAudioFromSource() // Bug 21993: disable microphone and camera input - chromiumFlags << "--use-fake-device-for-media-stream"; + //chromiumFlags << "--use-fake-device-for-media-stream"; + // Disable signed distance field font rendering on ATI/AMD GPUs, due to // https://highfidelity.manuscript.com/f/cases/13677/Text-showing-up-white-on-Marketplace-app std::string vendor{ (const char*)glGetString(GL_VENDOR) }; diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index efe3d59d90..c88a934acf 100755 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -435,7 +435,7 @@ DetailedMotionState* AvatarManager::createDetailedMotionState(OtherAvatarPointer return nullptr; } -void AvatarManager::rebuildAvatarPhysics(PhysicsEngine::Transaction& transaction, OtherAvatarPointer avatar) { +void AvatarManager::rebuildAvatarPhysics(PhysicsEngine::Transaction& transaction, const OtherAvatarPointer& avatar) { if (!avatar->_motionState) { avatar->_motionState = new AvatarMotionState(avatar, nullptr); } @@ -452,20 +452,24 @@ void AvatarManager::rebuildAvatarPhysics(PhysicsEngine::Transaction& transaction transaction.objectsToAdd.push_back(motionState); } motionState->clearIncomingDirtyFlags(); +} - // Rather than reconcile numbers of joints after change to model or LOD - // we blow away old detailedMotionStates and create anew all around. - +void AvatarManager::removeDetailedAvatarPhysics(PhysicsEngine::Transaction& transaction, const OtherAvatarPointer& avatar) { // delete old detailedMotionStates auto& detailedMotionStates = avatar->getDetailedMotionStates(); if (detailedMotionStates.size() != 0) { for (auto& detailedMotionState : detailedMotionStates) { transaction.objectsToRemove.push_back(detailedMotionState); } - avatar->resetDetailedMotionStates(); + avatar->forgetDetailedMotionStates(); } +} - // build new detailedMotionStates +void AvatarManager::rebuildDetailedAvatarPhysics(PhysicsEngine::Transaction& transaction, const OtherAvatarPointer& avatar) { + // Rather than reconcile numbers of joints after change to model or LOD + // we blow away old detailedMotionStates and create anew all around. + removeDetailedAvatarPhysics(transaction, avatar); + auto& detailedMotionStates = avatar->getDetailedMotionStates(); OtherAvatar::BodyLOD lod = avatar->getBodyLOD(); if (lod == OtherAvatar::BodyLOD::Sphere) { auto dMotionState = createDetailedMotionState(avatar, -1); @@ -483,24 +487,21 @@ void AvatarManager::rebuildAvatarPhysics(PhysicsEngine::Transaction& transaction } } } - avatar->_needsReinsertion = false; + avatar->_needsDetailedRebuild = false; } void AvatarManager::buildPhysicsTransaction(PhysicsEngine::Transaction& transaction) { _myAvatar->getCharacterController()->buildPhysicsTransaction(transaction); for (auto avatar : _otherAvatarsToChangeInPhysics) { bool isInPhysics = avatar->isInPhysicsSimulation(); - if (isInPhysics != avatar->shouldBeInPhysicsSimulation() || avatar->_needsReinsertion) { + if (isInPhysics != avatar->shouldBeInPhysicsSimulation()) { if (isInPhysics) { transaction.objectsToRemove.push_back(avatar->_motionState); avatar->_motionState = nullptr; - auto& detailedMotionStates = avatar->getDetailedMotionStates(); - for (auto& motionState : detailedMotionStates) { - transaction.objectsToRemove.push_back(motionState); - } - avatar->resetDetailedMotionStates(); + removeDetailedAvatarPhysics(transaction, avatar); } else { rebuildAvatarPhysics(transaction, avatar); + rebuildDetailedAvatarPhysics(transaction, avatar); } } else if (isInPhysics) { AvatarMotionState* motionState = avatar->_motionState; @@ -519,6 +520,10 @@ void AvatarManager::buildPhysicsTransaction(PhysicsEngine::Transaction& transact } motionState->clearIncomingDirtyFlags(); } + + if (avatar->_needsDetailedRebuild) { + rebuildDetailedAvatarPhysics(transaction, avatar); + } } } _otherAvatarsToChangeInPhysics.clear(); diff --git a/interface/src/avatar/AvatarManager.h b/interface/src/avatar/AvatarManager.h index db1bc125a4..ce23a80309 100644 --- a/interface/src/avatar/AvatarManager.h +++ b/interface/src/avatar/AvatarManager.h @@ -274,7 +274,9 @@ public slots: protected: AvatarSharedPointer addAvatar(const QUuid& sessionUUID, const QWeakPointer& mixerWeakPointer) override; DetailedMotionState* createDetailedMotionState(OtherAvatarPointer avatar, int32_t jointIndex); - void rebuildAvatarPhysics(PhysicsEngine::Transaction& transaction, OtherAvatarPointer avatar); + void rebuildAvatarPhysics(PhysicsEngine::Transaction& transaction, const OtherAvatarPointer& avatar); + void removeDetailedAvatarPhysics(PhysicsEngine::Transaction& transaction, const OtherAvatarPointer& avatar); + void rebuildDetailedAvatarPhysics(PhysicsEngine::Transaction& transaction, const OtherAvatarPointer& avatar); private: explicit AvatarManager(QObject* parent = 0); diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 3800a330bb..cce2af466d 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -1416,6 +1416,10 @@ void MyAvatar::setEnableDebugDrawAnimPose(bool isEnabled) { } } +void MyAvatar::setDebugDrawAnimPoseName(QString poseName) { + _debugDrawAnimPoseName.set(poseName); +} + void MyAvatar::setEnableDebugDrawPosition(bool isEnabled) { if (isEnabled) { const glm::vec4 red(1.0f, 0.0f, 0.0f, 1.0f); @@ -3086,15 +3090,26 @@ void MyAvatar::postUpdate(float deltaTime, const render::ScenePointer& scene) { } if (_enableDebugDrawAnimPose && animSkeleton) { - // build absolute AnimPoseVec from rig + AnimPoseVec absPoses; const Rig& rig = _skeletonModel->getRig(); - absPoses.reserve(rig.getJointStateCount()); - for (int i = 0; i < rig.getJointStateCount(); i++) { - absPoses.push_back(AnimPose(rig.getJointTransform(i))); + const glm::vec4 CYAN(0.1f, 0.6f, 0.6f, 1.0f); + + QString name = _debugDrawAnimPoseName.get(); + if (name.isEmpty()) { + // build absolute AnimPoseVec from rig transforms. i.e. the same that are used for rendering. + absPoses.reserve(rig.getJointStateCount()); + for (int i = 0; i < rig.getJointStateCount(); i++) { + absPoses.push_back(AnimPose(rig.getJointTransform(i))); + } + AnimDebugDraw::getInstance().addAbsolutePoses("myAvatarAnimPoses", animSkeleton, absPoses, xform, CYAN); + } else { + AnimNode::ConstPointer node = rig.findAnimNodeByName(name); + if (node) { + rig.buildAbsoluteRigPoses(node->getPoses(), absPoses); + AnimDebugDraw::getInstance().addAbsolutePoses("myAvatarAnimPoses", animSkeleton, absPoses, xform, CYAN); + } } - glm::vec4 cyan(0.1f, 0.6f, 0.6f, 1.0f); - AnimDebugDraw::getInstance().addAbsolutePoses("myAvatarAnimPoses", animSkeleton, absPoses, xform, cyan); } } @@ -6085,6 +6100,30 @@ QVariantList MyAvatar::getCollidingFlowJoints() { return result; } +int MyAvatar::getOverrideJointCount() const { + if (_skeletonModel) { + return _skeletonModel->getRig().getOverrideJointCount(); + } else { + return 0; + } +} + +bool MyAvatar::getFlowActive() const { + if (_skeletonModel) { + return _skeletonModel->getRig().getFlowActive(); + } else { + return false; + } +} + +bool MyAvatar::getNetworkGraphActive() const { + if (_skeletonModel) { + return _skeletonModel->getRig().getNetworkGraphActive(); + } else { + return false; + } +} + void MyAvatar::initFlowFromFST() { if (_skeletonModel->isLoaded()) { auto &flowData = _skeletonModel->getHFMModel().flowData; diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index d60c023caa..d092122863 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -267,7 +267,7 @@ class MyAvatar : public Avatar { * @property {number} analogPlusWalkSpeed - The walk speed of your avatar for the "AnalogPlus" control scheme. *

Warning: Setting this value also sets the value of analogPlusSprintSpeed to twice * the value.

- * @property {number} analogPlusSprintSpeed - The sprint speed of your avatar for the "AnalogPlus" control scheme. + * @property {number} analogPlusSprintSpeed - The sprint (run) speed of your avatar for the "AnalogPlus" control scheme. * @property {MyAvatar.SitStandModelType} userRecenterModel - Controls avatar leaning and recentering behavior. * @property {number} isInSittingState - true if your avatar is sitting (avatar leaning is disabled, * recenntering is enabled), false if it is standing (avatar leaning is enabled, and avatar recenters if it @@ -1835,6 +1835,10 @@ public: */ Q_INVOKABLE QVariantList getCollidingFlowJoints(); + int getOverrideJointCount() const; + bool getFlowActive() const; + bool getNetworkGraphActive() const; + public slots: /**jsdoc @@ -2018,12 +2022,20 @@ public slots: void setEnableDebugDrawDefaultPose(bool isEnabled); /**jsdoc - * Displays animation debug graphics. + * Displays animation debug graphics. By default it shows the animation poses used for rendering. + * However, the property MyAvatar.setDebugDrawAnimPoseName can be used to draw a specific animation node. * @function MyAvatar.setEnableDebugDrawAnimPose * @param {boolean} enabled - true to show the debug graphics, false to hide. */ void setEnableDebugDrawAnimPose(bool isEnabled); + /**jsdoc + * If set it determines which animation debug graphics to draw, when MyAvatar.setEnableDebugDrawAnimPose is set to true. + * @function MyAvatar.setDebugDrawAnimPoseName + * @param {boolean} enabled - true to show the debug graphics, false to hide. + */ + void setDebugDrawAnimPoseName(QString poseName); + /**jsdoc * Displays position debug graphics. * @function MyAvatar.setEnableDebugDrawPosition @@ -2190,33 +2202,35 @@ signals: void audioListenerModeChanged(); /**jsdoc - * Notifies when the analogPlusWalkSpeed value is changed. + * Triggered when the walk speed set for the "AnalogPlus" control scheme changes. * @function MyAvatar.analogPlusWalkSpeedChanged - * @param {float} value - the new avatar walk speed + * @param {number} speed - The new walk speed set for the "AnalogPlus" control scheme. * @returns {Signal} */ void analogPlusWalkSpeedChanged(float value); /**jsdoc - * Notifies when the analogPlusSprintSpeed value is changed. + * Triggered when the sprint (run) speed set for the "AnalogPlus" control scheme changes. * @function MyAvatar.analogPlusSprintSpeedChanged - * @param {float} value - the new avatar sprint speed + * @param {number} speed - The new sprint speed set for the "AnalogPlus" control scheme. * @returns {Signal} */ void analogPlusSprintSpeedChanged(float value); /**jsdoc - * Notifies when the sprintSpeed value is changed. + * Triggered when the sprint (run) speed set for the current control scheme (see + * {@link MyAvatar.getControlScheme|getControlScheme}) changes. * @function MyAvatar.sprintSpeedChanged - * @param {float} value - the new avatar sprint speed + * @param {number} speed -The new sprint speed set for the current control scheme. * @returns {Signal} */ void sprintSpeedChanged(float value); /**jsdoc - * Notifies when the walkBackwardSpeed value is changed. + * Triggered when the walk backward speed set for the current control scheme (see + * {@link MyAvatar.getControlScheme|getControlScheme}) changes. * @function MyAvatar.walkBackwardSpeedChanged - * @param {float} value - the new avatar walk backward speed + * @param {number} speed - The new walk backward speed set for the current control scheme. * @returns {Signal} */ void walkBackwardSpeedChanged(float value); @@ -2666,6 +2680,8 @@ private: bool _enableDebugDrawIKChains { false }; bool _enableDebugDrawDetailedCollision { false }; + ThreadSafeValueCache _debugDrawAnimPoseName; + mutable bool _cauterizationNeedsUpdate { false }; // do we need to scan children and update their "cauterized" state? AudioListenerMode _audioListenerMode; diff --git a/interface/src/avatar/MyCharacterController.cpp b/interface/src/avatar/MyCharacterController.cpp index aef1bcd668..2f59b70592 100755 --- a/interface/src/avatar/MyCharacterController.cpp +++ b/interface/src/avatar/MyCharacterController.cpp @@ -398,15 +398,13 @@ DetailedMotionState* MyCharacterController::createDetailedMotionStateForJoint(in } void MyCharacterController::clearDetailedMotionStates() { + // we don't actually clear the MotionStates here + // instead we twiddle some flags as a signal of what to do later _pendingFlags |= PENDING_FLAG_REMOVE_DETAILED_FROM_SIMULATION; // We make sure we don't add them again _pendingFlags &= ~PENDING_FLAG_ADD_DETAILED_TO_SIMULATION; } -void MyCharacterController::resetDetailedMotionStates() { - _detailedMotionStates.clear(); -} - void MyCharacterController::buildPhysicsTransaction(PhysicsEngine::Transaction& transaction) { for (size_t i = 0; i < _detailedMotionStates.size(); i++) { _detailedMotionStates[i]->forceActive(); @@ -416,6 +414,8 @@ void MyCharacterController::buildPhysicsTransaction(PhysicsEngine::Transaction& for (size_t i = 0; i < _detailedMotionStates.size(); i++) { transaction.objectsToRemove.push_back(_detailedMotionStates[i]); } + // NOTE: the DetailedMotionStates are deleted after being added to PhysicsEngine::Transaction::_objectsToRemove + // See AvatarManager::handleProcessedPhysicsTransaction() _detailedMotionStates.clear(); } if (_pendingFlags & PENDING_FLAG_ADD_DETAILED_TO_SIMULATION) { diff --git a/interface/src/avatar/MyCharacterController.h b/interface/src/avatar/MyCharacterController.h index 0b64f66850..7ddcf94f67 100644 --- a/interface/src/avatar/MyCharacterController.h +++ b/interface/src/avatar/MyCharacterController.h @@ -48,7 +48,6 @@ public: DetailedMotionState* createDetailedMotionStateForJoint(int32_t jointIndex); std::vector& getDetailedMotionStates() { return _detailedMotionStates; } void clearDetailedMotionStates(); - void resetDetailedMotionStates(); void buildPhysicsTransaction(PhysicsEngine::Transaction& transaction); diff --git a/interface/src/avatar/OtherAvatar.cpp b/interface/src/avatar/OtherAvatar.cpp index 6f83c61cd6..a6e2d6a998 100755 --- a/interface/src/avatar/OtherAvatar.cpp +++ b/interface/src/avatar/OtherAvatar.cpp @@ -177,7 +177,7 @@ const btCollisionShape* OtherAvatar::createCollisionShape(int32_t jointIndex, bo return ObjectMotionState::getShapeManager()->getShape(shapeInfo); } -void OtherAvatar::resetDetailedMotionStates() { +void OtherAvatar::forgetDetailedMotionStates() { // NOTE: the DetailedMotionStates are deleted after being added to PhysicsEngine::Transaction::_objectsToRemove // See AvatarManager::handleProcessedPhysicsTransaction() _detailedMotionStates.clear(); @@ -209,7 +209,7 @@ void OtherAvatar::computeShapeLOD() { if (newLOD != _bodyLOD) { _bodyLOD = newLOD; if (isInPhysicsSimulation()) { - _needsReinsertion = true; + _needsDetailedRebuild = true; } } } @@ -224,14 +224,14 @@ bool OtherAvatar::shouldBeInPhysicsSimulation() const { bool OtherAvatar::needsPhysicsUpdate() const { constexpr uint32_t FLAGS_OF_INTEREST = Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS | Simulation::DIRTY_POSITION | Simulation::DIRTY_COLLISION_GROUP; - return (_needsReinsertion || (_motionState && (bool)(_motionState->getIncomingDirtyFlags() & FLAGS_OF_INTEREST))); + return (_needsDetailedRebuild || (_motionState && (bool)(_motionState->getIncomingDirtyFlags() & FLAGS_OF_INTEREST))); } void OtherAvatar::rebuildCollisionShape() { if (_motionState) { // do not actually rebuild here, instead flag for later _motionState->addDirtyFlags(Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS); - _needsReinsertion = true; + _needsDetailedRebuild = true; } } diff --git a/interface/src/avatar/OtherAvatar.h b/interface/src/avatar/OtherAvatar.h index 498971d6ee..cfe0c8332d 100644 --- a/interface/src/avatar/OtherAvatar.h +++ b/interface/src/avatar/OtherAvatar.h @@ -54,7 +54,7 @@ public: const btCollisionShape* createCollisionShape(int32_t jointIndex, bool& isBound, std::vector& boundJoints); std::vector& getDetailedMotionStates() { return _detailedMotionStates; } - void resetDetailedMotionStates(); + void forgetDetailedMotionStates(); BodyLOD getBodyLOD() { return _bodyLOD; } void computeShapeLOD(); @@ -90,7 +90,7 @@ protected: int32_t _spaceIndex { -1 }; uint8_t _workloadRegion { workload::Region::INVALID }; BodyLOD _bodyLOD { BodyLOD::Sphere }; - bool _needsReinsertion { false }; + bool _needsDetailedRebuild { false }; }; using OtherAvatarPointer = std::shared_ptr; diff --git a/interface/src/scripting/PlatformInfoScriptingInterface.cpp b/interface/src/scripting/PlatformInfoScriptingInterface.cpp index 9a5a08503d..84c4d923d0 100644 --- a/interface/src/scripting/PlatformInfoScriptingInterface.cpp +++ b/interface/src/scripting/PlatformInfoScriptingInterface.cpp @@ -192,7 +192,7 @@ QString PlatformInfoScriptingInterface::getDisplay(int index) { } QString PlatformInfoScriptingInterface::getMemory() { - auto desc = platform::getMemory(0); + auto desc = platform::getMemory(); return QString(desc.dump().c_str()); } @@ -201,6 +201,10 @@ QString PlatformInfoScriptingInterface::getComputer() { return QString(desc.dump().c_str()); } +QString PlatformInfoScriptingInterface::getPlatform() { + auto desc = platform::getAll(); + return QString(desc.dump().c_str()); +} PlatformInfoScriptingInterface::PlatformTier PlatformInfoScriptingInterface::getTierProfiled() { return (PlatformInfoScriptingInterface::PlatformTier) platform::Profiler::profilePlatform(); diff --git a/interface/src/scripting/PlatformInfoScriptingInterface.h b/interface/src/scripting/PlatformInfoScriptingInterface.h index 476c5c5788..113509d6d9 100644 --- a/interface/src/scripting/PlatformInfoScriptingInterface.h +++ b/interface/src/scripting/PlatformInfoScriptingInterface.h @@ -51,6 +51,8 @@ public slots: * Gets the operating system type. * @function PlatformInfo.getOperatingSystemType * @returns {string} "WINDOWS", "MACOS", or "UNKNOWN". + * @deprecated This function is deprecated and will be removed. + * use getComputer()["OS"] instead */ QString getOperatingSystemType(); @@ -61,6 +63,10 @@ public slots: * @example Report the CPU being used. * print("CPU: " + PlatformInfo.getCPUBrand()); * // Example: Intel(R) Core(TM) i7-7820HK CPU @ 2.90GHz + * @deprecated This function is deprecated and will be removed. + * use getNumCPUs() to know the number of CPUs in the hardware, at least one is expected + * use getCPU(0)["vendor"] to get the brand of the vendor + * use getCPU(0)["model"] to get the model name of the cpu */ QString getCPUBrand(); @@ -68,6 +74,8 @@ public slots: * Gets the number of logical CPU cores. * @function PlatformInfo.getNumLogicalCores * @returns {number} The number of logical CPU cores. + * @deprecated This function is deprecated and will be removed. + * use getCPU(0)["numCores"] instead */ unsigned int getNumLogicalCores(); @@ -75,6 +83,8 @@ public slots: * Returns the total system memory in megabytes. * @function PlatformInfo.getTotalSystemMemoryMB * @returns {number} The total system memory in megabytes. + * @deprecated This function is deprecated and will be removed. + * use getMemory()["memTotal"] instead */ int getTotalSystemMemoryMB(); @@ -82,6 +92,10 @@ public slots: * Gets the graphics card type. * @function PlatformInfo.getGraphicsCardType * @returns {string} The graphics card type. + * @deprecated This function is deprecated and will be removed. + * use getNumGPUs() to know the number of GPUs in the hardware, at least one is expected + * use getGPU(0)["vendor"] to get the brand of the vendor + * use getGPU(0)["model"] to get the model name of the gpu */ QString getGraphicsCardType(); @@ -141,7 +155,7 @@ public slots: /**jsdoc * Get the description of the GPU at the index parameter * expected fields are: - * - gpuVendor... + * - vendor, model... * @param index The index of the GPU of the platform * @function PlatformInfo.getGPU * @returns {string} The GPU description json field @@ -183,6 +197,14 @@ public slots: */ QString getComputer(); + /**jsdoc + * Get the complete description of the Platform as an aggregated Json + * The expected object description is: + * { "computer": {...}, "memory": {...}, "cpus": [{...}, ...], "gpus": [{...}, ...], "displays": [{...}, ...] } + * @function PlatformInfo.getPlatform + * @returns {string} The Platform description json field + */ + QString getPlatform(); /**jsdoc * Get the Platform TIer profiled on startup of the Computer diff --git a/interface/src/ui/AnimStats.cpp b/interface/src/ui/AnimStats.cpp index 6317c069f4..d4696a8c04 100644 --- a/interface/src/ui/AnimStats.cpp +++ b/interface/src/ui/AnimStats.cpp @@ -94,6 +94,21 @@ void AnimStats::updateStats(bool force) { } emit walkingTextChanged(); + // print current overrideJointText + int overrideJointCount = myAvatar->getOverrideJointCount(); + _overrideJointText = QString("Override Joint Count: %1").arg(overrideJointCount); + emit overrideJointTextChanged(); + + // print current flowText + bool flowActive = myAvatar->getFlowActive(); + _flowText = QString("Flow: %1").arg(flowActive ? "enabled" : "disabled"); + emit flowTextChanged(); + + // print current networkGraphText + bool networkGraphActive = myAvatar->getNetworkGraphActive(); + _networkGraphText = QString("Network Graph: %1").arg(networkGraphActive ? "enabled" : "disabled"); + emit networkGraphTextChanged(); + // update animation debug alpha values QStringList newAnimAlphaValues; qint64 now = usecTimestampNow(); diff --git a/interface/src/ui/AnimStats.h b/interface/src/ui/AnimStats.h index 57d2a4f1a6..ccd7187d9b 100644 --- a/interface/src/ui/AnimStats.h +++ b/interface/src/ui/AnimStats.h @@ -25,6 +25,9 @@ class AnimStats : public QQuickItem { Q_PROPERTY(QString recenterText READ recenterText NOTIFY recenterTextChanged) Q_PROPERTY(QString sittingText READ sittingText NOTIFY sittingTextChanged) Q_PROPERTY(QString walkingText READ walkingText NOTIFY walkingTextChanged) + Q_PROPERTY(QString overrideJointText READ overrideJointText NOTIFY overrideJointTextChanged) + Q_PROPERTY(QString flowText READ flowText NOTIFY flowTextChanged) + Q_PROPERTY(QString networkGraphText READ networkGraphText NOTIFY networkGraphTextChanged) public: static AnimStats* getInstance(); @@ -43,6 +46,9 @@ public: QString recenterText() const { return _recenterText; } QString sittingText() const { return _sittingText; } QString walkingText() const { return _walkingText; } + QString overrideJointText() const { return _overrideJointText; } + QString flowText() const { return _flowText; } + QString networkGraphText() const { return _networkGraphText; } public slots: void forceUpdateStats() { updateStats(true); } @@ -58,6 +64,9 @@ signals: void recenterTextChanged(); void sittingTextChanged(); void walkingTextChanged(); + void overrideJointTextChanged(); + void flowTextChanged(); + void networkGraphTextChanged(); private: QStringList _animAlphaValues; @@ -76,6 +85,9 @@ private: QString _recenterText; QString _sittingText; QString _walkingText; + QString _overrideJointText; + QString _flowText; + QString _networkGraphText; }; #endif // hifi_AnimStats_h diff --git a/launchers/darwin/CMakeLists.txt b/launchers/darwin/CMakeLists.txt index 2de43d93cb..4da675fcc9 100644 --- a/launchers/darwin/CMakeLists.txt +++ b/launchers/darwin/CMakeLists.txt @@ -64,7 +64,8 @@ function(set_from_env _RESULT_NAME _ENV_VAR_NAME _DEFAULT_VALUE) endfunction() add_executable(${PROJECT_NAME} MACOSX_BUNDLE ${src_files}) -set_target_properties(${PROJECT_NAME} PROPERTIES OUTPUT_NAME ${APP_NAME}) +set_target_properties(${PROJECT_NAME} PROPERTIES OUTPUT_NAME ${APP_NAME} + MACOSX_BUNDLE_BUNDLE_NAME ${APP_NAME}) set_from_env(LAUNCHER_HMAC_SECRET LAUNCHER_HMAC_SECRET "") if (LAUNCHER_HMAC_SECRET STREQUAL "") message(FATAL_ERROR "LAUNCHER_HMAC_SECRET is not set") diff --git a/launchers/darwin/cmake/modules/MacOSXBundleInfo.plist.in b/launchers/darwin/cmake/modules/MacOSXBundleInfo.plist.in index 62d6856ba9..4c87bff3cf 100644 --- a/launchers/darwin/cmake/modules/MacOSXBundleInfo.plist.in +++ b/launchers/darwin/cmake/modules/MacOSXBundleInfo.plist.in @@ -5,7 +5,7 @@ CFBundleDevelopmentRegion English CFBundleExecutable - ${EXECUTABLE_NAME} + ${APP_NAME} CFBundleIconFile ${MACOSX_BUNDLE_ICON_FILE} CFBundleIdentifier @@ -29,7 +29,9 @@ Window NSPrincipalClass NSApplication + CFBundleName + ${MACOSX_BUNDLE_BUNDLE_NAME} CFBundleDisplayName - HQ Launcher + CFBundleName diff --git a/launchers/darwin/src/LatestBuildRequest.m b/launchers/darwin/src/LatestBuildRequest.m index 019637ed55..5119efa8f6 100644 --- a/launchers/darwin/src/LatestBuildRequest.m +++ b/launchers/darwin/src/LatestBuildRequest.m @@ -14,16 +14,26 @@ NSURLSession* session = [NSURLSession sharedSession]; NSURLSessionDataTask* dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { + + NSLog(@"Latest Build Request error: %@", error); + NSLog(@"Latest Build Request Data: %@", data); + NSHTTPURLResponse *ne = (NSHTTPURLResponse *)response; + NSLog(@"Latest Build Request Response: %ld", [ne statusCode]); Launcher* sharedLauncher = [Launcher sharedLauncher]; - NSLog(@"credentials request finished"); NSMutableData* webData = [NSMutableData data]; [webData appendData:data]; NSString* jsonString = [[NSString alloc] initWithBytes: [webData mutableBytes] length:[data length] encoding:NSUTF8StringEncoding]; NSData *jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding]; - id json = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:nil]; + NSLog(@"Latest Build Request -> json string: %@", jsonString); + NSError *jsonError = nil; + id json = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:&jsonError]; + + if (jsonError) { + NSLog(@"Latest Build request: Failed to convert Json to data"); + } NSFileManager* fileManager = [NSFileManager defaultManager]; - NSArray *values = [json valueForKey:@"results"]; + NSArray *values = [json valueForKey:@"results"]; NSDictionary *value = [values objectAtIndex:0]; @@ -37,10 +47,15 @@ dispatch_async(dispatch_get_main_queue(), ^{ Settings* settings = [Settings sharedSettings]; NSInteger currentVersion = [settings latestBuildVersion]; + NSLog(@"Latest Build Request -> does build directory exist: %@", appDirectoryExist ? @"TRUE" : @"FALSE"); + NSLog(@"Latest Build Request -> current version: %ld", currentVersion); + NSLog(@"Latest Build Request -> latest version: %ld", buildNumber.integerValue); + NSLog(@"Latest Build Request -> mac url: %@", macInstallerUrl); BOOL latestVersionAvailable = (currentVersion != buildNumber.integerValue); [[Settings sharedSettings] buildVersion:buildNumber.integerValue]; BOOL shouldDownloadInterface = (latestVersionAvailable || !appDirectoryExist); + NSLog(@"Latest Build Request -> SHOULD DOWNLOAD: %@", shouldDownloadInterface ? @"TRUE" : @"FALSE"); [sharedLauncher shouldDownloadLatestBuild:shouldDownloadInterface :macInstallerUrl]; }); }]; diff --git a/launchers/darwin/src/ProcessScreen.m b/launchers/darwin/src/ProcessScreen.m index fb1de799da..4aeb8abda1 100644 --- a/launchers/darwin/src/ProcessScreen.m +++ b/launchers/darwin/src/ProcessScreen.m @@ -25,7 +25,7 @@ break; case CHECKING_UPDATE: [self.boldStatus setStringValue:@"Getting updates..."]; - [self.smallStatus setStringValue:@"We're getting the lastest and greatest for you, one sec."]; + [self.smallStatus setStringValue:@"We're getting the latest and greatest for you, one sec."]; break; case RUNNING_INTERFACE_AFTER_UPDATE: [self.boldStatus setStringValue:@"You're good to go!"]; diff --git a/launchers/win32/LauncherDlg.cpp b/launchers/win32/LauncherDlg.cpp index d3cae39013..98c61794a0 100644 --- a/launchers/win32/LauncherDlg.cpp +++ b/launchers/win32/LauncherDlg.cpp @@ -213,6 +213,9 @@ BOOL CLauncherDlg::getHQInfo(const CString& orgname) { CString lowerOrgName = orgname; lowerOrgName.MakeLower(); LauncherUtils::hMac256(lowerOrgName, LAUNCHER_HMAC_SECRET, hash); + CString msg; + msg.Format(_T("Calculated hash: \"%s\" => \"%s\""), lowerOrgName, hash); + theApp._manager.addToLog(msg); return theApp._manager.readOrganizationJSON(hash) == LauncherUtils::ResponseError::NoError; } diff --git a/launchers/win32/LauncherManager.cpp b/launchers/win32/LauncherManager.cpp index fc79287457..47c84f1124 100644 --- a/launchers/win32/LauncherManager.cpp +++ b/launchers/win32/LauncherManager.cpp @@ -427,6 +427,11 @@ BOOL LauncherManager::extractApplication() { LauncherUtils::cStringToStd(installPath), [&](int type, int size) { onZipExtracted((ZipType)type, size); }); + if (success) { + addToLog(_T("Created thread for unzipping application.")); + } else { + addToLog(_T("Failed to create thread for unzipping application.")); + } return success; } @@ -449,6 +454,11 @@ BOOL LauncherManager::installContent() { LauncherUtils::cStringToStd(contentPath), [&](int type, int size) { onZipExtracted((ZipType)type, size); }); + if (success) { + addToLog(_T("Created thread for unzipping content.")); + } else { + addToLog(_T("Failed to create thread for unzipping content.")); + } return success; } diff --git a/launchers/win32/LauncherUtils.cpp b/launchers/win32/LauncherUtils.cpp index cfb8b765c5..3ffbd37c58 100644 --- a/launchers/win32/LauncherUtils.cpp +++ b/launchers/win32/LauncherUtils.cpp @@ -16,6 +16,7 @@ #pragma comment(lib, "winhttp") +#include "LauncherApp.h" #include "LauncherUtils.h" CString LauncherUtils::urlEncodeString(const CString& url) { @@ -183,6 +184,17 @@ LauncherUtils::ResponseError LauncherUtils::makeHTTPCall(const CString& callerNa BOOL haveContentLength = WinHttpQueryHeaders(hrequest, WINHTTP_QUERY_CONTENT_LENGTH, NULL, &szContentLength, &bufferBytes, &dwHeaderIndex); + DWORD statusCode; + DWORD statusCodeSize = sizeof(statusCode); + WinHttpQueryHeaders(hrequest, + WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER, + NULL, + &statusCode, &statusCodeSize, WINHTTP_NO_HEADER_INDEX); + + CString msg; + msg.Format(_T("Status code response (%s%s): %lu"), mainUrl, dirUrl, statusCode); + theApp._manager.addToLog(msg); + DWORD dwContentLength; if (haveContentLength) { dwContentLength = _wtoi(szContentLength); @@ -225,19 +237,37 @@ BOOL LauncherUtils::getFont(const CString& fontName, int fontSize, bool isBold, } uint64_t LauncherUtils::extractZip(const std::string& zipFile, const std::string& path, std::vector& files) { + { + CString msg; + msg.Format(_T("Reading zip file %s, extracting to %s"), CString(zipFile.c_str()), CString(path.c_str())); + theApp._manager.addToLog(msg); + } + mz_zip_archive zip_archive; memset(&zip_archive, 0, sizeof(zip_archive)); auto status = mz_zip_reader_init_file(&zip_archive, zipFile.c_str(), 0); - if (!status) return 0; + if (!status) { + auto zip_error = mz_zip_get_last_error(&zip_archive); + auto zip_error_msg = mz_zip_get_error_string(zip_error); + CString msg; + msg.Format(_T("Failed to initialize miniz: %d %s"), zip_error, CString(zip_error_msg)); + theApp._manager.addToLog(msg); + return 0; + } + int fileCount = (int)mz_zip_reader_get_num_files(&zip_archive); if (fileCount == 0) { + theApp._manager.addToLog(_T("Zip archive has a file count of 0")); + mz_zip_reader_end(&zip_archive); return 0; } mz_zip_archive_file_stat file_stat; if (!mz_zip_reader_file_stat(&zip_archive, 0, &file_stat)) { + theApp._manager.addToLog(_T("Zip archive cannot be stat'd")); + mz_zip_reader_end(&zip_archive); return 0; } @@ -263,6 +293,12 @@ uint64_t LauncherUtils::extractZip(const std::string& zipFile, const std::string } } + { + CString msg; + msg.Format(_T("Done unzipping archive, total size: %llu"), totalSize); + theApp._manager.addToLog(msg); + } + // Close the archive, freeing any resources it was using mz_zip_reader_end(&zip_archive); return totalSize; diff --git a/libraries/animation/src/AnimClip.cpp b/libraries/animation/src/AnimClip.cpp index 1a922e507d..e5edb46f69 100644 --- a/libraries/animation/src/AnimClip.cpp +++ b/libraries/animation/src/AnimClip.cpp @@ -10,10 +10,13 @@ #include "AnimClip.h" +#include + #include "GLMHelpers.h" #include "AnimationLogging.h" #include "AnimUtil.h" + AnimClip::AnimClip(const QString& id, const QString& url, float startFrame, float endFrame, float timeScale, bool loopFlag, bool mirrorFlag) : AnimNode(AnimNode::Type::Clip, id), _startFrame(startFrame), @@ -107,6 +110,19 @@ static std::vector buildJointIndexMap(const AnimSkeleton& dstSkeleton, cons return jointIndexMap; } +#ifdef USE_CUSTOM_ASSERT +#undef ASSERT +#define ASSERT(x) \ + do { \ + if (!(x)) { \ + int* bad_ptr = 0; \ + *bad_ptr = 0x0badf00d; \ + } \ + } while (0) +#else +#define ASSERT assert +#endif + void AnimClip::copyFromNetworkAnim() { assert(_networkAnim && _networkAnim->isLoaded() && _skeleton); _anim.clear(); @@ -165,11 +181,14 @@ void AnimClip::copyFromNetworkAnim() { for (int frame = 0; frame < animFrameCount; frame++) { const HFMAnimationFrame& animFrame = animModel.animationFrames[frame]; + ASSERT(frame >= 0 && frame < (int)animModel.animationFrames.size()); // extract the full rotations from the animFrame (including pre and post rotations from the animModel). std::vector animRotations; animRotations.reserve(animJointCount); for (int i = 0; i < animJointCount; i++) { + ASSERT(i >= 0 && i < (int)animModel.joints.size()); + ASSERT(i >= 0 && i < (int)animFrame.rotations.size()); animRotations.push_back(animModel.joints[i].preRotation * animFrame.rotations[i] * animModel.joints[i].postRotation); } @@ -180,10 +199,12 @@ void AnimClip::copyFromNetworkAnim() { std::vector avatarRotations; avatarRotations.reserve(avatarJointCount); for (int avatarJointIndex = 0; avatarJointIndex < avatarJointCount; avatarJointIndex++) { + ASSERT(avatarJointIndex >= 0 && avatarJointIndex < (int)avatarToAnimJointIndexMap.size()); int animJointIndex = avatarToAnimJointIndexMap[avatarJointIndex]; if (animJointIndex >= 0) { // This joint is in both animation and avatar. // Set the absolute rotation directly + ASSERT(animJointIndex >= 0 && animJointIndex < (int)animRotations.size()); avatarRotations.push_back(animRotations[animJointIndex]); } else { // This joint is NOT in the animation at all. @@ -192,6 +213,7 @@ void AnimClip::copyFromNetworkAnim() { glm::quat avatarParentAbsoluteRot; int avatarParentJointIndex = avatarSkeleton->getParentIndex(avatarJointIndex); if (avatarParentJointIndex >= 0) { + ASSERT(avatarParentJointIndex >= 0 && avatarParentJointIndex < (int)avatarRotations.size()); avatarParentAbsoluteRot = avatarRotations[avatarParentJointIndex]; } avatarRotations.push_back(avatarParentAbsoluteRot * avatarRelativeDefaultRot); @@ -201,6 +223,7 @@ void AnimClip::copyFromNetworkAnim() { // convert avatar rotations into relative frame avatarSkeleton->convertAbsoluteRotationsToRelative(avatarRotations); + ASSERT(frame >= 0 && frame < (int)_anim.size()); _anim[frame].reserve(avatarJointCount); for (int avatarJointIndex = 0; avatarJointIndex < avatarJointCount; avatarJointIndex++) { const AnimPose& avatarDefaultPose = avatarSkeleton->getRelativeDefaultPose(avatarJointIndex); @@ -209,12 +232,15 @@ void AnimClip::copyFromNetworkAnim() { glm::vec3 relativeScale = avatarDefaultPose.scale(); glm::vec3 relativeTranslation; + ASSERT(avatarJointIndex >= 0 && avatarJointIndex < (int)avatarToAnimJointIndexMap.size()); int animJointIndex = avatarToAnimJointIndexMap[avatarJointIndex]; if (animJointIndex >= 0) { // This joint is in both animation and avatar. + ASSERT(animJointIndex >= 0 && animJointIndex < (int)animFrame.translations.size()); const glm::vec3& animTrans = animFrame.translations[animJointIndex]; // retarget translation from animation to avatar + ASSERT(animJointIndex >= 0 && animJointIndex < (int)animModel.animationFrames[0].translations.size()); const glm::vec3& animZeroTrans = animModel.animationFrames[0].translations[animJointIndex]; relativeTranslation = avatarDefaultPose.trans() + boneLengthScale * (animTrans - animZeroTrans); } else { @@ -224,6 +250,7 @@ void AnimClip::copyFromNetworkAnim() { } // build the final pose + ASSERT(avatarJointIndex >= 0 && avatarJointIndex < (int)avatarRotations.size()); _anim[frame].push_back(AnimPose(relativeScale, avatarRotations[avatarJointIndex], relativeTranslation)); } } diff --git a/libraries/animation/src/AnimNode.h b/libraries/animation/src/AnimNode.h index 31e10ca2d5..ec5f2b7375 100644 --- a/libraries/animation/src/AnimNode.h +++ b/libraries/animation/src/AnimNode.h @@ -98,6 +98,8 @@ public: return result; } + const AnimPoseVec& getPoses() const { return getPosesInternal(); } + protected: virtual void setCurrentFrameInternal(float frame) {} diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 633a505d14..0f0c67b846 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -2025,6 +2025,9 @@ void Rig::updateFromControllerParameters(const ControllerParameters& params, flo if (params.isTalking) { if (_talkIdleInterpTime < 1.0f) { _talkIdleInterpTime += dt / TOTAL_EASE_IN_TIME; + if (_talkIdleInterpTime > 1.0f) { + _talkIdleInterpTime = 1.0f; + } float easeOutInValue = _talkIdleInterpTime < 0.5f ? 4.0f * powf(_talkIdleInterpTime, 3.0f) : 4.0f * powf((_talkIdleInterpTime - 1.0f), 3.0f) + 1.0f; _animVars.set("idleOverlayAlpha", easeOutInValue); } else { @@ -2033,6 +2036,9 @@ void Rig::updateFromControllerParameters(const ControllerParameters& params, flo } else { if (_talkIdleInterpTime < 1.0f) { _talkIdleInterpTime += dt / TOTAL_EASE_OUT_TIME; + if (_talkIdleInterpTime > 1.0f) { + _talkIdleInterpTime = 1.0f; + } float easeOutInValue = _talkIdleInterpTime < 0.5f ? 4.0f * powf(_talkIdleInterpTime, 3.0f) : 4.0f * powf((_talkIdleInterpTime - 1.0f), 3.0f) + 1.0f; float talkAlpha = 1.0f - easeOutInValue; _animVars.set("idleOverlayAlpha", talkAlpha); @@ -2232,6 +2238,14 @@ void Rig::initAnimGraph(const QUrl& url) { } } +AnimNode::ConstPointer Rig::findAnimNodeByName(const QString& name) const { + if (_animNode) { + return _animNode->findByName(name); + } else { + return nullptr; + } +} + bool Rig::getModelRegistrationPoint(glm::vec3& modelRegistrationPointOut) const { if (_animSkeleton && _rootJointIndex >= 0) { modelRegistrationPointOut = _geometryOffset * -_animSkeleton->getAbsoluteDefaultPose(_rootJointIndex).trans(); @@ -2258,7 +2272,7 @@ void Rig::applyOverridePoses() { } } -void Rig::buildAbsoluteRigPoses(const AnimPoseVec& relativePoses, AnimPoseVec& absolutePosesOut) { +void Rig::buildAbsoluteRigPoses(const AnimPoseVec& relativePoses, AnimPoseVec& absolutePosesOut) const { DETAILED_PERFORMANCE_TIMER("buildAbsolute"); if (!_animSkeleton) { return; @@ -2279,6 +2293,24 @@ void Rig::buildAbsoluteRigPoses(const AnimPoseVec& relativePoses, AnimPoseVec& a } } +int Rig::getOverrideJointCount() const { + int count = 0; + for (size_t i = 0; i < _internalPoseSet._overrideFlags.size(); i++) { + if (_internalPoseSet._overrideFlags[i]) { + count++; + } + } + return count; +} + +bool Rig::getFlowActive() const { + return _internalFlow.getActive(); +} + +bool Rig::getNetworkGraphActive() const { + return _sendNetworkNode; +} + glm::mat4 Rig::getJointTransform(int jointIndex) const { static const glm::mat4 IDENTITY; if (isIndexValid(jointIndex)) { diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index 786d14200e..9baf4644f2 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -196,6 +196,7 @@ public: void initAnimGraph(const QUrl& url); AnimNode::ConstPointer getAnimNode() const { return _animNode; } + AnimNode::ConstPointer findAnimNodeByName(const QString& name) const; AnimSkeleton::ConstPointer getAnimSkeleton() const { return _animSkeleton; } QScriptValue addAnimationStateHandler(QScriptValue handler, QScriptValue propertiesList); void removeAnimationStateHandler(QScriptValue handler); @@ -243,7 +244,11 @@ public: Flow& getFlow() { return _internalFlow; } float getUnscaledEyeHeight() const; + void buildAbsoluteRigPoses(const AnimPoseVec& relativePoses, AnimPoseVec& absolutePosesOut) const; + int getOverrideJointCount() const; + bool getFlowActive() const; + bool getNetworkGraphActive() const; signals: void onLoadComplete(); @@ -252,7 +257,6 @@ protected: bool isIndexValid(int index) const { return _animSkeleton && index >= 0 && index < _animSkeleton->getNumJoints(); } void updateAnimationStateHandlers(); void applyOverridePoses(); - void buildAbsoluteRigPoses(const AnimPoseVec& relativePoses, AnimPoseVec& absolutePosesOut); void updateHead(bool headEnabled, bool hipsEnabled, const AnimPose& headMatrix); void updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnabled, bool hipsEstimated, diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index 41396811f0..04ab0f7973 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -375,6 +375,9 @@ AudioClient::AudioClient() : } AudioClient::~AudioClient() { + + stop(); + if (_codec && _encoder) { _codec->releaseEncoder(_encoder); _encoder = nullptr; diff --git a/libraries/audio/src/AudioReverb.cpp b/libraries/audio/src/AudioReverb.cpp index a7c6fefd39..26f5528866 100644 --- a/libraries/audio/src/AudioReverb.cpp +++ b/libraries/audio/src/AudioReverb.cpp @@ -35,16 +35,6 @@ static const double SQRT2 = 1.41421356237309504880; static const double FIXQ31 = 2147483648.0; static const double FIXQ32 = 4294967296.0; -// Round an integer to the next power-of-two, at compile time. -// VS2013 does not support constexpr so macros are used instead. -#define SETBITS0(x) (x) -#define SETBITS1(x) (SETBITS0(x) | (SETBITS0(x) >> 1)) -#define SETBITS2(x) (SETBITS1(x) | (SETBITS1(x) >> 2)) -#define SETBITS3(x) (SETBITS2(x) | (SETBITS2(x) >> 4)) -#define SETBITS4(x) (SETBITS3(x) | (SETBITS3(x) >> 8)) -#define SETBITS5(x) (SETBITS4(x) | (SETBITS4(x) >> 16)) -#define NEXTPOW2(x) (SETBITS5((x) - 1) + 1) - // // Allpass delay modulation // @@ -111,6 +101,18 @@ static const int M_AP19 = 113; static const int M_AP20 = 107; static const int M_AP21 = 127; +// Round an integer to the next power-of-two, at compile time +constexpr uint32_t NEXTPOW2(uint32_t n) { + n -= 1; + n |= (n >> 1); + n |= (n >> 2); + n |= (n >> 4); + n |= (n >> 8); + n |= (n >> 16); + n += 1; + return n; +} + // // Filter design tools using analog-matched response. // All filter types approximate the s-plane response, including cutoff > Nyquist. @@ -1796,6 +1798,18 @@ void AudioReverb::render(float** inputs, float** outputs, int numFrames) { #include +// unaligned load/store without undefined behavior +static inline __m128i mm_loadu_si32(void const* mem_addr) { + int32_t temp; + memcpy(&temp, mem_addr, sizeof(int32_t)); + return _mm_cvtsi32_si128(temp); +} + +static inline void mm_storeu_si32(void* mem_addr, __m128i a) { + int32_t temp = _mm_cvtsi128_si32(a); + memcpy(mem_addr, &temp, sizeof(int32_t)); +} + // convert int16_t to float, deinterleave stereo void AudioReverb::convertInput(const int16_t* input, float** outputs, int numFrames) { __m128 scale = _mm_set1_ps(1/32768.0f); @@ -1816,7 +1830,7 @@ void AudioReverb::convertInput(const int16_t* input, float** outputs, int numFra _mm_storeu_ps(&outputs[1][i], f1); } for (; i < numFrames; i++) { - __m128i a0 = _mm_cvtsi32_si128(*(int32_t*)&input[2*i]); + __m128i a0 = mm_loadu_si32((__m128i*)&input[2*i]); __m128i a1 = a0; // deinterleave and sign-extend @@ -1887,7 +1901,7 @@ void AudioReverb::convertOutput(float** inputs, int16_t* output, int numFrames) // interleave a0 = _mm_unpacklo_epi16(a0, a1); - *(int32_t*)&output[2*i] = _mm_cvtsi128_si32(a0); + mm_storeu_si32((__m128i*)&output[2*i], a0); } } diff --git a/libraries/audio/src/AudioSRC.cpp b/libraries/audio/src/AudioSRC.cpp index d488eccb6a..625f45e9ae 100644 --- a/libraries/audio/src/AudioSRC.cpp +++ b/libraries/audio/src/AudioSRC.cpp @@ -793,6 +793,18 @@ int AudioSRC::multirateFilter4(const float* input0, const float* input1, const f #include // SSE2 +// unaligned load/store without undefined behavior +static inline __m128i mm_loadu_si32(void const* mem_addr) { + int32_t temp; + memcpy(&temp, mem_addr, sizeof(int32_t)); + return _mm_cvtsi32_si128(temp); +} + +static inline void mm_storeu_si32(void* mem_addr, __m128i a) { + int32_t temp = _mm_cvtsi128_si32(a); + memcpy(mem_addr, &temp, sizeof(int32_t)); +} + // convert int16_t to float, deinterleave stereo void AudioSRC::convertInput(const int16_t* input, float** outputs, int numFrames) { __m128 scale = _mm_set1_ps(1/32768.0f); @@ -839,7 +851,7 @@ void AudioSRC::convertInput(const int16_t* input, float** outputs, int numFrames _mm_storeu_ps(&outputs[1][i], f1); } for (; i < numFrames; i++) { - __m128i a0 = _mm_cvtsi32_si128(*(int32_t*)&input[2*i]); + __m128i a0 = mm_loadu_si32((__m128i*)&input[2*i]); __m128i a1 = a0; // deinterleave and sign-extend @@ -878,9 +890,9 @@ void AudioSRC::convertInput(const int16_t* input, float** outputs, int numFrames _mm_storeu_ps(&outputs[3][i], _mm_shuffle_ps(f1, f3, _MM_SHUFFLE(3,1,3,1))); } for (; i < numFrames; i++) { - __m128i a0 = _mm_cvtsi32_si128(*(int32_t*)&input[4*i+0]); + __m128i a0 = mm_loadu_si32((__m128i*)&input[4*i+0]); __m128i a1 = a0; - __m128i a2 = _mm_cvtsi32_si128(*(int32_t*)&input[4*i+2]); + __m128i a2 = mm_loadu_si32((__m128i*)&input[4*i+2]); __m128i a3 = a2; // deinterleave and sign-extend @@ -986,7 +998,7 @@ void AudioSRC::convertOutput(float** inputs, int16_t* output, int numFrames) { // interleave a0 = _mm_unpacklo_epi16(a0, a1); - *(int32_t*)&output[2*i] = _mm_cvtsi128_si32(a0); + mm_storeu_si32((__m128i*)&output[2*i], a0); } } else if (_numChannels == 4) { diff --git a/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.cpp index 9828a8beda..4258d4db75 100644 --- a/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.cpp @@ -109,6 +109,23 @@ bool Basic2DWindowOpenGLDisplayPlugin::internalActivate() { return Parent::internalActivate(); } +gpu::PipelinePointer Basic2DWindowOpenGLDisplayPlugin::getCompositeScenePipeline() { +#if defined(Q_OS_ANDROID) + return _linearToSRGBPipeline; +#else + return _SRGBToLinearPipeline; +#endif +} + +gpu::Element Basic2DWindowOpenGLDisplayPlugin::getCompositeFBColorSpace() { +#if defined(Q_OS_ANDROID) + return gpu::Element::COLOR_SRGBA_32; +#else + return gpu::Element::COLOR_RGBA_32; +#endif +} + + void Basic2DWindowOpenGLDisplayPlugin::compositeExtra() { #if defined(Q_OS_ANDROID) auto& virtualPadManager = VirtualPad::Manager::instance(); diff --git a/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.h index cc304c19c2..7f654915e1 100644 --- a/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.h @@ -37,6 +37,9 @@ public: virtual void pluginUpdate() override {}; + virtual gpu::PipelinePointer getCompositeScenePipeline() override; + virtual gpu::Element getCompositeFBColorSpace() override; + protected: mutable bool _isThrottled = false; diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp index 6c6c2aee80..73bc1d0aad 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp @@ -48,12 +48,16 @@ #include "CompositorHelper.h" #include "Logging.h" #include "RefreshRateController.h" + +using namespace shader::gpu::program; + extern QThread* RENDER_THREAD; class PresentThread : public QThread, public Dependency { using Mutex = std::mutex; using Condition = std::condition_variable; using Lock = std::unique_lock; + public: PresentThread() { @@ -380,57 +384,45 @@ void OpenGLDisplayPlugin::customizeContext() { } } - if (!_presentPipeline) { + if (!_drawTexturePipeline) { gpu::StatePointer blendState = gpu::StatePointer(new gpu::State()); blendState->setDepthTest(gpu::State::DepthTest(false)); blendState->setBlendFunction(true, - gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, - gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); + gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, + gpu::State::INV_SRC_ALPHA, + gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, + gpu::State::ONE); gpu::StatePointer scissorState = gpu::StatePointer(new gpu::State()); scissorState->setDepthTest(gpu::State::DepthTest(false)); scissorState->setScissorEnable(true); - { -#ifdef Q_OS_ANDROID - gpu::ShaderPointer program = gpu::Shader::createProgram(shader::gpu::program::DrawTextureGammaLinearToSRGB); -#else - gpu::ShaderPointer program = gpu::Shader::createProgram(shader::gpu::program::DrawTexture); -#endif - _simplePipeline = gpu::Pipeline::create(program, scissorState); - } - { -#ifdef Q_OS_ANDROID - gpu::ShaderPointer program = gpu::Shader::createProgram(shader::gpu::program::DrawTextureGammaLinearToSRGB); -#else - gpu::ShaderPointer program = gpu::Shader::createProgram(shader::gpu::program::DrawTextureGammaSRGBToLinear); -#endif - _presentPipeline = gpu::Pipeline::create(program, scissorState); - } + _drawTexturePipeline = gpu::Pipeline::create(gpu::Shader::createProgram(DrawTexture), scissorState); - { - gpu::ShaderPointer program = gpu::Shader::createProgram(shader::gpu::program::DrawTexture); - _hudPipeline = gpu::Pipeline::create(program, blendState); - } - { - gpu::ShaderPointer program = gpu::Shader::createProgram(shader::gpu::program::DrawTextureMirroredX); - _mirrorHUDPipeline = gpu::Pipeline::create(program, blendState); - } + _linearToSRGBPipeline = gpu::Pipeline::create(gpu::Shader::createProgram(DrawTextureGammaLinearToSRGB), scissorState); - { - gpu::ShaderPointer program = gpu::Shader::createProgram(shader::gpu::program::DrawTransformedTexture); - _cursorPipeline = gpu::Pipeline::create(program, blendState); - } + _SRGBToLinearPipeline = gpu::Pipeline::create(gpu::Shader::createProgram(DrawTextureGammaSRGBToLinear), scissorState); + + _hudPipeline = gpu::Pipeline::create(gpu::Shader::createProgram(DrawTexture), blendState); + + _mirrorHUDPipeline = gpu::Pipeline::create(gpu::Shader::createProgram(DrawTextureMirroredX), blendState); + + _cursorPipeline = gpu::Pipeline::create(gpu::Shader::createProgram(DrawTransformedTexture), blendState); } + updateCompositeFramebuffer(); } void OpenGLDisplayPlugin::uncustomizeContext() { - _presentPipeline.reset(); - _cursorPipeline.reset(); + + _drawTexturePipeline.reset(); + _linearToSRGBPipeline.reset(); + _SRGBToLinearPipeline.reset(); _hudPipeline.reset(); _mirrorHUDPipeline.reset(); + _cursorPipeline.reset(); _compositeFramebuffer.reset(); + withPresentThreadLock([&] { _currentFrame.reset(); _lastFrame = nullptr; @@ -535,11 +527,9 @@ void OpenGLDisplayPlugin::renderFromTexture(gpu::Batch& batch, const gpu::Textur batch.setStateScissorRect(scissor); batch.setViewportTransform(viewport); batch.setResourceTexture(0, texture); -#ifndef USE_GLES - batch.setPipeline(_presentPipeline); -#else - batch.setPipeline(_simplePipeline); -#endif + + batch.setPipeline(_drawTexturePipeline); + batch.draw(gpu::TRIANGLE_STRIP, 4); if (copyFbo) { gpu::Vec4i copyFboRect(0, 0, copyFbo->getWidth(), copyFbo->getHeight()); @@ -603,9 +593,10 @@ std::function OpenGL hudEyeViewports[eye] = eyeViewport(eye); }); return [=](gpu::Batch& batch, const gpu::TexturePointer& hudTexture, bool mirror) { - if (hudPipeline && hudTexture) { + auto pipeline = mirror ? hudMirrorPipeline : hudPipeline; + if (pipeline && hudTexture) { batch.enableStereo(false); - batch.setPipeline(mirror ? hudMirrorPipeline : hudPipeline); + batch.setPipeline(pipeline); batch.setResourceTexture(0, hudTexture); if (hudStereo) { for_each_eye([&](Eye eye) { @@ -644,6 +635,11 @@ void OpenGLDisplayPlugin::compositePointer() { }); } +// Overridden by Basic2DWindowDisplayPlugin and OculusDisplayPlugin +gpu::PipelinePointer OpenGLDisplayPlugin::getCompositeScenePipeline() { + return _drawTexturePipeline; +} + void OpenGLDisplayPlugin::compositeScene() { render([&](gpu::Batch& batch) { batch.enableStereo(false); @@ -652,7 +648,7 @@ void OpenGLDisplayPlugin::compositeScene() { batch.setStateScissorRect(ivec4(uvec2(), _compositeFramebuffer->getSize())); batch.resetViewTransform(); batch.setProjectionTransform(mat4()); - batch.setPipeline(_simplePipeline); + batch.setPipeline(getCompositeScenePipeline()); batch.setResourceTexture(0, _currentFrame->framebuffer->getRenderBuffer(0)); batch.draw(gpu::TRIANGLE_STRIP, 4); }); @@ -666,17 +662,6 @@ void OpenGLDisplayPlugin::compositeLayers() { compositeScene(); } -#ifdef HIFI_ENABLE_NSIGHT_DEBUG - if (false) // do not draw the HUD if running nsight debug -#endif - { - PROFILE_RANGE_EX(render_detail, "handleHUDBatch", 0xff0077ff, (uint64_t)presentCount()) - auto hudOperator = getHUDOperator(); - withPresentThreadLock([&] { - _hudOperator = hudOperator; - }); - } - { PROFILE_RANGE_EX(render_detail, "compositeExtra", 0xff0077ff, (uint64_t)presentCount()) compositeExtra(); @@ -924,11 +909,17 @@ void OpenGLDisplayPlugin::render(std::function f) { OpenGLDisplayPlugin::~OpenGLDisplayPlugin() { } +// Added this to allow desktop composite framebuffer to be RGBA while mobile is SRGBA +// Overridden by Basic2DWindowDisplayPlugin +// FIXME: Eventually it would be ideal to have both framebuffers be of the same type +gpu::Element OpenGLDisplayPlugin::getCompositeFBColorSpace() { + return gpu::Element::COLOR_RGBA_32; +} + void OpenGLDisplayPlugin::updateCompositeFramebuffer() { auto renderSize = glm::uvec2(getRecommendedRenderSize()); if (!_compositeFramebuffer || _compositeFramebuffer->getSize() != renderSize) { - _compositeFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("OpenGLDisplayPlugin::composite", gpu::Element::COLOR_RGBA_32, renderSize.x, renderSize.y)); - // _compositeFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("OpenGLDisplayPlugin::composite", gpu::Element::COLOR_SRGBA_32, renderSize.x, renderSize.y)); + _compositeFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("OpenGLDisplayPlugin::composite", getCompositeFBColorSpace(), renderSize.x, renderSize.y)); } } diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h index 562c5af5cf..eae9f86710 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h @@ -86,6 +86,8 @@ public: void copyTextureToQuickFramebuffer(NetworkTexturePointer source, QOpenGLFramebufferObject* target, GLsync* fenceSync) override; + virtual std::function getHUDOperator() override; + protected: friend class PresentThread; @@ -102,10 +104,12 @@ protected: virtual QThread::Priority getPresentPriority() { return QThread::HighPriority; } virtual void compositeLayers(); virtual void compositeScene(); - virtual std::function getHUDOperator(); virtual void compositePointer(); virtual void compositeExtra() {}; + virtual gpu::PipelinePointer getCompositeScenePipeline(); + virtual gpu::Element getCompositeFBColorSpace(); + // These functions must only be called on the presentation thread virtual void customizeContext(); virtual void uncustomizeContext(); @@ -149,9 +153,11 @@ protected: gpu::PipelinePointer _hudPipeline; gpu::PipelinePointer _mirrorHUDPipeline; gpu::ShaderPointer _mirrorHUDPS; - gpu::PipelinePointer _simplePipeline; - gpu::PipelinePointer _presentPipeline; + gpu::PipelinePointer _drawTexturePipeline; + gpu::PipelinePointer _linearToSRGBPipeline; + gpu::PipelinePointer _SRGBToLinearPipeline; gpu::PipelinePointer _cursorPipeline; + gpu::TexturePointer _displayTexture{}; float _compositeHUDAlpha { 1.0f }; diff --git a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp index 874454b391..3952c2c90e 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp @@ -1,4 +1,4 @@ -// +// // Created by Bradley Austin Davis on 2016/02/15 // Copyright 2016 High Fidelity, Inc. // @@ -402,25 +402,18 @@ void HmdDisplayPlugin::HUDRenderer::build() { format->setAttribute(gpu::Stream::POSITION, gpu::Stream::POSITION, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ), 0); format->setAttribute(gpu::Stream::TEXCOORD, gpu::Stream::TEXCOORD, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV)); uniformsBuffer = std::make_shared(sizeof(Uniforms), nullptr); - updatePipeline(); + + auto program = gpu::Shader::createProgram(shader::render_utils::program::hmd_ui); + gpu::StatePointer state = gpu::StatePointer(new gpu::State()); + state->setDepthTest(gpu::State::DepthTest(true, true, gpu::LESS_EQUAL)); + state->setBlendFunction(true, + gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, + gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); + + pipeline = gpu::Pipeline::create(program, state); } -void HmdDisplayPlugin::HUDRenderer::updatePipeline() { - if (!pipeline) { - auto program = gpu::Shader::createProgram(shader::render_utils::program::hmd_ui); - gpu::StatePointer state = gpu::StatePointer(new gpu::State()); - state->setDepthTest(gpu::State::DepthTest(true, true, gpu::LESS_EQUAL)); - state->setBlendFunction(true, - gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, - gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); - - pipeline = gpu::Pipeline::create(program, state); - } -} - -std::function HmdDisplayPlugin::HUDRenderer::render(HmdDisplayPlugin& plugin) { - updatePipeline(); - +std::function HmdDisplayPlugin::HUDRenderer::render() { auto hudPipeline = pipeline; auto hudFormat = format; auto hudVertices = vertices; @@ -479,7 +472,7 @@ void HmdDisplayPlugin::compositePointer() { } std::function HmdDisplayPlugin::getHUDOperator() { - return _hudRenderer.render(*this); + return _hudRenderer.render(); } HmdDisplayPlugin::~HmdDisplayPlugin() { diff --git a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h index e952b1e8db..9942222f48 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h @@ -48,6 +48,7 @@ public: void pluginUpdate() override {}; + std::function getHUDOperator() override; virtual StencilMaskMode getStencilMaskMode() const override { return StencilMaskMode::PAINT; } signals: @@ -62,7 +63,6 @@ protected: bool internalActivate() override; void internalDeactivate() override; - std::function getHUDOperator() override; void compositePointer() override; void internalPresent() override; void customizeContext() override; @@ -105,7 +105,7 @@ private: gpu::BufferPointer vertices; gpu::BufferPointer indices; uint32_t indexCount { 0 }; - gpu::PipelinePointer pipeline; + gpu::PipelinePointer pipeline { nullptr }; gpu::BufferPointer uniformsBuffer; @@ -123,7 +123,6 @@ private: static const int VERTEX_STRIDE { sizeof(Vertex) }; void build(); - void updatePipeline(); - std::function render(HmdDisplayPlugin& plugin); + std::function render(); } _hudRenderer; }; diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index 1ecbcb0c8b..5ac6e4f642 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -256,18 +256,28 @@ void EntityTreeRenderer::clear() { } // reset the engine - if (_wantScripts && !_shuttingDown) { - resetEntitiesScriptEngine(); - } - // remove all entities from the scene auto scene = _viewState->getMain3DScene(); - if (scene) { - for (const auto& entry : _entitiesInScene) { - const auto& renderer = entry.second; - fadeOutRenderable(renderer); + if (_shuttingDown) { + if (scene) { + render::Transaction transaction; + for (const auto& entry : _entitiesInScene) { + const auto& renderer = entry.second; + renderer->removeFromScene(scene, transaction); + } + scene->enqueueTransaction(transaction); } } else { - qCWarning(entitiesrenderer) << "EntitityTreeRenderer::clear(), Unexpected null scene, possibly during application shutdown"; + if (_wantScripts) { + resetEntitiesScriptEngine(); + } + if (scene) { + for (const auto& entry : _entitiesInScene) { + const auto& renderer = entry.second; + fadeOutRenderable(renderer); + } + } else { + qCWarning(entitiesrenderer) << "EntitityTreeRenderer::clear(), Unexpected null scene"; + } } _entitiesInScene.clear(); _renderablesToUpdate.clear(); @@ -1056,10 +1066,14 @@ void EntityTreeRenderer::fadeOutRenderable(const EntityRendererPointer& renderab render::Transaction transaction; auto scene = _viewState->getMain3DScene(); - transaction.setTransitionFinishedOperator(renderable->getRenderItemID(), [scene, renderable]() { - render::Transaction transaction; - renderable->removeFromScene(scene, transaction); - scene->enqueueTransaction(transaction); + EntityRendererWeakPointer weakRenderable = renderable; + transaction.setTransitionFinishedOperator(renderable->getRenderItemID(), [scene, weakRenderable]() { + auto renderable = weakRenderable.lock(); + if (renderable) { + render::Transaction transaction; + renderable->removeFromScene(scene, transaction); + scene->enqueueTransaction(transaction); + } }); scene->enqueueTransaction(transaction); diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index 1fc8a2382b..54edd3543c 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -368,6 +368,10 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& shapeInfo) { } if (type == SHAPE_TYPE_COMPOUND) { + if (!_compoundShapeResource || !_compoundShapeResource->isLoaded()) { + return; + } + updateModelBounds(); // should never fall in here when collision model not fully loaded diff --git a/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp b/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp index 7faebfdf56..ec1c890583 100644 --- a/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp @@ -70,7 +70,7 @@ bool ShapeEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPoin return true; } - if (_color != entity->getColor()) { + if (_color != toGlm(entity->getColor())) { return true; } if (_alpha != entity->getAlpha()) { @@ -130,12 +130,12 @@ void ShapeEntityRenderer::doRenderUpdateAsynchronousTyped(const TypedEntityPoint } }); - glm::u8vec3 color = entity->getColor(); + glm::vec3 color = toGlm(entity->getColor()); float alpha = entity->getAlpha(); if (_color != color || _alpha != alpha) { _color = color; _alpha = alpha; - _material->setAlbedo(toGlm(_color)); + _material->setAlbedo(color); _material->setOpacity(_alpha); auto materials = _materials.find("0"); diff --git a/libraries/entities-renderer/src/RenderableShapeEntityItem.h b/libraries/entities-renderer/src/RenderableShapeEntityItem.h index c9fd4801d5..6e4b05f716 100644 --- a/libraries/entities-renderer/src/RenderableShapeEntityItem.h +++ b/libraries/entities-renderer/src/RenderableShapeEntityItem.h @@ -43,7 +43,7 @@ private: PulsePropertyGroup _pulseProperties; std::shared_ptr _material { std::make_shared() }; - glm::u8vec3 _color; + glm::vec3 _color { NAN }; float _alpha; glm::vec3 _position; diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp index 3b615ba467..bb3e99157e 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp @@ -7,6 +7,7 @@ // #include "RenderableWebEntityItem.h" +#include #include #include @@ -46,7 +47,7 @@ static uint64_t MAX_NO_RENDER_INTERVAL = 30 * USECS_PER_SECOND; static uint8_t YOUTUBE_MAX_FPS = 30; // Don't allow more than 20 concurrent web views -static uint32_t _currentWebCount { 0 }; +static std::atomic _currentWebCount(0); static const uint32_t MAX_CONCURRENT_WEB_VIEWS = 20; static QTouchDevice _touchDevice; @@ -356,16 +357,15 @@ void WebEntityRenderer::buildWebSurface(const EntityItemPointer& entity, const Q void WebEntityRenderer::destroyWebSurface() { QSharedPointer webSurface; - ContentType contentType = ContentType::NoContent; withWriteLock([&] { webSurface.swap(_webSurface); - _contentType = contentType; - }); + _contentType = ContentType::NoContent; - if (webSurface) { - --_currentWebCount; - WebEntityRenderer::releaseWebSurface(webSurface, _cachedWebSurface, _connections); - } + if (webSurface) { + --_currentWebCount; + WebEntityRenderer::releaseWebSurface(webSurface, _cachedWebSurface, _connections); + } + }); } glm::vec2 WebEntityRenderer::getWindowSize(const TypedEntityPointer& entity) const { diff --git a/libraries/gl/src/gl/Context.cpp b/libraries/gl/src/gl/Context.cpp index a0d52ee223..d5d06d1195 100644 --- a/libraries/gl/src/gl/Context.cpp +++ b/libraries/gl/src/gl/Context.cpp @@ -27,6 +27,10 @@ #include "GLHelpers.h" #include "QOpenGLContextWrapper.h" +#if defined(GL_CUSTOM_CONTEXT) +#include +#endif + using namespace gl; #if defined(GL_CUSTOM_CONTEXT) @@ -42,7 +46,10 @@ std::atomic Context::_totalSwapchainMemoryUsage { 0 }; size_t Context::getSwapchainMemoryUsage() { return _totalSwapchainMemoryUsage.load(); } size_t Context::evalSurfaceMemoryUsage(uint32_t width, uint32_t height, uint32_t pixelSize) { - return width * height * pixelSize; + size_t result = width; + result *= height; + result *= pixelSize; + return result; } void Context::updateSwapchainMemoryUsage(size_t prevSize, size_t newSize) { @@ -126,7 +133,7 @@ void Context::clear() { #if defined(GL_CUSTOM_CONTEXT) static void setupPixelFormatSimple(HDC hdc) { - // FIXME build the PFD based on the + // FIXME build the PFD based on the static const PIXELFORMATDESCRIPTOR pfd = // pfd Tells Windows How We Want Things To Be { sizeof(PIXELFORMATDESCRIPTOR), // Size Of This Pixel Format Descriptor @@ -176,6 +183,7 @@ static void setupPixelFormatSimple(HDC hdc) { #define WGL_CONTEXT_PROFILE_MASK_ARB 0x9126 // Context create flag bits +#define WGL_CONTEXT_ROBUST_ACCESS_BIT_ARB 0x00000004 #define WGL_CONTEXT_DEBUG_BIT_ARB 0x00000001 #define WGL_CONTEXT_CORE_PROFILE_BIT_ARB 0x00000001 #define WGL_CONTEXT_ES2_PROFILE_BIT_EXT 0x00000004 @@ -196,17 +204,17 @@ GLAPI PFNWGLCREATECONTEXTATTRIBSARBPROC wglCreateContextAttribsARB; Q_GUI_EXPORT QOpenGLContext *qt_gl_global_share_context(); #if defined(GL_CUSTOM_CONTEXT) -bool Context::makeCurrent() { - BOOL result = wglMakeCurrent(_hdc, _hglrc); - assert(result); - updateSwapchainMemoryCounter(); - return result; -} - void Context::swapBuffers() { - SwapBuffers(_hdc); -} - void Context::doneCurrent() { - wglMakeCurrent(0, 0); +bool Context::makeCurrent() { + BOOL result = wglMakeCurrent(_hdc, _hglrc); + assert(result); + updateSwapchainMemoryCounter(); + return result; +} +void Context::swapBuffers() { + SwapBuffers(_hdc); +} +void Context::doneCurrent() { + wglMakeCurrent(0, 0); } #endif @@ -305,11 +313,18 @@ void Context::create(QOpenGLContext* shareContext) { #else contextAttribs.push_back(WGL_CONTEXT_CORE_PROFILE_BIT_ARB); #endif - contextAttribs.push_back(WGL_CONTEXT_FLAGS_ARB); - if (enableDebugLogger()) { - contextAttribs.push_back(WGL_CONTEXT_DEBUG_BIT_ARB); - } else { - contextAttribs.push_back(0); + { + int contextFlags = 0; + if (enableDebugLogger()) { + contextFlags |= WGL_CONTEXT_DEBUG_BIT_ARB; + } +#ifdef USE_KHR_ROBUSTNESS + contextFlags |= WGL_CONTEXT_ROBUST_ACCESS_BIT_ARB; +#endif + if (contextFlags != 0) { + contextAttribs.push_back(WGL_CONTEXT_FLAGS_ARB); + contextAttribs.push_back(contextFlags); + } } contextAttribs.push_back(0); HGLRC shareHglrc = nullptr; @@ -323,8 +338,8 @@ void Context::create(QOpenGLContext* shareContext) { if (_hglrc != 0) { createWrapperContext(); } - } - + } + if (_hglrc == 0) { // fallback, if the context creation failed, or USE_CUSTOM_CONTEXT is false qtCreate(shareContext); diff --git a/libraries/gl/src/gl/Context.h b/libraries/gl/src/gl/Context.h index 5254d58d38..7beb59e33f 100644 --- a/libraries/gl/src/gl/Context.h +++ b/libraries/gl/src/gl/Context.h @@ -23,7 +23,7 @@ class QOpenGLContext; class QThread; class QOpenGLDebugMessage; -#if defined(Q_OS_WIN) && defined(USE_GLES) +#if defined(Q_OS_WIN) && (defined(USE_GLES) || defined(USE_KHR_ROBUSTNESS)) //#if defined(Q_OS_WIN) #define GL_CUSTOM_CONTEXT #endif diff --git a/libraries/networking/src/LimitedNodeList.cpp b/libraries/networking/src/LimitedNodeList.cpp index 8fefe5820c..ac0f9e0b07 100644 --- a/libraries/networking/src/LimitedNodeList.cpp +++ b/libraries/networking/src/LimitedNodeList.cpp @@ -450,13 +450,16 @@ qint64 LimitedNodeList::sendPacket(std::unique_ptr packet, const HifiS auto size = sendUnreliablePacket(*packet, sockAddr, hmacAuth); if (size < 0) { auto now = usecTimestampNow(); - eachNode([now](const SharedNodePointer & node) { - qCDebug(networking) << "Stats for " << node->getPublicSocket() << "\n" - << " Last Heard Microstamp: " << node->getLastHeardMicrostamp() << " (" << (now - node->getLastHeardMicrostamp()) << "usec ago)\n" - << " Outbound Kbps: " << node->getOutboundKbps() << "\n" - << " Inbound Kbps: " << node->getInboundKbps() << "\n" - << " Ping: " << node->getPingMs(); - }); + if (now - _sendErrorStatsTime > ERROR_STATS_PERIOD_US) { + _sendErrorStatsTime = now; + eachNode([now](const SharedNodePointer& node) { + qCDebug(networking) << "Stats for " << node->getPublicSocket() << "\n" + << " Last Heard Microstamp: " << node->getLastHeardMicrostamp() << " (" << (now - node->getLastHeardMicrostamp()) << "usec ago)\n" + << " Outbound Kbps: " << node->getOutboundKbps() << "\n" + << " Inbound Kbps: " << node->getInboundKbps() << "\n" + << " Ping: " << node->getPingMs(); + }); + } } return size; } @@ -996,7 +999,7 @@ void LimitedNodeList::sendSTUNRequest() { const int NUM_INITIAL_STUN_REQUESTS_BEFORE_FAIL = 10; if (!_hasCompletedInitialSTUN) { - qCDebug(networking) << "Sending intial stun request to" << STUN_SERVER_HOSTNAME; + qCDebug(networking) << "Sending initial stun request to" << STUN_SERVER_HOSTNAME; if (_numInitialSTUNRequests > NUM_INITIAL_STUN_REQUESTS_BEFORE_FAIL) { // we're still trying to do our initial STUN we're over the fail threshold @@ -1185,7 +1188,7 @@ void LimitedNodeList::stopInitialSTUNUpdate(bool success) { // We now setup a timer here to fire every so often to check that our IP address has not changed. // Or, if we failed - if will check if we can eventually get a public socket - const int STUN_IP_ADDRESS_CHECK_INTERVAL_MSECS = 30 * 1000; + const int STUN_IP_ADDRESS_CHECK_INTERVAL_MSECS = 10 * 1000; QTimer* stunOccasionalTimer = new QTimer { this }; connect(stunOccasionalTimer, &QTimer::timeout, this, &LimitedNodeList::sendSTUNRequest); @@ -1243,15 +1246,22 @@ void LimitedNodeList::errorTestingLocalSocket() { } void LimitedNodeList::setLocalSocket(const HifiSockAddr& sockAddr) { - if (sockAddr != _localSockAddr) { + if (sockAddr.getAddress() != _localSockAddr.getAddress()) { if (_localSockAddr.isNull()) { qCInfo(networking) << "Local socket is" << sockAddr; + _localSockAddr = sockAddr; } else { qCInfo(networking) << "Local socket has changed from" << _localSockAddr << "to" << sockAddr; + _localSockAddr = sockAddr; + if (_hasTCPCheckedLocalSocket) { // Force a port change for NAT: + reset(); + _nodeSocket.rebind(0); + _localSockAddr.setPort(_nodeSocket.localPort()); + qCInfo(networking) << "Local port changed to" << _localSockAddr.getPort(); + } } - _localSockAddr = sockAddr; emit localSockAddrChanged(_localSockAddr); } } diff --git a/libraries/networking/src/LimitedNodeList.h b/libraries/networking/src/LimitedNodeList.h index f9f6bf3b3e..5f24401b10 100644 --- a/libraries/networking/src/LimitedNodeList.h +++ b/libraries/networking/src/LimitedNodeList.h @@ -497,6 +497,9 @@ private: float _outboundKbps { 0.0f }; bool _dropOutgoingNodeTraffic { false }; + + quint64 _sendErrorStatsTime { (quint64)0 }; + static const quint64 ERROR_STATS_PERIOD_US { 1 * USECS_PER_SECOND }; }; #endif // hifi_LimitedNodeList_h diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index 3d367bc761..96b713c583 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -418,6 +418,20 @@ void NodeList::sendDomainServerCheckIn() { auto accountManager = DependencyManager::get(); packetStream << FingerprintUtils::getMachineFingerprint(); + QString systemInfo; +#if defined Q_OS_WIN + systemInfo = "OS:Windows"; +#elif defined Q_OS_OSX + systemInfo = "OS:OSX"; +#elif defined Q_OS_LINUX + systemInfo = "OS:Linux"; +#elif defined Q_OS_ANDROID + systemInfo = "OS:Android"; +#else + systemInfo = "OS:Unknown"; +#endif + packetStream << systemInfo; + packetStream << _connectReason; if (_nodeDisconnectTimestamp < _nodeConnectTimestamp) { diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index 30066b68e8..e434867c9a 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -72,7 +72,7 @@ PacketVersion versionForPacketType(PacketType packetType) { return static_cast(DomainConnectionDeniedVersion::IncludesExtraInfo); case PacketType::DomainConnectRequest: - return static_cast(DomainConnectRequestVersion::HasReason); + return static_cast(DomainConnectRequestVersion::HasSystemInfo); case PacketType::DomainServerAddedNode: return static_cast(DomainServerAddedNodeVersion::PermissionsGrid); diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index 93a5d4e2b4..a244399c5a 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -346,7 +346,8 @@ enum class DomainConnectRequestVersion : PacketVersion { HasMachineFingerprint, AlwaysHasMachineFingerprint, HasTimestamp, - HasReason + HasReason, + HasSystemInfo }; enum class DomainConnectionDeniedVersion : PacketVersion { diff --git a/libraries/networking/src/udt/Socket.cpp b/libraries/networking/src/udt/Socket.cpp index 3a7a056c77..c56f276560 100644 --- a/libraries/networking/src/udt/Socket.cpp +++ b/libraries/networking/src/udt/Socket.cpp @@ -33,6 +33,7 @@ using namespace udt; Socket::Socket(QObject* parent, bool shouldChangeSocketOptions) : QObject(parent), + _udpSocket(parent), _readyReadBackupTimer(new QTimer(this)), _shouldChangeSocketOptions(shouldChangeSocketOptions) { @@ -50,6 +51,7 @@ Socket::Socket(QObject* parent, bool shouldChangeSocketOptions) : } void Socket::bind(const QHostAddress& address, quint16 port) { + _udpSocket.bind(address, port); if (_shouldChangeSocketOptions) { @@ -75,7 +77,7 @@ void Socket::rebind() { } void Socket::rebind(quint16 localPort) { - _udpSocket.close(); + _udpSocket.abort(); bind(QHostAddress::AnyIPv4, localPort); } diff --git a/libraries/octree/src/OctreeEditPacketSender.cpp b/libraries/octree/src/OctreeEditPacketSender.cpp index d8d3041270..7b84205691 100644 --- a/libraries/octree/src/OctreeEditPacketSender.cpp +++ b/libraries/octree/src/OctreeEditPacketSender.cpp @@ -46,6 +46,7 @@ bool OctreeEditPacketSender::serversExist() const { void OctreeEditPacketSender::queuePacketToNode(const QUuid& nodeUUID, std::unique_ptr packet) { bool wantDebug = false; + QMutexLocker lock(&_packetsQueueLock); DependencyManager::get()->eachNode([&](const SharedNodePointer& node){ // only send to the NodeTypes that are getMyNodeType() if (node->getType() == getMyNodeType() @@ -324,6 +325,8 @@ bool OctreeEditPacketSender::process() { void OctreeEditPacketSender::processNackPacket(ReceivedMessage& message, SharedNodePointer sendingNode) { // parse sending node from packet, retrieve packet history for that node + QMutexLocker lock(&_packetsQueueLock); + // if packet history doesn't exist for the sender node (somehow), bail if (_sentPacketHistories.count(sendingNode->getUUID()) == 0) { return; @@ -345,7 +348,7 @@ void OctreeEditPacketSender::processNackPacket(ReceivedMessage& message, SharedN } void OctreeEditPacketSender::nodeKilled(SharedNodePointer node) { - // TODO: add locks + QMutexLocker lock(&_packetsQueueLock); QUuid nodeUUID = node->getUUID(); _pendingEditPackets.erase(nodeUUID); _outgoingSequenceNumbers.erase(nodeUUID); diff --git a/libraries/octree/src/OctreeEditPacketSender.h b/libraries/octree/src/OctreeEditPacketSender.h index 1dad5b74c7..318f6b81bb 100644 --- a/libraries/octree/src/OctreeEditPacketSender.h +++ b/libraries/octree/src/OctreeEditPacketSender.h @@ -86,19 +86,20 @@ protected: void processPreServerExistsPackets(); // These are packets which are destined from know servers but haven't been released because they're still too small + // protected by _packetsQueueLock std::unordered_map _pendingEditPackets; // These are packets that are waiting to be processed because we don't yet know if there are servers int _maxPendingMessages; bool _releaseQueuedMessagesPending; QMutex _pendingPacketsLock; - QMutex _packetsQueueLock; // don't let different threads release the queue while another thread is writing to it + QMutex _packetsQueueLock{ QMutex::Recursive }; // don't let different threads release the queue while another thread is writing to it std::list _preServerEdits; // these will get packed into other larger packets std::list> _preServerSingleMessagePackets; // these will go out as is QMutex _releaseQueuedPacketMutex; - // TODO: add locks for this and _pendingEditPackets + // protected by _packetsQueueLock std::unordered_map _sentPacketHistories; std::unordered_map _outgoingSequenceNumbers; }; diff --git a/libraries/physics/src/PhysicalEntitySimulation.cpp b/libraries/physics/src/PhysicalEntitySimulation.cpp index f532d5209f..0a08aaa28d 100644 --- a/libraries/physics/src/PhysicalEntitySimulation.cpp +++ b/libraries/physics/src/PhysicalEntitySimulation.cpp @@ -309,20 +309,28 @@ void PhysicalEntitySimulation::buildMotionStatesForEntitiesThatNeedThem() { SetOfEntities::iterator entityItr = _entitiesToAddToPhysics.begin(); while (entityItr != _entitiesToAddToPhysics.end()) { EntityItemPointer entity = (*entityItr); - assert(!entity->getPhysicsInfo()); if (entity->isDead()) { prepareEntityForDelete(entity); entityItr = _entitiesToAddToPhysics.erase(entityItr); - } else if (!entity->shouldBePhysical()) { - // this entity should no longer be on _entitiesToAddToPhysics + continue; + } + if (entity->getPhysicsInfo()) { entityItr = _entitiesToAddToPhysics.erase(entityItr); + continue; + } + if (!entity->shouldBePhysical()) { + // this entity should no longer be on _entitiesToAddToPhysics if (entity->isMovingRelativeToParent()) { SetOfEntities::iterator itr = _simpleKinematicEntities.find(entity); if (itr == _simpleKinematicEntities.end()) { _simpleKinematicEntities.insert(entity); } } - } else if (entity->isReadyToComputeShape()) { + entityItr = _entitiesToAddToPhysics.erase(entityItr); + continue; + } + + if (entity->isReadyToComputeShape()) { ShapeRequest shapeRequest(entity); ShapeRequests::iterator requestItr = _shapeRequests.find(shapeRequest); if (requestItr == _shapeRequests.end()) { @@ -332,18 +340,7 @@ void PhysicalEntitySimulation::buildMotionStatesForEntitiesThatNeedThem() { uint32_t requestCount = ObjectMotionState::getShapeManager()->getWorkRequestCount(); btCollisionShape* shape = const_cast(ObjectMotionState::getShapeManager()->getShape(shapeInfo)); if (shape) { - EntityMotionState* motionState = static_cast(entity->getPhysicsInfo()); - if (!motionState) { - buildMotionState(shape, entity); - } else { - // Is it possible to fall in here? - // entity shouldn't be on _entitiesToAddToPhysics list if it already has a motionState. - // but just in case... - motionState->setShape(shape); - motionState->setRegion(_space->getRegion(entity->getSpaceIndex())); - _physicalObjects.insert(motionState); - _incomingChanges.insert(motionState); - } + buildMotionState(shape, entity); } else if (requestCount != ObjectMotionState::getShapeManager()->getWorkRequestCount()) { // shape doesn't exist but a new worker has been spawned to build it --> add to shapeRequests and wait shapeRequest.shapeHash = shapeInfo.getHash(); @@ -354,6 +351,7 @@ void PhysicalEntitySimulation::buildMotionStatesForEntitiesThatNeedThem() { } entityItr = _entitiesToAddToPhysics.erase(entityItr); } else { + // skip for later ++entityItr; } } diff --git a/libraries/physics/src/ThreadSafeDynamicsWorld.cpp b/libraries/physics/src/ThreadSafeDynamicsWorld.cpp index f6189121a9..00b8a71831 100644 --- a/libraries/physics/src/ThreadSafeDynamicsWorld.cpp +++ b/libraries/physics/src/ThreadSafeDynamicsWorld.cpp @@ -181,7 +181,7 @@ void ThreadSafeDynamicsWorld::drawConnectedSpheres(btIDebugDraw* drawer, btScala btVector3 xAxis = direction.cross(btVector3(0.0f, 1.0f, 0.0f)); xAxis = xAxis.length() < EPSILON ? btVector3(1.0f, 0.0f, 0.0f) : xAxis.normalize(); btVector3 zAxis = xAxis.cross(btVector3(0.0f, 1.0f, 0.0f)); - zAxis = (direction.normalize().getY() < EPSILON) ? btVector3(0.0f, 1.0f, 0.0f) : zAxis.normalize(); + zAxis = (direction.length2() < EPSILON || direction.normalize().getY() < EPSILON) ? btVector3(0.0f, 1.0f, 0.0f) : zAxis.normalize(); float fullCircle = 2.0f * PI; for (float i = 0; i < fullCircle; i += stepRadians) { float x1 = btSin(btScalar(i)) * radius1; diff --git a/libraries/platform/src/platform/Platform.h b/libraries/platform/src/platform/Platform.h index 7f73ff4ff4..9405c77ae0 100644 --- a/libraries/platform/src/platform/Platform.h +++ b/libraries/platform/src/platform/Platform.h @@ -27,12 +27,13 @@ json getGPU(int index); int getNumDisplays(); json getDisplay(int index); - -int getNumMemories(); -json getMemory(int index); + +json getMemory(); json getComputer(); +json getAll(); + } // namespace platform #endif // hifi_platform_h diff --git a/libraries/platform/src/platform/PlatformKeys.h b/libraries/platform/src/platform/PlatformKeys.h index fd29b2ff7f..1008c5ca4b 100644 --- a/libraries/platform/src/platform/PlatformKeys.h +++ b/libraries/platform/src/platform/PlatformKeys.h @@ -9,6 +9,9 @@ #define hifi_platform_PlatformKeys_h namespace platform { namespace keys{ + // "UNKNOWN" + extern const char* UNKNOWN; + namespace cpu { extern const char* vendor; extern const char* vendor_Intel; @@ -36,8 +39,9 @@ namespace platform { namespace keys{ extern const char* coordsTop; extern const char* coordsBottom; } + namespace memory { extern const char* memTotal; - + } namespace computer { extern const char* OS; extern const char* OS_WINDOWS; @@ -45,6 +49,8 @@ namespace platform { namespace keys{ extern const char* OS_LINUX; extern const char* OS_ANDROID; + extern const char* OSVersion; + extern const char* vendor; extern const char* vendor_Apple; @@ -52,6 +58,14 @@ namespace platform { namespace keys{ extern const char* profileTier; } - } } // namespace plaform::keys + + // Keys for categories used in json returned by getAll() + extern const char* CPUS; + extern const char* GPUS; + extern const char* DISPLAYS; + extern const char* MEMORY; + extern const char* COMPUTER; + +} } // namespace plaform::keys #endif diff --git a/libraries/platform/src/platform/backend/AndroidPlatform.cpp b/libraries/platform/src/platform/backend/AndroidPlatform.cpp index ee5a7e39b9..b0a4c5e67b 100644 --- a/libraries/platform/src/platform/backend/AndroidPlatform.cpp +++ b/libraries/platform/src/platform/backend/AndroidPlatform.cpp @@ -9,39 +9,45 @@ #include "AndroidPlatform.h" #include "../PlatformKeys.h" #include +#include using namespace platform; -void AndroidInstance::enumerateCpu() { +void AndroidInstance::enumerateCpus() { json cpu; cpu[keys::cpu::vendor] = ""; cpu[keys::cpu::model] = ""; cpu[keys::cpu::clockSpeed] = ""; cpu[keys::cpu::numCores] = 0; - _cpu.push_back(cpu); + + _cpus.push_back(cpu); } -void AndroidInstance::enumerateGpu() { +void AndroidInstance::enumerateGpus() { GPUIdent* ident = GPUIdent::getInstance(); json gpu = {}; - gpu[keys::gpu::vendor] = ident->getName().toUtf8().constData(); gpu[keys::gpu::model] = ident->getName().toUtf8().constData(); + gpu[keys::gpu::vendor] = findGPUVendorInDescription(gpu[keys::gpu::model].get()); gpu[keys::gpu::videoMemory] = ident->getMemory(); gpu[keys::gpu::driver] = ident->getDriver().toUtf8().constData(); - _gpu.push_back(gpu); - _display = ident->getOutput(); + _gpus.push_back(gpu); + _displays = ident->getOutput(); } void AndroidInstance::enumerateMemory() { json ram = {}; - ram[keys::memTotal]=0; - _memory.push_back(ram); + ram[keys::memory::memTotal]=0; + _memory = ram; } void AndroidInstance::enumerateComputer(){ _computer[keys::computer::OS] = keys::computer::OS_ANDROID; _computer[keys::computer::vendor] = ""; _computer[keys::computer::model] = ""; + + auto sysInfo = QSysInfo(); + + _computer[keys::computer::OSVersion] = sysInfo.kernelVersion().toStdString(); } diff --git a/libraries/platform/src/platform/backend/AndroidPlatform.h b/libraries/platform/src/platform/backend/AndroidPlatform.h index d1496383c0..6592b3519d 100644 --- a/libraries/platform/src/platform/backend/AndroidPlatform.h +++ b/libraries/platform/src/platform/backend/AndroidPlatform.h @@ -15,10 +15,10 @@ namespace platform { class AndroidInstance : public Instance { public: - void enumerateCpu() override; + void enumerateCpus() override; + void enumerateGpus() override; void enumerateMemory() override; - void enumerateGpu() override; - void enumerateComputer () override; + void enumerateComputer() override; }; } // namespace platform diff --git a/libraries/platform/src/platform/backend/LinuxPlatform.cpp b/libraries/platform/src/platform/backend/LinuxPlatform.cpp index 356df27e0a..61501669cb 100644 --- a/libraries/platform/src/platform/backend/LinuxPlatform.cpp +++ b/libraries/platform/src/platform/backend/LinuxPlatform.cpp @@ -13,36 +13,37 @@ #include #include #include +#include using namespace platform; -void LinuxInstance::enumerateCpu() { +void LinuxInstance::enumerateCpus() { json cpu = {}; cpu[keys::cpu::vendor] = CPUIdent::Vendor(); cpu[keys::cpu::model] = CPUIdent::Brand(); cpu[keys::cpu::numCores] = std::thread::hardware_concurrency(); - _cpu.push_back(cpu); + _cpus.push_back(cpu); } -void LinuxInstance::enumerateGpu() { +void LinuxInstance::enumerateGpus() { GPUIdent* ident = GPUIdent::getInstance(); json gpu = {}; - gpu[keys::gpu::vendor] = ident->getName().toUtf8().constData(); gpu[keys::gpu::model] = ident->getName().toUtf8().constData(); + gpu[keys::gpu::vendor] = findGPUVendorInDescription(gpu[keys::gpu::model].get()); gpu[keys::gpu::videoMemory] = ident->getMemory(); gpu[keys::gpu::driver] = ident->getDriver().toUtf8().constData(); - _gpu.push_back(gpu); - _display = ident->getOutput(); + _gpus.push_back(gpu); + _displays = ident->getOutput(); } void LinuxInstance::enumerateMemory() { json ram = {}; - ram[keys::memTotal]=0; + ram[keys::memory::memTotal]=0; - _memory.push_back(ram); + _memory = ram; } void LinuxInstance::enumerateComputer(){ @@ -50,5 +51,9 @@ void LinuxInstance::enumerateComputer(){ _computer[keys::computer::OS] = keys::computer::OS_LINUX; _computer[keys::computer::vendor] = ""; _computer[keys::computer::model] = ""; + + auto sysInfo = QSysInfo(); + + _computer[keys::computer::OSVersion] = sysInfo.kernelVersion().toStdString(); } diff --git a/libraries/platform/src/platform/backend/LinuxPlatform.h b/libraries/platform/src/platform/backend/LinuxPlatform.h index 1629101f41..2f2529db7c 100644 --- a/libraries/platform/src/platform/backend/LinuxPlatform.h +++ b/libraries/platform/src/platform/backend/LinuxPlatform.h @@ -15,10 +15,10 @@ namespace platform { class LinuxInstance : public Instance { public: - void enumerateCpu() override; + void enumerateCpus() override; + void enumerateGpus() override; void enumerateMemory() override; - void enumerateGpu() override; - void enumerateComputer () override; + void enumerateComputer() override; }; } // namespace platform diff --git a/libraries/platform/src/platform/backend/MACOSPlatform.cpp b/libraries/platform/src/platform/backend/MACOSPlatform.cpp index 7dbc403783..cacbd06816 100644 --- a/libraries/platform/src/platform/backend/MACOSPlatform.cpp +++ b/libraries/platform/src/platform/backend/MACOSPlatform.cpp @@ -21,32 +21,33 @@ #include #include +#include #endif using namespace platform; -void MACOSInstance::enumerateCpu() { +void MACOSInstance::enumerateCpus() { json cpu = {}; cpu[keys::cpu::vendor] = CPUIdent::Vendor(); cpu[keys::cpu::model] = CPUIdent::Brand(); cpu[keys::cpu::numCores] = std::thread::hardware_concurrency(); - _cpu.push_back(cpu); + _cpus.push_back(cpu); } -void MACOSInstance::enumerateGpu() { +void MACOSInstance::enumerateGpus() { #ifdef Q_OS_MAC GPUIdent* ident = GPUIdent::getInstance(); json gpu = {}; - gpu[keys::gpu::vendor] = ident->getName().toUtf8().constData(); gpu[keys::gpu::model] = ident->getName().toUtf8().constData(); + gpu[keys::gpu::vendor] = findGPUVendorInDescription(gpu[keys::gpu::model].get()); gpu[keys::gpu::videoMemory] = ident->getMemory(); gpu[keys::gpu::driver] = ident->getDriver().toUtf8().constData(); - _gpu.push_back(gpu); + _gpus.push_back(gpu); #endif @@ -101,7 +102,7 @@ void MACOSInstance::enumerateDisplays() { display["modeWidth"] = displayModeWidth; display["modeHeight"] = displayModeHeight; - _display.push_back(display); + _displays.push_back(display); #endif } @@ -111,9 +112,9 @@ void MACOSInstance::enumerateMemory() { #ifdef Q_OS_MAC long pages = sysconf(_SC_PHYS_PAGES); long page_size = sysconf(_SC_PAGE_SIZE); - ram[keys::memTotal] = pages * page_size; + ram[keys::memory::memTotal] = pages * page_size; #endif - _memory.push_back(ram); + _memory = ram; } void MACOSInstance::enumerateComputer(){ @@ -133,5 +134,9 @@ void MACOSInstance::enumerateComputer(){ free(model); #endif + + auto sysInfo = QSysInfo(); + + _computer[keys::computer::OSVersion] = sysInfo.kernelVersion().toStdString(); } diff --git a/libraries/platform/src/platform/backend/MACOSPlatform.h b/libraries/platform/src/platform/backend/MACOSPlatform.h index 4a257d8be5..e893dda739 100644 --- a/libraries/platform/src/platform/backend/MACOSPlatform.h +++ b/libraries/platform/src/platform/backend/MACOSPlatform.h @@ -15,11 +15,11 @@ namespace platform { class MACOSInstance : public Instance { public: - void enumerateCpu() override; - void enumerateMemory() override; - void enumerateGpu() override; + void enumerateCpus() override; + void enumerateGpus() override; void enumerateDisplays() override; - void enumerateComputer () override; + void enumerateMemory() override; + void enumerateComputer() override; }; } // namespace platform diff --git a/libraries/platform/src/platform/backend/Platform.cpp b/libraries/platform/src/platform/backend/Platform.cpp index 8e9fda30ed..dba41ce121 100644 --- a/libraries/platform/src/platform/backend/Platform.cpp +++ b/libraries/platform/src/platform/backend/Platform.cpp @@ -11,6 +11,8 @@ #include "../PlatformKeys.h" namespace platform { namespace keys { + const char* UNKNOWN = "UNKNOWN"; + namespace cpu { const char* vendor = "vendor"; const char* vendor_Intel = "Intel"; @@ -38,8 +40,9 @@ namespace platform { namespace keys { const char* coordsTop = "coordinatestop"; const char* coordsBottom = "coordinatesbottom"; } - const char* memTotal = "memTotal"; - + namespace memory { + const char* memTotal = "memTotal"; + } namespace computer { const char* OS = "OS"; const char* OS_WINDOWS = "WINDOWS"; @@ -47,6 +50,8 @@ namespace platform { namespace keys { const char* OS_LINUX = "LINUX"; const char* OS_ANDROID = "ANDROID"; + const char* OSVersion = "OSVersion"; + const char* vendor = "vendor"; const char* vendor_Apple = "Apple"; @@ -54,6 +59,12 @@ namespace platform { namespace keys { const char* profileTier = "profileTier"; } + + const char* CPUS = "cpus"; + const char* GPUS = "gpus"; + const char* DISPLAYS = "displays"; + const char* MEMORY = "memory"; + const char* COMPUTER = "computer"; }} #include @@ -117,14 +128,14 @@ json platform::getDisplay(int index) { return _instance->getDisplay(index); } -int platform::getNumMemories() { - return _instance->getNumMemories(); +json platform::getMemory() { + return _instance->getMemory(); } -json platform::getMemory(int index) { - return _instance->getMemory(index); -} - -json platform::getComputer(){ +json platform::getComputer() { return _instance->getComputer(); } + +json platform::getAll() { + return _instance->getAll(); +} diff --git a/libraries/platform/src/platform/backend/PlatformInstance.cpp b/libraries/platform/src/platform/backend/PlatformInstance.cpp index 3dd3e5f592..41786bca1f 100644 --- a/libraries/platform/src/platform/backend/PlatformInstance.cpp +++ b/libraries/platform/src/platform/backend/PlatformInstance.cpp @@ -16,10 +16,10 @@ using namespace platform; bool Instance::enumeratePlatform() { enumerateComputer(); - enumerateCpu(); - enumerateGpu(); - enumerateDisplays(); enumerateMemory(); + enumerateCpus(); + enumerateGpus(); + enumerateDisplays(); // And profile the platform and put the tier in "computer" _computer[keys::computer::profileTier] = Profiler::TierNames[Profiler::profilePlatform()]; @@ -28,55 +28,42 @@ bool Instance::enumeratePlatform() { } json Instance::getCPU(int index) { - assert(index <(int) _cpu.size()); - if (index >= (int)_cpu.size()) + assert(index <(int) _cpus.size()); + if (index >= (int)_cpus.size()) return json(); - return _cpu.at(index); -} - -//These are ripe for template.. will work on that next -json Instance::getMemory(int index) { - assert(index <(int) _memory.size()); - if(index >= (int)_memory.size()) - return json(); - - return _memory.at(index); + return _cpus.at(index); } json Instance::getGPU(int index) { - assert(index <(int) _gpu.size()); + assert(index <(int) _gpus.size()); - if (index >=(int) _gpu.size()) + if (index >=(int) _gpus.size()) return json(); - return _gpu.at(index); + return _gpus.at(index); } json Instance::getDisplay(int index) { - assert(index <(int) _display.size()); + assert(index <(int) _displays.size()); - if (index >=(int) _display.size()) + if (index >=(int) _displays.size()) return json(); - return _display.at(index); + return _displays.at(index); } Instance::~Instance() { - if (_cpu.size() > 0) { - _cpu.clear(); + if (_cpus.size() > 0) { + _cpus.clear(); } - if (_memory.size() > 0) { - _memory.clear(); + if (_gpus.size() > 0) { + _gpus.clear(); } - if (_gpu.size() > 0) { - _gpu.clear(); - } - - if (_display.size() > 0) { - _display.clear(); + if (_displays.size() > 0) { + _displays.clear(); } } @@ -106,17 +93,53 @@ json Instance::listAllKeys() { keys::display::coordsTop, keys::display::coordsBottom, - keys::memTotal, + keys::memory::memTotal, keys::computer::OS, keys::computer::OS_WINDOWS, keys::computer::OS_MACOS, keys::computer::OS_LINUX, keys::computer::OS_ANDROID, + keys::computer::OSVersion, keys::computer::vendor, keys::computer::vendor_Apple, keys::computer::model, - keys::computer::profileTier + keys::computer::profileTier, + + keys::CPUS, + keys::GPUS, + keys::DISPLAYS, + keys::MEMORY, + keys::COMPUTER, }}); return allKeys; } + +const char* Instance::findGPUVendorInDescription(const std::string& description) { + // intel integrated graphics + if (description.find(keys::gpu::vendor_Intel) != std::string::npos) { + return keys::gpu::vendor_Intel; + } + // AMD gpu + else if ((description.find(keys::gpu::vendor_AMD) != std::string::npos) || (description.find("Radeon") != std::string::npos)) { + return keys::gpu::vendor_AMD; + } + // NVIDIA gpu + else if (description.find(keys::gpu::vendor_NVIDIA) != std::string::npos) { + return keys::gpu::vendor_NVIDIA; + } else { + return keys::UNKNOWN; + } +} + +json Instance::getAll() { + json all = {}; + + all[keys::COMPUTER] = _computer; + all[keys::MEMORY] = _memory; + all[keys::CPUS] = _cpus; + all[keys::GPUS] = _gpus; + all[keys::DISPLAYS] = _displays; + + return all; +} diff --git a/libraries/platform/src/platform/backend/PlatformInstance.h b/libraries/platform/src/platform/backend/PlatformInstance.h index 95eb2ef25e..b7983446f5 100644 --- a/libraries/platform/src/platform/backend/PlatformInstance.h +++ b/libraries/platform/src/platform/backend/PlatformInstance.h @@ -19,36 +19,39 @@ class Instance { public: bool virtual enumeratePlatform(); - int getNumCPUs() { return (int)_cpu.size(); } + int getNumCPUs() { return (int)_cpus.size(); } json getCPU(int index); - int getNumGPUs() { return (int)_gpu.size(); } + int getNumGPUs() { return (int)_gpus.size(); } json getGPU(int index); - int getNumMemories() { return (int)_memory.size(); } - json getMemory(int index); - - int getNumDisplays() { return (int)_display.size(); } + int getNumDisplays() { return (int)_displays.size(); } json getDisplay(int index); + json getMemory() { return _memory; } + + json getComputer() { return _computer; } - json getComputer() {return _computer;} - - void virtual enumerateCpu()=0; - void virtual enumerateMemory()=0; - void virtual enumerateGpu()=0; + json getAll(); + + void virtual enumerateCpus()=0; + void virtual enumerateGpus()=0; void virtual enumerateDisplays() {} + void virtual enumerateMemory() = 0; void virtual enumerateComputer()=0; virtual ~Instance(); static json listAllKeys(); + // Helper function to filter the vendor name out of the description of a GPU + static const char* findGPUVendorInDescription(const std::string& description); + protected: - std::vector _cpu; - std::vector _memory; - std::vector _gpu; - std::vector _display; + std::vector _cpus; + std::vector _gpus; + std::vector _displays; + json _memory; json _computer; }; diff --git a/libraries/platform/src/platform/backend/WINPlatform.cpp b/libraries/platform/src/platform/backend/WINPlatform.cpp index e34d87d853..9cf01ce4e0 100644 --- a/libraries/platform/src/platform/backend/WINPlatform.cpp +++ b/libraries/platform/src/platform/backend/WINPlatform.cpp @@ -16,32 +16,33 @@ #ifdef Q_OS_WIN #include +#include #endif using namespace platform; -void WINInstance::enumerateCpu() { +void WINInstance::enumerateCpus() { json cpu = {}; cpu[keys::cpu::vendor] = CPUIdent::Vendor(); cpu[keys::cpu::model] = CPUIdent::Brand(); cpu[keys::cpu::numCores] = std::thread::hardware_concurrency(); - _cpu.push_back(cpu); + _cpus.push_back(cpu); } -void WINInstance::enumerateGpu() { +void WINInstance::enumerateGpus() { GPUIdent* ident = GPUIdent::getInstance(); json gpu = {}; - gpu[keys::gpu::vendor] = ident->getName().toUtf8().constData(); gpu[keys::gpu::model] = ident->getName().toUtf8().constData(); + gpu[keys::gpu::vendor] = findGPUVendorInDescription(gpu[keys::gpu::model].get()); gpu[keys::gpu::videoMemory] = ident->getMemory(); gpu[keys::gpu::driver] = ident->getDriver().toUtf8().constData(); - _gpu.push_back(gpu); - _display = ident->getOutput(); + _gpus.push_back(gpu); + _displays = ident->getOutput(); } void WINInstance::enumerateMemory() { @@ -52,9 +53,9 @@ void WINInstance::enumerateMemory() { statex.dwLength = sizeof(statex); GlobalMemoryStatusEx(&statex); int totalRam = statex.ullTotalPhys / 1024 / 1024; - ram[platform::keys::memTotal] = totalRam; + ram[platform::keys::memory::memTotal] = totalRam; #endif - _memory.push_back(ram); + _memory = ram; } void WINInstance::enumerateComputer(){ @@ -62,5 +63,8 @@ void WINInstance::enumerateComputer(){ _computer[keys::computer::vendor] = ""; _computer[keys::computer::model] = ""; + auto sysInfo = QSysInfo(); + + _computer[keys::computer::OSVersion] = sysInfo.kernelVersion().toStdString(); } diff --git a/libraries/platform/src/platform/backend/WINPlatform.h b/libraries/platform/src/platform/backend/WINPlatform.h index e540335d94..cb6d3f482f 100644 --- a/libraries/platform/src/platform/backend/WINPlatform.h +++ b/libraries/platform/src/platform/backend/WINPlatform.h @@ -15,9 +15,9 @@ namespace platform { class WINInstance : public Instance { public: - void enumerateCpu() override; + void enumerateCpus() override; + void enumerateGpus() override; void enumerateMemory() override; - void enumerateGpu() override; void enumerateComputer () override; }; } // namespace platform diff --git a/libraries/plugins/src/plugins/DisplayPlugin.cpp b/libraries/plugins/src/plugins/DisplayPlugin.cpp index 47503e8f85..2fe3d5fbea 100644 --- a/libraries/plugins/src/plugins/DisplayPlugin.cpp +++ b/libraries/plugins/src/plugins/DisplayPlugin.cpp @@ -35,15 +35,6 @@ void DisplayPlugin::waitForPresent() { } } -std::function DisplayPlugin::getHUDOperator() { - std::function hudOperator; - { - QMutexLocker locker(&_presentMutex); - hudOperator = _hudOperator; - } - return hudOperator; -} - glm::mat4 HmdDisplay::getEyeToHeadTransform(Eye eye) const { static const glm::mat4 xform; return xform; diff --git a/libraries/plugins/src/plugins/DisplayPlugin.h b/libraries/plugins/src/plugins/DisplayPlugin.h index 9dc1d7002d..1cad9b1e11 100644 --- a/libraries/plugins/src/plugins/DisplayPlugin.h +++ b/libraries/plugins/src/plugins/DisplayPlugin.h @@ -213,13 +213,12 @@ public: void waitForPresent(); float getAveragePresentTime() { return _movingAveragePresent.average / (float)USECS_PER_MSEC; } // in msec - std::function getHUDOperator(); - static const QString& MENU_PATH(); // for updating plugin-related commands. Mimics the input plugin. virtual void pluginUpdate() = 0; + virtual std::function getHUDOperator() { return nullptr; } virtual StencilMaskMode getStencilMaskMode() const { return StencilMaskMode::NONE; } using StencilMaskMeshOperator = std::function; virtual StencilMaskMeshOperator getStencilMaskMeshOperator() { return nullptr; } @@ -234,8 +233,6 @@ protected: gpu::ContextPointer _gpuContext; - std::function _hudOperator { std::function() }; - MovingAverage _movingAveragePresent; float _renderResolutionScale { 1.0f }; diff --git a/libraries/pointers/src/PickManager.cpp b/libraries/pointers/src/PickManager.cpp index 0cf5f90e3d..aadfbc41a0 100644 --- a/libraries/pointers/src/PickManager.cpp +++ b/libraries/pointers/src/PickManager.cpp @@ -122,22 +122,22 @@ void PickManager::update() { // FIXME: give each type its own expiry // Each type will update at least one pick, regardless of the expiry { - PROFILE_RANGE(picks, "StylusPicks"); + PROFILE_RANGE_EX(picks, "StylusPicks", 0xffff0000, (uint64_t)_totalPickCounts[PickQuery::Stylus]); PerformanceTimer perfTimer("StylusPicks"); _updatedPickCounts[PickQuery::Stylus] = _stylusPickCacheOptimizer.update(cachedPicks[PickQuery::Stylus], _nextPickToUpdate[PickQuery::Stylus], expiry, false); } { - PROFILE_RANGE(picks, "RayPicks"); + PROFILE_RANGE_EX(picks, "RayPicks", 0xffff0000, (uint64_t)_totalPickCounts[PickQuery::Ray]); PerformanceTimer perfTimer("RayPicks"); _updatedPickCounts[PickQuery::Ray] = _rayPickCacheOptimizer.update(cachedPicks[PickQuery::Ray], _nextPickToUpdate[PickQuery::Ray], expiry, shouldPickHUD); } { - PROFILE_RANGE(picks, "ParabolaPick"); - PerformanceTimer perfTimer("ParabolaPick"); + PROFILE_RANGE_EX(picks, "ParabolaPicks", 0xffff0000, (uint64_t)_totalPickCounts[PickQuery::Parabola]); + PerformanceTimer perfTimer("ParabolaPicks"); _updatedPickCounts[PickQuery::Parabola] = _parabolaPickCacheOptimizer.update(cachedPicks[PickQuery::Parabola], _nextPickToUpdate[PickQuery::Parabola], expiry, shouldPickHUD); } { - PROFILE_RANGE(picks, "CollisoinPicks"); + PROFILE_RANGE_EX(picks, "CollisionPicks", 0xffff0000, (uint64_t)_totalPickCounts[PickQuery::Collision]); PerformanceTimer perfTimer("CollisionPicks"); _updatedPickCounts[PickQuery::Collision] = _collisionPickCacheOptimizer.update(cachedPicks[PickQuery::Collision], _nextPickToUpdate[PickQuery::Collision], expiry, false); } diff --git a/libraries/qml/src/qml/impl/RenderEventHandler.cpp b/libraries/qml/src/qml/impl/RenderEventHandler.cpp index a1edfd6789..cc9fe34edc 100644 --- a/libraries/qml/src/qml/impl/RenderEventHandler.cpp +++ b/libraries/qml/src/qml/impl/RenderEventHandler.cpp @@ -49,8 +49,8 @@ bool RenderEventHandler::event(QEvent* e) { return QObject::event(e); } -RenderEventHandler::RenderEventHandler(SharedObject* shared, QThread* targetThread) - : _shared(shared) { +RenderEventHandler::RenderEventHandler(SharedObject* shared, QThread* targetThread) : + _shared(shared) { // Create the GL canvas in the same thread as the share canvas if (!_canvas.create(SharedObject::getSharedContext())) { qFatal("Unable to create new offscreen GL context"); @@ -136,7 +136,8 @@ void RenderEventHandler::qmlRender(bool sceneGraphSync) { resize(); - { + + if (_currentSize != QSize()) { PROFILE_RANGE(render_qml_gl, "render"); GLuint texture = SharedObject::getTextureCache().acquireTexture(_currentSize); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, _fbo); @@ -146,7 +147,7 @@ void RenderEventHandler::qmlRender(bool sceneGraphSync) { glClear(GL_COLOR_BUFFER_BIT); } else { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - _shared->_quickWindow->setRenderTarget(_fbo, _currentSize); + _shared->setRenderTarget(_fbo, _currentSize); _shared->_renderControl->render(); } _shared->_lastRenderTime = usecTimestampNow(); @@ -179,7 +180,7 @@ void RenderEventHandler::onQuit() { _fbo = 0; } - _shared->shutdownRendering(_canvas, _currentSize); + _shared->shutdownRendering(_currentSize); _canvas.doneCurrent(); } _canvas.moveToThreadWithContext(qApp->thread()); diff --git a/libraries/qml/src/qml/impl/SharedObject.cpp b/libraries/qml/src/qml/impl/SharedObject.cpp index b72f37481b..55788c8a02 100644 --- a/libraries/qml/src/qml/impl/SharedObject.cpp +++ b/libraries/qml/src/qml/impl/SharedObject.cpp @@ -78,7 +78,6 @@ SharedObject::SharedObject() { QObject::connect(qApp, &QCoreApplication::aboutToQuit, this, &SharedObject::onAboutToQuit); } - SharedObject::~SharedObject() { // After destroy returns, the rendering thread should be gone destroy(); @@ -173,7 +172,6 @@ void SharedObject::setRootItem(QQuickItem* rootItem) { QObject::connect(_renderControl, &QQuickRenderControl::renderRequested, this, &SharedObject::requestRender); QObject::connect(_renderControl, &QQuickRenderControl::sceneChanged, this, &SharedObject::requestRenderSync); #endif - } void SharedObject::destroy() { @@ -210,7 +208,7 @@ void SharedObject::destroy() { } // Block until the rendering thread has stopped // FIXME this is undesirable because this is blocking the main thread, - // but I haven't found a reliable way to do this only at application + // but I haven't found a reliable way to do this only at application // shutdown if (_renderThread) { _renderThread->wait(); @@ -220,10 +218,8 @@ void SharedObject::destroy() { #endif } - #define SINGLE_QML_ENGINE 0 - #if SINGLE_QML_ENGINE static QQmlEngine* globalEngine{ nullptr }; static size_t globalEngineRefCount{ 0 }; @@ -344,6 +340,11 @@ void SharedObject::setSize(const QSize& size) { #endif } +void SharedObject::setMaxFps(uint8_t maxFps) { + QMutexLocker locker(&_mutex); + _maxFps = maxFps; +} + bool SharedObject::preRender(bool sceneGraphSync) { #ifndef DISABLE_QML QMutexLocker lock(&_mutex); @@ -370,9 +371,9 @@ bool SharedObject::preRender(bool sceneGraphSync) { return true; } -void SharedObject::shutdownRendering(OffscreenGLCanvas& canvas, const QSize& size) { +void SharedObject::shutdownRendering(const QSize& size) { QMutexLocker locker(&_mutex); - if (size != QSize(0, 0)) { + if (size != QSize()) { getTextureCache().releaseSize(size); if (_latestTextureAndFence.first) { getTextureCache().releaseTexture(_latestTextureAndFence); @@ -380,19 +381,17 @@ void SharedObject::shutdownRendering(OffscreenGLCanvas& canvas, const QSize& siz } #ifndef DISABLE_QML _renderControl->invalidate(); - canvas.doneCurrent(); #endif wake(); } -bool SharedObject::isQuit() { +bool SharedObject::isQuit() const { QMutexLocker locker(&_mutex); return _quit; } void SharedObject::requestRender() { - // Don't queue multiple renders - if (_renderRequested) { + if (_quit) { return; } _renderRequested = true; @@ -402,18 +401,13 @@ void SharedObject::requestRenderSync() { if (_quit) { return; } - - { - QMutexLocker lock(&_mutex); - _syncRequested = true; - } - - requestRender(); + _renderRequested = true; + _syncRequested = true; } bool SharedObject::fetchTexture(TextureAndFence& textureAndFence) { QMutexLocker locker(&_mutex); - if (0 == _latestTextureAndFence.first) { + if (!_latestTextureAndFence.first) { return false; } textureAndFence = { 0, 0 }; @@ -421,8 +415,7 @@ bool SharedObject::fetchTexture(TextureAndFence& textureAndFence) { return true; } -void hifi::qml::impl::SharedObject::addToDeletionList(QObject * object) -{ +void SharedObject::addToDeletionList(QObject* object) { _deletionList.append(QPointer(object)); } @@ -469,11 +462,9 @@ void SharedObject::onRender() { return; } - QMutexLocker lock(&_mutex); if (_syncRequested) { - lock.unlock(); _renderControl->polishItems(); - lock.relock(); + QMutexLocker lock(&_mutex); QCoreApplication::postEvent(_renderObject, new OffscreenEvent(OffscreenEvent::RenderSync)); // sync and render request, main and render threads must be synchronized wait(); @@ -494,13 +485,11 @@ void SharedObject::onTimer() { { QMutexLocker locker(&_mutex); // Don't queue more than one frame at a time - if (0 != _latestTextureAndFence.first) { + if (_latestTextureAndFence.first) { return; } - } - { - if (_maxFps == 0) { + if (!_maxFps) { return; } auto minRenderInterval = USECS_PER_SECOND / _maxFps; diff --git a/libraries/qml/src/qml/impl/SharedObject.h b/libraries/qml/src/qml/impl/SharedObject.h index c9c0ef7bd0..50c56ad714 100644 --- a/libraries/qml/src/qml/impl/SharedObject.h +++ b/libraries/qml/src/qml/impl/SharedObject.h @@ -16,7 +16,6 @@ #include "TextureCache.h" - class QWindow; class QTimer; class QQuickWindow; @@ -24,7 +23,6 @@ class QQuickItem; class QOpenGLContext; class QQmlEngine; class QQmlContext; -class OffscreenGLCanvas; namespace hifi { namespace qml { @@ -51,11 +49,11 @@ public: void create(OffscreenSurface* surface); void setRootItem(QQuickItem* rootItem); void destroy(); - bool isQuit(); + bool isQuit() const; QSize getSize() const; void setSize(const QSize& size); - void setMaxFps(uint8_t maxFps) { _maxFps = maxFps; } + void setMaxFps(uint8_t maxFps); QQuickWindow* getWindow() { return _quickWindow; } QQuickItem* getRootItem() { return _rootItem; } @@ -72,7 +70,7 @@ private: bool event(QEvent* e) override; bool preRender(bool sceneGraphSync); - void shutdownRendering(OffscreenGLCanvas& canvas, const QSize& size); + void shutdownRendering(const QSize& size); // Called by the render event handler, from the render thread void initializeRenderControl(QOpenGLContext* context); void releaseTextureAndFence(); @@ -94,31 +92,30 @@ private: QList> _deletionList; // Texture management - TextureAndFence _latestTextureAndFence{ 0, 0 }; - QQuickItem* _item{ nullptr }; - QQuickItem* _rootItem{ nullptr }; - QQuickWindow* _quickWindow{ nullptr }; - QQmlContext* _qmlContext{ nullptr }; + TextureAndFence _latestTextureAndFence { 0, 0 }; + QQuickItem* _rootItem { nullptr }; + QQuickWindow* _quickWindow { nullptr }; + QQmlContext* _qmlContext { nullptr }; mutable QMutex _mutex; QWaitCondition _cond; #ifndef DISABLE_QML - QWindow* _proxyWindow{ nullptr }; - RenderControl* _renderControl{ nullptr }; - RenderEventHandler* _renderObject{ nullptr }; + QWindow* _proxyWindow { nullptr }; + RenderControl* _renderControl { nullptr }; + RenderEventHandler* _renderObject { nullptr }; - QTimer* _renderTimer{ nullptr }; - QThread* _renderThread{ nullptr }; + QTimer* _renderTimer { nullptr }; + QThread* _renderThread { nullptr }; #endif - uint64_t _lastRenderTime{ 0 }; - QSize _size{ 100, 100 }; - uint8_t _maxFps{ 60 }; + uint64_t _lastRenderTime { 0 }; + QSize _size { 100, 100 }; + uint8_t _maxFps { 60 }; - bool _renderRequested{ false }; - bool _syncRequested{ false }; - bool _quit{ false }; - bool _paused{ false }; + bool _renderRequested { false }; + bool _syncRequested { false }; + bool _quit { false }; + bool _paused { false }; }; } // namespace impl diff --git a/libraries/qml/src/qml/impl/TextureCache.h b/libraries/qml/src/qml/impl/TextureCache.h index c146d0bdbf..29f88955a4 100644 --- a/libraries/qml/src/qml/impl/TextureCache.h +++ b/libraries/qml/src/qml/impl/TextureCache.h @@ -35,9 +35,8 @@ public: using Size = uint64_t; struct TextureSet { - Size textureSize; // The number of surfaces with this size - size_t clientCount{ 0 }; + size_t clientCount { 0 }; ValueList returnedTextures; }; @@ -66,7 +65,7 @@ private: std::unordered_map _textureSizes; Mutex _mutex; std::list _returnedTextures; - size_t _totalTextureUsage{ 0 }; + size_t _totalTextureUsage { 0 }; }; }}} // namespace hifi::qml::impl diff --git a/libraries/recording/src/recording/ClipCache.cpp b/libraries/recording/src/recording/ClipCache.cpp index bc20e4d8eb..0fc65a2d79 100644 --- a/libraries/recording/src/recording/ClipCache.cpp +++ b/libraries/recording/src/recording/ClipCache.cpp @@ -18,7 +18,13 @@ using namespace recording; NetworkClipLoader::NetworkClipLoader(const QUrl& url) : Resource(url), - _clip(std::make_shared(url)) {} + _clip(std::make_shared(url)) { + if (url.isEmpty()) { + _loaded = false; + _startedLoading = false; + _failedToLoad = true; + } +} void NetworkClip::init(const QByteArray& clipData) { _clipData = clipData; diff --git a/libraries/render-utils/src/DeferredBufferWrite.slh b/libraries/render-utils/src/DeferredBufferWrite.slh index fc9310a520..de4581d66e 100644 --- a/libraries/render-utils/src/DeferredBufferWrite.slh +++ b/libraries/render-utils/src/DeferredBufferWrite.slh @@ -37,7 +37,6 @@ void packDeferredFragment(vec3 normal, float alpha, vec3 albedo, float roughness _fragColor0 = vec4(albedo, mix(packShadedMetallic(metallic), packScatteringMetallic(metallic), check)); _fragColor1 = vec4(packNormal(normal), clamp(roughness, 0.0, 1.0)); _fragColor2 = vec4(mix(emissive, vec3(scattering), check), occlusion); - _fragColor3 = vec4(isEmissiveEnabled() * emissive, 1.0); } @@ -49,7 +48,6 @@ void packDeferredFragmentLightmap(vec3 normal, float alpha, vec3 albedo, float r _fragColor0 = vec4(albedo, packLightmappedMetallic(metallic)); _fragColor1 = vec4(packNormal(normal), clamp(roughness, 0.0, 1.0)); _fragColor2 = vec4(isLightmapEnabled() * lightmap, 1.0); - _fragColor3 = vec4(isLightmapEnabled() * lightmap * albedo, 1.0); } @@ -59,7 +57,7 @@ void packDeferredFragmentUnlit(vec3 normal, float alpha, vec3 color) { } _fragColor0 = vec4(color, packUnlit()); _fragColor1 = vec4(packNormal(normal), 1.0); - // _fragColor2 = vec4(vec3(0.0), 1.0); + _fragColor2 = vec4(vec3(0.0), 1.0); _fragColor3 = vec4(color, 1.0); } @@ -69,7 +67,8 @@ void packDeferredFragmentTranslucent(vec3 normal, float alpha, vec3 albedo, floa } _fragColor0 = vec4(albedo.rgb, alpha); _fragColor1 = vec4(packNormal(normal), clamp(roughness, 0.0, 1.0)); - + _fragColor2 = vec4(vec3(0.0), 1.0); + _fragColor3 = vec4(0.0); } <@endif@> diff --git a/libraries/render-utils/src/DeferredLightingEffect.cpp b/libraries/render-utils/src/DeferredLightingEffect.cpp index 82b7f3102a..51729fc5cf 100644 --- a/libraries/render-utils/src/DeferredLightingEffect.cpp +++ b/libraries/render-utils/src/DeferredLightingEffect.cpp @@ -41,33 +41,17 @@ namespace gr { using namespace render; -struct LightLocations { - bool shadowTransform{ false }; - void initialize(const gpu::ShaderPointer& program) { - shadowTransform = program->getReflection().validUniformBuffer(ru::Buffer::ShadowParams); - } -}; - -static void loadLightProgram(int programId, bool lightVolume, gpu::PipelinePointer& program, LightLocationsPtr& locations); +static void loadLightProgram(int programId, bool lightVolume, gpu::PipelinePointer& program); void DeferredLightingEffect::init() { - _directionalAmbientSphereLightLocations = std::make_shared(); - _directionalSkyboxLightLocations = std::make_shared(); + loadLightProgram(shader::render_utils::program::directional_ambient_light, false, _directionalAmbientSphereLight); + loadLightProgram(shader::render_utils::program::directional_skybox_light, false, _directionalSkyboxLight); - _directionalAmbientSphereLightShadowLocations = std::make_shared(); - _directionalSkyboxLightShadowLocations = std::make_shared(); + loadLightProgram(shader::render_utils::program::directional_ambient_light_shadow, false, _directionalAmbientSphereLightShadow); + loadLightProgram(shader::render_utils::program::directional_skybox_light_shadow, false, _directionalSkyboxLightShadow); - _localLightLocations = std::make_shared(); - _localLightOutlineLocations = std::make_shared(); - - loadLightProgram(shader::render_utils::program::directional_ambient_light, false, _directionalAmbientSphereLight, _directionalAmbientSphereLightLocations); - loadLightProgram(shader::render_utils::program::directional_skybox_light, false, _directionalSkyboxLight, _directionalSkyboxLightLocations); - - loadLightProgram(shader::render_utils::program::directional_ambient_light_shadow, false, _directionalAmbientSphereLightShadow, _directionalAmbientSphereLightShadowLocations); - loadLightProgram(shader::render_utils::program::directional_skybox_light_shadow, false, _directionalSkyboxLightShadow, _directionalSkyboxLightShadowLocations); - - loadLightProgram(shader::render_utils::program::local_lights_shading, true, _localLight, _localLightLocations); - loadLightProgram(shader::render_utils::program::local_lights_drawOutline, true, _localLightOutline, _localLightOutlineLocations); + loadLightProgram(shader::render_utils::program::local_lights_shading, true, _localLight); + loadLightProgram(shader::render_utils::program::local_lights_drawOutline, true, _localLightOutline); } // FIXME: figure out how to move lightFrame into a varying in GeometryCache and RenderPipelines @@ -123,15 +107,9 @@ void DeferredLightingEffect::unsetLocalLightsBatch(gpu::Batch& batch) { batch.setUniformBuffer(ru::Buffer::LightClusterFrustumGrid, nullptr); } -static gpu::ShaderPointer makeLightProgram(int programId, LightLocationsPtr& locations) { +static void loadLightProgram(int programId, bool lightVolume, gpu::PipelinePointer& pipeline) { + gpu::ShaderPointer program = gpu::Shader::createProgram(programId); - locations->initialize(program); - return program; -} - -static void loadLightProgram(int programId, bool lightVolume, gpu::PipelinePointer& pipeline, LightLocationsPtr& locations) { - - gpu::ShaderPointer program = makeLightProgram(programId, locations); auto state = std::make_shared(); state->setColorWriteMask(true, true, true, false); @@ -456,7 +434,6 @@ void RenderDeferredSetup::run(const render::RenderContextPointer& renderContext, // Setup the global directional pass pipeline auto program = deferredLightingEffect->_directionalSkyboxLight; - LightLocationsPtr locations = deferredLightingEffect->_directionalSkyboxLightLocations; { if (keyLightCastShadows) { @@ -464,20 +441,16 @@ void RenderDeferredSetup::run(const render::RenderContextPointer& renderContext, // otherwise use the ambient sphere version if (hasAmbientMap) { program = deferredLightingEffect->_directionalSkyboxLightShadow; - locations = deferredLightingEffect->_directionalSkyboxLightShadowLocations; } else { program = deferredLightingEffect->_directionalAmbientSphereLightShadow; - locations = deferredLightingEffect->_directionalAmbientSphereLightShadowLocations; } } else { // If the keylight has an ambient Map then use the Skybox version of the pass // otherwise use the ambient sphere version if (hasAmbientMap) { program = deferredLightingEffect->_directionalSkyboxLight; - locations = deferredLightingEffect->_directionalSkyboxLightLocations; } else { program = deferredLightingEffect->_directionalAmbientSphereLight; - locations = deferredLightingEffect->_directionalAmbientSphereLightLocations; } } diff --git a/libraries/render-utils/src/DeferredLightingEffect.h b/libraries/render-utils/src/DeferredLightingEffect.h index 1cc6ca4767..84b3127443 100644 --- a/libraries/render-utils/src/DeferredLightingEffect.h +++ b/libraries/render-utils/src/DeferredLightingEffect.h @@ -37,10 +37,6 @@ #include "SubsurfaceScattering.h" #include "AmbientOcclusionEffect.h" - -struct LightLocations; -using LightLocationsPtr = std::shared_ptr; - // THis is where we currently accumulate the local lights, let s change that sooner than later class DeferredLightingEffect : public Dependency { SINGLETON_DEPENDENCY @@ -72,15 +68,6 @@ private: gpu::PipelinePointer _localLight; gpu::PipelinePointer _localLightOutline; - LightLocationsPtr _directionalSkyboxLightLocations; - LightLocationsPtr _directionalAmbientSphereLightLocations; - - LightLocationsPtr _directionalSkyboxLightShadowLocations; - LightLocationsPtr _directionalAmbientSphereLightShadowLocations; - - LightLocationsPtr _localLightLocations; - LightLocationsPtr _localLightOutlineLocations; - friend class LightClusteringPass; friend class RenderDeferredSetup; friend class RenderDeferredLocals; diff --git a/libraries/render-utils/src/RenderCommonTask.cpp b/libraries/render-utils/src/RenderCommonTask.cpp index 18532b7a66..ae53539770 100644 --- a/libraries/render-utils/src/RenderCommonTask.cpp +++ b/libraries/render-utils/src/RenderCommonTask.cpp @@ -111,7 +111,7 @@ void CompositeHUD::run(const RenderContextPointer& renderContext, const gpu::Fra assert(renderContext->args->_context); // We do not want to render HUD elements in secondary camera - if (renderContext->args->_renderMode == RenderArgs::RenderMode::SECONDARY_CAMERA_RENDER_MODE) { + if (nsightActive() || renderContext->args->_renderMode == RenderArgs::RenderMode::SECONDARY_CAMERA_RENDER_MODE) { return; } diff --git a/libraries/render-utils/src/deferred_light.slv b/libraries/render-utils/src/deferred_light.slv index 164fd9fb3b..2a68aa0e27 100644 --- a/libraries/render-utils/src/deferred_light.slv +++ b/libraries/render-utils/src/deferred_light.slv @@ -18,7 +18,7 @@ layout(location=RENDER_UTILS_ATTR_TEXCOORD01) out vec4 _texCoord01; void main(void) { const float depth = 1.0; - const vec4 UNIT_QUAD[4] = vec4[4]( + const mat4 UNIT_QUAD = mat4( vec4(-1.0, -1.0, depth, 1.0), vec4(1.0, -1.0, depth, 1.0), vec4(-1.0, 1.0, depth, 1.0), @@ -26,7 +26,7 @@ void main(void) { ); vec4 pos = UNIT_QUAD[gl_VertexID]; - _texCoord01.xy = (pos.xy + 1.0) * 0.5; + _texCoord01 = vec4((pos.xy + 1.0) * 0.5, 0.0, 0.0); gl_Position = pos; } diff --git a/libraries/render/src/render/Args.h b/libraries/render/src/render/Args.h index 7821692a60..1798208981 100644 --- a/libraries/render/src/render/Args.h +++ b/libraries/render/src/render/Args.h @@ -124,20 +124,20 @@ namespace render { DebugFlags _debugFlags { RENDER_DEBUG_NONE }; gpu::Batch* _batch = nullptr; - uint32_t _globalShapeKey{ 0 }; - uint32_t _itemShapeKey{ 0 }; - bool _enableTexturing{ true }; - bool _enableBlendshape{ true }; - bool _enableSkinning{ true }; + uint32_t _globalShapeKey { 0 }; + uint32_t _itemShapeKey { 0 }; + bool _enableTexturing { true }; + bool _enableBlendshape { true }; + bool _enableSkinning { true }; - bool _enableFade{ false }; + bool _enableFade { false }; RenderDetails _details; render::ScenePointer _scene; int8_t _cameraMode { -1 }; - std::function _hudOperator; - gpu::TexturePointer _hudTexture; + std::function _hudOperator { nullptr }; + gpu::TexturePointer _hudTexture { nullptr }; bool _takingSnapshot { false }; StencilMaskMode _stencilMaskMode { StencilMaskMode::NONE }; diff --git a/libraries/script-engine/src/RecordingScriptingInterface.cpp b/libraries/script-engine/src/RecordingScriptingInterface.cpp index f3bcbe6bb7..e57d80274a 100644 --- a/libraries/script-engine/src/RecordingScriptingInterface.cpp +++ b/libraries/script-engine/src/RecordingScriptingInterface.cpp @@ -93,7 +93,7 @@ void RecordingScriptingInterface::loadRecording(const QString& url, QScriptValue // when clip load fails, call the callback with the URL and failure boolean connect(clipLoader.data(), &recording::NetworkClipLoader::failed, callback.engine(), [this, weakClipLoader, url, callback](QNetworkReply::NetworkError error) mutable { - qCDebug(scriptengine) << "Failed to load recording from" << url; + qCDebug(scriptengine) << "Failed to load recording from\"" << url << '"'; if (callback.isFunction()) { QScriptValueList args { false, url }; diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index d820f0cb0f..02dcde3695 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -397,15 +397,20 @@ void ScriptEngine::executeOnScriptThread(std::function function, const Q } void ScriptEngine::waitTillDoneRunning() { + // Engine should be stopped already, but be defensive + stop(); + auto workerThread = thread(); - + + if (workerThread == QThread::currentThread()) { + qCWarning(scriptengine) << "ScriptEngine::waitTillDoneRunning called, but the script is on the same thread:" << getFilename(); + return; + } + if (_isThreaded && workerThread) { // We should never be waiting (blocking) on our own thread assert(workerThread != QThread::currentThread()); - // Engine should be stopped already, but be defensive - stop(); - auto startedWaiting = usecTimestampNow(); while (workerThread->isRunning()) { // If the final evaluation takes too long, then tell the script engine to stop running diff --git a/libraries/script-engine/src/ScriptEngines.cpp b/libraries/script-engine/src/ScriptEngines.cpp index 25c330e3fe..17f2aea9a5 100644 --- a/libraries/script-engine/src/ScriptEngines.cpp +++ b/libraries/script-engine/src/ScriptEngines.cpp @@ -114,7 +114,7 @@ QUrl expandScriptUrl(const QUrl& rawScriptURL) { url = QUrl::fromLocalFile(fileInfo.canonicalFilePath()); QUrl defaultScriptsLoc = PathUtils::defaultScriptsLocation(); - if (!defaultScriptsLoc.isParentOf(url)) { + if (!defaultScriptsLoc.isParentOf(url) && defaultScriptsLoc != url) { qCWarning(scriptengine) << "Script.include() ignoring file path" << "-- outside of standard libraries: " << url.path() diff --git a/libraries/shared/src/GPUIdent.cpp b/libraries/shared/src/GPUIdent.cpp index f092a56c17..c195b2ec3a 100644 --- a/libraries/shared/src/GPUIdent.cpp +++ b/libraries/shared/src/GPUIdent.cpp @@ -282,12 +282,13 @@ GPUIdent* GPUIdent::ensureQuery(const QString& vendor, const QString& renderer) if (!validAdapterList.empty()) { for (auto outy = adapterToOutputs.begin(); outy != adapterToOutputs.end(); ++outy) { - AdapterEntry entry = *outy; + AdapterEntry entry = *outy; for (auto test = entry.second.begin(); test != entry.second.end(); ++test) { - + std::wstring wDeviceName(test->DeviceName); + std::string deviceName(wDeviceName.begin(), wDeviceName.end()); + nlohmann::json output = {}; - output["description"] = entry.first.first.Description; - output["deviceName"]= test->DeviceName; + output["model"] = deviceName; output["coordinatesleft"] = test->DesktopCoordinates.left; output["coordinatesright"] = test->DesktopCoordinates.right; output["coordinatestop"] = test->DesktopCoordinates.top; diff --git a/libraries/shared/src/Preferences.h b/libraries/shared/src/Preferences.h index 9f84d9c8b7..9147117792 100644 --- a/libraries/shared/src/Preferences.h +++ b/libraries/shared/src/Preferences.h @@ -115,48 +115,39 @@ protected: const Lambda _triggerHandler; }; - -template -class TypedPreference : public Preference { -public: - using Getter = std::function; - using Setter = std::function; - - TypedPreference(const QString& category, const QString& name, Getter getter, Setter setter) - : Preference(category, name), _getter(getter), _setter(setter) { } - - T getValue() const { return _value; } - void setValue(const T& value) { if (_value != value) { _value = value; emitValueChanged(); } } - void load() override { _value = _getter(); } - void save() const override { - T oldValue = _getter(); - if (_value != oldValue) { - _setter(_value); - } - } - -protected: - T _value; - const Getter _getter; - const Setter _setter; -}; - -class BoolPreference : public TypedPreference { +class BoolPreference : public Preference { Q_OBJECT Q_PROPERTY(bool value READ getValue WRITE setValue NOTIFY valueChanged) public: + using Getter = std::function; + using Setter = std::function; + BoolPreference(const QString& category, const QString& name, Getter getter, Setter setter) - : TypedPreference(category, name, getter, setter) { } + : Preference(category, name), _getter(getter), _setter(setter) { } + + bool getValue() const { return _value; } + void setValue(const bool& value) { if (_value != value) { _value = value; emitValueChanged(); } } + void load() override { _value = _getter(); } + void save() const override { + bool oldValue = _getter(); + if (_value != oldValue) { + _setter(_value); + } + } signals: void valueChanged(); protected: + bool _value; + const Getter _getter; + const Setter _setter; + void emitValueChanged() override { emit valueChanged(); } }; -class FloatPreference : public TypedPreference { +class FloatPreference : public Preference { Q_OBJECT Q_PROPERTY(float value READ getValue WRITE setValue NOTIFY valueChanged) Q_PROPERTY(float min READ getMin CONSTANT) @@ -165,8 +156,21 @@ class FloatPreference : public TypedPreference { Q_PROPERTY(float decimals READ getDecimals CONSTANT) public: + using Getter = std::function; + using Setter = std::function; + FloatPreference(const QString& category, const QString& name, Getter getter, Setter setter) - : TypedPreference(category, name, getter, setter) { } + : Preference(category, name), _getter(getter), _setter(setter) { } + + float getValue() const { return _value; } + void setValue(const float& value) { if (_value != value) { _value = value; emitValueChanged(); } } + void load() override { _value = _getter(); } + void save() const override { + float oldValue = _getter(); + if (_value != oldValue) { + _setter(_value); + } + } float getMin() const { return _min; } void setMin(float min) { _min = min; }; @@ -186,14 +190,17 @@ signals: protected: void emitValueChanged() override { emit valueChanged(); } + float _value; + const Getter _getter; + const Setter _setter; + float _decimals { 0 }; float _min { 0 }; float _max { 1 }; float _step { 0.1f }; }; - -class IntPreference : public TypedPreference { +class IntPreference : public Preference { Q_OBJECT Q_PROPERTY(float value READ getValue WRITE setValue NOTIFY valueChanged) Q_PROPERTY(float min READ getMin CONSTANT) @@ -202,8 +209,21 @@ class IntPreference : public TypedPreference { Q_PROPERTY(int decimals READ getDecimals CONSTANT) public: + using Getter = std::function; + using Setter = std::function; + IntPreference(const QString& category, const QString& name, Getter getter, Setter setter) - : TypedPreference(category, name, getter, setter) { } + : Preference(category, name), _getter(getter), _setter(setter) { } + + int getValue() const { return _value; } + void setValue(const int& value) { if (_value != value) { _value = value; emitValueChanged(); } } + void load() override { _value = _getter(); } + void save() const override { + int oldValue = _getter(); + if (_value != oldValue) { + _setter(_value); + } + } float getMin() const { return _min; } void setMin(float min) { _min = min; }; @@ -221,6 +241,10 @@ signals: void valueChanged(); protected: + int _value; + const Getter _getter; + const Setter _setter; + void emitValueChanged() override { emit valueChanged(); } int _min { std::numeric_limits::min() }; @@ -229,19 +253,37 @@ protected: int _decimals { 0 }; }; -class StringPreference : public TypedPreference { +class StringPreference : public Preference { Q_OBJECT Q_PROPERTY(QString value READ getValue WRITE setValue NOTIFY valueChanged) public: + using Getter = std::function; + using Setter = std::function; + StringPreference(const QString& category, const QString& name, Getter getter, Setter setter) - : TypedPreference(category, name, getter, setter) { } + : Preference(category, name), _getter(getter), _setter(setter) { } + + + QString getValue() const { return _value; } + void setValue(const QString& value) { if (_value != value) { _value = value; emitValueChanged(); } } + void load() override { _value = _getter(); } + void save() const override { + QString oldValue = _getter(); + if (_value != oldValue) { + _setter(_value); + } + } signals: void valueChanged(); protected: void emitValueChanged() override { emit valueChanged(); } + + QString _value; + const Getter _getter; + const Setter _setter; }; class SliderPreference : public FloatPreference { @@ -303,7 +345,7 @@ public: ComboBoxPreference(const QString& category, const QString& name, Getter getter, Setter setter) : EditPreference(category, name, getter, setter) { } Type getType() override { return ComboBox; } - + const QStringList& getItems() { return _items; } void setItems(const QStringList& items) { _items = items; } @@ -342,6 +384,9 @@ class CheckPreference : public BoolPreference { Q_OBJECT Q_PROPERTY(bool indented READ getIndented CONSTANT) public: + using Getter = std::function; + using Setter = std::function; + CheckPreference(const QString& category, const QString& name, Getter getter, Setter setter) : BoolPreference(category, name, getter, setter) { } Type getType() override { return Checkbox; } diff --git a/libraries/ui/src/DockWidget.cpp b/libraries/ui/src/DockWidget.cpp index 3bcd479d61..44bac1f670 100644 --- a/libraries/ui/src/DockWidget.cpp +++ b/libraries/ui/src/DockWidget.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include @@ -28,6 +29,7 @@ DockWidget::DockWidget(const QString& title, QWidget* parent) : QDockWidget(titl auto offscreenUi = DependencyManager::get(); auto qmlEngine = offscreenUi->getSurfaceContext()->engine(); _quickView = std::shared_ptr(new QQuickView(qmlEngine, nullptr), quickViewDeleter); + _quickView->setFormat(getDefaultOpenGLSurfaceFormat()); QWidget* widget = QWidget::createWindowContainer(_quickView.get()); setWidget(widget); QWidget* headerWidget = new QWidget(); diff --git a/plugins/oculus/src/OculusDisplayPlugin.cpp b/plugins/oculus/src/OculusDisplayPlugin.cpp index df01591639..c493588992 100644 --- a/plugins/oculus/src/OculusDisplayPlugin.cpp +++ b/plugins/oculus/src/OculusDisplayPlugin.cpp @@ -124,6 +124,10 @@ void OculusDisplayPlugin::uncustomizeContext() { Parent::uncustomizeContext(); } +gpu::PipelinePointer OculusDisplayPlugin::getCompositeScenePipeline() { + return _SRGBToLinearPipeline; +} + static const uint64_t FRAME_BUDGET = (11 * USECS_PER_MSEC); static const uint64_t FRAME_OVER_BUDGET = (15 * USECS_PER_MSEC); @@ -163,7 +167,7 @@ void OculusDisplayPlugin::hmdPresent() { batch.setStateScissorRect(ivec4(uvec2(), _outputFramebuffer->getSize())); batch.resetViewTransform(); batch.setProjectionTransform(mat4()); - batch.setPipeline(_presentPipeline); + batch.setPipeline(_drawTexturePipeline); batch.setResourceTexture(0, _compositeFramebuffer->getRenderBuffer(0)); batch.draw(gpu::TRIANGLE_STRIP, 4); }); diff --git a/plugins/oculus/src/OculusDisplayPlugin.h b/plugins/oculus/src/OculusDisplayPlugin.h index 9209fd373e..8eda599fa9 100644 --- a/plugins/oculus/src/OculusDisplayPlugin.h +++ b/plugins/oculus/src/OculusDisplayPlugin.h @@ -34,6 +34,8 @@ protected: void uncustomizeContext() override; void cycleDebugOutput() override; + virtual gpu::PipelinePointer getCompositeScenePipeline() override; + private: static const char* NAME; ovrTextureSwapChain _textureSwapChain; diff --git a/scripts/simplifiedUI/defaultScripts.js b/scripts/simplifiedUI/defaultScripts.js index 3213303f2b..ccefcc3a95 100644 --- a/scripts/simplifiedUI/defaultScripts.js +++ b/scripts/simplifiedUI/defaultScripts.js @@ -11,12 +11,10 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -var DEFAULT_SCRIPTS_PATH_PREFIX = ScriptDiscoveryService.defaultScriptsPath + "/"; - var DEFAULT_SCRIPTS_SEPARATE = [ - DEFAULT_SCRIPTS_PATH_PREFIX + "system/controllers/controllerScripts.js", - DEFAULT_SCRIPTS_PATH_PREFIX + "ui/simplifiedUI.js" + "system/controllers/controllerScripts.js", + "ui/simplifiedUI.js" ]; function loadSeparateDefaults() { for (var i in DEFAULT_SCRIPTS_SEPARATE) { @@ -26,9 +24,9 @@ function loadSeparateDefaults() { var DEFAULT_SCRIPTS_COMBINED = [ - DEFAULT_SCRIPTS_PATH_PREFIX + "system/request-service.js", - DEFAULT_SCRIPTS_PATH_PREFIX + "system/progress.js", - DEFAULT_SCRIPTS_PATH_PREFIX + "system/away.js" + "system/request-service.js", + "system/progress.js", + "system/away.js" ]; function runDefaultsTogether() { for (var i in DEFAULT_SCRIPTS_COMBINED) { diff --git a/scripts/simplifiedUI/system/assets/animations/Cheering.fbx b/scripts/simplifiedUI/system/assets/animations/Cheering.fbx new file mode 100644 index 0000000000..8787bf4bd8 Binary files /dev/null and b/scripts/simplifiedUI/system/assets/animations/Cheering.fbx differ diff --git a/scripts/simplifiedUI/system/assets/animations/Clapping.fbx b/scripts/simplifiedUI/system/assets/animations/Clapping.fbx new file mode 100644 index 0000000000..d05b41866d Binary files /dev/null and b/scripts/simplifiedUI/system/assets/animations/Clapping.fbx differ diff --git a/scripts/simplifiedUI/system/assets/animations/Crying.fbx b/scripts/simplifiedUI/system/assets/animations/Crying.fbx new file mode 100644 index 0000000000..2e60ba2450 Binary files /dev/null and b/scripts/simplifiedUI/system/assets/animations/Crying.fbx differ diff --git a/scripts/simplifiedUI/system/assets/animations/Dancing.fbx b/scripts/simplifiedUI/system/assets/animations/Dancing.fbx new file mode 100644 index 0000000000..7759d273b7 Binary files /dev/null and b/scripts/simplifiedUI/system/assets/animations/Dancing.fbx differ diff --git a/scripts/simplifiedUI/system/assets/animations/Fall.fbx b/scripts/simplifiedUI/system/assets/animations/Fall.fbx new file mode 100644 index 0000000000..627e909bb4 Binary files /dev/null and b/scripts/simplifiedUI/system/assets/animations/Fall.fbx differ diff --git a/scripts/simplifiedUI/system/assets/animations/Love.fbx b/scripts/simplifiedUI/system/assets/animations/Love.fbx new file mode 100644 index 0000000000..159ccafd04 Binary files /dev/null and b/scripts/simplifiedUI/system/assets/animations/Love.fbx differ diff --git a/scripts/simplifiedUI/system/assets/animations/Pointing.fbx b/scripts/simplifiedUI/system/assets/animations/Pointing.fbx new file mode 100644 index 0000000000..da3c9bbeca Binary files /dev/null and b/scripts/simplifiedUI/system/assets/animations/Pointing.fbx differ diff --git a/scripts/simplifiedUI/system/assets/animations/Sit1.fbx b/scripts/simplifiedUI/system/assets/animations/Sit1.fbx new file mode 100644 index 0000000000..db75219980 Binary files /dev/null and b/scripts/simplifiedUI/system/assets/animations/Sit1.fbx differ diff --git a/scripts/simplifiedUI/system/assets/animations/Sit2.fbx b/scripts/simplifiedUI/system/assets/animations/Sit2.fbx new file mode 100644 index 0000000000..400b599794 Binary files /dev/null and b/scripts/simplifiedUI/system/assets/animations/Sit2.fbx differ diff --git a/scripts/simplifiedUI/system/assets/animations/Sit3.fbx b/scripts/simplifiedUI/system/assets/animations/Sit3.fbx new file mode 100644 index 0000000000..174fd75c4e Binary files /dev/null and b/scripts/simplifiedUI/system/assets/animations/Sit3.fbx differ diff --git a/scripts/simplifiedUI/system/assets/animations/Surprised.fbx b/scripts/simplifiedUI/system/assets/animations/Surprised.fbx new file mode 100644 index 0000000000..49362605b3 Binary files /dev/null and b/scripts/simplifiedUI/system/assets/animations/Surprised.fbx differ diff --git a/scripts/simplifiedUI/system/assets/animations/Waving.fbx b/scripts/simplifiedUI/system/assets/animations/Waving.fbx new file mode 100644 index 0000000000..e2442f64f4 Binary files /dev/null and b/scripts/simplifiedUI/system/assets/animations/Waving.fbx differ diff --git a/scripts/simplifiedUI/system/assets/data/createAppTooltips.json b/scripts/simplifiedUI/system/assets/data/createAppTooltips.json new file mode 100644 index 0000000000..b59797fca7 --- /dev/null +++ b/scripts/simplifiedUI/system/assets/data/createAppTooltips.json @@ -0,0 +1,623 @@ +{ + "shape": { + "tooltip": "The shape of this entity's geometry." + }, + "color": { + "tooltip": "The RGB value of this entity." + }, + "text": { + "tooltip": "The text to display on the entity." + }, + "textColor": { + "tooltip": "The color of the text." + }, + "textAlpha": { + "tooltip": "The alpha of the text." + }, + "backgroundColor": { + "tooltip": "The color of the background." + }, + "backgroundAlpha": { + "tooltip": "The alpha of the background." + }, + "lineHeight": { + "tooltip": "The height of each line of text. This determines the size of the text." + }, + "textBillboardMode": { + "tooltip": "If enabled, determines how the entity will face the camera.", + "jsPropertyName": "billboardMode" + }, + "topMargin": { + "tooltip": "The top margin, in meters." + }, + "rightMargin": { + "tooltip": "The right margin, in meters." + }, + "bottomMargin": { + "tooltip": "The bottom margin, in meters." + }, + "leftMargin": { + "tooltip": "The left margin, in meters." + }, + "zoneShapeType": { + "tooltip": "The shape of the volume in which the zone's lighting effects and avatar permissions have effect.", + "jsPropertyName": "shapeType" + }, + "zoneCompoundShapeURL": { + "tooltip": "The model file to use for the compound shape if Shape Type is \"Use Compound Shape URL\".", + "jsPropertyName": "compoundShapeURL" + }, + "flyingAllowed": { + "tooltip": "If enabled, users can fly in the zone." + }, + "ghostingAllowed": { + "tooltip": "If enabled, users with avatar collisions turned off will not collide with content in the zone." + }, + "filterURL": { + "tooltip": "The URL of a JS file that checks for changes to entity properties within the zone. Runs periodically." + }, + "keyLightMode": { + "tooltip": "Configures the key light in the zone. This light is directional." + }, + "keyLight.color": { + "tooltip": "The color of the key light." + }, + "keyLight.intensity": { + "tooltip": "The intensity of the key light." + }, + "keyLight.direction.y": { + "tooltip": "The angle in deg at which light emits. Starts in the entity's -z direction, and rotates around its y axis." + }, + "keyLight.direction.x": { + "tooltip": "The angle in deg at which light emits. Starts in the entity's -z direction, and rotates around its x axis." + }, + "keyLight.castShadows": { + "tooltip": "If enabled, shadows are cast. The entity or avatar casting the shadow must also have Cast Shadows enabled." + }, + "skyboxMode": { + "tooltip": "Configures the skybox in the zone. The skybox is a cube map image." + }, + "skybox.color": { + "tooltip": "If the URL is blank, this changes the color of the sky, otherwise it modifies the color of the skybox." + }, + "skybox.url": { + "tooltip": "A cube map image that is used to render the sky." + }, + "ambientLightMode": { + "tooltip": "Configures the ambient light in the zone. Use this if you want your skybox to reflect light on the content." + }, + "ambientLight.ambientIntensity": { + "tooltip": "The intensity of the ambient light." + }, + "ambientLight.ambientURL": { + "tooltip": "A cube map image that defines the color of the light coming from each direction." + }, + "hazeMode": { + "tooltip": "Configures the haze in the scene." + }, + "haze.hazeRange": { + "tooltip": "How far the haze extends out. This is measured in meters." + }, + "haze.hazeAltitudeEffect": { + "tooltip": "If enabled, this adjusts the haze intensity as it gets higher." + }, + "haze.hazeBaseRef": { + "tooltip": "The base of the altitude range. Measured in entity space." + }, + "haze.hazeCeiling": { + "tooltip": "The ceiling of the altitude range. Measured in entity space." + }, + "haze.hazeColor": { + "tooltip": "The color of the haze." + }, + "haze.hazeBackgroundBlend": { + "tooltip": "How much the skybox shows through the haze. The higher the value, the more it shows through." + }, + "haze.hazeEnableGlare": { + "tooltip": "If enabled, a glare is enabled on the skybox, based on the key light." + }, + "haze.hazeGlareColor": { + "tooltip": "The color of the glare based on the key light." + }, + "haze.hazeGlareAngle": { + "tooltip": "The angular size of the glare and how much it encompasses the skybox, based on the key light." + }, + "bloomMode": { + "tooltip": "Configures how much bright areas of the scene glow." + }, + "bloom.bloomIntensity": { + "tooltip": "The intensity, or brightness, of the bloom effect." + }, + "bloom.bloomThreshold": { + "tooltip": "The cutoff of the bloom. The higher the value, the more only bright areas of the scene will glow." + }, + "bloom.bloomSize": { + "tooltip": "The radius of bloom. The higher the value, the larger the bloom." + }, + "avatarPriority": { + "tooltip": "Alter Avatars' update priorities." + }, + "modelURL": { + "tooltip": "A mesh model from an FBX or OBJ file." + }, + "shapeType": { + "tooltip": "The shape of the collision hull used if collisions are enabled. This affects how an entity collides." + }, + "compoundShapeURL": { + "tooltip": "The model file to use for the compound shape if Collision Shape is \"Compound\"." + }, + "animation.url": { + "tooltip": "An animation to play on the model." + }, + "animation.running": { + "tooltip": "If enabled, the animation on the model will play automatically." + }, + "animation.allowTranslation": { + "tooltip": "If enabled, this allows an entity to move in space during an animation." + }, + "animation.loop": { + "tooltip": "If enabled, then the animation will continuously repeat." + }, + "animation.hold": { + "tooltip": "If enabled, then rotations and translations of the last frame played are maintained when the animation stops." + }, + "animation.currentFrame": { + "tooltip": "The current frame being played in the animation." + }, + "animation.firstFrame": { + "tooltip": "The first frame to play in the animation." + }, + "animation.lastFrame": { + "tooltip": "The last frame to play in the animation." + }, + "animation.fps": { + "tooltip": "The speed of the animation." + }, + "textures": { + "tooltip": "A JSON string containing a texture. Use a name from the Original Texture property to override it." + }, + "originalTextures": { + "tooltip": "A JSON string containing the original texture used on the model." + }, + "imageURL": { + "tooltip": "The URL for the image source." + }, + "imageColor": { + "tooltip": "The tint to be applied to the image.", + "jsPropertyName": "color" + }, + "emissive": { + "tooltip": "If enabled, the image will display at full brightness." + }, + "subImage": { + "tooltip": "The area of the image that is displayed." + }, + "imageBillboardMode": { + "tooltip": "If enabled, determines how the entity will face the camera.", + "jsPropertyName": "billboardMode" + }, + "keepAspectRatio": { + "tooltip": "If enabled, the image will maintain its original aspect ratio." + }, + "sourceUrl": { + "tooltip": "The URL for the web page source." + }, + "dpi": { + "tooltip": "The resolution to display the page at, in pixels per inch. Use this to resize your web source in the frame." + }, + "isEmitting": { + "tooltip": "If enabled, then particles are emitted." + }, + "lifespan": { + "tooltip": "How long each particle lives, measured in seconds." + }, + "maxParticles": { + "tooltip": "The maximum number of particles to render at one time. Older particles are swapped out for new ones." + }, + "particleTextures": { + "tooltip": "The URL of a JPG or PNG image file to display for each particle.", + "jsPropertyName": "textures" + }, + "emitRate": { + "tooltip": "The number of particles per second to emit." + }, + "emitSpeed": { + "tooltip": "The speed that each particle is emitted at, measured in m/s." + }, + "speedSpread": { + "tooltip": "The spread in speeds at which particles are emitted at, resulting in a variety of speeds." + }, + "particleShapeType": { + "tooltip": "The shape of the surface from which to emit particles.", + "jsPropertyName": "shapeType" + }, + "particleCompoundShapeURL": { + "tooltip": "The model file to use for the particle emitter if Shape Type is \"Use Compound Shape URL\".", + "jsPropertyName": "compoundShapeURL" + }, + "emitDimensions": { + "tooltip": "The outer limit radius in dimensions that the particles can be emitted from." + }, + "emitOrientation": { + "tooltip": "The orientation of particle emission relative to the entity's axes." + }, + "emitRadiusStart": { + "tooltip": "The inner limit radius in dimensions that the particles start emitting from." + }, + "emitterShouldTrail": { + "tooltip": "If enabled, then particles are \"left behind\" as the emitter moves, otherwise they are not." + }, + "particleRadiusTriple": { + "tooltip": "The size of each particle.", + "jsPropertyName": "particleRadius" + }, + "particleRadius": { + "tooltip": "The size of each particle." + }, + "radiusStart": { + "tooltip": "The start size of each particle." + }, + "radiusFinish": { + "tooltip": "The finish size of each particle." + }, + "radiusSpread": { + "tooltip": "The spread in size that each particle is given, resulting in a variety of sizes." + }, + "particleColorTriple": { + "tooltip": "The color of each particle.", + "jsPropertyName": "color" + }, + "particleColor": { + "tooltip": "The color of each particle.", + "jsPropertyName": "color" + }, + "colorStart": { + "tooltip": "The start color of each particle." + }, + "colorFinish": { + "tooltip": "The finish color of each particle." + }, + "colorSpread": { + "tooltip": "The spread in color that each particle is given, resulting in a variety of colors." + }, + "particleAlphaTriple": { + "tooltip": "The alpha of each particle.", + "jsPropertyName": "alpha" + }, + "alpha": { + "tooltip": "The alpha of each particle." + }, + "alphaStart": { + "tooltip": "The start alpha of each particle." + }, + "alphaFinish": { + "tooltip": "The finish alpha of each particle." + }, + "alphaSpread": { + "tooltip": "The spread in alpha that each particle is given, resulting in a variety of alphas." + }, + "emitAcceleration": { + "tooltip": "The acceleration that is applied to each particle during its lifetime." + }, + "accelerationSpread": { + "tooltip": "The spread in accelerations that each particle is given, resulting in a variety of accelerations." + }, + "particleSpinTriple": { + "tooltip": "The spin of each particle.", + "jsPropertyName": "particleSpin" + }, + "particleSpin": { + "tooltip": "The spin of each particle." + }, + "spinStart": { + "tooltip": "The start spin of each particle." + }, + "spinFinish": { + "tooltip": "The finish spin of each particle." + }, + "spinSpread": { + "tooltip": "The spread in spin that each particle is given, resulting in a variety of spins." + }, + "rotateWithEntity": { + "tooltip": "If enabled, each particle will spin relative to the rotation of the entity as a whole." + }, + "particlePolarTriple": { + "tooltip": "The angle range in deg at which particles are emitted. Starts in the entity's -z direction, and rotates around its y axis.", + "skipJSProperty": true + }, + "polarStart": { + "tooltip": "The start angle in deg at which particles are emitted. Starts in the entity's -z direction, and rotates around its y axis." + }, + "polarFinish": { + "tooltip": "The finish angle in deg at which particles are emitted. Starts in the entity's -z direction, and rotates around its y axis." + }, + "particleAzimuthTriple": { + "tooltip": "The angle range in deg at which particles are emitted. Starts in the entity's -x direction, and rotates around its z axis.", + "skipJSProperty": true + }, + "azimuthStart": { + "tooltip": "The start angle in deg at which particles are emitted. Starts in the entity's -x direction, and rotates around its z axis." + }, + "azimuthFinish": { + "tooltip": "The finish angle in deg at which particles are emitted. Starts in the entity's -x direction, and rotates around its z axis." + }, + "lightColor": { + "tooltip": "The color of the light emitted.", + "jsPropertyName": "color" + }, + "intensity": { + "tooltip": "The brightness of the light." + }, + "falloffRadius": { + "tooltip": "The distance from the light's center where the intensity is reduced." + }, + "isSpotlight": { + "tooltip": "If enabled, then the light is directional, otherwise the light is a point light which emits light in all directions." + }, + "exponent": { + "tooltip": "Affects the softness of the spotlight beam; the higher the value, the softer the beam." + }, + "cutoff": { + "tooltip": "Affects the size of the spotlight beam; the higher the value, the larger the beam." + }, + "materialURL": { + "tooltip": "The URL to an external JSON file or \"materialData\", \"materialData? to use Material Data." + }, + "materialData": { + "tooltip": "Can be used instead of a JSON file when material set to materialData." + }, + "parentMaterialName": { + "tooltip": "The target mesh indices or material names that this material entity should be assigned to on it's parent. This only supports parents that are Avatars as well as Shape or Model entity types." + }, + "priority": { + "tooltip": "The priority of the material, where a larger number means higher priority. Original materials = 0." + }, + "materialMappingMode": { + "tooltip": "How the material is mapped to the entity. If set to \"UV space\", then the material will be applied with the target entity's UV coordinates. If set to \"3D Projected\", then the 3D transform of the material entity will be used." + }, + "materialMappingPos": { + "tooltip": "The offset position of the bottom left of the material within the parent's UV space." + }, + "materialMappingScale": { + "tooltip": "How many times the material will repeat in each direction within the parent's UV space." + }, + "materialMappingRot": { + "tooltip": "How much to rotate the material within the parent's UV-space, in degrees." + }, + "materialRepeat": { + "tooltip": "If enabled, the material will repeat, otherwise it will clamp." + }, + "followCamera": { + "tooltip": "If enabled, the grid is always visible even as the camera moves to another position." + }, + "majorGridEvery": { + "tooltip": "The number of \"Minor Grid Every\" intervals at which to draw a thick grid line." + }, + "minorGridEvery": { + "tooltip": "The real number of meters at which to draw thin grid lines." + }, + "id": { + "tooltip": "The unique identifier of this entity." + }, + "name": { + "tooltip": "The name of this entity." + }, + "description": { + "tooltip": "Use this field to describe the entity." + }, + "position": { + "tooltip": "The global position of this entity." + }, + "localPosition": { + "tooltip": "The local position of this entity." + }, + "rotation": { + "tooltip": "The global rotation of this entity." + }, + "localRotation": { + "tooltip": "The local rotation of this entity." + }, + "dimensions": { + "tooltip": "The global dimensions of this entity." + }, + "localDimensions": { + "tooltip": "The local dimensions of this entity." + }, + "scale": { + "tooltip": "The global scaling of this entity.", + "skipJSProperty": true + }, + "registrationPoint": { + "tooltip": "The point in the entity at which the entity is rotated about." + }, + "visible": { + "tooltip": "If enabled, this entity will be visible." + }, + "locked": { + "tooltip": "If enabled, this entity will be locked." + }, + "collisionless": { + "tooltip": "If enabled, this entity will collide with other entities or avatars." + }, + "dynamic": { + "tooltip": "If enabled, this entity has collisions associated with it that can affect its movement." + }, + "collidesWithStatic": { + "tooltip": "If enabled, this entity will collide with other non-moving, static entities.", + "jsPropertyName": "collidesWith" + }, + "collidesWithDynamic": { + "tooltip": "If enabled, this entity will collide with other dynamic entities.", + "jsPropertyName": "collidesWith" + }, + "collidesWithKinematic": { + "tooltip": "If enabled, this entity will collide with other kinematic entities (they have velocity but are not dynamic).", + "jsPropertyName": "collidesWith" + }, + "collidesWithOtherAvatar": { + "tooltip": "If enabled, this entity will collide with other user's avatars.", + "jsPropertyName": "collidesWith" + }, + "collidesWithMyAvatar": { + "tooltip": "If enabled, this entity will collide with your own avatar.", + "jsPropertyName": "collidesWith" + }, + "collisionSoundURL": { + "tooltip": "The URL of a sound to play when the entity collides with something else." + }, + "grab.grabbable": { + "tooltip": "If enabled, this entity will allow grabbing input and will be movable." + }, + "grab.triggerable": { + "tooltip": "If enabled, the collider on this entity is used for triggering events." + }, + "cloneable": { + "tooltip": "If enabled, this entity can be duplicated." + }, + "cloneLifetime": { + "tooltip": "The lifetime for clones of this entity." + }, + "cloneLimit": { + "tooltip": "The total number of clones of this entity that can exist in the domain at any given time." + }, + "cloneDynamic": { + "tooltip": "If enabled, then clones created from this entity will be dynamic, allowing the clone to collide." + }, + "cloneAvatarEntity": { + "tooltip": "If enabled, then clones created from this entity will be created as avatar entities." + }, + "grab.grabFollowsController": { + "tooltip": "If enabled, grabbed entities will follow the movements of your hand controller instead of your avatar's hand." + }, + "canCastShadow": { + "tooltip": "If enabled, this geometry of this entity casts shadows when a shadow-casting light source shines on it." + }, + "ignorePickIntersection": { + "tooltip": "If enabled, this entity will not be considered for ray picks, and will also not occlude other entities when picking." + }, + "parentID": { + "tooltip": "The ID of the entity or avatar that this entity is parented to." + }, + "parentJointIndex": { + "tooltip": "If the entity is parented to an avatar, this joint defines where on the avatar the entity is parented." + }, + "href": { + "tooltip": "The URL that will be opened when a user clicks on this entity. Useful for web pages and portals." + }, + "script": { + "tooltip": "The URL to an external JS file to add behaviors to the client." + }, + "serverScripts": { + "tooltip": "The URL to an external JS file to add behaviors to the server." + }, + "serverScriptsStatus": { + "tooltip": "The status of the server script, if provided. This shows if it's running or has an error.", + "skipJSProperty": true + }, + "hasLifetime": { + "tooltip": "If enabled, the entity will disappear after a certain amount of time specified by Lifetime.", + "jsPropertyName": "lifetime" + }, + "lifetime": { + "tooltip": "The time this entity will exist in the environment for." + }, + "userData": { + "tooltip": "Used to store extra data about the entity in JSON format." + }, + "localVelocity": { + "tooltip": "The linear velocity vector of the entity. The velocity at which this entity moves forward in space." + }, + "damping": { + "tooltip": "The linear damping to slow down the linear velocity of an entity over time." + }, + "localAngularVelocity": { + "tooltip": "The angular velocity of the entity in rad/s with respect to its axes, about its pivot point." + }, + "angularDamping": { + "tooltip": "The angular damping to slow down the angular velocity of an entity over time." + }, + "restitution": { + "tooltip": "If enabled, the entity can bounce against other objects that also have Bounciness." + }, + "friction": { + "tooltip": "The friction applied to slow down an entity when it's moving against another entity." + }, + "density": { + "tooltip": "The density of the entity. The higher the density, the harder the entity is to move." + }, + "gravity": { + "tooltip": "The acceleration due to gravity that the entity should move with, in world space." + }, + "acceleration": { + "tooltip": "A acceleration that the entity should move with, in world space." + }, + "renderLayer": { + "tooltip": "The layer on which this entity is rendered." + }, + "primitiveMode": { + "tooltip": "The mode in which to draw an entity, either \"Solid\" or \"Wireframe\"." + }, + "groupCulled": { + "tooltip": "If false, individual pieces of the entity may be culled by the render engine. If true, either the entire entity will be culled, or it won't at all." + }, + "webColor": { + "tooltip": "The tint of the web entity." + }, + "webAlpha": { + "tooltip": "The alpha of the web entity." + }, + "maxFPS": { + "tooltip": "The FPS at which to render the web entity. Higher values will have a performance impact." + }, + "scriptURL": { + "tooltip": "The URL of a script to inject into the web page." + }, + "alignToGrid": { + "tooltip": "Used to align entities to the grid, or floor of the environment.", + "skipJSProperty": true + }, + "createModel": { + "tooltip": "An entity that is based on a custom mesh created from an .OBJ or .FBX.", + "skipJSProperty": true + }, + "createShape": { + "tooltip": "An entity that has many different primitive shapes.", + "skipJSProperty": true + }, + "createLight": { + "tooltip": "An entity that emits light.", + "skipJSProperty": true + }, + "createText": { + "tooltip": "An entity that displays text on a panel.", + "skipJSProperty": true + }, + "createImage": { + "tooltip": "An entity that displays an image on a panel.", + "skipJSProperty": true + }, + "createWeb": { + "tooltip": "An entity that displays a web page on a panel.", + "skipJSProperty": true + }, + "createZone": { + "tooltip": "An entity that can be used for skyboxes, lighting, and can constrain or change avatar behaviors.", + "skipJSProperty": true + }, + "createParticle": { + "tooltip": "An entity that emits particles.", + "skipJSProperty": true + }, + "createMaterial": { + "tooltip": "An entity that creates a material that can be attached to a Shape or Model.", + "skipJSProperty": true + }, + "useAssetServer": { + "tooltip": "A server that hosts content and assets. You can't take items that are hosted here into other domains.", + "skipJSProperty": true + }, + "importNewEntity": { + "tooltip": "Import a local or hosted file that can be used across domains.", + "skipJSProperty": true + } +} diff --git a/scripts/simplifiedUI/system/assets/images/Overlay-Viz-blank.png b/scripts/simplifiedUI/system/assets/images/Overlay-Viz-blank.png new file mode 100644 index 0000000000..bbbf44f7a9 Binary files /dev/null and b/scripts/simplifiedUI/system/assets/images/Overlay-Viz-blank.png differ diff --git a/scripts/simplifiedUI/system/assets/images/Particle-Sprite-Smoke-1.png b/scripts/simplifiedUI/system/assets/images/Particle-Sprite-Smoke-1.png new file mode 100644 index 0000000000..78c9b3da4a Binary files /dev/null and b/scripts/simplifiedUI/system/assets/images/Particle-Sprite-Smoke-1.png differ diff --git a/scripts/simplifiedUI/system/assets/images/close-small-light.svg b/scripts/simplifiedUI/system/assets/images/close-small-light.svg new file mode 100644 index 0000000000..f9edf95fca --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/close-small-light.svg @@ -0,0 +1,14 @@ + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/grabsprite-3.png b/scripts/simplifiedUI/system/assets/images/grabsprite-3.png new file mode 100644 index 0000000000..4ecc772a41 Binary files /dev/null and b/scripts/simplifiedUI/system/assets/images/grabsprite-3.png differ diff --git a/scripts/simplifiedUI/system/assets/images/hourglass.svg b/scripts/simplifiedUI/system/assets/images/hourglass.svg new file mode 100644 index 0000000000..5c5055b755 --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/hourglass.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + diff --git a/interface/resources/icons/create-icons/90-particles-01.svg b/scripts/simplifiedUI/system/assets/images/icon-particles.svg similarity index 100% rename from interface/resources/icons/create-icons/90-particles-01.svg rename to scripts/simplifiedUI/system/assets/images/icon-particles.svg diff --git a/scripts/simplifiedUI/system/assets/images/icon-point-light.svg b/scripts/simplifiedUI/system/assets/images/icon-point-light.svg new file mode 100644 index 0000000000..896c35b63b --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/icon-point-light.svg @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/icon-spot-light.svg b/scripts/simplifiedUI/system/assets/images/icon-spot-light.svg new file mode 100644 index 0000000000..ac2f87bb27 --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/icon-spot-light.svg @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/icon-zone.svg b/scripts/simplifiedUI/system/assets/images/icon-zone.svg new file mode 100644 index 0000000000..41aeac4951 --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/icon-zone.svg @@ -0,0 +1,73 @@ + + + +image/svg+xml \ No newline at end of file diff --git a/scripts/simplifiedUI/system/assets/images/ignore-target.svg b/scripts/simplifiedUI/system/assets/images/ignore-target.svg new file mode 100644 index 0000000000..3d685139ec --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/ignore-target.svg @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/kick-target.svg b/scripts/simplifiedUI/system/assets/images/kick-target.svg new file mode 100644 index 0000000000..21cb3a5462 --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/kick-target.svg @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/lock.svg b/scripts/simplifiedUI/system/assets/images/lock.svg new file mode 100644 index 0000000000..bb9658de00 --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/lock.svg @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/lod-a.svg b/scripts/simplifiedUI/system/assets/images/lod-a.svg new file mode 100644 index 0000000000..6845e0ff78 --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/lod-a.svg @@ -0,0 +1,46 @@ + + + +image/svg+xml \ No newline at end of file diff --git a/scripts/simplifiedUI/system/assets/images/lod-i.svg b/scripts/simplifiedUI/system/assets/images/lod-i.svg new file mode 100644 index 0000000000..f909f3b495 --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/lod-i.svg @@ -0,0 +1,46 @@ + + + +image/svg+xml \ No newline at end of file diff --git a/scripts/simplifiedUI/system/assets/images/luci-a.svg b/scripts/simplifiedUI/system/assets/images/luci-a.svg new file mode 100644 index 0000000000..40e1481eba --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/luci-a.svg @@ -0,0 +1,10 @@ + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/luci-i.svg b/scripts/simplifiedUI/system/assets/images/luci-i.svg new file mode 100644 index 0000000000..2d73339908 --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/luci-i.svg @@ -0,0 +1,13 @@ + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/materials/GridPattern.json b/scripts/simplifiedUI/system/assets/images/materials/GridPattern.json new file mode 100644 index 0000000000..468b709ea4 --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/materials/GridPattern.json @@ -0,0 +1,13 @@ +{ + "materialVersion": 1, + "materials": { + "albedo": [ + 0.0, + 0.0, + 7.0 + ], + "unlit": true, + "opacity": 0.4, + "albedoMap": "GridPattern.png" + } +} diff --git a/scripts/simplifiedUI/system/assets/images/materials/GridPattern.png b/scripts/simplifiedUI/system/assets/images/materials/GridPattern.png new file mode 100644 index 0000000000..2ecc7f8570 Binary files /dev/null and b/scripts/simplifiedUI/system/assets/images/materials/GridPattern.png differ diff --git a/scripts/simplifiedUI/system/assets/images/min-max-toggle.svg b/scripts/simplifiedUI/system/assets/images/min-max-toggle.svg new file mode 100644 index 0000000000..1699cc705d --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/min-max-toggle.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/mute-target.svg b/scripts/simplifiedUI/system/assets/images/mute-target.svg new file mode 100644 index 0000000000..1ed642c79e --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/mute-target.svg @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/progress-bar-2k.svg b/scripts/simplifiedUI/system/assets/images/progress-bar-2k.svg new file mode 100644 index 0000000000..45758c7c68 --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/progress-bar-2k.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/progress-bar-4k.svg b/scripts/simplifiedUI/system/assets/images/progress-bar-4k.svg new file mode 100644 index 0000000000..609ab9610b --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/progress-bar-4k.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/progress-bar-text.svg b/scripts/simplifiedUI/system/assets/images/progress-bar-text.svg new file mode 100644 index 0000000000..05ebb3f637 --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/progress-bar-text.svg @@ -0,0 +1,103 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/run.svg b/scripts/simplifiedUI/system/assets/images/run.svg new file mode 100644 index 0000000000..0957166346 --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/run.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/scripts/simplifiedUI/system/assets/images/textures/dirt.jpeg b/scripts/simplifiedUI/system/assets/images/textures/dirt.jpeg new file mode 100644 index 0000000000..694e4f3c9a Binary files /dev/null and b/scripts/simplifiedUI/system/assets/images/textures/dirt.jpeg differ diff --git a/scripts/simplifiedUI/system/assets/images/textures/grass.png b/scripts/simplifiedUI/system/assets/images/textures/grass.png new file mode 100644 index 0000000000..d51fe0cf28 Binary files /dev/null and b/scripts/simplifiedUI/system/assets/images/textures/grass.png differ diff --git a/scripts/simplifiedUI/system/assets/images/tools/add-remove-friends.svg b/scripts/simplifiedUI/system/assets/images/tools/add-remove-friends.svg new file mode 100644 index 0000000000..6ee9ce842d --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/tools/add-remove-friends.svg @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/tools/assets-01.svg b/scripts/simplifiedUI/system/assets/images/tools/assets-01.svg new file mode 100644 index 0000000000..d7bdd1d7f0 --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/tools/assets-01.svg @@ -0,0 +1,186 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/tools/bubble.svg b/scripts/simplifiedUI/system/assets/images/tools/bubble.svg new file mode 100644 index 0000000000..064b7734a9 --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/tools/bubble.svg @@ -0,0 +1,275 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/tools/cube-01.svg b/scripts/simplifiedUI/system/assets/images/tools/cube-01.svg new file mode 100644 index 0000000000..f8cf96e4f2 --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/tools/cube-01.svg @@ -0,0 +1,103 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/tools/directory.svg b/scripts/simplifiedUI/system/assets/images/tools/directory.svg new file mode 100644 index 0000000000..09f3c14bf0 --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/tools/directory.svg @@ -0,0 +1,255 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/tools/edit.svg b/scripts/simplifiedUI/system/assets/images/tools/edit.svg new file mode 100644 index 0000000000..f65e0cd84d --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/tools/edit.svg @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/tools/help.svg b/scripts/simplifiedUI/system/assets/images/tools/help.svg new file mode 100644 index 0000000000..b7fa8ca5cd --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/tools/help.svg @@ -0,0 +1,110 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/tools/ignore.svg b/scripts/simplifiedUI/system/assets/images/tools/ignore.svg new file mode 100644 index 0000000000..f315c5f249 --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/tools/ignore.svg @@ -0,0 +1,177 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/tools/kick.svg b/scripts/simplifiedUI/system/assets/images/tools/kick.svg new file mode 100644 index 0000000000..1eed6e7f43 --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/tools/kick.svg @@ -0,0 +1,140 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/tools/light-01.svg b/scripts/simplifiedUI/system/assets/images/tools/light-01.svg new file mode 100644 index 0000000000..4573c7d636 --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/tools/light-01.svg @@ -0,0 +1,112 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/tools/market.svg b/scripts/simplifiedUI/system/assets/images/tools/market.svg new file mode 100644 index 0000000000..0cec030933 --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/tools/market.svg @@ -0,0 +1,147 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/tools/mic.svg b/scripts/simplifiedUI/system/assets/images/tools/mic.svg new file mode 100644 index 0000000000..3a7c183bc4 --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/tools/mic.svg @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/tools/min-max-toggle.svg b/scripts/simplifiedUI/system/assets/images/tools/min-max-toggle.svg new file mode 100644 index 0000000000..1699cc705d --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/tools/min-max-toggle.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/tools/model-01.svg b/scripts/simplifiedUI/system/assets/images/tools/model-01.svg new file mode 100644 index 0000000000..e760d74d5c --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/tools/model-01.svg @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/tools/particle-01.svg b/scripts/simplifiedUI/system/assets/images/tools/particle-01.svg new file mode 100644 index 0000000000..cfcfb0ea7f --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/tools/particle-01.svg @@ -0,0 +1,161 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/tools/people.svg b/scripts/simplifiedUI/system/assets/images/tools/people.svg new file mode 100644 index 0000000000..5fedfefa75 --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/tools/people.svg @@ -0,0 +1,161 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/tools/polyvox.svg b/scripts/simplifiedUI/system/assets/images/tools/polyvox.svg new file mode 100644 index 0000000000..69f1e978ff --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/tools/polyvox.svg @@ -0,0 +1,39 @@ + + + + +polyvox + + + +voxels + + diff --git a/scripts/simplifiedUI/system/assets/images/tools/snap.svg b/scripts/simplifiedUI/system/assets/images/tools/snap.svg new file mode 100644 index 0000000000..c540f307ae --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/tools/snap.svg @@ -0,0 +1,109 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/tools/sphere-01.svg b/scripts/simplifiedUI/system/assets/images/tools/sphere-01.svg new file mode 100644 index 0000000000..975199c8da --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/tools/sphere-01.svg @@ -0,0 +1,108 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/tools/sphere-add.svg b/scripts/simplifiedUI/system/assets/images/tools/sphere-add.svg new file mode 100644 index 0000000000..59ca489b5f --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/tools/sphere-add.svg @@ -0,0 +1,77 @@ + + + +image/svg+xmladd + \ No newline at end of file diff --git a/scripts/simplifiedUI/system/assets/images/tools/sphere-delete.svg b/scripts/simplifiedUI/system/assets/images/tools/sphere-delete.svg new file mode 100644 index 0000000000..6997654500 --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/tools/sphere-delete.svg @@ -0,0 +1,76 @@ + + + +image/svg+xmldelete + \ No newline at end of file diff --git a/scripts/simplifiedUI/system/assets/images/tools/steam-invite.svg b/scripts/simplifiedUI/system/assets/images/tools/steam-invite.svg new file mode 100644 index 0000000000..ce225cca68 --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/tools/steam-invite.svg @@ -0,0 +1,112 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/tools/switch.svg b/scripts/simplifiedUI/system/assets/images/tools/switch.svg new file mode 100644 index 0000000000..e67a9aac04 --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/tools/switch.svg @@ -0,0 +1,167 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/tools/text-01.svg b/scripts/simplifiedUI/system/assets/images/tools/text-01.svg new file mode 100644 index 0000000000..d33d66d4a5 --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/tools/text-01.svg @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/tools/upload-01.svg b/scripts/simplifiedUI/system/assets/images/tools/upload-01.svg new file mode 100644 index 0000000000..149b10f3bc --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/tools/upload-01.svg @@ -0,0 +1,103 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/tools/voxel-add.svg b/scripts/simplifiedUI/system/assets/images/tools/voxel-add.svg new file mode 100644 index 0000000000..8e6e2c5b35 --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/tools/voxel-add.svg @@ -0,0 +1,104 @@ + + + +image/svg+xmladd + \ No newline at end of file diff --git a/scripts/simplifiedUI/system/assets/images/tools/voxel-delete.svg b/scripts/simplifiedUI/system/assets/images/tools/voxel-delete.svg new file mode 100644 index 0000000000..0b0d0b9787 --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/tools/voxel-delete.svg @@ -0,0 +1,103 @@ + + + +image/svg+xmldelete + \ No newline at end of file diff --git a/scripts/simplifiedUI/system/assets/images/tools/voxel-terrain.svg b/scripts/simplifiedUI/system/assets/images/tools/voxel-terrain.svg new file mode 100644 index 0000000000..e5ed16dbcd --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/tools/voxel-terrain.svg @@ -0,0 +1,101 @@ + + + +image/svg+xmlterrain + \ No newline at end of file diff --git a/scripts/simplifiedUI/system/assets/images/tools/voxels.svg b/scripts/simplifiedUI/system/assets/images/tools/voxels.svg new file mode 100644 index 0000000000..a0dbd63d45 --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/tools/voxels.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/tools/web-01.svg b/scripts/simplifiedUI/system/assets/images/tools/web-01.svg new file mode 100644 index 0000000000..903b3ac819 --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/tools/web-01.svg @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/tools/zone-01.svg b/scripts/simplifiedUI/system/assets/images/tools/zone-01.svg new file mode 100644 index 0000000000..29d17e5187 --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/tools/zone-01.svg @@ -0,0 +1,131 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/unlock.svg b/scripts/simplifiedUI/system/assets/images/unlock.svg new file mode 100644 index 0000000000..789a8b0ed5 --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/unlock.svg @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/models/Avatar-Overlay-v1.fbx b/scripts/simplifiedUI/system/assets/models/Avatar-Overlay-v1.fbx new file mode 100644 index 0000000000..db710702f2 Binary files /dev/null and b/scripts/simplifiedUI/system/assets/models/Avatar-Overlay-v1.fbx differ diff --git a/scripts/simplifiedUI/system/assets/models/Bubble-v14.fbx b/scripts/simplifiedUI/system/assets/models/Bubble-v14.fbx new file mode 100644 index 0000000000..c7d3122f00 Binary files /dev/null and b/scripts/simplifiedUI/system/assets/models/Bubble-v14.fbx differ diff --git a/scripts/simplifiedUI/system/assets/models/black-sphere.fbx b/scripts/simplifiedUI/system/assets/models/black-sphere.fbx new file mode 100644 index 0000000000..2e6dea233f Binary files /dev/null and b/scripts/simplifiedUI/system/assets/models/black-sphere.fbx differ diff --git a/scripts/simplifiedUI/system/assets/models/miniTabletBlank.fbx b/scripts/simplifiedUI/system/assets/models/miniTabletBlank.fbx new file mode 100644 index 0000000000..a2faa2a80a Binary files /dev/null and b/scripts/simplifiedUI/system/assets/models/miniTabletBlank.fbx differ diff --git a/scripts/simplifiedUI/system/assets/models/oculusSensorv11.fbx b/scripts/simplifiedUI/system/assets/models/oculusSensorv11.fbx new file mode 100644 index 0000000000..52fadc77dc Binary files /dev/null and b/scripts/simplifiedUI/system/assets/models/oculusSensorv11.fbx differ diff --git a/scripts/simplifiedUI/system/assets/models/teleport-cancel.fbx b/scripts/simplifiedUI/system/assets/models/teleport-cancel.fbx new file mode 100644 index 0000000000..1c12e28159 Binary files /dev/null and b/scripts/simplifiedUI/system/assets/models/teleport-cancel.fbx differ diff --git a/scripts/simplifiedUI/system/assets/models/teleport-destination.fbm/Teleportation-Destination-Texture2.png b/scripts/simplifiedUI/system/assets/models/teleport-destination.fbm/Teleportation-Destination-Texture2.png new file mode 100644 index 0000000000..eb9addcfca Binary files /dev/null and b/scripts/simplifiedUI/system/assets/models/teleport-destination.fbm/Teleportation-Destination-Texture2.png differ diff --git a/scripts/simplifiedUI/system/assets/models/teleport-destination.fbx b/scripts/simplifiedUI/system/assets/models/teleport-destination.fbx new file mode 100644 index 0000000000..5fdb0d56af Binary files /dev/null and b/scripts/simplifiedUI/system/assets/models/teleport-destination.fbx differ diff --git a/scripts/simplifiedUI/system/assets/models/teleport-seat.fbx b/scripts/simplifiedUI/system/assets/models/teleport-seat.fbx new file mode 100644 index 0000000000..cd7a9abc7e Binary files /dev/null and b/scripts/simplifiedUI/system/assets/models/teleport-seat.fbx differ diff --git a/scripts/simplifiedUI/system/assets/models/teleportationSpotBasev8.fbx b/scripts/simplifiedUI/system/assets/models/teleportationSpotBasev8.fbx new file mode 100644 index 0000000000..d651575dea Binary files /dev/null and b/scripts/simplifiedUI/system/assets/models/teleportationSpotBasev8.fbx differ diff --git a/scripts/simplifiedUI/system/assets/models/trackingSpacev18.fbx b/scripts/simplifiedUI/system/assets/models/trackingSpacev18.fbx new file mode 100644 index 0000000000..16597eb285 Binary files /dev/null and b/scripts/simplifiedUI/system/assets/models/trackingSpacev18.fbx differ diff --git a/scripts/simplifiedUI/system/assets/sounds/bubble.wav b/scripts/simplifiedUI/system/assets/sounds/bubble.wav new file mode 100644 index 0000000000..fd23a235d4 Binary files /dev/null and b/scripts/simplifiedUI/system/assets/sounds/bubble.wav differ diff --git a/scripts/simplifiedUI/system/assets/sounds/button-click.wav b/scripts/simplifiedUI/system/assets/sounds/button-click.wav new file mode 100644 index 0000000000..30a097ce45 Binary files /dev/null and b/scripts/simplifiedUI/system/assets/sounds/button-click.wav differ diff --git a/scripts/simplifiedUI/system/assets/sounds/button-hover.wav b/scripts/simplifiedUI/system/assets/sounds/button-hover.wav new file mode 100644 index 0000000000..cd76d0174c Binary files /dev/null and b/scripts/simplifiedUI/system/assets/sounds/button-hover.wav differ diff --git a/scripts/simplifiedUI/system/assets/sounds/countdown-tick.wav b/scripts/simplifiedUI/system/assets/sounds/countdown-tick.wav new file mode 100644 index 0000000000..015e1f642e Binary files /dev/null and b/scripts/simplifiedUI/system/assets/sounds/countdown-tick.wav differ diff --git a/scripts/simplifiedUI/system/assets/sounds/entitySnap.wav b/scripts/simplifiedUI/system/assets/sounds/entitySnap.wav new file mode 100644 index 0000000000..4584f3dcaa Binary files /dev/null and b/scripts/simplifiedUI/system/assets/sounds/entitySnap.wav differ diff --git a/scripts/simplifiedUI/system/assets/sounds/finish-recording.wav b/scripts/simplifiedUI/system/assets/sounds/finish-recording.wav new file mode 100644 index 0000000000..f224049f97 Binary files /dev/null and b/scripts/simplifiedUI/system/assets/sounds/finish-recording.wav differ diff --git a/scripts/simplifiedUI/system/assets/sounds/goodbye.wav b/scripts/simplifiedUI/system/assets/sounds/goodbye.wav new file mode 100644 index 0000000000..bd6c51a5c0 Binary files /dev/null and b/scripts/simplifiedUI/system/assets/sounds/goodbye.wav differ diff --git a/scripts/simplifiedUI/system/assets/sounds/hello.wav b/scripts/simplifiedUI/system/assets/sounds/hello.wav new file mode 100644 index 0000000000..6269dab5db Binary files /dev/null and b/scripts/simplifiedUI/system/assets/sounds/hello.wav differ diff --git a/scripts/simplifiedUI/system/assets/sounds/notification-general1.raw b/scripts/simplifiedUI/system/assets/sounds/notification-general1.raw new file mode 100644 index 0000000000..be81fa15c5 Binary files /dev/null and b/scripts/simplifiedUI/system/assets/sounds/notification-general1.raw differ diff --git a/scripts/simplifiedUI/system/assets/sounds/notification-general2.raw b/scripts/simplifiedUI/system/assets/sounds/notification-general2.raw new file mode 100644 index 0000000000..58f0bac19c Binary files /dev/null and b/scripts/simplifiedUI/system/assets/sounds/notification-general2.raw differ diff --git a/scripts/simplifiedUI/system/assets/sounds/rezzing.wav b/scripts/simplifiedUI/system/assets/sounds/rezzing.wav new file mode 100644 index 0000000000..3c059aecdf Binary files /dev/null and b/scripts/simplifiedUI/system/assets/sounds/rezzing.wav differ diff --git a/scripts/simplifiedUI/system/assets/sounds/short1.wav b/scripts/simplifiedUI/system/assets/sounds/short1.wav new file mode 100644 index 0000000000..fb03f5dd49 Binary files /dev/null and b/scripts/simplifiedUI/system/assets/sounds/short1.wav differ diff --git a/scripts/simplifiedUI/system/assets/sounds/start-recording.wav b/scripts/simplifiedUI/system/assets/sounds/start-recording.wav new file mode 100644 index 0000000000..71c69f3372 Binary files /dev/null and b/scripts/simplifiedUI/system/assets/sounds/start-recording.wav differ diff --git a/scripts/simplifiedUI/system/attachedEntitiesManager.js b/scripts/simplifiedUI/system/attachedEntitiesManager.js new file mode 100644 index 0000000000..061e27f595 --- /dev/null +++ b/scripts/simplifiedUI/system/attachedEntitiesManager.js @@ -0,0 +1,285 @@ +// +// attachedEntitiesManager.js +// +// Created by Seth Alves on 2016-1-20 +// Copyright 2016 High Fidelity, Inc. +// +// This script handles messages from the grab script related to wearables, and interacts with a doppelganger. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +Script.include("libraries/utils.js"); + +var DEFAULT_WEARABLE_DATA = { + joints: {} +}; + + +var MINIMUM_DROP_DISTANCE_FROM_JOINT = 0.8; +var ATTACHED_ENTITY_SEARCH_DISTANCE = 10.0; +var ATTACHED_ENTITIES_SETTINGS_KEY = "ATTACHED_ENTITIES"; +var DRESSING_ROOM_DISTANCE = 2.0; +var SHOW_TOOL_BAR = false; + +// tool bar + +if (SHOW_TOOL_BAR) { + var BUTTON_SIZE = 64; + var PADDING = 6; + Script.include(["libraries/toolBars.js"]); + + var toolBar = new ToolBar(0, 0, ToolBar.HORIZONTAL, "highfidelity.attachedEntities.toolbar"); + var lockButton = toolBar.addTool({ + width: BUTTON_SIZE, + height: BUTTON_SIZE, + imageURL: Script.resolvePath("assets/images/lock.svg"), + color: { + red: 255, + green: 255, + blue: 255 + }, + alpha: 1, + visible: true + }, false); +} + + +function mousePressEvent(event) { + var clickedOverlay = Overlays.getOverlayAtPoint({ + x: event.x, + y: event.y + }); + + if (lockButton === toolBar.clicked(clickedOverlay)) { + manager.toggleLocked(); + } +} + +function scriptEnding() { + if (SHOW_TOOL_BAR) { + toolBar.cleanup(); + } +} + +if (SHOW_TOOL_BAR) { + Controller.mousePressEvent.connect(mousePressEvent); +} +Script.scriptEnding.connect(scriptEnding); + + + +// attached entites + + +function AttachedEntitiesManager() { + var clothingLocked = false; + + this.subscribeToMessages = function() { + Messages.subscribe('Hifi-Object-Manipulation'); + Messages.messageReceived.connect(this.handleWearableMessages); + } + + this.handleWearableMessages = function(channel, message, sender) { + if (channel !== 'Hifi-Object-Manipulation') { + return; + } + + var parsedMessage = null; + + try { + parsedMessage = JSON.parse(message); + } catch (e) { + print('error parsing wearable message'); + return; + } + + if (parsedMessage.action === 'update' || + parsedMessage.action === 'loaded') { + // ignore + } else if (parsedMessage.action === 'release') { + manager.handleEntityRelease(parsedMessage.grabbedEntity, parsedMessage.joint) + // manager.saveAttachedEntities(); + } else if (parsedMessage.action === 'equip') { + // manager.saveAttachedEntities(); + } else { + print('attachedEntitiesManager -- unknown actions: ' + parsedMessage.action); + } + } + + this.handleEntityRelease = function(grabbedEntity, releasedFromJoint) { + // if this is still equipped, just rewrite the position information. + var grabData = getEntityCustomData('grabKey', grabbedEntity, {}); + + var allowedJoints = getEntityCustomData('wearable', grabbedEntity, DEFAULT_WEARABLE_DATA).joints; + + var props = Entities.getEntityProperties(grabbedEntity, ["position", "parentID", "parentJointIndex"]); + if (props.parentID === Uuid.NULL || props.parentID === MyAvatar.sessionUUID) { + var bestJointName = ""; + var bestJointIndex = -1; + var bestJointDistance = 0; + var bestJointOffset = null; + for (var jointName in allowedJoints) { + if ((releasedFromJoint == "LeftHand" || releasedFromJoint == "RightHand") && + (jointName == "LeftHand" || jointName == "RightHand")) { + // don't auto-attach to a hand if a hand just dropped something + continue; + } + var jointIndex = MyAvatar.getJointIndex(jointName); + if (jointIndex >= 0) { + var jointPosition = MyAvatar.getJointPosition(jointIndex); + var distanceFromJoint = Vec3.distance(jointPosition, props.position); + if (distanceFromJoint <= MINIMUM_DROP_DISTANCE_FROM_JOINT) { + if (bestJointIndex == -1 || distanceFromJoint < bestJointDistance) { + bestJointName = jointName; + bestJointIndex = jointIndex; + bestJointDistance = distanceFromJoint; + bestJointOffset = allowedJoints[jointName]; + } + } + } + } + + if (bestJointIndex != -1) { + var wearProps = Entities.getEntityProperties(grabbedEntity); + wearProps.parentID = MyAvatar.sessionUUID; + wearProps.parentJointIndex = bestJointIndex; + delete wearProps.localPosition; + delete wearProps.localRotation; + var updatePresets = false; + if (bestJointOffset && bestJointOffset.constructor === Array) { + if (!clothingLocked || bestJointOffset.length < 2) { + // we're unlocked or this thing didn't have a preset position, so update it + updatePresets = true; + } else { + // don't snap the entity to the preferred position if unlocked + wearProps.localPosition = bestJointOffset[0]; + wearProps.localRotation = bestJointOffset[1]; + } + } + + Entities.deleteEntity(grabbedEntity); + //the true boolean here after add entity adds it as an 'avatar entity', which can travel with you from server to server. + + var newEntity = Entities.addEntity(wearProps, true); + + if (updatePresets) { + this.updateRelativeOffsets(newEntity); + } + } else if (props.parentID != Uuid.NULL) { + // drop the entity and set it to have no parent (not on the avatar), unless it's being equipped in a hand. + if (props.parentID === MyAvatar.sessionUUID && + (props.parentJointIndex == MyAvatar.getJointIndex("RightHand") || + props.parentJointIndex == MyAvatar.getJointIndex("LeftHand"))) { + // this is equipped on a hand -- don't clear the parent. + } else { + var wearProps = Entities.getEntityProperties(grabbedEntity); + wearProps.parentID = Uuid.NULL; + wearProps.parentJointIndex = -1; + delete wearProps.id; + delete wearProps.created; + delete wearProps.age; + delete wearProps.ageAsText; + delete wearProps.naturalDimensions; + delete wearProps.naturalPosition; + delete wearProps.actionData; + delete wearProps.sittingPoints; + delete wearProps.boundingBox; + delete wearProps.avatarEntity; + delete wearProps.owningAvatarID; + delete wearProps.localPosition; + delete wearProps.localRotation; + Entities.deleteEntity(grabbedEntity); + Entities.addEntity(wearProps); + } + } + } + } + + this.updateRelativeOffsets = function(entityID) { + // save the preferred (current) relative position and rotation into the user-data of the entity + var props = Entities.getEntityProperties(entityID); + if (props.parentID == MyAvatar.sessionUUID) { + grabData = getEntityCustomData('grabKey', entityID, {}); + var wearableData = getEntityCustomData('wearable', entityID, DEFAULT_WEARABLE_DATA); + var currentJointName = MyAvatar.getJointNames()[props.parentJointIndex]; + wearableData.joints[currentJointName] = [props.localPosition, props.localRotation]; + setEntityCustomData('wearable', entityID, wearableData); + return true; + } + return false; + } + + // this.saveAttachedEntities = function() { + // print("--- saving attached entities ---"); + // saveData = []; + // var nearbyEntities = Entities.findEntities(MyAvatar.position, ATTACHED_ENTITY_SEARCH_DISTANCE); + // for (i = 0; i < nearbyEntities.length; i++) { + // var entityID = nearbyEntities[i]; + // if (this.updateRelativeOffsets(entityID)) { + // var props = Entities.getEntityProperties(entityID); // refresh, because updateRelativeOffsets changed them + // this.scrubProperties(props); + // saveData.push(props); + // } + // } + // Settings.setValue(ATTACHED_ENTITIES_SETTINGS_KEY, JSON.stringify(saveData)); + // } + + // this.scrubProperties = function(props) { + // var toScrub = ["queryAACube", "position", "rotation", + // "created", "ageAsText", "naturalDimensions", + // "naturalPosition", "velocity", "acceleration", + // "angularVelocity", "boundingBox"]; + // toScrub.forEach(function(propertyName) { + // delete props[propertyName]; + // }); + // // if the userData has a grabKey, clear old state + // if ("userData" in props) { + // try { + // parsedUserData = JSON.parse(props.userData); + // if ("grabKey" in parsedUserData) { + // parsedUserData.grabKey.refCount = 0; + // delete parsedUserData.grabKey["avatarId"]; + // props["userData"] = JSON.stringify(parsedUserData); + // } + // } catch (e) { + // } + // } + // } + + // this.loadAttachedEntities = function(grabbedEntity) { + // print("--- loading attached entities ---"); + // jsonAttachmentData = Settings.getValue(ATTACHED_ENTITIES_SETTINGS_KEY); + // var loadData = []; + // try { + // loadData = JSON.parse(jsonAttachmentData); + // } catch (e) { + // print('error parsing saved attachment data'); + // return; + // } + + // for (i = 0; i < loadData.length; i ++) { + // var savedProps = loadData[ i ]; + // var currentProps = Entities.getEntityProperties(savedProps.id); + // if (currentProps.id == savedProps.id && + // // TODO -- also check that parentJointIndex matches? + // currentProps.parentID == MyAvatar.sessionUUID) { + // // entity is already in-world. TODO -- patch it up? + // continue; + // } + // this.scrubProperties(savedProps); + // delete savedProps["id"]; + // savedProps.parentID = MyAvatar.sessionUUID; // this will change between sessions + // var loadedEntityID = Entities.addEntity(savedProps, true); + + // Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({ + // action: 'loaded', + // grabbedEntity: loadedEntityID + // })); + // } + // } +} + +var manager = new AttachedEntitiesManager(); +manager.subscribeToMessages(); \ No newline at end of file diff --git a/scripts/simplifiedUI/system/audio.js b/scripts/simplifiedUI/system/audio.js new file mode 100644 index 0000000000..a161b40ffd --- /dev/null +++ b/scripts/simplifiedUI/system/audio.js @@ -0,0 +1,92 @@ +"use strict"; + +// +// audio.js +// +// Created by Howard Stearns on 2 Jun 2016 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +/* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */ + +(function() { // BEGIN LOCAL_SCOPE + +var TABLET_BUTTON_NAME = "AUDIO"; +var HOME_BUTTON_TEXTURE = "http://hifi-content.s3.amazonaws.com/alan/dev/tablet-with-home-button.fbx/tablet-with-home-button.fbm/button-root.png"; +var AUDIO_QML_SOURCE = "hifi/audio/Audio.qml"; + +var MUTE_ICONS = { + icon: "icons/tablet-icons/mic-mute-i.svg", + activeIcon: "icons/tablet-icons/mic-mute-a.svg" +}; + +var UNMUTE_ICONS = { + icon: "icons/tablet-icons/mic-unmute-i.svg", + activeIcon: "icons/tablet-icons/mic-unmute-a.svg" +}; +var PTT_ICONS = { + icon: "icons/tablet-icons/mic-ptt-i.svg", + activeIcon: "icons/tablet-icons/mic-ptt-a.svg" +}; + +function onMuteToggled() { + if (Audio.pushToTalk) { + button.editProperties(PTT_ICONS); + } else if (Audio.muted) { + button.editProperties(MUTE_ICONS); + } else { + button.editProperties(UNMUTE_ICONS); + } +} + +var onAudioScreen = false; + +function onClicked() { + if (onAudioScreen) { + // for toolbar-mode: go back to home screen, this will close the window. + tablet.gotoHomeScreen(); + } else { + if (HMD.tabletID) { + Entities.editEntity(HMD.tabletID, { textures: JSON.stringify({ "tex.close": HOME_BUTTON_TEXTURE }) }); + } + tablet.loadQMLSource(AUDIO_QML_SOURCE); + } +} + +function onScreenChanged(type, url) { + onAudioScreen = (type === "QML" && url === AUDIO_QML_SOURCE); + // for toolbar mode: change button to active when window is first openend, false otherwise. + button.editProperties({isActive: onAudioScreen}); +} + +var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); +var button = tablet.addButton({ + icon: Audio.pushToTalk ? PTT_ICONS.icon : Audio.muted ? MUTE_ICONS.icon : UNMUTE_ICONS.icon, + activeIcon: Audio.pushToTalk ? PTT_ICONS.activeIcon : Audio.muted ? MUTE_ICONS.activeIcon : UNMUTE_ICONS.activeIcon, + text: TABLET_BUTTON_NAME, + sortOrder: 1 +}); + +onMuteToggled(); + +button.clicked.connect(onClicked); +tablet.screenChanged.connect(onScreenChanged); +Audio.mutedChanged.connect(onMuteToggled); +Audio.pushToTalkChanged.connect(onMuteToggled); +HMD.displayModeChanged.connect(onMuteToggled); + +Script.scriptEnding.connect(function () { + if (onAudioScreen) { + tablet.gotoHomeScreen(); + } + button.clicked.disconnect(onClicked); + tablet.screenChanged.disconnect(onScreenChanged); + Audio.mutedChanged.disconnect(onMuteToggled); + Audio.pushToTalkChanged.disconnect(onMuteToggled); + HMD.displayModeChanged.disconnect(onMuteToggled); + tablet.removeButton(button); +}); + +}()); // END LOCAL_SCOPE diff --git a/scripts/simplifiedUI/system/audioMuteOverlay.js b/scripts/simplifiedUI/system/audioMuteOverlay.js new file mode 100644 index 0000000000..9acc5ab123 --- /dev/null +++ b/scripts/simplifiedUI/system/audioMuteOverlay.js @@ -0,0 +1,130 @@ +// +// audioMuteOverlay.js +// +// client script that creates an overlay to provide mute feedback +// +// Created by Triplelexx on 17/03/09 +// Reworked by Seth Alves on 2019-2-17 +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +"use strict"; + +/* global Audio, Script, Overlays, Quat, MyAvatar, HMD */ + +(function() { // BEGIN LOCAL_SCOPE + + var lastShortTermInputLoudness = 0.0; + var lastLongTermInputLoudness = 0.0; + var sampleRate = 8.0; // Hz + + var shortTermAttackTC = Math.exp(-1.0 / (sampleRate * 0.500)); // 500 milliseconds attack + var shortTermReleaseTC = Math.exp(-1.0 / (sampleRate * 1.000)); // 1000 milliseconds release + + var longTermAttackTC = Math.exp(-1.0 / (sampleRate * 5.0)); // 5 second attack + var longTermReleaseTC = Math.exp(-1.0 / (sampleRate * 10.0)); // 10 seconds release + + var activationThreshold = 0.05; // how much louder short-term needs to be than long-term to trigger warning + + var holdReset = 2.0 * sampleRate; // 2 seconds hold + var holdCount = 0; + var warningOverlayID = null; + var pollInterval = null; + var warningText = "Muted"; + + function showWarning() { + if (warningOverlayID) { + return; + } + + if (HMD.active) { + warningOverlayID = Overlays.addOverlay("text3d", { + name: "Muted-Warning", + localPosition: { x: 0.0, y: -0.45, z: -1.0 }, + localOrientation: Quat.fromVec3Degrees({ x: 0.0, y: 0.0, z: 0.0, w: 1.0 }), + text: warningText, + textAlpha: 1, + textColor: { red: 226, green: 51, blue: 77 }, + backgroundAlpha: 0, + lineHeight: 0.042, + dimensions: { x: 0.11, y: 0.05 }, + visible: true, + ignoreRayIntersection: true, + drawInFront: true, + grabbable: false, + parentID: MyAvatar.SELF_ID, + parentJointIndex: MyAvatar.getJointIndex("_CAMERA_MATRIX") + }); + } + } + + function hideWarning() { + if (!warningOverlayID) { + return; + } + Overlays.deleteOverlay(warningOverlayID); + warningOverlayID = null; + } + + function startPoll() { + if (pollInterval) { + return; + } + pollInterval = Script.setInterval(function() { + var shortTermInputLoudness = Audio.inputLevel; + var longTermInputLoudness = shortTermInputLoudness; + + var shortTc = (shortTermInputLoudness > lastShortTermInputLoudness) ? shortTermAttackTC : shortTermReleaseTC; + var longTc = (longTermInputLoudness > lastLongTermInputLoudness) ? longTermAttackTC : longTermReleaseTC; + + shortTermInputLoudness += shortTc * (lastShortTermInputLoudness - shortTermInputLoudness); + longTermInputLoudness += longTc * (lastLongTermInputLoudness - longTermInputLoudness); + + lastShortTermInputLoudness = shortTermInputLoudness; + lastLongTermInputLoudness = longTermInputLoudness; + + if (shortTermInputLoudness > lastLongTermInputLoudness + activationThreshold) { + holdCount = holdReset; + } else { + holdCount = Math.max(holdCount - 1, 0); + } + + if (holdCount > 0) { + showWarning(); + } else { + hideWarning(); + } + }, 1000.0 / sampleRate); + } + + function stopPoll() { + if (!pollInterval) { + return; + } + Script.clearInterval(pollInterval); + pollInterval = null; + hideWarning(); + } + + function startOrStopPoll() { + if (Audio.warnWhenMuted && Audio.muted) { + startPoll(); + } else { + stopPoll(); + } + } + + function cleanup() { + stopPoll(); + } + + Script.scriptEnding.connect(cleanup); + + startOrStopPoll(); + Audio.mutedChanged.connect(startOrStopPoll); + Audio.warnWhenMutedChanged.connect(startOrStopPoll); + +}()); // END LOCAL_SCOPE diff --git a/scripts/simplifiedUI/system/audioScope.js b/scripts/simplifiedUI/system/audioScope.js new file mode 100644 index 0000000000..81d8e8fbd4 --- /dev/null +++ b/scripts/simplifiedUI/system/audioScope.js @@ -0,0 +1,95 @@ +"use strict"; +// +// audioScope.js +// scripts/system/ +// +// Created by Brad Hefta-Gaub on 3/10/2016 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +/* global Script, Tablet, AudioScope, Audio */ + +(function () { // BEGIN LOCAL_SCOPE + + var scopeVisibile = AudioScope.getVisible(); + var scopePaused = AudioScope.getPause(); + var autoPause = false; + + var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + var showScopeButton = tablet.addButton({ + icon: "icons/tablet-icons/scope.svg", + text: "Audio Scope", + isActive: scopeVisibile + }); + + var scopePauseImage = "icons/tablet-icons/scope-pause.svg"; + var scopePlayImage = "icons/tablet-icons/scope-play.svg"; + + var pauseScopeButton = tablet.addButton({ + icon: scopePaused ? scopePlayImage : scopePauseImage, + text: scopePaused ? "Unpause" : "Pause", + isActive: scopePaused + }); + + var autoPauseScopeButton = tablet.addButton({ + icon: "icons/tablet-icons/scope-auto.svg", + text: "Auto Pause", + isActive: autoPause + }); + + function setScopePause(paused) { + scopePaused = paused; + pauseScopeButton.editProperties({ + isActive: scopePaused, + icon: scopePaused ? scopePlayImage : scopePauseImage, + text: scopePaused ? "Unpause" : "Pause" + }); + AudioScope.setPause(scopePaused); + } + + showScopeButton.clicked.connect(function () { + // toggle button active state + scopeVisibile = !scopeVisibile; + showScopeButton.editProperties({ + isActive: scopeVisibile + }); + + AudioScope.setVisible(scopeVisibile); + }); + + pauseScopeButton.clicked.connect(function () { + // toggle button active state + setScopePause(!scopePaused); + }); + + autoPauseScopeButton.clicked.connect(function () { + // toggle button active state + autoPause = !autoPause; + autoPauseScopeButton.editProperties({ + isActive: autoPause, + text: autoPause ? "Auto Pause" : "Manual" + }); + }); + + Script.scriptEnding.connect(function () { + tablet.removeButton(showScopeButton); + tablet.removeButton(pauseScopeButton); + tablet.removeButton(autoPauseScopeButton); + }); + + Audio.noiseGateOpened.connect(function(){ + if (autoPause) { + setScopePause(false); + } + }); + + Audio.noiseGateClosed.connect(function(){ + // noise gate closed + if (autoPause) { + setScopePause(true); + } + }); + +}()); // END LOCAL_SCOPE \ No newline at end of file diff --git a/scripts/simplifiedUI/system/avatarFinderBeacon.js b/scripts/simplifiedUI/system/avatarFinderBeacon.js new file mode 100644 index 0000000000..00f3d15fbb --- /dev/null +++ b/scripts/simplifiedUI/system/avatarFinderBeacon.js @@ -0,0 +1,93 @@ +// avatarFinderBeacon.js +// +// Created by Thijs Wenker on 12/7/16 +// Copyright 2016 High Fidelity, Inc. +// +// Shows 2km long red beams for each avatar outside of the 20 meter radius of your avatar, tries to ignore AC Agents. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html + +var MIN_DISPLAY_DISTANCE = 20.0; // meters +var BEAM_COLOR = {red: 255, green: 0, blue: 0}; +var SHOW_THROUGH_WALLS = false; +var BEACON_LENGTH = 2000.0; // meters +var TRY_TO_IGNORE_AC_AGENTS = true; + +var HALF_BEACON_LENGTH = BEACON_LENGTH / 2.0; + +var beacons = {}; + +// List of .fst files used by AC scripts, that should be ignored in the script in case TRY_TO_IGNORE_AC_AGENTS is enabled +var POSSIBLE_AC_AVATARS = [ + 'http://hifi-content.s3.amazonaws.com/ozan/dev/avatars/invisible_avatar/invisible_avatar.fst', + 'http://hifi-content.s3.amazonaws.com/ozan/dev/avatars/camera_man/pod/_latest/camera_man_pod.fst' +]; + +AvatarFinderBeacon = function(avatar) { + var visible = false; + var avatarSessionUUID = avatar.sessionUUID; + this.overlay = Overlays.addOverlay('line3d', { + color: BEAM_COLOR, + dashed: false, + start: Vec3.sum(avatar.position, {x: 0, y: -HALF_BEACON_LENGTH, z: 0}), + end: Vec3.sum(avatar.position, {x: 0, y: HALF_BEACON_LENGTH, z: 0}), + rotation: {x: 0, y: 0, z: 0, w: 1}, + visible: visible, + drawInFront: SHOW_THROUGH_WALLS, + ignoreRayIntersection: true, + parentID: avatarSessionUUID, + parentJointIndex: -2 + }); + this.cleanup = function() { + Overlays.deleteOverlay(this.overlay); + }; + this.shouldShow = function() { + return Vec3.distance(MyAvatar.position, avatar.position) >= MIN_DISPLAY_DISTANCE; + }; + this.update = function() { + avatar = AvatarList.getAvatar(avatarSessionUUID); + Overlays.editOverlay(this.overlay, { + visible: this.shouldShow() + }); + }; +}; + +function updateBeacon(avatarSessionUUID) { + if (!(avatarSessionUUID in beacons)) { + var avatar = AvatarList.getAvatar(avatarSessionUUID); + if (TRY_TO_IGNORE_AC_AGENTS + && (POSSIBLE_AC_AVATARS.indexOf(avatar.skeletonModelURL) !== -1 || Vec3.length(avatar.position) === 0.0)) { + return; + } + beacons[avatarSessionUUID] = new AvatarFinderBeacon(avatar); + return; + } + beacons[avatarSessionUUID].update(); +} + +Window.domainChanged.connect(function () { + beacons = {}; +}); + +Script.update.connect(function() { + AvatarList.getAvatarIdentifiers().forEach(function(avatarSessionUUID) { + updateBeacon(avatarSessionUUID); + }); +}); + +AvatarList.avatarRemovedEvent.connect(function(avatarSessionUUID) { + if (avatarSessionUUID in beacons) { + beacons[avatarSessionUUID].cleanup(); + delete beacons[avatarSessionUUID]; + } +}); + +Script.scriptEnding.connect(function() { + for (var sessionUUID in beacons) { + if (!beacons.hasOwnProperty(sessionUUID)) { + return; + } + beacons[sessionUUID].cleanup(); + } +}); diff --git a/scripts/simplifiedUI/system/avatarapp.js b/scripts/simplifiedUI/system/avatarapp.js new file mode 100644 index 0000000000..6439d30023 --- /dev/null +++ b/scripts/simplifiedUI/system/avatarapp.js @@ -0,0 +1,663 @@ +"use strict"; +/*jslint vars:true, plusplus:true, forin:true*/ +/*global Tablet, Script, Entities, MyAvatar, Camera, Quat, HMD, Account, UserActivityLogger, Messages, print, + AvatarBookmarks, ContextOverlay, AddressManager +*/ +/* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */ +// +// avatarapp.js +// +// Created by Alexander Ivash on April 30, 2018 +// Copyright 2016 High Fidelity, Inc +// +// Distributed under the Apache License, Version 2.0 +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +(function() { // BEGIN LOCAL_SCOPE + +var AVATARAPP_QML_SOURCE = "hifi/AvatarApp.qml"; +Script.include("/~/system/libraries/controllers.js"); + +// constants from AvatarBookmarks.h +var ENTRY_AVATAR_URL = "avatarUrl"; +var ENTRY_AVATAR_ENTITIES = "avatarEntites"; +var ENTRY_AVATAR_SCALE = "avatarScale"; + +function executeLater(callback) { + Script.setTimeout(callback, 300); +} + +function isWearable(avatarEntity) { + return avatarEntity.properties.visible === true && + (avatarEntity.properties.parentID === MyAvatar.sessionUUID || avatarEntity.properties.parentID === MyAvatar.SELF_ID); +} + +function getMyAvatarWearables() { + var entitiesArray = MyAvatar.getAvatarEntitiesVariant(); + var wearablesArray = []; + + for (var i = 0; i < entitiesArray.length; ++i) { + var entity = entitiesArray[i]; + if (!isWearable(entity)) { + continue; + } + + var localRotation = entity.properties.localRotation; + entity.properties.localRotationAngles = Quat.safeEulerAngles(localRotation); + wearablesArray.push(entity); + } + + return wearablesArray; +} + +function getMyAvatar() { + var avatar = {}; + avatar[ENTRY_AVATAR_URL] = MyAvatar.skeletonModelURL; + avatar[ENTRY_AVATAR_SCALE] = MyAvatar.getAvatarScale(); + avatar[ENTRY_AVATAR_ENTITIES] = getMyAvatarWearables(); + return avatar; +} + +function getMyAvatarSettings() { + return { + dominantHand: MyAvatar.getDominantHand(), + hmdAvatarAlignmentType: MyAvatar.getHmdAvatarAlignmentType(), + collisionsEnabled: MyAvatar.getCollisionsEnabled(), + otherAvatarsCollisionsEnabled: MyAvatar.getOtherAvatarsCollisionsEnabled(), + collisionSoundUrl : MyAvatar.collisionSoundURL, + animGraphUrl: MyAvatar.getAnimGraphUrl(), + animGraphOverrideUrl : MyAvatar.getAnimGraphOverrideUrl(), + }; +} + +function updateAvatarWearables(avatar, callback, wearablesOverride) { + executeLater(function() { + var wearables = wearablesOverride ? wearablesOverride : getMyAvatarWearables(); + avatar[ENTRY_AVATAR_ENTITIES] = wearables; + + sendToQml({'method' : 'wearablesUpdated', 'wearables' : wearables}); + sendToQml({ 'method' : 'wearablesFrozenChanged', 'wearablesFrozen' : getWearablesFrozen()}); + + if(callback) + callback(); + }); +} + +var adjustWearables = { + opened : false, + cameraMode : '', + setOpened : function(value) { + if(this.opened !== value) { + if(value) { + this.cameraMode = Camera.mode; + + if(!HMD.active) { + Camera.mode = 'mirror'; + } + } else { + Camera.mode = this.cameraMode; + } + + this.opened = value; + } + } +}; + +var currentAvatarWearablesBackup = null; +var currentAvatar = null; +var currentAvatarSettings = getMyAvatarSettings(); + +var notifyScaleChanged = true; +function onTargetScaleChanged() { + if(currentAvatar.scale !== MyAvatar.getAvatarScale()) { + currentAvatar.scale = MyAvatar.getAvatarScale(); + if(notifyScaleChanged) { + sendToQml({'method' : 'scaleChanged', 'value' : currentAvatar.scale}); + } + } +} + +function onSkeletonModelURLChanged() { + if(currentAvatar || (currentAvatar.skeletonModelURL !== MyAvatar.skeletonModelURL)) { + fromQml({'method' : 'getAvatars'}); + } +} + +function onDominantHandChanged(dominantHand) { + if(currentAvatarSettings.dominantHand !== dominantHand) { + currentAvatarSettings.dominantHand = dominantHand; + sendToQml({'method' : 'settingChanged', 'name' : 'dominantHand', 'value' : dominantHand}); + } +} + +function onHmdAvatarAlignmentTypeChanged(type) { + if (currentAvatarSettings.hmdAvatarAlignmentType !== type) { + currentAvatarSettings.hmdAvatarAlignmentType = type; + sendToQml({'method' : 'settingChanged', 'name' : 'hmdAvatarAlignmentType', 'value' : type}); + } +} + +function onCollisionsEnabledChanged(enabled) { + if(currentAvatarSettings.collisionsEnabled !== enabled) { + currentAvatarSettings.collisionsEnabled = enabled; + sendToQml({'method' : 'settingChanged', 'name' : 'collisionsEnabled', 'value' : enabled}); + } +} + +function onOtherAvatarsCollisionsEnabledChanged(enabled) { + if (currentAvatarSettings.otherAvatarsCollisionsEnabled !== enabled) { + currentAvatarSettings.otherAvatarsCollisionsEnabled = enabled; + sendToQml({ 'method': 'settingChanged', 'name': 'otherAvatarsCollisionsEnabled', 'value': enabled }); + } +} + +function onNewCollisionSoundUrl(url) { + if(currentAvatarSettings.collisionSoundUrl !== url) { + currentAvatarSettings.collisionSoundUrl = url; + sendToQml({'method' : 'settingChanged', 'name' : 'collisionSoundUrl', 'value' : url}); + } +} + +function onAnimGraphUrlChanged(url) { + if (currentAvatarSettings.animGraphUrl !== url) { + currentAvatarSettings.animGraphUrl = url; + sendToQml({ 'method': 'settingChanged', 'name': 'animGraphUrl', 'value': currentAvatarSettings.animGraphUrl }); + + if (currentAvatarSettings.animGraphOverrideUrl !== MyAvatar.getAnimGraphOverrideUrl()) { + currentAvatarSettings.animGraphOverrideUrl = MyAvatar.getAnimGraphOverrideUrl(); + sendToQml({ 'method': 'settingChanged', 'name': 'animGraphOverrideUrl', + 'value': currentAvatarSettings.animGraphOverrideUrl }); + } + } +} + +var selectedAvatarEntityID = null; +var grabbedAvatarEntityChangeNotifier = null; + +var MARKETPLACE_PURCHASES_QML_PATH = "hifi/commerce/wallet/Wallet.qml"; +var MARKETPLACE_URL = Account.metaverseServerURL + "/marketplace"; +var MARKETPLACES_INJECT_SCRIPT_URL = Script.resolvePath("html/js/marketplacesInject.js"); + +function getWearablesFrozen() { + var wearablesFrozen = true; + var wearablesArray = getMyAvatarWearables(); + wearablesArray.forEach(function(wearable) { + if (isGrabbable(wearable.id)) { + wearablesFrozen = false; + } + }); + + return wearablesFrozen; +} + +function freezeWearables() { + var wearablesArray = getMyAvatarWearables(); + wearablesArray.forEach(function(wearable) { + setGrabbable(wearable.id, false); + }); +} + +function unfreezeWearables() { + var wearablesArray = getMyAvatarWearables(); + wearablesArray.forEach(function(wearable) { + setGrabbable(wearable.id, true); + }); +} + + +function fromQml(message) { // messages are {method, params}, like json-rpc. See also sendToQml. + switch (message.method) { + case 'getAvatars': + currentAvatar = getMyAvatar(); + currentAvatarSettings = getMyAvatarSettings(); + + message.data = { + 'bookmarks' : AvatarBookmarks.getBookmarks(), + 'displayName' : MyAvatar.displayName, + 'currentAvatar' : currentAvatar, + 'currentAvatarSettings' : currentAvatarSettings + }; + + for(var bookmarkName in message.data.bookmarks) { + var bookmark = message.data.bookmarks[bookmarkName]; + + if (bookmark.avatarEntites) { + bookmark.avatarEntites.forEach(function(avatarEntity) { + avatarEntity.properties.localRotationAngles = Quat.safeEulerAngles(avatarEntity.properties.localRotation); + }); + } + } + + sendToQml(message); + break; + case 'selectAvatar': + Entities.addingWearable.disconnect(onAddingWearable); + Entities.deletingWearable.disconnect(onDeletingWearable); + AvatarBookmarks.loadBookmark(message.name); + Entities.addingWearable.connect(onAddingWearable); + Entities.deletingWearable.connect(onDeletingWearable); + sendToQml({ 'method' : 'wearablesFrozenChanged', 'wearablesFrozen' : getWearablesFrozen()}); + break; + case 'deleteAvatar': + AvatarBookmarks.removeBookmark(message.name); + break; + case 'addAvatar': + AvatarBookmarks.addBookmark(message.name); + break; + case 'adjustWearable': + if(message.properties.localRotationAngles) { + message.properties.localRotation = Quat.fromVec3Degrees(message.properties.localRotationAngles); + } + + Entities.editEntity(message.entityID, message.properties); + message.properties = Entities.getEntityProperties(message.entityID, Object.keys(message.properties)); + + if(message.properties.localRotation) { + message.properties.localRotationAngles = Quat.safeEulerAngles(message.properties.localRotation); + } + + sendToQml({'method' : 'wearableUpdated', 'entityID' : message.entityID, wearableIndex : message.wearableIndex, properties : message.properties, updateUI : false}); + break; + case 'adjustWearablesOpened': + currentAvatarWearablesBackup = getMyAvatarWearables(); + adjustWearables.setOpened(true); + unfreezeWearables(); + + Entities.mousePressOnEntity.connect(onSelectedEntity); + Messages.subscribe('Hifi-Object-Manipulation'); + Messages.messageReceived.connect(handleWearableMessages); + break; + case 'adjustWearablesClosed': + if(!message.save) { + // revert changes using snapshot of wearables + if(currentAvatarWearablesBackup !== null) { + AvatarBookmarks.updateAvatarEntities(currentAvatarWearablesBackup); + updateAvatarWearables(currentAvatar, null, currentAvatarWearablesBackup); + } + } else { + sendToQml({'method' : 'updateAvatarInBookmarks'}); + } + + adjustWearables.setOpened(false); + ensureWearableSelected(null); + Entities.mousePressOnEntity.disconnect(onSelectedEntity); + Messages.messageReceived.disconnect(handleWearableMessages); + Messages.unsubscribe('Hifi-Object-Manipulation'); + break; + case 'addWearable': + + var joints = MyAvatar.getJointNames(); + var hipsIndex = -1; + + for(var i = 0; i < joints.length; ++i) { + if(joints[i] === 'Hips') { + hipsIndex = i; + break; + } + } + + var properties = { + name: "Custom wearable", + type: "Model", + modelURL: message.url, + parentID: MyAvatar.sessionUUID, + relayParentJoints: false, + parentJointIndex: hipsIndex + }; + + Entities.addingWearable.disconnect(onAddingWearable); + var entityID = Entities.addEntity(properties, true); + Entities.addingWearable.connect(onAddingWearable); + + updateAvatarWearables(currentAvatar, function() { + onSelectedEntity(entityID); + }); + break; + case 'selectWearable': + ensureWearableSelected(message.entityID); + break; + case 'deleteWearable': + + Entities.deletingWearable.disconnect(onDeletingWearable); + Entities.deleteEntity(message.entityID); + Entities.deletingWearable.connect(onDeletingWearable); + + updateAvatarWearables(currentAvatar); + break; + case 'changeDisplayName': + if (MyAvatar.displayName !== message.displayName) { + MyAvatar.displayName = message.displayName; + UserActivityLogger.palAction("display_name_change", message.displayName); + } + break; + case 'applyExternalAvatar': + var currentAvatarURL = MyAvatar.getFullAvatarURLFromPreferences(); + if(currentAvatarURL !== message.avatarURL) { + MyAvatar.useFullAvatarURL(message.avatarURL); + sendToQml({'method' : 'externalAvatarApplied', 'avatarURL' : message.avatarURL}); + } + break; + case 'navigate': + var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + if(message.url.indexOf('app://') === 0) { + if (message.url === 'app://marketplace') { + tablet.gotoWebScreen(MARKETPLACE_URL, MARKETPLACES_INJECT_SCRIPT_URL); + } else if (message.url === 'app://purchases') { + tablet.pushOntoStack(MARKETPLACE_PURCHASES_QML_PATH); + } + + } else if(message.url.indexOf('hifi://') === 0) { + AddressManager.handleLookupString(message.url, false); + } else if(message.url.indexOf('https://') === 0 || message.url.indexOf('http://') === 0) { + tablet.gotoWebScreen(message.url, MARKETPLACES_INJECT_SCRIPT_URL); + } + + break; + case 'setScale': + notifyScaleChanged = false; + MyAvatar.setAvatarScale(message.avatarScale); + currentAvatar.avatarScale = message.avatarScale; + notifyScaleChanged = true; + break; + case 'revertScale': + MyAvatar.setAvatarScale(message.avatarScale); + currentAvatar.avatarScale = message.avatarScale; + break; + case 'saveSettings': + MyAvatar.setAvatarScale(message.avatarScale); + currentAvatar.avatarScale = message.avatarScale; + + MyAvatar.setDominantHand(message.settings.dominantHand); + MyAvatar.setHmdAvatarAlignmentType(message.settings.hmdAvatarAlignmentType); + MyAvatar.setOtherAvatarsCollisionsEnabled(message.settings.otherAvatarsCollisionsEnabled); + MyAvatar.setCollisionsEnabled(message.settings.collisionsEnabled); + MyAvatar.collisionSoundURL = message.settings.collisionSoundUrl; + MyAvatar.setAnimGraphOverrideUrl(message.settings.animGraphOverrideUrl); + + currentAvatarSettings = getMyAvatarSettings(); + break; + case 'toggleWearablesFrozen': + var wearablesFrozen = getWearablesFrozen(); + wearablesFrozen = !wearablesFrozen; + if (wearablesFrozen) { + freezeWearables(); + } else { + unfreezeWearables(); + } + sendToQml({'method' : 'wearablesFrozenChanged', 'wearablesFrozen' : wearablesFrozen}); + break; + default: + print('Unrecognized message from AvatarApp.qml'); + } +} + +function isGrabbable(entityID) { + if(entityID === null) { + return false; + } + + var properties = Entities.getEntityProperties(entityID, ['avatarEntity', 'grab.grabbable']); + if (properties.avatarEntity) { + return properties.grab.grabbable; + } + + return false; +} + +function setGrabbable(entityID, grabbable) { + var properties = Entities.getEntityProperties(entityID, ['avatarEntity', 'grab.grabbable']); + if (properties.avatarEntity && properties.grab.grabbable != grabbable) { + var editProps = { grab: { grabbable: grabbable }}; + Entities.editEntity(entityID, editProps); + sendToQml({ 'method' : 'wearablesFrozenChanged', 'wearablesFrozen' : getWearablesFrozen()}); + } +} + +function ensureWearableSelected(entityID) { + if(selectedAvatarEntityID !== entityID) { + if(grabbedAvatarEntityChangeNotifier !== null) { + Script.clearInterval(grabbedAvatarEntityChangeNotifier); + grabbedAvatarEntityChangeNotifier = null; + } + selectedAvatarEntityID = entityID; + return true; + } + + return false; +} + +function isEntityBeingWorn(entityID) { + return Entities.getEntityProperties(entityID, 'parentID').parentID === MyAvatar.sessionUUID; +} + +function onSelectedEntity(entityID, pointerEvent) { + if(selectedAvatarEntityID !== entityID && isEntityBeingWorn(entityID)) + { + if(ensureWearableSelected(entityID)) { + sendToQml({'method' : 'selectAvatarEntity', 'entityID' : selectedAvatarEntityID}); + } + } +} + +function onAddingWearable(entityID) { + updateAvatarWearables(currentAvatar, function() { + sendToQml({'method' : 'updateAvatarInBookmarks'}); + }); + sendToQml({ 'method' : 'wearablesFrozenChanged', 'wearablesFrozen' : getWearablesFrozen()}); +} + +function onDeletingWearable(entityID) { + updateAvatarWearables(currentAvatar, function() { + sendToQml({'method' : 'updateAvatarInBookmarks'}); + }); + sendToQml({ 'method' : 'wearablesFrozenChanged', 'wearablesFrozen' : getWearablesFrozen()}); +} + +function handleWearableMessages(channel, message, sender) { + if (channel !== 'Hifi-Object-Manipulation') { + return; + } + + var parsedMessage = null; + + try { + parsedMessage = JSON.parse(message); + } catch (e) { + return; + } + + var entityID = parsedMessage.grabbedEntity; + + var updateWearable = function() { + // for some reasons Entities.getEntityProperties returns more than was asked.. + var propertyNames = ['localPosition', 'localRotation', 'dimensions', 'naturalDimensions']; + var entityProperties = Entities.getEntityProperties(selectedAvatarEntityID, propertyNames); + var properties = {}; + + propertyNames.forEach(function(propertyName) { + properties[propertyName] = entityProperties[propertyName]; + }); + + properties.localRotationAngles = Quat.safeEulerAngles(properties.localRotation); + sendToQml({'method' : 'wearableUpdated', 'entityID' : selectedAvatarEntityID, + 'wearableIndex' : -1, 'properties' : properties, updateUI : true}); + + }; + + if(parsedMessage.action === 'grab') { + if(selectedAvatarEntityID !== entityID) { + ensureWearableSelected(entityID); + sendToQml({'method' : 'selectAvatarEntity', 'entityID' : selectedAvatarEntityID}); + } + + grabbedAvatarEntityChangeNotifier = Script.setInterval(updateWearable, 1000); + } else if(parsedMessage.action === 'release') { + if(grabbedAvatarEntityChangeNotifier !== null) { + Script.clearInterval(grabbedAvatarEntityChangeNotifier); + grabbedAvatarEntityChangeNotifier = null; + updateWearable(); + } + } +} + +function sendToQml(message) { + tablet.sendToQml(message); +} + +function onBookmarkLoaded(bookmarkName) { + executeLater(function() { + currentAvatar = getMyAvatar(); + sendToQml({'method' : 'bookmarkLoaded', 'data' : {'name' : bookmarkName, 'currentAvatar' : currentAvatar} }); + }); +} + +function onBookmarkDeleted(bookmarkName) { + sendToQml({'method' : 'bookmarkDeleted', 'name' : bookmarkName}); +} + +function onBookmarkAdded(bookmarkName) { + var bookmark = AvatarBookmarks.getBookmark(bookmarkName); + bookmark.avatarEntites.forEach(function(avatarEntity) { + avatarEntity.properties.localRotationAngles = Quat.safeEulerAngles(avatarEntity.properties.localRotation); + }); + + sendToQml({ 'method': 'bookmarkAdded', 'bookmarkName': bookmarkName, 'bookmark': bookmark }); +} + +// +// Manage the connection between the button and the window. +// +var button; +var buttonName = "AVATAR"; +var tablet = null; + +function startup() { + tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + button = tablet.addButton({ + text: buttonName, + icon: "icons/tablet-icons/avatar-i.svg", + activeIcon: "icons/tablet-icons/avatar-a.svg", + sortOrder: 7 + }); + button.clicked.connect(onTabletButtonClicked); + tablet.screenChanged.connect(onTabletScreenChanged); +} + +startup(); + +var isWired = false; +function off() { + if(adjustWearables.opened) { + adjustWearables.setOpened(false); + ensureWearableSelected(null); + Entities.mousePressOnEntity.disconnect(onSelectedEntity); + + Messages.messageReceived.disconnect(handleWearableMessages); + Messages.unsubscribe('Hifi-Object-Manipulation'); + } + + if (isWired) { // It is not ok to disconnect these twice, hence guard. + isWired = false; + + AvatarBookmarks.bookmarkLoaded.disconnect(onBookmarkLoaded); + AvatarBookmarks.bookmarkDeleted.disconnect(onBookmarkDeleted); + AvatarBookmarks.bookmarkAdded.disconnect(onBookmarkAdded); + + Entities.addingWearable.disconnect(onAddingWearable); + Entities.deletingWearable.disconnect(onDeletingWearable); + MyAvatar.skeletonModelURLChanged.disconnect(onSkeletonModelURLChanged); + MyAvatar.dominantHandChanged.disconnect(onDominantHandChanged); + MyAvatar.hmdAvatarAlignmentTypeChanged.disconnect(onHmdAvatarAlignmentTypeChanged); + MyAvatar.collisionsEnabledChanged.disconnect(onCollisionsEnabledChanged); + MyAvatar.otherAvatarsCollisionsEnabledChanged.disconnect(onOtherAvatarsCollisionsEnabledChanged); + MyAvatar.newCollisionSoundURL.disconnect(onNewCollisionSoundUrl); + MyAvatar.animGraphUrlChanged.disconnect(onAnimGraphUrlChanged); + MyAvatar.targetScaleChanged.disconnect(onTargetScaleChanged); + } +} + +function on() { + + if (!isWired) { // It is not ok to connect these twice, hence guard. + isWired = true; + + AvatarBookmarks.bookmarkLoaded.connect(onBookmarkLoaded); + AvatarBookmarks.bookmarkDeleted.connect(onBookmarkDeleted); + AvatarBookmarks.bookmarkAdded.connect(onBookmarkAdded); + + Entities.addingWearable.connect(onAddingWearable); + Entities.deletingWearable.connect(onDeletingWearable); + MyAvatar.skeletonModelURLChanged.connect(onSkeletonModelURLChanged); + MyAvatar.dominantHandChanged.connect(onDominantHandChanged); + MyAvatar.hmdAvatarAlignmentTypeChanged.connect(onHmdAvatarAlignmentTypeChanged); + MyAvatar.collisionsEnabledChanged.connect(onCollisionsEnabledChanged); + MyAvatar.otherAvatarsCollisionsEnabledChanged.connect(onOtherAvatarsCollisionsEnabledChanged); + MyAvatar.newCollisionSoundURL.connect(onNewCollisionSoundUrl); + MyAvatar.animGraphUrlChanged.connect(onAnimGraphUrlChanged); + MyAvatar.targetScaleChanged.connect(onTargetScaleChanged); + } +} + +function onTabletButtonClicked() { + if (onAvatarAppScreen) { + // for toolbar-mode: go back to home screen, this will close the window. + tablet.gotoHomeScreen(); + } else { + ContextOverlay.enabled = false; + tablet.loadQMLSource(AVATARAPP_QML_SOURCE); + } +} +var hasEventBridge = false; +function wireEventBridge(on) { + if (on) { + if (!hasEventBridge) { + tablet.fromQml.connect(fromQml); + hasEventBridge = true; + } + } else { + if (hasEventBridge) { + tablet.fromQml.disconnect(fromQml); + hasEventBridge = false; + } + } +} + +var onAvatarAppScreen = false; +function onTabletScreenChanged(type, url) { + var onAvatarAppScreenNow = (type === "QML" && url === AVATARAPP_QML_SOURCE); + wireEventBridge(onAvatarAppScreenNow); + // for toolbar mode: change button to active when window is first openend, false otherwise. + button.editProperties({isActive: onAvatarAppScreenNow}); + + if (!onAvatarAppScreen && onAvatarAppScreenNow) { + on(); + } else if(onAvatarAppScreen && !onAvatarAppScreenNow) { + off(); + } + + onAvatarAppScreen = onAvatarAppScreenNow; + + if(onAvatarAppScreenNow) { + sendToQml({ 'method' : 'initialize', 'data' : { jointNames : MyAvatar.getJointNames() }}); + sendToQml({ 'method' : 'wearablesFrozenChanged', 'wearablesFrozen' : getWearablesFrozen()}); + } +} + +function shutdown() { + if (onAvatarAppScreen) { + tablet.gotoHomeScreen(); + } + button.clicked.disconnect(onTabletButtonClicked); + tablet.removeButton(button); + tablet.screenChanged.disconnect(onTabletScreenChanged); + + off(); +} + +// +// Cleanup. +// +Script.scriptEnding.connect(shutdown); + +}()); // END LOCAL_SCOPE diff --git a/scripts/simplifiedUI/system/bubble.js b/scripts/simplifiedUI/system/bubble.js new file mode 100644 index 0000000000..eca3b3dcd4 --- /dev/null +++ b/scripts/simplifiedUI/system/bubble.js @@ -0,0 +1,206 @@ +"use strict"; + +// +// bubble.js +// scripts/system/ +// +// Created by Brad Hefta-Gaub on 11/18/2016 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +/* global Script, Users, Overlays, AvatarList, Controller, Camera, getControllerWorldLocation, UserActivityLogger */ + +(function () { // BEGIN LOCAL_SCOPE + var button; + // Used for animating and disappearing the bubble + var bubbleOverlayTimestamp; + // Used for rate limiting the bubble sound + var lastBubbleSoundTimestamp = 0; + // Affects bubble height + var BUBBLE_HEIGHT_SCALE = 0.15; + // The bubble model itself + var bubbleOverlay = Overlays.addOverlay("model", { + url: Script.resolvePath("assets/models/Bubble-v14.fbx"), // If you'd like to change the model, modify this line (and the dimensions below) + dimensions: { x: MyAvatar.sensorToWorldScale, y: 0.75 * MyAvatar.sensorToWorldScale, z: MyAvatar.sensorToWorldScale }, + position: { x: MyAvatar.position.x, y: -MyAvatar.scale * 2 + MyAvatar.position.y + MyAvatar.scale * BUBBLE_HEIGHT_SCALE, z: MyAvatar.position.z }, + rotation: Quat.multiply(MyAvatar.orientation, Quat.fromVec3Degrees({x: 0.0, y: 180.0, z: 0.0})), + scale: { x: 2 , y: MyAvatar.scale * 0.5 + 0.5, z: 2 }, + visible: false, + ignoreRayIntersection: true + }); + // The bubble activation sound + var bubbleActivateSound = SoundCache.getSound(Script.resolvePath("assets/sounds/bubble.wav")); + // Is the update() function connected? + var updateConnected = false; + + var BUBBLE_VISIBLE_DURATION_MS = 3000; + var BUBBLE_RAISE_ANIMATION_DURATION_MS = 750; + var BUBBLE_SOUND_RATE_LIMIT_MS = 15000; + + // Hides the bubble model overlay + function hideOverlays() { + Overlays.editOverlay(bubbleOverlay, { + visible: false + }); + } + + // Make the bubble overlay visible, set its position, and play the sound + function createOverlays() { + var nowTimestamp = Date.now(); + if (nowTimestamp - lastBubbleSoundTimestamp >= BUBBLE_SOUND_RATE_LIMIT_MS) { + Audio.playSound(bubbleActivateSound, { + position: { x: MyAvatar.position.x, y: MyAvatar.position.y, z: MyAvatar.position.z }, + localOnly: true, + volume: 0.2 + }); + lastBubbleSoundTimestamp = nowTimestamp; + } + hideOverlays(); + if (updateConnected === true) { + updateConnected = false; + Script.update.disconnect(update); + } + + Overlays.editOverlay(bubbleOverlay, { + dimensions: { + x: MyAvatar.sensorToWorldScale, + y: 0.75 * MyAvatar.sensorToWorldScale, + z: MyAvatar.sensorToWorldScale + }, + position: { + x: MyAvatar.position.x, + y: -MyAvatar.scale * 2 + MyAvatar.position.y + MyAvatar.scale * BUBBLE_HEIGHT_SCALE, + z: MyAvatar.position.z + }, + rotation: Quat.multiply(MyAvatar.orientation, Quat.fromVec3Degrees({x: 0.0, y: 180.0, z: 0.0})), + scale: { + x: 2 , + y: MyAvatar.scale * 0.5 + 0.5 , + z: 2 + }, + visible: true + }); + bubbleOverlayTimestamp = nowTimestamp; + Script.update.connect(update); + updateConnected = true; + } + + // Called from the C++ scripting interface to show the bubble overlay + function enteredIgnoreRadius() { + createOverlays(); + UserActivityLogger.privacyShieldActivated(); + } + + // Used to set the state of the bubble HUD button + function writeButtonProperties(parameter) { + button.editProperties({isActive: parameter}); + } + + // The bubble script's update function + function update() { + var timestamp = Date.now(); + var delay = (timestamp - bubbleOverlayTimestamp); + var overlayAlpha = 1.0 - (delay / BUBBLE_VISIBLE_DURATION_MS); + if (overlayAlpha > 0) { + if (delay < BUBBLE_RAISE_ANIMATION_DURATION_MS) { + Overlays.editOverlay(bubbleOverlay, { + dimensions: { + x: MyAvatar.sensorToWorldScale, + y: 0.75 * MyAvatar.sensorToWorldScale, + z: MyAvatar.sensorToWorldScale + }, + // Quickly raise the bubble from the ground up + position: { + x: MyAvatar.position.x, + y: (-((BUBBLE_RAISE_ANIMATION_DURATION_MS - delay) / BUBBLE_RAISE_ANIMATION_DURATION_MS)) * MyAvatar.scale * 2 + MyAvatar.position.y + MyAvatar.scale * BUBBLE_HEIGHT_SCALE, + z: MyAvatar.position.z + }, + rotation: Quat.multiply(MyAvatar.orientation, Quat.fromVec3Degrees({x: 0.0, y: 180.0, z: 0.0})), + scale: { + x: 2 , + y: ((1 - ((BUBBLE_RAISE_ANIMATION_DURATION_MS - delay) / BUBBLE_RAISE_ANIMATION_DURATION_MS)) * MyAvatar.scale * 0.5 + 0.5), + z: 2 + } + }); + } else { + // Keep the bubble in place for a couple seconds + Overlays.editOverlay(bubbleOverlay, { + dimensions: { + x: MyAvatar.sensorToWorldScale, + y: 0.75 * MyAvatar.sensorToWorldScale, + z: MyAvatar.sensorToWorldScale + }, + position: { + x: MyAvatar.position.x, + y: MyAvatar.position.y + MyAvatar.scale * BUBBLE_HEIGHT_SCALE, + z: MyAvatar.position.z + }, + rotation: Quat.multiply(MyAvatar.orientation, Quat.fromVec3Degrees({x: 0.0, y: 180.0, z: 0.0})), + scale: { + x: 2, + y: MyAvatar.scale * 0.5 + 0.5 , + z: 2 + } + }); + } + } else { + hideOverlays(); + if (updateConnected === true) { + Script.update.disconnect(update); + updateConnected = false; + } + } + } + + // When the space bubble is toggled... + // NOTE: the c++ calls this with just the first param -- we added a second + // just for not logging the initial state of the bubble when we startup. + function onBubbleToggled(enabled, doNotLog) { + writeButtonProperties(enabled); + if (doNotLog !== true) { + UserActivityLogger.privacyShieldToggled(enabled); + } + if (enabled) { + createOverlays(); + } else { + hideOverlays(); + if (updateConnected === true) { + Script.update.disconnect(update); + updateConnected = false; + } + } + } + + // Setup the bubble button + var buttonName = "SHIELD"; + var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + button = tablet.addButton({ + icon: "icons/tablet-icons/bubble-i.svg", + activeIcon: "icons/tablet-icons/bubble-a.svg", + text: buttonName, + sortOrder: 4 + }); + + onBubbleToggled(Users.getIgnoreRadiusEnabled(), true); // pass in true so we don't log this initial one in the UserActivity table + + button.clicked.connect(Users.toggleIgnoreRadius); + Users.ignoreRadiusEnabledChanged.connect(onBubbleToggled); + Users.enteredIgnoreRadius.connect(enteredIgnoreRadius); + + // Cleanup the tablet button and overlays when script is stopped + Script.scriptEnding.connect(function () { + button.clicked.disconnect(Users.toggleIgnoreRadius); + if (tablet) { + tablet.removeButton(button); + } + Users.ignoreRadiusEnabledChanged.disconnect(onBubbleToggled); + Users.enteredIgnoreRadius.disconnect(enteredIgnoreRadius); + Overlays.deleteOverlay(bubbleOverlay); + if (updateConnected === true) { + Script.update.disconnect(update); + } + }); + +}()); // END LOCAL_SCOPE diff --git a/scripts/simplifiedUI/system/chat.js b/scripts/simplifiedUI/system/chat.js new file mode 100644 index 0000000000..749665f3d8 --- /dev/null +++ b/scripts/simplifiedUI/system/chat.js @@ -0,0 +1,1010 @@ +"use strict"; + +// Chat.js +// By Don Hopkins (dhopkins@donhopkins.com) +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +(function() { + + var webPageURL = Script.resolvePath("html/ChatPage.html"); // URL of tablet web page. + var randomizeWebPageURL = true; // Set to true for debugging. + var lastWebPageURL = ""; // Last random URL of tablet web page. + var onChatPage = false; // True when chat web page is opened. + var webHandlerConnected = false; // True when the web handler has been connected. + var channelName = "Chat"; // Unique name for channel that we listen to. + var tabletButtonName = "CHAT"; // Tablet button label. + var tabletButtonIcon = "icons/tablet-icons/menu-i.svg"; // Icon for chat button. + var tabletButtonActiveIcon = "icons/tablet-icons/menu-a.svg"; // Active icon for chat button. + var tabletButton = null; // The button we create in the tablet. + var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); // The awesome tablet. + var chatLog = []; // Array of chat messages in the form of [avatarID, displayName, message, data]. + var avatarIdentifiers = {}; // Map of avatar ids to dict of identifierParams. + var speechBubbleShowing = false; // Is the speech bubble visible? + var speechBubbleMessage = null; // The message shown in the speech bubble. + var speechBubbleData = null; // The data of the speech bubble message. + var speechBubbleTextID = null; // The id of the speech bubble local text entity. + var speechBubbleTimer = null; // The timer to pop down the speech bubble. + var speechBubbleParams = null; // The params used to create or edit the speech bubble. + + // Persistent variables saved in the Settings. + var chatName = ''; // The user's name shown in chat. + var chatLogMaxSize = 100; // The maximum number of chat messages we remember. + var sendTyping = true; // Send typing begin and end notification. + var identifyAvatarDuration = 10; // How long to leave the avatar identity line up, in seconds. + var identifyAvatarLineColor = { red: 0, green: 255, blue: 0 }; // The color of the avatar identity line. + var identifyAvatarMyJointName = 'Head'; // My bone from which to draw the avatar identity line. + var identifyAvatarYourJointName = 'Head'; // Your bone to which to draw the avatar identity line. + var speechBubbleDuration = 10; // How long to leave the speech bubble up, in seconds. + var speechBubbleTextColor = {red: 255, green: 255, blue: 255}; // The text color of the speech bubble. + var speechBubbleBackgroundColor = {red: 0, green: 0, blue: 0}; // The background color of the speech bubble. + var speechBubbleOffset = {x: 0, y: 0.3, z: 0.0}; // The offset from the joint to whic the speech bubble is attached. + var speechBubbleJointName = 'Head'; // The name of the joint to which the speech bubble is attached. + var speechBubbleLineHeight = 0.05; // The height of a line of text in the speech bubble. + var SPEECH_BUBBLE_MAX_WIDTH = 1; // meters + + var textSizeOverlay = Overlays.addOverlay("text3d", { + position: MyAvatar.position, + lineHeight: speechBubbleLineHeight, + leftMargin: 0, + topMargin: 0, + rightMargin: 0, + bottomMargin: 0, + ignoreRayIntersection: true, + visible: false + }); + + // Load the persistent variables from the Settings, with defaults. + function loadSettings() { + chatName = Settings.getValue('Chat_chatName', MyAvatar.displayName); + if (!chatName) { + chatName = randomAvatarName(); + } + chatLogMaxSize = Settings.getValue('Chat_chatLogMaxSize', 100); + sendTyping = Settings.getValue('Chat_sendTyping', true); + identifyAvatarDuration = Settings.getValue('Chat_identifyAvatarDuration', 10); + identifyAvatarLineColor = Settings.getValue('Chat_identifyAvatarLineColor', { red: 0, green: 255, blue: 0 }); + identifyAvatarMyJointName = Settings.getValue('Chat_identifyAvatarMyJointName', 'Head'); + identifyAvatarYourJointName = Settings.getValue('Chat_identifyAvatarYourJointName', 'Head'); + speechBubbleDuration = Settings.getValue('Chat_speechBubbleDuration', 10); + speechBubbleTextColor = Settings.getValue('Chat_speechBubbleTextColor', {red: 255, green: 255, blue: 255}); + speechBubbleBackgroundColor = Settings.getValue('Chat_speechBubbleBackgroundColor', {red: 0, green: 0, blue: 0}); + speechBubbleOffset = Settings.getValue('Chat_speechBubbleOffset', {x: 0.0, y: 0.3, z:0.0}); + speechBubbleJointName = Settings.getValue('Chat_speechBubbleJointName', 'Head'); + speechBubbleLineHeight = Settings.getValue('Chat_speechBubbleLineHeight', 0.05); + Overlays.editOverlay(textSizeOverlay, { + lineHeight: speechBubbleLineHeight + }); + + saveSettings(); + } + + // Save the persistent variables to the Settings. + function saveSettings() { + Settings.setValue('Chat_chatName', chatName); + Settings.setValue('Chat_chatLogMaxSize', chatLogMaxSize); + Settings.setValue('Chat_sendTyping', sendTyping); + Settings.setValue('Chat_identifyAvatarDuration', identifyAvatarDuration); + Settings.setValue('Chat_identifyAvatarLineColor', identifyAvatarLineColor); + Settings.setValue('Chat_identifyAvatarMyJointName', identifyAvatarMyJointName); + Settings.setValue('Chat_identifyAvatarYourJointName', identifyAvatarYourJointName); + Settings.setValue('Chat_speechBubbleDuration', speechBubbleDuration); + Settings.setValue('Chat_speechBubbleTextColor', speechBubbleTextColor); + Settings.setValue('Chat_speechBubbleBackgroundColor', speechBubbleBackgroundColor); + Settings.setValue('Chat_speechBubbleOffset', speechBubbleOffset); + Settings.setValue('Chat_speechBubbleJointName', speechBubbleJointName); + Settings.setValue('Chat_speechBubbleLineHeight', speechBubbleLineHeight); + } + + // Reset the Settings and persistent variables to the defaults. + function resetSettings() { + Settings.setValue('Chat_chatName', null); + Settings.setValue('Chat_chatLogMaxSize', null); + Settings.setValue('Chat_sendTyping', null); + Settings.setValue('Chat_identifyAvatarDuration', null); + Settings.setValue('Chat_identifyAvatarLineColor', null); + Settings.setValue('Chat_identifyAvatarMyJointName', null); + Settings.setValue('Chat_identifyAvatarYourJointName', null); + Settings.setValue('Chat_speechBubbleDuration', null); + Settings.setValue('Chat_speechBubbleTextColor', null); + Settings.setValue('Chat_speechBubbleBackgroundColor', null); + Settings.setValue('Chat_speechBubbleOffset', null); + Settings.setValue('Chat_speechBubbleJointName', null); + Settings.setValue('Chat_speechBubbleLineHeight', null); + + loadSettings(); + } + + // Update anything that might depend on the settings. + function updateSettings() { + updateSpeechBubble(); + trimChatLog(); + updateChatPage(); + } + + // Trim the chat log so it is no longer than chatLogMaxSize lines. + function trimChatLog() { + if (chatLog.length > chatLogMaxSize) { + chatLog.splice(0, chatLogMaxSize - chatLog.length); + } + } + + // Clear the local chat log. + function clearChatLog() { + //print("clearChatLog"); + chatLog = []; + updateChatPage(); + } + + // We got a chat message from the channel. + // Trim the chat log, save the latest message in the chat log, + // and show the message on the tablet, if the chat page is showing. + function handleTransmitChatMessage(avatarID, displayName, message, data) { + //print("receiveChat", "avatarID", avatarID, "displayName", displayName, "message", message, "data", data); + + trimChatLog(); + chatLog.push([avatarID, displayName, message, data]); + + if (onChatPage) { + tablet.emitScriptEvent( + JSON.stringify({ + type: "ReceiveChatMessage", + avatarID: avatarID, + displayName: displayName, + message: message, + data: data + })); + } + } + + // Trim the chat log, save the latest log message in the chat log, + // and show the message on the tablet, if the chat page is showing. + function logMessage(message, data) { + //print("logMessage", message, data); + + trimChatLog(); + chatLog.push([null, null, message, data]); + + if (onChatPage) { + tablet.emitScriptEvent( + JSON.stringify({ + type: "LogMessage", + message: message, + data: data + })); + } + } + + // An empty chat message was entered. + // Hide our speech bubble. + function emptyChatMessage(data) { + popDownSpeechBubble(); + } + + // Notification that we typed a keystroke. + function type() { + //print("type"); + } + + // Notification that we began typing. + // Notify everyone that we started typing. + function beginTyping() { + //print("beginTyping"); + if (!sendTyping) { + return; + } + + Messages.sendMessage( + channelName, + JSON.stringify({ + type: 'AvatarBeginTyping', + avatarID: MyAvatar.sessionUUID, + displayName: chatName + })); + } + + // Notification that somebody started typing. + function handleAvatarBeginTyping(avatarID, displayName) { + //print("handleAvatarBeginTyping:", "avatarID", avatarID, displayName); + } + + // Notification that we stopped typing. + // Notify everyone that we stopped typing. + function endTyping() { + //print("endTyping"); + if (!sendTyping) { + return; + } + + Messages.sendMessage( + channelName, + JSON.stringify({ + type: 'AvatarEndTyping', + avatarID: MyAvatar.sessionUUID, + displayName: chatName + })); + } + + // Notification that somebody stopped typing. + function handleAvatarEndTyping(avatarID, displayName) { + //print("handleAvatarEndTyping:", "avatarID", avatarID, displayName); + } + + // Identify an avatar by drawing a line from our head to their head. + // If the avatar is our own, then just draw a line up into the sky. + function identifyAvatar(yourAvatarID) { + //print("identifyAvatar", yourAvatarID); + + unidentifyAvatars(); + + var myAvatarID = MyAvatar.sessionUUID; + var myJointIndex = MyAvatar.getJointIndex(identifyAvatarMyJointName); + var myJointRotation = + Quat.multiply( + MyAvatar.orientation, + MyAvatar.getAbsoluteJointRotationInObjectFrame(myJointIndex)); + var myJointPosition = + Vec3.sum( + MyAvatar.position, + Vec3.multiplyQbyV( + MyAvatar.orientation, + MyAvatar.getAbsoluteJointTranslationInObjectFrame(myJointIndex))); + + var yourJointIndex = -1; + var yourJointPosition; + + if (yourAvatarID == myAvatarID) { + + // You pointed at your own name, so draw a line up from your head. + + yourJointPosition = { + x: myJointPosition.x, + y: myJointPosition.y + 1000.0, + z: myJointPosition.z + }; + + } else { + + // You pointed at somebody else's name, so draw a line from your head to their head. + + var yourAvatar = AvatarList.getAvatar(yourAvatarID); + if (!yourAvatar) { + return; + } + + yourJointIndex = yourAvatar.getJointIndex(identifyAvatarMyJointName) + + var yourJointRotation = + Quat.multiply( + yourAvatar.orientation, + yourAvatar.getAbsoluteJointRotationInObjectFrame(yourJointIndex)); + yourJointPosition = + Vec3.sum( + yourAvatar.position, + Vec3.multiplyQbyV( + yourAvatar.orientation, + yourAvatar.getAbsoluteJointTranslationInObjectFrame(yourJointIndex))); + + } + + var identifierParams = { + parentID: myAvatarID, + parentJointIndex: myJointIndex, + lifetime: identifyAvatarDuration, + start: myJointPosition, + endParentID: yourAvatarID, + endParentJointIndex: yourJointIndex, + end: yourJointPosition, + color: identifyAvatarLineColor, + alpha: 1 + }; + + avatarIdentifiers[yourAvatarID] = identifierParams; + + identifierParams.lineID = Overlays.addOverlay("line3d", identifierParams); + + //print("ADDOVERLAY lineID", lineID, "myJointPosition", JSON.stringify(myJointPosition), "yourJointPosition", JSON.stringify(yourJointPosition), "lineData", JSON.stringify(lineData)); + + identifierParams.timer = + Script.setTimeout(function() { + //print("DELETEOVERLAY lineID"); + unidentifyAvatar(yourAvatarID); + }, identifyAvatarDuration * 1000); + + } + + // Stop identifying an avatar. + function unidentifyAvatar(yourAvatarID) { + //print("unidentifyAvatar", yourAvatarID); + + var identifierParams = avatarIdentifiers[yourAvatarID]; + if (!identifierParams) { + return; + } + + if (identifierParams.timer) { + Script.clearTimeout(identifierParams.timer); + } + + if (identifierParams.lineID) { + Overlays.deleteOverlay(identifierParams.lineID); + } + + delete avatarIdentifiers[yourAvatarID]; + } + + // Stop identifying all avatars. + function unidentifyAvatars() { + var ids = []; + + for (var avatarID in avatarIdentifiers) { + ids.push(avatarID); + } + + for (var i = 0, n = ids.length; i < n; i++) { + var avatarID = ids[i]; + unidentifyAvatar(avatarID); + } + + } + + // Turn to face another avatar. + function faceAvatar(yourAvatarID, displayName) { + //print("faceAvatar:", yourAvatarID, displayName); + + var myAvatarID = MyAvatar.sessionUUID; + if (yourAvatarID == myAvatarID) { + // You clicked on yourself. + return; + } + + var yourAvatar = AvatarList.getAvatar(yourAvatarID); + if (!yourAvatar) { + logMessage(displayName + ' is not here!', null); + return; + } + + // Project avatar positions to the floor and get the direction between those points, + // then face my avatar towards your avatar. + var yourPosition = yourAvatar.position; + yourPosition.y = 0; + var myPosition = MyAvatar.position; + myPosition.y = 0; + var myOrientation = Quat.lookAtSimple(myPosition, yourPosition); + MyAvatar.orientation = myOrientation; + } + + // Make a hopefully unique random anonymous avatar name. + function randomAvatarName() { + return 'Anon_' + Math.floor(Math.random() * 1000000); + } + + // Change the avatar size to bigger. + function biggerSize() { + //print("biggerSize"); + logMessage("Increasing avatar size", null); + MyAvatar.increaseSize(); + } + + // Change the avatar size to smaller. + function smallerSize() { + //print("smallerSize"); + logMessage("Decreasing avatar size", null); + MyAvatar.decreaseSize(); + } + + // Set the avatar size to normal. + function normalSize() { + //print("normalSize"); + logMessage("Resetting avatar size to normal!", null); + MyAvatar.resetSize(); + } + + // Send out a "Who" message, including our avatarID as myAvatarID, + // which will be sent in the response, so we can tell the reply + // is to our request. + function transmitWho() { + //print("transmitWho"); + logMessage("Who is here?", null); + Messages.sendMessage( + channelName, + JSON.stringify({ + type: 'Who', + myAvatarID: MyAvatar.sessionUUID + })); + } + + // Send a reply to a "Who" message, with a friendly message, + // our avatarID and our displayName. myAvatarID is the id + // of the avatar who send the Who message, to whom we're + // responding. + function handleWho(myAvatarID) { + var avatarID = MyAvatar.sessionUUID; + if (myAvatarID == avatarID) { + // Don't reply to myself. + return; + } + + var message = "I'm here!"; + var data = {}; + + Messages.sendMessage( + channelName, + JSON.stringify({ + type: 'ReplyWho', + myAvatarID: myAvatarID, + avatarID: avatarID, + displayName: chatName, + message: message, + data: data + })); + } + + // Receive the reply to a "Who" message. Ignore it unless we were the one + // who sent it out (if myAvatarIS is our avatar's id). + function handleReplyWho(myAvatarID, avatarID, displayName, message, data) { + if (myAvatarID != MyAvatar.sessionUUID) { + return; + } + + handleTransmitChatMessage(avatarID, displayName, message, data); + } + + // Handle input form the user, possibly multiple lines separated by newlines. + // Each line may be a chat command starting with "/", or a chat message. + function handleChatMessage(message, data) { + + var messageLines = message.trim().split('\n'); + + for (var i = 0, n = messageLines.length; i < n; i++) { + var messageLine = messageLines[i]; + + if (messageLine.substr(0, 1) == '/') { + handleChatCommand(messageLine, data); + } else { + transmitChatMessage(messageLine, data); + } + } + + } + + // Handle a chat command prefixed by "/". + function handleChatCommand(message, data) { + + var commandLine = message.substr(1); + var tokens = commandLine.trim().split(' '); + var command = tokens[0]; + var rest = commandLine.substr(command.length + 1).trim(); + + //print("commandLine", commandLine, "command", command, "tokens", tokens, "rest", rest); + + switch (command) { + + case '?': + case 'help': + logMessage('Type "/?" or "/help" for help', null); + logMessage('Type "/name " to set your chat name, or "/name" to use your display name. If your display name is not defined, a random name will be used.', null); + logMessage('Type "/close" to close your overhead chat message.', null); + logMessage('Type "/say " to display a new message.', null); + logMessage('Type "/clear" to clear your chat log.', null); + logMessage('Type "/who" to ask who is in the chat session.', null); + logMessage('Type "/bigger", "/smaller" or "/normal" to change your avatar size.', null); + break; + + case 'name': + if (rest == '') { + if (MyAvatar.displayName) { + chatName = MyAvatar.displayName; + saveSettings(); + logMessage('Your chat name has been set to your display name "' + chatName + '".', null); + } else { + chatName = randomAvatarName(); + saveSettings(); + logMessage('Your avatar\'s display name is not defined, so your chat name has been set to "' + chatName + '".', null); + } + } else { + chatName = rest; + saveSettings(); + logMessage('Your chat name has been set to "' + chatName + '".', null); + } + break; + + case 'close': + popDownSpeechBubble(); + logMessage('Overhead chat message closed.', null); + break; + + case 'say': + if (rest == '') { + emptyChatMessage(data); + } else { + transmitChatMessage(rest, data); + } + break; + + case 'who': + transmitWho(); + break; + + case 'clear': + clearChatLog(); + break; + + case 'bigger': + biggerSize(); + break; + + case 'smaller': + smallerSize(); + break; + + case 'normal': + normalSize(); + break; + + case 'resetsettings': + resetSettings(); + updateSettings(); + break; + + case 'speechbubbleheight': + var y = parseInt(rest); + if (!isNaN(y)) { + speechBubbleOffset.y = y; + } + saveSettings(); + updateSettings(); + break; + + case 'speechbubbleduration': + var duration = parseFloat(rest); + if (!isNaN(duration)) { + speechBubbleDuration = duration; + } + saveSettings(); + updateSettings(); + break; + + default: + logMessage('Unknown chat command. Type "/help" or "/?" for help.', null); + break; + + } + + } + + // Send out a chat message to everyone. + function transmitChatMessage(message, data) { + //print("transmitChatMessage", 'avatarID', avatarID, 'displayName', displayName, 'message', message, 'data', data); + + popUpSpeechBubble(message, data); + + Messages.sendMessage( + channelName, + JSON.stringify({ + type: 'TransmitChatMessage', + avatarID: MyAvatar.sessionUUID, + displayName: chatName, + message: message, + data: data + })); + + } + + // Show the speech bubble. + function popUpSpeechBubble(message, data) { + //print("popUpSpeechBubble", message, data); + + popDownSpeechBubble(); + + speechBubbleShowing = true; + speechBubbleMessage = message; + speechBubbleData = data; + + updateSpeechBubble(); + + if (speechBubbleDuration > 0) { + speechBubbleTimer = Script.setTimeout( + function () { + popDownSpeechBubble(); + }, + speechBubbleDuration * 1000); + } + } + + // Update the speech bubble. + // This is factored out so we can update an existing speech bubble if any settings change. + function updateSpeechBubble() { + if (!speechBubbleShowing) { + return; + } + + var jointIndex = MyAvatar.getJointIndex(speechBubbleJointName); + var dimensions = { + x: 100.0, + y: 100.0, + z: 0.1 + }; + + speechBubbleParams = { + type: "Text", + lifetime: speechBubbleDuration, + parentID: MyAvatar.sessionUUID, + jointIndex: jointIndex, + dimensions: dimensions, + lineHeight: speechBubbleLineHeight, + leftMargin: 0, + topMargin: 0, + rightMargin: 0, + bottomMargin: 0, + faceCamera: true, + drawInFront: true, + ignoreRayIntersection: true, + text: speechBubbleMessage, + textColor: speechBubbleTextColor, + color: speechBubbleTextColor, + backgroundColor: speechBubbleBackgroundColor + }; + + // Only overlay text3d has a way to measure the text, not entities. + // So we make a temporary one just for measuring text, then delete it. + var speechBubbleTextOverlayID = Overlays.addOverlay("text3d", speechBubbleParams); + var textSize = Overlays.textSize(textSizeOverlay, speechBubbleMessage); + try { + Overlays.deleteOverlay(speechBubbleTextOverlayID); + } catch (e) {} + + //print("updateSpeechBubble:", "speechBubbleMessage", speechBubbleMessage, "textSize", textSize.width, textSize.height); + + var fudge = 0.02; + + var width = textSize.width + fudge; + var height = speechBubbleLineHeight + fudge; + + if (textSize.width >= SPEECH_BUBBLE_MAX_WIDTH) { + var numLines = Math.ceil(width); + height = speechBubbleLineHeight * numLines + fudge; + width = SPEECH_BUBBLE_MAX_WIDTH; + } + + dimensions = { + x: width, + y: height, + z: 0.1 + }; + speechBubbleParams.dimensions = dimensions; + + var headRotation = + Quat.multiply( + MyAvatar.orientation, + MyAvatar.getAbsoluteJointRotationInObjectFrame(jointIndex)); + var headPosition = + Vec3.sum( + MyAvatar.position, + Vec3.multiplyQbyV( + MyAvatar.orientation, + MyAvatar.getAbsoluteJointTranslationInObjectFrame(jointIndex))); + var rotatedOffset = + Vec3.multiplyQbyV( + headRotation, + speechBubbleOffset); + var position = + Vec3.sum( + headPosition, + rotatedOffset); + position.y += height / 2; // offset based on half of bubble height + speechBubbleParams.position = position; + + if (!speechBubbleTextID) { + speechBubbleTextID = + Entities.addEntity(speechBubbleParams, true); + } else { + Entities.editEntity(speechBubbleTextID, speechBubbleParams); + } + + //print("speechBubbleTextID:", speechBubbleTextID, "speechBubbleParams", JSON.stringify(speechBubbleParams)); + } + + // Hide the speech bubble. + function popDownSpeechBubble() { + cancelSpeechBubbleTimer(); + + speechBubbleShowing = false; + + //print("popDownSpeechBubble speechBubbleTextID", speechBubbleTextID); + + if (speechBubbleTextID) { + try { + Entities.deleteEntity(speechBubbleTextID); + } catch (e) {} + speechBubbleTextID = null; + } + } + + // Cancel the speech bubble popup timer. + function cancelSpeechBubbleTimer() { + if (speechBubbleTimer) { + Script.clearTimeout(speechBubbleTimer); + speechBubbleTimer = null; + } + } + + // Show the tablet web page and connect the web handler. + function showTabletWebPage() { + var url = Script.resolvePath(webPageURL); + if (randomizeWebPageURL) { + url += '?rand=' + Math.random(); + } + lastWebPageURL = url; + onChatPage = true; + tablet.gotoWebScreen(lastWebPageURL); + // Connect immediately so we don't miss anything. + connectWebHandler(); + } + + // Update the tablet web page with the chat log. + function updateChatPage() { + if (!onChatPage) { + return; + } + + tablet.emitScriptEvent( + JSON.stringify({ + type: "Update", + chatLog: chatLog + })); + } + + function onChatMessageReceived(channel, message, senderID) { + + // Ignore messages to any other channel than mine. + if (channel != channelName) { + return; + } + + // Parse the message and pull out the message parameters. + var messageData = JSON.parse(message); + var messageType = messageData.type; + + //print("MESSAGE", message); + //print("MESSAGEDATA", messageData, JSON.stringify(messageData)); + + switch (messageType) { + + case 'TransmitChatMessage': + handleTransmitChatMessage(messageData.avatarID, messageData.displayName, messageData.message, messageData.data); + break; + + case 'AvatarBeginTyping': + handleAvatarBeginTyping(messageData.avatarID, messageData.displayName); + break; + + case 'AvatarEndTyping': + handleAvatarEndTyping(messageData.avatarID, messageData.displayName); + break; + + case 'Who': + handleWho(messageData.myAvatarID); + break; + + case 'ReplyWho': + handleReplyWho(messageData.myAvatarID, messageData.avatarID, messageData.displayName, messageData.message, messageData.data); + break; + + default: + print("onChatMessageReceived: unknown messageType", messageType, "message", message); + break; + + } + + } + + // Handle events from the tablet web page. + function onWebEventReceived(event) { + if (!onChatPage) { + return; + } + + //print("onWebEventReceived: event", event); + + var eventData = JSON.parse(event); + var eventType = eventData.type; + + switch (eventType) { + + case 'Ready': + updateChatPage(); + break; + + case 'Update': + updateChatPage(); + break; + + case 'HandleChatMessage': + var message = eventData.message; + var data = eventData.data; + //print("onWebEventReceived: HandleChatMessage:", 'message', message, 'data', data); + handleChatMessage(message, data); + break; + + case 'PopDownSpeechBubble': + popDownSpeechBubble(); + break; + + case 'EmptyChatMessage': + emptyChatMessage(); + break; + + case 'Type': + type(); + break; + + case 'BeginTyping': + beginTyping(); + break; + + case 'EndTyping': + endTyping(); + break; + + case 'IdentifyAvatar': + identifyAvatar(eventData.avatarID); + break; + + case 'UnidentifyAvatar': + unidentifyAvatar(eventData.avatarID); + break; + + case 'FaceAvatar': + faceAvatar(eventData.avatarID, eventData.displayName); + break; + + case 'ClearChatLog': + clearChatLog(); + break; + + case 'Who': + transmitWho(); + break; + + case 'Bigger': + biggerSize(); + break; + + case 'Smaller': + smallerSize(); + break; + + case 'Normal': + normalSize(); + break; + + default: + print("onWebEventReceived: unexpected eventType", eventType); + break; + + } + } + + function onScreenChanged(type, url) { + //print("onScreenChanged", "type", type, "url", url, "lastWebPageURL", lastWebPageURL); + + if ((type === "Web") && + (url === lastWebPageURL)) { + if (!onChatPage) { + onChatPage = true; + connectWebHandler(); + } + } else { + if (onChatPage) { + onChatPage = false; + disconnectWebHandler(); + } + } + + } + + function connectWebHandler() { + if (webHandlerConnected) { + return; + } + + try { + tablet.webEventReceived.connect(onWebEventReceived); + } catch (e) { + print("connectWebHandler: error connecting: " + e); + return; + } + + webHandlerConnected = true; + //print("connectWebHandler connected"); + + updateChatPage(); + } + + function disconnectWebHandler() { + if (!webHandlerConnected) { + return; + } + + try { + tablet.webEventReceived.disconnect(onWebEventReceived); + } catch (e) { + print("disconnectWebHandler: error disconnecting web handler: " + e); + return; + } + webHandlerConnected = false; + + //print("disconnectWebHandler: disconnected"); + } + + // Show the tablet web page when the chat button on the tablet is clicked. + function onTabletButtonClicked() { + showTabletWebPage(); + } + + // Shut down the chat application when the tablet button is destroyed. + function onTabletButtonDestroyed() { + shutDown(); + } + + // Start up the chat application. + function startUp() { + //print("startUp"); + + loadSettings(); + + tabletButton = tablet.addButton({ + icon: tabletButtonIcon, + activeIcon: tabletButtonActiveIcon, + text: tabletButtonName + }); + + Messages.subscribe(channelName); + + tablet.screenChanged.connect(onScreenChanged); + + Messages.messageReceived.connect(onChatMessageReceived); + + tabletButton.clicked.connect(onTabletButtonClicked); + + Script.scriptEnding.connect(onTabletButtonDestroyed); + + logMessage('Type "/?" or "/help" for help with chat.', null); + + //print("Added chat button to tablet."); + } + + // Shut down the chat application. + function shutDown() { + //print("shutDown"); + + popDownSpeechBubble(); + unidentifyAvatars(); + disconnectWebHandler(); + + Overlays.deleteOverlay(textSizeOverlay); + + if (onChatPage) { + tablet.gotoHomeScreen(); + onChatPage = false; + } + + tablet.screenChanged.disconnect(onScreenChanged); + + Messages.messageReceived.disconnect(onChatMessageReceived); + + // Clean up the tablet button we made. + tabletButton.clicked.disconnect(onTabletButtonClicked); + tablet.removeButton(tabletButton); + tabletButton = null; + + //print("Removed chat button from tablet."); + } + + // Kick off the chat application! + startUp(); + +}()); diff --git a/scripts/simplifiedUI/system/clickToAvatarApp.js b/scripts/simplifiedUI/system/clickToAvatarApp.js new file mode 100644 index 0000000000..8024f595b5 --- /dev/null +++ b/scripts/simplifiedUI/system/clickToAvatarApp.js @@ -0,0 +1,7 @@ +(function () { + var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + this.clickDownOnEntity = function (entityID, mouseEvent) { + tablet.loadQMLSource("hifi/AvatarApp.qml"); + }; +} +); diff --git a/scripts/simplifiedUI/system/commerce/wallet.js b/scripts/simplifiedUI/system/commerce/wallet.js new file mode 100644 index 0000000000..86806fd8b4 --- /dev/null +++ b/scripts/simplifiedUI/system/commerce/wallet.js @@ -0,0 +1,737 @@ +"use strict"; +/* jslint vars:true, plusplus:true, forin:true */ +/* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */ +// +// wallet.js +// +// Created by Zach Fox on 2017-08-17 +// Copyright 2017 High Fidelity, Inc +// +// Distributed under the Apache License, Version 2.0 +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +/* global getConnectionData getControllerWorldLocation openLoginWindow WalletScriptingInterface */ + +(function () { // BEGIN LOCAL_SCOPE +Script.include("/~/system/libraries/accountUtils.js"); +Script.include("/~/system/libraries/connectionUtils.js"); +var AppUi = Script.require('appUi'); + +var MARKETPLACE_URL = Account.metaverseServerURL + "/marketplace"; + +// BEGIN AVATAR SELECTOR LOGIC +var UNSELECTED_COLOR = { red: 0x1F, green: 0xC6, blue: 0xA6 }; +var SELECTED_COLOR = { red: 0xF3, green: 0x91, blue: 0x29 }; +var HOVER_COLOR = { red: 0xD0, green: 0xD0, blue: 0xD0 }; + +var overlays = {}; // Keeps track of all our extended overlay data objects, keyed by target identifier. + +function ExtendedOverlay(key, type, properties) { // A wrapper around overlays to store the key it is associated with. + overlays[key] = this; + this.key = key; + this.selected = false; + this.hovering = false; + this.activeOverlay = Overlays.addOverlay(type, properties); // We could use different overlays for (un)selected... +} +// Instance methods: +ExtendedOverlay.prototype.deleteOverlay = function () { // remove display and data of this overlay + Overlays.deleteOverlay(this.activeOverlay); + delete overlays[this.key]; +}; + +ExtendedOverlay.prototype.editOverlay = function (properties) { // change display of this overlay + Overlays.editOverlay(this.activeOverlay, properties); +}; + +function color(selected, hovering) { + var base = hovering ? HOVER_COLOR : selected ? SELECTED_COLOR : UNSELECTED_COLOR; + function scale(component) { + return component; + } + return { red: scale(base.red), green: scale(base.green), blue: scale(base.blue) }; +} +// so we don't have to traverse the overlays to get the last one +var lastHoveringId = 0; +ExtendedOverlay.prototype.hover = function (hovering) { + this.hovering = hovering; + if (this.key === lastHoveringId) { + if (hovering) { + return; + } + lastHoveringId = 0; + } + this.editOverlay({ color: color(this.selected, hovering) }); + if (hovering) { + // un-hover the last hovering overlay + if (lastHoveringId && lastHoveringId !== this.key) { + ExtendedOverlay.get(lastHoveringId).hover(false); + } + lastHoveringId = this.key; + } +}; +ExtendedOverlay.prototype.select = function (selected) { + if (this.selected === selected) { + return; + } + + this.editOverlay({ color: color(selected, this.hovering) }); + this.selected = selected; +}; +// Class methods: +var selectedId = false; +ExtendedOverlay.isSelected = function (id) { + return selectedId === id; +}; +ExtendedOverlay.get = function (key) { // answer the extended overlay data object associated with the given avatar identifier + return overlays[key]; +}; +ExtendedOverlay.some = function (iterator) { // Bails early as soon as iterator returns truthy. + var key; + for (key in overlays) { + if (iterator(ExtendedOverlay.get(key))) { + return; + } + } +}; +ExtendedOverlay.unHover = function () { // calls hover(false) on lastHoveringId (if any) + if (lastHoveringId) { + ExtendedOverlay.get(lastHoveringId).hover(false); + } +}; + +// hit(overlay) on the one overlay intersected by pickRay, if any. +// noHit() if no ExtendedOverlay was intersected (helps with hover) +ExtendedOverlay.applyPickRay = function (pickRay, hit, noHit) { + // Depends on nearer coverOverlays to extend closer to us than farther ones. + var pickedOverlay = Overlays.findRayIntersection(pickRay); + if (!pickedOverlay.intersects) { + if (noHit) { + return noHit(); + } + return; + } + ExtendedOverlay.some(function (overlay) { // See if pickedOverlay is one of ours. + if ((overlay.activeOverlay) === pickedOverlay.overlayID) { + hit(overlay); + return true; + } + }); +}; + +function addAvatarNode(id) { + return new ExtendedOverlay(id, "sphere", { + drawInFront: true, + solid: true, + alpha: 0.8, + color: color(false, false), + ignoreRayIntersection: false + }); +} + +var pingPong = true; +var OVERLAY_SCALE = 0.032; +function updateOverlays() { + var eye = Camera.position; + AvatarList.getAvatarIdentifiers().forEach(function (id) { + if (!id) { + return; // don't update ourself, or avatars we're not interested in + } + var avatar = AvatarList.getAvatar(id); + if (!avatar) { + return; // will be deleted below if there had been an overlay. + } + var overlay = ExtendedOverlay.get(id); + if (!overlay) { // For now, we're treating this as a temporary loss, as from the personal space bubble. Add it back. + overlay = addAvatarNode(id); + } + var target = avatar.position; + var distance = Vec3.distance(target, eye); + var offset = 0.2; + // get diff between target and eye (a vector pointing to the eye from avatar position) + var diff = Vec3.subtract(target, eye); + var headIndex = avatar.getJointIndex("Head"); // base offset on 1/2 distance from hips to head if we can + if (headIndex > 0) { + offset = avatar.getAbsoluteJointTranslationInObjectFrame(headIndex).y / 2; + } + + // move a bit in front, towards the camera + target = Vec3.subtract(target, Vec3.multiply(Vec3.normalize(diff), offset)); + + // now bump it up a bit + target.y = target.y + offset; + + overlay.ping = pingPong; + overlay.editOverlay({ + color: color(ExtendedOverlay.isSelected(id), overlay.hovering), + position: target, + dimensions: OVERLAY_SCALE * distance + }); + }); + pingPong = !pingPong; + ExtendedOverlay.some(function (overlay) { // Remove any that weren't updated. (User is gone.) + if (overlay.ping === pingPong) { + overlay.deleteOverlay(); + } + }); +} +function removeOverlays() { + selectedId = false; + lastHoveringId = 0; + ExtendedOverlay.some(function (overlay) { + overlay.deleteOverlay(); + }); +} + +// +// Clicks. +// +function usernameFromIDReply(id, username, machineFingerprint, isAdmin) { + if (selectedId === id) { + var message = { + method: 'updateSelectedRecipientUsername', + userName: username === "" ? "unknown username" : username + }; + ui.sendMessage(message); + } +} +function handleClick(pickRay) { + ExtendedOverlay.applyPickRay(pickRay, function (overlay) { + var nextSelectedStatus = !overlay.selected; + var avatarId = overlay.key; + selectedId = nextSelectedStatus ? avatarId : false; + if (nextSelectedStatus) { + Users.requestUsernameFromID(avatarId); + } + var message = { + method: 'selectRecipient', + id: avatarId, + isSelected: nextSelectedStatus, + displayName: '"' + AvatarList.getAvatar(avatarId).sessionDisplayName + '"', + userName: '' + }; + ui.sendMessage(message); + + ExtendedOverlay.some(function (overlay) { + var id = overlay.key; + var selected = ExtendedOverlay.isSelected(id); + overlay.select(selected); + }); + + return true; + }); +} +function handleMouseEvent(mousePressEvent) { // handleClick if we get one. + if (!mousePressEvent.isLeftButton) { + return; + } + handleClick(Camera.computePickRay(mousePressEvent.x, mousePressEvent.y)); +} +function handleMouseMove(pickRay) { // given the pickRay, just do the hover logic + ExtendedOverlay.applyPickRay(pickRay, function (overlay) { + overlay.hover(true); + }, function () { + ExtendedOverlay.unHover(); + }); +} + +// handy global to keep track of which hand is the mouse (if any) +var currentHandPressed = 0; +var TRIGGER_CLICK_THRESHOLD = 0.85; +var TRIGGER_PRESS_THRESHOLD = 0.05; + +function handleMouseMoveEvent(event) { // find out which overlay (if any) is over the mouse position + var pickRay; + if (HMD.active) { + if (currentHandPressed !== 0) { + pickRay = controllerComputePickRay(currentHandPressed); + } else { + // nothing should hover, so + ExtendedOverlay.unHover(); + return; + } + } else { + pickRay = Camera.computePickRay(event.x, event.y); + } + handleMouseMove(pickRay); +} +function handleTriggerPressed(hand, value) { + // The idea is if you press one trigger, it is the one + // we will consider the mouse. Even if the other is pressed, + // we ignore it until this one is no longer pressed. + var isPressed = value > TRIGGER_PRESS_THRESHOLD; + if (currentHandPressed === 0) { + currentHandPressed = isPressed ? hand : 0; + return; + } + if (currentHandPressed === hand) { + currentHandPressed = isPressed ? hand : 0; + return; + } + // otherwise, the other hand is still triggered + // so do nothing. +} + +// We get mouseMoveEvents from the handControllers, via handControllerPointer. +// But we don't get mousePressEvents. +var triggerMapping = Controller.newMapping(Script.resolvePath('') + '-click'); +var triggerPressMapping = Controller.newMapping(Script.resolvePath('') + '-press'); +function controllerComputePickRay(hand) { + var controllerPose = getControllerWorldLocation(hand, true); + if (controllerPose.valid) { + return { origin: controllerPose.position, direction: Quat.getUp(controllerPose.orientation) }; + } +} +function makeClickHandler(hand) { + return function (clicked) { + if (clicked > TRIGGER_CLICK_THRESHOLD) { + var pickRay = controllerComputePickRay(hand); + handleClick(pickRay); + } + }; +} +function makePressHandler(hand) { + return function (value) { + handleTriggerPressed(hand, value); + }; +} +triggerMapping.from(Controller.Standard.RTClick).peek().to(makeClickHandler(Controller.Standard.RightHand)); +triggerMapping.from(Controller.Standard.LTClick).peek().to(makeClickHandler(Controller.Standard.LeftHand)); +triggerPressMapping.from(Controller.Standard.RT).peek().to(makePressHandler(Controller.Standard.RightHand)); +triggerPressMapping.from(Controller.Standard.LT).peek().to(makePressHandler(Controller.Standard.LeftHand)); +// END AVATAR SELECTOR LOGIC + +var sendMoneyRecipient; +var sendMoneyParticleEffectUpdateTimer; +var particleEffectTimestamp; +var sendMoneyParticleEffect; +var SEND_MONEY_PARTICLE_TIMER_UPDATE = 250; +var SEND_MONEY_PARTICLE_EMITTING_DURATION = 3000; +var SEND_MONEY_PARTICLE_LIFETIME_SECONDS = 8; +var SEND_MONEY_PARTICLE_PROPERTIES = { + accelerationSpread: { x: 0, y: 0, z: 0 }, + alpha: 1, + alphaFinish: 1, + alphaSpread: 0, + alphaStart: 1, + azimuthFinish: 0, + azimuthStart: -6, + color: { red: 143, green: 5, blue: 255 }, + colorFinish: { red: 255, green: 0, blue: 204 }, + colorSpread: { red: 0, green: 0, blue: 0 }, + colorStart: { red: 0, green: 136, blue: 255 }, + emitAcceleration: { x: 0, y: 0, z: 0 }, // Immediately gets updated to be accurate + emitDimensions: { x: 0, y: 0, z: 0 }, + emitOrientation: { x: 0, y: 0, z: 0 }, + emitRate: 4, + emitSpeed: 2.1, + emitterShouldTrail: true, + isEmitting: 1, + lifespan: SEND_MONEY_PARTICLE_LIFETIME_SECONDS + 1, // Immediately gets updated to be accurate + lifetime: SEND_MONEY_PARTICLE_LIFETIME_SECONDS + 1, + maxParticles: 20, + name: 'hfc-particles', + particleRadius: 0.2, + polarFinish: 0, + polarStart: 0, + radiusFinish: 0.05, + radiusSpread: 0, + radiusStart: 0.2, + speedSpread: 0, + textures: "http://hifi-content.s3.amazonaws.com/alan/dev/Particles/Bokeh-Particle-HFC.png", + type: 'ParticleEffect' +}; + +var MS_PER_SEC = 1000; +function updateSendMoneyParticleEffect() { + var timestampNow = Date.now(); + if ((timestampNow - particleEffectTimestamp) > (SEND_MONEY_PARTICLE_LIFETIME_SECONDS * MS_PER_SEC)) { + deleteSendMoneyParticleEffect(); + return; + } else if ((timestampNow - particleEffectTimestamp) > SEND_MONEY_PARTICLE_EMITTING_DURATION) { + Entities.editEntity(sendMoneyParticleEffect, { + isEmitting: 0 + }); + } else if (sendMoneyParticleEffect) { + var recipientPosition = AvatarList.getAvatar(sendMoneyRecipient).position; + var distance = Vec3.distance(recipientPosition, MyAvatar.position); + var accel = Vec3.subtract(recipientPosition, MyAvatar.position); + accel.y -= 3.0; + var life = Math.sqrt(2 * distance / Vec3.length(accel)); + Entities.editEntity(sendMoneyParticleEffect, { + emitAcceleration: accel, + lifespan: life + }); + } +} + +function deleteSendMoneyParticleEffect() { + if (sendMoneyParticleEffectUpdateTimer) { + Script.clearInterval(sendMoneyParticleEffectUpdateTimer); + sendMoneyParticleEffectUpdateTimer = null; + } + if (sendMoneyParticleEffect) { + sendMoneyParticleEffect = Entities.deleteEntity(sendMoneyParticleEffect); + } + sendMoneyRecipient = null; +} + +function onUsernameChanged() { + if (ui.checkIsOpen()) { + ui.open(WALLET_QML_SOURCE); + } +} + +var MARKETPLACE_QML_PATH = "hifi/commerce/marketplace/Marketplace.qml"; +function openMarketplace(optionalItem) { + ui.open(MARKETPLACE_QML_PATH); + + if (optionalItem) { + ui.tablet.sendToQml({ + method: 'updateMarketplaceQMLItem', + params: { itemId: optionalItem } + }); + } +} + +function setCertificateInfo(itemCertificateId) { + ui.tablet.sendToQml({ + method: 'inspectionCertificate_setCertificateId', + entityId: "", + certificateId: itemCertificateId + }); +} + +// Function Name: fromQml() +// +// Description: +// -Called when a message is received from SpectatorCamera.qml. The "message" argument is what is sent from the QML +// in the format "{method, params}", like json-rpc. See also sendToQml(). +function fromQml(message) { + switch (message.method) { + case 'passphrasePopup_cancelClicked': + case 'needsLogIn_cancelClicked': + ui.close(); + break; + case 'walletSetup_cancelClicked': + switch (message.referrer) { + case '': // User clicked "Wallet" app + case undefined: + case null: + ui.close(); + break; + case 'purchases': + case 'marketplace cta': + case 'mainPage': + openMarketplace(); + break; + default: + openMarketplace(); + break; + } + break; + case 'needsLogIn_loginClicked': + openLoginWindow(); + break; + case 'disableHmdPreview': + break; // do nothing here, handled in marketplaces.js + case 'maybeEnableHmdPreview': + break; // do nothing here, handled in marketplaces.js + case 'transactionHistory_linkClicked': + openMarketplace(message.itemId); + break; + case 'goToMarketplaceMainPage': + openMarketplace(); + break; + case 'goToMarketplaceItemPage': + openMarketplace(message.itemId); + break; + case 'refreshConnections': + print('Refreshing Connections...'); + getConnectionData(false); + break; + case 'enable_ChooseRecipientNearbyMode': + if (!isUpdateOverlaysWired) { + Script.update.connect(updateOverlays); + isUpdateOverlaysWired = true; + } + break; + case 'disable_ChooseRecipientNearbyMode': + if (isUpdateOverlaysWired) { + Script.update.disconnect(updateOverlays); + isUpdateOverlaysWired = false; + } + removeOverlays(); + break; + case 'sendAsset_sendPublicly': + deleteSendMoneyParticleEffect(); + sendMoneyRecipient = message.recipient; + var props = SEND_MONEY_PARTICLE_PROPERTIES; + props.parentID = MyAvatar.sessionUUID; + props.position = MyAvatar.position; + props.position.y += 0.2; + if (message.effectImage) { + props.textures = message.effectImage; + } + sendMoneyParticleEffect = Entities.addEntity(props, true); + particleEffectTimestamp = Date.now(); + updateSendMoneyParticleEffect(); + sendMoneyParticleEffectUpdateTimer = + Script.setInterval(updateSendMoneyParticleEffect, SEND_MONEY_PARTICLE_TIMER_UPDATE); + break; + case 'transactionHistory_goToBank': + if (Account.metaverseServerURL.indexOf("staging") >= 0) { + Window.location = "hifi://hifiqa-master-metaverse-staging"; // So that we can test in staging. + } else { + Window.location = "hifi://BankOfHighFidelity"; + } + break; + case 'purchases_updateWearables': + var currentlyWornWearables = []; + var ATTACHMENT_SEARCH_RADIUS = 100; // meters (just in case) + + var nearbyEntities = Entities.findEntitiesByType('Model', MyAvatar.position, ATTACHMENT_SEARCH_RADIUS); + + for (var i = 0; i < nearbyEntities.length; i++) { + var currentProperties = Entities.getEntityProperties( + nearbyEntities[i], ['certificateID', 'editionNumber', 'parentID'] + ); + if (currentProperties.parentID === MyAvatar.sessionUUID) { + currentlyWornWearables.push({ + entityID: nearbyEntities[i], + entityCertID: currentProperties.certificateID, + entityEdition: currentProperties.editionNumber + }); + } + } + + ui.tablet.sendToQml({ method: 'updateWearables', wornWearables: currentlyWornWearables }); + break; + case 'purchases_walletNotSetUp': + ui.tablet.sendToQml({ + method: 'updateWalletReferrer', + referrer: "purchases" + }); + break; + case 'purchases_openGoTo': + ui.open("hifi/tablet/TabletAddressDialog.qml"); + break; + case 'purchases_itemInfoClicked': + var itemId = message.itemId; + if (itemId && itemId !== "") { + openMarketplace(itemId); + } + break; + case 'purchases_itemCertificateClicked': + setCertificateInfo(message.itemCertificateId); + break; + case 'clearShouldShowDotHistory': + shouldShowDotHistory = false; + ui.messagesWaiting(shouldShowDotUpdates || shouldShowDotHistory); + break; + case 'clearShouldShowDotUpdates': + shouldShowDotUpdates = false; + ui.messagesWaiting(shouldShowDotUpdates || shouldShowDotHistory); + break; + case 'http.request': + // Handled elsewhere, don't log. + break; + case 'closeSendAsset': + ui.close(); + break; + default: + print('wallet.js: Unrecognized message from QML'); + } +} + +var isWired = false; +function walletOpened() { + Users.usernameFromIDReply.connect(usernameFromIDReply); + Controller.mousePressEvent.connect(handleMouseEvent); + Controller.mouseMoveEvent.connect(handleMouseMoveEvent); + triggerMapping.enable(); + triggerPressMapping.enable(); + isWired = true; + + if (shouldShowDotHistory) { + ui.sendMessage({ + method: 'updateRecentActivityMessageLight', + messagesWaiting: shouldShowDotHistory + }); + } +} + +function walletClosed() { + off(); +} + +function notificationDataProcessPageUpdates(data) { + return data.data.updates; +} + +function notificationDataProcessPageHistory(data) { + return data.data.history; +} + +var shouldShowDotUpdates = false; +function notificationPollCallbackUpdates(updatesArray) { + shouldShowDotUpdates = updatesArray.length > 0; + ui.messagesWaiting(shouldShowDotUpdates || shouldShowDotHistory); + + if (updatesArray.length > 0) { + var message; + if (!ui.notificationInitialCallbackMade[0]) { + message = updatesArray.length + " of your purchased items " + + (updatesArray.length === 1 ? "has an update " : "have updates ") + + "available. Open INVENTORY to update."; + ui.notificationDisplayBanner(message); + + ui.notificationPollCaresAboutSince[0] = true; + } else { + for (var i = 0; i < updatesArray.length; i++) { + message = "Update available for \"" + + updatesArray[i].base_item_title + "\"." + + "Open INVENTORY to update."; + ui.notificationDisplayBanner(message); + } + } + } +} +var shouldShowDotHistory = false; +function notificationPollCallbackHistory(historyArray) { + if (!ui.isOpen) { + var notificationCount = historyArray.length; + shouldShowDotHistory = shouldShowDotHistory || notificationCount > 0; + ui.messagesWaiting(shouldShowDotUpdates || shouldShowDotHistory); + + if (notificationCount > 0) { + var message; + if (!ui.notificationInitialCallbackMade[1]) { + message = "You have " + notificationCount + " unread recent " + + "transaction" + (notificationCount === 1 ? "" : "s") + ". Open INVENTORY to see all activity."; + ui.notificationDisplayBanner(message); + } else { + for (var i = 0; i < notificationCount; i++) { + var historyMessage = historyArray[i].message; + var sanitizedHistoryMessage = historyMessage.replace(/<\/?[^>]+(>|$)/g, ""); + message = '"' + sanitizedHistoryMessage + '" ' + + "Open INVENTORY to see all activity."; + ui.notificationDisplayBanner(message); + } + } + } + } +} + +function isReturnedDataEmptyUpdates(data) { + var updatesArray = data.data.updates; + return updatesArray.length === 0; +} + +function isReturnedDataEmptyHistory(data) { + var historyArray = data.data.history; + return historyArray.length === 0; +} + +var DEVELOPER_MENU = "Developer"; +var MARKETPLACE_ITEM_TESTER_LABEL = "Marketplace Item Tester"; +var MARKETPLACE_ITEM_TESTER_QML_SOURCE = "hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml"; +function installMarketplaceItemTester() { + if (!Menu.menuExists(DEVELOPER_MENU)) { + Menu.addMenu(DEVELOPER_MENU); + } + if (!Menu.menuItemExists(DEVELOPER_MENU, MARKETPLACE_ITEM_TESTER_LABEL)) { + Menu.addMenuItem({ + menuName: DEVELOPER_MENU, + menuItemName: MARKETPLACE_ITEM_TESTER_LABEL, + isCheckable: false + }); + } + + Menu.menuItemEvent.connect(function (menuItem) { + if (menuItem === MARKETPLACE_ITEM_TESTER_LABEL) { + ui.open(MARKETPLACE_ITEM_TESTER_QML_SOURCE); + } + }); +} + +function uninstallMarketplaceItemTester() { + if (Menu.menuExists(DEVELOPER_MENU) && + Menu.menuItemExists(DEVELOPER_MENU, MARKETPLACE_ITEM_TESTER_LABEL) + ) { + Menu.removeMenuItem(DEVELOPER_MENU, MARKETPLACE_ITEM_TESTER_LABEL); + } +} + +var BUTTON_NAME = "INVENTORY"; +var WALLET_QML_SOURCE = "hifi/commerce/wallet/Wallet.qml"; +var SENDASSET_QML_SOURCE = "hifi/commerce/common/sendAsset/SendAsset.qml"; +var NOTIFICATION_POLL_TIMEOUT = 300000; +var ui; +function startup() { + var notificationPollEndpointArray = ["/api/v1/commerce/available_updates?per_page=10"]; + var notificationPollTimeoutMsArray = [NOTIFICATION_POLL_TIMEOUT]; + var notificationDataProcessPageArray = [notificationDataProcessPageUpdates]; + var notificationPollCallbackArray = [notificationPollCallbackUpdates]; + var notificationPollStopPaginatingConditionMetArray = [isReturnedDataEmptyUpdates]; + var notificationPollCaresAboutSinceArray = [false]; + + if (!WalletScriptingInterface.limitedCommerce) { + notificationPollEndpointArray[1] = "/api/v1/commerce/history?per_page=10"; + notificationPollTimeoutMsArray[1] = NOTIFICATION_POLL_TIMEOUT; + notificationDataProcessPageArray[1] = notificationDataProcessPageHistory; + notificationPollCallbackArray[1] = notificationPollCallbackHistory; + notificationPollStopPaginatingConditionMetArray[1] = isReturnedDataEmptyHistory; + notificationPollCaresAboutSinceArray[1] = true; + } + + ui = new AppUi({ + buttonName: BUTTON_NAME, + sortOrder: 10, + home: WALLET_QML_SOURCE, + additionalAppScreens: SENDASSET_QML_SOURCE, + onOpened: walletOpened, + onClosed: walletClosed, + onMessage: fromQml, + notificationPollEndpoint: notificationPollEndpointArray, + notificationPollTimeoutMs: notificationPollTimeoutMsArray, + notificationDataProcessPage: notificationDataProcessPageArray, + notificationPollCallback: notificationPollCallbackArray, + notificationPollStopPaginatingConditionMet: notificationPollStopPaginatingConditionMetArray, + notificationPollCaresAboutSince: notificationPollCaresAboutSinceArray + }); + GlobalServices.myUsernameChanged.connect(onUsernameChanged); + installMarketplaceItemTester(); +} + +var isUpdateOverlaysWired = false; +function off() { + if (isWired) { + Users.usernameFromIDReply.disconnect(usernameFromIDReply); + Controller.mousePressEvent.disconnect(handleMouseEvent); + Controller.mouseMoveEvent.disconnect(handleMouseMoveEvent); + triggerMapping.disable(); + triggerPressMapping.disable(); + isWired = false; + } + + if (isUpdateOverlaysWired) { + Script.update.disconnect(updateOverlays); + isUpdateOverlaysWired = false; + } + removeOverlays(); +} + +function shutdown() { + GlobalServices.myUsernameChanged.disconnect(onUsernameChanged); + deleteSendMoneyParticleEffect(); + uninstallMarketplaceItemTester(); + off(); +} + +// +// Run the functions. +// +startup(); +Script.scriptEnding.connect(shutdown); +}()); // END LOCAL_SCOPE diff --git a/scripts/simplifiedUI/system/controllers/handTouch.js b/scripts/simplifiedUI/system/controllers/handTouch.js index c706d054c1..5939c6e3d2 100644 --- a/scripts/simplifiedUI/system/controllers/handTouch.js +++ b/scripts/simplifiedUI/system/controllers/handTouch.js @@ -17,7 +17,8 @@ (function () { var LEAP_MOTION_NAME = "LeapMotion"; - var handTouchEnabled = true; + // Hand touch is disabled due to twitchy finger bug when walking near walls or tables. see BUGZ-154. + var handTouchEnabled = false; var leapMotionEnabled = Controller.getRunningInputDeviceNames().indexOf(LEAP_MOTION_NAME) >= 0; var MSECONDS_AFTER_LOAD = 2000; var updateFingerWithIndex = 0; diff --git a/interface/resources/qml/hifi/tablet/Edit.qml b/scripts/simplifiedUI/system/create/Edit.qml similarity index 100% rename from interface/resources/qml/hifi/tablet/Edit.qml rename to scripts/simplifiedUI/system/create/Edit.qml diff --git a/interface/resources/qml/hifi/tablet/EditEntityList.qml b/scripts/simplifiedUI/system/create/EditEntityList.qml similarity index 87% rename from interface/resources/qml/hifi/tablet/EditEntityList.qml rename to scripts/simplifiedUI/system/create/EditEntityList.qml index 2afaa8cd82..94935c7bb5 100644 --- a/interface/resources/qml/hifi/tablet/EditEntityList.qml +++ b/scripts/simplifiedUI/system/create/EditEntityList.qml @@ -1,8 +1,8 @@ import QtQuick 2.7 import QtQuick.Controls 2.2 import QtWebChannel 1.0 -import "../../controls" -import "../toolbars" +import controls 1.0 +import hifi.toolbars 1.0 import QtGraphicalEffects 1.0 import controlsUit 1.0 as HifiControls import stylesUit 1.0 diff --git a/interface/resources/qml/hifi/tablet/EditTabButton.qml b/scripts/simplifiedUI/system/create/EditTabButton.qml similarity index 100% rename from interface/resources/qml/hifi/tablet/EditTabButton.qml rename to scripts/simplifiedUI/system/create/EditTabButton.qml diff --git a/interface/resources/qml/hifi/tablet/EditTabView.qml b/scripts/simplifiedUI/system/create/EditTabView.qml similarity index 94% rename from interface/resources/qml/hifi/tablet/EditTabView.qml rename to scripts/simplifiedUI/system/create/EditTabView.qml index 87db317b17..7e8789487c 100644 --- a/interface/resources/qml/hifi/tablet/EditTabView.qml +++ b/scripts/simplifiedUI/system/create/EditTabView.qml @@ -1,8 +1,8 @@ import QtQuick 2.7 import QtQuick.Controls 2.2 import QtWebChannel 1.0 -import "../../controls" -import "../toolbars" +import controls 1.0 +import hifi.toolbars 1.0 import QtGraphicalEffects 1.0 import controlsUit 1.0 as HifiControls import stylesUit 1.0 @@ -72,7 +72,7 @@ TabBar { NewEntityButton { - icon: "icons/create-icons/94-model-01.svg" + icon: "create-icons/94-model-01.svg" text: "MODEL" onClicked: { editRoot.sendToScript({ @@ -84,7 +84,7 @@ TabBar { } NewEntityButton { - icon: "icons/create-icons/21-cube-01.svg" + icon: "create-icons/21-cube-01.svg" text: "SHAPE" onClicked: { editRoot.sendToScript({ @@ -96,7 +96,7 @@ TabBar { } NewEntityButton { - icon: "icons/create-icons/24-light-01.svg" + icon: "create-icons/24-light-01.svg" text: "LIGHT" onClicked: { editRoot.sendToScript({ @@ -108,7 +108,7 @@ TabBar { } NewEntityButton { - icon: "icons/create-icons/20-text-01.svg" + icon: "create-icons/20-text-01.svg" text: "TEXT" onClicked: { editRoot.sendToScript({ @@ -120,7 +120,7 @@ TabBar { } NewEntityButton { - icon: "icons/create-icons/image.svg" + icon: "create-icons/image.svg" text: "IMAGE" onClicked: { editRoot.sendToScript({ @@ -132,7 +132,7 @@ TabBar { } NewEntityButton { - icon: "icons/create-icons/25-web-1-01.svg" + icon: "create-icons/25-web-1-01.svg" text: "WEB" onClicked: { editRoot.sendToScript({ @@ -144,7 +144,7 @@ TabBar { } NewEntityButton { - icon: "icons/create-icons/23-zone-01.svg" + icon: "create-icons/23-zone-01.svg" text: "ZONE" onClicked: { editRoot.sendToScript({ @@ -156,7 +156,7 @@ TabBar { } NewEntityButton { - icon: "icons/create-icons/90-particles-01.svg" + icon: "create-icons/90-particles-01.svg" text: "PARTICLE" onClicked: { editRoot.sendToScript({ @@ -168,7 +168,7 @@ TabBar { } NewEntityButton { - icon: "icons/create-icons/126-material-01.svg" + icon: "create-icons/126-material-01.svg" text: "MATERIAL" onClicked: { editRoot.sendToScript({ diff --git a/interface/resources/qml/hifi/tablet/EditTools.qml b/scripts/simplifiedUI/system/create/EditTools.qml similarity index 95% rename from interface/resources/qml/hifi/tablet/EditTools.qml rename to scripts/simplifiedUI/system/create/EditTools.qml index 976e98cd50..468935b287 100644 --- a/interface/resources/qml/hifi/tablet/EditTools.qml +++ b/scripts/simplifiedUI/system/create/EditTools.qml @@ -37,7 +37,7 @@ StackView { } function pushSource(path) { - var item = Qt.createComponent(Qt.resolvedUrl("../../" + path)); + var item = Qt.createComponent(Qt.resolvedUrl(path)); editRoot.push(item, itemProperties, StackView.Immediate); editRoot.currentItem.sendToScript.connect(editRoot.sendToScript); diff --git a/interface/resources/qml/hifi/tablet/EditToolsTabView.qml b/scripts/simplifiedUI/system/create/EditToolsTabView.qml similarity index 94% rename from interface/resources/qml/hifi/tablet/EditToolsTabView.qml rename to scripts/simplifiedUI/system/create/EditToolsTabView.qml index 1f062815b6..a333acc586 100644 --- a/interface/resources/qml/hifi/tablet/EditToolsTabView.qml +++ b/scripts/simplifiedUI/system/create/EditToolsTabView.qml @@ -1,8 +1,8 @@ import QtQuick 2.7 import QtQuick.Controls 2.2 import QtWebChannel 1.0 -import "../../controls" -import "../toolbars" +import controls 1.0 +import hifi.toolbars 1.0 import QtGraphicalEffects 1.0 import controlsUit 1.0 as HifiControls import stylesUit 1.0 @@ -78,7 +78,7 @@ TabBar { NewEntityButton { - icon: "icons/create-icons/94-model-01.svg" + icon: "create-icons/94-model-01.svg" text: "MODEL" onClicked: { editRoot.sendToScript({ @@ -90,7 +90,7 @@ TabBar { } NewEntityButton { - icon: "icons/create-icons/21-cube-01.svg" + icon: "create-icons/21-cube-01.svg" text: "SHAPE" onClicked: { editRoot.sendToScript({ @@ -102,7 +102,7 @@ TabBar { } NewEntityButton { - icon: "icons/create-icons/24-light-01.svg" + icon: "create-icons/24-light-01.svg" text: "LIGHT" onClicked: { editRoot.sendToScript({ @@ -114,7 +114,7 @@ TabBar { } NewEntityButton { - icon: "icons/create-icons/20-text-01.svg" + icon: "create-icons/20-text-01.svg" text: "TEXT" onClicked: { editRoot.sendToScript({ @@ -126,7 +126,7 @@ TabBar { } NewEntityButton { - icon: "icons/create-icons/image.svg" + icon: "create-icons/image.svg" text: "IMAGE" onClicked: { editRoot.sendToScript({ @@ -138,7 +138,7 @@ TabBar { } NewEntityButton { - icon: "icons/create-icons/25-web-1-01.svg" + icon: "create-icons/25-web-1-01.svg" text: "WEB" onClicked: { editRoot.sendToScript({ @@ -150,7 +150,7 @@ TabBar { } NewEntityButton { - icon: "icons/create-icons/23-zone-01.svg" + icon: "create-icons/23-zone-01.svg" text: "ZONE" onClicked: { editRoot.sendToScript({ @@ -162,7 +162,7 @@ TabBar { } NewEntityButton { - icon: "icons/create-icons/90-particles-01.svg" + icon: "create-icons/90-particles-01.svg" text: "PARTICLE" onClicked: { editRoot.sendToScript({ @@ -174,7 +174,7 @@ TabBar { } NewEntityButton { - icon: "icons/create-icons/126-material-01.svg" + icon: "create-icons/126-material-01.svg" text: "MATERIAL" onClicked: { editRoot.sendToScript({ diff --git a/interface/resources/qml/hifi/tablet/EntityList.qml b/scripts/simplifiedUI/system/create/EntityList.qml similarity index 100% rename from interface/resources/qml/hifi/tablet/EntityList.qml rename to scripts/simplifiedUI/system/create/EntityList.qml diff --git a/interface/resources/qml/hifi/tablet/NewEntityButton.qml b/scripts/simplifiedUI/system/create/NewEntityButton.qml similarity index 92% rename from interface/resources/qml/hifi/tablet/NewEntityButton.qml rename to scripts/simplifiedUI/system/create/NewEntityButton.qml index 1952ef7ee8..9c210ac95a 100644 --- a/interface/resources/qml/hifi/tablet/NewEntityButton.qml +++ b/scripts/simplifiedUI/system/create/NewEntityButton.qml @@ -6,7 +6,7 @@ Item { id: newEntityButton property var uuid; property string text: "ENTITY" - property string icon: "icons/edit-icon.svg" + property string icon: Path.resources + "icons/edit-icon.svg" property string activeText: newEntityButton.text property string activeIcon: newEntityButton.icon property bool isActive: false @@ -47,14 +47,6 @@ Item { anchors.topMargin: 0 } - function urlHelper(src) { - if (src.match(/\bhttp/)) { - return src; - } else { - return "../../../" + src; - } - } - Rectangle { id: buttonOutline color: "#00000000" @@ -96,7 +88,7 @@ Item { anchors.bottomMargin: 5 anchors.horizontalCenter: parent.horizontalCenter fillMode: Image.Stretch - source: newEntityButton.urlHelper(newEntityButton.icon) + source: newEntityButton.icon } ColorOverlay { diff --git a/interface/resources/qml/hifi/tablet/NewMaterialDialog.qml b/scripts/simplifiedUI/system/create/NewMaterialDialog.qml similarity index 99% rename from interface/resources/qml/hifi/tablet/NewMaterialDialog.qml rename to scripts/simplifiedUI/system/create/NewMaterialDialog.qml index dde372648b..75570327e0 100644 --- a/interface/resources/qml/hifi/tablet/NewMaterialDialog.qml +++ b/scripts/simplifiedUI/system/create/NewMaterialDialog.qml @@ -15,7 +15,7 @@ import QtQuick.Dialogs 1.2 as OriginalDialogs import stylesUit 1.0 import controlsUit 1.0 -import "../dialogs" +import dialogs 1.0 Rectangle { id: newMaterialDialog diff --git a/interface/resources/qml/hifi/tablet/NewMaterialWindow.qml b/scripts/simplifiedUI/system/create/NewMaterialWindow.qml similarity index 100% rename from interface/resources/qml/hifi/tablet/NewMaterialWindow.qml rename to scripts/simplifiedUI/system/create/NewMaterialWindow.qml diff --git a/interface/resources/qml/hifi/tablet/NewModelDialog.qml b/scripts/simplifiedUI/system/create/NewModelDialog.qml similarity index 99% rename from interface/resources/qml/hifi/tablet/NewModelDialog.qml rename to scripts/simplifiedUI/system/create/NewModelDialog.qml index 9540979479..1ded00d701 100644 --- a/interface/resources/qml/hifi/tablet/NewModelDialog.qml +++ b/scripts/simplifiedUI/system/create/NewModelDialog.qml @@ -14,7 +14,7 @@ import QtQuick.Dialogs 1.2 as OriginalDialogs import stylesUit 1.0 import controlsUit 1.0 -import "../dialogs" +import dialogs 1.0 Rectangle { id: newModelDialog diff --git a/interface/resources/qml/hifi/tablet/NewModelWindow.qml b/scripts/simplifiedUI/system/create/NewModelWindow.qml similarity index 100% rename from interface/resources/qml/hifi/tablet/NewModelWindow.qml rename to scripts/simplifiedUI/system/create/NewModelWindow.qml diff --git a/interface/resources/icons/create-icons/126-material-01.svg b/scripts/simplifiedUI/system/create/create-icons/126-material-01.svg similarity index 100% rename from interface/resources/icons/create-icons/126-material-01.svg rename to scripts/simplifiedUI/system/create/create-icons/126-material-01.svg diff --git a/interface/resources/icons/create-icons/20-text-01.svg b/scripts/simplifiedUI/system/create/create-icons/20-text-01.svg similarity index 100% rename from interface/resources/icons/create-icons/20-text-01.svg rename to scripts/simplifiedUI/system/create/create-icons/20-text-01.svg diff --git a/interface/resources/icons/create-icons/21-cube-01.svg b/scripts/simplifiedUI/system/create/create-icons/21-cube-01.svg similarity index 100% rename from interface/resources/icons/create-icons/21-cube-01.svg rename to scripts/simplifiedUI/system/create/create-icons/21-cube-01.svg diff --git a/interface/resources/icons/create-icons/22-sphere-01.svg b/scripts/simplifiedUI/system/create/create-icons/22-sphere-01.svg similarity index 100% rename from interface/resources/icons/create-icons/22-sphere-01.svg rename to scripts/simplifiedUI/system/create/create-icons/22-sphere-01.svg diff --git a/interface/resources/icons/create-icons/23-zone-01.svg b/scripts/simplifiedUI/system/create/create-icons/23-zone-01.svg similarity index 100% rename from interface/resources/icons/create-icons/23-zone-01.svg rename to scripts/simplifiedUI/system/create/create-icons/23-zone-01.svg diff --git a/interface/resources/icons/create-icons/24-light-01.svg b/scripts/simplifiedUI/system/create/create-icons/24-light-01.svg similarity index 100% rename from interface/resources/icons/create-icons/24-light-01.svg rename to scripts/simplifiedUI/system/create/create-icons/24-light-01.svg diff --git a/interface/resources/icons/create-icons/25-web-1-01.svg b/scripts/simplifiedUI/system/create/create-icons/25-web-1-01.svg similarity index 100% rename from interface/resources/icons/create-icons/25-web-1-01.svg rename to scripts/simplifiedUI/system/create/create-icons/25-web-1-01.svg diff --git a/scripts/simplifiedUI/system/create/create-icons/90-particles-01.svg b/scripts/simplifiedUI/system/create/create-icons/90-particles-01.svg new file mode 100644 index 0000000000..5e0105d7cd --- /dev/null +++ b/scripts/simplifiedUI/system/create/create-icons/90-particles-01.svg @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + diff --git a/interface/resources/icons/create-icons/94-model-01.svg b/scripts/simplifiedUI/system/create/create-icons/94-model-01.svg similarity index 100% rename from interface/resources/icons/create-icons/94-model-01.svg rename to scripts/simplifiedUI/system/create/create-icons/94-model-01.svg diff --git a/interface/resources/icons/create-icons/image.svg b/scripts/simplifiedUI/system/create/create-icons/image.svg similarity index 100% rename from interface/resources/icons/create-icons/image.svg rename to scripts/simplifiedUI/system/create/create-icons/image.svg diff --git a/scripts/simplifiedUI/system/dialTone.js b/scripts/simplifiedUI/system/dialTone.js new file mode 100644 index 0000000000..7c0a5b250d --- /dev/null +++ b/scripts/simplifiedUI/system/dialTone.js @@ -0,0 +1,36 @@ +"use strict"; + +// +// dialTone.js +// examples +// +// Created by Stephen Birarda on 06/08/15. +// Added disconnect HRS 6/11/15. +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +(function() { // BEGIN LOCAL_SCOPE + +// setup the local sound we're going to use +var connectSound = SoundCache.getSound(Script.resolvePath("assets/sounds/hello.wav")); +var disconnectSound = SoundCache.getSound(Script.resolvePath("assets/sounds/goodbye.wav")); +var micMutedSound = SoundCache.getSound(Script.resolvePath("assets/sounds/goodbye.wav")); + +// setup the options needed for that sound +var soundOptions = { + localOnly: true +}; + +// play the sound locally once we get the first audio packet from a mixer +Audio.receivedFirstPacket.connect(function(){ + Audio.playSound(connectSound, soundOptions); +}); + +Audio.disconnected.connect(function(){ + Audio.playSound(disconnectSound, soundOptions); +}); + +}()); // END LOCAL_SCOPE diff --git a/scripts/simplifiedUI/system/directory.js b/scripts/simplifiedUI/system/directory.js new file mode 100644 index 0000000000..f84429ab95 --- /dev/null +++ b/scripts/simplifiedUI/system/directory.js @@ -0,0 +1,134 @@ +// +// directory.js +// examples +// +// Created by David Rowe on 8 Jun 2015 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +Script.include([ + "libraries/toolBars.js", +]); + +var toolIconUrl = Script.resolvePath("assets/images/tools/"); + +var DIRECTORY_WINDOW_URL = Account.metaverseServerURL + "/directory"; +var directoryWindow = new OverlayWebWindow({ + title: 'Directory', + source: "about:blank", + width: 900, + height: 700, + visible: false +}); + +var toolHeight = 50; +var toolWidth = 50; +var TOOLBAR_MARGIN_Y = 0; + + +function showDirectory() { + directoryWindow.setURL(DIRECTORY_WINDOW_URL); + directoryWindow.setVisible(true); +} + +function hideDirectory() { + directoryWindow.setVisible(false); + directoryWindow.setURL("about:blank"); +} + +function toggleDirectory() { + if (directoryWindow.visible) { + hideDirectory(); + } else { + showDirectory(); + } +} + +var toolBar = (function() { + var that = {}, + toolBar, + browseDirectoryButton; + + function initialize() { + toolBar = new ToolBar(0, 0, ToolBar.HORIZONTAL, "highfidelity.directory.toolbar", function(windowDimensions, toolbar) { + return { + x: windowDimensions.x / 2, + y: windowDimensions.y + }; + }, { + x: -2 * toolWidth, + y: -TOOLBAR_MARGIN_Y - toolHeight + }); + browseDirectoryButton = toolBar.addTool({ + imageURL: toolIconUrl + "directory.svg", + subImage: { + x: 0, + y: Tool.IMAGE_WIDTH, + width: Tool.IMAGE_WIDTH, + height: Tool.IMAGE_HEIGHT + }, + width: toolWidth, + height: toolHeight, + alpha: 0.9, + visible: true + }); + + toolBar.showTool(browseDirectoryButton, true); + } + + var browseDirectoryButtonDown = false; + that.mousePressEvent = function(event) { + var clickedOverlay, + url, + file; + + if (!event.isLeftButton) { + // if another mouse button than left is pressed ignore it + return false; + } + + clickedOverlay = Overlays.getOverlayAtPoint({ + x: event.x, + y: event.y + }); + + + + if (browseDirectoryButton === toolBar.clicked(clickedOverlay)) { + toggleDirectory(); + return true; + } + + return false; + }; + + that.mouseReleaseEvent = function(event) { + var handled = false; + + + if (browseDirectoryButtonDown) { + var clickedOverlay = Overlays.getOverlayAtPoint({ + x: event.x, + y: event.y + }); + } + + newModelButtonDown = false; + browseDirectoryButtonDown = false; + + return handled; + } + + that.cleanup = function() { + toolBar.cleanup(); + }; + + initialize(); + return that; +}()); + +Controller.mousePressEvent.connect(toolBar.mousePressEvent) +Script.scriptEnding.connect(toolBar.cleanup); diff --git a/scripts/simplifiedUI/system/edit.js b/scripts/simplifiedUI/system/edit.js new file mode 100644 index 0000000000..cf99f3a618 --- /dev/null +++ b/scripts/simplifiedUI/system/edit.js @@ -0,0 +1,2858 @@ +// edit.js +// +// Created by Brad Hefta-Gaub on 10/2/14. +// Persist toolbar by HRS 6/11/15. +// Copyright 2014 High Fidelity, Inc. +// +// This script allows you to edit entities with a new UI/UX for mouse and trackpad based editing +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +/* global Script, SelectionDisplay, LightOverlayManager, CameraManager, Grid, GridTool, EntityListTool, Vec3, SelectionManager, + Overlays, OverlayWebWindow, UserActivityLogger, Settings, Entities, Tablet, Toolbars, Messages, Menu, Camera, + progressDialog, tooltip, MyAvatar, Quat, Controller, Clipboard, HMD, UndoStack, OverlaySystemWindow, + keyUpEventFromUIWindow:true */ + +(function() { // BEGIN LOCAL_SCOPE + +"use strict"; + +var EDIT_TOGGLE_BUTTON = "com.highfidelity.interface.system.editButton"; + +var CONTROLLER_MAPPING_NAME = "com.highfidelity.editMode"; + +Script.include([ + "libraries/stringHelpers.js", + "libraries/dataViewHelpers.js", + "libraries/progressDialog.js", + "libraries/entitySelectionTool.js", + "libraries/ToolTip.js", + "libraries/entityCameraTool.js", + "libraries/gridTool.js", + "libraries/entityList.js", + "libraries/utils.js", + "libraries/entityIconOverlayManager.js" +]); + +var CreateWindow = Script.require('./modules/createWindow.js'); + +var TITLE_OFFSET = 60; +var CREATE_TOOLS_WIDTH = 490; +var MAX_DEFAULT_ENTITY_LIST_HEIGHT = 942; + +var DEFAULT_IMAGE = "https://hifi-content.s3.amazonaws.com/DomainContent/production/no-image.jpg"; + +var createToolsWindow = new CreateWindow( + Script.resolvePath("create/EditTools.qml"), + 'Create Tools', + 'com.highfidelity.create.createToolsWindow', + function () { + var windowHeight = Window.innerHeight - TITLE_OFFSET; + if (windowHeight > MAX_DEFAULT_ENTITY_LIST_HEIGHT) { + windowHeight = MAX_DEFAULT_ENTITY_LIST_HEIGHT; + } + return { + size: { + x: CREATE_TOOLS_WIDTH, + y: windowHeight + }, + position: { + x: Window.x + Window.innerWidth - CREATE_TOOLS_WIDTH, + y: Window.y + TITLE_OFFSET + } + } + }, + false +); + +/** + * @description Returns true in case we should use the tablet version of the CreateApp + * @returns boolean + */ +var shouldUseEditTabletApp = function() { + return HMD.active || (!HMD.active && !Settings.getValue("desktopTabletBecomesToolbar", true)); +}; + + +var selectionDisplay = SelectionDisplay; +var selectionManager = SelectionManager; + +var PARTICLE_SYSTEM_URL = Script.resolvePath("assets/images/icon-particles.svg"); +var POINT_LIGHT_URL = Script.resolvePath("assets/images/icon-point-light.svg"); +var SPOT_LIGHT_URL = Script.resolvePath("assets/images/icon-spot-light.svg"); +var ZONE_URL = Script.resolvePath("assets/images/icon-zone.svg"); + +var entityIconOverlayManager = new EntityIconOverlayManager(['Light', 'ParticleEffect', 'Zone'], function(entityID) { + var properties = Entities.getEntityProperties(entityID, ['type', 'isSpotlight']); + if (properties.type === 'Light') { + return { + url: properties.isSpotlight ? SPOT_LIGHT_URL : POINT_LIGHT_URL, + }; + } else if (properties.type === 'Zone') { + return { + url: ZONE_URL, + }; + } else { + return { + url: PARTICLE_SYSTEM_URL, + }; + } +}); + +var cameraManager = new CameraManager(); + +var grid = new Grid(); +var gridTool = new GridTool({ + horizontalGrid: grid, + createToolsWindow: createToolsWindow, + shouldUseEditTabletApp: shouldUseEditTabletApp +}); +gridTool.setVisible(false); + +var EntityShapeVisualizer = Script.require('./modules/entityShapeVisualizer.js'); +var entityShapeVisualizer = new EntityShapeVisualizer(["Zone"]); + +var entityListTool = new EntityListTool(shouldUseEditTabletApp); + +selectionManager.addEventListener(function () { + selectionDisplay.updateHandles(); + entityIconOverlayManager.updatePositions(); + entityShapeVisualizer.setEntities(selectionManager.selections); +}); + +var DEGREES_TO_RADIANS = Math.PI / 180.0; +var RADIANS_TO_DEGREES = 180.0 / Math.PI; + +var MIN_ANGULAR_SIZE = 2; +var MAX_ANGULAR_SIZE = 45; +var allowLargeModels = true; +var allowSmallModels = true; + +var DEFAULT_DIMENSION = 0.20; + +var DEFAULT_DIMENSIONS = { + x: DEFAULT_DIMENSION, + y: DEFAULT_DIMENSION, + z: DEFAULT_DIMENSION +}; + +var DEFAULT_LIGHT_DIMENSIONS = Vec3.multiply(20, DEFAULT_DIMENSIONS); + +var MENU_AUTO_FOCUS_ON_SELECT = "Auto Focus on Select"; +var MENU_EASE_ON_FOCUS = "Ease Orientation on Focus"; +var MENU_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE = "Show Lights and Particle Systems in Create Mode"; +var MENU_SHOW_ZONES_IN_EDIT_MODE = "Show Zones in Create Mode"; + +var MENU_CREATE_ENTITIES_GRABBABLE = "Create Entities As Grabbable (except Zones, Particles, and Lights)"; +var MENU_ALLOW_SELECTION_LARGE = "Allow Selecting of Large Models"; +var MENU_ALLOW_SELECTION_SMALL = "Allow Selecting of Small Models"; +var MENU_ALLOW_SELECTION_LIGHTS = "Allow Selecting of Lights"; + +var SETTING_AUTO_FOCUS_ON_SELECT = "autoFocusOnSelect"; +var SETTING_EASE_ON_FOCUS = "cameraEaseOnFocus"; +var SETTING_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE = "showLightsAndParticlesInEditMode"; +var SETTING_SHOW_ZONES_IN_EDIT_MODE = "showZonesInEditMode"; + +var SETTING_EDIT_PREFIX = "Edit/"; + + +var CREATE_ENABLED_ICON = "icons/tablet-icons/edit-i.svg"; +var CREATE_DISABLED_ICON = "icons/tablet-icons/edit-disabled.svg"; + +// marketplace info, etc. not quite ready yet. +var SHOULD_SHOW_PROPERTY_MENU = false; +var INSUFFICIENT_PERMISSIONS_ERROR_MSG = "You do not have the necessary permissions to edit on this domain."; +var INSUFFICIENT_PERMISSIONS_IMPORT_ERROR_MSG = "You do not have the necessary permissions to place items on this domain."; + +var isActive = false; +var createButton = null; + +var IMPORTING_SVO_OVERLAY_WIDTH = 144; +var IMPORTING_SVO_OVERLAY_HEIGHT = 30; +var IMPORTING_SVO_OVERLAY_MARGIN = 5; +var IMPORTING_SVO_OVERLAY_LEFT_MARGIN = 34; +var importingSVOImageOverlay = Overlays.addOverlay("image", { + imageURL: Script.resolvePath("assets") + "/images/hourglass.svg", + width: 20, + height: 20, + alpha: 1.0, + x: Window.innerWidth - IMPORTING_SVO_OVERLAY_WIDTH, + y: Window.innerHeight - IMPORTING_SVO_OVERLAY_HEIGHT, + visible: false +}); +var importingSVOTextOverlay = Overlays.addOverlay("text", { + font: { + size: 14 + }, + text: "Importing SVO...", + leftMargin: IMPORTING_SVO_OVERLAY_LEFT_MARGIN, + x: Window.innerWidth - IMPORTING_SVO_OVERLAY_WIDTH - IMPORTING_SVO_OVERLAY_MARGIN, + y: Window.innerHeight - IMPORTING_SVO_OVERLAY_HEIGHT - IMPORTING_SVO_OVERLAY_MARGIN, + width: IMPORTING_SVO_OVERLAY_WIDTH, + height: IMPORTING_SVO_OVERLAY_HEIGHT, + backgroundColor: { + red: 80, + green: 80, + blue: 80 + }, + backgroundAlpha: 0.7, + visible: false +}); + +var MARKETPLACE_URL = Account.metaverseServerURL + "/marketplace"; +var marketplaceWindow = new OverlayWebWindow({ + title: 'Marketplace', + source: "about:blank", + width: 900, + height: 700, + visible: false +}); + +function showMarketplace(marketplaceID) { + var url = MARKETPLACE_URL; + if (marketplaceID) { + url = url + "/items/" + marketplaceID; + } + marketplaceWindow.setURL(url); + marketplaceWindow.setVisible(true); + marketplaceWindow.raise(); + + UserActivityLogger.logAction("opened_marketplace"); +} + +function hideMarketplace() { + marketplaceWindow.setVisible(false); + marketplaceWindow.setURL("about:blank"); +} + +// function toggleMarketplace() { +// if (marketplaceWindow.visible) { +// hideMarketplace(); +// } else { +// showMarketplace(); +// } +// } + +function adjustPositionPerBoundingBox(position, direction, registration, dimensions, orientation) { + // Adjust the position such that the bounding box (registration, dimensions and orientation) lies behind the original + // position in the given direction. + var CORNERS = [ + { x: 0, y: 0, z: 0 }, + { x: 0, y: 0, z: 1 }, + { x: 0, y: 1, z: 0 }, + { x: 0, y: 1, z: 1 }, + { x: 1, y: 0, z: 0 }, + { x: 1, y: 0, z: 1 }, + { x: 1, y: 1, z: 0 }, + { x: 1, y: 1, z: 1 }, + ]; + + // Go through all corners and find least (most negative) distance in front of position. + var distance = 0; + for (var i = 0, length = CORNERS.length; i < length; i++) { + var cornerVector = + Vec3.multiplyQbyV(orientation, Vec3.multiplyVbyV(Vec3.subtract(CORNERS[i], registration), dimensions)); + var cornerDistance = Vec3.dot(cornerVector, direction); + distance = Math.min(cornerDistance, distance); + } + position = Vec3.sum(Vec3.multiply(distance, direction), position); + return position; +} + +var GRABBABLE_ENTITIES_MENU_CATEGORY = "Edit"; + +// Handles any edit mode updates required when domains have switched +function checkEditPermissionsAndUpdate() { + if ((createButton === null) || (createButton === undefined)) { + //--EARLY EXIT--( nothing to safely update ) + return; + } + + var hasRezPermissions = (Entities.canRez() || Entities.canRezTmp() || Entities.canRezCertified() || Entities.canRezTmpCertified()); + createButton.editProperties({ + icon: (hasRezPermissions ? CREATE_ENABLED_ICON : CREATE_DISABLED_ICON), + captionColor: (hasRezPermissions ? "#ffffff" : "#888888"), + }); + + if (!hasRezPermissions && isActive) { + that.setActive(false); + tablet.gotoHomeScreen(); + } +} + +// Copies the properties in `b` into `a`. `a` will be modified. +function copyProperties(a, b) { + for (var key in b) { + a[key] = b[key]; + } + return a; +} + +const DEFAULT_DYNAMIC_PROPERTIES = { + dynamic: true, + damping: 0.39347, + angularDamping: 0.39347, + gravity: { x: 0, y: -9.8, z: 0 }, +}; + +const DEFAULT_NON_DYNAMIC_PROPERTIES = { + dynamic: false, + damping: 0, + angularDamping: 0, + gravity: { x: 0, y: 0, z: 0 }, +}; + +const DEFAULT_ENTITY_PROPERTIES = { + All: { + description: "", + rotation: { x: 0, y: 0, z: 0, w: 1 }, + collidesWith: "static,dynamic,kinematic,otherAvatar,myAvatar", + collisionSoundURL: "", + cloneable: false, + ignoreIK: true, + canCastShadow: true, + href: "", + script: "", + serverScripts:"", + velocity: { + x: 0, + y: 0, + z: 0 + }, + angularVelocity: { + x: 0, + y: 0, + z: 0 + }, + restitution: 0.5, + friction: 0.5, + density: 1000, + dynamic: false, + }, + Shape: { + shape: "Box", + dimensions: { x: 0.2, y: 0.2, z: 0.2 }, + color: { red: 0, green: 180, blue: 239 }, + }, + Text: { + text: "Text", + dimensions: { + x: 0.65, + y: 0.3, + z: 0.01 + }, + textColor: { red: 255, green: 255, blue: 255 }, + backgroundColor: { red: 0, green: 0, blue: 0 }, + lineHeight: 0.06, + faceCamera: false, + }, + Zone: { + dimensions: { + x: 10, + y: 10, + z: 10 + }, + flyingAllowed: true, + ghostingAllowed: true, + filter: "", + keyLightMode: "inherit", + keyLightColor: { red: 255, green: 255, blue: 255 }, + keyLight: { + intensity: 1.0, + direction: { + x: 0.0, + y: -0.707106769084930, // 45 degrees + z: 0.7071067690849304 + }, + castShadows: true + }, + ambientLightMode: "inherit", + ambientLight: { + ambientIntensity: 0.5, + ambientURL: "" + }, + hazeMode: "inherit", + haze: { + hazeRange: 1000, + hazeAltitudeEffect: false, + hazeBaseRef: 0, + hazeColor: { + red: 128, + green: 154, + blue: 179 + }, + hazeBackgroundBlend: 0, + hazeEnableGlare: false, + hazeGlareColor: { + red: 255, + green: 229, + blue: 179 + }, + }, + shapeType: "box", + bloomMode: "inherit", + avatarPriority: "inherit" + }, + Model: { + collisionShape: "none", + compoundShapeURL: "", + animation: { + url: "", + running: false, + allowTranslation: false, + loop: true, + hold: false, + currentFrame: 0, + firstFrame: 0, + lastFrame: 100000, + fps: 30.0, + } + }, + Image: { + dimensions: { + x: 0.5385, + y: 0.2819, + z: 0.0092 + }, + shapeType: "box", + collisionless: true, + keepAspectRatio: false, + imageURL: DEFAULT_IMAGE + }, + Web: { + dimensions: { + x: 1.6, + y: 0.9, + z: 0.01 + }, + sourceUrl: "https://highfidelity.com/", + dpi: 30, + }, + ParticleEffect: { + lifespan: 1.5, + maxParticles: 10, + textures: "https://content.highfidelity.com/DomainContent/production/Particles/wispy-smoke.png", + emitRate: 5.5, + emitSpeed: 0, + speedSpread: 0, + emitDimensions: { x: 0, y: 0, z: 0 }, + emitOrientation: { x: 0, y: 0, z: 0, w: 1 }, + emitterShouldTrail: true, + particleRadius: 0.25, + radiusStart: 0, + radiusSpread: 0, + particleColor: { + red: 255, + green: 255, + blue: 255 + }, + colorSpread: { + red: 0, + green: 0, + blue: 0 + }, + alpha: 0, + alphaStart: 1, + alphaSpread: 0, + emitAcceleration: { + x: 0, + y: 2.5, + z: 0 + }, + accelerationSpread: { + x: 0, + y: 0, + z: 0 + }, + particleSpin: 0, + spinSpread: 0, + rotateWithEntity: false, + polarStart: 0, + polarFinish: Math.PI, + azimuthStart: -Math.PI, + azimuthFinish: Math.PI + }, + Light: { + color: { red: 255, green: 255, blue: 255 }, + intensity: 5.0, + dimensions: DEFAULT_LIGHT_DIMENSIONS, + falloffRadius: 1.0, + isSpotlight: false, + exponent: 1.0, + cutoff: 75.0, + }, +}; + +var toolBar = (function () { + var EDIT_SETTING = "io.highfidelity.isEditing"; // for communication with other scripts + var that = {}, + toolBar, + activeButton = null, + systemToolbar = null, + dialogWindow = null, + tablet = null; + + function createNewEntity(requestedProperties) { + var dimensions = requestedProperties.dimensions ? requestedProperties.dimensions : DEFAULT_DIMENSIONS; + var position = getPositionToCreateEntity(); + var entityID = null; + + var properties = {}; + + copyProperties(properties, DEFAULT_ENTITY_PROPERTIES.All); + + var type = requestedProperties.type; + if (type === "Box" || type === "Sphere") { + copyProperties(properties, DEFAULT_ENTITY_PROPERTIES.Shape); + } else { + copyProperties(properties, DEFAULT_ENTITY_PROPERTIES[type]); + } + + // We apply the requested properties first so that they take priority over any default properties. + copyProperties(properties, requestedProperties); + + if (properties.dynamic) { + copyProperties(properties, DEFAULT_DYNAMIC_PROPERTIES); + } else { + copyProperties(properties, DEFAULT_NON_DYNAMIC_PROPERTIES); + } + + + if (position !== null && position !== undefined) { + var direction; + if (Camera.mode === "entity" || Camera.mode === "independent") { + direction = Camera.orientation; + } else { + direction = MyAvatar.orientation; + } + direction = Vec3.multiplyQbyV(direction, Vec3.UNIT_Z); + + var PRE_ADJUST_ENTITY_TYPES = ["Box", "Sphere", "Shape", "Text", "Image", "Web", "Material"]; + if (PRE_ADJUST_ENTITY_TYPES.indexOf(properties.type) !== -1) { + + // Adjust position of entity per bounding box prior to creating it. + var registration = properties.registration; + if (registration === undefined) { + var DEFAULT_REGISTRATION = { x: 0.5, y: 0.5, z: 0.5 }; + registration = DEFAULT_REGISTRATION; + } + + var orientation = properties.orientation; + if (orientation === undefined) { + properties.orientation = MyAvatar.orientation; + var DEFAULT_ORIENTATION = properties.orientation; + orientation = DEFAULT_ORIENTATION; + } else { + // If the orientation is already defined, we perform the corresponding rotation assuming that + // our start referential is the avatar referential. + properties.orientation = Quat.multiply(MyAvatar.orientation, properties.orientation); + var DEFAULT_ORIENTATION = properties.orientation; + orientation = DEFAULT_ORIENTATION; + } + + position = adjustPositionPerBoundingBox(position, direction, registration, dimensions, orientation); + } + + position = grid.snapToSurface(grid.snapToGrid(position, false, dimensions), dimensions); + properties.position = position; + + if (!properties.grab) { + properties.grab = {}; + if (Menu.isOptionChecked(MENU_CREATE_ENTITIES_GRABBABLE) && + !(properties.type === "Zone" || properties.type === "Light" || properties.type === "ParticleEffect")) { + properties.grab.grabbable = true; + } else { + properties.grab.grabbable = false; + } + } + + entityID = Entities.addEntity(properties); + SelectionManager.addEntity(entityID, false, this); + SelectionManager.saveProperties(); + pushCommandForSelections([{ + entityID: entityID, + properties: properties + }], [], true); + + var POST_ADJUST_ENTITY_TYPES = ["Model"]; + if (POST_ADJUST_ENTITY_TYPES.indexOf(properties.type) !== -1) { + // Adjust position of entity per bounding box after it has been created and auto-resized. + var initialDimensions = Entities.getEntityProperties(entityID, ["dimensions"]).dimensions; + var DIMENSIONS_CHECK_INTERVAL = 200; + var MAX_DIMENSIONS_CHECKS = 10; + var dimensionsCheckCount = 0; + var dimensionsCheckFunction = function () { + dimensionsCheckCount++; + var properties = Entities.getEntityProperties(entityID, ["dimensions", "registrationPoint", "rotation"]); + if (!Vec3.equal(properties.dimensions, initialDimensions)) { + position = adjustPositionPerBoundingBox(position, direction, properties.registrationPoint, + properties.dimensions, properties.rotation); + position = grid.snapToSurface(grid.snapToGrid(position, false, properties.dimensions), + properties.dimensions); + Entities.editEntity(entityID, { + position: position + }); + selectionManager._update(false, this); + } else if (dimensionsCheckCount < MAX_DIMENSIONS_CHECKS) { + Script.setTimeout(dimensionsCheckFunction, DIMENSIONS_CHECK_INTERVAL); + } + }; + Script.setTimeout(dimensionsCheckFunction, DIMENSIONS_CHECK_INTERVAL); + } + } else { + Window.notifyEditError("Can't create " + properties.type + ": " + + properties.type + " would be out of bounds."); + } + + selectionManager.clearSelections(this); + entityListTool.sendUpdate(); + selectionManager.setSelections([entityID], this); + + Window.setFocus(); + + return entityID; + } + + function closeExistingDialogWindow() { + if (dialogWindow) { + dialogWindow.close(); + dialogWindow = null; + } + } + + function cleanup() { + that.setActive(false); + if (tablet) { + tablet.removeButton(activeButton); + } + if (systemToolbar) { + systemToolbar.removeButton(EDIT_TOGGLE_BUTTON); + } + } + + var buttonHandlers = {}; // only used to tablet mode + + function addButton(name, handler) { + buttonHandlers[name] = handler; + } + + var SHAPE_TYPE_NONE = 0; + var SHAPE_TYPE_SIMPLE_HULL = 1; + var SHAPE_TYPE_SIMPLE_COMPOUND = 2; + var SHAPE_TYPE_STATIC_MESH = 3; + var SHAPE_TYPE_BOX = 4; + var SHAPE_TYPE_SPHERE = 5; + var DYNAMIC_DEFAULT = false; + + var MATERIAL_MODE_UV = 0; + var MATERIAL_MODE_PROJECTED = 1; + + function handleNewModelDialogResult(result) { + if (result) { + var url = result.url; + var shapeType; + switch (result.collisionShapeIndex) { + case SHAPE_TYPE_SIMPLE_HULL: + shapeType = "simple-hull"; + break; + case SHAPE_TYPE_SIMPLE_COMPOUND: + shapeType = "simple-compound"; + break; + case SHAPE_TYPE_STATIC_MESH: + shapeType = "static-mesh"; + break; + case SHAPE_TYPE_BOX: + shapeType = "box"; + break; + case SHAPE_TYPE_SPHERE: + shapeType = "sphere"; + break; + default: + shapeType = "none"; + } + + var dynamic = result.dynamic !== null ? result.dynamic : DYNAMIC_DEFAULT; + if (shapeType === "static-mesh" && dynamic) { + // The prompt should prevent this case + print("Error: model cannot be both static mesh and dynamic. This should never happen."); + } else if (url) { + createNewEntity({ + type: "Model", + modelURL: url, + shapeType: shapeType, + grab: { + grabbable: result.grabbable + }, + dynamic: dynamic, + }); + } + } + } + + function handleNewMaterialDialogResult(result) { + if (result) { + var materialURL = result.textInput; + //var materialMappingMode; + //switch (result.comboBox) { + // case MATERIAL_MODE_PROJECTED: + // materialMappingMode = "projected"; + // break; + // default: + // shapeType = "uv"; + //} + var materialData = ""; + if (materialURL.startsWith("materialData")) { + materialData = JSON.stringify({ + "materials": {} + }); + } + + var DEFAULT_LAYERED_MATERIAL_PRIORITY = 1; + if (materialURL) { + createNewEntity({ + type: "Material", + materialURL: materialURL, + //materialMappingMode: materialMappingMode, + priority: DEFAULT_LAYERED_MATERIAL_PRIORITY, + materialData: materialData + }); + } + } + } + + function fromQml(message) { // messages are {method, params}, like json-rpc. See also sendToQml. + var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + tablet.popFromStack(); + switch (message.method) { + case "newModelDialogAdd": + handleNewModelDialogResult(message.params); + closeExistingDialogWindow(); + break; + case "newModelDialogCancel": + closeExistingDialogWindow(); + break; + case "newEntityButtonClicked": + buttonHandlers[message.params.buttonName](); + break; + case "newMaterialDialogAdd": + handleNewMaterialDialogResult(message.params); + closeExistingDialogWindow(); + break; + case "newMaterialDialogCancel": + closeExistingDialogWindow(); + break; + } + } + + var entitiesToDelete = []; + var deletedEntityTimer = null; + var DELETE_ENTITY_TIMER_TIMEOUT = 100; + + function checkDeletedEntityAndUpdate(entityID) { + // Allow for multiple entity deletes before updating the entities selected. + entitiesToDelete.push(entityID); + if (deletedEntityTimer !== null) { + Script.clearTimeout(deletedEntityTimer); + } + deletedEntityTimer = Script.setTimeout(function () { + if (entitiesToDelete.length > 0) { + selectionManager.removeEntities(entitiesToDelete, this); + } + entityListTool.removeEntities(entitiesToDelete, selectionManager.selections); + entitiesToDelete = []; + deletedEntityTimer = null; + }, DELETE_ENTITY_TIMER_TIMEOUT); + } + + function initialize() { + Script.scriptEnding.connect(cleanup); + Window.domainChanged.connect(function () { + if (isActive) { + tablet.gotoHomeScreen(); + } + that.setActive(false); + that.clearEntityList(); + checkEditPermissionsAndUpdate(); + }); + + HMD.displayModeChanged.connect(function() { + if (isActive) { + tablet.gotoHomeScreen(); + } + that.setActive(false); + }); + + Entities.canAdjustLocksChanged.connect(function (canAdjustLocks) { + if (isActive && !canAdjustLocks) { + that.setActive(false); + } + checkEditPermissionsAndUpdate(); + }); + + Entities.canRezChanged.connect(checkEditPermissionsAndUpdate); + Entities.canRezTmpChanged.connect(checkEditPermissionsAndUpdate); + Entities.canRezCertifiedChanged.connect(checkEditPermissionsAndUpdate); + Entities.canRezTmpCertifiedChanged.connect(checkEditPermissionsAndUpdate); + var hasRezPermissions = (Entities.canRez() || Entities.canRezTmp() || Entities.canRezCertified() || Entities.canRezTmpCertified()); + + Entities.deletingEntity.connect(checkDeletedEntityAndUpdate); + + var createButtonIconRsrc = (hasRezPermissions ? CREATE_ENABLED_ICON : CREATE_DISABLED_ICON); + tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + activeButton = tablet.addButton({ + captionColor: hasRezPermissions ? "#ffffff" : "#888888", + icon: createButtonIconRsrc, + activeIcon: "icons/tablet-icons/edit-a.svg", + text: "CREATE", + sortOrder: 10 + }); + createButton = activeButton; + tablet.screenChanged.connect(function (type, url) { + var isGoingToHomescreenOnDesktop = (!shouldUseEditTabletApp() && + (url === 'hifi/tablet/TabletHome.qml' || url === '')); + if (isActive && (type !== "QML" || url !== Script.resolvePath("create/Edit.qml")) && !isGoingToHomescreenOnDesktop) { + that.setActive(false); + } + }); + tablet.fromQml.connect(fromQml); + createToolsWindow.fromQml.addListener(fromQml); + + createButton.clicked.connect(function() { + if ( ! (Entities.canRez() || Entities.canRezTmp() || Entities.canRezCertified() || Entities.canRezTmpCertified()) ) { + Window.notifyEditError(INSUFFICIENT_PERMISSIONS_ERROR_MSG); + return; + } + + that.toggle(); + }); + + addButton("importEntitiesButton", function() { + Window.browseChanged.connect(onFileOpenChanged); + Window.browseAsync("Select Model to Import", "", "*.json"); + }); + + addButton("openAssetBrowserButton", function() { + Window.showAssetServer(); + }); + function createNewEntityDialogButtonCallback(entityType) { + return function() { + if (shouldUseEditTabletApp()) { + // tablet version of new-model dialog + var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + tablet.pushOntoStack(Script.resolvePath("create/New" + entityType + "Dialog.qml")); + } else { + closeExistingDialogWindow(); + var qmlPath = Script.resolvePath("create/New" + entityType + "Window.qml"); + var DIALOG_WINDOW_SIZE = { x: 500, y: 300 }; + dialogWindow = Desktop.createWindow(qmlPath, { + title: "New " + entityType + " Entity", + flags: Desktop.ALWAYS_ON_TOP | Desktop.CLOSE_BUTTON_HIDES, + presentationMode: Desktop.PresentationMode.NATIVE, + size: DIALOG_WINDOW_SIZE, + visible: true + }); + dialogWindow.fromQml.connect(fromQml); + } + }; + } + + addButton("newModelButton", createNewEntityDialogButtonCallback("Model")); + + addButton("newShapeButton", function () { + createNewEntity({ + type: "Shape", + shape: "Cube", + }); + }); + + addButton("newLightButton", function () { + createNewEntity({ + type: "Light", + }); + }); + + addButton("newTextButton", function () { + createNewEntity({ + type: "Text", + }); + }); + + addButton("newImageButton", function () { + createNewEntity({ + type: "Image", + }); + }); + + addButton("newWebButton", function () { + createNewEntity({ + type: "Web", + }); + }); + + addButton("newZoneButton", function () { + createNewEntity({ + type: "Zone", + }); + }); + + addButton("newParticleButton", function () { + createNewEntity({ + type: "ParticleEffect", + }); + }); + + addButton("newMaterialButton", createNewEntityDialogButtonCallback("Material")); + + var deactivateCreateIfDesktopWindowsHidden = function() { + if (!shouldUseEditTabletApp() && !entityListTool.isVisible() && !createToolsWindow.isVisible()) { + that.setActive(false); + } + }; + entityListTool.interactiveWindowHidden.addListener(this, deactivateCreateIfDesktopWindowsHidden); + createToolsWindow.interactiveWindowHidden.addListener(this, deactivateCreateIfDesktopWindowsHidden); + + that.setActive(false); + } + + that.clearEntityList = function () { + entityListTool.clearEntityList(); + }; + + that.toggle = function () { + that.setActive(!isActive); + if (!isActive) { + tablet.gotoHomeScreen(); + } + }; + + that.setActive = function (active) { + ContextOverlay.enabled = !active; + Settings.setValue(EDIT_SETTING, active); + if (active) { + Controller.captureEntityClickEvents(); + } else { + Controller.releaseEntityClickEvents(); + + closeExistingDialogWindow(); + } + if (active === isActive) { + return; + } + if (active && !Entities.canRez() && !Entities.canRezTmp() && !Entities.canRezCertified() && !Entities.canRezTmpCertified()) { + Window.notifyEditError(INSUFFICIENT_PERMISSIONS_ERROR_MSG); + return; + } + Messages.sendLocalMessage("edit-events", JSON.stringify({ + enabled: active + })); + isActive = active; + activeButton.editProperties({isActive: isActive}); + undoHistory.setEnabled(isActive); + + var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + + if (!isActive) { + entityListTool.setVisible(false); + gridTool.setVisible(false); + grid.setEnabled(false); + propertiesTool.setVisible(false); + selectionManager.clearSelections(this); + cameraManager.disable(); + selectionDisplay.disableTriggerMapping(); + tablet.landscape = false; + Controller.disableMapping(CONTROLLER_MAPPING_NAME); + } else { + if (shouldUseEditTabletApp()) { + tablet.loadQMLSource(Script.resolvePath("create/Edit.qml"), true); + } else { + // make other apps inactive while in desktop mode + tablet.gotoHomeScreen(); + } + UserActivityLogger.enabledEdit(); + entityListTool.setVisible(true); + entityListTool.sendUpdate(); + gridTool.setVisible(true); + grid.setEnabled(true); + propertiesTool.setVisible(true); + selectionDisplay.enableTriggerMapping(); + print("starting tablet in landscape mode"); + tablet.landscape = true; + Controller.enableMapping(CONTROLLER_MAPPING_NAME); + // Not sure what the following was meant to accomplish, but it currently causes + // everybody else to think that Interface has lost focus overall. fogbugzid:558 + // Window.setFocus(); + } + entityIconOverlayManager.setVisible(isActive && Menu.isOptionChecked(MENU_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE)); + Entities.setDrawZoneBoundaries(isActive && Menu.isOptionChecked(MENU_SHOW_ZONES_IN_EDIT_MODE)); + }; + + initialize(); + return that; +})(); + +var selectedEntityID; +var orientation; +var intersection; + + +function rayPlaneIntersection(pickRay, point, normal) { // + // + // This version of the test returns the intersection of a line with a plane + // + var collides = Vec3.dot(pickRay.direction, normal); + + var d = -Vec3.dot(point, normal); + var t = -(Vec3.dot(pickRay.origin, normal) + d) / collides; + + return Vec3.sum(pickRay.origin, Vec3.multiply(pickRay.direction, t)); +} + +function rayPlaneIntersection2(pickRay, point, normal) { + // + // This version of the test returns false if the ray is directed away from the plane + // + var collides = Vec3.dot(pickRay.direction, normal); + var d = -Vec3.dot(point, normal); + var t = -(Vec3.dot(pickRay.origin, normal) + d) / collides; + if (t < 0.0) { + return false; + } else { + return Vec3.sum(pickRay.origin, Vec3.multiply(pickRay.direction, t)); + } +} + +function findClickedEntity(event) { + var pickZones = event.isControl; + + if (pickZones) { + Entities.setZonesArePickable(true); + } + + var pickRay = Camera.computePickRay(event.x, event.y); + var tabletIDs = getMainTabletIDs(); + if (tabletIDs.length > 0) { + var overlayResult = Overlays.findRayIntersection(pickRay, true, tabletIDs); + if (overlayResult.intersects) { + return null; + } + } + + var entityResult = Entities.findRayIntersection(pickRay, true); // want precision picking + var iconResult = entityIconOverlayManager.findRayIntersection(pickRay); + iconResult.accurate = true; + + if (pickZones) { + Entities.setZonesArePickable(false); + } + + var result; + + if (iconResult.intersects) { + result = iconResult; + } else if (entityResult.intersects) { + result = entityResult; + } else { + return null; + } + + if (!result.accurate) { + return null; + } + + var foundEntity = result.entityID; + return { + pickRay: pickRay, + entityID: foundEntity, + intersection: result.intersection + }; +} + +// Handles selections on overlays while in edit mode by querying entities from +// entityIconOverlayManager. +function handleOverlaySelectionToolUpdates(channel, message, sender) { + var wantDebug = false; + if (sender !== MyAvatar.sessionUUID || channel !== 'entityToolUpdates') + return; + + var data = JSON.parse(message); + + if (data.method === "selectOverlay") { + if (!selectionDisplay.triggered() || selectionDisplay.triggeredHand === data.hand) { + if (wantDebug) { + print("setting selection to overlay " + data.overlayID); + } + var entity = entityIconOverlayManager.findEntity(data.overlayID); + + if (entity !== null) { + selectionManager.setSelections([entity], this); + } + } + } +} + +function handleMessagesReceived(channel, message, sender) { + switch( channel ){ + case 'entityToolUpdates': { + handleOverlaySelectionToolUpdates( channel, message, sender ); + break; + } + default: { + return; + } + } +} + +Messages.subscribe("entityToolUpdates"); +Messages.messageReceived.connect(handleMessagesReceived); + +var mouseHasMovedSincePress = false; +var mousePressStartTime = 0; +var mousePressStartPosition = { + x: 0, + y: 0 +}; +var mouseDown = false; + +function mousePressEvent(event) { + mouseDown = true; + mousePressStartPosition = { + x: event.x, + y: event.y + }; + mousePressStartTime = Date.now(); + mouseHasMovedSincePress = false; + mouseCapturedByTool = false; + + if (propertyMenu.mousePressEvent(event) || progressDialog.mousePressEvent(event)) { + mouseCapturedByTool = true; + return; + } + if (isActive) { + if (cameraManager.mousePressEvent(event) || selectionDisplay.mousePressEvent(event)) { + // Event handled; do nothing. + return; + } + } +} + +var mouseCapturedByTool = false; +var lastMousePosition = null; +var CLICK_TIME_THRESHOLD = 500 * 1000; // 500 ms +var CLICK_MOVE_DISTANCE_THRESHOLD = 20; +var IDLE_MOUSE_TIMEOUT = 200; + +var lastMouseMoveEvent = null; + +function mouseMoveEventBuffered(event) { + lastMouseMoveEvent = event; +} + +function mouseMove(event) { + if (mouseDown && !mouseHasMovedSincePress) { + var timeSincePressMicro = Date.now() - mousePressStartTime; + + var dX = mousePressStartPosition.x - event.x; + var dY = mousePressStartPosition.y - event.y; + var sqDist = (dX * dX) + (dY * dY); + + // If less than CLICK_TIME_THRESHOLD has passed since the mouse click AND the mouse has moved + // less than CLICK_MOVE_DISTANCE_THRESHOLD distance, then don't register this as a mouse move + // yet. The goal is to provide mouse clicks that are more lenient to small movements. + if (timeSincePressMicro < CLICK_TIME_THRESHOLD && sqDist < CLICK_MOVE_DISTANCE_THRESHOLD) { + return; + } + mouseHasMovedSincePress = true; + } + + if (!isActive) { + return; + } + + // allow the selectionDisplay and cameraManager to handle the event first, if it doesn't handle it, then do our own thing + if (selectionDisplay.mouseMoveEvent(event) || propertyMenu.mouseMoveEvent(event) || cameraManager.mouseMoveEvent(event)) { + return; + } + + lastMousePosition = { + x: event.x, + y: event.y + }; +} + +function mouseReleaseEvent(event) { + mouseDown = false; + + if (lastMouseMoveEvent) { + mouseMove(lastMouseMoveEvent); + lastMouseMoveEvent = null; + } + if (propertyMenu.mouseReleaseEvent(event)) { + return true; + } + if (isActive && selectionManager.hasSelection()) { + tooltip.show(false); + } + if (mouseCapturedByTool) { + + return; + } + + cameraManager.mouseReleaseEvent(event); + + if (!mouseHasMovedSincePress) { + mouseClickEvent(event); + } +} + +function wasTabletOrEditHandleClicked(event) { + var rayPick = Camera.computePickRay(event.x, event.y); + var result = Overlays.findRayIntersection(rayPick, true); + if (result.intersects) { + var overlayID = result.overlayID; + var tabletIDs = getMainTabletIDs(); + if (tabletIDs.indexOf(overlayID) >= 0) { + return true; + } else if (selectionDisplay.isEditHandle(overlayID)) { + return true; + } + } + return false; +} + +function mouseClickEvent(event) { + var wantDebug = false; + var result, properties, tabletClicked; + if (isActive && event.isLeftButton) { + result = findClickedEntity(event); + var tabletOrEditHandleClicked = wasTabletOrEditHandleClicked(event); + if (tabletOrEditHandleClicked) { + return; + } + + if (result === null || result === undefined) { + if (!event.isShifted) { + selectionManager.clearSelections(this); + } + return; + } + toolBar.setActive(true); + var pickRay = result.pickRay; + var foundEntity = result.entityID; + if (HMD.tabletID && foundEntity === HMD.tabletID) { + return; + } + properties = Entities.getEntityProperties(foundEntity); + var halfDiagonal = Vec3.length(properties.dimensions) / 2.0; + + if (wantDebug) { + print("Checking properties: " + properties.id + " " + " - Half Diagonal:" + halfDiagonal); + } + // P P - Model + // /| A - Palm + // / | d B - unit vector toward tip + // / | X - base of the perpendicular line + // A---X----->B d - distance fom axis + // x x - distance from A + // + // |X-A| = (P-A).B + // X === A + ((P-A).B)B + // d = |P-X| + + var A = pickRay.origin; + var B = Vec3.normalize(pickRay.direction); + var P = properties.position; + + var x = Vec3.dot(Vec3.subtract(P, A), B); + + var angularSize = 2 * Math.atan(halfDiagonal / Vec3.distance(Camera.getPosition(), properties.position)) * + 180 / Math.PI; + + var sizeOK = (allowLargeModels || angularSize < MAX_ANGULAR_SIZE) && + (allowSmallModels || angularSize > MIN_ANGULAR_SIZE); + + if (0 < x && sizeOK) { + selectedEntityID = foundEntity; + orientation = MyAvatar.orientation; + intersection = rayPlaneIntersection(pickRay, P, Quat.getForward(orientation)); + + if (!event.isShifted) { + selectionManager.setSelections([foundEntity], this); + } else { + selectionManager.addEntity(foundEntity, true, this); + } + selectionManager.saveProperties(); + + if (wantDebug) { + print("Model selected: " + foundEntity); + } + selectionDisplay.select(selectedEntityID, event); + + if (Menu.isOptionChecked(MENU_AUTO_FOCUS_ON_SELECT)) { + cameraManager.enable(); + cameraManager.focus(selectionManager.worldPosition, + selectionManager.worldDimensions, + Menu.isOptionChecked(MENU_EASE_ON_FOCUS)); + } + } + } else if (event.isRightButton) { + result = findClickedEntity(event); + if (result) { + if (SHOULD_SHOW_PROPERTY_MENU !== true) { + return; + } + properties = Entities.getEntityProperties(result.entityID); + if (properties.marketplaceID) { + propertyMenu.marketplaceID = properties.marketplaceID; + propertyMenu.updateMenuItemText(showMenuItem, "Show in Marketplace"); + } else { + propertyMenu.marketplaceID = null; + propertyMenu.updateMenuItemText(showMenuItem, "No marketplace info"); + } + propertyMenu.setPosition(event.x, event.y); + propertyMenu.show(); + } else { + propertyMenu.hide(); + } + } +} + +Controller.mousePressEvent.connect(mousePressEvent); +Controller.mouseMoveEvent.connect(mouseMoveEventBuffered); +Controller.mouseReleaseEvent.connect(mouseReleaseEvent); + + +// In order for editVoxels and editModels to play nice together, they each check to see if a "delete" menu item already +// exists. If it doesn't they add it. If it does they don't. They also only delete the menu item if they were the one that +// added it. +var modelMenuAddedDelete = false; +var originalLightsArePickable = Entities.getLightsArePickable(); + +function setupModelMenus() { + // adj our menuitems + Menu.addMenuItem({ + menuName: "Edit", + menuItemName: "Undo", + shortcutKey: 'Ctrl+Z', + position: 0, + }); + Menu.addMenuItem({ + menuName: "Edit", + menuItemName: "Redo", + shortcutKey: 'Ctrl+Y', + position: 1, + }); + + Menu.addMenuItem({ + menuName: "Edit", + menuItemName: "Entities", + isSeparator: true + }); + if (!Menu.menuItemExists("Edit", "Delete")) { + Menu.addMenuItem({ + menuName: "Edit", + menuItemName: "Delete", + shortcutKeyEvent: { + text: "delete" + }, + afterItem: "Entities", + }); + modelMenuAddedDelete = true; + } + + Menu.addMenuItem({ + menuName: "Edit", + menuItemName: "Parent Entity to Last", + afterItem: "Entities" + }); + + Menu.addMenuItem({ + menuName: "Edit", + menuItemName: "Unparent Entity", + afterItem: "Parent Entity to Last" + }); + + Menu.addMenuItem({ + menuName: GRABBABLE_ENTITIES_MENU_CATEGORY, + menuItemName: MENU_CREATE_ENTITIES_GRABBABLE, + afterItem: "Unparent Entity", + isCheckable: true, + isChecked: Settings.getValue(SETTING_EDIT_PREFIX + MENU_CREATE_ENTITIES_GRABBABLE, true) + }); + + Menu.addMenuItem({ + menuName: "Edit", + menuItemName: MENU_ALLOW_SELECTION_LARGE, + afterItem: MENU_CREATE_ENTITIES_GRABBABLE, + isCheckable: true, + isChecked: Settings.getValue(SETTING_EDIT_PREFIX + MENU_ALLOW_SELECTION_LARGE, true) + }); + Menu.addMenuItem({ + menuName: "Edit", + menuItemName: MENU_ALLOW_SELECTION_SMALL, + afterItem: MENU_ALLOW_SELECTION_LARGE, + isCheckable: true, + isChecked: Settings.getValue(SETTING_EDIT_PREFIX + MENU_ALLOW_SELECTION_SMALL, true) + }); + Menu.addMenuItem({ + menuName: "Edit", + menuItemName: MENU_ALLOW_SELECTION_LIGHTS, + afterItem: MENU_ALLOW_SELECTION_SMALL, + isCheckable: true, + isChecked: Settings.getValue(SETTING_EDIT_PREFIX + MENU_ALLOW_SELECTION_LIGHTS, false) + }); + Menu.addMenuItem({ + menuName: "Edit", + menuItemName: "Select All Entities In Box", + afterItem: "Allow Selecting of Lights" + }); + Menu.addMenuItem({ + menuName: "Edit", + menuItemName: "Select All Entities Touching Box", + afterItem: "Select All Entities In Box" + }); + + Menu.addMenuItem({ + menuName: "Edit", + menuItemName: "Export Entities", + afterItem: "Entities" + }); + Menu.addMenuItem({ + menuName: "Edit", + menuItemName: "Import Entities", + afterItem: "Export Entities" + }); + Menu.addMenuItem({ + menuName: "Edit", + menuItemName: "Import Entities from URL", + afterItem: "Import Entities" + }); + + Menu.addMenuItem({ + menuName: "Edit", + menuItemName: MENU_AUTO_FOCUS_ON_SELECT, + isCheckable: true, + isChecked: Settings.getValue(SETTING_AUTO_FOCUS_ON_SELECT) === "true" + }); + Menu.addMenuItem({ + menuName: "Edit", + menuItemName: MENU_EASE_ON_FOCUS, + afterItem: MENU_AUTO_FOCUS_ON_SELECT, + isCheckable: true, + isChecked: Settings.getValue(SETTING_EASE_ON_FOCUS) === "true" + }); + Menu.addMenuItem({ + menuName: "Edit", + menuItemName: MENU_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE, + afterItem: MENU_EASE_ON_FOCUS, + isCheckable: true, + isChecked: Settings.getValue(SETTING_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE) !== "false" + }); + Menu.addMenuItem({ + menuName: "Edit", + menuItemName: MENU_SHOW_ZONES_IN_EDIT_MODE, + afterItem: MENU_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE, + isCheckable: true, + isChecked: Settings.getValue(SETTING_SHOW_ZONES_IN_EDIT_MODE) !== "false" + }); + + Entities.setLightsArePickable(false); +} + +setupModelMenus(); // do this when first running our script. + +function cleanupModelMenus() { + Menu.removeMenuItem("Edit", "Undo"); + Menu.removeMenuItem("Edit", "Redo"); + + Menu.removeSeparator("Edit", "Entities"); + if (modelMenuAddedDelete) { + // delete our menuitems + Menu.removeMenuItem("Edit", "Delete"); + } + + Menu.removeMenuItem("Edit", "Parent Entity to Last"); + Menu.removeMenuItem("Edit", "Unparent Entity"); + Menu.removeMenuItem("Edit", "Allow Selecting of Large Models"); + Menu.removeMenuItem("Edit", "Allow Selecting of Small Models"); + Menu.removeMenuItem("Edit", "Allow Selecting of Lights"); + Menu.removeMenuItem("Edit", "Select All Entities In Box"); + Menu.removeMenuItem("Edit", "Select All Entities Touching Box"); + + Menu.removeMenuItem("Edit", "Export Entities"); + Menu.removeMenuItem("Edit", "Import Entities"); + Menu.removeMenuItem("Edit", "Import Entities from URL"); + + Menu.removeMenuItem("Edit", MENU_AUTO_FOCUS_ON_SELECT); + Menu.removeMenuItem("Edit", MENU_EASE_ON_FOCUS); + Menu.removeMenuItem("Edit", MENU_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE); + Menu.removeMenuItem("Edit", MENU_SHOW_ZONES_IN_EDIT_MODE); + Menu.removeMenuItem("Edit", MENU_CREATE_ENTITIES_GRABBABLE); +} + +Script.scriptEnding.connect(function () { + toolBar.setActive(false); + Settings.setValue(SETTING_AUTO_FOCUS_ON_SELECT, Menu.isOptionChecked(MENU_AUTO_FOCUS_ON_SELECT)); + Settings.setValue(SETTING_EASE_ON_FOCUS, Menu.isOptionChecked(MENU_EASE_ON_FOCUS)); + Settings.setValue(SETTING_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE, Menu.isOptionChecked(MENU_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE)); + Settings.setValue(SETTING_SHOW_ZONES_IN_EDIT_MODE, Menu.isOptionChecked(MENU_SHOW_ZONES_IN_EDIT_MODE)); + + Settings.setValue(SETTING_EDIT_PREFIX + MENU_ALLOW_SELECTION_LARGE, Menu.isOptionChecked(MENU_ALLOW_SELECTION_LARGE)); + Settings.setValue(SETTING_EDIT_PREFIX + MENU_ALLOW_SELECTION_SMALL, Menu.isOptionChecked(MENU_ALLOW_SELECTION_SMALL)); + Settings.setValue(SETTING_EDIT_PREFIX + MENU_ALLOW_SELECTION_LIGHTS, Menu.isOptionChecked(MENU_ALLOW_SELECTION_LIGHTS)); + + + progressDialog.cleanup(); + cleanupModelMenus(); + tooltip.cleanup(); + selectionDisplay.cleanup(); + entityShapeVisualizer.cleanup(); + Entities.setLightsArePickable(originalLightsArePickable); + + Overlays.deleteOverlay(importingSVOImageOverlay); + Overlays.deleteOverlay(importingSVOTextOverlay); + + Controller.keyReleaseEvent.disconnect(keyReleaseEvent); + Controller.keyPressEvent.disconnect(keyPressEvent); + + Controller.mousePressEvent.disconnect(mousePressEvent); + Controller.mouseMoveEvent.disconnect(mouseMoveEventBuffered); + Controller.mouseReleaseEvent.disconnect(mouseReleaseEvent); + + Messages.messageReceived.disconnect(handleMessagesReceived); + Messages.unsubscribe("entityToolUpdates"); + createButton = null; +}); + +var lastOrientation = null; +var lastPosition = null; + +// Do some stuff regularly, like check for placement of various overlays +Script.update.connect(function (deltaTime) { + progressDialog.move(); + selectionDisplay.checkControllerMove(); + var dOrientation = Math.abs(Quat.dot(Camera.orientation, lastOrientation) - 1); + var dPosition = Vec3.distance(Camera.position, lastPosition); + if (dOrientation > 0.001 || dPosition > 0.001) { + propertyMenu.hide(); + lastOrientation = Camera.orientation; + lastPosition = Camera.position; + } + if (lastMouseMoveEvent) { + mouseMove(lastMouseMoveEvent); + lastMouseMoveEvent = null; + } +}); + +function insideBox(center, dimensions, point) { + return (Math.abs(point.x - center.x) <= (dimensions.x / 2.0)) && + (Math.abs(point.y - center.y) <= (dimensions.y / 2.0)) && + (Math.abs(point.z - center.z) <= (dimensions.z / 2.0)); +} + +function selectAllEntitiesInCurrentSelectionBox(keepIfTouching) { + if (selectionManager.hasSelection()) { + // Get all entities touching the bounding box of the current selection + var boundingBoxCorner = Vec3.subtract(selectionManager.worldPosition, + Vec3.multiply(selectionManager.worldDimensions, 0.5)); + var entities = Entities.findEntitiesInBox(boundingBoxCorner, selectionManager.worldDimensions); + + if (!keepIfTouching) { + var isValid; + if (selectionManager.localPosition === null || selectionManager.localPosition === undefined) { + isValid = function (position) { + return insideBox(selectionManager.worldPosition, selectionManager.worldDimensions, position); + }; + } else { + isValid = function (position) { + var localPosition = Vec3.multiplyQbyV(Quat.inverse(selectionManager.localRotation), + Vec3.subtract(position, + selectionManager.localPosition)); + return insideBox(Vec3.ZERO, selectionManager.localDimensions, localPosition); + }; + } + for (var i = 0; i < entities.length; ++i) { + var properties = Entities.getEntityProperties(entities[i]); + if (!isValid(properties.position)) { + entities.splice(i, 1); + --i; + } + } + } + selectionManager.setSelections(entities, this); + } +} + +function sortSelectedEntities(selected) { + var sortedEntities = selected.slice(); + var begin = 0; + while (begin < sortedEntities.length) { + var elementRemoved = false; + var next = begin + 1; + while (next < sortedEntities.length) { + var beginID = sortedEntities[begin]; + var nextID = sortedEntities[next]; + + if (Entities.isChildOfParent(beginID, nextID)) { + sortedEntities[begin] = nextID; + sortedEntities[next] = beginID; + sortedEntities.splice(next, 1); + elementRemoved = true; + break; + } else if (Entities.isChildOfParent(nextID, beginID)) { + sortedEntities.splice(next, 1); + elementRemoved = true; + break; + } + next++; + } + if (!elementRemoved) { + begin++; + } + } + return sortedEntities; +} + +function recursiveDelete(entities, childrenList, deletedIDs, entityHostType) { + var wantDebug = false; + var entitiesLength = entities.length; + var initialPropertySets = Entities.getMultipleEntityProperties(entities); + var entityHostTypes = Entities.getMultipleEntityProperties(entities, 'entityHostType'); + for (var i = 0; i < entitiesLength; ++i) { + var entityID = entities[i]; + + if (entityHostTypes[i].entityHostType !== entityHostType) { + if (wantDebug) { + console.log("Skipping deletion of entity " + entityID + " with conflicting entityHostType: " + + entityHostTypes[i].entityHostType + ", expected: " + entityHostType); + } + continue; + } + + var children = Entities.getChildrenIDs(entityID); + var grandchildrenList = []; + recursiveDelete(children, grandchildrenList, deletedIDs, entityHostType); + childrenList.push({ + entityID: entityID, + properties: initialPropertySets[i], + children: grandchildrenList + }); + deletedIDs.push(entityID); + Entities.deleteEntity(entityID); + } +} + +function unparentSelectedEntities() { + if (SelectionManager.hasSelection()) { + var selectedEntities = selectionManager.selections; + var parentCheck = false; + + if (selectedEntities.length < 1) { + Window.notifyEditError("You must have an entity selected in order to unparent it."); + return; + } + selectedEntities.forEach(function (id, index) { + var parentId = Entities.getEntityProperties(id, ["parentID"]).parentID; + if (parentId !== null && parentId.length > 0 && parentId !== Uuid.NULL) { + parentCheck = true; + } + Entities.editEntity(id, {parentID: null}); + return true; + }); + if (parentCheck) { + if (selectedEntities.length > 1) { + Window.notify("Entities unparented"); + } else { + Window.notify("Entity unparented"); + } + } else { + if (selectedEntities.length > 1) { + Window.notify("Selected Entities have no parents"); + } else { + Window.notify("Selected Entity does not have a parent"); + } + } + } else { + Window.notifyEditError("You have nothing selected to unparent"); + } +} +function parentSelectedEntities() { + if (SelectionManager.hasSelection()) { + var selectedEntities = selectionManager.selections; + if (selectedEntities.length <= 1) { + Window.notifyEditError("You must have multiple entities selected in order to parent them"); + return; + } + var parentCheck = false; + var lastEntityId = selectedEntities[selectedEntities.length - 1]; + selectedEntities.forEach(function (id, index) { + if (lastEntityId !== id) { + var parentId = Entities.getEntityProperties(id, ["parentID"]).parentID; + if (parentId !== lastEntityId) { + parentCheck = true; + } + Entities.editEntity(id, {parentID: lastEntityId}); + } + }); + + if (parentCheck) { + Window.notify("Entities parented"); + } else { + Window.notify("Entities are already parented to last"); + } + } else { + Window.notifyEditError("You have nothing selected to parent"); + } +} +function deleteSelectedEntities() { + if (SelectionManager.hasSelection()) { + var deletedIDs = []; + + SelectionManager.saveProperties(); + var savedProperties = []; + var newSortedSelection = sortSelectedEntities(selectionManager.selections); + var entityHostTypes = Entities.getMultipleEntityProperties(newSortedSelection, 'entityHostType'); + for (var i = 0; i < newSortedSelection.length; ++i) { + var entityID = newSortedSelection[i]; + var initialProperties = SelectionManager.savedProperties[entityID]; + if (initialProperties.locked || + (initialProperties.avatarEntity && initialProperties.owningAvatarID !== MyAvatar.sessionUUID)) { + continue; + } + var children = Entities.getChildrenIDs(entityID); + var childList = []; + recursiveDelete(children, childList, deletedIDs, entityHostTypes[i].entityHostType); + savedProperties.push({ + entityID: entityID, + properties: initialProperties, + children: childList + }); + deletedIDs.push(entityID); + Entities.deleteEntity(entityID); + } + + if (savedProperties.length > 0) { + SelectionManager.clearSelections(this); + pushCommandForSelections([], savedProperties); + entityListTool.deleteEntities(deletedIDs); + } + } +} + +function toggleSelectedEntitiesLocked() { + if (SelectionManager.hasSelection()) { + var locked = !Entities.getEntityProperties(SelectionManager.selections[0], ["locked"]).locked; + for (var i = 0; i < selectionManager.selections.length; i++) { + var entityID = SelectionManager.selections[i]; + Entities.editEntity(entityID, { + locked: locked + }); + } + entityListTool.sendUpdate(); + selectionManager._update(false, this); + } +} + +function toggleSelectedEntitiesVisible() { + if (SelectionManager.hasSelection()) { + var visible = !Entities.getEntityProperties(SelectionManager.selections[0], ["visible"]).visible; + for (var i = 0; i < selectionManager.selections.length; i++) { + var entityID = SelectionManager.selections[i]; + Entities.editEntity(entityID, { + visible: visible + }); + } + entityListTool.sendUpdate(); + selectionManager._update(false, this); + } +} + +function onFileSaveChanged(filename) { + Window.saveFileChanged.disconnect(onFileSaveChanged); + if (filename !== "") { + var success = Clipboard.exportEntities(filename, selectionManager.selections); + if (!success) { + Window.notifyEditError("Export failed."); + } + } +} + +function onFileOpenChanged(filename) { + // disconnect the event, otherwise the requests will stack up + try { + // Not all calls to onFileOpenChanged() connect an event. + Window.browseChanged.disconnect(onFileOpenChanged); + } catch (e) { + // Ignore. + } + + var importURL = null; + if (filename !== "") { + importURL = filename; + if (!/^(http|https):\/\//.test(filename)) { + importURL = "file:///" + importURL; + } + } + if (importURL) { + if (!isActive && (Entities.canRez() && Entities.canRezTmp() && Entities.canRezCertified() && Entities.canRezTmpCertified())) { + toolBar.toggle(); + } + importSVO(importURL); + } +} + +function onPromptTextChanged(prompt) { + Window.promptTextChanged.disconnect(onPromptTextChanged); + if (prompt !== "") { + if (!isActive && (Entities.canRez() && Entities.canRezTmp() && Entities.canRezCertified() && Entities.canRezTmpCertified())) { + toolBar.toggle(); + } + importSVO(prompt); + } +} + +function handleMenuEvent(menuItem) { + if (menuItem === "Allow Selecting of Small Models") { + allowSmallModels = Menu.isOptionChecked("Allow Selecting of Small Models"); + } else if (menuItem === "Allow Selecting of Large Models") { + allowLargeModels = Menu.isOptionChecked("Allow Selecting of Large Models"); + } else if (menuItem === "Allow Selecting of Lights") { + Entities.setLightsArePickable(Menu.isOptionChecked("Allow Selecting of Lights")); + } else if (menuItem === "Delete") { + deleteSelectedEntities(); + } else if (menuItem === "Undo") { + undoHistory.undo(); + } else if (menuItem === "Redo") { + undoHistory.redo(); + } else if (menuItem === "Parent Entity to Last") { + parentSelectedEntities(); + } else if (menuItem === "Unparent Entity") { + unparentSelectedEntities(); + } else if (menuItem === "Export Entities") { + if (!selectionManager.hasSelection()) { + Window.notifyEditError("No entities have been selected."); + } else { + Window.saveFileChanged.connect(onFileSaveChanged); + Window.saveAsync("Select Where to Save", "", "*.json"); + } + } else if (menuItem === "Import Entities" || menuItem === "Import Entities from URL") { + if (menuItem === "Import Entities") { + Window.browseChanged.connect(onFileOpenChanged); + Window.browseAsync("Select Model to Import", "", "*.json"); + } else { + Window.promptTextChanged.connect(onPromptTextChanged); + Window.promptAsync("URL of SVO to import", ""); + } + } else if (menuItem === "Select All Entities In Box") { + selectAllEntitiesInCurrentSelectionBox(false); + } else if (menuItem === "Select All Entities Touching Box") { + selectAllEntitiesInCurrentSelectionBox(true); + } else if (menuItem === MENU_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE) { + entityIconOverlayManager.setVisible(isActive && Menu.isOptionChecked(MENU_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE)); + } else if (menuItem === MENU_SHOW_ZONES_IN_EDIT_MODE) { + Entities.setDrawZoneBoundaries(isActive && Menu.isOptionChecked(MENU_SHOW_ZONES_IN_EDIT_MODE)); + } else if (menuItem === MENU_CREATE_ENTITIES_GRABBABLE) { + Settings.setValue(SETTING_EDIT_PREFIX + menuItem, Menu.isOptionChecked(menuItem)); + } + tooltip.show(false); +} + +var HALF_TREE_SCALE = 16384; + +function getPositionToCreateEntity(extra) { + var CREATE_DISTANCE = 2; + var position; + var delta = extra !== undefined ? extra : 0; + if (Camera.mode === "entity" || Camera.mode === "independent") { + position = Vec3.sum(Camera.position, Vec3.multiply(Quat.getForward(Camera.orientation), CREATE_DISTANCE + delta)); + } else { + position = Vec3.sum(MyAvatar.position, Vec3.multiply(Quat.getForward(MyAvatar.orientation), CREATE_DISTANCE + delta)); + } + + if (position.x > HALF_TREE_SCALE || position.y > HALF_TREE_SCALE || position.z > HALF_TREE_SCALE) { + return null; + } + return position; +} + +function importSVO(importURL) { + if (!Entities.canRez() && !Entities.canRezTmp() && + !Entities.canRezCertified() && !Entities.canRezTmpCertified()) { + Window.notifyEditError(INSUFFICIENT_PERMISSIONS_IMPORT_ERROR_MSG); + return; + } + + Overlays.editOverlay(importingSVOTextOverlay, { + visible: true + }); + Overlays.editOverlay(importingSVOImageOverlay, { + visible: true + }); + + var success = Clipboard.importEntities(importURL); + + if (success) { + var VERY_LARGE = 10000; + var isLargeImport = Clipboard.getClipboardContentsLargestDimension() >= VERY_LARGE; + var position = Vec3.ZERO; + if (!isLargeImport) { + position = getPositionToCreateEntity(Clipboard.getClipboardContentsLargestDimension() / 2); + } + if (position !== null && position !== undefined) { + var pastedEntityIDs = Clipboard.pasteEntities(position); + if (!isLargeImport) { + // The first entity in Clipboard gets the specified position with the rest being relative to it. Therefore, move + // entities after they're imported so that they're all the correct distance in front of and with geometric mean + // centered on the avatar/camera direction. + var deltaPosition = Vec3.ZERO; + var entityPositions = []; + var entityParentIDs = []; + + var propType = Entities.getEntityProperties(pastedEntityIDs[0], ["type"]).type; + var NO_ADJUST_ENTITY_TYPES = ["Zone", "Light", "ParticleEffect"]; + if (NO_ADJUST_ENTITY_TYPES.indexOf(propType) === -1) { + var targetDirection; + if (Camera.mode === "entity" || Camera.mode === "independent") { + targetDirection = Camera.orientation; + } else { + targetDirection = MyAvatar.orientation; + } + targetDirection = Vec3.multiplyQbyV(targetDirection, Vec3.UNIT_Z); + + var targetPosition = getPositionToCreateEntity(); + var deltaParallel = HALF_TREE_SCALE; // Distance to move entities parallel to targetDirection. + var deltaPerpendicular = Vec3.ZERO; // Distance to move entities perpendicular to targetDirection. + for (var i = 0, length = pastedEntityIDs.length; i < length; i++) { + var curLoopEntityProps = Entities.getEntityProperties(pastedEntityIDs[i], ["position", "dimensions", + "registrationPoint", "rotation", "parentID"]); + var adjustedPosition = adjustPositionPerBoundingBox(targetPosition, targetDirection, + curLoopEntityProps.registrationPoint, curLoopEntityProps.dimensions, curLoopEntityProps.rotation); + var delta = Vec3.subtract(adjustedPosition, curLoopEntityProps.position); + var distance = Vec3.dot(delta, targetDirection); + deltaParallel = Math.min(distance, deltaParallel); + deltaPerpendicular = Vec3.sum(Vec3.subtract(delta, Vec3.multiply(distance, targetDirection)), + deltaPerpendicular); + entityPositions[i] = curLoopEntityProps.position; + entityParentIDs[i] = curLoopEntityProps.parentID; + } + deltaPerpendicular = Vec3.multiply(1 / pastedEntityIDs.length, deltaPerpendicular); + deltaPosition = Vec3.sum(Vec3.multiply(deltaParallel, targetDirection), deltaPerpendicular); + } + + if (grid.getSnapToGrid()) { + var firstEntityProps = Entities.getEntityProperties(pastedEntityIDs[0], ["position", "dimensions", + "registrationPoint"]); + var positionPreSnap = Vec3.sum(deltaPosition, firstEntityProps.position); + position = grid.snapToSurface(grid.snapToGrid(positionPreSnap, false, firstEntityProps.dimensions, + firstEntityProps.registrationPoint), firstEntityProps.dimensions, firstEntityProps.registrationPoint); + deltaPosition = Vec3.subtract(position, firstEntityProps.position); + } + + if (!Vec3.equal(deltaPosition, Vec3.ZERO)) { + for (var editEntityIndex = 0, numEntities = pastedEntityIDs.length; editEntityIndex < numEntities; editEntityIndex++) { + if (Uuid.isNull(entityParentIDs[editEntityIndex])) { + Entities.editEntity(pastedEntityIDs[editEntityIndex], { + position: Vec3.sum(deltaPosition, entityPositions[editEntityIndex]) + }); + } + } + } + } + + if (isActive) { + selectionManager.setSelections(pastedEntityIDs, this); + } + } else { + Window.notifyEditError("Can't import entities: entities would be out of bounds."); + } + } else { + Window.notifyEditError("There was an error importing the entity file."); + } + + Overlays.editOverlay(importingSVOTextOverlay, { + visible: false + }); + Overlays.editOverlay(importingSVOImageOverlay, { + visible: false + }); +} +Window.svoImportRequested.connect(importSVO); + +Menu.menuItemEvent.connect(handleMenuEvent); + +var keyPressEvent = function (event) { + if (isActive) { + cameraManager.keyPressEvent(event); + } +}; +var keyReleaseEvent = function (event) { + if (isActive) { + cameraManager.keyReleaseEvent(event); + } +}; +Controller.keyReleaseEvent.connect(keyReleaseEvent); +Controller.keyPressEvent.connect(keyPressEvent); + +function deleteKey(value) { + if (value === 0) { // on release + deleteSelectedEntities(); + } +} +function deselectKey(value) { + if (value === 0) { // on release + selectionManager.clearSelections(this); + } +} +function toggleKey(value) { + if (value === 0) { // on release + selectionDisplay.toggleSpaceMode(); + } +} +function focusKey(value) { + if (value === 0) { // on release + cameraManager.enable(); + if (selectionManager.hasSelection()) { + cameraManager.focus(selectionManager.worldPosition, selectionManager.worldDimensions, + Menu.isOptionChecked(MENU_EASE_ON_FOCUS)); + } + } +} +function gridKey(value) { + if (value === 0) { // on release + if (selectionManager.hasSelection()) { + grid.moveToSelection(); + } + } +} +function recursiveAdd(newParentID, parentData) { + if (parentData.children !== undefined) { + var children = parentData.children; + for (var i = 0; i < children.length; i++) { + var childProperties = children[i].properties; + childProperties.parentID = newParentID; + var newChildID = Entities.addEntity(childProperties); + recursiveAdd(newChildID, children[i]); + } + } +} + +var UndoHistory = function(onUpdate) { + this.history = []; + // The current position is the index of the last executed action in the history array. + // + // -1 0 1 2 3 <- position + // A B C D <- actions in history + // + // If our lastExecutedIndex is 1, the last executed action is B. + // If we undo, we undo B (index 1). If we redo, we redo C (index 2). + this.lastExecutedIndex = -1; + this.enabled = true; + this.onUpdate = onUpdate; +}; + +UndoHistory.prototype.pushCommand = function(undoFn, undoArgs, redoFn, redoArgs) { + if (!this.enabled) { + return; + } + // Delete any history following the last executed action. + this.history.splice(this.lastExecutedIndex + 1); + this.history.push({ + undoFn: undoFn, + undoArgs: undoArgs, + redoFn: redoFn, + redoArgs: redoArgs + }); + this.lastExecutedIndex++; + + if (this.onUpdate) { + this.onUpdate(); + } +}; +UndoHistory.prototype.setEnabled = function(enabled) { + this.enabled = enabled; + if (this.onUpdate) { + this.onUpdate(); + } +}; +UndoHistory.prototype.canUndo = function() { + return this.enabled && this.lastExecutedIndex >= 0; +}; +UndoHistory.prototype.canRedo = function() { + return this.enabled && this.lastExecutedIndex < this.history.length - 1; +}; +UndoHistory.prototype.undo = function() { + if (!this.canUndo()) { + console.warn("Cannot undo action"); + return; + } + + var command = this.history[this.lastExecutedIndex]; + command.undoFn(command.undoArgs); + this.lastExecutedIndex--; + + if (this.onUpdate) { + this.onUpdate(); + } +}; +UndoHistory.prototype.redo = function() { + if (!this.canRedo()) { + console.warn("Cannot redo action"); + return; + } + + var command = this.history[this.lastExecutedIndex + 1]; + command.redoFn(command.redoArgs); + this.lastExecutedIndex++; + + if (this.onUpdate) { + this.onUpdate(); + } +}; + +function updateUndoRedoMenuItems() { + Menu.setMenuEnabled("Edit > Undo", undoHistory.canUndo()); + Menu.setMenuEnabled("Edit > Redo", undoHistory.canRedo()); +} +var undoHistory = new UndoHistory(updateUndoRedoMenuItems); +updateUndoRedoMenuItems(); + +// When an entity has been deleted we need a way to "undo" this deletion. Because it's not currently +// possible to create an entity with a specific id, earlier undo commands to the deleted entity +// will fail if there isn't a way to find the new entity id. +var DELETED_ENTITY_MAP = {}; + +function applyEntityProperties(data) { + var editEntities = data.editEntities; + var createEntities = data.createEntities; + var deleteEntities = data.deleteEntities; + var selectedEntityIDs = []; + var selectEdits = createEntities.length === 0 || !data.selectCreated; + var i, entityID, entityProperties; + for (i = 0; i < createEntities.length; i++) { + entityID = createEntities[i].entityID; + entityProperties = createEntities[i].properties; + var newEntityID = Entities.addEntity(entityProperties); + recursiveAdd(newEntityID, createEntities[i]); + DELETED_ENTITY_MAP[entityID] = newEntityID; + if (data.selectCreated) { + selectedEntityIDs.push(newEntityID); + } + } + for (i = 0; i < deleteEntities.length; i++) { + entityID = deleteEntities[i].entityID; + if (DELETED_ENTITY_MAP[entityID] !== undefined) { + entityID = DELETED_ENTITY_MAP[entityID]; + } + Entities.deleteEntity(entityID); + var index = selectedEntityIDs.indexOf(entityID); + if (index >= 0) { + selectedEntityIDs.splice(index, 1); + } + } + for (i = 0; i < editEntities.length; i++) { + entityID = editEntities[i].entityID; + if (DELETED_ENTITY_MAP[entityID] !== undefined) { + entityID = DELETED_ENTITY_MAP[entityID]; + } + entityProperties = editEntities[i].properties; + if (entityProperties !== null) { + Entities.editEntity(entityID, entityProperties); + } + if (selectEdits) { + selectedEntityIDs.push(entityID); + } + } + + // We might be getting an undo while edit.js is disabled. If that is the case, don't set + // our selections, causing the edit widgets to display. + if (isActive) { + selectionManager.setSelections(selectedEntityIDs, this); + selectionManager.saveProperties(); + } +} + +// For currently selected entities, push a command to the UndoStack that uses the current entity properties for the +// redo command, and the saved properties for the undo command. Also, include create and delete entity data. +function pushCommandForSelections(createdEntityData, deletedEntityData, doNotSaveEditProperties) { + doNotSaveEditProperties = false; + var undoData = { + editEntities: [], + createEntities: deletedEntityData || [], + deleteEntities: createdEntityData || [], + selectCreated: true + }; + var redoData = { + editEntities: [], + createEntities: createdEntityData || [], + deleteEntities: deletedEntityData || [], + selectCreated: true + }; + for (var i = 0; i < SelectionManager.selections.length; i++) { + var entityID = SelectionManager.selections[i]; + var initialProperties = SelectionManager.savedProperties[entityID]; + var currentProperties = null; + if (!initialProperties) { + continue; + } + + if (doNotSaveEditProperties) { + initialProperties = null; + } else { + currentProperties = Entities.getEntityProperties(entityID); + } + + undoData.editEntities.push({ + entityID: entityID, + properties: initialProperties + }); + redoData.editEntities.push({ + entityID: entityID, + properties: currentProperties + }); + } + undoHistory.pushCommand(applyEntityProperties, undoData, applyEntityProperties, redoData); +} + +var ServerScriptStatusMonitor = function(entityID, statusCallback) { + var self = this; + + self.entityID = entityID; + self.active = true; + self.sendRequestTimerID = null; + + var onStatusReceived = function(success, isRunning, status, errorInfo) { + if (self.active) { + statusCallback({ + statusRetrieved: success, + isRunning: isRunning, + status: status, + errorInfo: errorInfo + }); + self.sendRequestTimerID = Script.setTimeout(function() { + if (self.active) { + Entities.getServerScriptStatus(entityID, onStatusReceived); + } + }, 1000); + } + }; + self.stop = function() { + self.active = false; + }; + + Entities.getServerScriptStatus(entityID, onStatusReceived); +}; + +var PropertiesTool = function (opts) { + var that = {}; + + var webView = null; + webView = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + webView.setVisible = function(value) {}; + + var visible = false; + + // This keeps track of the last entity ID that was selected. If multiple entities + // are selected or if no entity is selected this will be `null`. + var currentSelectedEntityID = null; + var statusMonitor = null; + var blockPropertyUpdates = false; + + that.setVisible = function (newVisible) { + visible = newVisible; + webView.setVisible(shouldUseEditTabletApp() && visible); + createToolsWindow.setVisible(!shouldUseEditTabletApp() && visible); + }; + + that.setVisible(false); + + function emitScriptEvent(data) { + var dataString = JSON.stringify(data); + webView.emitScriptEvent(dataString); + createToolsWindow.emitScriptEvent(dataString); + } + + function updateScriptStatus(info) { + info.type = "server_script_status"; + emitScriptEvent(info); + } + + function resetScriptStatus() { + updateScriptStatus({ + statusRetrieved: undefined, + isRunning: undefined, + status: "", + errorInfo: "" + }); + } + + that.setSpaceMode = function(spaceMode) { + emitScriptEvent({ + type: 'setSpaceMode', + spaceMode: spaceMode + }) + }; + + function updateSelections(selectionUpdated, caller) { + if (blockPropertyUpdates) { + return; + } + + var data = { + type: 'update', + spaceMode: selectionDisplay.getSpaceMode(), + isPropertiesToolUpdate: caller === this, + }; + + if (selectionUpdated) { + resetScriptStatus(); + + if (selectionManager.selections.length !== 1) { + if (statusMonitor !== null) { + statusMonitor.stop(); + statusMonitor = null; + } + currentSelectedEntityID = null; + } else if (currentSelectedEntityID !== selectionManager.selections[0]) { + if (statusMonitor !== null) { + statusMonitor.stop(); + } + var entityID = selectionManager.selections[0]; + currentSelectedEntityID = entityID; + statusMonitor = new ServerScriptStatusMonitor(entityID, updateScriptStatus); + } + } + + var selections = []; + for (var i = 0; i < selectionManager.selections.length; i++) { + var entity = {}; + entity.id = selectionManager.selections[i]; + entity.properties = Entities.getEntityProperties(selectionManager.selections[i]); + if (entity.properties.rotation !== undefined) { + entity.properties.rotation = Quat.safeEulerAngles(entity.properties.rotation); + } + if (entity.properties.localRotation !== undefined) { + entity.properties.localRotation = Quat.safeEulerAngles(entity.properties.localRotation); + } + if (entity.properties.emitOrientation !== undefined) { + entity.properties.emitOrientation = Quat.safeEulerAngles(entity.properties.emitOrientation); + } + if (entity.properties.keyLight !== undefined && entity.properties.keyLight.direction !== undefined) { + entity.properties.keyLight.direction = Vec3.toPolar(entity.properties.keyLight.direction); + entity.properties.keyLight.direction.z = 0.0; + } + selections.push(entity); + } + data.selections = selections; + + emitScriptEvent(data); + } + selectionManager.addEventListener(updateSelections, this); + + + var onWebEventReceived = function(data) { + try { + data = JSON.parse(data); + } catch(e) { + return; + } + var i, properties, dY, diff, newPosition; + if (data.type === "update") { + + if (data.properties || data.propertiesMap) { + var propertiesMap = data.propertiesMap; + if (propertiesMap === undefined) { + propertiesMap = [{ + entityIDs: data.ids, + properties: data.properties, + }]; + } + + var sendListUpdate = false; + propertiesMap.forEach(function(propertiesObject) { + var properties = propertiesObject.properties; + var updateEntityIDs = propertiesObject.entityIDs; + if (properties.dynamic === false) { + // this object is leaving dynamic, so we zero its velocities + properties.localVelocity = Vec3.ZERO; + properties.localAngularVelocity = Vec3.ZERO; + } + if (properties.rotation !== undefined) { + properties.rotation = Quat.fromVec3Degrees(properties.rotation); + } + if (properties.localRotation !== undefined) { + properties.localRotation = Quat.fromVec3Degrees(properties.localRotation); + } + if (properties.emitOrientation !== undefined) { + properties.emitOrientation = Quat.fromVec3Degrees(properties.emitOrientation); + } + if (properties.keyLight !== undefined && properties.keyLight.direction !== undefined) { + var currentKeyLightDirection = Vec3.toPolar(Entities.getEntityProperties(selectionManager.selections[0], ['keyLight.direction']).keyLight.direction); + if (properties.keyLight.direction.x === undefined) { + properties.keyLight.direction.x = currentKeyLightDirection.x; + } + if (properties.keyLight.direction.y === undefined) { + properties.keyLight.direction.y = currentKeyLightDirection.y; + } + properties.keyLight.direction = Vec3.fromPolar(properties.keyLight.direction.x, properties.keyLight.direction.y); + } + + updateEntityIDs.forEach(function (entityID) { + Entities.editEntity(entityID, properties); + }); + + if (properties.name !== undefined || properties.modelURL !== undefined || properties.materialURL !== undefined || + properties.visible !== undefined || properties.locked !== undefined) { + + sendListUpdate = true; + } + + }); + if (sendListUpdate) { + entityListTool.sendUpdate(); + } + } + + + if (data.onlyUpdateEntities) { + blockPropertyUpdates = true; + } else { + pushCommandForSelections(); + SelectionManager.saveProperties(); + } + selectionManager._update(false, this); + blockPropertyUpdates = false; + } else if (data.type === 'saveUserData' || data.type === 'saveMaterialData') { + data.ids.forEach(function(entityID) { + Entities.editEntity(entityID, data.properties); + }); + } else if (data.type === "showMarketplace") { + showMarketplace(); + } else if (data.type === "action") { + if (data.action === "moveSelectionToGrid") { + if (selectionManager.hasSelection()) { + selectionManager.saveProperties(); + dY = grid.getOrigin().y - (selectionManager.worldPosition.y - selectionManager.worldDimensions.y / 2); + diff = { + x: 0, + y: dY, + z: 0 + }; + for (i = 0; i < selectionManager.selections.length; i++) { + properties = selectionManager.savedProperties[selectionManager.selections[i]]; + newPosition = Vec3.sum(properties.position, diff); + Entities.editEntity(selectionManager.selections[i], { + position: newPosition + }); + } + pushCommandForSelections(); + selectionManager._update(false, this); + } + } else if (data.action === "moveAllToGrid") { + if (selectionManager.hasSelection()) { + selectionManager.saveProperties(); + for (i = 0; i < selectionManager.selections.length; i++) { + properties = selectionManager.savedProperties[selectionManager.selections[i]]; + var bottomY = properties.boundingBox.center.y - properties.boundingBox.dimensions.y / 2; + dY = grid.getOrigin().y - bottomY; + diff = { + x: 0, + y: dY, + z: 0 + }; + newPosition = Vec3.sum(properties.position, diff); + Entities.editEntity(selectionManager.selections[i], { + position: newPosition + }); + } + pushCommandForSelections(); + selectionManager._update(false, this); + } + } else if (data.action === "resetToNaturalDimensions") { + if (selectionManager.hasSelection()) { + selectionManager.saveProperties(); + for (i = 0; i < selectionManager.selections.length; i++) { + properties = selectionManager.savedProperties[selectionManager.selections[i]]; + var naturalDimensions = properties.naturalDimensions; + + // If any of the natural dimensions are not 0, resize + if (properties.type === "Model" && naturalDimensions.x === 0 && naturalDimensions.y === 0 && + naturalDimensions.z === 0) { + Window.notifyEditError("Cannot reset entity to its natural dimensions: Model URL" + + " is invalid or the model has not yet been loaded."); + } else { + Entities.editEntity(selectionManager.selections[i], { + dimensions: properties.naturalDimensions + }); + } + } + pushCommandForSelections(); + selectionManager._update(false, this); + } + } else if (data.action === "previewCamera") { + if (selectionManager.hasSelection()) { + Camera.mode = "entity"; + Camera.cameraEntity = selectionManager.selections[0]; + } + } else if (data.action === "rescaleDimensions") { + var multiplier = data.percentage / 100.0; + if (selectionManager.hasSelection()) { + selectionManager.saveProperties(); + for (i = 0; i < selectionManager.selections.length; i++) { + properties = selectionManager.savedProperties[selectionManager.selections[i]]; + Entities.editEntity(selectionManager.selections[i], { + dimensions: Vec3.multiply(multiplier, properties.dimensions) + }); + } + pushCommandForSelections(); + selectionManager._update(false, this); + } + } else if (data.action === "reloadClientScripts") { + if (selectionManager.hasSelection()) { + var timestamp = Date.now(); + for (i = 0; i < selectionManager.selections.length; i++) { + Entities.editEntity(selectionManager.selections[i], { + scriptTimestamp: timestamp + }); + } + } + } else if (data.action === "reloadServerScripts") { + if (selectionManager.hasSelection()) { + for (i = 0; i < selectionManager.selections.length; i++) { + Entities.reloadServerScripts(selectionManager.selections[i]); + } + } + } + } else if (data.type === "propertiesPageReady") { + updateSelections(true); + } else if (data.type === "tooltipsRequest") { + emitScriptEvent({ + type: 'tooltipsReply', + tooltips: Script.require('./assets/data/createAppTooltips.json'), + hmdActive: HMD.active, + }); + } else if (data.type === "propertyRangeRequest") { + var propertyRanges = {}; + data.properties.forEach(function (property) { + propertyRanges[property] = Entities.getPropertyInfo(property); + }); + emitScriptEvent({ + type: 'propertyRangeReply', + propertyRanges: propertyRanges, + }); + } else if (data.type === "materialTargetRequest") { + var parentModelData; + var properties = Entities.getEntityProperties(data.entityID, ["type", "parentID"]); + if (properties.type === "Material" && properties.parentID !== Uuid.NULL) { + var parentType = Entities.getEntityProperties(properties.parentID, ["type"]).type; + if (parentType === "Model" || Entities.getNestableType(properties.parentID) === "avatar") { + parentModelData = Graphics.getModel(properties.parentID); + } else if (parentType === "Shape" || parentType === "Box" || parentType === "Sphere") { + parentModelData = {}; + parentModelData.numMeshes = 1; + parentModelData.materialNames = []; + } + } + emitScriptEvent({ + type: 'materialTargetReply', + entityID: data.entityID, + materialTargetData: parentModelData, + }); + } + }; + + HMD.displayModeChanged.connect(function() { + emitScriptEvent({ + type: 'hmdActiveChanged', + hmdActive: HMD.active, + }); + }); + + createToolsWindow.webEventReceived.addListener(this, onWebEventReceived); + + webView.webEventReceived.connect(this, onWebEventReceived); + + return that; +}; + + +var PopupMenu = function () { + var self = this; + + var MENU_ITEM_HEIGHT = 21; + var MENU_ITEM_SPACING = 1; + var TEXT_MARGIN = 7; + + var overlays = []; + var overlayInfo = {}; + + var visible = false; + + var upColor = { + red: 0, + green: 0, + blue: 0 + }; + var downColor = { + red: 192, + green: 192, + blue: 192 + }; + var overColor = { + red: 128, + green: 128, + blue: 128 + }; + + self.onSelectMenuItem = function () {}; + + self.addMenuItem = function (name) { + var id = Overlays.addOverlay("text", { + text: name, + backgroundAlpha: 1.0, + backgroundColor: upColor, + topMargin: TEXT_MARGIN, + leftMargin: TEXT_MARGIN, + width: 210, + height: MENU_ITEM_HEIGHT, + font: { + size: 12 + }, + visible: false + }); + overlays.push(id); + overlayInfo[id] = { + name: name + }; + return id; + }; + + self.updateMenuItemText = function (id, newText) { + Overlays.editOverlay(id, { + text: newText + }); + }; + + self.setPosition = function (x, y) { + for (var key in overlayInfo) { + Overlays.editOverlay(key, { + x: x, + y: y + }); + y += MENU_ITEM_HEIGHT + MENU_ITEM_SPACING; + } + }; + + self.onSelected = function () {}; + + var pressingOverlay = null; + var hoveringOverlay = null; + + self.mousePressEvent = function (event) { + if (event.isLeftButton) { + var overlay = Overlays.getOverlayAtPoint({ + x: event.x, + y: event.y + }); + if (overlay in overlayInfo) { + pressingOverlay = overlay; + Overlays.editOverlay(pressingOverlay, { + backgroundColor: downColor + }); + } else { + self.hide(); + } + return false; + } + }; + self.mouseMoveEvent = function (event) { + if (visible) { + var overlay = Overlays.getOverlayAtPoint({ + x: event.x, + y: event.y + }); + if (!pressingOverlay) { + if (hoveringOverlay !== null && overlay !== hoveringOverlay) { + Overlays.editOverlay(hoveringOverlay, { + backgroundColor: upColor + }); + hoveringOverlay = null; + } + if (overlay !== hoveringOverlay && overlay in overlayInfo) { + Overlays.editOverlay(overlay, { + backgroundColor: overColor + }); + hoveringOverlay = overlay; + } + } + } + return false; + }; + self.mouseReleaseEvent = function (event) { + var overlay = Overlays.getOverlayAtPoint({ + x: event.x, + y: event.y + }); + if (pressingOverlay !== null && pressingOverlay !== undefined) { + if (overlay === pressingOverlay) { + self.onSelectMenuItem(overlayInfo[overlay].name); + } + Overlays.editOverlay(pressingOverlay, { + backgroundColor: upColor + }); + pressingOverlay = null; + self.hide(); + } + }; + + self.setVisible = function (newVisible) { + if (newVisible !== visible) { + visible = newVisible; + for (var key in overlayInfo) { + Overlays.editOverlay(key, { + visible: newVisible + }); + } + } + }; + self.show = function () { + self.setVisible(true); + }; + self.hide = function () { + self.setVisible(false); + }; + + function cleanup() { + ContextOverlay.enabled = true; + for (var i = 0; i < overlays.length; i++) { + Overlays.deleteOverlay(overlays[i]); + } + Controller.mousePressEvent.disconnect(self.mousePressEvent); + Controller.mouseMoveEvent.disconnect(self.mouseMoveEvent); + Controller.mouseReleaseEvent.disconnect(self.mouseReleaseEvent); + + Entities.canRezChanged.disconnect(checkEditPermissionsAndUpdate); + Entities.canRezTmpChanged.disconnect(checkEditPermissionsAndUpdate); + Entities.canRezCertifiedChanged.disconnect(checkEditPermissionsAndUpdate); + Entities.canRezTmpCertifiedChanged.disconnect(checkEditPermissionsAndUpdate); + } + + Controller.mousePressEvent.connect(self.mousePressEvent); + Controller.mouseMoveEvent.connect(self.mouseMoveEvent); + Controller.mouseReleaseEvent.connect(self.mouseReleaseEvent); + Script.scriptEnding.connect(cleanup); + + return this; +}; + +function whenPressed(fn) { + return function(value) { + if (value > 0) { + fn(); + } + }; +} + +function whenReleased(fn) { + return function(value) { + if (value === 0) { + fn(); + } + }; +} + +var isOnMacPlatform = Controller.getValue(Controller.Hardware.Application.PlatformMac); + +var mapping = Controller.newMapping(CONTROLLER_MAPPING_NAME); +if (isOnMacPlatform) { + mapping.from([Controller.Hardware.Keyboard.Backspace]).to(deleteKey); +} else { + mapping.from([Controller.Hardware.Keyboard.Delete]).to(deleteKey); +} +mapping.from([Controller.Hardware.Keyboard.T]).to(toggleKey); +mapping.from([Controller.Hardware.Keyboard.F]).to(focusKey); +mapping.from([Controller.Hardware.Keyboard.G]).to(gridKey); +mapping.from([Controller.Hardware.Keyboard.X]) + .when([Controller.Hardware.Keyboard.Control]) + .to(whenReleased(function() { selectionManager.cutSelectedEntities() })); +mapping.from([Controller.Hardware.Keyboard.C]) + .when([Controller.Hardware.Keyboard.Control]) + .to(whenReleased(function() { selectionManager.copySelectedEntities() })); +mapping.from([Controller.Hardware.Keyboard.V]) + .when([Controller.Hardware.Keyboard.Control]) + .to(whenReleased(function() { selectionManager.pasteEntities() })); +mapping.from([Controller.Hardware.Keyboard.D]) + .when([Controller.Hardware.Keyboard.Control]) + .to(whenReleased(function() { selectionManager.duplicateSelection() })); + +// Bind undo to ctrl-shift-z to maintain backwards-compatibility +mapping.from([Controller.Hardware.Keyboard.Z]) + .when([Controller.Hardware.Keyboard.Control, Controller.Hardware.Keyboard.Shift]) + .to(whenPressed(function() { undoHistory.redo() })); + + +mapping.from([Controller.Hardware.Keyboard.P]) + .when([Controller.Hardware.Keyboard.Control, Controller.Hardware.Keyboard.Shift]) + .to(whenReleased(function() { unparentSelectedEntities(); })); + +mapping.from([Controller.Hardware.Keyboard.P]) + .when([Controller.Hardware.Keyboard.Control, !Controller.Hardware.Keyboard.Shift]) + .to(whenReleased(function() { parentSelectedEntities(); })); + +keyUpEventFromUIWindow = function(keyUpEvent) { + var WANT_DEBUG_MISSING_SHORTCUTS = false; + + var pressedValue = 0.0; + + if ((!isOnMacPlatform && keyUpEvent.keyCodeString === "Delete") + || (isOnMacPlatform && keyUpEvent.keyCodeString === "Backspace")) { + + deleteKey(pressedValue); + } else if (keyUpEvent.keyCodeString === "T") { + toggleKey(pressedValue); + } else if (keyUpEvent.keyCodeString === "F") { + focusKey(pressedValue); + } else if (keyUpEvent.keyCodeString === "G") { + gridKey(pressedValue); + } else if (keyUpEvent.controlKey && keyUpEvent.keyCodeString === "X") { + selectionManager.cutSelectedEntities(); + } else if (keyUpEvent.controlKey && keyUpEvent.keyCodeString === "C") { + selectionManager.copySelectedEntities(); + } else if (keyUpEvent.controlKey && keyUpEvent.keyCodeString === "V") { + selectionManager.pasteEntities(); + } else if (keyUpEvent.controlKey && keyUpEvent.keyCodeString === "D") { + selectionManager.duplicateSelection(); + } else if (!isOnMacPlatform && keyUpEvent.controlKey && !keyUpEvent.shiftKey && keyUpEvent.keyCodeString === "Z") { + undoHistory.undo(); // undo is only handled via handleMenuItem on Mac + } else if (keyUpEvent.controlKey && !keyUpEvent.shiftKey && keyUpEvent.keyCodeString === "P") { + parentSelectedEntities(); + } else if (keyUpEvent.controlKey && keyUpEvent.shiftKey && keyUpEvent.keyCodeString === "P") { + unparentSelectedEntities(); + } else if (!isOnMacPlatform && + ((keyUpEvent.controlKey && keyUpEvent.shiftKey && keyUpEvent.keyCodeString === "Z") || + (keyUpEvent.controlKey && keyUpEvent.keyCodeString === "Y"))) { + undoHistory.redo(); // redo is only handled via handleMenuItem on Mac + } else if (WANT_DEBUG_MISSING_SHORTCUTS) { + console.warn("unhandled key event: " + JSON.stringify(keyUpEvent)) + } +}; + +var propertyMenu = new PopupMenu(); + +propertyMenu.onSelectMenuItem = function (name) { + + if (propertyMenu.marketplaceID) { + showMarketplace(propertyMenu.marketplaceID); + } +}; + +var showMenuItem = propertyMenu.addMenuItem("Show in Marketplace"); + +var propertiesTool = new PropertiesTool(); + +selectionDisplay.onSpaceModeChange = function(spaceMode) { + entityListTool.setSpaceMode(spaceMode); + propertiesTool.setSpaceMode(spaceMode); +}; + +}()); // END LOCAL_SCOPE diff --git a/scripts/simplifiedUI/system/emote.js b/scripts/simplifiedUI/system/emote.js new file mode 100644 index 0000000000..6dfd1ae1ef --- /dev/null +++ b/scripts/simplifiedUI/system/emote.js @@ -0,0 +1,179 @@ +"use strict"; + +// +// emote.js +// scripts/system/ +// +// Created by Brad Hefta-Gaub on 7 Jan 2018 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +/* globals Script, Tablet */ +/* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */ + +(function() { // BEGIN LOCAL_SCOPE + + +var EMOTE_ANIMATIONS = + ['Crying', 'Surprised', 'Dancing', 'Cheering', 'Waving', 'Fall', 'Pointing', 'Clapping', 'Sit1', 'Sit2', 'Sit3', 'Love']; +var ANIMATIONS = Array(); + +var eventMappingName = "io.highfidelity.away"; // restoreAnimation on hand controller button events, too +var eventMapping = Controller.newMapping(eventMappingName); + +EMOTE_ANIMATIONS.forEach(function (name) { + var animationURL = Script.resolvePath("assets/animations/" + name + ".fbx"); + var resource = AnimationCache.prefetch(animationURL); + var animation = AnimationCache.getAnimation(animationURL); + ANIMATIONS[name] = { url: animationURL, animation: animation, resource: resource}; +}); + + +var EMOTE_APP_BASE = "html/EmoteApp.html"; +var EMOTE_APP_URL = Script.resolvePath(EMOTE_APP_BASE); +var EMOTE_LABEL = "EMOTE"; +var EMOTE_APP_SORT_ORDER = 12; +var FPS = 60; +var MSEC_PER_SEC = 1000; +var FINISHED = 3; // see ScriptableResource::State + +var onEmoteScreen = false; +var button; +var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); +var activeTimer = false; // Used to cancel active timer if a user plays an animation while another animation is playing +var activeEmote = false; // To keep track of the currently playing emote + +button = tablet.addButton({ + icon: "icons/tablet-icons/emote-i.svg", + activeIcon: "icons/tablet-icons/emote-a.svg", + text: EMOTE_LABEL, + sortOrder: EMOTE_APP_SORT_ORDER +}); + +function onClicked() { + if (onEmoteScreen) { + tablet.gotoHomeScreen(); + } else { + onEmoteScreen = true; + tablet.gotoWebScreen(EMOTE_APP_URL); + } +} + +function onScreenChanged(type, url) { + onEmoteScreen = type === "Web" && (url.indexOf(EMOTE_APP_BASE) === url.length - EMOTE_APP_BASE.length); + button.editProperties({ isActive: onEmoteScreen }); +} + +// Handle the events we're receiving from the web UI +function onWebEventReceived(event) { + + // Converts the event to a JavasScript Object + if (typeof event === "string") { + event = JSON.parse(event); + } + + if (event.type === "click") { + + // Allow for a random sitting animation when a user selects sit + var randSit = Math.floor(Math.random() * 3) + 1; + + var emoteName = event.data; + + if (emoteName === "Sit"){ + emoteName = event.data + randSit; // Sit1, Sit2, Sit3 + } + + if (ANIMATIONS[emoteName].resource.state === FINISHED) { + + if (activeTimer !== false) { + Script.clearTimeout(activeTimer); + } + + // If the activeEmote is different from the chosen emote, then play the new emote + // This is a second click on the same emote as the activeEmote, and we will just stop it + if (activeEmote !== emoteName) { + activeEmote = emoteName; + + + // Sit is the only animation currently that plays and then ends at the last frame + if (emoteName.match(/^Sit.*$/)) { + + // If user provides input during a sit, the avatar animation state should be restored + Controller.keyPressEvent.connect(restoreAnimation); + Controller.enableMapping(eventMappingName); + MyAvatar.overrideAnimation(ANIMATIONS[emoteName].url, FPS, false, 0, frameCount); + + } else { + + activeEmote = emoteName; + var frameCount = ANIMATIONS[emoteName].animation.frames.length; + MyAvatar.overrideAnimation(ANIMATIONS[emoteName].url, FPS, false, 0, frameCount); + + var timeOut = MSEC_PER_SEC * frameCount / FPS; + activeTimer = Script.setTimeout(function () { + MyAvatar.restoreAnimation(); + activeTimer = false; + activeEmote = false; + }, timeOut); + + } + + } else { + activeEmote = false; + MyAvatar.restoreAnimation(); + } + } + } +} + +// Restore the navigation animation states (idle, walk, run) +function restoreAnimation() { + MyAvatar.restoreAnimation(); + + // Make sure the input is disconnected after animations are restored so it doesn't affect any emotes other than sit + Controller.keyPressEvent.disconnect(restoreAnimation); + Controller.disableMapping(eventMappingName); +} + +// Note peek() so as to not interfere with other mappings. +eventMapping.from(Controller.Standard.LeftPrimaryThumb).peek().to(restoreAnimation); +eventMapping.from(Controller.Standard.RightPrimaryThumb).peek().to(restoreAnimation); +eventMapping.from(Controller.Standard.LeftSecondaryThumb).peek().to(restoreAnimation); +eventMapping.from(Controller.Standard.RightSecondaryThumb).peek().to(restoreAnimation); +eventMapping.from(Controller.Standard.LB).peek().to(restoreAnimation); +eventMapping.from(Controller.Standard.LS).peek().to(restoreAnimation); +eventMapping.from(Controller.Standard.RY).peek().to(restoreAnimation); +eventMapping.from(Controller.Standard.RX).peek().to(restoreAnimation); +eventMapping.from(Controller.Standard.LY).peek().to(restoreAnimation); +eventMapping.from(Controller.Standard.LX).peek().to(restoreAnimation); +eventMapping.from(Controller.Standard.LeftGrip).peek().to(restoreAnimation); +eventMapping.from(Controller.Standard.RB).peek().to(restoreAnimation); +eventMapping.from(Controller.Standard.RS).peek().to(restoreAnimation); +eventMapping.from(Controller.Standard.RightGrip).peek().to(restoreAnimation); +eventMapping.from(Controller.Standard.Back).peek().to(restoreAnimation); +eventMapping.from(Controller.Standard.Start).peek().to(restoreAnimation); + + +button.clicked.connect(onClicked); +tablet.screenChanged.connect(onScreenChanged); +tablet.webEventReceived.connect(onWebEventReceived); + +Script.scriptEnding.connect(function () { + if (onEmoteScreen) { + tablet.gotoHomeScreen(); + } + button.clicked.disconnect(onClicked); + tablet.screenChanged.disconnect(onScreenChanged); + if (tablet) { + tablet.removeButton(button); + } + if (activeTimer !== false) { + Script.clearTimeout(activeTimer); + MyAvatar.restoreAnimation(); + } +}); + + +}()); // END LOCAL_SCOPE diff --git a/scripts/simplifiedUI/system/fingerPaint.js b/scripts/simplifiedUI/system/fingerPaint.js new file mode 100644 index 0000000000..88245503e8 --- /dev/null +++ b/scripts/simplifiedUI/system/fingerPaint.js @@ -0,0 +1,465 @@ +// +// fingerPaint.js +// +// Created by David Rowe on 15 Feb 2017 +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +(function () { + var tablet, + button, + BUTTON_NAME = "PAINT", + isFingerPainting = false, + shouldPointFingers = false, + leftHand = null, + rightHand = null, + leftBrush = null, + rightBrush = null, + CONTROLLER_MAPPING_NAME = "com.highfidelity.fingerPaint", + isTabletDisplayed = false, + HIFI_POINT_INDEX_MESSAGE_CHANNEL = "Hifi-Point-Index", + HIFI_GRAB_DISABLE_MESSAGE_CHANNEL = "Hifi-Grab-Disable", + HIFI_POINTER_DISABLE_MESSAGE_CHANNEL = "Hifi-Pointer-Disable"; + HOW_TO_EXIT_MESSAGE = "Press B on your controller to exit FingerPainting mode"; + + function paintBrush(name) { + // Paints in 3D. + var brushName = name, + STROKE_COLOR = { red: 250, green: 0, blue: 0 }, + ERASE_SEARCH_RADIUS = 0.1, // m + isDrawingLine = false, + entityID, + basePosition, + strokePoints, + strokeNormals, + strokeWidths, + timeOfLastPoint, + MIN_STROKE_LENGTH = 0.005, // m + MIN_STROKE_INTERVAL = 66, // ms + MAX_POINTS_PER_LINE = 70; // Hard-coded limit in PolyLineEntityItem.h. + + function strokeNormal() { + return Vec3.multiplyQbyV(Camera.getOrientation(), Vec3.UNIT_NEG_Z); + } + + function startLine(position, width) { + // Start drawing a polyline. + + if (isDrawingLine) { + print("ERROR: startLine() called when already drawing line"); + // Nevertheless, continue on and start a new line. + } + + basePosition = position; + + strokePoints = [Vec3.ZERO]; + strokeNormals = [strokeNormal()]; + strokeWidths = [width]; + timeOfLastPoint = Date.now(); + + entityID = Entities.addEntity({ + type: "PolyLine", + name: "fingerPainting", + color: STROKE_COLOR, + position: position, + linePoints: strokePoints, + normals: strokeNormals, + strokeWidths: strokeWidths, + dimensions: { x: 10, y: 10, z: 10 } + }); + + isDrawingLine = true; + } + + function drawLine(position, width) { + // Add a stroke to the polyline if stroke is a sufficient length. + var localPosition, + distanceToPrevious, + MAX_DISTANCE_TO_PREVIOUS = 1.0; + + if (!isDrawingLine) { + print("ERROR: drawLine() called when not drawing line"); + return; + } + + localPosition = Vec3.subtract(position, basePosition); + distanceToPrevious = Vec3.distance(localPosition, strokePoints[strokePoints.length - 1]); + + if (distanceToPrevious > MAX_DISTANCE_TO_PREVIOUS) { + // Ignore occasional spurious finger tip positions. + return; + } + + if (distanceToPrevious >= MIN_STROKE_LENGTH + && (Date.now() - timeOfLastPoint) >= MIN_STROKE_INTERVAL + && strokePoints.length < MAX_POINTS_PER_LINE) { + strokePoints.push(localPosition); + strokeNormals.push(strokeNormal()); + strokeWidths.push(width); + timeOfLastPoint = Date.now(); + + Entities.editEntity(entityID, { + linePoints: strokePoints, + normals: strokeNormals, + strokeWidths: strokeWidths + }); + } + } + + function finishLine(position, width) { + // Finish drawing polyline; delete if it has only 1 point. + + if (!isDrawingLine) { + print("ERROR: finishLine() called when not drawing line"); + return; + } + + if (strokePoints.length === 1) { + // Delete "empty" line. + Entities.deleteEntity(entityID); + } + + isDrawingLine = false; + } + + function cancelLine() { + // Cancel any line being drawn. + if (isDrawingLine) { + Entities.deleteEntity(entityID); + isDrawingLine = false; + } + } + + function eraseClosestLine(position) { + // Erase closest line that is within search radius of finger tip. + var entities, + entitiesLength, + properties, + i, + pointsLength, + j, + distance, + found = false, + foundID, + foundDistance = ERASE_SEARCH_RADIUS; + + // Find entities with bounding box within search radius. + entities = Entities.findEntities(position, ERASE_SEARCH_RADIUS); + + // Fine polyline entity with closest point within search radius. + for (i = 0, entitiesLength = entities.length; i < entitiesLength; i += 1) { + properties = Entities.getEntityProperties(entities[i], ["type", "position", "linePoints"]); + if (properties.type === "PolyLine") { + basePosition = properties.position; + for (j = 0, pointsLength = properties.linePoints.length; j < pointsLength; j += 1) { + distance = Vec3.distance(position, Vec3.sum(basePosition, properties.linePoints[j])); + if (distance <= foundDistance) { + found = true; + foundID = entities[i]; + foundDistance = distance; + } + } + } + } + + // Delete found entity. + if (found) { + Entities.deleteEntity(foundID); + } + } + + function tearDown() { + cancelLine(); + } + + return { + startLine: startLine, + drawLine: drawLine, + finishLine: finishLine, + cancelLine: cancelLine, + eraseClosestLine: eraseClosestLine, + tearDown: tearDown + }; + } + + function handController(name) { + // Translates controller data into application events. + var handName = name, + + triggerPressedCallback, + triggerPressingCallback, + triggerReleasedCallback, + gripPressedCallback, + + rawTriggerValue = 0.0, + triggerValue = 0.0, + isTriggerPressed = false, + TRIGGER_SMOOTH_RATIO = 0.1, + TRIGGER_OFF = 0.05, + TRIGGER_ON = 0.1, + TRIGGER_START_WIDTH_RAMP = 0.15, + TRIGGER_FINISH_WIDTH_RAMP = 1.0, + TRIGGER_RAMP_WIDTH = TRIGGER_FINISH_WIDTH_RAMP - TRIGGER_START_WIDTH_RAMP, + MIN_LINE_WIDTH = 0.005, + MAX_LINE_WIDTH = 0.03, + RAMP_LINE_WIDTH = MAX_LINE_WIDTH - MIN_LINE_WIDTH, + + rawGripValue = 0.0, + gripValue = 0.0, + isGripPressed = false, + GRIP_SMOOTH_RATIO = 0.1, + GRIP_OFF = 0.05, + GRIP_ON = 0.1; + + function onTriggerPress(value) { + // Controller values are only updated when they change so store latest for use in update. + rawTriggerValue = value; + } + + function updateTriggerPress(value) { + var wasTriggerPressed, + fingerTipPosition, + lineWidth; + + triggerValue = triggerValue * TRIGGER_SMOOTH_RATIO + rawTriggerValue * (1.0 - TRIGGER_SMOOTH_RATIO); + + wasTriggerPressed = isTriggerPressed; + if (isTriggerPressed) { + isTriggerPressed = triggerValue > TRIGGER_OFF; + } else { + isTriggerPressed = triggerValue > TRIGGER_ON; + } + + if (wasTriggerPressed || isTriggerPressed) { + fingerTipPosition = MyAvatar.getJointPosition(handName === "left" ? "LeftHandIndex4" : "RightHandIndex4"); + if (triggerValue < TRIGGER_START_WIDTH_RAMP) { + lineWidth = MIN_LINE_WIDTH; + } else { + lineWidth = MIN_LINE_WIDTH + + (triggerValue - TRIGGER_START_WIDTH_RAMP) / TRIGGER_RAMP_WIDTH * RAMP_LINE_WIDTH; + } + + if (!wasTriggerPressed && isTriggerPressed) { + triggerPressedCallback(fingerTipPosition, lineWidth); + } else if (wasTriggerPressed && isTriggerPressed) { + triggerPressingCallback(fingerTipPosition, lineWidth); + } else { + triggerReleasedCallback(fingerTipPosition, lineWidth); + } + } + } + + function onGripPress(value) { + // Controller values are only updated when they change so store latest for use in update. + rawGripValue = value; + } + + function updateGripPress() { + var fingerTipPosition; + + gripValue = gripValue * GRIP_SMOOTH_RATIO + rawGripValue * (1.0 - GRIP_SMOOTH_RATIO); + + if (isGripPressed) { + isGripPressed = gripValue > GRIP_OFF; + } else { + isGripPressed = gripValue > GRIP_ON; + if (isGripPressed) { + fingerTipPosition = MyAvatar.getJointPosition(handName === "left" ? "LeftHandIndex4" : "RightHandIndex4"); + gripPressedCallback(fingerTipPosition); + } + } + } + + function onUpdate() { + updateTriggerPress(); + updateGripPress(); + } + + function setUp(onTriggerPressed, onTriggerPressing, onTriggerReleased, onGripPressed) { + triggerPressedCallback = onTriggerPressed; + triggerPressingCallback = onTriggerPressing; + triggerReleasedCallback = onTriggerReleased; + gripPressedCallback = onGripPressed; + } + + function tearDown() { + // Nothing to do. + } + + return { + onTriggerPress: onTriggerPress, + onGripPress: onGripPress, + onUpdate: onUpdate, + setUp: setUp, + tearDown: tearDown + }; + } + + function updateHandFunctions() { + // Update other scripts' hand functions. + var enabled = !isFingerPainting || isTabletDisplayed; + + Messages.sendMessage(HIFI_GRAB_DISABLE_MESSAGE_CHANNEL, JSON.stringify({ + holdEnabled: enabled, + nearGrabEnabled: enabled, + farGrabEnabled: enabled + }), true); + Messages.sendMessage(HIFI_POINTER_DISABLE_MESSAGE_CHANNEL, JSON.stringify({ + pointerEnabled: enabled + }), true); + + var newShouldPointFingers = !enabled; + if (newShouldPointFingers !== shouldPointFingers) { + Messages.sendMessage(HIFI_POINT_INDEX_MESSAGE_CHANNEL, JSON.stringify({ + pointIndex: newShouldPointFingers + }), true); + shouldPointFingers = newShouldPointFingers; + } + } + + function howToExitTutorial() { + HMD.requestShowHandControllers(); + setControllerPartLayer('button_b', 'highlight'); + messageWindow = Window.alert(HOW_TO_EXIT_MESSAGE); + setControllerPartLayer('button_b', 'blank'); + HMD.requestHideHandControllers(); + Settings.setValue("FingerPaintTutorialComplete", true); + } + + function enableProcessing() { + // Connect controller API to handController objects. + leftHand = handController("left"); + rightHand = handController("right"); + var controllerMapping = Controller.newMapping(CONTROLLER_MAPPING_NAME); + controllerMapping.from(Controller.Standard.LT).to(leftHand.onTriggerPress); + controllerMapping.from(Controller.Standard.LeftGrip).to(leftHand.onGripPress); + controllerMapping.from(Controller.Standard.RT).to(rightHand.onTriggerPress); + controllerMapping.from(Controller.Standard.RightGrip).to(rightHand.onGripPress); + controllerMapping.from(Controller.Standard.B).to(onButtonClicked); + Controller.enableMapping(CONTROLLER_MAPPING_NAME); + + if (!Settings.getValue("FingerPaintTutorialComplete")) { + howToExitTutorial(); + } + + // Connect handController outputs to paintBrush objects. + leftBrush = paintBrush("left"); + leftHand.setUp(leftBrush.startLine, leftBrush.drawLine, leftBrush.finishLine, leftBrush.eraseClosestLine); + rightBrush = paintBrush("right"); + rightHand.setUp(rightBrush.startLine, rightBrush.drawLine, rightBrush.finishLine, rightBrush.eraseClosestLine); + + // Messages channels for enabling/disabling other scripts' functions. + Messages.subscribe(HIFI_POINT_INDEX_MESSAGE_CHANNEL); + Messages.subscribe(HIFI_GRAB_DISABLE_MESSAGE_CHANNEL); + Messages.subscribe(HIFI_POINTER_DISABLE_MESSAGE_CHANNEL); + + // Update hand controls. + Script.update.connect(leftHand.onUpdate); + Script.update.connect(rightHand.onUpdate); + } + + function disableProcessing() { + Script.update.disconnect(leftHand.onUpdate); + Script.update.disconnect(rightHand.onUpdate); + + Controller.disableMapping(CONTROLLER_MAPPING_NAME); + + leftBrush.tearDown(); + leftBrush = null; + leftHand.tearDown(); + leftHand = null; + + rightBrush.tearDown(); + rightBrush = null; + rightHand.tearDown(); + rightHand = null; + + Messages.unsubscribe(HIFI_POINT_INDEX_MESSAGE_CHANNEL); + Messages.unsubscribe(HIFI_GRAB_DISABLE_MESSAGE_CHANNEL); + Messages.unsubscribe(HIFI_POINTER_DISABLE_MESSAGE_CHANNEL); + } + + function onButtonClicked() { + var wasFingerPainting = isFingerPainting; + + isFingerPainting = !isFingerPainting; + button.editProperties({ isActive: isFingerPainting }); + + print("Finger painting: " + isFingerPainting ? "on" : "off"); + + if (wasFingerPainting) { + leftBrush.cancelLine(); + rightBrush.cancelLine(); + } + + if (isFingerPainting) { + enableProcessing(); + } + + updateHandFunctions(); + + if (!isFingerPainting) { + disableProcessing(); + } + } + + function onTabletScreenChanged(type, url) { + var TABLET_SCREEN_CLOSED = "Closed"; + + isTabletDisplayed = type !== TABLET_SCREEN_CLOSED; + updateHandFunctions(); + } + + function setUp() { + tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + if (!tablet) { + return; + } + + // Tablet button. + button = tablet.addButton({ + icon: "icons/tablet-icons/finger-paint-i.svg", + activeIcon: "icons/tablet-icons/finger-paint-a.svg", + text: BUTTON_NAME, + isActive: isFingerPainting + }); + button.clicked.connect(onButtonClicked); + + // Track whether tablet is displayed or not. + tablet.screenChanged.connect(onTabletScreenChanged); + } + + function tearDown() { + if (!tablet) { + return; + } + + if (isFingerPainting) { + isFingerPainting = false; + updateHandFunctions(); + disableProcessing(); + } + + tablet.screenChanged.disconnect(onTabletScreenChanged); + + button.clicked.disconnect(onButtonClicked); + tablet.removeButton(button); + } + + /** + * A controller is made up of parts, and each part can have multiple "layers," + * which are really just different texures. For example, the "trigger" part + * has "normal" and "highlight" layers. + */ + function setControllerPartLayer(part, layer) { + data = {}; + data[part] = layer; + Messages.sendLocalMessage('Controller-Set-Part-Layer', JSON.stringify(data)); + } + + setUp(); + Script.scriptEnding.connect(tearDown); +}()); diff --git a/scripts/simplifiedUI/system/firstPersonHMD.js b/scripts/simplifiedUI/system/firstPersonHMD.js new file mode 100644 index 0000000000..5fdee1b7b5 --- /dev/null +++ b/scripts/simplifiedUI/system/firstPersonHMD.js @@ -0,0 +1,23 @@ +"use strict"; + +// +// firstPersonHMD.js +// system +// +// Created by Zander Otavka on 6/24/16 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +(function() { // BEGIN LOCAL_SCOPE + +// Automatically enter first person mode when entering HMD mode +HMD.displayModeChanged.connect(function(isHMDMode) { + if (isHMDMode) { + Camera.setModeString("first person"); + } +}); + +}()); // END LOCAL_SCOPE diff --git a/scripts/simplifiedUI/system/generalSettings.js b/scripts/simplifiedUI/system/generalSettings.js new file mode 100644 index 0000000000..d3848da7d0 --- /dev/null +++ b/scripts/simplifiedUI/system/generalSettings.js @@ -0,0 +1,56 @@ +"use strict"; + +// +// generalSettings.js +// scripts/system/ +// +// Created by Dante Ruiz on 9 Feb 2017 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +/* globals Tablet, Toolbars, Script, HMD, DialogsManager */ + +(function() { // BEGIN LOCAL_SCOPE + + var button; + var buttonName = "Settings"; + var toolBar = null; + var tablet = null; + var settings = "hifi/tablet/TabletGeneralPreferences.qml" + function onClicked(){ + if (tablet) { + tablet.loadQMLSource(settings); + } + } + + if (Settings.getValue("HUDUIEnabled")) { + toolBar = Toolbars.getToolbar("com.highfidelity.interface.toolbar.system"); + button = toolBar.addButton({ + objectName: buttonName, + imageURL: Script.resolvePath("assets/images/tools/directory.svg"), + visible: true, + alpha: 0.9 + }); + } else { + tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + button = tablet.addButton({ + icon: "icons/tablet-icons/goto-i.svg", + text: buttonName + }); + } + + button.clicked.connect(onClicked); + + Script.scriptEnding.connect(function () { + button.clicked.disconnect(onClicked); + if (tablet) { + tablet.removeButton(button); + } + if (toolBar) { + toolBar.removeButton(buttonName); + } + }); + +}()); // END LOCAL_SCOPE diff --git a/scripts/simplifiedUI/system/goto.js b/scripts/simplifiedUI/system/goto.js new file mode 100644 index 0000000000..5cc5bad844 --- /dev/null +++ b/scripts/simplifiedUI/system/goto.js @@ -0,0 +1,65 @@ +"use strict"; + +// +// goto.js +// scripts/system/ +// +// Created by Howard Stearns on 2 Jun 2016 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +/* globals Tablet, Toolbars, Script, HMD, DialogsManager */ + +(function() { // BEGIN LOCAL_SCOPE + +var button; +var buttonName = "GOTO"; +var toolBar = null; +var tablet = null; +var onGotoScreen = false; +function onAddressBarShown(visible) { + button.editProperties({isActive: visible}); +} + +function onClicked(){ + DialogsManager.toggleAddressBar(); + onGotoScreen = !onGotoScreen; +} + +if (Settings.getValue("HUDUIEnabled")) { + toolBar = Toolbars.getToolbar("com.highfidelity.interface.toolbar.system"); + button = toolBar.addButton({ + objectName: buttonName, + imageURL: Script.resolvePath("assets/images/tools/directory.svg"), + visible: true, + alpha: 0.9 + }); +} else { + tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + button = tablet.addButton({ + icon: "icons/tablet-icons/goto-i.svg", + activeIcon: "icons/tablet-icons/goto-a.svg", + text: buttonName + }); +} + +button.clicked.connect(onClicked); +DialogsManager.addressBarShown.connect(onAddressBarShown); + +Script.scriptEnding.connect(function () { + if (onGotoScreen) { + DialogsManager.toggleAddressBar(); + } + button.clicked.disconnect(onClicked); + if (tablet) { + tablet.removeButton(button); + } + if (toolBar) { + toolBar.removeButton(buttonName); + } + DialogsManager.addressBarShown.disconnect(onAddressBarShown); +}); + +}()); // END LOCAL_SCOPE diff --git a/scripts/simplifiedUI/system/help.js b/scripts/simplifiedUI/system/help.js new file mode 100644 index 0000000000..40bbf6dbe2 --- /dev/null +++ b/scripts/simplifiedUI/system/help.js @@ -0,0 +1,29 @@ +"use strict"; +/* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */ +// +// help.js +// scripts/system/ +// +// Created by Howard Stearns on 2 Nov 2016 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +/* globals Tablet, Script, HMD, Controller, Menu */ + +(function () { // BEGIN LOCAL_SCOPE +var AppUi = Script.require('appUi'); + +var HELP_URL = Script.resourcesPath() + "html/tabletHelp.html"; +var HELP_BUTTON_NAME = "HELP"; +var ui; +function startup() { + ui = new AppUi({ + buttonName: HELP_BUTTON_NAME, + sortOrder: 6, + home: HELP_URL + }); +} +startup(); +}()); // END LOCAL_SCOPE diff --git a/scripts/simplifiedUI/system/hmd.js b/scripts/simplifiedUI/system/hmd.js new file mode 100644 index 0000000000..858b93ef1e --- /dev/null +++ b/scripts/simplifiedUI/system/hmd.js @@ -0,0 +1,92 @@ +"use strict"; + +// +// hmd.js +// scripts/system/ +// +// Created by Howard Stearns on 2 Jun 2016 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +/* globals HMD, Script, Menu, Tablet, Camera */ +/* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */ + +(function() { // BEGIN LOCAL_SCOPE + +var headset; // The preferred headset. Default to the first one found in the following list. +var displayMenuName = "Display"; +var desktopMenuItemName = "Desktop"; +['HTC Vive', 'Oculus Rift', 'WindowMS'].forEach(function (name) { + if (!headset && Menu.menuItemExists(displayMenuName, name)) { + headset = name; + } +}); + +var controllerDisplay = false; +function updateControllerDisplay() { + if (HMD.active && Menu.isOptionChecked("Third Person")) { + if (!controllerDisplay) { + HMD.requestShowHandControllers(); + controllerDisplay = true; + } + } else if (controllerDisplay) { + HMD.requestHideHandControllers(); + controllerDisplay = false; + } +} + +var button; +var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + +var switchToVR = "ENTER VR"; +var switchToDesktop = "EXIT VR"; + +function onHmdChanged(isHmd) { + HMD.closeTablet(); + if (isHmd) { + button.editProperties({ + icon: "icons/tablet-icons/switch-desk-i.svg", + text: switchToDesktop + }); + } else { + button.editProperties({ + icon: "icons/tablet-icons/switch-vr-i.svg", + text: switchToVR + }); + } + updateControllerDisplay(); +} + +function onClicked() { + var isDesktop = Menu.isOptionChecked(desktopMenuItemName); + Menu.setIsOptionChecked(isDesktop ? headset : desktopMenuItemName, true); + if (!isDesktop) { + UserActivityLogger.logAction("exit_vr"); + } +} + +if (headset) { + button = tablet.addButton({ + icon: HMD.active ? "icons/tablet-icons/switch-desk-i.svg" : "icons/tablet-icons/switch-vr-i.svg", + text: HMD.active ? switchToDesktop : switchToVR, + sortOrder: 2 + }); + onHmdChanged(HMD.active); + + button.clicked.connect(onClicked); + HMD.displayModeChanged.connect(onHmdChanged); + Camera.modeUpdated.connect(updateControllerDisplay); + + Script.scriptEnding.connect(function () { + button.clicked.disconnect(onClicked); + if (tablet) { + tablet.removeButton(button); + } + HMD.displayModeChanged.disconnect(onHmdChanged); + Camera.modeUpdated.disconnect(updateControllerDisplay); + }); +} + +}()); // END LOCAL_SCOPE diff --git a/scripts/simplifiedUI/system/html/ChatPage.html b/scripts/simplifiedUI/system/html/ChatPage.html new file mode 100644 index 0000000000..9606eeab3e --- /dev/null +++ b/scripts/simplifiedUI/system/html/ChatPage.html @@ -0,0 +1,511 @@ + + + + Chat + + + + + + + + +
+ +
+ Chat +
+ +
+ +
+ +
+ +
+ + + + + + diff --git a/scripts/simplifiedUI/system/html/EmoteApp.html b/scripts/simplifiedUI/system/html/EmoteApp.html new file mode 100644 index 0000000000..6b42fb8dc8 --- /dev/null +++ b/scripts/simplifiedUI/system/html/EmoteApp.html @@ -0,0 +1,138 @@ + + + + Emote App + + + + + + +
+

Emote App

+
+
+

Choose an emote:

+

+

+

+

+

+

+

+

+

+

+
+ + + + + \ No newline at end of file diff --git a/scripts/simplifiedUI/system/html/SnapshotReview.html b/scripts/simplifiedUI/system/html/SnapshotReview.html new file mode 100644 index 0000000000..f080cd204a --- /dev/null +++ b/scripts/simplifiedUI/system/html/SnapshotReview.html @@ -0,0 +1,38 @@ + + + Share + + + + + + + +
+ + + +
+
+
+
+
+
+
+
+
+
+ +
+ +
+
+ +
+ +
+
+ + diff --git a/scripts/simplifiedUI/system/html/css/SnapshotReview.css b/scripts/simplifiedUI/system/html/css/SnapshotReview.css new file mode 100644 index 0000000000..54d39aaad3 --- /dev/null +++ b/scripts/simplifiedUI/system/html/css/SnapshotReview.css @@ -0,0 +1,346 @@ +/* +// SnapshotReview.css +// +// Created by Howard Stearns for David Rowe 8/22/2016. +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +*/ + +/* +// START styling of top bar and its contents +*/ + +.title { + padding: 6px 10px; + text-align: left; + height: 26px; + line-height: 26px; + clear: both; +} + +.title label { + position: relative; + font-size: 18px; + float: left; +} + +#snapshotSettings { + position: relative; + float: right; +} +#settingsLabel { + position: relative; + float: right; + font-family: Raleway-SemiBold; + font-size: 14px; +} +.hifi-glyph { + font-size: 30px; + top: -4px; +} +input[type=button].naked { + color: #afafaf; + background: none; +} +input[type=button].naked:hover { + color: #ffffff; +} +input[type=button].naked:active { + color: #afafaf; +} +/* +// END styling of top bar and its contents +*/ + +/* +// START styling of snapshot instructions panel +*/ +.snapshotInstructions { + font-family: Raleway-Regular; + margin: 0 20px; + width: 100%; + height: 50%; +} +/* +// END styling of snapshot instructions panel +*/ + +/* +// START styling of snapshot pane and its contents +*/ +#snapshot-pane { + width: 100%; + height: 560px; + display: flex; + justify-content: center; + align-items: center; +} + +#snapshot-images { + width: 100%; + display: flex; + justify-content: center; + flex-direction: column; +} + +#snapshot-images img { + max-width: 100%; + max-height: 100%; +} + +.gifLabel { + position:absolute; + left: 15px; + top: 10px; + font-family: Raleway-SemiBold; + font-size: 18px; + color: white; + text-shadow: 2px 2px 3px #000000; +} +/* +// END styling of snapshot pane and its contents +*/ + +/* +// START styling of share overlay +*/ +.shareControls { + display: flex; + justify-content: space-between; + flex-direction: row; + align-items: center; + height: 65px; + line-height: 65px; + width: calc(100% - 8px); + position: absolute; + bottom: 4px; + left: 4px; + right: 4px; +} +.showShareButtonsButtonDiv { + display: inline-flex; + align-items: center; + font-family: Raleway-SemiBold; + font-size: 14px; + color: white; + width: 75px; + height: 100%; + margin-bottom: 0px; +} +.showShareButtonsButtonDiv.active:hover { + background-color: rgba(0, 0, 0, 0.45); + background-size: 2px; +} +.showShareButtonsButtonDiv > label { + text-shadow: 2px 2px 3px #000000; + margin-bottom: -14px; + margin-left: 12px; +} +.showShareButtonsButtonDiv:hover > label { + text-shadow: none; +} +.showShareButtonDots { + display: block; + width: 40px; + height: 40px; + font-family: HiFi-Glyphs; + font-size: 60px; + position: absolute; + left: 6px; + bottom: 32px; + color: white; + pointer-events: none; +} +.shareButtons { + display: flex; + align-items: flex-end; + height: 40px; + width: calc(100% - 60px); + margin-bottom: -24px; + margin-left: 0; +} +.shareButtons img { + width: 40px; + height: 40px; +} +.shareButton { + width: 40px; + height: 40px; + display: inline-block; +} +.shareButton.disabled { + background-color: #000000; + opacity: 0.5; +} +.shareControlsHelp { + height: 25px; + line-height: 25px; + position: absolute; + bottom: 40px; + left: 73px; + right: 0; + font-family: Raleway-Regular; + font-weight: 500; + font-size: 16px; + padding-left: 8px; + color: white; +} +.helpTextDiv { + width: 350px; + height: 65px; + margin-right: 15px; + line-height: 65px; + position: absolute; + bottom: 0; + right: 0; + font-family: Raleway-Regular; + font-weight: 500; + font-size: 16px; + color: white; +} +/* +// END styling of share overlay +*/ + +/* +// START styling of confirmation message +*/ +.confirmationMessageContainer { + width: 100%; + height: 100%; + position: absolute; + background-color: rgba(0, 0, 0, 0.45); + text-align: center; + left: 0; + top: 0; + pointer-events: none; + color: white; + font-weight: bold; + font-size: 16px; +} +.confirmationMessage { + width: 130px; + height: 130px; + margin: 50px auto 0 auto; +} +.confirmationMessage > img { + width: 72px; + height: 72px; + display: block; + margin: 0 auto; + padding: 10px 0 0 0; +} +/* +// END styling of uploading message +*/ + +/* +// START styling of snapshot controls (bottom panel) and its contents +*/ +#snapshot-controls { + width: 100%; + position: absolute; + left: 0; + overflow: hidden; + display: flex; + justify-content: center; +} +#snap-settings { + display: inline; + width: 150px; + margin: 2px auto 0 auto; +} +#snap-settings form input { + margin-bottom: 5px; +} + +#snap-button { + width: 72px; + height: 72px; + padding: 0; + border-radius: 50%; + background: #EA4C5F; + border: 3px solid white; + margin: 2px auto 0 auto; + box-sizing: content-box; + display: inline; + outline:none; +} +#snap-button:disabled { + background: gray; +} +#snap-button:hover:enabled { + background: #C62147; +} +#snap-button:active:enabled { + background: #EA4C5F; +} +#snap-settings-right { + display: inline; + width: 150px; + margin: auto; +} +/* +// END styling of snapshot controls (bottom panel) and its contents +*/ + + +/* +// START polaroid styling +*/ + +#print-button { + width: 72px; + height: 72px; + margin-left: 30px; + margin-top: -10px; + box-sizing: content-box; + display: inline; + outline:none; +} + +.print-icon { + margin: auto; +} + +.print-icon-default { + background: url(../img/button-snap-print.svg) no-repeat; + margin-right: -1px; + width: 64px; + height: 64px; +} + +.print-icon-loading { + background: url(../img/loader.gif) no-repeat; + width: 32px; + height: 32px; +} + +/* +// END polaroid styling +*/ + + +/* +// START misc styling +*/ +body { + padding: 0; + margin: 0; + overflow: hidden; +} +p { + margin: 2px 0; +} +h4 { + margin: 14px 0 0 0; +} +.centeredImage { + margin: 0 auto; + display: block; +} +/* +// END misc styling +*/ diff --git a/scripts/simplifiedUI/system/html/css/colpick.css b/scripts/simplifiedUI/system/html/css/colpick.css new file mode 100644 index 0000000000..fc50c4b3fb --- /dev/null +++ b/scripts/simplifiedUI/system/html/css/colpick.css @@ -0,0 +1,433 @@ +/* +colpick Color Picker / colpick.com +*/ + +/*Main container*/ +.colpick { + position: absolute; + width: 346px; + height: 170px; + overflow: hidden; + display: none; + font-family: Arial, Helvetica, sans-serif; + background:#ebebeb; + border: 1px solid #bbb; + -webkit-border-radius: 5px; + -moz-border-radius: 5px; + border-radius: 5px; + + /*Prevents selecting text when dragging the selectors*/ + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + -o-user-select: none; + user-select: none; +} +/*Color selection box with gradients*/ +.colpick_color { + position: absolute; + touch-action: none; + left: 7px; + top: 7px; + width: 156px; + height: 156px; + overflow: hidden; + outline: 1px solid #aaa; + cursor: crosshair; +} +.colpick_color_overlay1 { + position: absolute; + left:0; + top:0; + width: 156px; + height: 156px; + -ms-filter: "progid:DXImageTransform.Microsoft.gradient(GradientType=1,startColorstr='#ffffff', endColorstr='#00ffffff')"; /* IE8 */ + background: -moz-linear-gradient(left, rgba(255,255,255,1) 0%, rgba(255,255,255,0) 100%); /* FF3.6+ */ + background: -webkit-gradient(linear, left top, right top, color-stop(0%,rgba(255,255,255,1)), color-stop(100%,rgba(255,255,255,0))); /* Chrome,Safari4+ */ + background: -webkit-linear-gradient(left, rgba(255,255,255,1) 0%,rgba(255,255,255,0) 100%); /* Chrome10+,Safari5.1+ */ + background: -o-linear-gradient(left, rgba(255,255,255,1) 0%,rgba(255,255,255,0) 100%); /* Opera 11.10+ */ + background: -ms-linear-gradient(left, rgba(255,255,255,1) 0%,rgba(255,255,255,0) 100%); /* IE10+ */ + background: linear-gradient(to right, rgba(255,255,255,1) 0%, rgba(255,255,255,0) 100%); + filter: progid:DXImageTransform.Microsoft.gradient(GradientType=1,startColorstr='#ffffff', endColorstr='#00ffffff'); /* IE6 & IE7 */ +} +.colpick_color_overlay2 { + position: absolute; + left:0; + top:0; + width: 156px; + height: 156px; + -ms-filter: "progid:DXImageTransform.Microsoft.gradient(GradientType=0,startColorstr='#00000000', endColorstr='#000000')"; /* IE8 */ + background: -moz-linear-gradient(top, rgba(0,0,0,0) 0%, rgba(0,0,0,1) 100%); /* FF3.6+ */ + background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(0,0,0,0)), color-stop(100%,rgba(0,0,0,1))); /* Chrome,Safari4+ */ + background: -webkit-linear-gradient(top, rgba(0,0,0,0) 0%,rgba(0,0,0,1) 100%); /* Chrome10+,Safari5.1+ */ + background: -o-linear-gradient(top, rgba(0,0,0,0) 0%,rgba(0,0,0,1) 100%); /* Opera 11.10+ */ + background: -ms-linear-gradient(top, rgba(0,0,0,0) 0%,rgba(0,0,0,1) 100%); /* IE10+ */ + background: linear-gradient(to bottom, rgba(0,0,0,0) 0%,rgba(0,0,0,1) 100%); /* W3C */ + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#00000000', endColorstr='#000000',GradientType=0 ); /* IE6-9 */ +} +/*Circular color selector*/ +.colpick_selector_outer { + background:none; + position: absolute; + width: 11px; + height: 11px; + margin: -6px 0 0 -6px; + border: 1px solid black; + border-radius: 50%; +} +.colpick_selector_inner{ + position: absolute; + width: 9px; + height: 9px; + border: 1px solid white; + border-radius: 50%; +} +/*Vertical hue bar*/ +.colpick_hue { + position: absolute; + touch-action: none; + top: 6px; + left: 175px; + width: 19px; + height: 156px; + border: 1px solid #aaa; + cursor: n-resize; +} +/*Hue bar sliding indicator*/ +.colpick_hue_arrs { + position: absolute; + touch-action: none; + left: -8px; + width: 35px; + height: 7px; + margin: -7px 0 0 0; +} +.colpick_hue_larr { + position:absolute; + touch-action: none; + width: 0; + height: 0; + border-top: 6px solid transparent; + border-bottom: 6px solid transparent; + border-left: 7px solid #858585; +} +.colpick_hue_rarr { + position:absolute; + touch-action: none; + right:0; + width: 0; + height: 0; + border-top: 6px solid transparent; + border-bottom: 6px solid transparent; + border-right: 7px solid #858585; +} +/*New color box*/ +.colpick_new_color { + position: absolute; + touch-action: none; + left: 207px; + top: 6px; + width: 60px; + height: 27px; + background: #f00; + border: 1px solid #8f8f8f; +} +/*Current color box*/ +.colpick_current_color { + position: absolute; + touch-action: none; + left: 277px; + top: 6px; + width: 60px; + height: 27px; + background: #f00; + border: 1px solid #8f8f8f; +} +/*Input field containers*/ +.colpick_field, .colpick_hex_field { + position: absolute; + touch-action: none; + height: 20px; + width: 60px; + overflow:hidden; + background:#f3f3f3; + color:#b8b8b8; + font-size:12px; + border:1px solid #bdbdbd; + -webkit-border-radius: 3px; + -moz-border-radius: 3px; + border-radius: 3px; +} +.colpick_rgb_r { + top: 40px; + left: 207px; +} +.colpick_rgb_g { + top: 67px; + left: 207px; +} +.colpick_rgb_b { + top: 94px; + left: 207px; +} +.colpick_hsb_h { + top: 40px; + left: 277px; +} +.colpick_hsb_s { + top: 67px; + left: 277px; +} +.colpick_hsb_b { + top: 94px; + left: 277px; +} +.colpick_hex_field { + width: 68px; + left: 207px; + top: 121px; +} +/*Text field container on focus*/ +.colpick_focus { + border-color: #999; +} +/*Field label container*/ +.colpick_field_letter { + position: absolute; + width: 12px; + height: 20px; + line-height: 20px; + padding-left: 4px; + background: #efefef; + border-right: 1px solid #bdbdbd; + font-weight: bold; + color:#777; +} +/*Text inputs*/ +.colpick_field input, .colpick_hex_field input { + position: absolute; + touch-action: none; + right: 11px; + margin: 0; + padding: 0; + height: 20px; + line-height: 20px; + background: transparent; + border: none; + font-size: 12px; + font-family: Arial, Helvetica, sans-serif; + color: #555; + text-align: right; + outline: none; +} +.colpick_hex_field input { + right: 4px; +} +/*Field up/down arrows*/ +.colpick_field_arrs { + position: absolute; + touch-action: none; + top: 0; + right: 0; + width: 9px; + height: 21px; + cursor: n-resize; +} +.colpick_field_uarr { + position: absolute; + touch-action: none; + top: 5px; + width: 0; + height: 0; + border-left: 4px solid transparent; + border-right: 4px solid transparent; + border-bottom: 4px solid #959595; +} +.colpick_field_darr { + position: absolute; + touch-action: none; + bottom:5px; + width: 0; + height: 0; + border-left: 4px solid transparent; + border-right: 4px solid transparent; + border-top: 4px solid #959595; +} +/*Submit/Select button*/ +.colpick_submit { + position: absolute; + touch-action: none; + left: 207px; + top: 149px; + width: 130px; + height: 22px; + line-height:22px; + background: #efefef; + text-align: center; + color: #555; + font-size: 12px; + font-weight:bold; + border: 1px solid #bdbdbd; + -webkit-border-radius: 3px; + -moz-border-radius: 3px; + border-radius: 3px; +} +.colpick_submit:hover { + background:#f3f3f3; + border-color:#999; + cursor: pointer; +} + +/*full layout with no submit button*/ +.colpick_full_ns .colpick_submit { + display:none; +} +.colpick_full_ns .colpick_new_color { + width: 130px; + height: 25px; +} +.colpick_full_ns .colpick_rgb_r, .colpick_full_ns .colpick_hsb_h { + top: 42px; +} +.colpick_full_ns .colpick_rgb_g, .colpick_full_ns .colpick_hsb_s { + top: 73px; +} +.colpick_full_ns .colpick_rgb_b, .colpick_full_ns .colpick_hsb_b { + top: 104px; +} +.colpick_full_ns .colpick_hex_field { + top: 135px; +} + +/*rgbhex layout*/ +.colpick_rgbhex .colpick_hsb_h, .colpick_rgbhex .colpick_hsb_s, .colpick_rgbhex .colpick_hsb_b { + display:none; +} +.colpick_rgbhex { + width:282px; +} +.colpick_rgbhex .colpick_field, .colpick_rgbhex .colpick_submit { + width:68px; +} +.colpick_rgbhex .colpick_new_color { + width:34px; + border-right:none; +} +.colpick_rgbhex .colpick_current_color { + width:34px; + left:240px; + border-left:none; +} + +/*rgbhex layout, no submit button*/ +.colpick_rgbhex_ns .colpick_submit { + display:none; +} +.colpick_rgbhex_ns .colpick_new_color{ + width:34px; + border: 1px solid #8f8f8f; +} +.colpick_rgbhex_ns .colpick_rgb_r { + top: 42px; +} +.colpick_rgbhex_ns .colpick_rgb_g { + top: 73px; +} +.colpick_rgbhex_ns .colpick_rgb_b { + top: 104px; +} +.colpick_rgbhex_ns .colpick_hex_field { + top: 135px; +} + +/*hex layout*/ +.colpick_hex .colpick_hsb_h, .colpick_hex .colpick_hsb_s, .colpick_hex .colpick_hsb_b, .colpick_hex .colpick_rgb_r, .colpick_hex .colpick_rgb_g, .colpick_hex .colpick_rgb_b { + display:none; +} +.colpick_hex { + width:206px; + height:201px; +} +.colpick_hex .colpick_hex_field { + width:72px; + height:25px; + top:168px; + left:80px; +} +.colpick_hex .colpick_hex_field div, .colpick_hex .colpick_hex_field input { + height: 25px; + line-height: 25px; +} +.colpick_hex .colpick_new_color { + left:9px; + top:168px; + width:30px; + border-right:none; +} +.colpick_hex .colpick_current_color { + left:39px; + top:168px; + width:30px; + border-left:none; +} +.colpick_hex .colpick_submit { + left:164px; + top: 168px; + width:30px; + height:25px; + line-height: 25px; +} + +/*hex layout, no submit button*/ +.colpick_hex_ns .colpick_submit { + display:none; +} +.colpick_hex_ns .colpick_hex_field { + width:80px; +} +.colpick_hex_ns .colpick_new_color{ + width:60px; + border: 1px solid #8f8f8f; +} + +/*Dark color scheme*/ +.colpick_dark { + background: #161616; + border-color: #2a2a2a; +} +.colpick_dark .colpick_color { + outline-color: #333; +} +.colpick_dark .colpick_hue { + border-color: #555; +} +.colpick_dark .colpick_field, .colpick_dark .colpick_hex_field { + background: #101010; + border-color: #2d2d2d; +} +.colpick_dark .colpick_field_letter { + background: #131313; + border-color: #2d2d2d; + color: #696969; +} +.colpick_dark .colpick_field input, .colpick_dark .colpick_hex_field input { + color: #7a7a7a; +} +.colpick_dark .colpick_field_uarr { + border-bottom-color:#696969; +} +.colpick_dark .colpick_field_darr { + border-top-color:#696969; +} +.colpick_dark .colpick_focus { + border-color:#444; +} +.colpick_dark .colpick_submit { + background: #131313; + border-color:#2d2d2d; + color:#7a7a7a; +} +.colpick_dark .colpick_submit:hover { + background-color:#101010; + border-color:#444; +} \ No newline at end of file diff --git a/scripts/simplifiedUI/system/html/css/edit-style.css b/scripts/simplifiedUI/system/html/css/edit-style.css new file mode 100644 index 0000000000..470e57ad6d --- /dev/null +++ b/scripts/simplifiedUI/system/html/css/edit-style.css @@ -0,0 +1,1796 @@ +/* +// edit-style.css +// +// Created by Ryan Huffman on 13 Nov 2014 +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +*/ + +@font-face { + font-family: Raleway-Regular; + src: url(../../../../resources/fonts/Raleway-Regular.ttf), /* Windows production */ + url(../../../../fonts/Raleway-Regular.ttf), /* OSX production */ + url(../../../../interface/resources/fonts/Raleway-Regular.ttf), /* Development, running script in /HiFi/examples */ + url(../fonts/Raleway-Regular.ttf); /* Marketplace script */ +} + +@font-face { + font-family: Raleway-Light; + src: url(../../../../resources/fonts/Raleway-Light.ttf), + url(../../../../fonts/Raleway-Light.ttf), + url(../../../../interface/resources/fonts/Raleway-Light.ttf), + url(../fonts/Raleway-Light.ttf); +} + +@font-face { + font-family: Raleway-Bold; + src: url(../../../../resources/fonts/Raleway-Bold.ttf), + url(../../../../fonts/Raleway-Bold.ttf), + url(../../../../interface/resources/fonts/Raleway-Bold.ttf), + url(../fonts/Raleway-Bold.ttf); +} + +@font-face { + font-family: Raleway-SemiBold; + src: url(../../../../resources/fonts/Raleway-SemiBold.ttf), + url(../../../../fonts/Raleway-SemiBold.ttf), + url(../../../../interface/resources/fonts/Raleway-SemiBold.ttf), + url(../fonts/Raleway-SemiBold.ttf); +} + +@font-face { + font-family: FiraSans-SemiBold; + src: url(../../../../resources/fonts/FiraSans-SemiBold.ttf), + url(../../../../fonts/FiraSans-SemiBold.ttf), + url(../../../../interface/resources/fonts/FiraSans-SemiBold.ttf), + url(../fonts/FiraSans-SemiBold.ttf); +} + +@font-face { + font-family: AnonymousPro-Regular; + src: url(../../../../resources/fonts/AnonymousPro-Regular.ttf), + url(../../../../fonts/AnonymousPro-Regular.ttf), + url(../../../../interface/resources/fonts/AnonymousPro-Regular.ttf), + url(../fonts/AnonymousPro-Regular.ttf); +} + +@font-face { + font-family: HiFi-Glyphs; + src: url(../../../../resources/fonts/hifi-glyphs.ttf), + url(../../../../fonts/hifi-glyphs.ttf), + url(../../../../interface/resources/fonts/hifi-glyphs.ttf), + url(../fonts/hifi-glyphs.ttf); +} + +* { + margin: 0; + padding: 0; +} + +body { + + color: #afafaf; + background-color: #404040; + font-family: Raleway-Regular; + font-size: 12px; + + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + + overflow-x: hidden; + overflow-y: auto; +} + +table { + font-family: FiraSans-SemiBold; + font-size: 15px; + color: #afafaf; + border-collapse: collapse; + width: 100%; + border: 2px solid #575757; + border-radius: 7px; +} + +thead { + font-family: Raleway-Regular; + font-size: 12px; + text-transform: uppercase; + background-color: #1c1c1c; + padding: 1px 0; + border-bottom: 1px solid #575757; + width: 100%; +} + +tbody { + width: 100%; + display: block; +} + +tfoot { + font-family: Raleway-Light; + font-size: 13px; + background-color: #1c1c1c; + border-top: 1px solid #575757; + width: 100%; +} + +tfoot tr { + background-color: #1c1cff; +} + +thead tr { + height: 26px; /* 28px with thead padding */ +} + +thead th { + height: 26px; + background-color: #1c1c1c; + border-right: 1px solid #575757; +} + +thead th:last-child { + border: none; +} + +tbody td { + height: 26px; +} + +tfoot td { + height: 18px; + width: 100%; + background-color: #1c1c1c; + margin-left: 12px; +} + +tr { + width: 100%; + cursor: pointer; +} + +tr:nth-child(odd) { + background-color: #2e2e2e; +} + +tr:nth-child(even) { + background-color: #1c1c1c; +} + +tr:focus { + outline: none; +} + +tr.selected { + color: #000000; + background-color: #00b4ef; +} + +tr.selected + tr.selected { + border-top: 1px solid #2e2e2e; +} + +th { + text-align: center; + word-wrap: nowrap; + white-space: nowrap; + padding-left: 12px; + padding-right: 12px; +} + +td { + overflow: hidden; + text-overflow: clip; + white-space: nowrap; + word-wrap: nowrap; + padding-left: 12px; + padding-right: 12px; +} + +td.hidden { + padding-left: 0; + padding-right: 0; +} + +td.url { + white-space: nowrap; + overflow: hidden; +} + + +input[type="text"], input[type="search"], input[type="number"], textarea { + margin: 0; + padding: 0 12px; + color: #afafaf; + background-color: #252525; + border: none; + font-family: FiraSans-SemiBold; + font-size: 15px; +} + +textarea { + font-family: AnonymousPro-Regular; + font-size: 16px; + padding-top: 5px; + padding-bottom: 5px; + min-height: 64px; + width: 100%; + resize: vertical; +} + +input::-webkit-input-placeholder { + font-style: italic; +} + +input:focus, textarea:focus, button:focus { + color: #fff; + background-color: #000; + outline: 1px solid #00b4ef; + outline-offset: -1px; +} + +input::selection, textarea::selection { + color: #000000; + background-color: #00b4ef; +} + +input.search { + border-radius: 14px; +} + +input.search:focus { + outline: none; + box-sizing: border-box; + height: 26px; + margin-top: 1px; + margin-bottom: 1px; + box-shadow: 0 0 0 1px #00b4ef; +} + +input:disabled, textarea:disabled, .draggable-number.text[disabled="disabled"] { + background-color: #383838; + color: #afafaf; +} + +input[type="text"] { + height: 28px; + width: 100%; +} + +input.multi-diff:not(:focus) + span.multi-diff, +textarea.multi-diff:not(:focus) + span.multi-diff, +.draggable-number.multi-diff>input:not(:focus)+span.multi-diff, +dl>dt.multi-diff:not(:focus) + span.multi-diff { + visibility: visible; + position: absolute; + display: inline-block; + z-index: 2; + top: 7.5px; + left: 20px; + max-width: 50px; + min-width: 10px; + width: 50%; + height: 13px; + background-image: linear-gradient(transparent 0%, transparent 10%, #afafaf 10%, #afafaf 20%, transparent 20%, transparent 45%, #afafaf 45%, #afafaf 55%, transparent 55%, transparent 80%, #afafaf 80%, #afafaf 90%, transparent 90%, transparent 100%); + background-repeat: no-repeat; + pointer-events: none; +} + +input.multi-diff:not(:focus)::-webkit-input-placeholder, input.multi-diff:not(:focus) { + color: transparent; +} + +.draggable-number.multi-diff .text { + color: transparent; +} + +.dropdown > span.multi-diff { + top: 5px; + left: 10px; +} + +.text, .url, .texture, .textarea { + position: relative; +} + +input[type="search"] { + height: 28px; + width: 100%; +} +input[type="search"]::-webkit-search-cancel-button { + -webkit-appearance: none; + height: 20px; + width: 20px; + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAACXBIWXMAAC4jAAAuIwF4pT92AAAAB3RJTUUH4goNAQIFbBwsbwAAABl0RVh0Q29tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAAZfSURBVDgRAVQGq/kAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA9PT0YAwMDBgAAAAD8/Pz5+vr67MrKyv0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAA+Pj4KAgICQgAAAE3///9RAQEBFQAAAAD////pAQEBu/39/ab+/v7BxcXF9gAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAADs7OzMEBASIAQEBRAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP8AAACm+/v7cMXFxewAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAPT09OwEBAagBAQEcAQEBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8AAAAA2f///2XCwsLDAAAAAAAAAAABAAAAAAAAAAA9PT0KAwMDt////z4AAAAAAAAAAAEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAcIBAQFJvr6+9gAAAAACAAAAAAAAAAAAAABg////PgEBAQAAAAAAS0tLADg4OAAAAAAAAAAAAP///wADAwMAQEBAACEhIQD///8A////AP7+/j76+vpWAAAAAAAAAAACAAAAAD09PQ8CAgJkAQEBAP///wD///8ACgoKAFhYWAAyMjIAAAAAAAICAgBGRkYAT09PABEREQAAAAAAAAAAAAAAAAACAgJwOjo6EAAAAAAEAAAAAAICAg8BAQExAAAAAAEBAQABAQEAsrKyAAoKCgBaWloA9/f3ABsbGwBISEgAtra2AM7OzgACAgIA////AP///wABAQEuBQUFDgAAAPAEAAAAAPz8/BkEBAQAAQEBAAAAAAAAAAAA+vr6AKioqAALCwsAZWVlAAcHBwC/v78Au7u7AAEBAQD///8AAAAAAAAAAAAAAAABAAAAAAAAAAACAAAAAAQEBOgBAQEAAQEBAAEBAQABAQEAAQEBAPz8/ADT09MADg4OAP39/QDQ0NAA/v7+AP///wAAAAAAAAAAAAEBAQABAQEAAQEBAAAAAAACAAAAAAAAAP8AAAD/AAAAAAAAAAAAAAAAAAAAACkpKQBQUFAAx8fHAObm5gBfX18AFxcXAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAP39/fz+/v7z////AP///wD///8AJycnAGFhYQDc3NwApaWlAJaWlgD29vYAZmZmABQUFAACAgIAAQEBAAEBAQABAQH1AAAA/AAAAAACAAAAAPr6+ukBAQGkAAAAAAAAAAABAQEAQEBAAObm5gCmpqYA+fn5APPz8wCdnZ0A////ACwsLAD///8AAAAAAAAAAAD///+k9vb26QAAAAABAAAAAAAAAAA+Pj4uAgICxgAAAAsAAAAAEBAQAPr6+gD29vYAAAAAAAAAAAABAQEAAgICAP///wD+/v4AAAAAAAAAAPL8/Pw/xMTE0AAAAAACAAAAAAAAAAD5+fnV////nQICAgABAQEA8fHxAPX19QABAQEAAAAAAAAAAAD///8A/v7+AP7+/gAAAAAAAAAAAP7+/p36+vrSAAAAAAAAAAADAAAAAAAAAADl5eX/ICAgwQAAAA////8q////BgEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD1/f39mAEBAXrGxsb7AAAAAAAAAAADAAAAAAAAAAAAAAAA4eHh/BgYGLsBAQHDBAQEHAAAACP///8AAQEBAAAAAAAAAAAAAAAA+////7QBAQFu+fn5m8bGxvoAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPz8/Cv7+/iUBAQFMAgICEQICAgD8/PzdAwMDs/j4+OvHx8f5AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8TUnpZ7EwQgAAAABJRU5ErkJggg==') +} + +input[type="number"] { + position: relative; + height: 28px; + width: 124px; +} +input[type=number] { + padding-right: 3px; +} +input[type=number]::-webkit-inner-spin-button { + opacity: 1.0; + display: block; + position: relative; + width: 10px; + height: 90%; + overflow: hidden; + font-family: HiFi-Glyphs; + font-size: 32px; + color: #afafaf; + cursor: pointer; + background-color: #000000; +} +input[type=number]::-webkit-inner-spin-button:before, +input[type=number]::-webkit-inner-spin-button:after { + position:absolute; + left: -19px; + line-height: 8px; + text-align: center; +} +input[type=number]::-webkit-inner-spin-button:before { + content: "6"; + top: 4px; +} +input[type=number]::-webkit-inner-spin-button:after { + content: "5"; + bottom: 4px; +} + +input[type=number].hover-up::-webkit-inner-spin-button:before, +input[type=number].hover-down::-webkit-inner-spin-button:after { + color: #ffffff; +} + +input[type=range] { + -webkit-appearance: none; + background: #2e2e2e; + height: 1.8rem; + border-radius: 1rem; +} +input[type=range]::-webkit-slider-thumb { + -webkit-appearance:none; + width: 0.6rem; + height: 1.8rem; + padding:0; + margin: 0; + background-color: #696969; + border-radius: 1rem; +} +input[type=range]::-webkit-slider-thumb:hover { + background-color: white; +} +input[type=range]:focus { + outline: none; +} + +input.no-spin::-webkit-outer-spin-button, +input.no-spin::-webkit-inner-spin-button { + display: none; + -webkit-appearance: none; + margin: 0; /* <-- Apparently some margin are still there even though it's hidden */ + padding-right: 12px; +} + +input[type=button], button.hifi-edit-button { + font-family: Raleway-Bold; + font-size: 13px; + text-transform: uppercase; + vertical-align: top; + height: 28px; + min-width: 120px; + padding: 0 18px; + margin-right: 6px; + border-radius: 5px; + border: none; + color: #fff; + background-color: #000; + background: linear-gradient(#343434 20%, #000 100%); + cursor: pointer; +} + +input[type=button].glyph, button.hifi-edit-button.glyph { + font-family: HiFi-Glyphs; + font-size: 20px; + text-transform: none; + min-width: 32px; + padding: 0; +} + +input[type=button].red, button.hifi-edit-button.red { + color: #fff; + background-color: #94132e; + background: linear-gradient(#d42043 20%, #94132e 100%); +} +input[type=button].blue, button.hifi-edit-button.blue { + color: #fff; + background-color: #1080b8; + background: linear-gradient(#00b4ef 20%, #1080b8 100%); +} +input[type=button].white, button.hifi-edit-button.white { + color: #121212; + background-color: #afafaf; + background: linear-gradient(#fff 20%, #afafaf 100%); +} + +input[type=button]:enabled:hover, button.hifi-edit-button:enabled:hover { + background: linear-gradient(#000, #000); + border: none; +} +input[type=button].red:enabled:hover, button.hifi-edit-button.red:enabled:hover { + background: linear-gradient(#d42043, #d42043); + border: none; +} +input[type=button].blue:enabled:hover, button.hifi-edit-button.blue:enabled:hover { + background: linear-gradient(#00b4ef, #00b4ef); + border: none; +} +input[type=button].white:enabled:hover, button.hifi-edit-button.white:enabled:hover { + background: linear-gradient(#fff, #fff); + border: none; +} + +input[type=button]:active, button.hifi-edit-button:active { + background: linear-gradient(#343434, #343434); +} +input[type=button].red:active, button.hifi-edit-button.red:active { + background: linear-gradient(#94132e, #94132e); +} +input[type=button].blue:active, button.hifi-edit-button.blue:active { + background: linear-gradient(#1080b8, #1080b8); +} +input[type=button].white:active, button.hifi-edit-button.white:active { + background: linear-gradient(#afafaf, #afafaf); +} + +input[type=button]:disabled, button.hifi-edit-button:disabled { + color: #252525; + background: linear-gradient(#575757 20%, #252525 100%); +} + +input[type=button][pressed=pressed], button.hifi-edit-button[pressed=pressed] { + color: #00b4ef; +} + +input[type=checkbox] { + display: none; +} +input[type=checkbox] + label { + padding-left: 24px; + background-repeat: no-repeat; + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAACpSURBVDhPY2xoaGD68+dPMSMjY9L////VgTQjAw4AlH8PxLOPHj1azWxjY1MBVNsBFBfBpwkEgNKcQGwtJyfHyATkF0KEiQdAzYlMQEIUyicFyDD9+/ePgRxMvsb///4zkIOZ/v0HmkAGHginYjGNGAzS+BpdkAj8mun/3//92DyPD//993cG88nTJ4+Zm5p/BSZeJYb/DEJADEzNOPF7hn8Mk69cvVIPAHN5pyfo70F5AAAAAElFTkSuQmCC); + cursor: pointer; +} +input[type=checkbox]:enabled + label:hover { + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAClSURBVDhPY2hoaGD6//9/6b9//64D8T8gGycASr/7+/dv5/79+1kYgIxKqDjRAKiniRFIv2JgYBAFYlLAE0aQ66AckgDjjx8/yNP44cMH8jS+fPmSPI0PHz4kT+PNmzfJ03jp0iXyNJ46dYo8jYcPHyYnAbxm+vnzZz8wLhlIwd+/f5/BrKSkdExCQuLrnz9/lIBpUAiIQekXF34PTGmTT548WQ8AokXg+rhVtPYAAAAASUVORK5CYII=); +} +input[type=checkbox]:checked + label { + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAFLSURBVDhPjZK9SgNBFIXvbCaQRDQq7mIhQRPBRisJKcwLWOobaCsExEaxcEEQe0trGysfwg0EwWoDsbFIJUaIBJOwus547saNP3FlPzgzzJxzL5edFbZtG77v7wkhtrXWS9gFRQC/DZ07jnOYKJfL+8ie4n7mvyIGdhpay+VyQuK8y5dPZoHuVtbpZcLi4wjJ1x4t316R9dDgBlsSi8mGu7pJjyJFzVaH+r7iqyHSELSQzVADjS0UgjlDKUUsLzVO98+9kSLGV5qaHXhjU0GWNSxk3hCIwnsfeMNCjTArLmHeUBodoLiE+R+jxuHPUZP4elGE3teonx2S/Q7lJzOUlkYQ+A4/xzyegzNhXmJpwTMXry9IFjcoa84O0r+QXpcK1cugCLREZadyoA19Ergxwf96nKjd1KqlYqmLQ540TUNwItUmRWdu3T36AODjwgpY9xqqAAAAAElFTkSuQmCC); +} +input[type=checkbox]:checked + label:hover { + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAEySURBVDhPnZLPSsNAEMa/XVPBCE0RhNy0OarP4Av4AD6JB0GwVBA8efBBxHsgh4CQswcRoUIpiIpVAm3zZ5M4szFSbQPBH3xkJvNNZskOer2eLIriKM/ze1JOcS1UHmdZduF5ngEKjr/fN4Z6+oKerwA2gxC4HAFPEWVLsAzgZAvYt3Q6Enw6jg7uBAaTFMNwhpnKdbXCkAJdy8ROu4XrXW2HTJIErHcFDD6nC02Mom8PwymeE2gvS0ZRBBaTlsOXEmdlrfLLOI7Bakrl/zWxCT8T/904f9QW/b06qtrCUdtFCqdjYs2Q2jAPX8c2XQd7Kr/wfV8vwIPs4Ga1ixe5Xrr/YFLTYfKIvWzM6ZtwXZdX7lxXG0L+sxXHcW5t254opRzawQ0S72+dPmjTroIgOP0CQSMt5LDn1T8AAAAASUVORK5CYII=); +} +input.multi-diff[type=checkbox] + label { + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAFbSURBVDhPY2xoaGD68+dPMSMjY9L////VgTQjAw4AlH8PxLOPHj1azWxjY1MBVNsBFBfBpwkEgNKcQGwtJyfHyALkF4IE34gqM9zU9WT4wicG4mIA1l/fGIyOL2EQeP8EZEAiC5AQBUlcMQ5ieMXIwfDo9SeG73/+gRXDAAsTI4Pd9wdgTVAgw/Tv3z8GEP7Jwctw78M3DE0goPr6BoPludVgdTAM1wgCv//9B9PIQOPNDYaAGxtRNIEw03+gYhDGBtSBNgVc3wiWR8dM//4DTQBidKD++jqD//X1YDlsGMWpMKD26jqD79V1GM5DxihOZQWGntqrawy+V9ZiOA0dw21k/f6JwerzHQbvS2swTMeGGfPz8l8BLRP9KizDwP0WHk+EwGum/3//94M8y/nmEdZAwIb//vs7g/nk6ZPHzE3NvwITrxLDfwYhIAamZpz4PcM/hslXrl6pBwAmfz5iaAlAuAAAAABJRU5ErkJggg==); +} +input.multi-diff[type=checkbox] + label:hover { + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAFPSURBVDhPjZJBSwJBHMXfrG6rWEkl1MFDGOihDn2JIIrqc3QJunbyFhUkRieD+hYepWteuxctXiJ1Q5xmdmZ3bWZTUHezfvAu/3lv3n+HRblcTrbb7fN+v/8eBMFgFpxz13Gcu3q9bqHb7V4M5/9GhatE3cIsy0o99YBKC3jliCWbBK43gK0MoDI9otfTB/vPBC9Uwu4xMC8IzSOSBsFxIYNqMTGcAIYQAlodD3j5/IqENIc5gqt1P/SNZKhaXR0a5E/5BEcrwH1xEHrGZbiuC604DpZ81AoiPJ/WROM4e4sSt3kaaRopNrg7z1FZdSLmcU2saqrX20lTXC5/RFabFmk2m+GLnBnbWJMOThJv4SV/QRqNBjNNM9UiGeQHdDiejZSSG5TSG71zjnVivyVOKlNLlEqlx+xCds7zvU31G6Z938dvEq4QjLMH27ZPvwHFVYQr3h7uHwAAAABJRU5ErkJggg==); +} + +.rgb.fstuple .color-picker.multi-diff:after { + width: 20px; + height: 20px; + content: ' '; + background: darkgray; + display: flex; + clip-path: polygon(0 0, 0 100%, 100% 100%); +} + +.icon-input input { + position: relative; + padding-left: 36px; +} +.icon-input span { + position: absolute; + left: 6px; + top: -2px; + font-family: HiFi-Glyphs; + font-size: 30px; + color: #afafaf; +} +.icon-input input:focus + span { + color: #ffffff; +} + +.icon { + font-family: HiFi-Glyphs; + color: white; +} + +#property-type-icon { + font-size: 50px; +} + +.selectable { + -webkit-touch-callout: text; + -webkit-user-select: text; + -khtml-user-select: text; + -moz-user-select: text; + -ms-user-select: text; + user-select: text; + cursor: text; +} + +.color-box { + display: inline-block; + width: 15pt; + height: 15pt; + border: 0.75pt solid black; + margin: 1.5pt; + cursor: pointer; +} + +.color-box.highlight { + width: 13.5pt; + height: 13.5pt; + border: 1.5pt solid black; +} + +#properties-list { + display: flex; + flex-direction: column; + + margin-top: 16px; +} + +#properties-list .fieldset { + position: relative; + /* 0.1px on the top is to prevent margin collapsing between this and it's first child */ + margin: 0 -21px 21px -21px; + padding: 0.1px 21px 0 21px; + border: none; + border-top: 1px rgb(90,90,90) solid; + box-shadow: 0 -1px 0 rgb(37,37,37); +} + +#properties-list .fieldset.fstuple, #properties-list .fieldset.fsrow { + margin-top: 21px; + border: none; + box-shadow: none; +} + +#properties-list > .fieldset[data-collapsed="true"] + .fieldset { + margin-top: 0; +} + +#properties-list > .fieldset[data-collapsed="true"] > *:not(div.legend) { + display: none !important; +} + +.section-header { + padding: 0 16px; + border-top: 1px rgb(90,90,90) solid; + box-shadow: 1px -1px 0 rgb(37,37,37); + border-bottom: 1px solid rgb(37, 37, 37); +} + +div.section-header, hr { + display: flex; + flex-flow: row nowrap; + padding: 10px 16px; + font-family: Raleway-Regular; + font-size: 12px; + color: #afafaf; + height: 28px; + text-transform: uppercase; + outline: none; + margin-bottom: 10px; + align-items: center; +} + +.section.minor { + margin: 0 21px; + box-shadow: 1px -1px 0 rgb(37,37,37); + border-left: 1px solid #575757; +} + +.container.property { + padding: 0 16px; +} + +.stretch { + width: 100%; +} + +div.section-header .label { + width: 100%; +} + +.section.minor div.section-header { + border-right: 0; +} + +div.section[collapsed="true"] > .container { + display: none; +} + +div.section[collapsed="true"], div.section[collapsed="true"] > .section-header { + margin-bottom: 0; +} + +.section.major { + margin-bottom: 20px; +} + +.section.minor.last { + margin-bottom: 20px; + border-bottom: 1px solid rgb(37,37,37); +} + +.section-header { + background-color: #373737; +} + + +.section-header span { + font-size: 30px; + font-family: HiFi-Glyphs; +} + +.triple-label { + text-transform: uppercase; + text-align: center; + padding: 6px 0; + cursor: default; +} + +.triple-item { + margin-right: 10px; +} + +.triple-item.rgb.fstuple { + display: block !important; +} + +.section-header[collapsed="true"] { + margin-bottom: -21px; +} + +#properties-list .sub-section-header { + border-top: none; + box-shadow: none; + margin-top: 8px; +} + +.sub-section-header + .property { + margin-top: 0; +} + +hr { + border: none; + padding-top: 2px; +} + +.property { + min-height: 28px; +} + +.property.checkbox { + width: auto; +} + +span.indented { + padding-left: 16px; +} + +.property label, .number label { + display: table-cell; + vertical-align: middle; + font-family: Raleway-SemiBold; + font-size: 14px; +} +.property label .unit, .number label .unit { + margin-left: 8px; + font-family: Raleway-Light; + font-size: 13px; +} + +.property div.legend, .number div.legend { + display: table-cell; + vertical-align: middle; + font-family: Raleway-SemiBold; + font-size: 14px; +} +.property div.legend .unit, .number div.legend .unit { + margin-left: 8px; + font-family: Raleway-Light; + font-size: 13px; +} + +.value { + display: block; + min-height: 18px; +} +.value label { + display: inline-block; + vertical-align: top; +} +.value div.legend { + display: inline-block; + vertical-align: top; + width: 48px; +} +.value span { + font-size: 15px; + margin-right: 4px; +} + +#placeholder-property-type { + display: flex; + align-items: center; + width: auto; + margin-right: 20px; +} + +#placeholder-property-locked { + margin-left: 6px; +} + +.checkbox + .checkbox { + margin-top: 0; +} + +.checkbox-sub-props { + margin-top: 18px; +} + +.property .number { + float: left; +} +.property .number + .number { + margin-left: 10px; +} + +.property.range label{ + padding-bottom: 3px; +} +.property.range input[type=number]{ + margin-left: 0.8rem; + width: 5.4rem; + height: 1.8rem; +} + +.dropdown { + position: relative; + margin-bottom: -17px; +} + +.dropdown select { + clear: both; +} + +.dropdown dl { + clear: both; + cursor: pointer; + font-family: FiraSans-SemiBold; + font-size: 15px; + width: 292px; + height: 28px; + padding: 0 28px 0 12px; + color: #afafaf; + background: #575757; + position: relative; + display: flex; + align-items: center; +} + +.dropdown dl[dropped="true"] { + color: #404040; + background: linear-gradient(#afafaf, #afafaf); + z-index: 998; +} + +.dropdown dt { + height: 100%; + box-sizing: border-box; + border-right: 1px solid #121212; + width: 100%; +} +.dropdown dt:hover { + color: #404040; +} +.dropdown dt:focus { + outline: none; +} +.dropdown dt span:first-child { + display: inline-block; + position: relative; + top: 5px; +} +.dropdown dt span:last-child { + font-family: HiFi-Glyphs; + font-size: 42px; + float: right; + margin-right: -48px; + position: relative; + left: -12px; + top: -9px; +} + +.dropdown dd { + position: absolute; + top: 28px; + left: 3px; + display: none; +} +.dropdown dl[dropped="true"] dd { + display: block; +} + +.dropdown li { + list-style-type: none; + padding: 3px 0 1px 12px; + width: 320px; + height: auto; + font-family: FiraSans-SemiBold; + font-size: 15px; + color: #404040; + background-color: #afafaf; + z-index: 999; +} +.dropdown li:hover { + background-color: #00b4ef; +} + +.dropdown dl[disabled="disabled"], .dropdown dl[disabled="disabled"][dropped="true"] { + color: #252525; + background: linear-gradient(#575757 20%, #252525 100%); +} +.dropdown dl[disabled="disabled"] dd { + display: none; +} +.dropdown dl[disabled="disabled"] dt:hover { + color: #252525; +} + +.multiselect-box { + position: absolute; +} +.multiselect-box select { + font-family: FiraSans-SemiBold; + font-size: 15px; + color: #afafaf; + background-color: #252525; + border: none; + text-align-last: center; +} +.over-select { + position: absolute; + left: 0; + right: 0; + top: 0; + bottom: 0; +} +.multiselect-options { + position: absolute; + display: none; + border: none; +} +.multiselect-options span { + font-family: hifi-glyphs; + font-size: 13px; + color: #000000; +} +.multiselect-options label { + z-index: 2; + display: block; + font-family: FiraSans-SemiBold; + font-size: 11px; + color: #000000; + background-color: #afafaf; +} +.multiselect-options label:hover { + background-color: #1e90ff; +} +.multiselect-options input[type=checkbox] + label { + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACwAAAAsCAYAAAAehFoBAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAC4jAAAuIwF4pT92AAAAB3RJTUUH4goSADUOYnF4LQAAABl0RVh0Q29tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAAIMSURBVFjD7ZmxkqowFIZ/7mwJPen1AezV3t6hFvrQhweAHvrQ8wL2xt4HwD7ppd+tvHOvu0gCYdEZTsmAfpNzzpcTcAB84o3iD94sZuCx4+Pxwvl8dl4JcL1ef84lMQPPwBZDSgkp5XsASylBKUUYhhBCvDbw7XYDpRRKKTRNA8YYOOevC5ymKZRS/13jnHdCTwLMOW8tAc45GGNomuY1gKuq6lxFIQQopdMDXy4X5HmudW8URdMCSynBGNOG3Ww20wHf9dVWl4+wbav7a8CMsW9G+Cm22+1T2F8BzvMc1+u18z5CCJIkseNhKSX2+z2qqjLWl84zhBAURQHXde0A31Oa57nWbqSrLwDwPA9FUcD3fTtb82NKu8QOAHVda+srSRJt2E7gtpQKIXA4HH6csmzpyxj4dDo9TalSCpRS1HX9TV86RujSlxGwlBJpmnY+rJRCGIZ/s2BTX9qnZgBwHAee52mJ/l7nx+PRqr6MVtj3fZRlaVRf/5aGDX0Z17DrusiyrHfqhuqrt9aiKEIcx4OBTfU1aOMIggBlWYIQ0utP+uhr8CyxXC5RFIUxdBAE1srKePgxbcbVamWlnAZNa7rNSAhBlmWv8yLlWTPa0Nco83BbM2ZZZsUIowzwj80YxzEWi8VoB4IPGz9yb0YhBHa73agnGGtHJNd1R4ed9FVV33Awf6ebgd8b+Av9A/rq6s3hjgAAAABJRU5ErkJggg=='); + background-size: 11px 11px; + background-position: top 5px left 14px; +} +.multiselect-options input[type=checkbox]:enabled + label:hover { + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACwAAAAsCAYAAAAehFoBAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAC4jAAAuIwF4pT92AAAAB3RJTUUH4goSADUOYnF4LQAAABl0RVh0Q29tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAAIMSURBVFjD7ZmxkqowFIZ/7mwJPen1AezV3t6hFvrQhweAHvrQ8wL2xt4HwD7ppd+tvHOvu0gCYdEZTsmAfpNzzpcTcAB84o3iD94sZuCx4+Pxwvl8dl4JcL1ef84lMQPPwBZDSgkp5XsASylBKUUYhhBCvDbw7XYDpRRKKTRNA8YYOOevC5ymKZRS/13jnHdCTwLMOW8tAc45GGNomuY1gKuq6lxFIQQopdMDXy4X5HmudW8URdMCSynBGNOG3Ww20wHf9dVWl4+wbav7a8CMsW9G+Cm22+1T2F8BzvMc1+u18z5CCJIkseNhKSX2+z2qqjLWl84zhBAURQHXde0A31Oa57nWbqSrLwDwPA9FUcD3fTtb82NKu8QOAHVda+srSRJt2E7gtpQKIXA4HH6csmzpyxj4dDo9TalSCpRS1HX9TV86RujSlxGwlBJpmnY+rJRCGIZ/s2BTX9qnZgBwHAee52mJ/l7nx+PRqr6MVtj3fZRlaVRf/5aGDX0Z17DrusiyrHfqhuqrt9aiKEIcx4OBTfU1aOMIggBlWYIQ0utP+uhr8CyxXC5RFIUxdBAE1srKePgxbcbVamWlnAZNa7rNSAhBlmWv8yLlWTPa0Nco83BbM2ZZZsUIowzwj80YxzEWi8VoB4IPGz9yb0YhBHa73agnGGtHJNd1R4ed9FVV33Awf6ebgd8b+Av9A/rq6s3hjgAAAABJRU5ErkJggg=='); +} +.multiselect-options input[type=checkbox]:checked + label { + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACwAAAAsCAYAAAAehFoBAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAC4jAAAuIwF4pT92AAAAB3RJTUUH4goSADMveELP9QAAABl0RVh0Q29tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAAIqSURBVFjD7ZmxkqowFIb/7GwJPfT6APZib+9QC33o4QGghz70vIC9sfcBsE966bPNWlxnlQTDRWc4JUT4hpPz5SQSAAofFF/4sJiBx47v+wun04m8E+B6vVbzlJiBZ2CLIYRQQgj1EcBCCEUpRRRF4Jyrtwa+Xq+glEJKia7rkKYpGGPqbYHzPFdSyn+uMcZ6oScBZowpzvmje0jTVHVd9x7ATdMoxtjTMZxzUErV5MDn81mVZak1No7jab+wEEKlaaoNGwQBmQz4pq9H8/IeNo5jMmnRpWmKeyP8FZvN5insfwEuy1JdLpfecb7vI8uy3tb2Szelu91ONU1jtP9jjKmmabRgq6qC4zh2VrpbSsuy1FqNdPUFAK7roqoqeJ6ntXH4Mk1pn9gBoG1bbX1lWaYN2wv8KKWcc+z3+z+7LFv6MgY+Ho9PUyqlBKUUbduqe33pGKFPX0bAQgiV53nvj6WUiKIIt2K0qS/tXTMAEELguq6W6H/nOQ6Hg1V9GX1hz/NIXdckCALtB7Vta1VfxnPYcRwURUEeNSGmYaqvwVqL45gkSfIysKm+Xlo4wjAkdV3D9/1BLxmir5d7ieVySaqqMoYOw3CwEV5ufkyLcbVaIUkSq2d1xt2abjH6vo+iKKwfLA5uL58Vow19jdIPPyrGoiisGGGUBv6+GJMkwWKxGO2M+dvGQ36LEZxztd1uRz0Qt7ZFchwHY8NOelQ1NAjm/+lm4M8G/gH2zx33BSr7jAAAAABJRU5ErkJggg=='); +} +.multiselect-options input[type=checkbox]:checked + label:hover { + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACwAAAAsCAYAAAAehFoBAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAC4jAAAuIwF4pT92AAAAB3RJTUUH4goSADMveELP9QAAABl0RVh0Q29tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAAIqSURBVFjD7ZmxkqowFIb/7GwJPfT6APZib+9QC33o4QGghz70vIC9sfcBsE966bPNWlxnlQTDRWc4JUT4hpPz5SQSAAofFF/4sJiBx47v+wun04m8E+B6vVbzlJiBZ2CLIYRQQgj1EcBCCEUpRRRF4Jyrtwa+Xq+glEJKia7rkKYpGGPqbYHzPFdSyn+uMcZ6oScBZowpzvmje0jTVHVd9x7ATdMoxtjTMZxzUErV5MDn81mVZak1No7jab+wEEKlaaoNGwQBmQz4pq9H8/IeNo5jMmnRpWmKeyP8FZvN5insfwEuy1JdLpfecb7vI8uy3tb2Szelu91ONU1jtP9jjKmmabRgq6qC4zh2VrpbSsuy1FqNdPUFAK7roqoqeJ6ntXH4Mk1pn9gBoG1bbX1lWaYN2wv8KKWcc+z3+z+7LFv6MgY+Ho9PUyqlBKUUbduqe33pGKFPX0bAQgiV53nvj6WUiKIIt2K0qS/tXTMAEELguq6W6H/nOQ6Hg1V9GX1hz/NIXdckCALtB7Vta1VfxnPYcRwURUEeNSGmYaqvwVqL45gkSfIysKm+Xlo4wjAkdV3D9/1BLxmir5d7ieVySaqqMoYOw3CwEV5ufkyLcbVaIUkSq2d1xt2abjH6vo+iKKwfLA5uL58Vow19jdIPPyrGoiisGGGUBv6+GJMkwWKxGO2M+dvGQ36LEZxztd1uRz0Qt7ZFchwHY8NOelQ1NAjm/+lm4M8G/gH2zx33BSr7jAAAAABJRU5ErkJggg=='); +} + +.dynamic-multiselect { + position: relative; + top: 6px; + padding-bottom: 6px; +} + +div.refresh { + box-sizing: border-box; + padding-right: 44px; +} +div.refresh input[type="button"] { + float: right; + margin-right: -44px; + position: relative; + left: 10px; +} + +.color-picker { + box-sizing: border-box; + width: 26px; + height: 26px; + border: 3px solid #2B2B2B; + cursor: pointer; +} +.color-picker:focus { + outline: none; +} +.color-picker[active="true"] { + border-color: #000; +} + +.color-picker[disabled="disabled"] { + border-color: #afafaf; +} + +.colpick { + z-index: 3; +} +.colpick[disabled="disabled"] { + display: none !important; +} + +.rgb label { + float: left; + margin-top: 10px; + margin-left: 21px; +} +.rgb label + * { + clear: both; +} + +.rgb div.legend { + float: left; + margin-top: 10px; + margin-left: 21px; +} +.rgb div.legend + * { + clear: both; +} + +.draggable-number-container { + flex: 0 1 124px; +} +.draggable-number { + position: relative; + height: 28px; + flex: 0 1 124px; + display: flex; + align-items: center; +} + +.draggable-number .text { + position: absolute; + display: inline-block; + color: #afafaf; + background-color: #252525; + font-family: FiraSans-SemiBold; + font-size: 15px; + margin: 0; + padding: 0 16px; + height: 28px; + width: 100%; + line-height: 2; + box-sizing: border-box; + z-index: 1; +} +.draggable-number .text:hover { + cursor: ew-resize; +} +.draggable-number .left-arrow, .draggable-number .right-arrow { + position: absolute; + display: inline-block; + font-family: HiFi-Glyphs; + font-size: 20px; + z-index: 2; +} +.draggable-number span:hover { + cursor: default; +} +.draggable-number .left-arrow { + top: 3px; + left: 0; + transform: rotate(180deg); +} +.draggable-number .right-arrow { + top: 3px; + right: 0; +} +.draggable-number input[type=number] { + position: absolute; + right: 0; + width: 100%; +} +.draggable-number input[type=button] { + position: absolute; + top: 0; +} +.draggable-number input::-webkit-inner-spin-button { + -webkit-appearance: none; + visibility: hidden; +} +.draggable-number.fstuple { + height: 28px; + width: 124px; + left: 12px; +} +.draggable-number.fstuple + .draggable-number.fstuple { + margin-left: 28px; +} +.draggable-number.fstuple input { + right: -10px; +} +.draggable-number.fstuple .sublabel { + position: absolute; + top: 6px; + left: -16px; + font-family: FiraSans-SemiBold; + font-size: 15px; +} + +.rect .rect-row { + margin-bottom: 8px; +} + +.row .property { + width: auto; + display: inline-block; + margin-right: 6px; +} +.row .property:last-child { + margin-right: 0; +} +.row .property input { + clear: both; + float: left; +} + +.property.texture { + display: block; +} +.property.texture input { + margin: 0.4rem 0; +} +.texture-image img { + padding: 0; + margin: 0; + width: 100%; + height: 100%; + display: none; +} +.texture-image { + display: block; + position: relative; + background-repeat: no-repeat; + background-position: center; + background-size: 100% 100%; + margin-top: 0.4rem; + height:128px; + width: 128px; + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAACXBIWXMAAAsTAAALEwEAmpwYAAAKT2lDQ1BQaG90b3Nob3AgSUNDIHByb2ZpbGUAAHjanVNnVFPpFj333vRCS4iAlEtvUhUIIFJCi4AUkSYqIQkQSoghodkVUcERRUUEG8igiAOOjoCMFVEsDIoK2AfkIaKOg6OIisr74Xuja9a89+bN/rXXPues852zzwfACAyWSDNRNYAMqUIeEeCDx8TG4eQuQIEKJHAAEAizZCFz/SMBAPh+PDwrIsAHvgABeNMLCADATZvAMByH/w/qQplcAYCEAcB0kThLCIAUAEB6jkKmAEBGAYCdmCZTAKAEAGDLY2LjAFAtAGAnf+bTAICd+Jl7AQBblCEVAaCRACATZYhEAGg7AKzPVopFAFgwABRmS8Q5ANgtADBJV2ZIALC3AMDOEAuyAAgMADBRiIUpAAR7AGDIIyN4AISZABRG8lc88SuuEOcqAAB4mbI8uSQ5RYFbCC1xB1dXLh4ozkkXKxQ2YQJhmkAuwnmZGTKBNA/g88wAAKCRFRHgg/P9eM4Ors7ONo62Dl8t6r8G/yJiYuP+5c+rcEAAAOF0ftH+LC+zGoA7BoBt/qIl7gRoXgugdfeLZrIPQLUAoOnaV/Nw+H48PEWhkLnZ2eXk5NhKxEJbYcpXff5nwl/AV/1s+X48/Pf14L7iJIEyXYFHBPjgwsz0TKUcz5IJhGLc5o9H/LcL//wd0yLESWK5WCoU41EScY5EmozzMqUiiUKSKcUl0v9k4t8s+wM+3zUAsGo+AXuRLahdYwP2SycQWHTA4vcAAPK7b8HUKAgDgGiD4c93/+8//UegJQCAZkmScQAAXkQkLlTKsz/HCAAARKCBKrBBG/TBGCzABhzBBdzBC/xgNoRCJMTCQhBCCmSAHHJgKayCQiiGzbAdKmAv1EAdNMBRaIaTcA4uwlW4Dj1wD/phCJ7BKLyBCQRByAgTYSHaiAFiilgjjggXmYX4IcFIBBKLJCDJiBRRIkuRNUgxUopUIFVIHfI9cgI5h1xGupE7yAAygvyGvEcxlIGyUT3UDLVDuag3GoRGogvQZHQxmo8WoJvQcrQaPYw2oefQq2gP2o8+Q8cwwOgYBzPEbDAuxsNCsTgsCZNjy7EirAyrxhqwVqwDu4n1Y8+xdwQSgUXACTYEd0IgYR5BSFhMWE7YSKggHCQ0EdoJNwkDhFHCJyKTqEu0JroR+cQYYjIxh1hILCPWEo8TLxB7iEPENyQSiUMyJ7mQAkmxpFTSEtJG0m5SI+ksqZs0SBojk8naZGuyBzmULCAryIXkneTD5DPkG+Qh8lsKnWJAcaT4U+IoUspqShnlEOU05QZlmDJBVaOaUt2ooVQRNY9aQq2htlKvUYeoEzR1mjnNgxZJS6WtopXTGmgXaPdpr+h0uhHdlR5Ol9BX0svpR+iX6AP0dwwNhhWDx4hnKBmbGAcYZxl3GK+YTKYZ04sZx1QwNzHrmOeZD5lvVVgqtip8FZHKCpVKlSaVGyovVKmqpqreqgtV81XLVI+pXlN9rkZVM1PjqQnUlqtVqp1Q61MbU2epO6iHqmeob1Q/pH5Z/YkGWcNMw09DpFGgsV/jvMYgC2MZs3gsIWsNq4Z1gTXEJrHN2Xx2KruY/R27iz2qqaE5QzNKM1ezUvOUZj8H45hx+Jx0TgnnKKeX836K3hTvKeIpG6Y0TLkxZVxrqpaXllirSKtRq0frvTau7aedpr1Fu1n7gQ5Bx0onXCdHZ4/OBZ3nU9lT3acKpxZNPTr1ri6qa6UbobtEd79up+6Ynr5egJ5Mb6feeb3n+hx9L/1U/W36p/VHDFgGswwkBtsMzhg8xTVxbzwdL8fb8VFDXcNAQ6VhlWGX4YSRudE8o9VGjUYPjGnGXOMk423GbcajJgYmISZLTepN7ppSTbmmKaY7TDtMx83MzaLN1pk1mz0x1zLnm+eb15vft2BaeFostqi2uGVJsuRaplnutrxuhVo5WaVYVVpds0atna0l1rutu6cRp7lOk06rntZnw7Dxtsm2qbcZsOXYBtuutm22fWFnYhdnt8Wuw+6TvZN9un2N/T0HDYfZDqsdWh1+c7RyFDpWOt6azpzuP33F9JbpL2dYzxDP2DPjthPLKcRpnVOb00dnF2e5c4PziIuJS4LLLpc+Lpsbxt3IveRKdPVxXeF60vWdm7Obwu2o26/uNu5p7ofcn8w0nymeWTNz0MPIQ+BR5dE/C5+VMGvfrH5PQ0+BZ7XnIy9jL5FXrdewt6V3qvdh7xc+9j5yn+M+4zw33jLeWV/MN8C3yLfLT8Nvnl+F30N/I/9k/3r/0QCngCUBZwOJgUGBWwL7+Hp8Ib+OPzrbZfay2e1BjKC5QRVBj4KtguXBrSFoyOyQrSH355jOkc5pDoVQfujW0Adh5mGLw34MJ4WHhVeGP45wiFga0TGXNXfR3ENz30T6RJZE3ptnMU85ry1KNSo+qi5qPNo3ujS6P8YuZlnM1VidWElsSxw5LiquNm5svt/87fOH4p3iC+N7F5gvyF1weaHOwvSFpxapLhIsOpZATIhOOJTwQRAqqBaMJfITdyWOCnnCHcJnIi/RNtGI2ENcKh5O8kgqTXqS7JG8NXkkxTOlLOW5hCepkLxMDUzdmzqeFpp2IG0yPTq9MYOSkZBxQqohTZO2Z+pn5mZ2y6xlhbL+xW6Lty8elQfJa7OQrAVZLQq2QqboVFoo1yoHsmdlV2a/zYnKOZarnivN7cyzytuQN5zvn//tEsIS4ZK2pYZLVy0dWOa9rGo5sjxxedsK4xUFK4ZWBqw8uIq2Km3VT6vtV5eufr0mek1rgV7ByoLBtQFr6wtVCuWFfevc1+1dT1gvWd+1YfqGnRs+FYmKrhTbF5cVf9go3HjlG4dvyr+Z3JS0qavEuWTPZtJm6ebeLZ5bDpaql+aXDm4N2dq0Dd9WtO319kXbL5fNKNu7g7ZDuaO/PLi8ZafJzs07P1SkVPRU+lQ27tLdtWHX+G7R7ht7vPY07NXbW7z3/T7JvttVAVVN1WbVZftJ+7P3P66Jqun4lvttXa1ObXHtxwPSA/0HIw6217nU1R3SPVRSj9Yr60cOxx++/p3vdy0NNg1VjZzG4iNwRHnk6fcJ3/ceDTradox7rOEH0x92HWcdL2pCmvKaRptTmvtbYlu6T8w+0dbq3nr8R9sfD5w0PFl5SvNUyWna6YLTk2fyz4ydlZ19fi753GDborZ752PO32oPb++6EHTh0kX/i+c7vDvOXPK4dPKy2+UTV7hXmq86X23qdOo8/pPTT8e7nLuarrlca7nuer21e2b36RueN87d9L158Rb/1tWeOT3dvfN6b/fF9/XfFt1+cif9zsu72Xcn7q28T7xf9EDtQdlD3YfVP1v+3Njv3H9qwHeg89HcR/cGhYPP/pH1jw9DBY+Zj8uGDYbrnjg+OTniP3L96fynQ89kzyaeF/6i/suuFxYvfvjV69fO0ZjRoZfyl5O/bXyl/erA6xmv28bCxh6+yXgzMV70VvvtwXfcdx3vo98PT+R8IH8o/2j5sfVT0Kf7kxmTk/8EA5jz/GMzLdsAAAAgY0hSTQAAeiUAAICDAAD5/wAAgOkAAHUwAADqYAAAOpgAABdvkl/FRgAABhNJREFUeNrsnVFy4joQRVsSCwAqBMwqsrRsIavMEkICoeAf2+8j1R5ZGDBgpLzoUDVVmTT2dc8It/paOpi3t7faOSciImVZyn6/l6qqRETEWivj8VistYPFd7ud1HUtIiLGGBmPx5JKX0RkMplIzvmPnHNijBERaS7Ef1lrB40bY1oXgH5a/ZH+8P7+LlVVycfHR/MGa60URdGcYOi4MUaKomhGaGx9EZHlcplMP2X+Ly8vPwOgLEtxzklVVVJVVfOznqAsy9YFXhuvqqq5AF/Lj+srtr7+LpV+yvz1mNF+vxcRkdVqJdZaeXp6ap1ws9m0TjibzVoj6lJ8vV6fjJdlKev1ujViU+j7t8tc8p9Op1KWpYw06L9JL0Av0r9l+jXl3nhd11JV1VE8tn5YM3PI3xjzoxVOGvyDU7zQj6s/0tGmI++amtNV087F9Wf/FnVPzRtCXz8RdV1nlb/efUbaJy4Wi0FqzjU1yRgjs9ls0Jp3jb6IyPPzczL9lPkvFot/dwCtB/om/x9oyJoXxps65NW8mPpdNTeX/JtBEtYE/+AUL/Tj6g/qA3TVnD41a6g++Bp9rYOp9FPnH80HOBcvy1I2m81D++BL+o/2AX5r/vgA+AD4AOif8AH8EdpVcy71sX3jWp/8W2AKff/TkUv+Oufr9AF0YuKc66xJ18T7eNP3nP9WfZ0EzufzJPqp8y+KQuq67vYBdETqCDpVU/rEw5oUnr+rD46h73/qUuinzh8fAP22D6AjxznXcqq6akrf+KmaFB6vf4+t7/sAelfIJf/GB9jtdmKMkdVq1dQM3zg4VVNU/NY+1Bgjh8Oh6YM1+dj6X19fzXwgp/wbH0DFtS7oyf0RdKqmhPFr+1RdseKfP7a+Px/IKX98APTbPoDOJrv60L417d54TH3V8lfS5pT/yfUA6/X6qOZcqkm3xrUm6X9CTH3fB0ihnzr/Ix9A/3T1qbfWpGvjMfX9T0UK/dT54wOg/88H8EfGPTVr6D740frhLDmn/Hv5AH1qku9t31KTzh3/aP1LPsBfzr+XDxCO0K6ack/N6qp5MfUv+QB/Of/ePsCQfWmfc6EfV3/kjzZrrRwOh9YtKHSm/LjOH3yrMTzej4c1y//51PHoP0a/tR7AOSdFURw9rz5VU049zw7jl2qWrosP++BY+iI/+wJS6afMv9kXoA6gvimsieHzZr/m6MTp3PPuc3G9SP95OPpx9JtOgT4cHwA+QCJ9+ADwAeADsC+AfQHo/4b1APAB4APAB4APAB8APgB9OD4AfAD4AFFqEnwA+AD4APgA6P86HwA+AHyAZhIBHwA+AHwA+AD04X/eB4APAB8APgB8APgA8AHow/P0AeADwAeADwAfAD4AfAD68Px8APgA8AHgA8AHgA8AH0DO70/v6lHvjaOfVn8U/iLcXx5OUML96X49vRTX3/nPw9FPo9+sB5hMJuKck+VyeVRTrLWtdfNdcf95eldNCuOfn5+tSYy/Pz+2voi0fICc8p/P5z93gJAPEN4+wufN4evaePj99eH+ePTj6p/1Abp60kt9Ksf/v46HDwAfAD6A/6gUPgD7AtgXwPP4DNcDwAeADwAfAD4AfAD4ADyPz289AHyA+Pqp84cPIPAB8AHwAfAB8AHgA7Q+HfAB4APAB4APAB+APjw3HwA+AHwA+ADwAeADwAegD8/TB4APAB8APgB8APgA8AHow/PzAeADwAeADwAfAD4AfACJ//316KfVH/mjLeb31+vx/kWhH0+/tR7AOSdFUUT9/nq9oK4+OJa+iLT25+eUf7MvIOQDxPr+en2F++PRj6PfdAr04fgA8AES6cMHgA8AH4B9AewLQP83rAeADwAfAD4AfAD4APAB6MPxAeADwAeIUpPgA8AHwAfAB0D/1/kA8AHgAzSTCPgA8AHgA8AHoA//8z4AfAD4APAB4APAB4APQB+epw8AHwA+AHwA+ADwAeAD0Ifn5wPAB4APAB8APgB8gBz5AOb19bX2TYLpdNpqQ7bbbctJGjJeVZVst9vWLSu2/vf3t+Sc/yicFIRr0C7Fu76f/lw8XBePflr9/wYAqWwWUSLcO54AAAAASUVORK5CYII='); +} +.texture-image.no-texture { + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAACXBIWXMAAAsTAAALEwEAmpwYAAAKT2lDQ1BQaG90b3Nob3AgSUNDIHByb2ZpbGUAAHjanVNnVFPpFj333vRCS4iAlEtvUhUIIFJCi4AUkSYqIQkQSoghodkVUcERRUUEG8igiAOOjoCMFVEsDIoK2AfkIaKOg6OIisr74Xuja9a89+bN/rXXPues852zzwfACAyWSDNRNYAMqUIeEeCDx8TG4eQuQIEKJHAAEAizZCFz/SMBAPh+PDwrIsAHvgABeNMLCADATZvAMByH/w/qQplcAYCEAcB0kThLCIAUAEB6jkKmAEBGAYCdmCZTAKAEAGDLY2LjAFAtAGAnf+bTAICd+Jl7AQBblCEVAaCRACATZYhEAGg7AKzPVopFAFgwABRmS8Q5ANgtADBJV2ZIALC3AMDOEAuyAAgMADBRiIUpAAR7AGDIIyN4AISZABRG8lc88SuuEOcqAAB4mbI8uSQ5RYFbCC1xB1dXLh4ozkkXKxQ2YQJhmkAuwnmZGTKBNA/g88wAAKCRFRHgg/P9eM4Ors7ONo62Dl8t6r8G/yJiYuP+5c+rcEAAAOF0ftH+LC+zGoA7BoBt/qIl7gRoXgugdfeLZrIPQLUAoOnaV/Nw+H48PEWhkLnZ2eXk5NhKxEJbYcpXff5nwl/AV/1s+X48/Pf14L7iJIEyXYFHBPjgwsz0TKUcz5IJhGLc5o9H/LcL//wd0yLESWK5WCoU41EScY5EmozzMqUiiUKSKcUl0v9k4t8s+wM+3zUAsGo+AXuRLahdYwP2SycQWHTA4vcAAPK7b8HUKAgDgGiD4c93/+8//UegJQCAZkmScQAAXkQkLlTKsz/HCAAARKCBKrBBG/TBGCzABhzBBdzBC/xgNoRCJMTCQhBCCmSAHHJgKayCQiiGzbAdKmAv1EAdNMBRaIaTcA4uwlW4Dj1wD/phCJ7BKLyBCQRByAgTYSHaiAFiilgjjggXmYX4IcFIBBKLJCDJiBRRIkuRNUgxUopUIFVIHfI9cgI5h1xGupE7yAAygvyGvEcxlIGyUT3UDLVDuag3GoRGogvQZHQxmo8WoJvQcrQaPYw2oefQq2gP2o8+Q8cwwOgYBzPEbDAuxsNCsTgsCZNjy7EirAyrxhqwVqwDu4n1Y8+xdwQSgUXACTYEd0IgYR5BSFhMWE7YSKggHCQ0EdoJNwkDhFHCJyKTqEu0JroR+cQYYjIxh1hILCPWEo8TLxB7iEPENyQSiUMyJ7mQAkmxpFTSEtJG0m5SI+ksqZs0SBojk8naZGuyBzmULCAryIXkneTD5DPkG+Qh8lsKnWJAcaT4U+IoUspqShnlEOU05QZlmDJBVaOaUt2ooVQRNY9aQq2htlKvUYeoEzR1mjnNgxZJS6WtopXTGmgXaPdpr+h0uhHdlR5Ol9BX0svpR+iX6AP0dwwNhhWDx4hnKBmbGAcYZxl3GK+YTKYZ04sZx1QwNzHrmOeZD5lvVVgqtip8FZHKCpVKlSaVGyovVKmqpqreqgtV81XLVI+pXlN9rkZVM1PjqQnUlqtVqp1Q61MbU2epO6iHqmeob1Q/pH5Z/YkGWcNMw09DpFGgsV/jvMYgC2MZs3gsIWsNq4Z1gTXEJrHN2Xx2KruY/R27iz2qqaE5QzNKM1ezUvOUZj8H45hx+Jx0TgnnKKeX836K3hTvKeIpG6Y0TLkxZVxrqpaXllirSKtRq0frvTau7aedpr1Fu1n7gQ5Bx0onXCdHZ4/OBZ3nU9lT3acKpxZNPTr1ri6qa6UbobtEd79up+6Ynr5egJ5Mb6feeb3n+hx9L/1U/W36p/VHDFgGswwkBtsMzhg8xTVxbzwdL8fb8VFDXcNAQ6VhlWGX4YSRudE8o9VGjUYPjGnGXOMk423GbcajJgYmISZLTepN7ppSTbmmKaY7TDtMx83MzaLN1pk1mz0x1zLnm+eb15vft2BaeFostqi2uGVJsuRaplnutrxuhVo5WaVYVVpds0atna0l1rutu6cRp7lOk06rntZnw7Dxtsm2qbcZsOXYBtuutm22fWFnYhdnt8Wuw+6TvZN9un2N/T0HDYfZDqsdWh1+c7RyFDpWOt6azpzuP33F9JbpL2dYzxDP2DPjthPLKcRpnVOb00dnF2e5c4PziIuJS4LLLpc+Lpsbxt3IveRKdPVxXeF60vWdm7Obwu2o26/uNu5p7ofcn8w0nymeWTNz0MPIQ+BR5dE/C5+VMGvfrH5PQ0+BZ7XnIy9jL5FXrdewt6V3qvdh7xc+9j5yn+M+4zw33jLeWV/MN8C3yLfLT8Nvnl+F30N/I/9k/3r/0QCngCUBZwOJgUGBWwL7+Hp8Ib+OPzrbZfay2e1BjKC5QRVBj4KtguXBrSFoyOyQrSH355jOkc5pDoVQfujW0Adh5mGLw34MJ4WHhVeGP45wiFga0TGXNXfR3ENz30T6RJZE3ptnMU85ry1KNSo+qi5qPNo3ujS6P8YuZlnM1VidWElsSxw5LiquNm5svt/87fOH4p3iC+N7F5gvyF1weaHOwvSFpxapLhIsOpZATIhOOJTwQRAqqBaMJfITdyWOCnnCHcJnIi/RNtGI2ENcKh5O8kgqTXqS7JG8NXkkxTOlLOW5hCepkLxMDUzdmzqeFpp2IG0yPTq9MYOSkZBxQqohTZO2Z+pn5mZ2y6xlhbL+xW6Lty8elQfJa7OQrAVZLQq2QqboVFoo1yoHsmdlV2a/zYnKOZarnivN7cyzytuQN5zvn//tEsIS4ZK2pYZLVy0dWOa9rGo5sjxxedsK4xUFK4ZWBqw8uIq2Km3VT6vtV5eufr0mek1rgV7ByoLBtQFr6wtVCuWFfevc1+1dT1gvWd+1YfqGnRs+FYmKrhTbF5cVf9go3HjlG4dvyr+Z3JS0qavEuWTPZtJm6ebeLZ5bDpaql+aXDm4N2dq0Dd9WtO319kXbL5fNKNu7g7ZDuaO/PLi8ZafJzs07P1SkVPRU+lQ27tLdtWHX+G7R7ht7vPY07NXbW7z3/T7JvttVAVVN1WbVZftJ+7P3P66Jqun4lvttXa1ObXHtxwPSA/0HIw6217nU1R3SPVRSj9Yr60cOxx++/p3vdy0NNg1VjZzG4iNwRHnk6fcJ3/ceDTradox7rOEH0x92HWcdL2pCmvKaRptTmvtbYlu6T8w+0dbq3nr8R9sfD5w0PFl5SvNUyWna6YLTk2fyz4ydlZ19fi753GDborZ752PO32oPb++6EHTh0kX/i+c7vDvOXPK4dPKy2+UTV7hXmq86X23qdOo8/pPTT8e7nLuarrlca7nuer21e2b36RueN87d9L158Rb/1tWeOT3dvfN6b/fF9/XfFt1+cif9zsu72Xcn7q28T7xf9EDtQdlD3YfVP1v+3Njv3H9qwHeg89HcR/cGhYPP/pH1jw9DBY+Zj8uGDYbrnjg+OTniP3L96fynQ89kzyaeF/6i/suuFxYvfvjV69fO0ZjRoZfyl5O/bXyl/erA6xmv28bCxh6+yXgzMV70VvvtwXfcdx3vo98PT+R8IH8o/2j5sfVT0Kf7kxmTk/8EA5jz/GMzLdsAAAAgY0hSTQAAeiUAAICDAAD5/wAAgOkAAHUwAADqYAAAOpgAABdvkl/FRgAAB81JREFUeNrsnTGPm0oXht97FWm2Ch2pTEeHpUihsyvTuXO67Ta/IPkr+Qfp3MWdO7Zad0SKZDo6XIWOrTzV9xVXZ8SygGHXG4/t96lW68GGw8vMmZlzDv98+/btfyBXy780wXXzTv74/fs3rXFFfPz4kT0AoQAoAJqAAiAUAKEACAVAKABCARAKgFAAhAIgFAChAAgFQCgAQgEQCoBQAIQCIBQAoQAIBUAoAHLmvDv3C7i7u4PjOMiyDOv1+mC75XKJoiga2wRBAN/34TgOHMdBWZYoigJpmiLPcwrARhzHAQD4vg/P81pvlLRrwvM8zGYz00ZrbY5xHAe+7yPPc9zf36MsSwrAVmazGX78+DHoGM/zsFgsAAB5nmOz2ZgeQimF8XiMMAxNu+VyaQRCH8Ai8jyH4zgIw7D3MUopzOdzAECaplitVk+GB601kiTBz58/obWG4ziIoohOoI38+vULABCGYWd3X2U6nUIphbIsEcdxa7uiKPDw8GCGGtd1KQDbKMsSWZZBKYXJZNLrGN/3zdN/iDRNTdcvx1EAFqGUwmazeeIQduG6LpRSAIAsy3r9hrRjD2BxL5AkiXEI+8wetNa9PXtp13eIoQBOQJIkxmHrcgjlJkov8JKpJwVgIVpr47CFYdh6g/f7/ZM5/9CehgKwmDRNURQFlFKYTqeNN/rx8dH0AH2faBn7KYAzQKZ1QRCYZd0qf/78MX+PRqNe3ymO5W63owBsR9bwZShoGirEq++zeBQEweBZAwVwYh4eHqC1RhAErQ6jOHVdK3yu65qhJE1TDgHn5BDKTW6auxdFYdYOgiDAYrF40k4phTAM8fnzZyilUBRF54rhOfIOF06SJMYPaPt8v99jOp3C8zx4nget9bPZQ5ZlF3fzL0IAZVke9OLv7+/Njl/brCHLMozHY4xGI3z48MH0EEVRIMuyi40H+EdqBbNS6HXBSqGEAiAUAAVAE1AAhAIgFAChAAgFQCgAQgGQq+Eom0GLxeJgGHYVSdCUhM02yrI0qV5hGGIymaAsy9b0LNd1cXt7CwDYbDa98wOA/zKLVquVSQGr/nYTbe2iKDIh53JtZVmiLEvsdjtst9tn5z7EDmfXA3QFXdaTMbvYbrdm568tgkdueJ7njbt3QwJA+8YJ1tsFQQDXdXFzc2N2E0Uwk8kEX758eXbMEDtY2QOsVqtn//v69SsAYL1eH9xK7dNGgjuiKMJ4PH4WmSN7+QBMFu/3798bn1oAzz47NvVrqmYgz2azRpv1scNV+wDVaN969y6JIEmSWBmyJenlIgZbcgvOzgmUqJxqkmY18ldCvGwkz/MntQcogBcgETrVMV98Aptvfh1JTKEAXsBms4HWGp7nYT6fw3Ec5Hlufbi253lQSkFr3VqmhgLoQVmW2G63ZigQx8/2my/FKCR17WLWAV7LfD5vzOFLkqS1W0/T1HT9RVFY5/jNZjMz3ouvorVGHMet9QheYoer7AGq478Y2LaiDTc3N3Bd90megSwG2YQVPcDQ+a/ccK01ttutWSWsetl/i7bfq16TzP1lGFgul0exw9X2AJLGJV3joRXCl3rnXbUDhmQKl2WJ9XoNrbV1vdXZCUCWWqvVQGR8HFIgqmuaKUiCSJcA+nrzWmvzdA/ZN6EAKlTz/eXmA3iSuXOoNEzfBRsA+PTpU+PnUjxSfnvo9/ZNR6cAakjFj2rqd3VtQJ6u1z5h1e+SdYbqdK5aWHLImC0OoFQgpRN4YPoD/LfRVC8C2TQlkhVC3/dfVDG0/l1xHCOKIvi+b572atJoURSdtYnbfAHxV0aj0TP/oY8dzqYH6OscHXK26tO+rqcujmNTIKqtJkDfc0vTFMvl8smu436/R57niOO4NSbh0HfLkFHtpYbY4dgwOfRKYXIooQAIBUAB0AQUAKEACAVAKABCARAKgFAA5Gp4s93AKIrw/v17ExsnFEWB/X6P3W6HLMtaN0+GJkwOad+W2FlPLq3GHFSRdq85h2PYyGoByG6cvJOnHiEryZJSg7e+s1ZNmOyzSza0ffWYJsIwbMzk7Tp+6Dm81kZWC0BoCnSU7dowDE2K12q1alT60EDJYwVWKqUQRdHgPf9jnfMQG52dDyA5fLKnLlGztiB5Bn1eP3fuNvr31IaWZM9jhHIdEwk5G1Jk4hxtdPJZQJZlJrLWlnBpx3FMmrnrup3RReduIyumgXJxtryRUxw4mQXIO4Yv0UZWCMDWN3I2vX7u0mxk1RtDmp6yoQmTbe27kjK7iOMYt7e3CIIA2+22VyLIWyZ5Hrsnsmol0Jac+fo51QtSXJKNrOgBuvLsTrUOUO8FxAP3ff/gTXiLc3irt5aevAdQSpmpja0vZqq+fm4ymfz18i5vaaOTC0DSvapv8rQRmRY6joPxeHwxNjqpAGSpUwx8ikKJQ5AyNFKb4BJsdBIfwPM8BEFgFjXSNG3debMJSUv7GyuWf8tGby6Aaq2c+qvaJce/a3p2ioTJQ73A3d3di6aBbef8WhtZKQDJ6K1fTJ7neHx8PFjWTcbbvvPePm8QbVtc6ft/+UwKUdfbDT3n19roGDA59EphciihAAgFQAHQBBQAoQAIBUAoAEIBEAqAUACEAiAUAKEACAVAKABCARAKgFAAhAIgFAChAAgFQC4CkxgiceKEPQC5Iv4/APgB2O7x8IXXAAAAAElFTkSuQmCC'); +} +.texture-image.no-preview { + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAIAAABMXPacAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAA8sSURBVHhe7Z3rbxXFG8d7B9SWthRabLmIYlHkIEXKJdXYBEXxHtEXprwxxsR3/jG+8PLCaDDGeAkmKsTEoCUVKoVCA6WNtLS2UEUKBSy0tKW/D+eZM9nu7tmz55z+mC2Zz4tl9tk5c2bnO/PMM2dnS+6nn36aYzFH7vvvv6+SFhMoAY4fPy7nljvG448/zjFPTiymsAIYxgpgGCuAYawAhrECGMYKYBgrgGGsAIaxAhjGCmAYK4BhrACGsQIYxgpgGCuAYawAhrECGMYKYBgrgGGsAIaxAhjmLhQgPz+/pKRk3rx56jzaRHFf0ObNmxctWkTi7Nmzp0+fFqNm+/btRUVFP/30kzp3UFtbu27duqVLl+bl3e5Y169f7+rqam1tvXnzpmSIFNHdF1RTU7M6TkNDQ0FBgbImWLVqFZfUSQKyvfzyy88991x1dfXU1NSFCxdGRkbuueeeurq6pqam0tJSlS96RNcFSQvSo9V5IC+88MIDDzwwOjr6448/fvTRR19++eVnn322Z8+ev//+u7i4+M0331ywYIHKGjGiK8Aff/zBMRaL5ebmiiUZjz322MqVK/Ez33333ZkzZxgBYh8eHt67d++lS5do/W3btokxakRXANxIf38/3mPNmjXKlARxpkeOHKGtxaIZHx9vaWkhwfTg9WZRILoCgIQG0r7JKC8vlxm7s7NTLC6YyW/cuFFYWIiPUqYoEWkB+vr6cOJLlizBwyiTB2l9vA0xj1hcTE9PDw4OkiA6EkukiLQAcOzYMY4bN26UUy8LFy7k+O+//8qpL1euXOF43333yWmkiLoATKqEQwSmlZWVyjQTIiWOwZG+npYjSNQFwIG0tbWRqK+vF4sL1r0qlZzJyUmOYXLeeaIuAHR3d+PfmQbE27hgguUY3LgS/0RzHMwBAei/R48ezcvL8x0EOCiOxEJy6osoJ1JFjTkgAHR0dExMTBDLexe0EvsTKQUMgsWLF3OUWChqzA0BGARoQBN7wyHWa6Ojo1x6+OGHlWkmaEOoeuvWrXPnzilTlJgbAgBeiEEQi8W8Pf3kyZMct27d6v0JGsf15JNPkmA5lmyhYJY5IwAenNmYBW1RUZEyJSBMYiYoLi7etWtXWVmZsubkkHPHjh2EsCjX3NysrBFjzggANDSeRJ04wEF9//33rLYqKip27979yiuvNDY2Pvvss2+//TZ+ieBn//79V69eVbkjRv6WLVv4hxW/nEcB+iyuo6ura3x8XJnicIqToV8zGpgSlDXO2NhYZ2cnV+WnIVZtTLxEn+fPn9+3b180p9+qqiqOd9ub8ihH67M8xuPT65mf1YXocXe+KY+PGhoa6unp4Rjl1tfcbQLMOawAhrECGMYKYBgrgGGsAIaxAhjGCmAYK4BhrACGyfy3oNdff72mpkadJLh27Vpvb29LS8vExIRYdu7c6dpLOz09ffPmTXLypadOnVLWnJzGxsZYLKZOPHR0dDQ3N7/33nv5+fkff/yx7/PFBQsWvPPOO5T/4YcfLly4sKmpaXBw8Ntvv5Wr7777bsAOUbINDw+Th5IpX1kTyGcPHz7c2tqqTHG4NW7wzz//9N2tHczs/BY0NjZ2PQFVLy4uXr9+/UsvvaQuJxgfH1eZ4tkKCwsrKiq2b9/u3XbozOkEzaamps6ePUueZHvcsOfl5ZFHtkH4oorzQOFU7MqVKzS0S6fy8nKxeDvckiVLOGbza2u22yW/+eYbOo46ie9Te/XVV5ctW7Z8+fK//vpLWXNyfvjhB2ctaaaGhoYNGzZs3bq1q6tLWeP88ssvdCh14oFLDz30EA3tuxFRhBGRkvHJJ5+olB8XLlxg6NCs/f39ypRo93/++Wfp0qWMP+fuCnna7N2TGp5ZngMQ48iRIyQefPBBsfhy69atgwcPjo6OlpSU+G42SQaicv80tPfBJBbslBwsQDBDQ0McpVk1CMBAx2HyFa79jUhFfeRTmTH7k7DsEky5DxBPffHiRRKytS0kNMTAwAAN4d0tigX7+fPnfaeHkEjlxbFoEIAvlTFRXV0tRhBnNTIy4hwT6TL7Asgz2zBvBUlO/K+chkQc1IoVK+RUI5YzZ87IaWZIX3buMpIJAP+Jroxv5zQgOmW52WL2BZDtyv/995+cJkMeHHJX6T42wcPgZ5gJ1HkCsWTjf4C+TCuXlpZqFyctLl6etpZpIH5F6eScAjNglgVg+n3iiSdIuHoiI/f2S19xamtrN23a9NprrzEVt7W1uSKWtWvXPu2HuhzfHkF/pFfef//9ypSTQxoLPi3lw3dV3Ez4UnU5/nicJpZuBAigvTzfyyU9DWQfAkG2UdCLL76oPeC99947f/58Et3d3cQMYhTk0b8TejGhfXt7uzpPgCfxuhf49ddfVSonp6enhyhr1apVeHyxkOYYxv8QJauUA9yaXpEQCKEH8zAJThGA1pd7lLamM0mCPNhl73vGZDsCGK10FgGffvnyZZYqP//8s7qcgCY7EUemMvz+F198ceDAAaZiyaA5duwYixov6nIcaWhpdEHSfIucBqCKm4m8hSDIBhHp3URoMgHEr9wefHoaYChw71qbjMlWgK+//pp1o/DBBx98/vnnLBfp3epyAmI4ujDs3bv3t99+I/J5/vnnfd++4/7pj17U5TjohzsuKysTL8yRNM5HwqpgVHEzce7KoYlpUynZO83qaYAOxzGbFYCQrQAsXOkXgrc7+4IYuA5WwgHvvaSEVuMoKy859vb23r6QNbQ+zof2Je2cAAQ9DYhCWU4AMPtRUBhko2B9fX1aiwAnEu3IakCOYfxPSFgN4HnwP7h7xHA6GT0NyFScZQgEZgRgimYyKCwsrKurU6Y0weHIbwO0FEfGX5bxuBPp8kR0jAPX22d8EY2Oa6qqqiJt3gVlzKFDhzjGYjFaUCzpgs/BGzQ2NnJkWg7pAMMg8Y/8Wul1Mn19fUiONtl3fzAmAP0XN8IgcM0EGzZs2JkElSOBTAMsLDiGnwBUWR74XpUjvuxiJS/TgK8AdBpUz34CAGMCgPy27hoEdC5Zr3lRORIQ8krYMzExMTAwIMaUqLI8iE/XyCCgj+NnxKLRoWf2/gcyfyBDGDNv3jw6csCP70C0QPvSUq6tzgKelK5EUxJZElazlFMX/PB6efkIJXsD0IKCgsrKSuclmpi1t6S9uBy6lJzMy1My5ae892DExdn/R8wYd+fu6DmHFcAwVgDDWAEMYwUwjBXAMFYAw1gBDGMFMIwVwDBp/xSxZs2aqqqqsbGxw4cPK1PiD2W0t7cne0K9ePHitWvXXr9+Xf4aKFRWVj7yyCMkKIfSxKgpLS1lpT4yMqIrxinGU6dOBf95OGH16tXV1dWuSmrkmbs6iTM5OXnjxo2enh7560Oap+O7MZz7AVzIF6kTPwI+m+FPEbT1+vXrN2/eXFJSokzxfXAYH330UXXuYd26dWRw/uoZi8WwgPPZukYKdO5vJI0FDdR5IL6V1KxYseL2FzvYuHFjQ0NDU1OTa7uRXFUnftTU1EieZKh8yUlPALott3T58mXSiC9GkJ/mA/aDyo1JNsjPz6fdr169OjU15SxnVqioqCgrK/NW0oXefrF///4DBw5QN2r1zDPPFBcXqxyhOXnypBTlReVITnoCyP20tLS4Gq6/v58hvGjRIudfi9HIrqnR0VG9jWfZsmXz58/nnoeGhiQt9llBVxIXFCCA3n7R3d3d0dFBY3EXRUVF4hjTAq8oRXlROZKTtgATExN9fX0DAwMyGsQ+PT0te3V8b1iMztqIpbe3l6JkNIh9VtCVpEGdlUyJPOjnI3J6Z0hDALkZbozuL63pbG6vReMSQFqcEcOACPhUZoj/kUrKPonwhcvTlTDbimeRNASQt1mkp9N5uUPn+y2Dg4M4Ge7f1eOQTR4taf+zcuVKfI6UI5sbli9f7pyfs0GaWwpnmLoqGYxswwr/dHNWSEMA7o37kfdecK+4b+luchUv5NudnS0iiEU/Rmfg5+XlBb/QEZ7gSjoh0CpPwOy1adMmQrVz58653tgJAz1MFTQT79+w8xJWACZSvobeoWN2r9MXAWSfmkb8u8v/UIjuaOk6igCkrYMrqXnqqad2JyAA3bZtG8N037593n2VKamvr1cFzaS2tlblSE5YAeQenLvPpJc57w0ng0thYaL3u0mLcGN6Bwf+p7CwkOmRfiqWixcv4rsIqLP3QmEqqRkeHqZWQK8njMH1U+233nor5FLDCcs3KcpFypckIOz2dLkHhiqrG7EAlZYmlqAb6Oksaoj65W+6iWOhG+pdU1IOGjjLQSGGF5nlD1BmTMhKCq2trXpcAkOT5RuV37Fjx1dffaWs4Whvb3f9DbvwhBoBdE8aiASr5y0O5B0j519MlVvSDt21/iooKBCPxFEVEYcGwhhmwAYgrUwiZSV9YUQeOnQI31VVVZXWe4NZEkoAqT3tyIrRibwQ6Ww4Qho6mvgTmoNG4ZZ0/EO70/cZ7+rzDojc+VTGe3VBur+3kvq/MInnCgINqD+JDLxQxqQWIDc3VzoyHYSB5uT333/HfUtDS2agCYhqWN8CpxKwyiVpI/XhmUhQJBkyQz7rrWRbWxvu3lXJZMhw0RW+A6QWQLoz9+DyoYI3hmFlzxHN+CAJp/+RAMk5SWqyjIXE/ySrJOsyjikLp+OzaiEKohxl+v+TWgCpt2+rgTfOu3TpEoENrQ/OcBP/w0RHyMGUKxYnrAbod84IyheCa/K4YH4KrqSvAK6i6urq3njjDcbu6dOnXTVUOWZCf1KX48opqweZOwNIEQVp/6PXTS7w77SyDHC9C5NeT0RBorOz0+V/5PcWL5OTk0hFkEq2EydOKKsHJlWVcoCjl8KTVVJUd1XStyjmp4MHD6qTBLt27VIpB3v27NEDZUMcSbugbrhBdeJHij9dTDyAvFQrWaMQXyLS+Pj4tWvX9PAn/kV5hgJhJXYxMgLIQDm+u3SBeZgOKJM2/YuhwJSoN+SWlJTQiJTphTZlzRlQSXBWkjUwsan6cBy+iLD9+PHjzc3Nzv22RLQqhwfEphBukx6mTH6wEEn2kOru/NPFc4gMn4hZZhcrgGGsAIaxAhjGCmAYK4BhrACGsQIYxgpgGCuAYawAhrECGMYKYBgrgGGsAIaxAhjGCmAYK4BhrACGsQIYxgpgGCuAYdS2FIsp7AgwSk7O/wCqCi/+JioQYgAAAABJRU5ErkJggg=='); +} + +.two-column { + display: table; + width: 100%; +} +.two-column > div { + display: table-cell; + width: 50%; +} + +#properties-list .fieldset .two-column { + padding-top: 10px; + display: flex; +} + +#properties-list .two-column .fieldset { + width: 50%; + margin: 0; + padding: 0; + border-top: none; + box-shadow: none; +} + +#properties-list .two-column .column { + position: relative; + top: -10px; +} + +#properties-list .two-column .fieldset div.legend { + width: 100%; + margin: 21px -21px 0 -21px; + padding: 16px 0 0 21px; + font-family: Raleway-Regular; + font-size: 12px; + color: #afafaf; + height: 10px; + text-transform: uppercase; + outline: none; +} + +#properties-list .two-column + .property { + margin-top: 6px; +} + +.fieldset .checkbox-sub-props { + margin-top: 0; +} + +.fieldset .checkbox-sub-props .property:first-child { + margin-top: 0; +} + +.column { + vertical-align: top; +} + +.indent { + margin-left: 24px; +} + +::-webkit-scrollbar { + width: 20px; + height: 10px; +} +::-webkit-scrollbar-track { + background-color: #2e2e2e; +} +#entity-table-scroll::-webkit-scrollbar-track { + border-bottom-right-radius: 7px; +} + +::-webkit-scrollbar-thumb { + background-color: #696969; + border: 2px solid #2e2e2e; + border-radius: 8px; +} + +/* FIXME: Revisit textarea resizer/corner when move to Qt 5.6 or later: see if can get resizer/corner to always be visible and +have correct background color with and without scrollbars. */ +textarea:enabled::-webkit-resizer { + background-size: 10px 10px; + background: #252525 url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAIAAAACUFjqAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAXSURBVChTY1RVVWXADZigNA4wMqUZGACS3gCD5UUtKAAAAABJRU5ErkJggg==) no-repeat bottom right; +} +textarea:focus::-webkit-resizer { + background-size: 10px 10px; + background: #000000 url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAACOSURBVChThdC5DQQhDAXQvyRI5LQxFdABARWQElAPogYkiqEWQhLYGe8xxzJaS5a/8AuQHwDG2n+Lvee0hBDQWlO+hRvy3mNZFjDG5vCDOOeIMaL3/guPKISAWiu9n+AVSSlhraXdF86Qcw6tNdoTvEOlFOScd6iUOv3JGEMopYQx9jNvaawnoHnNr8Z4AuRLPOq2gPgnAAAAAElFTkSuQmCC) no-repeat bottom right; +} +textarea:enabled[scrolling="true"]::-webkit-resizer { + background-size: 10px 10px; + background: #2e2e2e url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAACKSURBVChTjdAxDsMgDAXQT4UYuQIzCwsSKxsSJ4YDoByDY7AwUOG2aZMQqX+xhd9gzIwxA3/k8a7LCCFgraX+Fk4UY4RSCoyxNfwgzjlyzhhjXOEvSimhtUbvB3hGUkp472m2wxUKIaD3TnOCd6jWim3bvlBrfdjJOUeolEJoZj/4PMH83bl/BXgCWSs2Z09IjgoAAAAASUVORK5CYII=) no-repeat bottom right; +} + + +div#grid-section, body#entity-list-body { + padding-bottom: 0; + margin: 16px; +} + +#entity-list-header { + margin-bottom: 36px; +} + +#entity-list-header div { + display: inline-block; + width: 65px; + margin-right: 6px; +} + +#entity-list-header div input:first-child { + margin-right: 0; + float: left; + width: 33px; + border-right: 1px solid #808080; + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} +#entity-list-header div input:last-child { + margin-right: 0; + float: right; + border-top-left-radius: 0; + border-bottom-left-radius: 0; +} + +#delete { + float: right; + margin-right: 0; + background-color: #ff0000; +} + +#entity-list { + position: relative; /* New positioning context. */ +} + +#filter-area { + padding-right: 168px; + padding-bottom: 24px; +} + +#filter-type-multiselect-box select { + border-radius: 14.5px; + width: 107px; + height: 28px; +} +#filter-type-options { + position: absolute; + top: 48px; +} +#filter-type-options div { + position: relative; + height: 22px; +} +#filter-type-options span { + position: relative; + top: 3px; + font-family: HiFi-Glyphs; + font-size: 13px; + color: #000000; + padding-left: 6px; + padding-right: 4px; +} +#filter-type-options label { + position: absolute; + top: -20px; + z-index: 2; + height: 22px; + width: 200px; + padding-top: 1px; +} +#filter-type-options-buttons { + top: -22px; + width: 224px; + z-index: 2; + background-color: #afafaf; + padding-bottom: 6px; +} +#filter-type-options input[type=button] { + position: relative; + left: 16px; + z-index: 3; + height: 23px; + min-width: 60px; + font-size: 10px; + color: #000; + background: linear-gradient(#afafaf 20%, #808080 100%); +} +#filter-type-options input[type=button]:enabled:hover { + background: linear-gradient(#afafaf 20%, #575757 100%); +} + +#filter-search-and-icon { + position: relative; + left: 118px; + width: calc(100% - 126px); +} + +#filter-in-view { + position: absolute; + top: 0; + right: 126px; +} + +#filter-radius-and-unit { + position: relative; + float: right; + margin-right: -168px; + top: -45px; +} +#filter-radius-and-unit label { + margin-left: 2px; +} +#filter-radius-and-unit span { + position: relative; + top: 25px; + right: 9px; + z-index: 2; + font-style: italic; +} +#filter-radius-and-unit input { + width: 120px; + border-radius: 14.5px; + font-style: italic; +} +#filter-radius-and-unit input[type=number]::-webkit-inner-spin-button { + display: none; +} + +#entity-list-footer { + padding-top: 9px; +} + +#footer-text { + float: right; + padding-top: 12px; + padding-right: 22px; +} + +input[type=button]#export { + height: 38px; + width: 180px; +} + +#no-entities { + display: none; + position: absolute; + top: 80px; + padding: 12px; + font-family: FiraSans-SemiBold; + font-size: 15px; + font-style: italic; + color: #afafaf; +} + +#entity-table-columns-multiselect { + position: absolute; + top: 51px; + right: 22px; +} +#entity-table-columns-multiselect-box select { + height: 28px; + width: 20px; + background-color: #1c1c1c; + border-top-right-radius: 7px; +} +#entity-table-columns-options { + position: absolute; + top: 50px; + right: 110px; +} +#entity-table-columns-options div { + position: relative; + height: 22px; +} +#entity-table-columns-options label { + position: absolute; + top: -22px; + height: 22px; + width: 100px; + padding-top: 4px; +} +#entity-table-columns-options input[type=checkbox] + label { + padding-left: 30px; +} + +#entity-table-scroll { + /* Height is set by JavaScript. */ + width: 100%; + overflow-x: hidden; + overflow-y: auto; + box-sizing: border-box; + padding-top: 28px; /* Space for header and footer outside of scroll region. */ + margin-top: 28px; + border-left: 2px solid #575757; + border-right: 2px solid #575757; + border-bottom: 2px solid #575757; + border-bottom-left-radius: 7px; + border-bottom-right-radius: 7px; + background-color: #1c1c1c; +} + +#entity-table-scroll .glyph { + font-family: HiFi-Glyphs; + font-size: 15px; +} + +#entity-table { + margin-top: -28px; + margin-bottom: -18px; + table-layout: fixed; + border: none; + background-color: #1c1c1c; +} + +#entity-table thead tr, #entity-table thead tr th { + background: none; +} + +#entity-table .glyph { + margin: 0 -2px 0 -2px; + vertical-align: middle; +} + +#entity-table thead { + box-sizing: border-box; + border: 2px solid #575757; + border-top-left-radius: 7px; + border-top-right-radius: 7px; + border-bottom: 1px solid #575757; + position: absolute; + top: 49px; + left: 0; + width: 100%; + word-wrap: nowrap; + white-space: nowrap; + overflow: hidden; +} + +#entity-table th { + display: inline-block; + box-sizing: border-box; + padding: 5px 0 0 0; + vertical-align: middle; + overflow: hidden; + text-overflow: ellipsis; +} + +#entity-table th:focus { + outline: none; +} + +#entity-table th .glyph { + position: relative; + left: 4px; +} +#entity-table th .glyph + .sort-order { + position: relative; + left: 4px; +} + +#entity-table thead .sort-order { + display: inline-block; + width: 8px; + margin: -5px 0 -3px 0; + vertical-align: middle; +} + +#entity-table thead .resizer { + position: absolute; + top: 1px; + height: 26px; + width: 10px; + cursor: col-resize; +} + +#entity-table .dragging { + background-color: #b3ecff; +} + +#entity-table td { + box-sizing: border-box; +} +#entity-table td.glyph { + text-align: center; + padding: 0; +} + +#properties-base { + border-top: none !important; + box-shadow: none !important; + margin-bottom: 5px !important; +} + +#properties-base #property-type-icon { + font-family: HiFi-Glyphs; + font-size: 31px; + color: #00b4ef; + margin: -4px 12px -4px -2px; + width: auto; + display: none; +} + +#properties-base #property-type { + padding: 5px 24px 5px 0; + border-right: 1px solid #808080; + width: auto; + display: inline-block; +} + +#properties-base .checkbox label span { + font-family: HiFi-Glyphs; + font-size: 20px; + padding-right: 6px; + vertical-align: top; + position: relative; + top: -4px; +} + +#properties-base input[type=checkbox]:checked + label span { + color: #ffffff; +} + +#id label { + width: 24px; +} +#property-id { + display: inline-block; +} +#property-id::selection { + color: #000000; + background-color: #00b4ef; +} + +input#property-scale-button-rescale { + min-width: 50px; + left: 152px; +} +input#property-scale-button-reset { + margin-right: 0; + left: 250px; +} + +#property-userData-static, +#property-materialData-static { + display: none; + z-index: 99; + position: absolute; + width: 96%; + padding-left: 1%; + margin-top: 5px; + margin-bottom: 10px; + background-color: #2e2e2e; +} + +#property-userData-saved, +#property-materialData-saved { + margin-top: 5px; + font-size: 16px; + display: none; +} + + +#div-property-collisionSoundURL[style*="display: none"] + .property { + margin-top: 0; +} + +.context-menu { + display: none; + position: fixed; + color: #000000; + background-color: #afafaf; + padding: 5px 0 5px 0; + cursor: default; +} +.context-menu li { + list-style-type: none; + padding: 4px 18px 4px 18px; + margin: 0; + white-space: nowrap; +} +.context-menu li:hover { + background-color: #e3e3e3; +} +.context-menu li.separator { + border-top: 1px solid #333333; + margin: 5px 5px; + padding: 0 0; +} +.context-menu li.disabled { + color: #333333; +} +.context-menu li.separator:hover, .context-menu li.disabled:hover { + background-color: #afafaf; +} + +input.rename-entity { + height: 100%; + width: 100%; + border: none; + font-family: FiraSans-SemiBold; + font-size: 15px; + /* need this to show the text cursor when the input field is empty */ + padding-left: 2px; +} + +.create-app-tooltip { + z-index: 100; + position: absolute; + background: #6a6a6a; + border: 1px solid black; + width: 258px; + min-height: 20px; + padding: 5px; + z-index: 100; +} + +.create-app-tooltip .create-app-tooltip-description { + font-size: 12px; + font-style: italic; + color: #ffffff; +} + +.create-app-tooltip .create-app-tooltip-js-attribute { + font-family: Raleway-SemiBold; + font-size: 11px; + color: #000000; + bottom: 0; + margin-top: 5px; +} + +#toggle-space-mode::before { + font-family: HiFi-Glyphs; + font-size: 20px; + text-transform: none; + min-width: 32px; + padding-right: 4px; + vertical-align: middle; +} + +#toggle-space-mode.space-mode-local::before { + content: "m"; +} + +#toggle-space-mode.space-mode-world::before { + content: "\e02c"; +} + +.container { + display: flex; + flex-flow: row nowrap; + margin-bottom: 8px; + min-height: 28px; +} + +.container > label { + margin-top: 6px; + width: 160px; + min-width: 160px; + max-width: 160px; +} + +.container > div.checkbox { + padding-top: 6px; +} + +.container > .value { + width: 100%; +} + +.container .row { + display: flex; + flex-flow: row nowrap; +} + +.container.shrink { + width: min-content; +} + +.fstuple { + display: flex; + flex-flow: row; +} +.fstuple input { + margin-left: 4px; + margin-right: 10px; +} +.fstuple label.red, .fstuple label.x, .fstuple label.w { + color: #C62147; +} +.fstuple label.green, .fstuple label.y, .fstuple label.h { + color: #359D85; +} +.fstuple label.blue, .fstuple label.z { + color: #0093C5; +} + +.xyz.fstuple, .pyr.fstuple { + position: relative; + left: -12px; + min-width: 50px; + width: 100px; +} + +.rgb.fstuple .tuple { + display: none; +} + +input.number-slider { + background: #575757; + border-radius: 4px; + color: white; +} + +.fstuple > div { + display: flex; + align-items: center; + justify-content: left; +} + +.flex-row { + display: flex; + flex-flow: row; +} + +.flex-column { + display: flex; + flex-flow: column; +} + +.flex-center { + align-items: center; +} + +.flex-evenly-spaced { + flex: 1; +} + +#property-serverScripts-status { + font-family: Raleway-Light; + font-size: 14px; + margin: 6px 0; + cursor: default; +} + +#property-name, #property-id { + display: flex; + width: 100%; +} + +.spacemode-hidden { + display: none; +} + +#placeholder-property-type { + min-width: 0; +} + +.collapse-icon { + cursor: pointer; +} + +#property-userData-editor.error { + border: 2px solid red; +} + +#property-userData-editorStatus { + color: white; + background-color: red; + padding: 5px; + display: none; + cursor: pointer; +} + +#property-materialData-editor.error { + border: 2px solid red; +} + +#property-materialData-editorStatus { + color: white; + background-color: red; + padding: 5px; + display: none; + cursor: pointer; +} + +input[type=number].hide-spinner::-webkit-inner-spin-button { + -webkit-appearance: none; + visibility: hidden; +} + +div.jsoneditor-menu a.jsoneditor-poweredBy { + display: none; +} diff --git a/scripts/simplifiedUI/system/html/css/hifi-style.css b/scripts/simplifiedUI/system/html/css/hifi-style.css new file mode 100644 index 0000000000..90a5b366c2 --- /dev/null +++ b/scripts/simplifiedUI/system/html/css/hifi-style.css @@ -0,0 +1,175 @@ +/* +// hifi-style.css +// +// Created by Zach Fox on 2017-04-18 +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +*/ + +@font-face { + font-family: Raleway-Regular; + src: url(../../../../resources/fonts/Raleway-Regular.ttf), /* Windows production */ + url(../../../../fonts/Raleway-Regular.ttf), /* OSX production */ + url(../../../../interface/resources/fonts/Raleway-Regular.ttf); /* Development, running script in /HiFi/examples */ +} + +@font-face { + font-family: Raleway-Light; + src: url(../../../../resources/fonts/Raleway-Light.ttf), + url(../../../../fonts/Raleway-Light.ttf), + url(../../../../interface/resources/fonts/Raleway-Light.ttf); +} + +@font-face { + font-family: Raleway-Bold; + src: url(../../../../resources/fonts/Raleway-Bold.ttf), + url(../../../../fonts/Raleway-Bold.ttf), + url(../../../../interface/resources/fonts/Raleway-Bold.ttf); +} + +@font-face { + font-family: Raleway-SemiBold; + src: url(../../../../resources/fonts/Raleway-SemiBold.ttf), + url(../../../../fonts/Raleway-SemiBold.ttf), + url(../../../../interface/resources/fonts/Raleway-SemiBold.ttf); +} + +@font-face { + font-family: FiraSans-SemiBold; + src: url(../../../../resources/fonts/FiraSans-SemiBold.ttf), + url(../../../../fonts/FiraSans-SemiBold.ttf), + url(../../../../interface/resources/fonts/FiraSans-SemiBold.ttf); +} + +@font-face { + font-family: AnonymousPro-Regular; + src: url(../../../../resources/fonts/AnonymousPro-Regular.ttf), + url(../../../../fonts/AnonymousPro-Regular.ttf), + url(../../../../interface/resources/fonts/AnonymousPro-Regular.ttf); +} + +@font-face { + font-family: HiFi-Glyphs; + src: url(../../../../resources/fonts/hifi-glyphs.ttf), + url(../../../../fonts/hifi-glyphs.ttf), + url(../../../../interface/resources/fonts/hifi-glyphs.ttf); +} + +body { + color: #afafaf; + background-color: #404040; + font-family: Raleway-Regular; + font-size: 15px; + + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + + overflow-x: hidden; + overflow-y: auto; +} + +hr { + border: none; + background: #404040 url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAAECAYAAACp8Z5+AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAAjSURBVBhXY1RVVf3PgARYjIyMoEwIYHRwcEBRwQSloYCBAQCwjgPMiI7W2QAAAABJRU5ErkJggg==) repeat-x top left; + padding: 1px; + -webkit-margin-before: 0; + -webkit-margin-after: 0; + -webkit-margin-start: 0; + -webkit-margin-end: 0; + width: 100%; + position: absolute; +} + +.hifi-glyph { + font-family: HiFi-Glyphs; + border: none; + //margin: -10px; + padding: 0; +} + +input[type=radio] { + width: 2em; + margin: 0; + padding: 0; + font-size: 1em; + opacity: 0; +} +input[type=radio] + label{ + display: inline-block; + margin-left: -2em; + line-height: 2em; + font-family: Raleway-SemiBold; + font-size: 14px; +} +input[type=radio] + label > span{ + display: inline-block; + width: 20px; + height: 20px; + margin: 5px; + border-radius: 50%; + background: #6B6A6B; + background-image: linear-gradient(#7D7D7D, #6B6A6B); + vertical-align: bottom; +} +input[type=radio]:checked + label > span{ + background-image: linear-gradient(#7D7D7D, #6B6A6B); +} +input[type=radio]:active + label > span, +input[type=radio]:hover + label > span{ + background-image: linear-gradient(#FFFFFF, #AFAFAF); +} +input[type=radio]:checked + label > span > span, +input[type=radio]:active + label > span > span{ + display: block; + width: 10px; + height: 10px; + margin: 3px; + border: 2px solid #36CDFF; + border-radius: 50%; + background: #00B4EF; +} + +.grayButton { + font-family: Raleway-Bold; + font-size: 13px; + color: black; + padding: 0 10px; + border-radius: 3px; + border-width: 0; + background-image: linear-gradient(#FFFFFF, #AFAFAF); + min-height: 30px; +} +.grayButton:hover { + background-image: linear-gradient(#FFFFFF, #FFFFFF); +} +.grayButton:active { + background-image: linear-gradient(#AFAFAF, #AFAFAF); +} +.grayButton:disabled { + background-image: linear-gradient(#FFFFFF, ##AFAFAF); +} +.blueButton { + font-family: Raleway-Bold; + font-size: 13px; + color: white; + padding: 0 10px; + border-radius: 3px; + border-width: 0; + background-image: linear-gradient(#00B4EF, #1080B8); + min-height: 30px; +} +.blueButton:hover { + background-image: linear-gradient(#00B4EF, #00B4EF); +} +.blueButton:active { + background-image: linear-gradient(#1080B8, #1080B8); +} +.blueButton:disabled { + background-image: linear-gradient(#FFFFFF, #AFAFAF); +} diff --git a/scripts/simplifiedUI/system/html/css/img/jsoneditor-icons.svg b/scripts/simplifiedUI/system/html/css/img/jsoneditor-icons.svg new file mode 100644 index 0000000000..1b40068aad --- /dev/null +++ b/scripts/simplifiedUI/system/html/css/img/jsoneditor-icons.svg @@ -0,0 +1,893 @@ + + + JSON Editor Icons + + + + image/svg+xml + + JSON Editor Icons + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/html/css/img/mt-expand-hover.svg b/scripts/simplifiedUI/system/html/css/img/mt-expand-hover.svg new file mode 100644 index 0000000000..a8e84c42ad --- /dev/null +++ b/scripts/simplifiedUI/system/html/css/img/mt-expand-hover.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/scripts/simplifiedUI/system/html/css/img/mt-expand-normal.svg b/scripts/simplifiedUI/system/html/css/img/mt-expand-normal.svg new file mode 100644 index 0000000000..aac349ebda --- /dev/null +++ b/scripts/simplifiedUI/system/html/css/img/mt-expand-normal.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/scripts/simplifiedUI/system/html/css/img/mt-goto-hover.svg b/scripts/simplifiedUI/system/html/css/img/mt-goto-hover.svg new file mode 100644 index 0000000000..4cad54331a --- /dev/null +++ b/scripts/simplifiedUI/system/html/css/img/mt-goto-hover.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/scripts/simplifiedUI/system/html/css/img/mt-goto-normal.svg b/scripts/simplifiedUI/system/html/css/img/mt-goto-normal.svg new file mode 100644 index 0000000000..ead63329fb --- /dev/null +++ b/scripts/simplifiedUI/system/html/css/img/mt-goto-normal.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/scripts/simplifiedUI/system/html/css/img/mt-mute-hover.svg b/scripts/simplifiedUI/system/html/css/img/mt-mute-hover.svg new file mode 100644 index 0000000000..9a18ccd933 --- /dev/null +++ b/scripts/simplifiedUI/system/html/css/img/mt-mute-hover.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/scripts/simplifiedUI/system/html/css/img/mt-mute-normal.svg b/scripts/simplifiedUI/system/html/css/img/mt-mute-normal.svg new file mode 100644 index 0000000000..472f03f138 --- /dev/null +++ b/scripts/simplifiedUI/system/html/css/img/mt-mute-normal.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/scripts/simplifiedUI/system/html/css/jsoneditor.css b/scripts/simplifiedUI/system/html/css/jsoneditor.css new file mode 100644 index 0000000000..eedef60a7f --- /dev/null +++ b/scripts/simplifiedUI/system/html/css/jsoneditor.css @@ -0,0 +1,930 @@ +/* reset styling (prevent conflicts with bootstrap, materialize.css, etc.) */ + +div.jsoneditor input { + height: auto; + border: inherit; +} + +div.jsoneditor input:focus { + border: none !important; + box-shadow: none !important; +} + +div.jsoneditor table { + border-collapse: collapse; + width: auto; +} + +div.jsoneditor td, +div.jsoneditor th { + padding: 0; + display: table-cell; + text-align: left; + vertical-align: inherit; + border-radius: inherit; +} + + +div.jsoneditor-field, +div.jsoneditor-value, +div.jsoneditor-readonly { + border: 1px solid transparent; + min-height: 16px; + min-width: 32px; + padding: 2px; + margin: 1px; + word-wrap: break-word; + float: left; +} + +/* adjust margin of p elements inside editable divs, needed for Opera, IE */ + +div.jsoneditor-field p, +div.jsoneditor-value p { + margin: 0; +} + +div.jsoneditor-value { + word-break: break-word; +} + +div.jsoneditor-readonly { + min-width: 16px; + color: red; +} + +div.jsoneditor-empty { + border-color: lightgray; + border-style: dashed; + border-radius: 2px; +} + +div.jsoneditor-field.jsoneditor-empty::after, +div.jsoneditor-value.jsoneditor-empty::after { + pointer-events: none; + color: lightgray; + font-size: 8pt; +} + +div.jsoneditor-field.jsoneditor-empty::after { + content: "field"; +} + +div.jsoneditor-value.jsoneditor-empty::after { + content: "value"; +} + +div.jsoneditor-value.jsoneditor-url, +a.jsoneditor-value.jsoneditor-url { + color: green; + text-decoration: underline; +} + +a.jsoneditor-value.jsoneditor-url { + display: inline-block; + padding: 2px; + margin: 2px; +} + +a.jsoneditor-value.jsoneditor-url:hover, +a.jsoneditor-value.jsoneditor-url:focus { + color: #ee422e; +} + +div.jsoneditor td.jsoneditor-separator { + padding: 3px 0; + vertical-align: top; + color: gray; +} + +div.jsoneditor-field[contenteditable=true]:focus, +div.jsoneditor-field[contenteditable=true]:hover, +div.jsoneditor-value[contenteditable=true]:focus, +div.jsoneditor-value[contenteditable=true]:hover, +div.jsoneditor-field.jsoneditor-highlight, +div.jsoneditor-value.jsoneditor-highlight { + background-color: #FFFFAB; + border: 1px solid yellow; + border-radius: 2px; +} + +div.jsoneditor-field.jsoneditor-highlight-active, +div.jsoneditor-field.jsoneditor-highlight-active:focus, +div.jsoneditor-field.jsoneditor-highlight-active:hover, +div.jsoneditor-value.jsoneditor-highlight-active, +div.jsoneditor-value.jsoneditor-highlight-active:focus, +div.jsoneditor-value.jsoneditor-highlight-active:hover { + background-color: #ffee00; + border: 1px solid #ffc700; + border-radius: 2px; +} + +div.jsoneditor-value.jsoneditor-string { + color: #008000; +} + +div.jsoneditor-value.jsoneditor-object, +div.jsoneditor-value.jsoneditor-array { + min-width: 16px; + color: #808080; +} + +div.jsoneditor-value.jsoneditor-number { + color: #ee422e; +} + +div.jsoneditor-value.jsoneditor-boolean { + color: #ff8c00; +} + +div.jsoneditor-value.jsoneditor-null { + color: #004ED0; +} + +div.jsoneditor-value.jsoneditor-invalid { + color: #000000; +} + +div.jsoneditor-tree button { + width: 24px; + height: 24px; + padding: 0; + margin: 0; + border: none; + cursor: pointer; + background: transparent url("img/jsoneditor-icons.svg"); +} + +div.jsoneditor-mode-view tr.jsoneditor-expandable td.jsoneditor-tree, +div.jsoneditor-mode-form tr.jsoneditor-expandable td.jsoneditor-tree { + cursor: pointer; +} + +div.jsoneditor-tree button.jsoneditor-collapsed { + background-position: 0 -48px; +} + +div.jsoneditor-tree button.jsoneditor-expanded { + background-position: 0 -72px; +} + +div.jsoneditor-tree button.jsoneditor-contextmenu { + background-position: -48px -72px; +} + +div.jsoneditor-tree button.jsoneditor-contextmenu:hover, +div.jsoneditor-tree button.jsoneditor-contextmenu:focus, +div.jsoneditor-tree button.jsoneditor-contextmenu.jsoneditor-selected, +tr.jsoneditor-selected.jsoneditor-first button.jsoneditor-contextmenu { + background-position: -48px -48px; +} + +div.jsoneditor-tree *:focus { + outline: none; +} + +div.jsoneditor-tree button:focus { + /* TODO: nice outline for buttons with focus + outline: #97B0F8 solid 2px; + box-shadow: 0 0 8px #97B0F8; + */ + background-color: #f5f5f5; + outline: #e5e5e5 solid 1px; +} + +div.jsoneditor-tree button.jsoneditor-invisible { + visibility: hidden; + background: none; +} + +#userdata-editor{ + height:100%; +} + +div.jsoneditor { + color: #1A1A1A; + border: 1px solid #2e2e2e; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + box-sizing: border-box; + width: 100%; + overflow: hidden; + position: relative; + padding: 0; + line-height: 100%; +} + +div.jsoneditor-tree table.jsoneditor-tree { + border-collapse: collapse; + border-spacing: 0; + width: 100%; + margin: 0; +} + +div.jsoneditor-outer { + width: 100%; + margin: -35px 0 0 0; + padding: 0 0 0 0; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + box-sizing: border-box; + overflow-y: auto; +} + +.ace-jsoneditor { + min-height: 150px; + height: auto !important; +} + +div.jsoneditor-tree { + width: 100%; + position: relative; +} + +textarea.jsoneditor-text { + width: 100%; + margin: 0; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + box-sizing: border-box; + outline-width: 0; + border: none; + background-color: white; + resize: none; +} + +tr.jsoneditor-highlight, +tr.jsoneditor-selected { + background-color: #e6e6e6; +} + +tr.jsoneditor-selected button.jsoneditor-dragarea, +tr.jsoneditor-selected button.jsoneditor-contextmenu { + visibility: hidden; +} + +tr.jsoneditor-selected.jsoneditor-first button.jsoneditor-dragarea, +tr.jsoneditor-selected.jsoneditor-first button.jsoneditor-contextmenu { + visibility: visible; +} + +div.jsoneditor-tree button.jsoneditor-dragarea { + background: url("img/jsoneditor-icons.svg") -72px -72px; + cursor: move; +} + +div.jsoneditor-tree button.jsoneditor-dragarea:hover, +div.jsoneditor-tree button.jsoneditor-dragarea:focus, +tr.jsoneditor-selected.jsoneditor-first button.jsoneditor-dragarea { + background-position: -72px -48px; +} + +div.jsoneditor tr, +div.jsoneditor th, +div.jsoneditor td { + padding: 0; + margin: 0; + overflow: visible; +} + +div.jsoneditor td { + vertical-align: top; +} + +div.jsoneditor td.jsoneditor-tree { + vertical-align: top; +} + +div.jsoneditor-field, +div.jsoneditor-value, +div.jsoneditor td, +div.jsoneditor th, +div.jsoneditor textarea, +.jsoneditor-schema-error { + font-family: droid sans mono, consolas, monospace, courier new, courier, sans-serif; + font-size: 10pt; + color: grey; +} + +/* popover */ + +.jsoneditor-schema-error { + cursor: default; + display: inline-block; + /*font-family: arial, sans-serif;*/ + height: 24px; + line-height: 24px; + position: relative; + text-align: center; + width: 24px; +} + +div.jsoneditor-tree .jsoneditor-schema-error { + width: 24px; + height: 24px; + padding: 0; + margin: 0 4px 0 0; + background: url("img/jsoneditor-icons.svg") -168px -48px; +} + +.jsoneditor-schema-error .jsoneditor-popover { + background-color: #4c4c4c; + border-radius: 3px; + box-shadow: 0 0 5px rgba(0,0,0,0.4); + color: #fff; + display: none; + padding: 7px 10px; + position: absolute; + width: 200px; + z-index: 4; +} + +.jsoneditor-schema-error .jsoneditor-popover.jsoneditor-above { + bottom: 32px; + left: -98px; +} + +.jsoneditor-schema-error .jsoneditor-popover.jsoneditor-below { + top: 32px; + left: -98px; +} + +.jsoneditor-schema-error .jsoneditor-popover.jsoneditor-left { + top: -7px; + right: 32px; +} + +.jsoneditor-schema-error .jsoneditor-popover.jsoneditor-right { + top: -7px; + left: 32px; +} + +.jsoneditor-schema-error .jsoneditor-popover:before { + border-right: 7px solid transparent; + border-left: 7px solid transparent; + content: ''; + display: block; + left: 50%; + margin-left: -7px; + position: absolute; +} + +.jsoneditor-schema-error .jsoneditor-popover.jsoneditor-above:before { + border-top: 7px solid #4c4c4c; + bottom: -7px; +} + +.jsoneditor-schema-error .jsoneditor-popover.jsoneditor-below:before { + border-bottom: 7px solid #4c4c4c; + top: -7px; +} + +.jsoneditor-schema-error .jsoneditor-popover.jsoneditor-left:before { + border-left: 7px solid #4c4c4c; + border-top: 7px solid transparent; + border-bottom: 7px solid transparent; + content: ''; + top: 19px; + right: -14px; + left: inherit; + margin-left: inherit; + margin-top: -7px; + position: absolute; +} + +.jsoneditor-schema-error .jsoneditor-popover.jsoneditor-right:before { + border-right: 7px solid #4c4c4c; + border-top: 7px solid transparent; + border-bottom: 7px solid transparent; + content: ''; + top: 19px; + left: -14px; + margin-left: inherit; + margin-top: -7px; + position: absolute; +} + +.jsoneditor-schema-error:hover .jsoneditor-popover, +.jsoneditor-schema-error:focus .jsoneditor-popover { + display: block; + -webkit-animation: fade-in .3s linear 1, move-up .3s linear 1; + -moz-animation: fade-in .3s linear 1, move-up .3s linear 1; + -ms-animation: fade-in .3s linear 1, move-up .3s linear 1; +} + +@-webkit-keyframes fade-in { + from { + opacity: 0; + } + + to { + opacity: 1; + } +} + +@-moz-keyframes fade-in { + from { + opacity: 0; + } + + to { + opacity: 1; + } +} + +@-ms-keyframes fade-in { + from { + opacity: 0; + } + + to { + opacity: 1; + } +} + +/*@-webkit-keyframes move-up {*/ + +/*from { bottom: 24px; }*/ + +/*to { bottom: 32px; }*/ + +/*}*/ + +/*@-moz-keyframes move-up {*/ + +/*from { bottom: 24px; }*/ + +/*to { bottom: 32px; }*/ + +/*}*/ + +/*@-ms-keyframes move-up {*/ + +/*from { bottom: 24px; }*/ + +/*to { bottom: 32px; }*/ + +/*}*/ + +/* JSON schema errors displayed at the bottom of the editor in mode text and code */ + +.jsoneditor .jsoneditor-text-errors { + width: 100%; + border-collapse: collapse; + background-color: #ffef8b; + border-top: 1px solid #ffd700; +} + +.jsoneditor .jsoneditor-text-errors td { + padding: 3px 6px; + vertical-align: middle; +} + +.jsoneditor-text-errors .jsoneditor-schema-error { + border: none; + width: 24px; + height: 24px; + padding: 0; + margin: 0 4px 0 0; + background: url("img/jsoneditor-icons.svg") -168px -48px; +} +/* ContextMenu - main menu */ + +div.jsoneditor-contextmenu-root { + position: relative; + width: 0; + height: 0; +} + +div.jsoneditor-contextmenu { + position: absolute; + box-sizing: content-box; + z-index: 998; +} + +div.jsoneditor-contextmenu ul, +div.jsoneditor-contextmenu li { + box-sizing: content-box; +} + +div.jsoneditor-contextmenu ul { + position: relative; + left: 0; + top: 0; + width: 124px; + background: white; + border: 1px solid #d3d3d3; + box-shadow: 2px 2px 12px rgba(128, 128, 128, 0.3); + list-style: none; + margin: 0; + padding: 0; +} + +div.jsoneditor-contextmenu ul li button { + padding: 0; + margin: 0; + width: 124px; + height: 24px; + border: none; + cursor: pointer; + color: #4d4d4d; + background: transparent; + font-size: 10pt; + font-family: arial, sans-serif; + box-sizing: border-box; + line-height: 26px; + text-align: left; +} + +/* Fix button padding in firefox */ + +div.jsoneditor-contextmenu ul li button::-moz-focus-inner { + padding: 0; + border: 0; +} + +div.jsoneditor-contextmenu ul li button:hover, +div.jsoneditor-contextmenu ul li button:focus { + color: #1a1a1a; + background-color: #f5f5f5; + outline: none; +} + +div.jsoneditor-contextmenu ul li button.jsoneditor-default { + width: 92px; +} + +div.jsoneditor-contextmenu ul li button.jsoneditor-expand { + float: right; + width: 32px; + height: 24px; + border-left: 1px solid #e5e5e5; +} + +div.jsoneditor-contextmenu div.jsoneditor-icon { + float: left; + width: 24px; + height: 24px; + border: none; + padding: 0; + margin: 0; + background-image: url("img/jsoneditor-icons.svg"); +} + +div.jsoneditor-contextmenu ul li button div.jsoneditor-expand { + float: right; + width: 24px; + height: 24px; + padding: 0; + margin: 0 4px 0 0; + background: url("img/jsoneditor-icons.svg") 0 -72px; + opacity: 0.4; +} + +div.jsoneditor-contextmenu ul li button:hover div.jsoneditor-expand, +div.jsoneditor-contextmenu ul li button:focus div.jsoneditor-expand, +div.jsoneditor-contextmenu ul li.jsoneditor-selected div.jsoneditor-expand, +div.jsoneditor-contextmenu ul li button.jsoneditor-expand:hover div.jsoneditor-expand, +div.jsoneditor-contextmenu ul li button.jsoneditor-expand:focus div.jsoneditor-expand { + opacity: 1; +} + +div.jsoneditor-contextmenu div.jsoneditor-separator { + height: 0; + border-top: 1px solid #e5e5e5; + padding-top: 5px; + margin-top: 5px; +} + +div.jsoneditor-contextmenu button.jsoneditor-remove > div.jsoneditor-icon { + background-position: -24px -24px; +} + +div.jsoneditor-contextmenu button.jsoneditor-remove:hover > div.jsoneditor-icon, +div.jsoneditor-contextmenu button.jsoneditor-remove:focus > div.jsoneditor-icon { + background-position: -24px 0; +} + +div.jsoneditor-contextmenu button.jsoneditor-append > div.jsoneditor-icon { + background-position: 0 -24px; +} + +div.jsoneditor-contextmenu button.jsoneditor-append:hover > div.jsoneditor-icon, +div.jsoneditor-contextmenu button.jsoneditor-append:focus > div.jsoneditor-icon { + background-position: 0 0; +} + +div.jsoneditor-contextmenu button.jsoneditor-insert > div.jsoneditor-icon { + background-position: 0 -24px; +} + +div.jsoneditor-contextmenu button.jsoneditor-insert:hover > div.jsoneditor-icon, +div.jsoneditor-contextmenu button.jsoneditor-insert:focus > div.jsoneditor-icon { + background-position: 0 0; +} + +div.jsoneditor-contextmenu button.jsoneditor-duplicate > div.jsoneditor-icon { + background-position: -48px -24px; +} + +div.jsoneditor-contextmenu button.jsoneditor-duplicate:hover > div.jsoneditor-icon, +div.jsoneditor-contextmenu button.jsoneditor-duplicate:focus > div.jsoneditor-icon { + background-position: -48px 0; +} + +div.jsoneditor-contextmenu button.jsoneditor-sort-asc > div.jsoneditor-icon { + background-position: -168px -24px; +} + +div.jsoneditor-contextmenu button.jsoneditor-sort-asc:hover > div.jsoneditor-icon, +div.jsoneditor-contextmenu button.jsoneditor-sort-asc:focus > div.jsoneditor-icon { + background-position: -168px 0; +} + +div.jsoneditor-contextmenu button.jsoneditor-sort-desc > div.jsoneditor-icon { + background-position: -192px -24px; +} + +div.jsoneditor-contextmenu button.jsoneditor-sort-desc:hover > div.jsoneditor-icon, +div.jsoneditor-contextmenu button.jsoneditor-sort-desc:focus > div.jsoneditor-icon { + background-position: -192px 0; +} + +/* ContextMenu - sub menu */ + +div.jsoneditor-contextmenu ul li button.jsoneditor-selected, +div.jsoneditor-contextmenu ul li button.jsoneditor-selected:hover, +div.jsoneditor-contextmenu ul li button.jsoneditor-selected:focus { + color: white; + background-color: #ee422e; +} + +div.jsoneditor-contextmenu ul li { + overflow: hidden; +} + +div.jsoneditor-contextmenu ul li ul { + display: none; + position: relative; + left: -10px; + top: 0; + border: none; + box-shadow: inset 0 0 10px rgba(128, 128, 128, 0.5); + padding: 0 10px; + /* TODO: transition is not supported on IE8-9 */ + -webkit-transition: all 0.3s ease-out; + -moz-transition: all 0.3s ease-out; + -o-transition: all 0.3s ease-out; + transition: all 0.3s ease-out; +} + + + +div.jsoneditor-contextmenu ul li ul li button { + padding-left: 24px; + animation: all ease-in-out 1s; +} + +div.jsoneditor-contextmenu ul li ul li button:hover, +div.jsoneditor-contextmenu ul li ul li button:focus { + background-color: #f5f5f5; +} + +div.jsoneditor-contextmenu button.jsoneditor-type-string > div.jsoneditor-icon { + background-position: -144px -24px; +} + +div.jsoneditor-contextmenu button.jsoneditor-type-string:hover > div.jsoneditor-icon, +div.jsoneditor-contextmenu button.jsoneditor-type-string:focus > div.jsoneditor-icon, +div.jsoneditor-contextmenu button.jsoneditor-type-string.jsoneditor-selected > div.jsoneditor-icon { + background-position: -144px 0; +} + +div.jsoneditor-contextmenu button.jsoneditor-type-auto > div.jsoneditor-icon { + background-position: -120px -24px; +} + +div.jsoneditor-contextmenu button.jsoneditor-type-auto:hover > div.jsoneditor-icon, +div.jsoneditor-contextmenu button.jsoneditor-type-auto:focus > div.jsoneditor-icon, +div.jsoneditor-contextmenu button.jsoneditor-type-auto.jsoneditor-selected > div.jsoneditor-icon { + background-position: -120px 0; +} + +div.jsoneditor-contextmenu button.jsoneditor-type-object > div.jsoneditor-icon { + background-position: -72px -24px; +} + +div.jsoneditor-contextmenu button.jsoneditor-type-object:hover > div.jsoneditor-icon, +div.jsoneditor-contextmenu button.jsoneditor-type-object:focus > div.jsoneditor-icon, +div.jsoneditor-contextmenu button.jsoneditor-type-object.jsoneditor-selected > div.jsoneditor-icon { + background-position: -72px 0; +} + +div.jsoneditor-contextmenu button.jsoneditor-type-array > div.jsoneditor-icon { + background-position: -96px -24px; +} + +div.jsoneditor-contextmenu button.jsoneditor-type-array:hover > div.jsoneditor-icon, +div.jsoneditor-contextmenu button.jsoneditor-type-array:focus > div.jsoneditor-icon, +div.jsoneditor-contextmenu button.jsoneditor-type-array.jsoneditor-selected > div.jsoneditor-icon { + background-position: -96px 0; +} + +div.jsoneditor-contextmenu button.jsoneditor-type-modes > div.jsoneditor-icon { + background-image: none; + width: 6px; +} +div.jsoneditor-menu { + width: 100%; + height: 35px; + padding: 2px; + margin: 0; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + box-sizing: border-box; + color: white; + background-color: #2e2e2e; + border-bottom: 1px solid #2e2e2e; +} + +div.jsoneditor-menu > button, +div.jsoneditor-menu > div.jsoneditor-modes > button { + width: 26px; + height: 26px; + margin: 2px; + padding: 0; + border-radius: 2px; + border: 1px solid transparent; + background: transparent url("img/jsoneditor-icons.svg"); + color: white; + opacity: 0.8; + font-family: arial, sans-serif; + font-size: 10pt; + float: left; +} + +div.jsoneditor-menu > button:hover, +div.jsoneditor-menu > div.jsoneditor-modes > button:hover { + background-color: rgba(255,255,255,0.2); + border: 1px solid rgba(255,255,255,0.4); +} + +div.jsoneditor-menu > button:focus, +div.jsoneditor-menu > button:active, +div.jsoneditor-menu > div.jsoneditor-modes > button:focus, +div.jsoneditor-menu > div.jsoneditor-modes > button:active { + background-color: rgba(255,255,255,0.3); +} + +div.jsoneditor-menu > button:disabled, +div.jsoneditor-menu > div.jsoneditor-modes > button:disabled { + opacity: 0.5; +} + +div.jsoneditor-menu > button.jsoneditor-collapse-all { + background-position: 0 -96px; +} + +div.jsoneditor-menu > button.jsoneditor-expand-all { + background-position: 0 -120px; +} + +div.jsoneditor-menu > button.jsoneditor-undo { + background-position: -24px -96px; +} + +div.jsoneditor-menu > button.jsoneditor-undo:disabled { + background-position: -24px -120px; +} + +div.jsoneditor-menu > button.jsoneditor-redo { + background-position: -48px -96px; +} + +div.jsoneditor-menu > button.jsoneditor-redo:disabled { + background-position: -48px -120px; +} + +div.jsoneditor-menu > button.jsoneditor-compact { + background-position: -72px -96px; +} + +div.jsoneditor-menu > button.jsoneditor-format { + background-position: -72px -120px; +} + +div.jsoneditor-menu > div.jsoneditor-modes { + display: inline-block; + float: left; +} + +div.jsoneditor-menu > div.jsoneditor-modes > button { + background-image: none; + width: auto; + padding-left: 6px; + padding-right: 6px; +} + +div.jsoneditor-menu > button.jsoneditor-separator, +div.jsoneditor-menu > div.jsoneditor-modes > button.jsoneditor-separator { + margin-left: 10px; +} + +div.jsoneditor-menu a { + font-family: arial, sans-serif; + font-size: 10pt; + color: white; + opacity: 0.8; + vertical-align: middle; +} + +div.jsoneditor-menu a:hover { + opacity: 1; +} + +div.jsoneditor-menu a.jsoneditor-poweredBy { + font-size: 8pt; + position: absolute; + right: 0; + top: 0; + padding: 10px; +} +table.jsoneditor-search input, +table.jsoneditor-search div.jsoneditor-results { + font-family: arial, sans-serif; + font-size: 10pt; + color: #1A1A1A; + background: transparent; + /* For Firefox */ +} + +table.jsoneditor-search div.jsoneditor-results { + color: white; + padding-right: 5px; + line-height: 24px; +} + +table.jsoneditor-search { + position: absolute; + right: 4px; + top: 4px; + border-collapse: collapse; + border-spacing: 0; +} + +table.jsoneditor-search div.jsoneditor-frame { + border: 1px solid transparent; + background-color: white; + padding: 0 2px; + margin: 0; +} + +table.jsoneditor-search div.jsoneditor-frame table { + border-collapse: collapse; +} + +table.jsoneditor-search input { + width: 120px; + border: none; + outline: none; + margin: 1px; + line-height: 20px; +} + +table.jsoneditor-search button { + width: 16px; + height: 24px; + padding: 0; + margin: 0; + border: none; + background: url("img/jsoneditor-icons.svg"); + vertical-align: top; +} + +table.jsoneditor-search button:hover { + background-color: transparent; +} + +table.jsoneditor-search button.jsoneditor-refresh { + width: 18px; + background-position: -99px -73px; +} + +table.jsoneditor-search button.jsoneditor-next { + cursor: pointer; + background-position: -124px -73px; +} + +table.jsoneditor-search button.jsoneditor-next:hover { + background-position: -124px -49px; +} + +table.jsoneditor-search button.jsoneditor-previous { + cursor: pointer; + background-position: -148px -73px; + margin-right: 2px; +} + +table.jsoneditor-search button.jsoneditor-previous:hover { + background-position: -148px -49px; +} diff --git a/scripts/simplifiedUI/system/html/css/marketplaces.css b/scripts/simplifiedUI/system/html/css/marketplaces.css new file mode 100644 index 0000000000..04c132eab1 --- /dev/null +++ b/scripts/simplifiedUI/system/html/css/marketplaces.css @@ -0,0 +1,224 @@ +/* +// +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +*/ + +/* + CSS rules copied from edit-style.css. + Edit-style.css is not used in its entirety because don't want custom scrollbars; default scrollbar styling is used in order + to match other marketplace pages. +*/ + +@font-face { + font-family: Raleway-Regular; + src: url(../../../../resources/fonts/Raleway-Regular.ttf), /* Windows production */ + url(../../../../fonts/Raleway-Regular.ttf), /* OSX production */ + url(../../../../interface/resources/fonts/Raleway-Regular.ttf); /* Development, running script in /HiFi/examples */ +} + +@font-face { + font-family: Raleway-Bold; + src: url(../../../../resources/fonts/Raleway-Bold.ttf), + url(../../../../fonts/Raleway-Bold.ttf), + url(../../../../interface/resources/fonts/Raleway-Bold.ttf); +} + +@font-face { + font-family: Raleway-SemiBold; + src: url(../../../../resources/fonts/Raleway-SemiBold.ttf), + url(../../../../fonts/Raleway-SemiBold.ttf), + url(../../../../interface/resources/fonts/Raleway-SemiBold.ttf); +} + +@font-face { + font-family: FiraSans-SemiBold; + src: url(../../../../resources/fonts/FiraSans-SemiBold.ttf), + url(../../../../fonts/FiraSans-SemiBold.ttf), + url(../../../../interface/resources/fonts/FiraSans-SemiBold.ttf); +} + +* { + margin: 0; + padding: 0; +} + +body { + padding: 21px 21px 21px 21px; + + color: #afafaf; + background-color: #404040; + font-family: Raleway-Regular; + font-size: 15px; + + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + + overflow-x: hidden; + overflow-y: auto; +} + +input[type=button] { + font-family: Raleway-Bold; + font-size: 13px; + text-transform: uppercase; + vertical-align: top; + height: 28px; + min-width: 120px; + padding: 0px 18px; + margin-right: 6px; + border-radius: 5px; + border: none; + color: #fff; + background-color: #000; + background: linear-gradient(#343434 20%, #000 100%); + cursor: pointer; +} + +input[type=button].blue { + color: #fff; + background-color: #1080b8; + background: linear-gradient(#00b4ef 20%, #1080b8 100%); +} + + +/* + Marketplaces-specific CSS. +*/ + +body { + background: white; + padding: 0 0 0 0; + font-family:Raleway-SemiBold; +} +.marketplaces-container { + display: inline-block; + color: black; + width: 94%; + margin-left: 3%; + height: 100%; +} +.marketplaces-title { + margin-top: 45px; + margin-bottom: 20px; +} +.marketplaces-intro-text { + margin-bottom: 30px; +} +.marketplace-tile { + float:left; + width: 100%; + margin-bottom: 25px; +} +.marketplace-tile-first-column { + text-align: center; + float: left; + width: 33%; +} +.marketplace-tile-second-column { + float: left; + margin-left:4%; + width: 62%; +} +.exploreButton { + font-size: 16px !important; + width: 200px !important; + height: 45px !important; + margin-top: 20px; + margin-bottom: 30px; +} +.tile-divider { + width: 100%; + margin-left: 0%; + display: block; + height: 1px; + border: 0; + border-top: 1px solid lightgrey; + margin: 1em 0; + padding: 0; + margin-bottom: 30px; +} +.marketplace-tile-description { + margin-top: 15px; + margin-bottom: 30px; +} +.marketplace-tile-image { + margin-top:15px; + max-width: 256px; + height: 128px; + margin-bottom:60px; + -webkit-box-shadow: -1px 4px 16px 0px rgba(0, 0, 0, 0.48); + -moz-box-shadow: -1px 4px 16px 0px rgba(0, 0, 0, 0.48); + box-shadow: -1px 4px 16px 0px rgba(0, 0, 0, 0.48); +} +.marketplace-clara-steps { + padding-left: 15px; +} +.marketplace-clara-steps > li { + margin-top: 5px; +} + +#marketplace-navigation { + width: 100%; + height: 50px; + background: #00b4ef; + position: fixed; + bottom: 0; +} +#marketplace-navigation .glyph { + /* + // Target look but can't use font in injected script. + font-family: HiFi-Glyphs; + font-size: 40px; + margin-left: 20px; + */ + font-family: sans-serif; + font-size: 24px; + margin-left: 20px; + margin-right: 3px; + color: #fff; + line-height: 50px; +} +#marketplace-navigation .text { + color: #fff; + font-size: 18px; + line-height: 50px; + vertical-align: top; + position: relative; + top: 1px; +} +#marketplace-navigation input { + position: absolute; + right: 20px; + margin-top: 12px; + padding-left: 15px; + padding-right: 15px; +} + +@media (max-width:768px) { + .marketplace-tile-first-column { + float: left; + width: 100%; + } + .marketplace-tile-second-column { + float: left; + width: 100%; + } + .exploreButton-holder { + width:100%; + text-align:center; + } + .tile-divider { + width: 100%; + margin-left: 0; + } + .marketplace-tile-image { + margin-bottom: 15px; + } +} diff --git a/scripts/simplifiedUI/system/html/css/miniTablet.css b/scripts/simplifiedUI/system/html/css/miniTablet.css new file mode 100644 index 0000000000..7598332d28 --- /dev/null +++ b/scripts/simplifiedUI/system/html/css/miniTablet.css @@ -0,0 +1,92 @@ +/* +miniTablet.css + +Created by David Rowe on 20 Aug 2018. +Copyright 2018 High Fidelity, Inc. + +Distributed under the Apache License, Version 2.0. +See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +*/ + +* { + box-sizing: border-box; + padding: 0; + margin: 0; + user-select: none; +} + +html { + background-color: #404040; +} + +body { + height: 100%; +} + +section { + background-color: #404040; + position: relative; + padding: 16px 16px; +} + +.button { + width: 116px; + height: 84px; + margin-top: 16px; + text-align: center; +} + + .button:first-child { + margin-top: 0; + } + +img { + width: 40px; +} + +#mute { + padding-top: 19px; + background-size: 100% 100%; + background-image: url("./img/mt-mute-normal.svg"); +} + + #mute:hover { + background-image: url("./img/mt-mute-hover.svg"); + } + +#goto { + padding-top: 19px; + background-size: 100% 100%; + background-image: url("./img/mt-goto-normal.svg"); +} + + #goto:hover { + background-image: url("./img/mt-goto-hover.svg"); + } + + #goto:hover.unhover { + background-image: url("./img/mt-goto-normal.svg"); + } + +#expand { + position: absolute; + right: 1px; + bottom: -1px; + width: 50px; + height: 50px; + background-size: 100% 100%; + background-image: url("./img/mt-expand-normal.svg"); +} + + #expand:hover { + background-image: url("./img/mt-expand-hover.svg"); + } + + #expand:hover.unhover { + background-image: url("./img/mt-expand-normal.svg"); + } + + #expand img { + width:34px; + margin-top: 7px; + } diff --git a/scripts/simplifiedUI/system/html/entityList.html b/scripts/simplifiedUI/system/html/entityList.html new file mode 100644 index 0000000000..986e5c09b0 --- /dev/null +++ b/scripts/simplifiedUI/system/html/entityList.html @@ -0,0 +1,93 @@ + + + + Entity List + + + + + + + + + + + + + +
+ +
+ + +
+ + + +
+
+
+
+
+ +
+
+
+ +
+ + +
+
+
+
+ Y +
+ +
+ + +
+
+
+ + + + +
+
+
+ +
+
+
+ +
+
+
+ There are no entities to display. Please check your filters or create an entity to begin. +
+
+
+ + + diff --git a/scripts/simplifiedUI/system/html/entityProperties.html b/scripts/simplifiedUI/system/html/entityProperties.html new file mode 100644 index 0000000000..67f03a33a2 --- /dev/null +++ b/scripts/simplifiedUI/system/html/entityProperties.html @@ -0,0 +1,51 @@ + + + + + Properties + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+
+
+
+
+
+
+
+
+
+ +
+ + + diff --git a/scripts/simplifiedUI/system/html/gridControls.html b/scripts/simplifiedUI/system/html/gridControls.html new file mode 100644 index 0000000000..8d6ee34bc0 --- /dev/null +++ b/scripts/simplifiedUI/system/html/gridControls.html @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + +
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+
+
+
+
+ +
+ + +
+
+
+ + diff --git a/scripts/simplifiedUI/system/html/img/blast_icon.svg b/scripts/simplifiedUI/system/html/img/blast_icon.svg new file mode 100644 index 0000000000..31df8e7f53 --- /dev/null +++ b/scripts/simplifiedUI/system/html/img/blast_icon.svg @@ -0,0 +1,20 @@ + + + + + + + + + diff --git a/scripts/simplifiedUI/system/html/img/blocks-tile.png b/scripts/simplifiedUI/system/html/img/blocks-tile.png new file mode 100644 index 0000000000..49de535c1c Binary files /dev/null and b/scripts/simplifiedUI/system/html/img/blocks-tile.png differ diff --git a/scripts/simplifiedUI/system/html/img/button-snap-print.svg b/scripts/simplifiedUI/system/html/img/button-snap-print.svg new file mode 100644 index 0000000000..d1570711d7 --- /dev/null +++ b/scripts/simplifiedUI/system/html/img/button-snap-print.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/html/img/clara-tile.png b/scripts/simplifiedUI/system/html/img/clara-tile.png new file mode 100644 index 0000000000..ae431dd510 Binary files /dev/null and b/scripts/simplifiedUI/system/html/img/clara-tile.png differ diff --git a/scripts/simplifiedUI/system/html/img/expand.svg b/scripts/simplifiedUI/system/html/img/expand.svg new file mode 100644 index 0000000000..f57e624374 --- /dev/null +++ b/scripts/simplifiedUI/system/html/img/expand.svg @@ -0,0 +1,85 @@ + + + +image/svg+xml + + + + + + + + + \ No newline at end of file diff --git a/scripts/simplifiedUI/system/html/img/fb_icon.svg b/scripts/simplifiedUI/system/html/img/fb_icon.svg new file mode 100644 index 0000000000..6d67d17bb2 --- /dev/null +++ b/scripts/simplifiedUI/system/html/img/fb_icon.svg @@ -0,0 +1,13 @@ + + + + + + diff --git a/scripts/simplifiedUI/system/html/img/hifi-marketplace-tile.png b/scripts/simplifiedUI/system/html/img/hifi-marketplace-tile.png new file mode 100644 index 0000000000..9a95c081a0 Binary files /dev/null and b/scripts/simplifiedUI/system/html/img/hifi-marketplace-tile.png differ diff --git a/scripts/simplifiedUI/system/html/img/hifi_icon.svg b/scripts/simplifiedUI/system/html/img/hifi_icon.svg new file mode 100644 index 0000000000..acbb98a3b3 --- /dev/null +++ b/scripts/simplifiedUI/system/html/img/hifi_icon.svg @@ -0,0 +1,19 @@ + + + + + + + diff --git a/scripts/simplifiedUI/system/html/img/loader.gif b/scripts/simplifiedUI/system/html/img/loader.gif new file mode 100644 index 0000000000..c464703c84 Binary files /dev/null and b/scripts/simplifiedUI/system/html/img/loader.gif differ diff --git a/scripts/simplifiedUI/system/html/img/snapshotIcon.png b/scripts/simplifiedUI/system/html/img/snapshotIcon.png new file mode 100644 index 0000000000..5cb2742a32 Binary files /dev/null and b/scripts/simplifiedUI/system/html/img/snapshotIcon.png differ diff --git a/scripts/simplifiedUI/system/html/img/twitter_icon.svg b/scripts/simplifiedUI/system/html/img/twitter_icon.svg new file mode 100644 index 0000000000..0393d963f2 --- /dev/null +++ b/scripts/simplifiedUI/system/html/img/twitter_icon.svg @@ -0,0 +1,12 @@ + + + + + + diff --git a/scripts/simplifiedUI/system/html/js/SnapshotReview.js b/scripts/simplifiedUI/system/html/js/SnapshotReview.js new file mode 100644 index 0000000000..1e8be9d644 --- /dev/null +++ b/scripts/simplifiedUI/system/html/js/SnapshotReview.js @@ -0,0 +1,814 @@ +/*jslint browser:true */ +/*jslint maxlen: 180*/ +"use strict"; +// +// SnapshotReview.js +// scripts/system/html/js/ +// +// Created by Howard Stearns 8/22/2016 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +var paths = []; +var idCounter = 0; +var imageCount = 0; +var blastShareText = "Blast to my Connections", + blastAlreadySharedText = "Already Blasted to Connections", + hifiShareText = "Share to Snaps Feed", + hifiAlreadySharedText = "Already Shared to Snaps Feed", + facebookShareText = "Share to Facebook", + twitterShareText = "Share to Twitter", + shareButtonLabelTextActive = "SHARE:", + shareButtonLabelTextInactive = "SHARE"; + +function fileExtensionMatches(filePath, extension) { + return filePath.split('.').pop().toLowerCase() === extension; +} + +function showSetupInstructions() { + var snapshotImagesDiv = document.getElementById("snapshot-images"); + snapshotImagesDiv.className = "snapshotInstructions"; + snapshotImagesDiv.innerHTML = 'Snapshot Instructions' + + '
' + + '

Take and share snaps and GIFs with people in High Fidelity, Facebook, and Twitter.

' + + "

Setup Instructions

" + + "

Before you can begin taking snaps, please choose where you'd like to save snaps on your computer:

" + + '
' + + '
' + + '' + + '
'; + document.getElementById("snap-button").disabled = true; +} +function showSetupComplete() { + var snapshotImagesDiv = document.getElementById("snapshot-images"); + snapshotImagesDiv.className = "snapshotInstructions"; + snapshotImagesDiv.innerHTML = 'Snapshot Instructions' + + '
' + + '
' + + '

Snapshot location set.

' + + '

Press the big red button to take a snap!

' + + '
'; + document.getElementById("snap-button").disabled = false; +} +function showSnapshotInstructions() { + var snapshotImagesDiv = document.getElementById("snapshot-images"); + snapshotImagesDiv.className = "snapshotInstructions"; + snapshotImagesDiv.innerHTML = 'Snapshot Instructions' + + '
' + + '

Take and share snaps and GIFs with people in High Fidelity, Facebook, and Twitter.

' + + '
' + + '
' + + '

Press the big red button to take a snap!

' + + '
'; +} +function chooseSnapshotLocation() { + EventBridge.emitWebEvent(JSON.stringify({ + type: "snapshot", + action: "chooseSnapshotLocation" + })); +} +function login() { + EventBridge.emitWebEvent(JSON.stringify({ + type: "snapshot", + action: "login" + })); +} +function clearImages() { + var snapshotImagesDiv = document.getElementById("snapshot-images"); + snapshotImagesDiv.classList.remove("snapshotInstructions"); + while (snapshotImagesDiv.hasChildNodes()) { + snapshotImagesDiv.removeChild(snapshotImagesDiv.lastChild); + } + paths = []; + imageCount = 0; + idCounter = 0; +} + +function selectImageWithHelpText(selectedID, isSelected) { + if (selectedID.id) { + selectedID = selectedID.id; // sometimes (?), `selectedID` is passed as an HTML object to these functions; we just want the ID + } + var imageContainer = document.getElementById(selectedID), + image = document.getElementById(selectedID + 'img'), + shareBar = document.getElementById(selectedID + "shareBar"), + helpTextDiv = document.getElementById(selectedID + "helpTextDiv"), + showShareButtonsButtonDiv = document.getElementById(selectedID + "showShareButtonsButtonDiv"), + itr, + containers = document.getElementsByClassName("shareControls"); + + if (isSelected) { + showShareButtonsButtonDiv.onclick = function () { selectImageWithHelpText(selectedID, false); }; + showShareButtonsButtonDiv.classList.remove("inactive"); + showShareButtonsButtonDiv.classList.add("active"); + + image.onclick = function () { selectImageWithHelpText(selectedID, false); }; + imageContainer.style.outline = "4px solid #00b4ef"; + imageContainer.style.outlineOffset = "-4px"; + + shareBar.style.backgroundColor = "rgba(0, 0, 0, 0.45)"; + shareBar.style.pointerEvents = "initial"; + + helpTextDiv.style.visibility = "visible"; + + for (itr = 0; itr < containers.length; itr += 1) { + var parentID = containers[itr].id.slice(0, 2); + if (parentID !== selectedID) { + selectImageWithHelpText(parentID, false); + } + } + } else { + showShareButtonsButtonDiv.onclick = function () { selectImageWithHelpText(selectedID, true); }; + showShareButtonsButtonDiv.classList.remove("active"); + showShareButtonsButtonDiv.classList.add("inactive"); + + image.onclick = function () { selectImageWithHelpText(selectedID, true); }; + imageContainer.style.outline = "none"; + + shareBar.style.backgroundColor = "rgba(0, 0, 0, 0.0)"; + shareBar.style.pointerEvents = "none"; + + helpTextDiv.style.visibility = "hidden"; + } +} +function selectImageToShare(selectedID, isSelected) { + if (selectedID.id) { + selectedID = selectedID.id; // sometimes (?), `selectedID` is passed as an HTML object to these functions; we just want the ID + } + var imageContainer = document.getElementById(selectedID), + image = document.getElementById(selectedID + 'img'), + shareBar = document.getElementById(selectedID + "shareBar"), + showShareButtonsDots = document.getElementById(selectedID + "showShareButtonsDots"), + showShareButtonsLabel = document.getElementById(selectedID + "showShareButtonsLabel"), + shareButtonsDiv = document.getElementById(selectedID + "shareButtonsDiv"), + shareBarHelp = document.getElementById(selectedID + "shareBarHelp"), + showShareButtonsButtonDiv = document.getElementById(selectedID + "showShareButtonsButtonDiv"), + itr, + containers = document.getElementsByClassName("shareControls"); + + if (isSelected) { + showShareButtonsButtonDiv.onclick = function () { selectImageToShare(selectedID, false); }; + showShareButtonsButtonDiv.classList.remove("inactive"); + showShareButtonsButtonDiv.classList.add("active"); + + image.onclick = function () { selectImageToShare(selectedID, false); }; + imageContainer.style.outline = "4px solid #00b4ef"; + imageContainer.style.outlineOffset = "-4px"; + + shareBar.style.backgroundColor = "rgba(0, 0, 0, 0.45)"; + shareBar.style.pointerEvents = "initial"; + + showShareButtonsDots.style.visibility = "hidden"; + showShareButtonsLabel.innerHTML = shareButtonLabelTextActive; + + shareButtonsDiv.style.visibility = "visible"; + shareBarHelp.style.visibility = "visible"; + + for (itr = 0; itr < containers.length; itr += 1) { + var parentID = containers[itr].id.slice(0, 2); + if (parentID !== selectedID) { + selectImageToShare(parentID, false); + } + } + } else { + showShareButtonsButtonDiv.onclick = function () { selectImageToShare(selectedID, true); }; + showShareButtonsButtonDiv.classList.remove("active"); + showShareButtonsButtonDiv.classList.add("inactive"); + + image.onclick = function () { selectImageToShare(selectedID, true); }; + imageContainer.style.outline = "none"; + + shareBar.style.backgroundColor = "rgba(0, 0, 0, 0.0)"; + shareBar.style.pointerEvents = "none"; + + showShareButtonsDots.style.visibility = "visible"; + showShareButtonsLabel.innerHTML = shareButtonLabelTextInactive; + + shareButtonsDiv.style.visibility = "hidden"; + shareBarHelp.style.visibility = "hidden"; + } +} +function createShareBar(parentID, isLoggedIn, canShare, isGif, blastButtonDisabled, hifiButtonDisabled, canBlast) { + var shareBar = document.createElement("div"), + shareBarHelpID = parentID + "shareBarHelp", + shareButtonsDivID = parentID + "shareButtonsDiv", + showShareButtonsButtonDivID = parentID + "showShareButtonsButtonDiv", + showShareButtonsDotsID = parentID + "showShareButtonsDots", + showShareButtonsLabelID = parentID + "showShareButtonsLabel", + blastToConnectionsButtonID = parentID + "blastToConnectionsButton", + shareWithEveryoneButtonID = parentID + "shareWithEveryoneButton", + facebookButtonID = parentID + "facebookButton", + twitterButtonID = parentID + "twitterButton", + shareBarInnerHTML = ''; + + shareBar.id = parentID + "shareBar"; + shareBar.className = "shareControls"; + + if (isLoggedIn) { + if (canShare) { + shareBarInnerHTML = '' + + '
' + + '' + + '' + + '' + + '
' + + '' + + ''; + + // Add onclick handler to parent DIV's img to toggle share buttons + document.getElementById(parentID + 'img').onclick = function () { selectImageToShare(parentID, true); }; + } else { + shareBarInnerHTML = '
' + + '' + + '' + + '' + + '
' + + '' + + ''; + // Add onclick handler to parent DIV's img to toggle share buttons + document.getElementById(parentID + 'img').onclick = function () { selectImageWithHelpText(parentID, true); }; + } + } else { + shareBarInnerHTML = '
' + + '' + + '' + + '' + + '
' + + '' + + ''; + // Add onclick handler to parent DIV's img to toggle share buttons + document.getElementById(parentID + 'img').onclick = function () { selectImageWithHelpText(parentID, true); }; + } + + shareBar.innerHTML = shareBarInnerHTML; + + return shareBar; +} +function appendShareBar(divID, isLoggedIn, canShare, isGif, blastButtonDisabled, hifiButtonDisabled, canBlast) { + if (divID.id) { + divID = divID.id; // sometimes (?), `containerID` is passed as an HTML object to these functions; we just want the ID + } + document.getElementById(divID).appendChild(createShareBar(divID, isLoggedIn, canShare, isGif, blastButtonDisabled, hifiButtonDisabled, canBlast)); + if (divID === "p0") { + if (isLoggedIn) { + if (canShare) { + selectImageToShare(divID, true); + } else { + selectImageWithHelpText(divID, true); + } + } else { + selectImageWithHelpText(divID, true); + } + } + if (isLoggedIn && canShare) { + if (canBlast) { + shareButtonHovered('blast', divID, false); + } else { + shareButtonHovered('hifi', divID, false); + } + } +} +function shareForUrl(selectedID) { + EventBridge.emitWebEvent(JSON.stringify({ + type: "snapshot", + action: "shareSnapshotForUrl", + data: paths[parseInt(selectedID.substring(1), 10)] + })); +} +function addImage(image_data, isLoggedIn, canShare, isGifLoading, isShowingPreviousImages, blastButtonDisabled, hifiButtonDisabled, canBlast) { + if (!image_data.localPath) { + return; + } + var imageContainer = document.createElement("DIV"), + img = document.createElement("IMG"), + isGif = fileExtensionMatches(image_data.localPath, "gif"), + id = "p" + (isGif ? "1" : "0"); + imageContainer.id = id; + imageContainer.style.width = "95%"; + imageContainer.style.height = "240px"; + imageContainer.style.margin = "5px auto"; + imageContainer.style.display = "flex"; + imageContainer.style.justifyContent = "center"; + imageContainer.style.alignItems = "center"; + imageContainer.style.position = "relative"; + img.id = id + "img"; + img.src = image_data.localPath; + imageContainer.appendChild(img); + document.getElementById("snapshot-images").appendChild(imageContainer); + paths.push(image_data.localPath); + img.onload = function () { + if (isGif) { + imageContainer.innerHTML += 'GIF'; + } + if (!isGifLoading) { + appendShareBar(id, isLoggedIn, canShare, isGif, blastButtonDisabled, hifiButtonDisabled, canBlast); + } + if ((!isShowingPreviousImages && ((isGif && !isGifLoading) || !isGif)) || (isShowingPreviousImages && !image_data.story_id)) { + shareForUrl(id); + } + if (isShowingPreviousImages && isLoggedIn && image_data.story_id) { + updateShareInfo(id, image_data.story_id); + } + if (isShowingPreviousImages) { + requestPrintButtonUpdate(); + } + }; + img.onerror = function () { + img.onload = null; + img.src = image_data.errorPath; + EventBridge.emitWebEvent(JSON.stringify({ + type: "snapshot", + action: "alertSnapshotLoadFailed" + })); + }; +} +function showConfirmationMessage(selectedID, destination) { + if (selectedID.id) { + selectedID = selectedID.id; // sometimes (?), `containerID` is passed as an HTML object to these functions; we just want the ID + } + + var opacity = 2.0, + fadeRate = 0.05, + timeBetweenFadesMS = 50, + confirmationMessageContainer = document.createElement("div"), + confirmationMessage = document.createElement("div"); + confirmationMessageContainer.className = "confirmationMessageContainer"; + + confirmationMessage.className = "confirmationMessage"; + + var socialIcon = document.createElement("img"); + switch (destination) { + case 'blast': + socialIcon.src = "img/blast_icon.svg"; + confirmationMessage.appendChild(socialIcon); + confirmationMessage.innerHTML += 'Blast Sent!'; + confirmationMessage.style.backgroundColor = "#EA4C5F"; + break; + case 'hifi': + socialIcon.src = "img/hifi_icon.svg"; + confirmationMessage.appendChild(socialIcon); + confirmationMessage.innerHTML += 'Snap Shared!'; + confirmationMessage.style.backgroundColor = "#1FC6A6"; + break; + } + + confirmationMessageContainer.appendChild(confirmationMessage); + document.getElementById(selectedID).appendChild(confirmationMessageContainer); + + setInterval(function () { + if (opacity <= fadeRate) { + confirmationMessageContainer.remove(); + } + opacity -= fadeRate; + confirmationMessageContainer.style.opacity = opacity; + }, timeBetweenFadesMS); +} +function showUploadingMessage(selectedID, destination) { + if (selectedID.id) { + selectedID = selectedID.id; // sometimes (?), `containerID` is passed as an HTML object to these functions; we just want the ID + } + + var shareBarHelp = document.getElementById(selectedID + "shareBarHelp"); + + shareBarHelp.innerHTML = 'Preparing to Share'; + shareBarHelp.classList.add("uploading"); + shareBarHelp.setAttribute("data-destination", destination); +} +function hideUploadingMessageAndMaybeShare(selectedID, storyID) { + if (selectedID.id) { + selectedID = selectedID.id; // sometimes (?), `containerID` is passed as an HTML object to these functions; we just want the ID + } + + var shareBarHelp = document.getElementById(selectedID + "shareBarHelp"), + shareBarHelpDestination = shareBarHelp.getAttribute("data-destination"); + + shareBarHelp.classList.remove("uploading"); + if (shareBarHelpDestination) { + switch (shareBarHelpDestination) { + case 'blast': + blastToConnections(selectedID, selectedID === "p1"); + shareBarHelp.innerHTML = blastAlreadySharedText; + break; + case 'hifi': + shareWithEveryone(selectedID, selectedID === "p1"); + shareBarHelp.innerHTML = hifiAlreadySharedText; + break; + case 'facebook': + var facebookButton = document.getElementById(selectedID + "facebookButton"); + window.open(facebookButton.getAttribute("href"), "_blank"); + shareBarHelp.innerHTML = facebookShareText; + // This emitWebEvent() call isn't necessary in the "hifi" and "blast" cases + // because the "removeFromStoryIDsToMaybeDelete()" call happens + // in snapshot.js when sharing with that method. + EventBridge.emitWebEvent(JSON.stringify({ + type: "snapshot", + action: "removeFromStoryIDsToMaybeDelete", + story_id: storyID + })); + break; + case 'twitter': + var twitterButton = document.getElementById(selectedID + "twitterButton"); + window.open(twitterButton.getAttribute("href"), "_blank"); + shareBarHelp.innerHTML = twitterShareText; + EventBridge.emitWebEvent(JSON.stringify({ + type: "snapshot", + action: "removeFromStoryIDsToMaybeDelete", + story_id: storyID + })); + break; + } + + shareBarHelp.setAttribute("data-destination", ""); + } +} +function updateShareInfo(containerID, storyID) { + if (containerID.id) { + containerID = containerID.id; // sometimes (?), `containerID` is passed as an HTML object to these functions; we just want the ID + } + var shareBar = document.getElementById(containerID + "shareBar"), + parentDiv = document.getElementById(containerID), + shareURL = "https://highfidelity.com/user_stories/" + storyID, + facebookButton = document.getElementById(containerID + "facebookButton"), + twitterButton = document.getElementById(containerID + "twitterButton"); + + parentDiv.setAttribute('data-story-id', storyID); + + facebookButton.setAttribute("target", "_blank"); + facebookButton.setAttribute("href", 'https://www.facebook.com/dialog/feed?app_id=1585088821786423&link=' + shareURL); + + twitterButton.setAttribute("target", "_blank"); + twitterButton.setAttribute("href", 'https://twitter.com/intent/tweet?text=I%20just%20took%20a%20snapshot!&url=' + shareURL + '&via=highfidelityVR&hashtags=VR,HiFi'); + + hideUploadingMessageAndMaybeShare(containerID, storyID); +} +function blastToConnections(selectedID, isGif) { + if (selectedID.id) { + selectedID = selectedID.id; // sometimes (?), `selectedID` is passed as an HTML object to these functions; we just want the ID + } + + var blastToConnectionsButton = document.getElementById(selectedID + "blastToConnectionsButton"), + shareBar = document.getElementById(selectedID + "shareBar"), + shareBarHelp = document.getElementById(selectedID + "shareBarHelp"); + blastToConnectionsButton.onclick = function () { }; + + var storyID = document.getElementById(selectedID).getAttribute("data-story-id"); + + if (storyID) { + EventBridge.emitWebEvent(JSON.stringify({ + type: "snapshot", + action: "blastToConnections", + story_id: storyID, + isGif: isGif + })); + showConfirmationMessage(selectedID, 'blast'); + blastToConnectionsButton.classList.add("disabled"); + blastToConnectionsButton.style.backgroundColor = "#000000"; + blastToConnectionsButton.style.opacity = "0.5"; + shareBarHelp.style.backgroundColor = "#000000"; + shareBarHelp.style.opacity = "0.5"; + } else { + showUploadingMessage(selectedID, 'blast'); + } +} +function shareWithEveryone(selectedID, isGif) { + if (selectedID.id) { + selectedID = selectedID.id; // sometimes (?), `selectedID` is passed as an HTML object to these functions; we just want the ID + } + + var shareWithEveryoneButton = document.getElementById(selectedID + "shareWithEveryoneButton"), + shareBar = document.getElementById(selectedID + "shareBar"), + shareBarHelp = document.getElementById(selectedID + "shareBarHelp"); + shareWithEveryoneButton.onclick = function () { }; + + var storyID = document.getElementById(selectedID).getAttribute("data-story-id"); + + if (storyID) { + EventBridge.emitWebEvent(JSON.stringify({ + type: "snapshot", + action: "shareSnapshotWithEveryone", + story_id: storyID, + isGif: isGif + })); + showConfirmationMessage(selectedID, 'hifi'); + shareWithEveryoneButton.classList.add("disabled"); + shareWithEveryoneButton.style.backgroundColor = "#000000"; + shareWithEveryoneButton.style.opacity = "0.5"; + shareBarHelp.style.backgroundColor = "#000000"; + shareBarHelp.style.opacity = "0.5"; + } else { + showUploadingMessage(selectedID, 'hifi'); + } +} +function shareButtonHovered(destination, selectedID, shouldAlsoModifyOther) { + if (selectedID.id) { + selectedID = selectedID.id; // sometimes (?), `selectedID` is passed as an HTML object to these functions; we just want the ID + } + var shareBarHelp = document.getElementById(selectedID + "shareBarHelp"), + shareButtonsDiv = document.getElementById(selectedID + "shareButtonsDiv").childNodes, + itr; + + if (!shareBarHelp.classList.contains("uploading")) { + for (itr = 0; itr < shareButtonsDiv.length; itr += 1) { + shareButtonsDiv[itr].style.backgroundColor = "rgba(0, 0, 0, 0)"; + } + shareBarHelp.style.opacity = "1.0"; + switch (destination) { + case 'blast': + var blastToConnectionsButton = document.getElementById(selectedID + "blastToConnectionsButton"); + if (!blastToConnectionsButton.classList.contains("disabled")) { + shareBarHelp.style.backgroundColor = "#EA4C5F"; + shareBarHelp.style.opacity = "1.0"; + blastToConnectionsButton.style.backgroundColor = "#EA4C5F"; + blastToConnectionsButton.style.opacity = "1.0"; + shareBarHelp.innerHTML = blastShareText; + } else { + shareBarHelp.style.backgroundColor = "#000000"; + shareBarHelp.style.opacity = "0.5"; + blastToConnectionsButton.style.backgroundColor = "#000000"; + blastToConnectionsButton.style.opacity = "0.5"; + shareBarHelp.innerHTML = blastAlreadySharedText; + } + break; + case 'hifi': + var shareWithEveryoneButton = document.getElementById(selectedID + "shareWithEveryoneButton"); + if (!shareWithEveryoneButton.classList.contains("disabled")) { + shareBarHelp.style.backgroundColor = "#1FC6A6"; + shareBarHelp.style.opacity = "1.0"; + shareWithEveryoneButton.style.backgroundColor = "#1FC6A6"; + shareWithEveryoneButton.style.opacity = "1.0"; + shareBarHelp.innerHTML = hifiShareText; + } else { + shareBarHelp.style.backgroundColor = "#000000"; + shareBarHelp.style.opacity = "0.5"; + shareWithEveryoneButton.style.backgroundColor = "#000000"; + shareWithEveryoneButton.style.opacity = "0.5"; + shareBarHelp.innerHTML = hifiAlreadySharedText; + } + break; + case 'facebook': + shareBarHelp.style.backgroundColor = "#3C58A0"; + shareBarHelp.innerHTML = facebookShareText; + document.getElementById(selectedID + "facebookButton").style.backgroundColor = "#3C58A0"; + break; + case 'twitter': + shareBarHelp.style.backgroundColor = "#00B4EE"; + shareBarHelp.innerHTML = twitterShareText; + document.getElementById(selectedID + "twitterButton").style.backgroundColor = "#00B4EE"; + break; + } + } + + if (shouldAlsoModifyOther && imageCount > 1) { + if (selectedID === "p0" && !document.getElementById("p1").classList.contains("processingGif")) { + shareButtonHovered(destination, "p1", false); + } else if (selectedID === "p1") { + shareButtonHovered(destination, "p0", false); + } + } +} +function shareButtonClicked(destination, selectedID) { + if (selectedID.id) { + selectedID = selectedID.id; // sometimes (?), `selectedID` is passed as an HTML object to these functions; we just want the ID + } + var storyID = document.getElementById(selectedID).getAttribute("data-story-id"); + + if (!storyID) { + showUploadingMessage(selectedID, destination); + } else { + EventBridge.emitWebEvent(JSON.stringify({ + type: "snapshot", + action: "removeFromStoryIDsToMaybeDelete", + story_id: storyID + })); + } +} + +function handleCaptureSetting(setting) { + var stillAndGif = document.getElementById('stillAndGif'), + stillOnly = document.getElementById('stillOnly'); + + stillAndGif.checked = setting; + stillOnly.checked = !setting; + + stillAndGif.onclick = function () { + EventBridge.emitWebEvent(JSON.stringify({ + type: "snapshot", + action: "captureStillAndGif" + })); + }; + + stillOnly.onclick = function () { + EventBridge.emitWebEvent(JSON.stringify({ + type: "snapshot", + action: "captureStillOnly" + })); + }; + +} +window.onload = function () { + // Uncomment the line below to test functionality in a browser. + // See definition of "testInBrowser()" to modify tests. + //testInBrowser(4); + openEventBridge(function () { + // Set up a handler for receiving the data, and tell the .js we are ready to receive it. + EventBridge.scriptEventReceived.connect(function (message) { + + message = JSON.parse(message); + + if (message.type !== "snapshot") { + return; + } + + var messageOptions = message.options; + + switch (message.action) { + case 'showSetupInstructions': + showSetupInstructions(); + break; + case 'snapshotLocationChosen': + clearImages(); + showSetupComplete(); + break; + case 'clearPreviousImages': + clearImages(); + break; + case 'showPreviousImages': + clearImages(); + imageCount = message.image_data.length; + if (imageCount > 0) { + message.image_data.forEach(function (element, idx) { + addImage(element, messageOptions.isLoggedIn, message.canShare, false, true, message.image_data[idx].blastButtonDisabled, message.image_data[idx].hifiButtonDisabled, messageOptions.canBlast); + }); + } else { + showSnapshotInstructions(); + } + break; + case 'addImages': + // The last element of the message contents list contains a bunch of options, + // including whether or not we can share stuff + // The other elements of the list contain image paths. + if (messageOptions.containsGif === true) { + if (messageOptions.processingGif === true) { + imageCount = message.image_data.length + 1; // "+1" for the GIF that'll finish processing soon + message.image_data.push({ localPath: messageOptions.loadingGifPath }); + message.image_data.forEach(function (element, idx) { + addImage(element, messageOptions.isLoggedIn, idx === 0 && messageOptions.canShare, idx === 1, false, false, false, true); + }); + document.getElementById("p1").classList.add("processingGif"); + document.getElementById("snap-button").disabled = true; + } else { + var gifPath = message.image_data[0].localPath, + p1img = document.getElementById('p1img'); + p1img.src = gifPath; + + paths[1] = gifPath; + shareForUrl("p1"); + appendShareBar("p1", messageOptions.isLoggedIn, messageOptions.canShare, true, false, false, messageOptions.canBlast); + document.getElementById("p1").classList.remove("processingGif"); + document.getElementById("snap-button").disabled = false; + } + } else { + imageCount = message.image_data.length; + message.image_data.forEach(function (element) { + addImage(element, messageOptions.isLoggedIn, messageOptions.canShare, false, false, false, false, true); + }); + document.getElementById("snap-button").disabled = false; + } + break; + case 'captureSettings': + handleCaptureSetting(message.setting); + break; + case 'setPrintButtonEnabled': + setPrintButtonEnabled(); + break; + case 'setPrintButtonLoading': + setPrintButtonLoading(); + break; + case 'setPrintButtonDisabled': + setPrintButtonDisabled(); + break; + case 'snapshotUploadComplete': + var isGif = fileExtensionMatches(message.image_url, "gif"); + updateShareInfo(isGif ? "p1" : "p0", message.story_id); + if (isPrintProcessing()) { + setPrintButtonEnabled(); + } + break; + default: + console.log("Unknown message action received in SnapshotReview.js."); + break; + } + }); + + EventBridge.emitWebEvent(JSON.stringify({ + type: "snapshot", + action: "ready" + })); + });; +}; +function snapshotSettings() { + EventBridge.emitWebEvent(JSON.stringify({ + type: "snapshot", + action: "openSettings" + })); +} +function takeSnapshot() { + document.getElementById("snap-button").disabled = true; + EventBridge.emitWebEvent(JSON.stringify({ + type: "snapshot", + action: "takeSnapshot" + })); +} + +function isPrintDisabled() { + var printElement = document.getElementById('print-icon'); + + return printElement.classList.contains("print-icon") && + printElement.classList.contains("print-icon-default") && + document.getElementById('print-button').disabled; +} +function isPrintProcessing() { + var printElement = document.getElementById('print-icon'); + + return printElement.classList.contains("print-icon") && + printElement.classList.contains("print-icon-loading") && + document.getElementById('print-button').disabled; +} +function isPrintEnabled() { + var printElement = document.getElementById('print-icon'); + + return printElement.classList.contains("print-icon") && + printElement.classList.contains("print-icon-default") && + !document.getElementById('print-button').disabled; +} + +function setPrintButtonLoading() { + document.getElementById('print-icon').className = "print-icon print-icon-loading"; + document.getElementById('print-button').disabled = true; +} +function setPrintButtonDisabled() { + document.getElementById('print-icon').className = "print-icon print-icon-default"; + document.getElementById('print-button').disabled = true; +} +function setPrintButtonEnabled() { + document.getElementById('print-button').disabled = false; + document.getElementById('print-icon').className = "print-icon print-icon-default"; +} + +function requestPrintButtonUpdate() { + EventBridge.emitWebEvent(JSON.stringify({ + type: "snapshot", + action: "requestPrintButtonUpdate" + })); +} + +function printToPolaroid() { + if (isPrintEnabled()) { + EventBridge.emitWebEvent(JSON.stringify({ + type: "snapshot", + action: "printToPolaroid" + })); + } else { + setPrintButtonLoading(); + } +} + +function testInBrowser(test) { + if (test === 0) { + showSetupInstructions(); + } else if (test === 1) { + imageCount = 2; + //addImage({ localPath: 'http://lorempixel.com/553/255' }); + addImage({ localPath: 'D:/Dropbox/Screenshots/High Fidelity Snapshots/hifi-snap-by-zfox-on-2017-05-01_13-28-58.jpg', story_id: 1338 }, true, true, false, true, false, false, true); + addImage({ localPath: 'D:/Dropbox/Screenshots/High Fidelity Snapshots/hifi-snap-by-zfox-on-2017-05-01_13-28-58.gif', story_id: 1337 }, true, true, false, true, false, false, true); + } else if (test === 2) { + addImage({ localPath: 'D:/Dropbox/Screenshots/High Fidelity Snapshots/hifi-snap-by-zfox-on-2017-05-01_13-28-58.jpg', story_id: 1338 }, true, true, false, true, false, false, true); + addImage({ localPath: 'D:/Dropbox/Screenshots/High Fidelity Snapshots/hifi-snap-by-zfox-on-2017-05-01_13-28-58.gif', story_id: 1337 }, true, true, false, true, false, false, true); + showConfirmationMessage("p0", 'blast'); + showConfirmationMessage("p1", 'hifi'); + } else if (test === 3) { + imageCount = 2; + //addImage({ localPath: 'http://lorempixel.com/553/255' }); + addImage({ localPath: 'D:/Dropbox/Screenshots/High Fidelity Snapshots/hifi-snap-by-zfox-on-2017-05-01_13-28-58.jpg', story_id: 1338 }, true, true, false, true, false, false, true); + addImage({ localPath: 'D:/Dropbox/Screenshots/High Fidelity Snapshots/hifi-snap-by-zfox-on-2017-05-01_13-28-58.gif', story_id: 1337 }, true, true, false, true, false, false, true); + showUploadingMessage("p0", 'hifi'); + } else if (test === 4) { + imageCount = 2; + //addImage({ localPath: 'http://lorempixel.com/553/255' }); + addImage({ localPath: 'D:/Dropbox/Screenshots/High Fidelity Snapshots/hifi-snap-by-zfox-on-2017-05-01_13-28-58.jpg', story_id: 1338 }, false, true, false, true, false, false, true); + addImage({ localPath: 'D:/Dropbox/Screenshots/High Fidelity Snapshots/hifi-snap-by-zfox-on-2017-05-01_13-28-58.gif', story_id: 1337 }, false, true, false, true, false, false, true); +} +} diff --git a/scripts/simplifiedUI/system/html/js/colpick.js b/scripts/simplifiedUI/system/html/js/colpick.js new file mode 100644 index 0000000000..e4ad65dfb6 --- /dev/null +++ b/scripts/simplifiedUI/system/html/js/colpick.js @@ -0,0 +1,616 @@ +/* +colpick Color Picker +Copyright 2013 Jose Vargas. Licensed under GPL license. Based on Stefan Petre's Color Picker www.eyecon.ro, dual licensed +under the MIT and GPL licenses + +For usage and examples: colpick.com/plugin + */ + +/* global console, document, Element, EventBridge, jQuery, navigator, window, _ $ */ + +(function ($) { + var colpick = function () { + var + tpl = '
' + + '
' + + '
' + + '
' + + '
' + + '
#
' + + '
' + + '
R
' + + '
' + + '
' + + '
G
' + + '
' + + '
B
' + + '
' + + '
' + + '
H
' + + '
' + + '
' + + '
S
' + + '
' + + '
' + + '
B
' + + '
' + + '
' + + '
', + defaults = { + showEvent: 'click', + onShow: function () {}, + onBeforeShow: function(){}, + onHide: function () {}, + onChange: function () {}, + onSubmit: function () {}, + colorScheme: 'light', + color: '3289c7', + livePreview: true, + flat: false, + layout: 'full', + submit: 1, + submitText: 'OK', + height: 156 + }, + // Fill the inputs of the plugin + fillRGBFields = function (hsb, cal) { + var rgb = hsbToRgb(hsb); + $(cal).data('colpick').fields + .eq(1).val(rgb.r).end() + .eq(2).val(rgb.g).end() + .eq(3).val(rgb.b).end(); + }, + fillHSBFields = function (hsb, cal) { + $(cal).data('colpick').fields + .eq(4).val(Math.round(hsb.h)).end() + .eq(5).val(Math.round(hsb.s)).end() + .eq(6).val(Math.round(hsb.b)).end(); + }, + fillHexFields = function (hsb, cal) { + $(cal).data('colpick').fields.eq(0).val(hsbToHex(hsb)); + }, + // Set the round selector position + setSelector = function (hsb, cal) { + $(cal).data('colpick').selector.css('backgroundColor', '#' + hsbToHex({h: hsb.h, s: 100, b: 100})); + $(cal).data('colpick').selectorIndic.css({ + left: parseInt($(cal).data('colpick').height * hsb.s/100, 10), + top: parseInt($(cal).data('colpick').height * (100-hsb.b)/100, 10) + }); + }, + // Set the hue selector position + setHue = function (hsb, cal) { + $(cal).data('colpick').hue.css('top', + parseInt($(cal).data('colpick').height - $(cal).data('colpick').height * hsb.h / 360, 10)); + }, + // Set current and new colors + setCurrentColor = function (hsb, cal) { + $(cal).data('colpick').currentColor.css('backgroundColor', '#' + hsbToHex(hsb)); + }, + setNewColor = function (hsb, cal) { + $(cal).data('colpick').newColor.css('backgroundColor', '#' + hsbToHex(hsb)); + }, + // Called when the new color is changed + change = function (ev) { + var cal = $(this).parent().parent(), col; + if (this.parentNode.className.indexOf('_hex') > 0) { + cal.data('colpick').color = col = hexToHsb(fixHex(this.value)); + fillRGBFields(col, cal.get(0)); + fillHSBFields(col, cal.get(0)); + fillHexFields(col, cal.get(0)); + } else if (this.parentNode.className.indexOf('_hsb') > 0) { + cal.data('colpick').color = col = fixHSB({ + h: parseInt(cal.data('colpick').fields.eq(4).val(), 10), + s: parseInt(cal.data('colpick').fields.eq(5).val(), 10), + b: parseInt(cal.data('colpick').fields.eq(6).val(), 10) + }); + fillRGBFields(col, cal.get(0)); + fillHexFields(col, cal.get(0)); + fillHSBFields(col, cal.get(0)); + } else { + cal.data('colpick').color = col = rgbToHsb(fixRGB({ + r: parseInt(cal.data('colpick').fields.eq(1).val(), 10), + g: parseInt(cal.data('colpick').fields.eq(2).val(), 10), + b: parseInt(cal.data('colpick').fields.eq(3).val(), 10) + })); + fillHexFields(col, cal.get(0)); + fillHSBFields(col, cal.get(0)); + fillRGBFields(col, cal.get(0)); + } + setSelector(col, cal.get(0)); + setHue(col, cal.get(0)); + setNewColor(col, cal.get(0)); + cal.data('colpick').onChange.apply(cal.parent(), + [col, hsbToHex(col), hsbToRgb(col), cal.data('colpick').el, 0]); + }, + // Change style on blur and on focus of inputs + blur = function (ev) { + $(this).parent().removeClass('colpick_focus'); + }, + focus = function () { + $(this).parent().parent().data('colpick').fields.parent().removeClass('colpick_focus'); + $(this).parent().addClass('colpick_focus'); + }, + // Increment/decrement arrows functions + downIncrement = function (ev) { + ev.preventDefault ? ev.preventDefault() : ev.returnValue = false; + var field = $(this).parent().find('input').focus(); + var current = { + el: $(this).parent().addClass('colpick_slider'), + max: this.parentNode.className.indexOf('_hsb_h') > 0 ? 360 : + (this.parentNode.className.indexOf('_hsb') > 0 ? 100 : 255), + y: ev.pageY, + field: field, + val: parseInt(field.val(), 10), + preview: $(this).parent().parent().data('colpick').livePreview + }; + $(document).mouseup(current, upIncrement); + $(document).mousemove(current, moveIncrement); + }, + moveIncrement = function (ev) { + ev.data.field.val(Math.max(0, Math.min(ev.data.max, parseInt(ev.data.val - ev.pageY + ev.data.y, 10)))); + if (ev.data.preview) { + change.apply(ev.data.field.get(0), [true]); + } + return false; + }, + upIncrement = function (ev) { + change.apply(ev.data.field.get(0), [true]); + ev.data.el.removeClass('colpick_slider').find('input').focus(); + $(document).off('mouseup', upIncrement); + $(document).off('mousemove', moveIncrement); + return false; + }, + // Hue slider functions + downHue = function (ev) { + ev.preventDefault ? ev.preventDefault() : ev.returnValue = false; + var current = { + cal: $(this).parent(), + y: $(this).offset().top + }; + $(document).on('mouseup touchend',current,upHue); + $(document).on('mousemove touchmove',current,moveHue); + + var pageY = ((ev.type === 'touchstart') ? ev.originalEvent.changedTouches[0].pageY : ev.pageY ); + change.apply( + current.cal.data('colpick') + .fields.eq(4).val(parseInt(360 * (current.cal.data('colpick').height - + (pageY - current.y)) / current.cal.data('colpick').height, 10)) + .get(0), + [current.cal.data('colpick').livePreview] + ); + return false; + }, + moveHue = function (ev) { + var pageY = ((ev.type === 'touchmove') ? ev.originalEvent.changedTouches[0].pageY : ev.pageY ); + change.apply( + ev.data.cal.data('colpick') + .fields.eq(4).val(parseInt(360 * (ev.data.cal.data('colpick').height - + Math.max(0, Math.min(ev.data.cal.data('colpick').height, (pageY - ev.data.y)))) / + ev.data.cal.data('colpick').height, 10)) + .get(0), + [ev.data.preview] + ); + return false; + }, + upHue = function (ev) { + fillRGBFields(ev.data.cal.data('colpick').color, ev.data.cal.get(0)); + fillHexFields(ev.data.cal.data('colpick').color, ev.data.cal.get(0)); + $(document).off('mouseup touchend',upHue); + $(document).off('mousemove touchmove',moveHue); + return false; + }, + // Color selector functions + downSelector = function (ev) { + ev.preventDefault ? ev.preventDefault() : ev.returnValue = false; + var current = { + cal: $(this).parent(), + pos: $(this).offset() + }; + current.preview = current.cal.data('colpick').livePreview; + + $(document).on('mouseup touchend',current,upSelector); + $(document).on('mousemove touchmove',current,moveSelector); + + var pageX,pageY; + if (ev.type === 'touchstart') { + pageX = ev.originalEvent.changedTouches[0].pageX, + pageY = ev.originalEvent.changedTouches[0].pageY; + } else { + pageX = ev.pageX; + pageY = ev.pageY; + } + + change.apply( + current.cal.data('colpick').fields + .eq(6).val(parseInt(100 * (current.cal.data('colpick').height - (pageY - current.pos.top)) / + current.cal.data('colpick').height, 10)).end() + .eq(5).val(parseInt(100*(pageX - current.pos.left)/current.cal.data('colpick').height, 10)) + .get(0), + [current.preview] + ); + return false; + }, + moveSelector = function (ev) { + var pageX,pageY; + if (ev.type === 'touchmove') { + pageX = ev.originalEvent.changedTouches[0].pageX, + pageY = ev.originalEvent.changedTouches[0].pageY; + } else { + pageX = ev.pageX; + pageY = ev.pageY; + } + + change.apply( + ev.data.cal.data('colpick').fields + .eq(6).val(parseInt(100 * (ev.data.cal.data('colpick').height - + Math.max(0, Math.min(ev.data.cal.data('colpick').height, (pageY - ev.data.pos.top)))) / + ev.data.cal.data('colpick').height, 10)).end() + .eq(5).val(parseInt(100 * (Math.max(0, Math.min(ev.data.cal.data('colpick').height, + (pageX - ev.data.pos.left)))) / ev.data.cal.data('colpick').height, 10)) + .get(0), + [ev.data.preview] + ); + return false; + }, + upSelector = function (ev) { + fillRGBFields(ev.data.cal.data('colpick').color, ev.data.cal.get(0)); + fillHexFields(ev.data.cal.data('colpick').color, ev.data.cal.get(0)); + $(document).off('mouseup touchend',upSelector); + $(document).off('mousemove touchmove',moveSelector); + return false; + }, + // Submit button + clickSubmit = function (ev) { + var cal = $(this).parent(); + var col = cal.data('colpick').color; + cal.data('colpick').origColor = col; + setCurrentColor(col, cal.get(0)); + cal.data('colpick').onSubmit(col, hsbToHex(col), hsbToRgb(col), cal.data('colpick').el); + }, + // Show/hide the color picker + show = function (ev) { + if ($(this).attr('disabled')) { + return; + } + // Prevent the trigger of any direct parent + ev.stopPropagation(); + var cal = $('#' + $(this).data('colpickId')); + cal.data('colpick').onBeforeShow.apply(this, [cal.get(0)]); + var pos = $(this).offset(); + var top = pos.top + this.offsetHeight; + var left = pos.left; + var viewPort = getViewport(); + var calW = cal.width(); + if (left + calW > viewPort.l + viewPort.w) { + left -= calW; + } + cal.css({left: left + 'px', top: top + 'px'}); + if (cal.data('colpick').onShow.apply(this, [cal.get(0)]) !== false) { + cal.show(); + } + // Hide when user clicks outside + $('html').mousedown({cal:cal}, hide); + cal.mousedown(function(ev){ + ev.stopPropagation(); + }); + }, + hide = function (ev) { + if (ev.data.cal.data('colpick').onHide.apply(this, [ev.data.cal.get(0)]) !== false) { + ev.data.cal.hide(); + } + $('html').off('mousedown', hide); + }, + getViewport = function () { + var m = document.compatMode === 'CSS1Compat'; + return { + l : window.pageXOffset || (m ? document.documentElement.scrollLeft : document.body.scrollLeft), + w : window.innerWidth || (m ? document.documentElement.clientWidth : document.body.clientWidth) + }; + }, + // Fix the values if the user enters a negative or high value + fixHSB = function (hsb) { + hsb.h = isNaN(hsb.h) ? 0 : hsb.h; + hsb.s = isNaN(hsb.s) ? 0 : hsb.s; + hsb.b = isNaN(hsb.b) ? 0 : hsb.b; + return { + h: Math.min(360, Math.max(0, hsb.h)), + s: Math.min(100, Math.max(0, hsb.s)), + b: Math.min(100, Math.max(0, hsb.b)) + }; + }, + fixRGB = function (rgb) { + rgb.r = isNaN(rgb.r) ? 0 : rgb.r; + rgb.g = isNaN(rgb.g) ? 0 : rgb.g; + rgb.b = isNaN(rgb.b) ? 0 : rgb.b; + return { + r: Math.min(255, Math.max(0, rgb.r)), + g: Math.min(255, Math.max(0, rgb.g)), + b: Math.min(255, Math.max(0, rgb.b)) + }; + }, + fixHex = function (hex) { + var len = 6 - hex.length; + if (len > 0) { + var o = []; + for (var i=0; i').attr('style', + 'height:8.333333%; filter:progid:' + + 'DXImageTransform.Microsoft.gradient(GradientType=0,startColorstr=' + stops[i] + + ', endColorstr=' + stops[i + 1] + + '); -ms-filter: "progid:DXImageTransform.Microsoft.gradient(GradientType=0,startColorstr=' + + stops[i] + ', endColorstr=' + stops[i + 1] + ')";'); + huebar.append(div); + } + } else { + var stopList = stops.join(','); + huebar.attr('style', 'background:-webkit-linear-gradient(top,' + stopList + + '); background: -o-linear-gradient(top,' + stopList + + '); background: -ms-linear-gradient(top,' + stopList + + '); background:-moz-linear-gradient(top,' + stopList + + '); -webkit-linear-gradient(top,' + stopList + + '); background:linear-gradient(to bottom,' + stopList + '); '); + } + cal.find('div.colpick_hue').on('mousedown touchstart',downHue); + options.newColor = cal.find('div.colpick_new_color'); + options.currentColor = cal.find('div.colpick_current_color'); + // Store options and fill with default color + cal.data('colpick', options); + fillRGBFields(options.color, cal.get(0)); + fillHSBFields(options.color, cal.get(0)); + fillHexFields(options.color, cal.get(0)); + setHue(options.color, cal.get(0)); + setSelector(options.color, cal.get(0)); + setCurrentColor(options.color, cal.get(0)); + setNewColor(options.color, cal.get(0)); + // Append to body if flat=false, else show in place + if (options.flat) { + cal.appendTo(this).show(); + cal.css({ + position: 'relative', + display: 'block' + }); + } else { + cal.appendTo(document.body); + $(this).on(options.showEvent, show); + cal.css({ + position:'absolute' + }); + } + } + }); + }, + // Shows the picker + showPicker: function() { + return this.each( function () { + if ($(this).data('colpickId')) { + show.apply(this); + } + }); + }, + // Hides the picker + hidePicker: function() { + return this.each( function () { + if ($(this).data('colpickId')) { + $('#' + $(this).data('colpickId')).hide(); + } + }); + }, + // Sets a color as new and current (default) + setColor: function(col, setCurrent) { + setCurrent = (typeof setCurrent === "undefined") ? 1 : setCurrent; + if (typeof col === 'string') { + col = hexToHsb(col); + } else if (col.r !== undefined && col.g !== undefined && col.b !== undefined) { + col = rgbToHsb(col); + } else if (col.h !== undefined && col.s !== undefined && col.b !== undefined) { + col = fixHSB(col); + } else { + return this; + } + return this.each(function(){ + if ($(this).data('colpickId')) { + var cal = $('#' + $(this).data('colpickId')); + cal.data('colpick').color = col; + cal.data('colpick').origColor = col; + fillRGBFields(col, cal.get(0)); + fillHSBFields(col, cal.get(0)); + fillHexFields(col, cal.get(0)); + setHue(col, cal.get(0)); + setSelector(col, cal.get(0)); + + setNewColor(col, cal.get(0)); + cal.data('colpick').onChange.apply(cal.parent(), + [col, hsbToHex(col), hsbToRgb(col), cal.data('colpick').el, 1]); + if (setCurrent) { + setCurrentColor(col, cal.get(0)); + } + } + }); + } + }; + }(); + // Color space convertions + var hexToRgb = function (hexString) { + if (typeof hexString !== "string") { + print("Error - ColPick.js::hexToRgb expects string object."); + return; + } + + var hexNumber = parseInt(((hexString.indexOf('#') > -1) ? hexString.substring(1) : hexString), 16); + return { r: hexNumber >> 16, g: (hexNumber & 0x00FF00) >> 8, b: (hexNumber & 0x0000FF)}; + }; + var hexToHsb = function (hexString) { + if (typeof hexString !== "string") { + print("Error - ColPick.js::hexToHsb expects string object."); + return; + } + + return rgbToHsb(hexToRgb(hexString)); + }; + var rgbToHsb = function (rgb) { + var hsb = {h: 0, s: 0, b: 0}; + var min = Math.min(rgb.r, rgb.g, rgb.b); + var max = Math.max(rgb.r, rgb.g, rgb.b); + var delta = max - min; + hsb.b = max; + hsb.s = max != 0 ? 255 * delta / max : 0; // eslint-disable-line eqeqeq + if (hsb.s != 0) { // eslint-disable-line eqeqeq + if (rgb.r == max) { // eslint-disable-line eqeqeq + hsb.h = (rgb.g - rgb.b) / delta; + } else if (rgb.g == max) { // eslint-disable-line eqeqeq + hsb.h = 2 + (rgb.b - rgb.r) / delta; + } else { + hsb.h = 4 + (rgb.r - rgb.g) / delta; + } + } else { + hsb.h = -1; + } + hsb.h *= 60; + if (hsb.h < 0) { + hsb.h += 360; + } + hsb.s *= 100/255; + hsb.b *= 100/255; + return hsb; + }; + var hsbToRgb = function (hsb) { + var rgb = {}; + var h = hsb.h; + var s = hsb.s*255/100; + var v = hsb.b*255/100; + if (s == 0) { // eslint-disable-line eqeqeq + rgb.r = rgb.g = rgb.b = v; + } else { + var t1 = v; + var t2 = (255-s)*v/255; + var t3 = (t1-t2)*(h%60)/60; + if (h==360) { // eslint-disable-line eqeqeq + h = 0; + } + if (h<60) { + rgb.r=t1; rgb.b=t2; rgb.g=t2+t3; + } else if (h<120) { + rgb.g=t1; rgb.b=t2; rgb.r=t1-t3; + } else if (h<180) { + rgb.g=t1; rgb.r=t2; rgb.b=t2+t3; + } else if (h<240) { + rgb.b=t1; rgb.r=t2; rgb.g=t1-t3; + } else if (h<300) { + rgb.b=t1; rgb.g=t2; rgb.r=t2+t3; + } else if (h<360) { + rgb.r=t1; rgb.g=t2; rgb.b=t1-t3; + } else { + rgb.r=0; rgb.g=0; rgb.b=0; + } + } + return {r:Math.round(rgb.r), g:Math.round(rgb.g), b:Math.round(rgb.b)}; + }; + var rgbToHex = function (rgb) { + var hex = [ + rgb.r.toString(16), + rgb.g.toString(16), + rgb.b.toString(16) + ]; + $.each(hex, function (nr, val) { + if (val.length == 1) { // eslint-disable-line eqeqeq + hex[nr] = '0' + val; + } + }); + return hex.join(''); + }; + var hsbToHex = function (hsb) { + return rgbToHex(hsbToRgb(hsb)); + }; + $.fn.extend({ + colpick: colpick.init, + colpickHide: colpick.hidePicker, + colpickShow: colpick.showPicker, + colpickSetColor: colpick.setColor + }); + $.extend({ + colpick:{ + rgbToHex: rgbToHex, + rgbToHsb: rgbToHsb, + hsbToHex: hsbToHex, + hsbToRgb: hsbToRgb, + hexToHsb: hexToHsb, + hexToRgb: hexToRgb + } + }); +})(jQuery); + diff --git a/scripts/simplifiedUI/system/html/js/createAppTooltip.js b/scripts/simplifiedUI/system/html/js/createAppTooltip.js new file mode 100644 index 0000000000..3eb206d8a3 --- /dev/null +++ b/scripts/simplifiedUI/system/html/js/createAppTooltip.js @@ -0,0 +1,116 @@ +// createAppTooltip.js +// +// Created by Thijs Wenker on 17 Oct 2018 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html + +const CREATE_APP_TOOLTIP_OFFSET = 20; +const TOOLTIP_DELAY = 500; // ms +const TOOLTIP_DEBUG = false; + +function CreateAppTooltip() { + this._tooltipData = null; + this._tooltipDiv = null; + this._delayTimeout = null; + this._isEnabled = false; +} + +CreateAppTooltip.prototype = { + _tooltipData: null, + _tooltipDiv: null, + _delayTimeout: null, + _isEnabled: null, + + _removeTooltipIfExists: function() { + if (this._delayTimeout !== null) { + window.clearTimeout(this._delayTimeout); + this._delayTimeout = null; + } + + if (this._tooltipDiv !== null) { + this._tooltipDiv.remove(); + this._tooltipDiv = null; + } + }, + + setIsEnabled: function(isEnabled) { + this._isEnabled = isEnabled; + }, + + setTooltipData: function(tooltipData) { + this._tooltipData = tooltipData; + }, + + registerTooltipElement: function(element, tooltipID, jsPropertyName) { + element.addEventListener("mouseover", function() { + if (!this._isEnabled) { + return; + } + + this._removeTooltipIfExists(); + + this._delayTimeout = window.setTimeout(function() { + let tooltipData = this._tooltipData[tooltipID]; + + if (!tooltipData || tooltipData.tooltip === "") { + if (!TOOLTIP_DEBUG) { + return; + } + tooltipData = { tooltip: 'PLEASE SET THIS TOOLTIP' }; + } + + let elementRect = element.getBoundingClientRect(); + let elTip = document.createElement("div"); + elTip.className = "create-app-tooltip"; + + let elTipDescription = document.createElement("div"); + elTipDescription.className = "create-app-tooltip-description"; + elTipDescription.innerText = tooltipData.tooltip; + elTip.appendChild(elTipDescription); + + let jsAttribute = jsPropertyName; + if (tooltipData.jsPropertyName) { + jsAttribute = tooltipData.jsPropertyName; + } + + if (!tooltipData.skipJSProperty) { + let elTipJSAttribute = document.createElement("div"); + elTipJSAttribute.className = "create-app-tooltip-js-attribute"; + elTipJSAttribute.innerText = `JS Attribute: ${jsAttribute}`; + elTip.appendChild(elTipJSAttribute); + } + + document.body.appendChild(elTip); + + let elementTop = window.pageYOffset + elementRect.top; + + let desiredTooltipTop = elementTop + element.clientHeight + CREATE_APP_TOOLTIP_OFFSET; + let desiredTooltipLeft = window.pageXOffset + elementRect.left; + + if ((window.innerHeight + window.pageYOffset) < (desiredTooltipTop + elTip.clientHeight)) { + // show above when otherwise out of bounds + elTip.style.top = elementTop - CREATE_APP_TOOLTIP_OFFSET - elTip.clientHeight; + } else { + // show tooltip below by default + elTip.style.top = desiredTooltipTop; + } + if ((window.innerWidth + window.pageXOffset) < (desiredTooltipLeft + elTip.clientWidth)) { + elTip.style.left = document.body.clientWidth + window.pageXOffset - elTip.offsetWidth; + } else { + elTip.style.left = desiredTooltipLeft; + } + + this._tooltipDiv = elTip; + }.bind(this), TOOLTIP_DELAY); + }.bind(this), false); + element.addEventListener("mouseout", function() { + if (!this._isEnabled) { + return; + } + + this._removeTooltipIfExists(); + }.bind(this), false); + } +}; diff --git a/scripts/simplifiedUI/system/html/js/draggableNumber.js b/scripts/simplifiedUI/system/html/js/draggableNumber.js new file mode 100644 index 0000000000..3c7b74290c --- /dev/null +++ b/scripts/simplifiedUI/system/html/js/draggableNumber.js @@ -0,0 +1,268 @@ +// draggableNumber.js +// +// Created by David Back on 7 Nov 2018 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html + +const DELTA_X_FOCUS_THRESHOLD = 1; +const ENTER_KEY = 13; + +function DraggableNumber(min, max, step, decimals, dragStart, dragEnd) { + this.min = min; + this.max = max; + this.step = step !== undefined ? step : 1; + this.multiDiffModeEnabled = false; + this.decimals = decimals; + this.dragStartFunction = dragStart; + this.dragEndFunction = dragEnd; + this.dragging = false; + this.initialMouseEvent = null; + this.lastMouseEvent = null; + this.valueChangeFunction = null; + this.multiDiffStepFunction = null; + this.initialize(); +} + +DraggableNumber.prototype = { + showInput: function() { + this.elText.style.visibility = "hidden"; + this.elLeftArrow.style.visibility = "hidden"; + this.elRightArrow.style.visibility = "hidden"; + this.elInput.style.opacity = 1; + }, + + hideInput: function() { + this.elText.style.visibility = "visible"; + this.elLeftArrow.style.visibility = "visible"; + this.elRightArrow.style.visibility = "visible"; + this.elInput.style.opacity = 0; + }, + + mouseDown: function(event) { + if (event.target === this.elText && !this.isDisabled()) { + this.initialMouseEvent = event; + this.lastMouseEvent = event; + document.addEventListener("mousemove", this.onDocumentMouseMove); + document.addEventListener("mouseup", this.onDocumentMouseUp); + } + }, + + mouseUp: function(event) { + if (!this.dragging && event.target === this.elText && this.initialMouseEvent) { + let dx = event.clientX - this.initialMouseEvent.clientX; + if (Math.abs(dx) <= DELTA_X_FOCUS_THRESHOLD) { + this.showInput(); + this.elInput.focus(); + } + this.initialMouseEvent = null; + } + }, + + documentMouseMove: function(event) { + if (!this.dragging && this.initialMouseEvent) { + let dxFromInitial = event.clientX - this.initialMouseEvent.clientX; + if (Math.abs(dxFromInitial) > DELTA_X_FOCUS_THRESHOLD) { + if (this.dragStartFunction) { + this.dragStartFunction(); + } + this.dragging = true; + } + this.lastMouseEvent = event; + } + if (this.dragging && this.lastMouseEvent) { + let dragDelta = event.clientX - this.lastMouseEvent.clientX; + if (dragDelta !== 0) { + if (this.multiDiffModeEnabled) { + if (this.multiDiffStepFunction) { + this.multiDiffStepFunction(dragDelta * this.step); + } + } else { + if (dragDelta > 0) { + this.elInput.stepUp(dragDelta); + } else { + this.elInput.stepDown(-dragDelta); + } + this.inputChange(); + if (this.valueChangeFunction) { + this.valueChangeFunction(); + } + } + } + this.lastMouseEvent = event; + } + }, + + documentMouseUp: function(event) { + if (this.dragging) { + if (this.dragEndFunction) { + this.dragEndFunction(); + } + this.dragging = false; + } + this.lastMouseEvent = null; + document.removeEventListener("mousemove", this.onDocumentMouseMove); + document.removeEventListener("mouseup", this.onDocumentMouseUp); + }, + + stepUp: function() { + if (!this.isDisabled()) { + if (this.multiDiffModeEnabled) { + if (this.multiDiffStepFunction) { + this.multiDiffStepFunction(this.step, true); + } + } else { + this.elInput.value = parseFloat(this.elInput.value) + this.step; + this.inputChange(); + if (this.valueChangeFunction) { + this.valueChangeFunction(); + } + } + } + }, + + stepDown: function() { + if (!this.isDisabled()) { + if (this.multiDiffModeEnabled) { + if (this.multiDiffStepFunction) { + this.multiDiffStepFunction(-this.step, true); + } + } else { + this.elInput.value = parseFloat(this.elInput.value) - this.step; + this.inputChange(); + if (this.valueChangeFunction) { + this.valueChangeFunction(); + } + } + } + }, + + setValue: function(newValue, isMultiDiff) { + if (isMultiDiff !== undefined) { + this.setMultiDiff(isMultiDiff); + } + + if (isNaN(newValue)) { + console.error("DraggableNumber.setValue() > " + newValue + " is not a number."); + return; + } + + if (newValue !== "" && this.decimals !== undefined) { + this.elInput.value = parseFloat(newValue).toFixed(this.decimals); + } else { + this.elInput.value = newValue; + } + this.elText.firstChild.data = this.elInput.value; + }, + + setMultiDiff: function(isMultiDiff) { + this.multiDiffModeEnabled = isMultiDiff; + if (isMultiDiff) { + this.elDiv.classList.add('multi-diff'); + } else { + this.elDiv.classList.remove('multi-diff'); + } + }, + + setValueChangeFunction: function(valueChangeFunction) { + this.valueChangeFunction = valueChangeFunction.bind(this.elInput); + this.elInput.addEventListener("change", this.valueChangeFunction); + }, + + setMultiDiffStepFunction: function (multiDiffStepFunction) { + this.multiDiffStepFunction = multiDiffStepFunction; + }, + + inputChange: function() { + let value = this.elInput.value; + if (this.max !== undefined) { + value = Math.min(this.max, value); + } + if (this.min !== undefined) { + value = Math.max(this.min, value); + } + this.setValue(value); + }, + + inputBlur: function(ev) { + this.hideInput(); + }, + + keyPress: function(event) { + if (event.keyCode === ENTER_KEY) { + if (this.valueChangeFunction) { + this.valueChangeFunction(); + } + this.inputBlur(); + } + }, + + isDisabled: function() { + return this.elText.getAttribute("disabled") === "disabled"; + }, + + updateMinMax: function(min, max) { + this.min = min; + this.max = max; + if (this.min !== undefined) { + this.elInput.setAttribute("min", this.min); + } + if (this.max !== undefined) { + this.elInput.setAttribute("max", this.max); + } + }, + + initialize: function() { + this.onMouseDown = this.mouseDown.bind(this); + this.onMouseUp = this.mouseUp.bind(this); + this.onDocumentMouseMove = this.documentMouseMove.bind(this); + this.onDocumentMouseUp = this.documentMouseUp.bind(this); + this.onStepUp = this.stepUp.bind(this); + this.onStepDown = this.stepDown.bind(this); + this.onInputChange = this.inputChange.bind(this); + this.onInputBlur = this.inputBlur.bind(this); + this.onKeyPress = this.keyPress.bind(this); + + this.elDiv = document.createElement('div'); + this.elDiv.className = "draggable-number"; + + this.elText = document.createElement('span'); + this.elText.className = "text"; + this.elText.innerText = " "; + this.elText.style.visibility = "visible"; + this.elText.addEventListener("mousedown", this.onMouseDown); + this.elText.addEventListener("mouseup", this.onMouseUp); + + this.elLeftArrow = document.createElement('span'); + this.elRightArrow = document.createElement('span'); + this.elLeftArrow.className = 'left-arrow'; + this.elLeftArrow.innerHTML = 'D'; + this.elLeftArrow.addEventListener("click", this.onStepDown); + this.elRightArrow.className = 'right-arrow'; + this.elRightArrow.innerHTML = 'D'; + this.elRightArrow.addEventListener("click", this.onStepUp); + + this.elMultiDiff = document.createElement('span'); + this.elMultiDiff.className = 'multi-diff'; + + this.elInput = document.createElement('input'); + this.elInput.className = "input"; + this.elInput.setAttribute("type", "number"); + this.updateMinMax(this.min, this.max); + if (this.step !== undefined) { + this.elInput.setAttribute("step", this.step); + } + this.elInput.style.opacity = 0; + this.elInput.addEventListener("change", this.onInputChange); + this.elInput.addEventListener("blur", this.onInputBlur); + this.elInput.addEventListener("keypress", this.onKeyPress); + this.elInput.addEventListener("focus", this.showInput.bind(this)); + + this.elDiv.appendChild(this.elLeftArrow); + this.elDiv.appendChild(this.elText); + this.elDiv.appendChild(this.elInput); + this.elDiv.appendChild(this.elMultiDiff); + this.elDiv.appendChild(this.elRightArrow); + } +}; diff --git a/scripts/simplifiedUI/system/html/js/entityList.js b/scripts/simplifiedUI/system/html/js/entityList.js new file mode 100644 index 0000000000..b15c4e6703 --- /dev/null +++ b/scripts/simplifiedUI/system/html/js/entityList.js @@ -0,0 +1,1426 @@ +// entityList.js +// +// Created by Ryan Huffman on 19 Nov 2014 +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html + +const ASCENDING_SORT = 1; +const DESCENDING_SORT = -1; +const ASCENDING_STRING = '▴'; +const DESCENDING_STRING = '▾'; +const BYTES_PER_MEGABYTE = 1024 * 1024; +const COLLAPSE_EXTRA_INFO = "E"; +const EXPAND_EXTRA_INFO = "D"; +const FILTER_IN_VIEW_ATTRIBUTE = "pressed"; +const WINDOW_NONVARIABLE_HEIGHT = 227; +const EMPTY_ENTITY_ID = "0"; +const MAX_LENGTH_RADIUS = 9; +const MINIMUM_COLUMN_WIDTH = 24; +const SCROLLBAR_WIDTH = 20; +const RESIZER_WIDTH = 10; +const DELTA_X_MOVE_COLUMNS_THRESHOLD = 2; +const DELTA_X_COLUMN_SWAP_POSITION = 5; +const CERTIFIED_PLACEHOLDER = "** Certified **"; + +function decimalMegabytes(number) { + return number ? (number / BYTES_PER_MEGABYTE).toFixed(1) : ""; +} + +function displayIfNonZero(number) { + return number ? number : ""; +} + +function getFilename(url) { + let urlParts = url.split('/'); + return urlParts[urlParts.length - 1]; +} + +const COLUMNS = { + type: { + columnHeader: "Type", + propertyID: "type", + initialWidth: 0.16, + initiallyShown: true, + alwaysShown: true, + defaultSortOrder: ASCENDING_SORT, + }, + name: { + columnHeader: "Name", + propertyID: "name", + initialWidth: 0.34, + initiallyShown: true, + alwaysShown: true, + defaultSortOrder: ASCENDING_SORT, + }, + url: { + columnHeader: "File", + dropdownLabel: "File", + propertyID: "url", + initialWidth: 0.34, + initiallyShown: true, + defaultSortOrder: ASCENDING_SORT, + }, + locked: { + columnHeader: "", + glyph: true, + propertyID: "locked", + initialWidth: 0.08, + initiallyShown: true, + alwaysShown: true, + defaultSortOrder: DESCENDING_SORT, + }, + visible: { + columnHeader: "", + glyph: true, + propertyID: "visible", + initialWidth: 0.08, + initiallyShown: true, + alwaysShown: true, + defaultSortOrder: DESCENDING_SORT, + }, + verticesCount: { + columnHeader: "Verts", + dropdownLabel: "Vertices", + propertyID: "verticesCount", + initialWidth: 0.08, + defaultSortOrder: DESCENDING_SORT, + }, + texturesCount: { + columnHeader: "Texts", + dropdownLabel: "Textures", + propertyID: "texturesCount", + initialWidth: 0.08, + defaultSortOrder: DESCENDING_SORT, + }, + texturesSize: { + columnHeader: "Text MB", + dropdownLabel: "Texture Size", + propertyID: "texturesSize", + initialWidth: 0.10, + format: decimalMegabytes, + defaultSortOrder: DESCENDING_SORT, + }, + hasTransparent: { + columnHeader: "", + glyph: true, + dropdownLabel: "Transparency", + propertyID: "hasTransparent", + initialWidth: 0.04, + defaultSortOrder: DESCENDING_SORT, + }, + isBaked: { + columnHeader: "", + glyph: true, + dropdownLabel: "Baked", + propertyID: "isBaked", + initialWidth: 0.08, + defaultSortOrder: DESCENDING_SORT, + }, + drawCalls: { + columnHeader: "Draws", + dropdownLabel: "Draws", + propertyID: "drawCalls", + initialWidth: 0.08, + defaultSortOrder: DESCENDING_SORT, + }, + hasScript: { + columnHeader: "k", + glyph: true, + dropdownLabel: "Script", + propertyID: "hasScript", + initialWidth: 0.06, + defaultSortOrder: DESCENDING_SORT, + }, +}; + +const FILTER_TYPES = [ + "Shape", + "Model", + "Image", + "Light", + "Zone", + "Web", + "Material", + "ParticleEffect", + "PolyLine", + "PolyVox", + "Text", + "Grid", +]; + +const DOUBLE_CLICK_TIMEOUT = 300; // ms +const RENAME_COOLDOWN = 400; // ms + +// List of all entities +let entities = []; +// List of all entities, indexed by Entity ID +let entitiesByID = {}; +// The filtered and sorted list of entities passed to ListView +let visibleEntities = []; +// List of all entities that are currently selected +let selectedEntities = []; + +let entityList = null; // The ListView + +/** + * @type EntityListContextMenu + */ +let entityListContextMenu = null; + +let currentSortColumnID = 'type'; +let currentSortOrder = ASCENDING_SORT; +let elSortOrders = {}; +let typeFilters = []; +let isFilterInView = false; + +let columns = []; +let columnsByID = {}; +let lastResizeEvent = null; +let resizeColumnIndex = 0; +let elTargetTh = null; +let elTargetSpan = null; +let targetColumnIndex = 0; +let lastColumnSwapPosition = -1; +let initialThEvent = null; +let renameTimeout = null; +let renameLastBlur = null; +let renameLastEntityID = null; +let isRenameFieldBeingMoved = false; +let elFilterTypeInputs = {}; + +let elEntityTable, + elEntityTableHeader, + elEntityTableBody, + elEntityTableScroll, + elEntityTableHeaderRow, + elRefresh, + elToggleLocked, + elToggleVisible, + elDelete, + elFilterTypeMultiselectBox, + elFilterTypeText, + elFilterTypeOptions, + elFilterTypeOptionsButtons, + elFilterTypeSelectAll, + elFilterTypeClearAll, + elFilterSearch, + elFilterInView, + elFilterRadius, + elExport, + elPal, + elSelectedEntitiesCount, + elVisibleEntitiesCount, + elNoEntitiesMessage, + elColumnsMultiselectBox, + elColumnsOptions, + elToggleSpaceMode, + elRenameInput; + +const ENABLE_PROFILING = false; +let profileIndent = ''; +const PROFILE_NOOP = function(_name, fn, args) { + fn.apply(this, args); +} ; +const PROFILE = !ENABLE_PROFILING ? PROFILE_NOOP : function(name, fn, args) { + console.log("PROFILE-Web " + profileIndent + "(" + name + ") Begin"); + let previousIndent = profileIndent; + profileIndent += ' '; + let before = Date.now(); + fn.apply(this, args); + let delta = Date.now() - before; + profileIndent = previousIndent; + console.log("PROFILE-Web " + profileIndent + "(" + name + ") End " + delta + "ms"); +}; + +function loaded() { + openEventBridge(function() { + elEntityTable = document.getElementById("entity-table"); + elEntityTableHeader = document.getElementById("entity-table-header"); + elEntityTableBody = document.getElementById("entity-table-body"); + elEntityTableScroll = document.getElementById("entity-table-scroll"); + elRefresh = document.getElementById("refresh"); + elToggleLocked = document.getElementById("locked"); + elToggleVisible = document.getElementById("visible"); + elDelete = document.getElementById("delete"); + elFilterTypeMultiselectBox = document.getElementById("filter-type-multiselect-box"); + elFilterTypeText = document.getElementById("filter-type-text"); + elFilterTypeOptions = document.getElementById("filter-type-options"); + elFilterTypeOptionsButtons = document.getElementById("filter-type-options-buttons"); + elFilterTypeSelectAll = document.getElementById('filter-type-select-all'); + elFilterTypeClearAll = document.getElementById('filter-type-clear-all'); + elFilterSearch = document.getElementById("filter-search"); + elFilterInView = document.getElementById("filter-in-view"); + elFilterRadius = document.getElementById("filter-radius"); + elExport = document.getElementById("export"); + elPal = document.getElementById("pal"); + elSelectedEntitiesCount = document.getElementById("selected-entities-count"); + elVisibleEntitiesCount = document.getElementById("visible-entities-count"); + elNoEntitiesMessage = document.getElementById("no-entities"); + elColumnsMultiselectBox = document.getElementById("entity-table-columns-multiselect-box"); + elColumnsOptions = document.getElementById("entity-table-columns-options"); + elToggleSpaceMode = document.getElementById('toggle-space-mode'); + + document.body.onclick = onBodyClick; + elToggleLocked.onclick = function() { + EventBridge.emitWebEvent(JSON.stringify({ type: 'toggleLocked' })); + }; + elToggleVisible.onclick = function() { + EventBridge.emitWebEvent(JSON.stringify({ type: 'toggleVisible' })); + }; + elExport.onclick = function() { + EventBridge.emitWebEvent(JSON.stringify({ type: 'export'})); + }; + elPal.onclick = function() { + EventBridge.emitWebEvent(JSON.stringify({ type: 'pal' })); + }; + elDelete.onclick = function() { + EventBridge.emitWebEvent(JSON.stringify({ type: 'delete' })); + }; + elToggleSpaceMode.onclick = function() { + EventBridge.emitWebEvent(JSON.stringify({ type: 'toggleSpaceMode' })); + }; + elRefresh.onclick = refreshEntities; + elFilterTypeMultiselectBox.onclick = onToggleTypeDropdown; + elFilterTypeSelectAll.onclick = onSelectAllTypes; + elFilterTypeClearAll.onclick = onClearAllTypes; + elFilterSearch.onkeyup = refreshEntityList; + elFilterSearch.onsearch = refreshEntityList; + elFilterInView.onclick = onToggleFilterInView; + elFilterRadius.onkeyup = onRadiusChange; + elFilterRadius.onchange = onRadiusChange; + elColumnsMultiselectBox.onclick = onToggleColumnsDropdown; + + // create filter type dropdown checkboxes with label and icon for each type + for (let i = 0; i < FILTER_TYPES.length; ++i) { + let type = FILTER_TYPES[i]; + let typeFilterID = "filter-type-" + type; + + let elDiv = document.createElement('div'); + elDiv.onclick = onToggleTypeFilter; + elFilterTypeOptions.insertBefore(elDiv, elFilterTypeOptionsButtons); + + let elInput = document.createElement('input'); + elInput.setAttribute("type", "checkbox"); + elInput.setAttribute("id", typeFilterID); + elInput.setAttribute("filterType", type); + elInput.checked = true; // all types are checked initially + elFilterTypeInputs[type] = elInput; + elDiv.appendChild(elInput); + + let elLabel = document.createElement('label'); + elLabel.setAttribute("for", typeFilterID); + elLabel.innerText = type; + elDiv.appendChild(elLabel); + + let elSpan = document.createElement('span'); + elSpan.setAttribute("class", "typeIcon"); + elSpan.innerHTML = ENTITY_TYPE_ICON[type]; + + elLabel.insertBefore(elSpan, elLabel.childNodes[0]); + + toggleTypeFilter(elInput, false); // add all types to the initial types filter + } + + // create columns + elHeaderTr = document.createElement("tr"); + elEntityTableHeader.appendChild(elHeaderTr); + let columnIndex = 0; + for (let columnID in COLUMNS) { + let columnData = COLUMNS[columnID]; + + let elTh = document.createElement("th"); + let thID = "entity-" + columnID; + elTh.setAttribute("id", thID); + elTh.setAttribute("columnIndex", columnIndex); + elTh.setAttribute("columnID", columnID); + if (columnData.glyph) { + let elGlyph = document.createElement("span"); + elGlyph.className = "glyph"; + elGlyph.innerHTML = columnData.columnHeader; + elTh.appendChild(elGlyph); + } else { + elTh.innerText = columnData.columnHeader; + } + elTh.onmousedown = function(event) { + if (event.target.nodeName === 'TH') { + elTargetTh = event.target; + targetColumnIndex = parseInt(elTargetTh.getAttribute("columnIndex")); + lastColumnSwapPosition = event.clientX; + } else if (event.target.nodeName === 'SPAN') { + elTargetSpan = event.target; + } + initialThEvent = event; + }; + + let elResizer = document.createElement("span"); + elResizer.className = "resizer"; + elResizer.innerHTML = " "; + elResizer.onmousedown = onStartResize; + elTh.appendChild(elResizer); + + let elSortOrder = document.createElement("span"); + elSortOrder.className = "sort-order"; + elTh.appendChild(elSortOrder); + elHeaderTr.appendChild(elTh); + + elSortOrders[columnID] = elSortOrder; + + // add column to columns dropdown if it is not set to be always shown + if (columnData.alwaysShown !== true) { + let columnDropdownID = "entity-table-column-" + columnID; + + let elDiv = document.createElement('div'); + elDiv.onclick = onToggleColumn; + elColumnsOptions.appendChild(elDiv); + + let elInput = document.createElement('input'); + elInput.setAttribute("type", "checkbox"); + elInput.setAttribute("id", columnDropdownID); + elInput.setAttribute("columnID", columnID); + elInput.checked = columnData.initiallyShown === true; + elDiv.appendChild(elInput); + + let elLabel = document.createElement('label'); + elLabel.setAttribute("for", columnDropdownID); + elLabel.innerText = columnData.dropdownLabel; + elDiv.appendChild(elLabel); + } + + let initialWidth = columnData.initiallyShown === true ? columnData.initialWidth : 0; + columns.push({ + columnID: columnID, + elTh: elTh, + elResizer: elResizer, + width: initialWidth, + data: columnData + }); + columnsByID[columnID] = columns[columnIndex]; + + ++columnIndex; + } + + elEntityTableHeaderRow = document.querySelectorAll("#entity-table thead th"); + + entityList = new ListView(elEntityTableBody, elEntityTableScroll, elEntityTableHeaderRow, createRow, updateRow, + clearRow, preRefresh, postRefresh, preRefresh, WINDOW_NONVARIABLE_HEIGHT); + + entityListContextMenu = new EntityListContextMenu(); + + function startRenamingEntity(entityID) { + renameLastEntityID = entityID; + let entity = entitiesByID[entityID]; + if (!entity || entity.locked || !entity.elRow) { + return; + } + + let elCell = entity.elRow.childNodes[getColumnIndex("name")]; + elRenameInput = document.createElement("input"); + elRenameInput.setAttribute('class', 'rename-entity'); + elRenameInput.value = entity.name; + let ignoreClicks = function(event) { + event.stopPropagation(); + }; + elRenameInput.onclick = ignoreClicks; + elRenameInput.ondblclick = ignoreClicks; + elRenameInput.onkeyup = function(keyEvent) { + if (keyEvent.key === "Enter") { + elRenameInput.blur(); + } + }; + + elRenameInput.onblur = function(event) { + if (isRenameFieldBeingMoved) { + return; + } + let value = elRenameInput.value; + EventBridge.emitWebEvent(JSON.stringify({ + type: 'rename', + entityID: entityID, + name: value + })); + entity.name = value; + elRenameInput.parentElement.innerText = value; + + renameLastBlur = Date.now(); + elRenameInput = null; + }; + + elCell.innerHTML = ""; + elCell.appendChild(elRenameInput); + + elRenameInput.select(); + } + + function preRefresh() { + // move the rename input to the body + if (!isRenameFieldBeingMoved && elRenameInput) { + isRenameFieldBeingMoved = true; + document.body.appendChild(elRenameInput); + // keep the focus + elRenameInput.focus(); + } + } + + function postRefresh() { + if (!elRenameInput || !isRenameFieldBeingMoved) { + return; + } + let entity = entitiesByID[renameLastEntityID]; + if (!entity || entity.locked || !entity.elRow) { + return; + } + let elCell = entity.elRow.childNodes[getColumnIndex("name")]; + elCell.innerHTML = ""; + elCell.appendChild(elRenameInput); + // keep the focus + elRenameInput.focus(); + isRenameFieldBeingMoved = false; + } + + entityListContextMenu.setOnSelectedCallback(function(optionName, selectedEntityID) { + switch (optionName) { + case "Cut": + EventBridge.emitWebEvent(JSON.stringify({ type: 'cut' })); + break; + case "Copy": + EventBridge.emitWebEvent(JSON.stringify({ type: 'copy' })); + break; + case "Paste": + EventBridge.emitWebEvent(JSON.stringify({ type: 'paste' })); + break; + case "Rename": + startRenamingEntity(selectedEntityID); + break; + case "Duplicate": + EventBridge.emitWebEvent(JSON.stringify({ type: 'duplicate' })); + break; + case "Delete": + EventBridge.emitWebEvent(JSON.stringify({ type: 'delete' })); + break; + } + }); + + function onRowContextMenu(clickEvent) { + if (elRenameInput) { + // disallow the context menu from popping up while renaming + return; + } + + let entityID = this.dataset.entityID; + + if (!selectedEntities.includes(entityID)) { + let selection = [entityID]; + updateSelectedEntities(selection); + + EventBridge.emitWebEvent(JSON.stringify({ + type: "selectionUpdate", + focus: false, + entityIds: selection, + })); + } + + let enabledContextMenuItems = ['Copy', 'Paste', 'Duplicate']; + if (entitiesByID[entityID] && !entitiesByID[entityID].locked) { + enabledContextMenuItems.push('Cut'); + enabledContextMenuItems.push('Rename'); + enabledContextMenuItems.push('Delete'); + } + + entityListContextMenu.open(clickEvent, entityID, enabledContextMenuItems); + } + + let clearRenameTimeout = () => { + if (renameTimeout !== null) { + window.clearTimeout(renameTimeout); + renameTimeout = null; + } + }; + + function onRowClicked(clickEvent) { + let entityID = this.dataset.entityID; + let selection = [entityID]; + + if (clickEvent.ctrlKey) { + let selectedIndex = selectedEntities.indexOf(entityID); + if (selectedIndex >= 0) { + selection = []; + selection = selection.concat(selectedEntities); + selection.splice(selectedIndex, 1); + } else { + selection = selection.concat(selectedEntities); + } + } else if (clickEvent.shiftKey && selectedEntities.length > 0) { + let previousItemFound = -1; + let clickedItemFound = -1; + for (let i = 0, len = visibleEntities.length; i < len; ++i) { + let entity = visibleEntities[i]; + if (clickedItemFound === -1 && entityID === entity.id) { + clickedItemFound = i; + } else if (previousItemFound === -1 && selectedEntities[0] === entity.id) { + previousItemFound = i; + } + } + if (previousItemFound !== -1 && clickedItemFound !== -1) { + selection = []; + let toItem = Math.max(previousItemFound, clickedItemFound); + for (let i = Math.min(previousItemFound, clickedItemFound); i <= toItem; i++) { + selection.push(visibleEntities[i].id); + } + if (previousItemFound > clickedItemFound) { + // always make sure that we add the items in the right order + selection.reverse(); + } + } + } else if (!clickEvent.ctrlKey && !clickEvent.shiftKey && selectedEntities.length === 1) { + // if reselecting the same entity then start renaming it + if (selectedEntities[0] === entityID) { + if (renameLastBlur && renameLastEntityID === entityID && (Date.now() - renameLastBlur) < RENAME_COOLDOWN) { + + return; + } + clearRenameTimeout(); + renameTimeout = window.setTimeout(() => { + renameTimeout = null; + startRenamingEntity(entityID); + }, DOUBLE_CLICK_TIMEOUT); + } + } + + updateSelectedEntities(selection, false); + + EventBridge.emitWebEvent(JSON.stringify({ + type: "selectionUpdate", + focus: false, + entityIds: selection, + })); + } + + function onRowDoubleClicked() { + clearRenameTimeout(); + + let selection = [this.dataset.entityID]; + updateSelectedEntities(selection, false); + + EventBridge.emitWebEvent(JSON.stringify({ + type: "selectionUpdate", + focus: true, + entityIds: selection, + })); + } + + function updateEntityData(entityData) { + entities = []; + entitiesByID = {}; + visibleEntities.length = 0; // maintains itemData reference in ListView + + PROFILE("map-data", function() { + entityData.forEach(function(entity) { + let type = entity.type; + let filename = getFilename(entity.url); + + let entityData = { + id: entity.id, + name: entity.name, + type: type, + url: entity.certificateID === "" ? filename : "" + CERTIFIED_PLACEHOLDER + "", + fullUrl: entity.certificateID === "" ? filename : CERTIFIED_PLACEHOLDER, + locked: entity.locked, + visible: entity.visible, + certificateID: entity.certificateID, + verticesCount: displayIfNonZero(entity.verticesCount), + texturesCount: displayIfNonZero(entity.texturesCount), + texturesSize: entity.texturesSize, + hasTransparent: entity.hasTransparent, + isBaked: entity.isBaked, + drawCalls: displayIfNonZero(entity.drawCalls), + hasScript: entity.hasScript, + elRow: null, // if this entity has a visible row element assigned to it + selected: false // if this entity is selected for edit regardless of having a visible row + }; + + entities.push(entityData); + entitiesByID[entityData.id] = entityData; + }); + }); + + refreshEntityList(); + } + + const isNullOrEmpty = function(value) { + return value === undefined || value === null || value === ""; + }; + + function refreshEntityList() { + PROFILE("refresh-entity-list", function() { + PROFILE("filter", function() { + let searchTerm = elFilterSearch.value.toLowerCase(); + visibleEntities = entities.filter(function(e) { + let type = e.type === "Box" || e.type === "Sphere" ? "Shape" : e.type; + let typeFilter = typeFilters.indexOf(type) > -1; + let searchFilter = searchTerm === '' || (e.name.toLowerCase().indexOf(searchTerm) > -1 || + e.type.toLowerCase().indexOf(searchTerm) > -1 || + e.fullUrl.toLowerCase().indexOf(searchTerm) > -1 || + e.id.toLowerCase().indexOf(searchTerm) > -1); + return typeFilter && searchFilter; + }); + }); + + PROFILE("sort", function() { + let isAscendingSort = currentSortOrder === ASCENDING_SORT; + let isDefaultSort = currentSortOrder === COLUMNS[currentSortColumnID].defaultSortOrder; + visibleEntities.sort((entityA, entityB) => { + /** + * If the default sort is ascending, empty should be considered largest. + * If the default sort is descending, empty should be considered smallest. + */ + if (!isAscendingSort) { + [entityA, entityB] = [entityB, entityA]; + } + let valueA = entityA[currentSortColumnID]; + let valueB = entityB[currentSortColumnID]; + + if (valueA === valueB) { + return entityA.id < entityB.id ? -1 : 1; + } + + if (isNullOrEmpty(valueA)) { + return (isDefaultSort ? 1 : -1) * (isAscendingSort ? 1 : -1); + } + if (isNullOrEmpty(valueB)) { + return (isDefaultSort ? -1 : 1) * (isAscendingSort ? 1 : -1); + } + if (typeof(valueA) === "string") { + return valueA.localeCompare(valueB); + } + return valueA < valueB ? -1 : 1; + }); + }); + + PROFILE("update-dom", function() { + entityList.itemData = visibleEntities; + entityList.refresh(); + updateColumnWidths(); + }); + + refreshFooter(); + refreshNoEntitiesMessage(); + }); + } + + function removeEntities(deletedIDs) { + // Loop from the back so we can pop items off while iterating + + // delete any entities matching deletedIDs list from entities and entitiesByID lists + // if the entity had an associated row element then ensure row is unselected and clear it's entity + for (let j = entities.length - 1; j >= 0; --j) { + let id = entities[j].id; + for (let i = 0, length = deletedIDs.length; i < length; ++i) { + if (id === deletedIDs[i]) { + let elRow = entities[j].elRow; + if (elRow) { + elRow.className = ''; + elRow.dataset.entityID = EMPTY_ENTITY_ID; + } + entities.splice(j, 1); + delete entitiesByID[id]; + break; + } + } + } + + // delete any entities matching deletedIDs list from selectedEntities list + for (let j = selectedEntities.length - 1; j >= 0; --j) { + let id = selectedEntities[j].id; + for (let i = 0, length = deletedIDs.length; i < length; ++i) { + if (id === deletedIDs[i]) { + selectedEntities.splice(j, 1); + break; + } + } + } + + // delete any entities matching deletedIDs list from visibleEntities list + // if this was a row that was above our current row offset (a hidden top row in the top buffer), + // then decrease row offset accordingly + let firstVisibleRow = entityList.getFirstVisibleRowIndex(); + for (let j = visibleEntities.length - 1; j >= 0; --j) { + let id = visibleEntities[j].id; + for (let i = 0, length = deletedIDs.length; i < length; ++i) { + if (id === deletedIDs[i]) { + if (j < firstVisibleRow && entityList.rowOffset > 0) { + entityList.rowOffset--; + } + visibleEntities.splice(j, 1); + break; + } + } + } + + entityList.refresh(); + + refreshFooter(); + refreshNoEntitiesMessage(); + } + + function clearEntities() { + // clear the associated entity ID from all visible row elements + let firstVisibleRow = entityList.getFirstVisibleRowIndex(); + let lastVisibleRow = entityList.getLastVisibleRowIndex(); + for (let i = firstVisibleRow; i <= lastVisibleRow && i < visibleEntities.length; i++) { + let entity = visibleEntities[i]; + entity.elRow.dataset.entityID = EMPTY_ENTITY_ID; + } + + entities = []; + entitiesByID = {}; + visibleEntities.length = 0; // maintains itemData reference in ListView + + entityList.resetToTop(); + entityList.clear(); + + refreshFooter(); + refreshNoEntitiesMessage(); + } + + function setSortColumn(columnID) { + PROFILE("set-sort-column", function() { + if (currentSortColumnID === columnID) { + currentSortOrder *= -1; + } else { + elSortOrders[currentSortColumnID].innerHTML = ""; + currentSortColumnID = columnID; + currentSortOrder = COLUMNS[currentSortColumnID].defaultSortOrder; + } + refreshSortOrder(); + refreshEntityList(); + }); + } + + function refreshSortOrder() { + elSortOrders[currentSortColumnID].innerHTML = currentSortOrder === ASCENDING_SORT ? ASCENDING_STRING : DESCENDING_STRING; + } + + function refreshEntities() { + EventBridge.emitWebEvent(JSON.stringify({ type: 'refresh' })); + } + + function refreshFooter() { + elSelectedEntitiesCount.innerText = selectedEntities.length; + elVisibleEntitiesCount.innerText = visibleEntities.length; + } + + function refreshNoEntitiesMessage() { + if (visibleEntities.length > 0) { + elNoEntitiesMessage.style.display = "none"; + } else { + elNoEntitiesMessage.style.display = "block"; + } + } + + function updateSelectedEntities(selectedIDs, autoScroll) { + let notFound = false; + + // reset all currently selected entities and their rows first + selectedEntities.forEach(function(id) { + let entity = entitiesByID[id]; + if (entity !== undefined) { + entity.selected = false; + if (entity.elRow) { + entity.elRow.className = ''; + } + } + }); + + // then reset selected entities list with newly selected entities and set them selected + selectedEntities = []; + selectedIDs.forEach(function(id) { + selectedEntities.push(id); + let entity = entitiesByID[id]; + if (entity !== undefined) { + entity.selected = true; + if (entity.elRow) { + entity.elRow.className = 'selected'; + } + } else { + notFound = true; + } + }); + + if (autoScroll && selectedIDs.length > 0) { + let firstItem = Number.MAX_VALUE; + let lastItem = -1; + let itemFound = false; + visibleEntities.forEach(function(entity, index) { + if (selectedIDs.indexOf(entity.id) !== -1) { + if (firstItem > index) { + firstItem = index; + } + if (lastItem < index) { + lastItem = index; + } + itemFound = true; + } + }); + if (itemFound) { + entityList.scrollToRow(firstItem, lastItem); + } + } + + elToggleSpaceMode.disabled = selectedIDs.length > 1; + + refreshFooter(); + + return notFound; + } + + function createRow() { + let elRow = document.createElement("tr"); + columns.forEach(function(column) { + let elRowColumn = document.createElement("td"); + elRowColumn.className = createColumnClassName(column.columnID); + elRow.appendChild(elRowColumn); + }); + elRow.oncontextmenu = onRowContextMenu; + elRow.onclick = onRowClicked; + elRow.ondblclick = onRowDoubleClicked; + return elRow; + } + + function updateRow(elRow, itemData) { + // update all column texts and glyphs to this entity's data + for (let i = 0; i < columns.length; ++i) { + let column = columns[i]; + let elCell = elRow.childNodes[i]; + if (column.data.glyph) { + elCell.innerHTML = itemData[column.data.propertyID] ? column.data.columnHeader : null; + } else { + let value = itemData[column.data.propertyID]; + if (column.data.format) { + value = column.data.format(value); + } + elCell.innerHTML = value; + } + elCell.style = "min-width:" + column.widthPx + "px;" + "max-width:" + column.widthPx + "px;"; + elCell.className = createColumnClassName(column.columnID); + } + + // if this entity was previously selected flag it's row as selected + if (itemData.selected) { + elRow.className = 'selected'; + } else { + elRow.className = ''; + } + + // if this row previously had an associated entity ID that wasn't the new entity ID then clear + // the ID from the row and the row element from the previous entity's data, then set the new + // entity ID to the row and the row element to the new entity's data + let prevEntityID = elRow.dataset.entityID; + let newEntityID = itemData.id; + let validPrevItemID = prevEntityID !== undefined && prevEntityID !== EMPTY_ENTITY_ID; + if (validPrevItemID && prevEntityID !== newEntityID && entitiesByID[prevEntityID].elRow === elRow) { + elRow.dataset.entityID = EMPTY_ENTITY_ID; + entitiesByID[prevEntityID].elRow = null; + } + if (!validPrevItemID || prevEntityID !== newEntityID) { + elRow.dataset.entityID = newEntityID; + entitiesByID[newEntityID].elRow = elRow; + } + } + + function clearRow(elRow) { + // reset all texts and glyphs for each of the row's columns + for (let i = 0; i < columns.length; ++i) { + let cell = elRow.childNodes[i]; + if (columns[i].data.glyph) { + cell.innerHTML = ""; + } else { + cell.innerText = ""; + } + } + + // clear the row from any associated entity + let entityID = elRow.dataset.entityID; + if (entityID && entitiesByID[entityID]) { + entitiesByID[entityID].elRow = null; + } + + // reset the row to hidden and clear the entity from the row + elRow.className = ''; + elRow.dataset.entityID = EMPTY_ENTITY_ID; + } + + function onToggleFilterInView() { + isFilterInView = !isFilterInView; + if (isFilterInView) { + elFilterInView.setAttribute(FILTER_IN_VIEW_ATTRIBUTE, FILTER_IN_VIEW_ATTRIBUTE); + } else { + elFilterInView.removeAttribute(FILTER_IN_VIEW_ATTRIBUTE); + } + EventBridge.emitWebEvent(JSON.stringify({ type: "filterInView", filterInView: isFilterInView })); + refreshEntities(); + } + + function onRadiusChange() { + elFilterRadius.value = elFilterRadius.value.replace(/[^0-9]/g, ''); + elFilterRadius.value = Math.max(elFilterRadius.value, 0); + EventBridge.emitWebEvent(JSON.stringify({ type: 'radius', radius: elFilterRadius.value })); + refreshEntities(); + } + + function getColumnIndex(columnID) { + for (let i = 0; i < columns.length; ++i) { + if (columns[i].columnID === columnID) { + return i; + } + } + return -1; + } + + function createColumnClassName(columnID) { + let column = columnsByID[columnID]; + let visible = column.elTh.style.visibility !== "hidden"; + let className = column.data.glyph ? "glyph" : ""; + className += visible ? "" : " hidden"; + return className; + } + + function isColumnsDropdownVisible() { + return elColumnsOptions.style.display === "block"; + } + + function toggleColumnsDropdown() { + elColumnsOptions.style.display = isColumnsDropdownVisible() ? "none" : "block"; + } + + function onToggleColumnsDropdown(event) { + toggleColumnsDropdown(); + if (isTypeDropdownVisible()) { + toggleTypeDropdown(); + } + event.stopPropagation(); + } + + function toggleColumn(elInput, refresh) { + let columnID = elInput.getAttribute("columnID"); + let columnChecked = elInput.checked; + + if (columnChecked) { + let widthNeeded = columnsByID[columnID].data.initialWidth; + + let numberVisibleColumns = 0; + for (let i = 0; i < columns.length; ++i) { + let column = columns[i]; + if (column.columnID === columnID) { + column.width = widthNeeded; + } else if (column.width > 0) { + ++numberVisibleColumns; + } + } + + for (let i = 0; i < columns.length; ++i) { + let column = columns[i]; + if (column.columnID !== columnID && column.width > 0) { + column.width -= column.width * widthNeeded; + } + } + } else { + let widthLoss = 0; + + let numberVisibleColumns = 0; + for (let i = 0; i < columns.length; ++i) { + let column = columns[i]; + if (column.columnID === columnID) { + widthLoss = column.width; + column.width = 0; + } else if (column.width > 0) { + ++numberVisibleColumns; + } + } + + for (let i = 0; i < columns.length; ++i) { + let column = columns[i]; + if (column.columnID !== columnID && column.width > 0) { + let newTotalWidth = (1 - widthLoss); + column.width += (column.width / newTotalWidth) * widthLoss; + } + } + } + + updateColumnWidths(); + } + + function onToggleColumn(event) { + let elTarget = event.target; + if (elTarget instanceof HTMLInputElement) { + toggleColumn(elTarget, true); + } + event.stopPropagation(); + } + + function isTypeDropdownVisible() { + return elFilterTypeOptions.style.display === "block"; + } + + function toggleTypeDropdown() { + elFilterTypeOptions.style.display = isTypeDropdownVisible() ? "none" : "block"; + } + + function onToggleTypeDropdown(event) { + toggleTypeDropdown(); + if (isColumnsDropdownVisible()) { + toggleColumnsDropdown(); + } + event.stopPropagation(); + } + + function refreshTypeFilter(refreshList) { + if (typeFilters.length === 0) { + elFilterTypeText.innerText = "No Types"; + } else if (typeFilters.length === FILTER_TYPES.length) { + elFilterTypeText.innerText = "All Types"; + } else { + elFilterTypeText.innerText = "Types..."; + } + + if (refreshList) { + refreshEntityList(); + } + } + + function toggleTypeFilter(elInput, refreshList) { + let type = elInput.getAttribute("filterType"); + let typeChecked = elInput.checked; + + let typeFilterIndex = typeFilters.indexOf(type); + if (!typeChecked && typeFilterIndex > -1) { + typeFilters.splice(typeFilterIndex, 1); + } else if (typeChecked && typeFilterIndex === -1) { + typeFilters.push(type); + } + + refreshTypeFilter(refreshList); + } + + function onToggleTypeFilter(event) { + let elTarget = event.target; + if (elTarget instanceof HTMLInputElement) { + toggleTypeFilter(elTarget, true); + } + event.stopPropagation(); + } + + function onSelectAllTypes(event) { + for (let type in elFilterTypeInputs) { + elFilterTypeInputs[type].checked = true; + } + typeFilters = FILTER_TYPES; + refreshTypeFilter(true); + event.stopPropagation(); + } + + function onClearAllTypes(event) { + for (let type in elFilterTypeInputs) { + elFilterTypeInputs[type].checked = false; + } + typeFilters = []; + refreshTypeFilter(true); + event.stopPropagation(); + } + + function onBodyClick(event) { + // if clicking anywhere outside of the multiselect dropdowns (since click event bubbled up to onBodyClick and + // propagation wasn't stopped in the toggle type/column callbacks) and the dropdown is open then close it + if (isTypeDropdownVisible()) { + toggleTypeDropdown(); + } + if (isColumnsDropdownVisible()) { + toggleColumnsDropdown(); + } + } + + function onStartResize(event) { + lastResizeEvent = event; + resizeColumnIndex = parseInt(this.parentNode.getAttribute("columnIndex")); + event.stopPropagation(); + } + + function updateColumnWidths() { + let fullWidth = elEntityTableBody.offsetWidth; + let remainingWidth = fullWidth; + let scrollbarVisible = elEntityTableScroll.scrollHeight > elEntityTableScroll.clientHeight; + let resizerRight = scrollbarVisible ? SCROLLBAR_WIDTH - RESIZER_WIDTH/2 : -RESIZER_WIDTH/2; + let visibleColumns = 0; + + for (let i = columns.length - 1; i > 0; --i) { + let column = columns[i]; + column.widthPx = Math.ceil(column.width * fullWidth); + column.elTh.style = "min-width:" + column.widthPx + "px;" + "max-width:" + column.widthPx + "px;"; + let columnVisible = column.width > 0; + column.elTh.style.visibility = columnVisible ? "visible" : "hidden"; + if (column.elResizer) { + column.elResizer.style = "right:" + resizerRight + "px;"; + column.elResizer.style.visibility = columnVisible && visibleColumns > 0 ? "visible" : "hidden"; + } + resizerRight += column.widthPx; + remainingWidth -= column.widthPx; + if (columnVisible) { + ++visibleColumns; + } + } + + // assign all remaining space to the first column + let column = columns[0]; + column.widthPx = remainingWidth; + column.width = remainingWidth / fullWidth; + column.elTh.style = "min-width:" + column.widthPx + "px;" + "max-width:" + column.widthPx + "px;"; + let columnVisible = column.width > 0; + column.elTh.style.visibility = columnVisible ? "visible" : "hidden"; + if (column.elResizer) { + column.elResizer.style = "right:" + resizerRight + "px;"; + column.elResizer.style.visibility = columnVisible && visibleColumns > 0 ? "visible" : "hidden"; + } + + entityList.refresh(); + } + + function swapColumns(columnAIndex, columnBIndex) { + let columnA = columns[columnAIndex]; + let columnB = columns[columnBIndex]; + let columnATh = columns[columnAIndex].elTh; + let columnBTh = columns[columnBIndex].elTh; + let columnThParent = columnATh.parentNode; + columnThParent.removeChild(columnBTh); + columnThParent.insertBefore(columnBTh, columnATh); + columnATh.setAttribute("columnIndex", columnBIndex); + columnBTh.setAttribute("columnIndex", columnAIndex); + columnA.elResizer.setAttribute("columnIndex", columnBIndex); + columnB.elResizer.setAttribute("columnIndex", columnAIndex); + + for (let i = 0; i < visibleEntities.length; ++i) { + let elRow = visibleEntities[i].elRow; + if (elRow) { + let columnACell = elRow.childNodes[columnAIndex]; + let columnBCell = elRow.childNodes[columnBIndex]; + elRow.removeChild(columnBCell); + elRow.insertBefore(columnBCell, columnACell); + } + } + + columns[columnAIndex] = columnB; + columns[columnBIndex] = columnA; + + updateColumnWidths(); + } + + document.onmousemove = function(event) { + if (lastResizeEvent) { + startTh = null; + + let column = columns[resizeColumnIndex]; + + let nextColumnIndex = resizeColumnIndex + 1; + let nextColumn = columns[nextColumnIndex]; + while (nextColumn.width === 0) { + nextColumn = columns[++nextColumnIndex]; + } + + let fullWidth = elEntityTableBody.offsetWidth; + let dx = event.clientX - lastResizeEvent.clientX; + let dPct = dx / fullWidth; + + let newColWidth = column.width + dPct; + let newNextColWidth = nextColumn.width - dPct; + + if (newColWidth * fullWidth >= MINIMUM_COLUMN_WIDTH && newNextColWidth * fullWidth >= MINIMUM_COLUMN_WIDTH) { + column.width += dPct; + nextColumn.width -= dPct; + updateColumnWidths(); + lastResizeEvent = event; + } + } else if (elTargetTh) { + let dxFromInitial = event.clientX - initialThEvent.clientX; + if (Math.abs(dxFromInitial) >= DELTA_X_MOVE_COLUMNS_THRESHOLD) { + elTargetTh.className = "dragging"; + } + if (targetColumnIndex < columns.length - 1) { + let nextColumnIndex = targetColumnIndex + 1; + let nextColumnTh = columns[nextColumnIndex].elTh; + let nextColumnStartX = nextColumnTh.getBoundingClientRect().left; + if (event.clientX >= nextColumnStartX && event.clientX - lastColumnSwapPosition >= DELTA_X_COLUMN_SWAP_POSITION) { + swapColumns(targetColumnIndex, nextColumnIndex); + targetColumnIndex = nextColumnIndex; + lastColumnSwapPosition = event.clientX; + } + } + if (targetColumnIndex >= 1) { + let prevColumnIndex = targetColumnIndex - 1; + let prevColumnTh = columns[prevColumnIndex].elTh; + let prevColumnEndX = prevColumnTh.getBoundingClientRect().right; + if (event.clientX <= prevColumnEndX && lastColumnSwapPosition - event.clientX >= DELTA_X_COLUMN_SWAP_POSITION) { + swapColumns(prevColumnIndex, targetColumnIndex); + targetColumnIndex = prevColumnIndex; + lastColumnSwapPosition = event.clientX; + } + } + } else if (elTargetSpan) { + let dxFromInitial = event.clientX - initialThEvent.clientX; + if (Math.abs(dxFromInitial) >= DELTA_X_MOVE_COLUMNS_THRESHOLD) { + elTargetTh = elTargetSpan.parentNode; + elTargetTh.className = "dragging"; + targetColumnIndex = parseInt(elTargetTh.getAttribute("columnIndex")); + lastColumnSwapPosition = event.clientX; + elTargetSpan = null; + } + } + }; + + document.onmouseup = function(event) { + if (elTargetTh) { + if (elTargetTh.className !== "dragging" && elTargetTh === event.target) { + let columnID = elTargetTh.getAttribute("columnID"); + setSortColumn(columnID); + } + elTargetTh.className = ""; + } else if (elTargetSpan) { + let columnID = elTargetSpan.parentNode.getAttribute("columnID"); + setSortColumn(columnID); + } + lastResizeEvent = null; + elTargetTh = null; + elTargetSpan = null; + initialThEvent = null; + }; + + function setSpaceMode(spaceMode) { + if (spaceMode === "local") { + elToggleSpaceMode.className = "space-mode-local hifi-edit-button"; + elToggleSpaceMode.innerText = "Local"; + } else { + elToggleSpaceMode.className = "space-mode-world hifi-edit-button"; + elToggleSpaceMode.innerText = "World"; + } + } + + const KEY_CODES = { + BACKSPACE: 8, + DELETE: 46 + }; + + document.addEventListener("keyup", function (keyUpEvent) { + const FILTERED_NODE_NAMES = ["INPUT", "TEXTAREA"]; + if (FILTERED_NODE_NAMES.includes(keyUpEvent.target.nodeName)) { + return; + } + + let {code, key, keyCode, altKey, ctrlKey, metaKey, shiftKey} = keyUpEvent; + + let controlKey = window.navigator.platform.startsWith("Mac") ? metaKey : ctrlKey; + + let keyCodeString; + switch (keyCode) { + case KEY_CODES.DELETE: + keyCodeString = "Delete"; + break; + case KEY_CODES.BACKSPACE: + keyCodeString = "Backspace"; + break; + default: + keyCodeString = String.fromCharCode(keyUpEvent.keyCode); + break; + } + + if (controlKey && keyCodeString === "A") { + let visibleEntityIDs = visibleEntities.map(visibleEntity => visibleEntity.id); + let selectionIncludesAllVisibleEntityIDs = visibleEntityIDs.every(visibleEntityID => { + return selectedEntities.includes(visibleEntityID); + }); + + let selection = []; + + if (!selectionIncludesAllVisibleEntityIDs) { + selection = visibleEntityIDs; + } + + updateSelectedEntities(selection); + + EventBridge.emitWebEvent(JSON.stringify({ + type: "selectionUpdate", + focus: false, + entityIds: selection, + })); + + return; + } + + + EventBridge.emitWebEvent(JSON.stringify({ + type: 'keyUpEvent', + keyUpEvent: { + code, + key, + keyCode, + keyCodeString, + altKey, + controlKey, + shiftKey, + } + })); + }, false); + + if (window.EventBridge !== undefined) { + EventBridge.scriptEventReceived.connect(function(data) { + data = JSON.parse(data); + if (data.type === "clearEntityList") { + clearEntities(); + } else if (data.type === "selectionUpdate") { + let notFound = updateSelectedEntities(data.selectedIDs, true); + if (notFound) { + refreshEntities(); + } + } else if (data.type === "update" && data.selectedIDs !== undefined) { + PROFILE("update", function() { + let newEntities = data.entities; + if (newEntities) { + if (newEntities.length === 0) { + clearEntities(); + } else { + updateEntityData(newEntities); + updateSelectedEntities(data.selectedIDs, true); + } + } + setSpaceMode(data.spaceMode); + }); + } else if (data.type === "removeEntities" && data.deletedIDs !== undefined && data.selectedIDs !== undefined) { + removeEntities(data.deletedIDs); + updateSelectedEntities(data.selectedIDs, true); + } else if (data.type === "deleted" && data.ids) { + removeEntities(data.ids); + } else if (data.type === "setSpaceMode") { + setSpaceMode(data.spaceMode); + } + }); + } + + refreshSortOrder(); + refreshEntities(); + + window.addEventListener("resize", updateColumnWidths); + }); + + augmentSpinButtons(); + disableDragDrop(); + + document.addEventListener("contextmenu", function(event) { + entityListContextMenu.close(); + + // Disable default right-click context menu which is not visible in the HMD and makes it seem like the app has locked + event.preventDefault(); + }, false); + + // close context menu when switching focus to another window + $(window).blur(function() { + entityListContextMenu.close(); + }); +} diff --git a/scripts/simplifiedUI/system/html/js/entityListContextMenu.js b/scripts/simplifiedUI/system/html/js/entityListContextMenu.js new file mode 100644 index 0000000000..d71719f252 --- /dev/null +++ b/scripts/simplifiedUI/system/html/js/entityListContextMenu.js @@ -0,0 +1,163 @@ +// +// entityListContextMenu.js +// +// exampleContextMenus.js was originally created by David Rowe on 22 Aug 2018. +// Modified to entityListContextMenu.js by Thijs Wenker on 10 Oct 2018 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +/* eslint-env browser */ +const CONTEXT_MENU_CLASS = "context-menu"; + +/** + * ContextMenu class for EntityList + * @constructor + */ +function EntityListContextMenu() { + this._elContextMenu = null; + this._onSelectedCallback = null; + this._listItems = []; + this._initialize(); +} + +EntityListContextMenu.prototype = { + + /** + * @private + */ + _elContextMenu: null, + + /** + * @private + */ + _onSelectedCallback: null, + + /** + * @private + */ + _selectedEntityID: null, + + /** + * @private + */ + _listItems: null, + + /** + * Close the context menu + */ + close: function() { + if (this.isContextMenuOpen()) { + this._elContextMenu.style.display = "none"; + } + }, + + isContextMenuOpen: function() { + return this._elContextMenu.style.display === "block"; + }, + + /** + * Open the context menu + * @param clickEvent + * @param selectedEntityID + * @param enabledOptions + */ + open: function(clickEvent, selectedEntityID, enabledOptions) { + this._selectedEntityID = selectedEntityID; + + this._listItems.forEach(function(listItem) { + let enabled = enabledOptions.includes(listItem.label); + listItem.enabled = enabled; + listItem.element.setAttribute('class', enabled ? '' : 'disabled'); + }); + + this._elContextMenu.style.display = "block"; + this._elContextMenu.style.left + = Math.min(clickEvent.pageX, document.body.offsetWidth - this._elContextMenu.offsetWidth).toString() + "px"; + this._elContextMenu.style.top + = Math.min(clickEvent.pageY, document.body.clientHeight - this._elContextMenu.offsetHeight).toString() + "px"; + clickEvent.stopPropagation(); + }, + + /** + * Set the callback for when a menu item is selected + * @param onSelectedCallback + */ + setOnSelectedCallback: function(onSelectedCallback) { + this._onSelectedCallback = onSelectedCallback; + }, + + /** + * Add a labeled item to the context menu + * @param itemLabel + * @private + */ + _addListItem: function(itemLabel) { + let elListItem = document.createElement("li"); + elListItem.innerText = itemLabel; + + let listItem = { + label: itemLabel, + element: elListItem, + enabled: false + }; + + elListItem.addEventListener("click", function () { + if (listItem.enabled && this._onSelectedCallback) { + this._onSelectedCallback.call(this, itemLabel, this._selectedEntityID); + } + }.bind(this), false); + + elListItem.setAttribute('class', 'disabled'); + + this._listItems.push(listItem); + this._elContextMenu.appendChild(elListItem); + }, + + /** + * Add a separator item to the context menu + * @private + */ + _addListSeparator: function() { + let elListItem = document.createElement("li"); + elListItem.setAttribute('class', 'separator'); + this._elContextMenu.appendChild(elListItem); + }, + + /** + * Initialize the context menu. + * @private + */ + _initialize: function() { + this._elContextMenu = document.createElement("ul"); + this._elContextMenu.setAttribute("class", CONTEXT_MENU_CLASS); + document.body.appendChild(this._elContextMenu); + + this._addListItem("Cut"); + this._addListItem("Copy"); + this._addListItem("Paste"); + this._addListSeparator(); + this._addListItem("Rename"); + this._addListItem("Duplicate"); + this._addListItem("Delete"); + + // Ignore clicks on context menu background or separator. + this._elContextMenu.addEventListener("click", function(event) { + // Sink clicks on context menu background or separator but let context menu item clicks through. + if (event.target.classList.contains(CONTEXT_MENU_CLASS)) { + event.stopPropagation(); + } + }); + + // Provide means to close context menu without clicking menu item. + document.body.addEventListener("click", this.close.bind(this)); + document.body.addEventListener("keydown", function(event) { + // Close context menu with Esc key. + if (this.isContextMenuOpen() && event.key === "Escape") { + this.close(); + } + }.bind(this)); + } +}; diff --git a/scripts/simplifiedUI/system/html/js/entityProperties.js b/scripts/simplifiedUI/system/html/js/entityProperties.js new file mode 100644 index 0000000000..e64543d41f --- /dev/null +++ b/scripts/simplifiedUI/system/html/js/entityProperties.js @@ -0,0 +1,4419 @@ +// entityProperties.js +// +// Created by Ryan Huffman on 13 Nov 2014 +// Modified by David Back on 19 Oct 2018 +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html + +/* global alert, augmentSpinButtons, clearTimeout, console, document, Element, + EventBridge, JSONEditor, openEventBridge, setTimeout, window, _, $ */ + +const DEGREES_TO_RADIANS = Math.PI / 180.0; + +const NO_SELECTION = ","; + +const PROPERTY_SPACE_MODE = Object.freeze({ + ALL: 0, + LOCAL: 1, + WORLD: 2 +}); + +const PROPERTY_SELECTION_VISIBILITY = Object.freeze({ + SINGLE_SELECTION: 1, + MULTIPLE_SELECTIONS: 2, + MULTI_DIFF_SELECTIONS: 4, + ANY_SELECTIONS: 7, /* SINGLE_SELECTION | MULTIPLE_SELECTIONS | MULTI_DIFF_SELECTIONS */ +}); + +// Multiple-selection behavior +const PROPERTY_MULTI_DISPLAY_MODE = Object.freeze({ + DEFAULT: 0, + /** + * Comma separated values + * Limited for properties with type "string" or "textarea" and readOnly enabled + */ + COMMA_SEPARATED_VALUES: 1, +}); + +const GROUPS = [ + { + id: "base", + properties: [ + { + label: NO_SELECTION, + type: "icon", + icons: ENTITY_TYPE_ICON, + propertyID: "type", + replaceID: "placeholder-property-type", + }, + { + label: "Name", + type: "string", + propertyID: "name", + placeholder: "Name", + replaceID: "placeholder-property-name", + }, + { + label: "ID", + type: "string", + propertyID: "id", + placeholder: "ID", + readOnly: true, + replaceID: "placeholder-property-id", + multiDisplayMode: PROPERTY_MULTI_DISPLAY_MODE.COMMA_SEPARATED_VALUES, + }, + { + label: "Description", + type: "string", + propertyID: "description", + }, + { + label: "Parent", + type: "string", + propertyID: "parentID", + onChange: parentIDChanged, + }, + { + label: "Parent Joint Index", + type: "number", + propertyID: "parentJointIndex", + }, + { + label: "", + glyph: "", + type: "bool", + propertyID: "locked", + replaceID: "placeholder-property-locked", + }, + { + label: "", + glyph: "", + type: "bool", + propertyID: "visible", + replaceID: "placeholder-property-visible", + }, + { + label: "Render Layer", + type: "dropdown", + options: { + world: "World", + front: "Front", + hud: "HUD" + }, + propertyID: "renderLayer", + }, + { + label: "Primitive Mode", + type: "dropdown", + options: { + solid: "Solid", + lines: "Wireframe", + }, + propertyID: "primitiveMode", + }, + ] + }, + { + id: "shape", + addToGroup: "base", + properties: [ + { + label: "Shape", + type: "dropdown", + options: { Cube: "Box", Sphere: "Sphere", Tetrahedron: "Tetrahedron", Octahedron: "Octahedron", + Icosahedron: "Icosahedron", Dodecahedron: "Dodecahedron", Hexagon: "Hexagon", + Triangle: "Triangle", Octagon: "Octagon", Cylinder: "Cylinder", Cone: "Cone", + Circle: "Circle", Quad: "Quad" }, + propertyID: "shape", + }, + { + label: "Color", + type: "color", + propertyID: "color", + }, + ] + }, + { + id: "text", + addToGroup: "base", + properties: [ + { + label: "Text", + type: "string", + propertyID: "text", + }, + { + label: "Text Color", + type: "color", + propertyID: "textColor", + }, + { + label: "Text Alpha", + type: "number-draggable", + min: 0, + max: 1, + step: 0.01, + decimals: 2, + propertyID: "textAlpha", + }, + { + label: "Background Color", + type: "color", + propertyID: "backgroundColor", + }, + { + label: "Background Alpha", + type: "number-draggable", + min: 0, + max: 1, + step: 0.01, + decimals: 2, + propertyID: "backgroundAlpha", + }, + { + label: "Line Height", + type: "number-draggable", + min: 0, + step: 0.001, + decimals: 4, + unit: "m", + propertyID: "lineHeight", + }, + { + label: "Billboard Mode", + type: "dropdown", + options: { none: "None", yaw: "Yaw", full: "Full"}, + propertyID: "textBillboardMode", + propertyName: "billboardMode", // actual entity property name + }, + { + label: "Top Margin", + type: "number-draggable", + step: 0.01, + decimals: 2, + propertyID: "topMargin", + }, + { + label: "Right Margin", + type: "number-draggable", + step: 0.01, + decimals: 2, + propertyID: "rightMargin", + }, + { + label: "Bottom Margin", + type: "number-draggable", + step: 0.01, + decimals: 2, + propertyID: "bottomMargin", + }, + { + label: "Left Margin", + type: "number-draggable", + step: 0.01, + decimals: 2, + propertyID: "leftMargin", + }, + ] + }, + { + id: "zone", + addToGroup: "base", + properties: [ + { + label: "Shape Type", + type: "dropdown", + options: { "box": "Box", "sphere": "Sphere", "ellipsoid": "Ellipsoid", + "cylinder-y": "Cylinder", "compound": "Use Compound Shape URL" }, + propertyID: "zoneShapeType", + propertyName: "shapeType", // actual entity property name + }, + { + label: "Compound Shape URL", + type: "string", + propertyID: "zoneCompoundShapeURL", + propertyName: "compoundShapeURL", // actual entity property name + }, + { + label: "Flying Allowed", + type: "bool", + propertyID: "flyingAllowed", + }, + { + label: "Ghosting Allowed", + type: "bool", + propertyID: "ghostingAllowed", + }, + { + label: "Filter", + type: "string", + propertyID: "filterURL", + }, + { + label: "Key Light", + type: "dropdown", + options: { inherit: "Inherit", disabled: "Off", enabled: "On" }, + propertyID: "keyLightMode", + + }, + { + label: "Key Light Color", + type: "color", + propertyID: "keyLight.color", + showPropertyRule: { "keyLightMode": "enabled" }, + }, + { + label: "Light Intensity", + type: "number-draggable", + min: 0, + max: 40, + step: 0.01, + decimals: 2, + propertyID: "keyLight.intensity", + showPropertyRule: { "keyLightMode": "enabled" }, + }, + { + label: "Light Horizontal Angle", + type: "number-draggable", + step: 0.1, + multiplier: DEGREES_TO_RADIANS, + decimals: 2, + unit: "deg", + propertyID: "keyLight.direction.y", + showPropertyRule: { "keyLightMode": "enabled" }, + }, + { + label: "Light Vertical Angle", + type: "number-draggable", + step: 0.1, + multiplier: DEGREES_TO_RADIANS, + decimals: 2, + unit: "deg", + propertyID: "keyLight.direction.x", + showPropertyRule: { "keyLightMode": "enabled" }, + }, + { + label: "Cast Shadows", + type: "bool", + propertyID: "keyLight.castShadows", + showPropertyRule: { "keyLightMode": "enabled" }, + }, + { + label: "Skybox", + type: "dropdown", + options: { inherit: "Inherit", disabled: "Off", enabled: "On" }, + propertyID: "skyboxMode", + }, + { + label: "Skybox Color", + type: "color", + propertyID: "skybox.color", + showPropertyRule: { "skyboxMode": "enabled" }, + }, + { + label: "Skybox Source", + type: "string", + propertyID: "skybox.url", + showPropertyRule: { "skyboxMode": "enabled" }, + }, + { + label: "Ambient Light", + type: "dropdown", + options: { inherit: "Inherit", disabled: "Off", enabled: "On" }, + propertyID: "ambientLightMode", + }, + { + label: "Ambient Intensity", + type: "number-draggable", + min: 0, + max: 200, + step: 0.1, + decimals: 2, + propertyID: "ambientLight.ambientIntensity", + showPropertyRule: { "ambientLightMode": "enabled" }, + }, + { + label: "Ambient Source", + type: "string", + propertyID: "ambientLight.ambientURL", + showPropertyRule: { "ambientLightMode": "enabled" }, + }, + { + type: "buttons", + buttons: [ { id: "copy", label: "Copy from Skybox", + className: "black", onClick: copySkyboxURLToAmbientURL } ], + propertyID: "copyURLToAmbient", + showPropertyRule: { "ambientLightMode": "enabled" }, + }, + { + label: "Haze", + type: "dropdown", + options: { inherit: "Inherit", disabled: "Off", enabled: "On" }, + propertyID: "hazeMode", + }, + { + label: "Range", + type: "number-draggable", + min: 1, + max: 10000, + step: 1, + decimals: 0, + unit: "m", + propertyID: "haze.hazeRange", + showPropertyRule: { "hazeMode": "enabled" }, + }, + { + label: "Use Altitude", + type: "bool", + propertyID: "haze.hazeAltitudeEffect", + showPropertyRule: { "hazeMode": "enabled" }, + }, + { + label: "Base", + type: "number-draggable", + min: -1000, + max: 1000, + step: 1, + decimals: 0, + unit: "m", + propertyID: "haze.hazeBaseRef", + showPropertyRule: { "hazeMode": "enabled" }, + }, + { + label: "Ceiling", + type: "number-draggable", + min: -1000, + max: 5000, + step: 1, + decimals: 0, + unit: "m", + propertyID: "haze.hazeCeiling", + showPropertyRule: { "hazeMode": "enabled" }, + }, + { + label: "Haze Color", + type: "color", + propertyID: "haze.hazeColor", + showPropertyRule: { "hazeMode": "enabled" }, + }, + { + label: "Background Blend", + type: "number-draggable", + min: 0, + max: 1, + step: 0.001, + decimals: 3, + propertyID: "haze.hazeBackgroundBlend", + showPropertyRule: { "hazeMode": "enabled" }, + }, + { + label: "Enable Glare", + type: "bool", + propertyID: "haze.hazeEnableGlare", + showPropertyRule: { "hazeMode": "enabled" }, + }, + { + label: "Glare Color", + type: "color", + propertyID: "haze.hazeGlareColor", + showPropertyRule: { "hazeMode": "enabled" }, + }, + { + label: "Glare Angle", + type: "number-draggable", + min: 0, + max: 180, + step: 1, + decimals: 0, + propertyID: "haze.hazeGlareAngle", + showPropertyRule: { "hazeMode": "enabled" }, + }, + { + label: "Bloom", + type: "dropdown", + options: { inherit: "Inherit", disabled: "Off", enabled: "On" }, + propertyID: "bloomMode", + }, + { + label: "Bloom Intensity", + type: "number-draggable", + min: 0, + max: 1, + step: 0.001, + decimals: 3, + propertyID: "bloom.bloomIntensity", + showPropertyRule: { "bloomMode": "enabled" }, + }, + { + label: "Bloom Threshold", + type: "number-draggable", + min: 0, + max: 1, + step: 0.001, + decimals: 3, + propertyID: "bloom.bloomThreshold", + showPropertyRule: { "bloomMode": "enabled" }, + }, + { + label: "Bloom Size", + type: "number-draggable", + min: 0, + max: 2, + step: 0.001, + decimals: 3, + propertyID: "bloom.bloomSize", + showPropertyRule: { "bloomMode": "enabled" }, + }, + { + label: "Avatar Priority", + type: "dropdown", + options: { inherit: "Inherit", crowd: "Crowd", hero: "Hero" }, + propertyID: "avatarPriority", + }, + + ] + }, + { + id: "model", + addToGroup: "base", + properties: [ + { + label: "Model", + type: "string", + placeholder: "URL", + propertyID: "modelURL", + hideIfCertified: true, + }, + { + label: "Collision Shape", + type: "dropdown", + options: { "none": "No Collision", "box": "Box", "sphere": "Sphere", "compound": "Compound" , + "simple-hull": "Basic - Whole model", "simple-compound": "Good - Sub-meshes" , + "static-mesh": "Exact - All polygons (non-dynamic only)" }, + propertyID: "shapeType", + }, + { + label: "Compound Shape", + type: "string", + propertyID: "compoundShapeURL", + hideIfCertified: true, + }, + { + label: "Animation", + type: "string", + propertyID: "animation.url", + hideIfCertified: true, + }, + { + label: "Play Automatically", + type: "bool", + propertyID: "animation.running", + }, + { + label: "Loop", + type: "bool", + propertyID: "animation.loop", + }, + { + label: "Allow Transition", + type: "bool", + propertyID: "animation.allowTranslation", + }, + { + label: "Hold", + type: "bool", + propertyID: "animation.hold", + }, + { + label: "Animation Frame", + type: "number-draggable", + propertyID: "animation.currentFrame", + }, + { + label: "First Frame", + type: "number-draggable", + propertyID: "animation.firstFrame", + }, + { + label: "Last Frame", + type: "number-draggable", + propertyID: "animation.lastFrame", + }, + { + label: "Animation FPS", + type: "number-draggable", + propertyID: "animation.fps", + }, + { + label: "Texture", + type: "textarea", + propertyID: "textures", + }, + { + label: "Original Texture", + type: "textarea", + propertyID: "originalTextures", + readOnly: true, + hideIfCertified: true, + }, + { + label: "Group Culled", + type: "bool", + propertyID: "groupCulled", + }, + ] + }, + { + id: "image", + addToGroup: "base", + properties: [ + { + label: "Image", + type: "string", + placeholder: "URL", + propertyID: "imageURL", + }, + { + label: "Color", + type: "color", + propertyID: "imageColor", + propertyName: "color", // actual entity property name + }, + { + label: "Emissive", + type: "bool", + propertyID: "emissive", + }, + { + label: "Sub Image", + type: "rect", + min: 0, + step: 1, + subLabels: [ "x", "y", "w", "h" ], + propertyID: "subImage", + }, + { + label: "Billboard Mode", + type: "dropdown", + options: { none: "None", yaw: "Yaw", full: "Full"}, + propertyID: "imageBillboardMode", + propertyName: "billboardMode", // actual entity property name + }, + { + label: "Keep Aspect Ratio", + type: "bool", + propertyID: "keepAspectRatio", + }, + ] + }, + { + id: "web", + addToGroup: "base", + properties: [ + { + label: "Source", + type: "string", + propertyID: "sourceUrl", + }, + { + label: "Source Resolution", + type: "number-draggable", + propertyID: "dpi", + }, + { + label: "Web Color", + type: "color", + propertyID: "webColor", + propertyName: "color", // actual entity property name + }, + { + label: "Web Alpha", + type: "number-draggable", + step: 0.001, + decimals: 3, + propertyID: "webAlpha", + propertyName: "alpha", + min: 0, + max: 1, + }, + { + label: "Max FPS", + type: "number-draggable", + step: 1, + decimals: 0, + propertyID: "maxFPS", + }, + { + label: "Script URL", + type: "string", + propertyID: "scriptURL", + placeholder: "URL", + }, + ] + }, + { + id: "light", + addToGroup: "base", + properties: [ + { + label: "Light Color", + type: "color", + propertyID: "lightColor", + propertyName: "color", // actual entity property name + }, + { + label: "Intensity", + type: "number-draggable", + min: 0, + max: 10000, + step: 0.1, + decimals: 2, + propertyID: "intensity", + }, + { + label: "Fall-Off Radius", + type: "number-draggable", + min: 0, + max: 10000, + step: 0.1, + decimals: 2, + unit: "m", + propertyID: "falloffRadius", + }, + { + label: "Spotlight", + type: "bool", + propertyID: "isSpotlight", + }, + { + label: "Spotlight Exponent", + type: "number-draggable", + min: 0, + step: 0.01, + decimals: 2, + propertyID: "exponent", + }, + { + label: "Spotlight Cut-Off", + type: "number-draggable", + step: 0.01, + decimals: 2, + propertyID: "cutoff", + }, + ] + }, + { + id: "material", + addToGroup: "base", + properties: [ + { + label: "Material URL", + type: "string", + propertyID: "materialURL", + }, + { + label: "Material Data", + type: "textarea", + buttons: [ { id: "clear", label: "Clear Material Data", className: "red", onClick: clearMaterialData }, + { id: "edit", label: "Edit as JSON", className: "blue", onClick: newJSONMaterialEditor }, + { id: "save", label: "Save Material Data", className: "black", onClick: saveMaterialData } ], + propertyID: "materialData", + }, + { + label: "Material Target", + type: "dynamic-multiselect", + propertyUpdate: materialTargetPropertyUpdate, + propertyID: "parentMaterialName", + selectionVisibility: PROPERTY_SELECTION_VISIBILITY.SINGLE_SELECTION, + }, + { + label: "Priority", + type: "number-draggable", + min: 0, + propertyID: "priority", + }, + { + label: "Material Mapping Mode", + type: "dropdown", + options: { + uv: "UV space", projected: "3D projected" + }, + propertyID: "materialMappingMode", + }, + { + label: "Material Position", + type: "vec2", + vec2Type: "xyz", + min: 0, + max: 1, + step: 0.1, + decimals: 4, + subLabels: [ "x", "y" ], + propertyID: "materialMappingPos", + }, + { + label: "Material Scale", + type: "vec2", + vec2Type: "xyz", + min: 0, + step: 0.1, + decimals: 4, + subLabels: [ "x", "y" ], + propertyID: "materialMappingScale", + }, + { + label: "Material Rotation", + type: "number-draggable", + step: 0.1, + decimals: 2, + unit: "deg", + propertyID: "materialMappingRot", + }, + { + label: "Material Repeat", + type: "bool", + propertyID: "materialRepeat", + }, + ] + }, + { + id: "grid", + addToGroup: "base", + properties: [ + { + label: "Color", + type: "color", + propertyID: "gridColor", + propertyName: "color", // actual entity property name + }, + { + label: "Follow Camera", + type: "bool", + propertyID: "followCamera", + }, + { + label: "Major Grid Every", + type: "number-draggable", + min: 0, + step: 1, + decimals: 0, + propertyID: "majorGridEvery", + }, + { + label: "Minor Grid Every", + type: "number-draggable", + min: 0, + step: 0.01, + decimals: 2, + propertyID: "minorGridEvery", + }, + ] + }, + { + id: "particles", + addToGroup: "base", + properties: [ + { + label: "Emit", + type: "bool", + propertyID: "isEmitting", + }, + { + label: "Lifespan", + type: "number-draggable", + unit: "s", + step: 0.01, + decimals: 2, + propertyID: "lifespan", + }, + { + label: "Max Particles", + type: "number-draggable", + step: 1, + propertyID: "maxParticles", + }, + { + label: "Texture", + type: "texture", + propertyID: "particleTextures", + propertyName: "textures", // actual entity property name + }, + ] + }, + { + id: "particles_emit", + label: "EMIT", + isMinor: true, + properties: [ + { + label: "Emit Rate", + type: "number-draggable", + step: 1, + propertyID: "emitRate", + }, + { + label: "Emit Speed", + type: "number-draggable", + step: 0.1, + decimals: 2, + propertyID: "emitSpeed", + }, + { + label: "Speed Spread", + type: "number-draggable", + step: 0.1, + decimals: 2, + propertyID: "speedSpread", + }, + { + label: "Shape Type", + type: "dropdown", + options: { "box": "Box", "ellipsoid": "Ellipsoid", + "cylinder-y": "Cylinder", "circle": "Circle", "plane": "Plane", + "compound": "Use Compound Shape URL" }, + propertyID: "particleShapeType", + propertyName: "shapeType", + }, + { + label: "Compound Shape URL", + type: "string", + propertyID: "particleCompoundShapeURL", + propertyName: "compoundShapeURL", + }, + { + label: "Emit Dimensions", + type: "vec3", + vec3Type: "xyz", + step: 0.01, + round: 100, + subLabels: [ "x", "y", "z" ], + propertyID: "emitDimensions", + }, + { + label: "Emit Radius Start", + type: "number-draggable", + step: 0.001, + decimals: 3, + propertyID: "emitRadiusStart" + }, + { + label: "Emit Orientation", + type: "vec3", + vec3Type: "pyr", + step: 0.01, + round: 100, + subLabels: [ "x", "y", "z" ], + unit: "deg", + propertyID: "emitOrientation", + }, + { + label: "Trails", + type: "bool", + propertyID: "emitterShouldTrail", + }, + ] + }, + { + id: "particles_size", + label: "SIZE", + isMinor: true, + properties: [ + { + type: "triple", + label: "Size", + propertyID: "particleRadiusTriple", + properties: [ + { + label: "Start", + type: "number-draggable", + step: 0.01, + decimals: 2, + propertyID: "radiusStart", + fallbackProperty: "particleRadius", + }, + { + label: "Middle", + type: "number-draggable", + step: 0.01, + decimals: 2, + propertyID: "particleRadius", + }, + { + label: "Finish", + type: "number-draggable", + step: 0.01, + decimals: 2, + propertyID: "radiusFinish", + fallbackProperty: "particleRadius", + }, + ] + }, + { + label: "Size Spread", + type: "number-draggable", + step: 0.01, + decimals: 2, + propertyID: "radiusSpread", + }, + ] + }, + { + id: "particles_color", + label: "COLOR", + isMinor: true, + properties: [ + { + type: "triple", + label: "Color", + propertyID: "particleColorTriple", + properties: [ + { + label: "Start", + type: "color", + propertyID: "colorStart", + fallbackProperty: "color", + }, + { + label: "Middle", + type: "color", + propertyID: "particleColor", + propertyName: "color", // actual entity property name + }, + { + label: "Finish", + type: "color", + propertyID: "colorFinish", + fallbackProperty: "color", + }, + ] + }, + { + label: "Color Spread", + type: "color", + propertyID: "colorSpread", + }, + ] + }, + { + id: "particles_alpha", + label: "ALPHA", + isMinor: true, + properties: [ + { + type: "triple", + label: "Alpha", + propertyID: "particleAlphaTriple", + properties: [ + { + label: "Start", + type: "number-draggable", + step: 0.001, + decimals: 3, + propertyID: "alphaStart", + fallbackProperty: "alpha", + }, + { + label: "Middle", + type: "number-draggable", + step: 0.001, + decimals: 3, + propertyID: "alpha", + }, + { + label: "Finish", + type: "number-draggable", + step: 0.001, + decimals: 3, + propertyID: "alphaFinish", + fallbackProperty: "alpha", + }, + ] + }, + { + label: "Alpha Spread", + type: "number-draggable", + step: 0.001, + decimals: 3, + propertyID: "alphaSpread", + }, + ] + }, + { + id: "particles_acceleration", + label: "ACCELERATION", + isMinor: true, + properties: [ + { + label: "Emit Acceleration", + type: "vec3", + vec3Type: "xyz", + step: 0.01, + round: 100, + subLabels: [ "x", "y", "z" ], + propertyID: "emitAcceleration", + }, + { + label: "Acceleration Spread", + type: "vec3", + vec3Type: "xyz", + step: 0.01, + round: 100, + subLabels: [ "x", "y", "z" ], + propertyID: "accelerationSpread", + }, + ] + }, + { + id: "particles_spin", + label: "SPIN", + isMinor: true, + properties: [ + { + type: "triple", + label: "Spin", + propertyID: "particleSpinTriple", + properties: [ + { + label: "Start", + type: "number-draggable", + step: 0.1, + decimals: 2, + multiplier: DEGREES_TO_RADIANS, + unit: "deg", + propertyID: "spinStart", + fallbackProperty: "particleSpin", + }, + { + label: "Middle", + type: "number-draggable", + step: 0.1, + decimals: 2, + multiplier: DEGREES_TO_RADIANS, + unit: "deg", + propertyID: "particleSpin", + }, + { + label: "Finish", + type: "number-draggable", + step: 0.1, + decimals: 2, + multiplier: DEGREES_TO_RADIANS, + unit: "deg", + propertyID: "spinFinish", + fallbackProperty: "particleSpin", + }, + ] + }, + { + label: "Spin Spread", + type: "number-draggable", + step: 0.1, + decimals: 2, + multiplier: DEGREES_TO_RADIANS, + unit: "deg", + propertyID: "spinSpread", + }, + { + label: "Rotate with Entity", + type: "bool", + propertyID: "rotateWithEntity", + }, + ] + }, + { + id: "particles_constraints", + label: "CONSTRAINTS", + isMinor: true, + properties: [ + { + type: "triple", + label: "Horizontal Angle", + propertyID: "particlePolarTriple", + properties: [ + { + label: "Start", + type: "number-draggable", + step: 0.1, + decimals: 2, + multiplier: DEGREES_TO_RADIANS, + unit: "deg", + propertyID: "polarStart", + }, + { + label: "Finish", + type: "number-draggable", + step: 0.1, + decimals: 2, + multiplier: DEGREES_TO_RADIANS, + unit: "deg", + propertyID: "polarFinish", + }, + ], + }, + { + type: "triple", + label: "Vertical Angle", + propertyID: "particleAzimuthTriple", + properties: [ + { + label: "Start", + type: "number-draggable", + step: 0.1, + decimals: 2, + multiplier: DEGREES_TO_RADIANS, + unit: "deg", + propertyID: "azimuthStart", + }, + { + label: "Finish", + type: "number-draggable", + step: 0.1, + decimals: 2, + multiplier: DEGREES_TO_RADIANS, + unit: "deg", + propertyID: "azimuthFinish", + }, + ] + } + ] + }, + { + id: "spatial", + label: "SPATIAL", + properties: [ + { + label: "Position", + type: "vec3", + vec3Type: "xyz", + step: 0.1, + decimals: 4, + subLabels: [ "x", "y", "z" ], + unit: "m", + propertyID: "position", + spaceMode: PROPERTY_SPACE_MODE.WORLD, + }, + { + label: "Local Position", + type: "vec3", + vec3Type: "xyz", + step: 0.1, + decimals: 4, + subLabels: [ "x", "y", "z" ], + unit: "m", + propertyID: "localPosition", + spaceMode: PROPERTY_SPACE_MODE.LOCAL, + }, + { + label: "Rotation", + type: "vec3", + vec3Type: "pyr", + step: 0.1, + decimals: 4, + subLabels: [ "x", "y", "z" ], + unit: "deg", + propertyID: "rotation", + spaceMode: PROPERTY_SPACE_MODE.WORLD, + }, + { + label: "Local Rotation", + type: "vec3", + vec3Type: "pyr", + step: 0.1, + decimals: 4, + subLabels: [ "x", "y", "z" ], + unit: "deg", + propertyID: "localRotation", + spaceMode: PROPERTY_SPACE_MODE.LOCAL, + }, + { + label: "Dimensions", + type: "vec3", + vec3Type: "xyz", + step: 0.01, + decimals: 4, + subLabels: [ "x", "y", "z" ], + unit: "m", + propertyID: "dimensions", + spaceMode: PROPERTY_SPACE_MODE.WORLD, + }, + { + label: "Local Dimensions", + type: "vec3", + vec3Type: "xyz", + step: 0.01, + decimals: 4, + subLabels: [ "x", "y", "z" ], + unit: "m", + propertyID: "localDimensions", + spaceMode: PROPERTY_SPACE_MODE.LOCAL, + }, + { + label: "Scale", + type: "number-draggable", + defaultValue: 100, + unit: "%", + buttons: [ { id: "rescale", label: "Rescale", className: "blue", onClick: rescaleDimensions }, + { id: "reset", label: "Reset Dimensions", className: "red", onClick: resetToNaturalDimensions } ], + propertyID: "scale", + }, + { + label: "Pivot", + type: "vec3", + vec3Type: "xyz", + step: 0.001, + decimals: 4, + subLabels: [ "x", "y", "z" ], + unit: "(ratio of dimension)", + propertyID: "registrationPoint", + }, + { + label: "Align", + type: "buttons", + buttons: [ { id: "selection", label: "Selection to Grid", className: "black", onClick: moveSelectionToGrid }, + { id: "all", label: "All to Grid", className: "black", onClick: moveAllToGrid } ], + propertyID: "alignToGrid", + }, + ] + }, + { + id: "behavior", + label: "BEHAVIOR", + properties: [ + { + label: "Grabbable", + type: "bool", + propertyID: "grab.grabbable", + }, + { + label: "Cloneable", + type: "bool", + propertyID: "cloneable", + }, + { + label: "Clone Lifetime", + type: "number-draggable", + min: -1, + unit: "s", + propertyID: "cloneLifetime", + showPropertyRule: { "cloneable": "true" }, + }, + { + label: "Clone Limit", + type: "number-draggable", + min: 0, + propertyID: "cloneLimit", + showPropertyRule: { "cloneable": "true" }, + }, + { + label: "Clone Dynamic", + type: "bool", + propertyID: "cloneDynamic", + showPropertyRule: { "cloneable": "true" }, + }, + { + label: "Clone Avatar Entity", + type: "bool", + propertyID: "cloneAvatarEntity", + showPropertyRule: { "cloneable": "true" }, + }, + { + label: "Triggerable", + type: "bool", + propertyID: "grab.triggerable", + }, + { + label: "Follow Controller", + type: "bool", + propertyID: "grab.grabFollowsController", + }, + { + label: "Cast Shadows", + type: "bool", + propertyID: "canCastShadow", + }, + { + label: "Link", + type: "string", + propertyID: "href", + placeholder: "URL", + }, + { + label: "Ignore Pick Intersection", + type: "bool", + propertyID: "ignorePickIntersection", + }, + { + label: "Script", + type: "string", + buttons: [ { id: "reload", label: "F", className: "glyph", onClick: reloadScripts } ], + propertyID: "script", + placeholder: "URL", + hideIfCertified: true, + }, + { + label: "Server Script", + type: "string", + buttons: [ { id: "reload", label: "F", className: "glyph", onClick: reloadServerScripts } ], + propertyID: "serverScripts", + placeholder: "URL", + }, + { + label: "Server Script Status", + type: "placeholder", + indentedLabel: true, + propertyID: "serverScriptStatus", + selectionVisibility: PROPERTY_SELECTION_VISIBILITY.SINGLE_SELECTION, + }, + { + label: "Lifetime", + type: "number", + unit: "s", + propertyID: "lifetime", + }, + { + label: "User Data", + type: "textarea", + buttons: [ { id: "clear", label: "Clear User Data", className: "red", onClick: clearUserData }, + { id: "edit", label: "Edit as JSON", className: "blue", onClick: newJSONEditor }, + { id: "save", label: "Save User Data", className: "black", onClick: saveUserData } ], + propertyID: "userData", + }, + ] + }, + { + id: "collision", + label: "COLLISION", + properties: [ + { + label: "Collides", + type: "bool", + inverse: true, + propertyID: "collisionless", + }, + { + label: "Static Entities", + type: "bool", + propertyID: "collidesWithStatic", + propertyName: "static", // actual subProperty name + subPropertyOf: "collidesWith", + showPropertyRule: { "collisionless": "false" }, + }, + { + label: "Kinematic Entities", + type: "bool", + propertyID: "collidesWithKinematic", + propertyName: "kinematic", // actual subProperty name + subPropertyOf: "collidesWith", + showPropertyRule: { "collisionless": "false" }, + }, + { + label: "Dynamic Entities", + type: "bool", + propertyID: "collidesWithDynamic", + propertyName: "dynamic", // actual subProperty name + subPropertyOf: "collidesWith", + showPropertyRule: { "collisionless": "false" }, + }, + { + label: "My Avatar", + type: "bool", + propertyID: "collidesWithMyAvatar", + propertyName: "myAvatar", // actual subProperty name + subPropertyOf: "collidesWith", + showPropertyRule: { "collisionless": "false" }, + }, + { + label: "Other Avatars", + type: "bool", + propertyID: "collidesWithOtherAvatar", + propertyName: "otherAvatar", // actual subProperty name + subPropertyOf: "collidesWith", + showPropertyRule: { "collisionless": "false" }, + }, + { + label: "Collision Sound", + type: "string", + placeholder: "URL", + propertyID: "collisionSoundURL", + showPropertyRule: { "collisionless": "false" }, + hideIfCertified: true, + }, + { + label: "Dynamic", + type: "bool", + propertyID: "dynamic", + }, + ] + }, + { + id: "physics", + label: "PHYSICS", + properties: [ + { + label: "Linear Velocity", + type: "vec3", + vec3Type: "xyz", + step: 0.01, + decimals: 4, + subLabels: [ "x", "y", "z" ], + unit: "m/s", + propertyID: "localVelocity", + }, + { + label: "Linear Damping", + type: "number-draggable", + min: 0, + max: 1, + step: 0.001, + decimals: 4, + propertyID: "damping", + }, + { + label: "Angular Velocity", + type: "vec3", + vec3Type: "pyr", + multiplier: DEGREES_TO_RADIANS, + decimals: 4, + subLabels: [ "x", "y", "z" ], + unit: "deg/s", + propertyID: "localAngularVelocity", + }, + { + label: "Angular Damping", + type: "number-draggable", + min: 0, + max: 1, + step: 0.001, + decimals: 4, + propertyID: "angularDamping", + }, + { + label: "Bounciness", + type: "number-draggable", + step: 0.001, + decimals: 4, + propertyID: "restitution", + }, + { + label: "Friction", + type: "number-draggable", + step: 0.01, + decimals: 4, + propertyID: "friction", + }, + { + label: "Density", + type: "number-draggable", + step: 1, + decimals: 4, + propertyID: "density", + }, + { + label: "Gravity", + type: "vec3", + vec3Type: "xyz", + subLabels: [ "x", "y", "z" ], + step: 0.1, + decimals: 4, + unit: "m/s2", + propertyID: "gravity", + }, + { + label: "Acceleration", + type: "vec3", + vec3Type: "xyz", + subLabels: [ "x", "y", "z" ], + step: 0.1, + decimals: 4, + unit: "m/s2", + propertyID: "acceleration", + }, + ] + }, +]; + +const GROUPS_PER_TYPE = { + None: [ 'base', 'spatial', 'behavior', 'collision', 'physics' ], + Shape: [ 'base', 'shape', 'spatial', 'behavior', 'collision', 'physics' ], + Text: [ 'base', 'text', 'spatial', 'behavior', 'collision', 'physics' ], + Zone: [ 'base', 'zone', 'spatial', 'behavior', 'physics' ], + Model: [ 'base', 'model', 'spatial', 'behavior', 'collision', 'physics' ], + Image: [ 'base', 'image', 'spatial', 'behavior', 'collision', 'physics' ], + Web: [ 'base', 'web', 'spatial', 'behavior', 'collision', 'physics' ], + Light: [ 'base', 'light', 'spatial', 'behavior', 'collision', 'physics' ], + Material: [ 'base', 'material', 'spatial', 'behavior' ], + ParticleEffect: [ 'base', 'particles', 'particles_emit', 'particles_size', 'particles_color', 'particles_alpha', + 'particles_acceleration', 'particles_spin', 'particles_constraints', 'spatial', 'behavior', 'physics' ], + PolyLine: [ 'base', 'spatial', 'behavior', 'collision', 'physics' ], + PolyVox: [ 'base', 'spatial', 'behavior', 'collision', 'physics' ], + Grid: [ 'base', 'grid', 'spatial', 'behavior', 'physics' ], + Multiple: [ 'base', 'spatial', 'behavior', 'collision', 'physics' ], +}; + +const EDITOR_TIMEOUT_DURATION = 1500; +const DEBOUNCE_TIMEOUT = 125; + +const COLOR_MIN = 0; +const COLOR_MAX = 255; +const COLOR_STEP = 1; + +const MATERIAL_PREFIX_STRING = "mat::"; + +const PENDING_SCRIPT_STATUS = "[ Fetching status ]"; +const NOT_RUNNING_SCRIPT_STATUS = "Not running"; +const ENTITY_SCRIPT_STATUS = { + pending: "Pending", + loading: "Loading", + error_loading_script: "Error loading script", // eslint-disable-line camelcase + error_running_script: "Error running script", // eslint-disable-line camelcase + running: "Running", + unloaded: "Unloaded" +}; + +const ENABLE_DISABLE_SELECTOR = "input, textarea, span, .dropdown dl, .color-picker"; + +const PROPERTY_NAME_DIVISION = { + GROUP: 0, + PROPERTY: 1, + SUB_PROPERTY: 2, +}; + +const RECT_ELEMENTS = { + X_NUMBER: 0, + Y_NUMBER: 1, + WIDTH_NUMBER: 2, + HEIGHT_NUMBER: 3, +}; + +const VECTOR_ELEMENTS = { + X_NUMBER: 0, + Y_NUMBER: 1, + Z_NUMBER: 2, +}; + +const COLOR_ELEMENTS = { + COLOR_PICKER: 0, + RED_NUMBER: 1, + GREEN_NUMBER: 2, + BLUE_NUMBER: 3, +}; + +const TEXTURE_ELEMENTS = { + IMAGE: 0, + TEXT_INPUT: 1, +}; + +const JSON_EDITOR_ROW_DIV_INDEX = 2; + +let elGroups = {}; +let properties = {}; +let propertyRangeRequests = []; +let colorPickers = {}; +let particlePropertyUpdates = {}; +let selectedEntityIDs = new Set(); +let currentSelections = []; +let createAppTooltip = new CreateAppTooltip(); +let currentSpaceMode = PROPERTY_SPACE_MODE.LOCAL; + + +function createElementFromHTML(htmlString) { + let elTemplate = document.createElement('template'); + elTemplate.innerHTML = htmlString.trim(); + return elTemplate.content.firstChild; +} + +function isFlagSet(value, flag) { + return (value & flag) === flag; +} + +/** + * GENERAL PROPERTY/GROUP FUNCTIONS + */ + +function getPropertyInputElement(propertyID) { + let property = properties[propertyID]; + switch (property.data.type) { + case 'string': + case 'number': + case 'bool': + case 'dropdown': + case 'textarea': + case 'texture': + return property.elInput; + case 'number-draggable': + return property.elNumber.elInput; + case 'rect': + return { + x: property.elNumberX.elInput, + y: property.elNumberY.elInput, + width: property.elNumberWidth.elInput, + height: property.elNumberHeight.elInput + }; + case 'vec3': + case 'vec2': + return { x: property.elNumberX.elInput, y: property.elNumberY.elInput, z: property.elNumberZ.elInput }; + case 'color': + return { red: property.elNumberR.elInput, green: property.elNumberG.elInput, blue: property.elNumberB.elInput }; + case 'icon': + return property.elLabel; + case 'dynamic-multiselect': + return property.elDivOptions; + default: + return undefined; + } +} + +function enableChildren(el, selector) { + let elSelectors = el.querySelectorAll(selector); + for (let selectorIndex = 0; selectorIndex < elSelectors.length; ++selectorIndex) { + elSelectors[selectorIndex].removeAttribute('disabled'); + } +} + +function disableChildren(el, selector) { + let elSelectors = el.querySelectorAll(selector); + for (let selectorIndex = 0; selectorIndex < elSelectors.length; ++selectorIndex) { + elSelectors[selectorIndex].setAttribute('disabled', 'disabled'); + } +} + +function enableProperties() { + enableChildren(document.getElementById("properties-list"), ENABLE_DISABLE_SELECTOR); + enableChildren(document, ".colpick"); + + let elLocked = getPropertyInputElement("locked"); + if (elLocked.checked === false) { + removeStaticUserData(); + removeStaticMaterialData(); + } +} + +function disableProperties() { + disableChildren(document.getElementById("properties-list"), ENABLE_DISABLE_SELECTOR); + disableChildren(document, ".colpick"); + for (let pickKey in colorPickers) { + colorPickers[pickKey].colpickHide(); + } + + let elLocked = getPropertyInputElement("locked"); + if (elLocked.checked === true) { + if ($('#property-userData-editor').css('display') === "block") { + showStaticUserData(); + } + if ($('#property-materialData-editor').css('display') === "block") { + showStaticMaterialData(); + } + } +} + +function showPropertyElement(propertyID, show) { + setPropertyVisibility(properties[propertyID], show); +} + +function setPropertyVisibility(property, visible) { + property.elContainer.style.display = visible ? null : "none"; +} + +function resetProperties() { + for (let propertyID in properties) { + let property = properties[propertyID]; + let propertyData = property.data; + + switch (propertyData.type) { + case 'number': + case 'string': { + property.elInput.classList.remove('multi-diff'); + if (propertyData.defaultValue !== undefined) { + property.elInput.value = propertyData.defaultValue; + } else { + property.elInput.value = ""; + } + break; + } + case 'bool': { + property.elInput.classList.remove('multi-diff'); + property.elInput.checked = false; + break; + } + case 'number-draggable': { + if (propertyData.defaultValue !== undefined) { + property.elNumber.setValue(propertyData.defaultValue, false); + } else { + property.elNumber.setValue("", false); + } + break; + } + case 'rect': { + property.elNumberX.setValue("", false); + property.elNumberY.setValue("", false); + property.elNumberWidth.setValue("", false); + property.elNumberHeight.setValue("", false); + break; + } + case 'vec3': + case 'vec2': { + property.elNumberX.setValue("", false); + property.elNumberY.setValue("", false); + if (property.elNumberZ !== undefined) { + property.elNumberZ.setValue("", false); + } + break; + } + case 'color': { + property.elColorPicker.style.backgroundColor = "rgb(" + 0 + "," + 0 + "," + 0 + ")"; + property.elNumberR.setValue("", false); + property.elNumberG.setValue("", false); + property.elNumberB.setValue("", false); + break; + } + case 'dropdown': { + property.elInput.classList.remove('multi-diff'); + property.elInput.value = ""; + setDropdownText(property.elInput); + break; + } + case 'textarea': { + property.elInput.classList.remove('multi-diff'); + property.elInput.value = ""; + setTextareaScrolling(property.elInput); + break; + } + case 'icon': { + property.elSpan.style.display = "none"; + break; + } + case 'texture': { + property.elInput.classList.remove('multi-diff'); + property.elInput.value = ""; + property.elInput.imageLoad(property.elInput.value); + break; + } + case 'dynamic-multiselect': { + resetDynamicMultiselectProperty(property.elDivOptions); + break; + } + } + + let showPropertyRules = properties[propertyID].showPropertyRules; + if (showPropertyRules !== undefined) { + for (let propertyToHide in showPropertyRules) { + showPropertyElement(propertyToHide, false); + } + } + } + + resetServerScriptStatus(); +} + +function resetServerScriptStatus() { + let elServerScriptError = document.getElementById("property-serverScripts-error"); + let elServerScriptStatus = document.getElementById("property-serverScripts-status"); + elServerScriptError.parentElement.style.display = "none"; + elServerScriptStatus.innerText = NOT_RUNNING_SCRIPT_STATUS; +} + +function showGroupsForType(type) { + if (type === "Box" || type === "Sphere") { + showGroupsForTypes(["Shape"]); + return; + } + showGroupsForTypes([type]); +} + +function getGroupsForTypes(types) { + return Object.keys(elGroups).filter((groupKey) => { + return types.map(type => GROUPS_PER_TYPE[type].includes(groupKey)).every(function (hasGroup) { + return hasGroup; + }); + }); +} + +function showGroupsForTypes(types) { + Object.entries(elGroups).forEach(([groupKey, elGroup]) => { + if (types.map(type => GROUPS_PER_TYPE[type].includes(groupKey)).every(function (hasGroup) { return hasGroup; })) { + elGroup.style.display = "block"; + } else { + elGroup.style.display = "none"; + } + }); +} + +function getFirstSelectedID() { + if (selectedEntityIDs.size === 0) { + return null; + } + return selectedEntityIDs.values().next().value; +} + +/** + * Returns true when the user is currently dragging the numeric slider control of the property + * @param propertyName - name of property + * @returns {boolean} currentlyDragging + */ +function isCurrentlyDraggingProperty(propertyName) { + return properties[propertyName] && properties[propertyName].dragging === true; +} + +const SUPPORTED_FALLBACK_TYPES = ['number', 'number-draggable', 'rect', 'vec3', 'vec2', 'color']; + +function getMultiplePropertyValue(originalPropertyName) { + // if this is a compound property name (i.e. animation.running) + // then split it by . up to 3 times to find property value + + let propertyData = null; + if (properties[originalPropertyName] !== undefined) { + propertyData = properties[originalPropertyName].data; + } + + let propertyValues = []; + let splitPropertyName = originalPropertyName.split('.'); + if (splitPropertyName.length > 1) { + let propertyGroupName = splitPropertyName[PROPERTY_NAME_DIVISION.GROUP]; + let propertyName = splitPropertyName[PROPERTY_NAME_DIVISION.PROPERTY]; + propertyValues = currentSelections.map(selection => { + let groupProperties = selection.properties[propertyGroupName]; + if (groupProperties === undefined || groupProperties[propertyName] === undefined) { + return undefined; + } + if (splitPropertyName.length === PROPERTY_NAME_DIVISION.SUB_PROPERTY + 1) { + let subPropertyName = splitPropertyName[PROPERTY_NAME_DIVISION.SUB_PROPERTY]; + return groupProperties[propertyName][subPropertyName]; + } else { + return groupProperties[propertyName]; + } + }); + } else { + propertyValues = currentSelections.map(selection => selection.properties[originalPropertyName]); + } + + if (propertyData !== null && propertyData.fallbackProperty !== undefined && + SUPPORTED_FALLBACK_TYPES.includes(propertyData.type)) { + + let fallbackMultiValue = null; + + for (let i = 0; i < propertyValues.length; ++i) { + let isPropertyNotNumber = false; + let propertyValue = propertyValues[i]; + if (propertyValue === undefined) { + continue; + } + switch (propertyData.type) { + case 'number': + case 'number-draggable': + isPropertyNotNumber = isNaN(propertyValue) || propertyValue === null; + break; + case 'rect': + case 'vec3': + case 'vec2': + isPropertyNotNumber = isNaN(propertyValue.x) || propertyValue.x === null; + break; + case 'color': + isPropertyNotNumber = isNaN(propertyValue.red) || propertyValue.red === null; + break; + } + if (isPropertyNotNumber) { + if (fallbackMultiValue === null) { + fallbackMultiValue = getMultiplePropertyValue(propertyData.fallbackProperty); + } + propertyValues[i] = fallbackMultiValue.values[i]; + } + } + } + + const firstValue = propertyValues[0]; + const isMultiDiffValue = !propertyValues.every((x) => deepEqual(firstValue, x)); + + if (isMultiDiffValue) { + return { + value: undefined, + values: propertyValues, + isMultiDiffValue: true + } + } + + return { + value: propertyValues[0], + values: propertyValues, + isMultiDiffValue: false + }; +} + +/** + * Retrieve more detailed info for differing Numeric MultiplePropertyValue + * @param multiplePropertyValue - input multiplePropertyValue + * @param propertyData + * @returns {{keys: *[], propertyComponentDiff, averagePerPropertyComponent}} + */ +function getDetailedNumberMPVDiff(multiplePropertyValue, propertyData) { + let detailedValues = {}; + // Fixed numbers can't be easily averaged since they're strings, so lets keep an array of unmodified numbers + let unmodifiedValues = {}; + const DEFAULT_KEY = 0; + let uniqueKeys = new Set([]); + multiplePropertyValue.values.forEach(function(propertyValue) { + if (typeof propertyValue === "object") { + Object.entries(propertyValue).forEach(function([key, value]) { + if (!uniqueKeys.has(key)) { + uniqueKeys.add(key); + detailedValues[key] = []; + unmodifiedValues[key] = []; + } + detailedValues[key].push(applyInputNumberPropertyModifiers(value, propertyData)); + unmodifiedValues[key].push(value); + }); + } else { + if (!uniqueKeys.has(DEFAULT_KEY)) { + uniqueKeys.add(DEFAULT_KEY); + detailedValues[DEFAULT_KEY] = []; + unmodifiedValues[DEFAULT_KEY] = []; + } + detailedValues[DEFAULT_KEY].push(applyInputNumberPropertyModifiers(propertyValue, propertyData)); + unmodifiedValues[DEFAULT_KEY].push(propertyValue); + } + }); + let keys = [...uniqueKeys]; + + let propertyComponentDiff = {}; + Object.entries(detailedValues).forEach(function([key, value]) { + propertyComponentDiff[key] = [...new Set(value)].length > 1; + }); + + let averagePerPropertyComponent = {}; + Object.entries(unmodifiedValues).forEach(function([key, value]) { + let average = value.reduce((a, b) => a + b) / value.length; + averagePerPropertyComponent[key] = applyInputNumberPropertyModifiers(average, propertyData); + }); + + return { + keys, + propertyComponentDiff, + averagePerPropertyComponent, + }; +} + +function getDetailedSubPropertyMPVDiff(multiplePropertyValue, subPropertyName) { + let isChecked = false; + let checkedValues = multiplePropertyValue.values.map((value) => value.split(",").includes(subPropertyName)); + let isMultiDiff = !checkedValues.every(value => value === checkedValues[0]); + if (!isMultiDiff) { + isChecked = checkedValues[0]; + } + return { + isChecked, + isMultiDiff + } +} + +function updateVisibleSpaceModeProperties() { + for (let propertyID in properties) { + if (properties.hasOwnProperty(propertyID)) { + let property = properties[propertyID]; + let propertySpaceMode = property.spaceMode; + let elProperty = properties[propertyID].elContainer; + if (propertySpaceMode !== PROPERTY_SPACE_MODE.ALL && propertySpaceMode !== currentSpaceMode) { + elProperty.classList.add('spacemode-hidden'); + } else { + elProperty.classList.remove('spacemode-hidden'); + } + } + } +} + +/** + * PROPERTY UPDATE FUNCTIONS + */ + +function createPropertyUpdateObject(originalPropertyName, propertyValue) { + let propertyUpdate = {}; + // if this is a compound property name (i.e. animation.running) then split it by . up to 3 times + let splitPropertyName = originalPropertyName.split('.'); + if (splitPropertyName.length > 1) { + let propertyGroupName = splitPropertyName[PROPERTY_NAME_DIVISION.GROUP]; + let propertyName = splitPropertyName[PROPERTY_NAME_DIVISION.PROPERTY]; + propertyUpdate[propertyGroupName] = {}; + if (splitPropertyName.length === PROPERTY_NAME_DIVISION.SUB_PROPERTY + 1) { + let subPropertyName = splitPropertyName[PROPERTY_NAME_DIVISION.SUB_PROPERTY]; + propertyUpdate[propertyGroupName][propertyName] = {}; + propertyUpdate[propertyGroupName][propertyName][subPropertyName] = propertyValue; + } else { + propertyUpdate[propertyGroupName][propertyName] = propertyValue; + } + } else { + propertyUpdate[originalPropertyName] = propertyValue; + } + return propertyUpdate; +} + +function updateProperty(originalPropertyName, propertyValue, isParticleProperty) { + let propertyUpdate = createPropertyUpdateObject(originalPropertyName, propertyValue); + + // queue up particle property changes with the debounced sync to avoid + // causing particle emitting to reset excessively with each value change + if (isParticleProperty) { + Object.keys(propertyUpdate).forEach(function (propertyUpdateKey) { + particlePropertyUpdates[propertyUpdateKey] = propertyUpdate[propertyUpdateKey]; + }); + particleSyncDebounce(); + } else { + // only update the entity property value itself if in the middle of dragging + // prevent undo command push, saving new property values, and property update + // callback until drag is complete (additional update sent via dragEnd callback) + let onlyUpdateEntity = isCurrentlyDraggingProperty(originalPropertyName); + updateProperties(propertyUpdate, onlyUpdateEntity); + } +} + +let particleSyncDebounce = _.debounce(function () { + updateProperties(particlePropertyUpdates); + particlePropertyUpdates = {}; +}, DEBOUNCE_TIMEOUT); + +function updateProperties(propertiesToUpdate, onlyUpdateEntity) { + if (onlyUpdateEntity === undefined) { + onlyUpdateEntity = false; + } + EventBridge.emitWebEvent(JSON.stringify({ + ids: [...selectedEntityIDs], + type: "update", + properties: propertiesToUpdate, + onlyUpdateEntities: onlyUpdateEntity + })); +} + +function updateMultiDiffProperties(propertiesMapToUpdate, onlyUpdateEntity) { + if (onlyUpdateEntity === undefined) { + onlyUpdateEntity = false; + } + EventBridge.emitWebEvent(JSON.stringify({ + type: "update", + propertiesMap: propertiesMapToUpdate, + onlyUpdateEntities: onlyUpdateEntity + })); +} + +function createEmitTextPropertyUpdateFunction(property) { + return function() { + property.elInput.classList.remove('multi-diff'); + updateProperty(property.name, this.value, property.isParticleProperty); + }; +} + +function createEmitCheckedPropertyUpdateFunction(property) { + return function() { + updateProperty(property.name, property.data.inverse ? !this.checked : this.checked, property.isParticleProperty); + }; +} + +function createDragStartFunction(property) { + return function() { + property.dragging = true; + }; +} + +function createDragEndFunction(property) { + return function() { + property.dragging = false; + + if (this.multiDiffModeEnabled) { + let propertyMultiValue = getMultiplePropertyValue(property.name); + let updateObjects = []; + const selectedEntityIDsArray = [...selectedEntityIDs]; + + for (let i = 0; i < selectedEntityIDsArray.length; ++i) { + let entityID = selectedEntityIDsArray[i]; + updateObjects.push({ + entityIDs: [entityID], + properties: createPropertyUpdateObject(property.name, propertyMultiValue.values[i]), + }); + } + + // send a full updateMultiDiff post-dragging to count as an action in the undo stack + updateMultiDiffProperties(updateObjects); + } else { + // send an additional update post-dragging to consider whole property change from dragStart to dragEnd to be 1 action + this.valueChangeFunction(); + } + }; +} + +function createEmitNumberPropertyUpdateFunction(property) { + return function() { + let value = parseFloat(applyOutputNumberPropertyModifiers(parseFloat(this.value), property.data)); + updateProperty(property.name, value, property.isParticleProperty); + }; +} + +function createEmitNumberPropertyComponentUpdateFunction(property, propertyComponent) { + return function() { + let propertyMultiValue = getMultiplePropertyValue(property.name); + let value = parseFloat(applyOutputNumberPropertyModifiers(parseFloat(this.value), property.data)); + + if (propertyMultiValue.isMultiDiffValue) { + let updateObjects = []; + const selectedEntityIDsArray = [...selectedEntityIDs]; + + for (let i = 0; i < selectedEntityIDsArray.length; ++i) { + let entityID = selectedEntityIDsArray[i]; + + let propertyObject = propertyMultiValue.values[i]; + propertyObject[propertyComponent] = value; + + let updateObject = createPropertyUpdateObject(property.name, propertyObject); + updateObjects.push({ + entityIDs: [entityID], + properties: updateObject, + }); + + mergeDeep(currentSelections[i].properties, updateObject); + } + + // only update the entity property value itself if in the middle of dragging + // prevent undo command push, saving new property values, and property update + // callback until drag is complete (additional update sent via dragEnd callback) + let onlyUpdateEntity = isCurrentlyDraggingProperty(property.name); + updateMultiDiffProperties(updateObjects, onlyUpdateEntity); + } else { + let propertyValue = propertyMultiValue.value; + propertyValue[propertyComponent] = value; + updateProperty(property.name, propertyValue, property.isParticleProperty); + } + }; +} + +function createEmitColorPropertyUpdateFunction(property) { + return function() { + emitColorPropertyUpdate(property.name, property.elNumberR.elInput.value, property.elNumberG.elInput.value, + property.elNumberB.elInput.value, property.isParticleProperty); + }; +} + +function emitColorPropertyUpdate(propertyName, red, green, blue, isParticleProperty) { + let newValue = { + red: red, + green: green, + blue: blue + }; + updateProperty(propertyName, newValue, isParticleProperty); +} + +function toggleBooleanCSV(inputCSV, property, enable) { + let values = inputCSV.split(","); + if (enable && !values.includes(property)) { + values.push(property); + } else if (!enable && values.includes(property)) { + values = values.filter(value => value !== property); + } + return values.join(","); +} + +function updateCheckedSubProperty(propertyName, propertyMultiValue, subPropertyElement, subPropertyString, isParticleProperty) { + if (propertyMultiValue.isMultiDiffValue) { + let updateObjects = []; + const selectedEntityIDsArray = [...selectedEntityIDs]; + + for (let i = 0; i < selectedEntityIDsArray.length; ++i) { + let newValue = toggleBooleanCSV(propertyMultiValue.values[i], subPropertyString, subPropertyElement.checked); + updateObjects.push({ + entityIDs: [selectedEntityIDsArray[i]], + properties: createPropertyUpdateObject(propertyName, newValue), + }); + } + + updateMultiDiffProperties(updateObjects); + } else { + updateProperty(propertyName, toggleBooleanCSV(propertyMultiValue.value, subPropertyString, subPropertyElement.checked), + isParticleProperty); + } +} + +/** + * PROPERTY ELEMENT CREATION FUNCTIONS + */ + +function createStringProperty(property, elProperty) { + let elementID = property.elementID; + let propertyData = property.data; + + elProperty.className = "text"; + + let elInput = createElementFromHTML(` + + `); + + + elInput.addEventListener('change', createEmitTextPropertyUpdateFunction(property)); + if (propertyData.onChange !== undefined) { + elInput.addEventListener('change', propertyData.onChange); + } + + + let elMultiDiff = document.createElement('span'); + elMultiDiff.className = "multi-diff"; + + elProperty.appendChild(elInput); + elProperty.appendChild(elMultiDiff); + + if (propertyData.buttons !== undefined) { + addButtons(elProperty, elementID, propertyData.buttons, false); + } + + return elInput; +} + +function createBoolProperty(property, elProperty) { + let propertyName = property.name; + let elementID = property.elementID; + let propertyData = property.data; + + elProperty.className = "checkbox"; + + if (propertyData.glyph !== undefined) { + let elSpan = document.createElement('span'); + elSpan.innerHTML = propertyData.glyph; + elSpan.className = 'icon'; + elProperty.appendChild(elSpan); + } + + let elInput = document.createElement('input'); + elInput.setAttribute("id", elementID); + elInput.setAttribute("type", "checkbox"); + + elProperty.appendChild(elInput); + elProperty.appendChild(createElementFromHTML(``)); + + let subPropertyOf = propertyData.subPropertyOf; + if (subPropertyOf !== undefined) { + elInput.addEventListener('change', function() { + let subPropertyMultiValue = getMultiplePropertyValue(subPropertyOf); + + updateCheckedSubProperty(subPropertyOf, + subPropertyMultiValue, + elInput, propertyName, property.isParticleProperty); + }); + } else { + elInput.addEventListener('change', createEmitCheckedPropertyUpdateFunction(property)); + } + + return elInput; +} + +function createNumberProperty(property, elProperty) { + let elementID = property.elementID; + let propertyData = property.data; + + elProperty.className = "text"; + + let elInput = createElementFromHTML(` + + `); + + if (propertyData.min !== undefined) { + elInput.setAttribute("min", propertyData.min); + } + if (propertyData.max !== undefined) { + elInput.setAttribute("max", propertyData.max); + } + if (propertyData.step !== undefined) { + elInput.setAttribute("step", propertyData.step); + } + if (propertyData.defaultValue !== undefined) { + elInput.value = propertyData.defaultValue; + } + + elInput.addEventListener('change', createEmitNumberPropertyUpdateFunction(property)); + + let elMultiDiff = document.createElement('span'); + elMultiDiff.className = "multi-diff"; + + elProperty.appendChild(elInput); + elProperty.appendChild(elMultiDiff); + + if (propertyData.buttons !== undefined) { + addButtons(elProperty, elementID, propertyData.buttons, false); + } + + return elInput; +} + +function updateNumberMinMax(property) { + let elInput = property.elInput; + let min = property.data.min; + let max = property.data.max; + if (min !== undefined) { + elInput.setAttribute("min", min); + } + if (max !== undefined) { + elInput.setAttribute("max", max); + } +} + +/** + * + * @param {object} property - property update on step + * @param {string} [propertyComponent] - propertyComponent to update on step (e.g. enter 'x' to just update position.x) + * @returns {Function} + */ +function createMultiDiffStepFunction(property, propertyComponent) { + return function(step, shouldAddToUndoHistory) { + if (shouldAddToUndoHistory === undefined) { + shouldAddToUndoHistory = false; + } + + let propertyMultiValue = getMultiplePropertyValue(property.name); + if (!propertyMultiValue.isMultiDiffValue) { + console.log("setMultiDiffStepFunction is only supposed to be called in MultiDiff mode."); + return; + } + + let multiplier = property.data.multiplier !== undefined ? property.data.multiplier : 1; + + let applyDelta = step * multiplier; + + if (selectedEntityIDs.size !== propertyMultiValue.values.length) { + console.log("selectedEntityIDs and propertyMultiValue got out of sync."); + return; + } + let updateObjects = []; + const selectedEntityIDsArray = [...selectedEntityIDs]; + + for (let i = 0; i < selectedEntityIDsArray.length; ++i) { + let entityID = selectedEntityIDsArray[i]; + + let updatedValue; + if (propertyComponent !== undefined) { + let objectToUpdate = propertyMultiValue.values[i]; + objectToUpdate[propertyComponent] += applyDelta; + updatedValue = objectToUpdate; + } else { + updatedValue = propertyMultiValue.values[i] + applyDelta; + } + let propertiesUpdate = createPropertyUpdateObject(property.name, updatedValue); + updateObjects.push({ + entityIDs: [entityID], + properties: propertiesUpdate + }); + // We need to store these so that we can send a full update on the dragEnd + mergeDeep(currentSelections[i].properties, propertiesUpdate); + } + + updateMultiDiffProperties(updateObjects, !shouldAddToUndoHistory); + } +} + +function createNumberDraggableProperty(property, elProperty) { + let elementID = property.elementID; + let propertyData = property.data; + + elProperty.className += " draggable-number-container"; + + let dragStartFunction = createDragStartFunction(property); + let dragEndFunction = createDragEndFunction(property); + let elDraggableNumber = new DraggableNumber(propertyData.min, propertyData.max, propertyData.step, + propertyData.decimals, dragStartFunction, dragEndFunction); + + let defaultValue = propertyData.defaultValue; + if (defaultValue !== undefined) { + elDraggableNumber.elInput.value = defaultValue; + } + + let valueChangeFunction = createEmitNumberPropertyUpdateFunction(property); + elDraggableNumber.setValueChangeFunction(valueChangeFunction); + + elDraggableNumber.setMultiDiffStepFunction(createMultiDiffStepFunction(property)); + + elDraggableNumber.elInput.setAttribute("id", elementID); + elProperty.appendChild(elDraggableNumber.elDiv); + + if (propertyData.buttons !== undefined) { + addButtons(elDraggableNumber.elDiv, elementID, propertyData.buttons, false); + } + + return elDraggableNumber; +} + +function updateNumberDraggableMinMax(property) { + let propertyData = property.data; + property.elNumber.updateMinMax(propertyData.min, propertyData.max); +} + +function createRectProperty(property, elProperty) { + let propertyData = property.data; + + elProperty.className = "rect"; + + let elXYRow = document.createElement('div'); + elXYRow.className = "rect-row fstuple"; + elProperty.appendChild(elXYRow); + + let elWidthHeightRow = document.createElement('div'); + elWidthHeightRow.className = "rect-row fstuple"; + elProperty.appendChild(elWidthHeightRow); + + + let elNumberX = createTupleNumberInput(property, propertyData.subLabels[RECT_ELEMENTS.X_NUMBER]); + let elNumberY = createTupleNumberInput(property, propertyData.subLabels[RECT_ELEMENTS.Y_NUMBER]); + let elNumberWidth = createTupleNumberInput(property, propertyData.subLabels[RECT_ELEMENTS.WIDTH_NUMBER]); + let elNumberHeight = createTupleNumberInput(property, propertyData.subLabels[RECT_ELEMENTS.HEIGHT_NUMBER]); + + elXYRow.appendChild(elNumberX.elDiv); + elXYRow.appendChild(elNumberY.elDiv); + elWidthHeightRow.appendChild(elNumberWidth.elDiv); + elWidthHeightRow.appendChild(elNumberHeight.elDiv); + + elNumberX.setValueChangeFunction(createEmitNumberPropertyComponentUpdateFunction(property, 'x')); + elNumberY.setValueChangeFunction(createEmitNumberPropertyComponentUpdateFunction(property, 'y')); + elNumberWidth.setValueChangeFunction(createEmitNumberPropertyComponentUpdateFunction(property, 'width')); + elNumberHeight.setValueChangeFunction(createEmitNumberPropertyComponentUpdateFunction(property, 'height')); + + elNumberX.setMultiDiffStepFunction(createMultiDiffStepFunction(property, 'x')); + elNumberY.setMultiDiffStepFunction(createMultiDiffStepFunction(property, 'y')); + elNumberX.setMultiDiffStepFunction(createMultiDiffStepFunction(property, 'width')); + elNumberY.setMultiDiffStepFunction(createMultiDiffStepFunction(property, 'height')); + + let elResult = []; + elResult[RECT_ELEMENTS.X_NUMBER] = elNumberX; + elResult[RECT_ELEMENTS.Y_NUMBER] = elNumberY; + elResult[RECT_ELEMENTS.WIDTH_NUMBER] = elNumberWidth; + elResult[RECT_ELEMENTS.HEIGHT_NUMBER] = elNumberHeight; + return elResult; +} + +function updateRectMinMax(property) { + let min = property.data.min; + let max = property.data.max; + property.elNumberX.updateMinMax(min, max); + property.elNumberY.updateMinMax(min, max); + property.elNumberWidth.updateMinMax(min, max); + property.elNumberHeight.updateMinMax(min, max); +} + +function createVec3Property(property, elProperty) { + let propertyData = property.data; + + elProperty.className = propertyData.vec3Type + " fstuple"; + + let elNumberX = createTupleNumberInput(property, propertyData.subLabels[VECTOR_ELEMENTS.X_NUMBER]); + let elNumberY = createTupleNumberInput(property, propertyData.subLabels[VECTOR_ELEMENTS.Y_NUMBER]); + let elNumberZ = createTupleNumberInput(property, propertyData.subLabels[VECTOR_ELEMENTS.Z_NUMBER]); + elProperty.appendChild(elNumberX.elDiv); + elProperty.appendChild(elNumberY.elDiv); + elProperty.appendChild(elNumberZ.elDiv); + + elNumberX.setValueChangeFunction(createEmitNumberPropertyComponentUpdateFunction(property, 'x')); + elNumberY.setValueChangeFunction(createEmitNumberPropertyComponentUpdateFunction(property, 'y')); + elNumberZ.setValueChangeFunction(createEmitNumberPropertyComponentUpdateFunction(property, 'z')); + + elNumberX.setMultiDiffStepFunction(createMultiDiffStepFunction(property, 'x')); + elNumberY.setMultiDiffStepFunction(createMultiDiffStepFunction(property, 'y')); + elNumberZ.setMultiDiffStepFunction(createMultiDiffStepFunction(property, 'z')); + + let elResult = []; + elResult[VECTOR_ELEMENTS.X_NUMBER] = elNumberX; + elResult[VECTOR_ELEMENTS.Y_NUMBER] = elNumberY; + elResult[VECTOR_ELEMENTS.Z_NUMBER] = elNumberZ; + return elResult; +} + +function createVec2Property(property, elProperty) { + let propertyData = property.data; + + elProperty.className = propertyData.vec2Type + " fstuple"; + + let elTuple = document.createElement('div'); + elTuple.className = "tuple"; + + elProperty.appendChild(elTuple); + + let elNumberX = createTupleNumberInput(property, propertyData.subLabels[VECTOR_ELEMENTS.X_NUMBER]); + let elNumberY = createTupleNumberInput(property, propertyData.subLabels[VECTOR_ELEMENTS.Y_NUMBER]); + elProperty.appendChild(elNumberX.elDiv); + elProperty.appendChild(elNumberY.elDiv); + + elNumberX.setValueChangeFunction(createEmitNumberPropertyComponentUpdateFunction(property, 'x')); + elNumberY.setValueChangeFunction(createEmitNumberPropertyComponentUpdateFunction(property, 'y')); + + elNumberX.setMultiDiffStepFunction(createMultiDiffStepFunction(property, 'x')); + elNumberY.setMultiDiffStepFunction(createMultiDiffStepFunction(property, 'y')); + + let elResult = []; + elResult[VECTOR_ELEMENTS.X_NUMBER] = elNumberX; + elResult[VECTOR_ELEMENTS.Y_NUMBER] = elNumberY; + return elResult; +} + +function updateVectorMinMax(property) { + let min = property.data.min; + let max = property.data.max; + property.elNumberX.updateMinMax(min, max); + property.elNumberY.updateMinMax(min, max); + if (property.elNumberZ) { + property.elNumberZ.updateMinMax(min, max); + } +} + +function createColorProperty(property, elProperty) { + let propertyName = property.name; + let elementID = property.elementID; + let propertyData = property.data; + + elProperty.className += " rgb fstuple"; + + let elColorPicker = document.createElement('div'); + elColorPicker.className = "color-picker"; + elColorPicker.setAttribute("id", elementID); + + let elTuple = document.createElement('div'); + elTuple.className = "tuple"; + + elProperty.appendChild(elColorPicker); + elProperty.appendChild(elTuple); + + if (propertyData.min === undefined) { + propertyData.min = COLOR_MIN; + } + if (propertyData.max === undefined) { + propertyData.max = COLOR_MAX; + } + if (propertyData.step === undefined) { + propertyData.step = COLOR_STEP; + } + + let elNumberR = createTupleNumberInput(property, "red"); + let elNumberG = createTupleNumberInput(property, "green"); + let elNumberB = createTupleNumberInput(property, "blue"); + elTuple.appendChild(elNumberR.elDiv); + elTuple.appendChild(elNumberG.elDiv); + elTuple.appendChild(elNumberB.elDiv); + + let valueChangeFunction = createEmitColorPropertyUpdateFunction(property); + elNumberR.setValueChangeFunction(valueChangeFunction); + elNumberG.setValueChangeFunction(valueChangeFunction); + elNumberB.setValueChangeFunction(valueChangeFunction); + + let colorPickerID = "#" + elementID; + colorPickers[colorPickerID] = $(colorPickerID).colpick({ + colorScheme: 'dark', + layout: 'rgbhex', + color: '000000', + submit: false, // We don't want to have a submission button + onShow: function(colpick) { + // The original color preview within the picker needs to be updated on show because + // prior to the picker being shown we don't have access to the selections' starting color. + colorPickers[colorPickerID].colpickSetColor({ + "r": elNumberR.elInput.value, + "g": elNumberG.elInput.value, + "b": elNumberB.elInput.value + }); + + // Set the color picker active after setting the color, otherwise an update will be sent on open. + $(colorPickerID).attr('active', 'true'); + }, + onHide: function(colpick) { + $(colorPickerID).attr('active', 'false'); + }, + onChange: function(hsb, hex, rgb, el) { + $(el).css('background-color', '#' + hex); + if ($(colorPickerID).attr('active') === 'true') { + emitColorPropertyUpdate(propertyName, rgb.r, rgb.g, rgb.b); + } + } + }); + + let elResult = []; + elResult[COLOR_ELEMENTS.COLOR_PICKER] = elColorPicker; + elResult[COLOR_ELEMENTS.RED_NUMBER] = elNumberR; + elResult[COLOR_ELEMENTS.GREEN_NUMBER] = elNumberG; + elResult[COLOR_ELEMENTS.BLUE_NUMBER] = elNumberB; + return elResult; +} + +function createDropdownProperty(property, propertyID, elProperty) { + let elementID = property.elementID; + let propertyData = property.data; + + elProperty.className = "dropdown"; + + let elInput = document.createElement('select'); + elInput.setAttribute("id", elementID); + elInput.setAttribute("propertyID", propertyID); + + for (let optionKey in propertyData.options) { + let option = document.createElement('option'); + option.value = optionKey; + option.text = propertyData.options[optionKey]; + elInput.add(option); + } + + elInput.addEventListener('change', createEmitTextPropertyUpdateFunction(property)); + + elProperty.appendChild(elInput); + + return elInput; +} + +function createTextareaProperty(property, elProperty) { + let elementID = property.elementID; + let propertyData = property.data; + + elProperty.className = "textarea"; + + let elInput = document.createElement('textarea'); + elInput.setAttribute("id", elementID); + if (propertyData.readOnly) { + elInput.readOnly = true; + } + + elInput.addEventListener('change', createEmitTextPropertyUpdateFunction(property)); + + let elMultiDiff = document.createElement('span'); + elMultiDiff.className = "multi-diff"; + + elProperty.appendChild(elInput); + elProperty.appendChild(elMultiDiff); + + if (propertyData.buttons !== undefined) { + addButtons(elProperty, elementID, propertyData.buttons, true); + } + + return elInput; +} + +function createIconProperty(property, elProperty) { + let elementID = property.elementID; + + elProperty.className = "value"; + + let elSpan = document.createElement('span'); + elSpan.setAttribute("id", elementID + "-icon"); + elSpan.className = 'icon'; + + elProperty.appendChild(elSpan); + + return elSpan; +} + +function createTextureProperty(property, elProperty) { + let elementID = property.elementID; + + elProperty.className = "texture"; + + let elDiv = document.createElement("div"); + let elImage = document.createElement("img"); + elDiv.className = "texture-image no-texture"; + elDiv.appendChild(elImage); + + let elInput = document.createElement('input'); + elInput.setAttribute("id", elementID); + elInput.setAttribute("type", "text"); + + let imageLoad = function(url) { + elDiv.style.display = null; + if (url.slice(0, 5).toLowerCase() === "atp:/") { + elImage.src = ""; + elImage.style.display = "none"; + elDiv.classList.remove("with-texture"); + elDiv.classList.remove("no-texture"); + elDiv.classList.add("no-preview"); + } else if (url.length > 0) { + elDiv.classList.remove("no-texture"); + elDiv.classList.remove("no-preview"); + elDiv.classList.add("with-texture"); + elImage.src = url; + elImage.style.display = "block"; + } else { + elImage.src = ""; + elImage.style.display = "none"; + elDiv.classList.remove("with-texture"); + elDiv.classList.remove("no-preview"); + elDiv.classList.add("no-texture"); + } + }; + elInput.imageLoad = imageLoad; + elInput.setMultipleValues = function() { + elDiv.style.display = "none"; + }; + elInput.addEventListener('change', createEmitTextPropertyUpdateFunction(property)); + elInput.addEventListener('change', function(ev) { + imageLoad(ev.target.value); + }); + + elProperty.appendChild(elInput); + let elMultiDiff = document.createElement('span'); + elMultiDiff.className = "multi-diff"; + elProperty.appendChild(elMultiDiff); + elProperty.appendChild(elDiv); + + let elResult = []; + elResult[TEXTURE_ELEMENTS.IMAGE] = elImage; + elResult[TEXTURE_ELEMENTS.TEXT_INPUT] = elInput; + return elResult; +} + +function createButtonsProperty(property, elProperty) { + let elementID = property.elementID; + let propertyData = property.data; + + elProperty.className = "text"; + + if (propertyData.buttons !== undefined) { + addButtons(elProperty, elementID, propertyData.buttons, false); + } + + return elProperty; +} + +function createDynamicMultiselectProperty(property, elProperty) { + let elementID = property.elementID; + let propertyData = property.data; + + elProperty.className = "dynamic-multiselect"; + + let elDivOptions = document.createElement('div'); + elDivOptions.setAttribute("id", elementID + "-options"); + elDivOptions.style = "overflow-y:scroll;max-height:160px;"; + + let elDivButtons = document.createElement('div'); + elDivButtons.setAttribute("id", elDivOptions.getAttribute("id") + "-buttons"); + + let elLabel = document.createElement('label'); + elLabel.innerText = "No Options"; + elDivOptions.appendChild(elLabel); + + let buttons = [ { id: "selectAll", label: "Select All", className: "black", onClick: selectAllMaterialTarget }, + { id: "clearAll", label: "Clear All", className: "black", onClick: clearAllMaterialTarget } ]; + addButtons(elDivButtons, elementID, buttons, false); + + elProperty.appendChild(elDivOptions); + elProperty.appendChild(elDivButtons); + + return elDivOptions; +} + +function resetDynamicMultiselectProperty(elDivOptions) { + let elInputs = elDivOptions.getElementsByTagName("input"); + while (elInputs.length > 0) { + let elDivOption = elInputs[0].parentNode; + elDivOption.parentNode.removeChild(elDivOption); + } + elDivOptions.firstChild.style.display = null; // show "No Options" text + elDivOptions.parentNode.lastChild.style.display = "none"; // hide Select/Clear all buttons +} + +function createTupleNumberInput(property, subLabel) { + let propertyElementID = property.elementID; + let propertyData = property.data; + let elementID = propertyElementID + "-" + subLabel.toLowerCase(); + + let elLabel = document.createElement('label'); + elLabel.className = "sublabel " + subLabel; + elLabel.innerText = subLabel[0].toUpperCase() + subLabel.slice(1); + elLabel.setAttribute("for", elementID); + elLabel.style.visibility = "visible"; + + let dragStartFunction = createDragStartFunction(property); + let dragEndFunction = createDragEndFunction(property); + let elDraggableNumber = new DraggableNumber(propertyData.min, propertyData.max, propertyData.step, + propertyData.decimals, dragStartFunction, dragEndFunction); + elDraggableNumber.elInput.setAttribute("id", elementID); + elDraggableNumber.elDiv.className += " fstuple"; + elDraggableNumber.elDiv.insertBefore(elLabel, elDraggableNumber.elLeftArrow); + + return elDraggableNumber; +} + +function addButtons(elProperty, propertyID, buttons, newRow) { + let elDiv = document.createElement('div'); + elDiv.className = "row"; + + buttons.forEach(function(button) { + let elButton = document.createElement('input'); + elButton.className = button.className; + elButton.setAttribute("type", "button"); + elButton.setAttribute("id", propertyID + "-button-" + button.id); + elButton.setAttribute("value", button.label); + elButton.addEventListener("click", button.onClick); + if (newRow) { + elDiv.appendChild(elButton); + } else { + elProperty.appendChild(elButton); + } + }); + + if (newRow) { + elProperty.appendChild(document.createElement('br')); + elProperty.appendChild(elDiv); + } +} + +function createProperty(propertyData, propertyElementID, propertyName, propertyID, elProperty) { + let property = { + data: propertyData, + elementID: propertyElementID, + name: propertyName, + elProperty: elProperty, + }; + let propertyType = propertyData.type; + + switch (propertyType) { + case 'string': { + property.elInput = createStringProperty(property, elProperty); + break; + } + case 'bool': { + property.elInput = createBoolProperty(property, elProperty); + break; + } + case 'number': { + property.elInput = createNumberProperty(property, elProperty); + break; + } + case 'number-draggable': { + property.elNumber = createNumberDraggableProperty(property, elProperty); + break; + } + case 'rect': { + let elRect = createRectProperty(property, elProperty); + property.elNumberX = elRect[RECT_ELEMENTS.X_NUMBER]; + property.elNumberY = elRect[RECT_ELEMENTS.Y_NUMBER]; + property.elNumberWidth = elRect[RECT_ELEMENTS.WIDTH_NUMBER]; + property.elNumberHeight = elRect[RECT_ELEMENTS.HEIGHT_NUMBER]; + break; + } + case 'vec3': { + let elVec3 = createVec3Property(property, elProperty); + property.elNumberX = elVec3[VECTOR_ELEMENTS.X_NUMBER]; + property.elNumberY = elVec3[VECTOR_ELEMENTS.Y_NUMBER]; + property.elNumberZ = elVec3[VECTOR_ELEMENTS.Z_NUMBER]; + break; + } + case 'vec2': { + let elVec2 = createVec2Property(property, elProperty); + property.elNumberX = elVec2[VECTOR_ELEMENTS.X_NUMBER]; + property.elNumberY = elVec2[VECTOR_ELEMENTS.Y_NUMBER]; + break; + } + case 'color': { + let elColor = createColorProperty(property, elProperty); + property.elColorPicker = elColor[COLOR_ELEMENTS.COLOR_PICKER]; + property.elNumberR = elColor[COLOR_ELEMENTS.RED_NUMBER]; + property.elNumberG = elColor[COLOR_ELEMENTS.GREEN_NUMBER]; + property.elNumberB = elColor[COLOR_ELEMENTS.BLUE_NUMBER]; + break; + } + case 'dropdown': { + property.elInput = createDropdownProperty(property, propertyID, elProperty); + break; + } + case 'textarea': { + property.elInput = createTextareaProperty(property, elProperty); + break; + } + case 'icon': { + property.elSpan = createIconProperty(property, elProperty); + break; + } + case 'texture': { + let elTexture = createTextureProperty(property, elProperty); + property.elImage = elTexture[TEXTURE_ELEMENTS.IMAGE]; + property.elInput = elTexture[TEXTURE_ELEMENTS.TEXT_INPUT]; + break; + } + case 'buttons': { + property.elProperty = createButtonsProperty(property, elProperty); + break; + } + case 'dynamic-multiselect': { + property.elDivOptions = createDynamicMultiselectProperty(property, elProperty); + break; + } + case 'placeholder': + case 'sub-header': { + break; + } + default: { + console.log("EntityProperties - Unknown property type " + + propertyType + " set to property " + propertyID); + break; + } + } + + return property; +} + + +/** + * PROPERTY-SPECIFIC CALLBACKS + */ + +function parentIDChanged() { + if (currentSelections.length === 1 && currentSelections[0].properties.type === "Material") { + requestMaterialTarget(); + } +} + + +/** + * BUTTON CALLBACKS + */ + +function rescaleDimensions() { + EventBridge.emitWebEvent(JSON.stringify({ + type: "action", + action: "rescaleDimensions", + percentage: parseFloat(document.getElementById("property-scale").value) + })); +} + +function moveSelectionToGrid() { + EventBridge.emitWebEvent(JSON.stringify({ + type: "action", + action: "moveSelectionToGrid" + })); +} + +function moveAllToGrid() { + EventBridge.emitWebEvent(JSON.stringify({ + type: "action", + action: "moveAllToGrid" + })); +} + +function resetToNaturalDimensions() { + EventBridge.emitWebEvent(JSON.stringify({ + type: "action", + action: "resetToNaturalDimensions" + })); +} + +function reloadScripts() { + EventBridge.emitWebEvent(JSON.stringify({ + type: "action", + action: "reloadClientScripts" + })); +} + +function reloadServerScripts() { + // invalidate the current status (so that same-same updates can still be observed visually) + document.getElementById("property-serverScripts-status").innerText = PENDING_SCRIPT_STATUS; + EventBridge.emitWebEvent(JSON.stringify({ + type: "action", + action: "reloadServerScripts" + })); +} + +function copySkyboxURLToAmbientURL() { + let skyboxURL = getPropertyInputElement("skybox.url").value; + getPropertyInputElement("ambientLight.ambientURL").value = skyboxURL; + updateProperty("ambientLight.ambientURL", skyboxURL, false); +} + + +/** + * USER DATA FUNCTIONS + */ + +function clearUserData() { + let elUserData = getPropertyInputElement("userData"); + deleteJSONEditor(); + elUserData.value = ""; + showUserDataTextArea(); + showNewJSONEditorButton(); + hideSaveUserDataButton(); + updateProperty('userData', elUserData.value, false); +} + +function newJSONEditor() { + getPropertyInputElement("userData").classList.remove('multi-diff'); + deleteJSONEditor(); + createJSONEditor(); + let data = {}; + setEditorJSON(data); + hideUserDataTextArea(); + hideNewJSONEditorButton(); + showSaveUserDataButton(); +} + +/** + * @param {Set.} [entityIDsToUpdate] Entity IDs to update userData for. + */ +function saveUserData(entityIDsToUpdate) { + saveJSONUserData(true, entityIDsToUpdate); +} + +function setJSONError(property, isError) { + $("#property-"+ property + "-editor").toggleClass('error', isError); + let $propertyUserDataEditorStatus = $("#property-"+ property + "-editorStatus"); + $propertyUserDataEditorStatus.css('display', isError ? 'block' : 'none'); + $propertyUserDataEditorStatus.text(isError ? 'Invalid JSON code - look for red X in your code' : ''); +} + +/** + * @param {boolean} noUpdate - don't update the UI, but do send a property update. + * @param {Set.} [entityIDsToUpdate] - Entity IDs to update userData for. + */ +function setUserDataFromEditor(noUpdate, entityIDsToUpdate) { + let errorFound = false; + try { + editor.get(); + } catch (e) { + errorFound = true; + } + + setJSONError('userData', errorFound); + + if (errorFound) { + return; + } + + let text = editor.getText(); + if (noUpdate) { + EventBridge.emitWebEvent( + JSON.stringify({ + ids: [...entityIDsToUpdate], + type: "saveUserData", + properties: { + userData: text + } + }) + ); + } else { + updateProperty('userData', text, false); + } +} + +let editor = null; + +function createJSONEditor() { + let container = document.getElementById("property-userData-editor"); + let options = { + search: false, + mode: 'tree', + modes: ['code', 'tree'], + name: 'userData', + onError: function(e) { + alert('JSON editor:' + e); + }, + onChange: function() { + let currentJSONString = editor.getText(); + + if (currentJSONString === '{"":""}') { + return; + } + $('#property-userData-button-save').attr('disabled', false); + } + }; + editor = new JSONEditor(container, options); +} + +function showSaveUserDataButton() { + $('#property-userData-button-save').show(); +} + +function hideSaveUserDataButton() { + $('#property-userData-button-save').hide(); +} + +function disableSaveUserDataButton() { + $('#property-userData-button-save').attr('disabled', true); +} + +function showNewJSONEditorButton() { + $('#property-userData-button-edit').show(); +} + +function hideNewJSONEditorButton() { + $('#property-userData-button-edit').hide(); +} + +function showUserDataTextArea() { + $('#property-userData').show(); +} + +function hideUserDataTextArea() { + $('#property-userData').hide(); +} + +function hideUserDataSaved() { + $('#property-userData-saved').hide(); +} + +function showStaticUserData() { + if (editor !== null) { + let $propertyUserDataStatic = $('#property-userData-static'); + $propertyUserDataStatic.show(); + $propertyUserDataStatic.css('height', $('#property-userData-editor').height()); + $propertyUserDataStatic.text(editor.getText()); + } +} + +function removeStaticUserData() { + $('#property-userData-static').hide(); +} + +function setEditorJSON(json) { + editor.set(json); + if (editor.hasOwnProperty('expandAll')) { + editor.expandAll(); + } +} + +function deleteJSONEditor() { + if (editor !== null) { + setJSONError('userData', false); + editor.destroy(); + editor = null; + } +} + +let savedJSONTimer = null; + +/** + * @param {boolean} noUpdate - don't update the UI, but do send a property update. + * @param {Set.} [entityIDsToUpdate] Entity IDs to update userData for + */ +function saveJSONUserData(noUpdate, entityIDsToUpdate) { + setUserDataFromEditor(noUpdate, entityIDsToUpdate ? entityIDsToUpdate : selectedEntityIDs); + $('#property-userData-saved').show(); + $('#property-userData-button-save').attr('disabled', true); + if (savedJSONTimer !== null) { + clearTimeout(savedJSONTimer); + } + savedJSONTimer = setTimeout(function() { + hideUserDataSaved(); + }, EDITOR_TIMEOUT_DURATION); +} + + +/** + * MATERIAL DATA FUNCTIONS + */ + +function clearMaterialData() { + let elMaterialData = getPropertyInputElement("materialData"); + deleteJSONMaterialEditor(); + elMaterialData.value = ""; + showMaterialDataTextArea(); + showNewJSONMaterialEditorButton(); + hideSaveMaterialDataButton(); + updateProperty('materialData', elMaterialData.value, false); +} + +function newJSONMaterialEditor() { + getPropertyInputElement("materialData").classList.remove('multi-diff'); + deleteJSONMaterialEditor(); + createJSONMaterialEditor(); + let data = {}; + setMaterialEditorJSON(data); + hideMaterialDataTextArea(); + hideNewJSONMaterialEditorButton(); + showSaveMaterialDataButton(); +} + +function saveMaterialData() { + saveJSONMaterialData(true); +} + +/** + * @param {boolean} noUpdate - don't update the UI, but do send a property update. + * @param {Set.} [entityIDsToUpdate] - Entity IDs to update materialData for. + */ +function setMaterialDataFromEditor(noUpdate, entityIDsToUpdate) { + let errorFound = false; + try { + materialEditor.get(); + } catch (e) { + errorFound = true; + } + + setJSONError('materialData', errorFound); + + if (errorFound) { + return; + } + let text = materialEditor.getText(); + if (noUpdate) { + EventBridge.emitWebEvent( + JSON.stringify({ + ids: [...entityIDsToUpdate], + type: "saveMaterialData", + properties: { + materialData: text + } + }) + ); + } else { + updateProperty('materialData', text, false); + } +} + +let materialEditor = null; + +function createJSONMaterialEditor() { + let container = document.getElementById("property-materialData-editor"); + let options = { + search: false, + mode: 'tree', + modes: ['code', 'tree'], + name: 'materialData', + onError: function(e) { + alert('JSON editor:' + e); + }, + onChange: function() { + let currentJSONString = materialEditor.getText(); + + if (currentJSONString === '{"":""}') { + return; + } + $('#property-materialData-button-save').attr('disabled', false); + } + }; + materialEditor = new JSONEditor(container, options); +} + +function showSaveMaterialDataButton() { + $('#property-materialData-button-save').show(); +} + +function hideSaveMaterialDataButton() { + $('#property-materialData-button-save').hide(); +} + +function disableSaveMaterialDataButton() { + $('#property-materialData-button-save').attr('disabled', true); +} + +function showNewJSONMaterialEditorButton() { + $('#property-materialData-button-edit').show(); +} + +function hideNewJSONMaterialEditorButton() { + $('#property-materialData-button-edit').hide(); +} + +function showMaterialDataTextArea() { + $('#property-materialData').show(); +} + +function hideMaterialDataTextArea() { + $('#property-materialData').hide(); +} + +function hideMaterialDataSaved() { + $('#property-materialData-saved').hide(); +} + +function showStaticMaterialData() { + if (materialEditor !== null) { + let $propertyMaterialDataStatic = $('#property-materialData-static'); + $propertyMaterialDataStatic.show(); + $propertyMaterialDataStatic.css('height', $('#property-materialData-editor').height()); + $propertyMaterialDataStatic.text(materialEditor.getText()); + } +} + +function removeStaticMaterialData() { + $('#property-materialData-static').hide(); +} + +function setMaterialEditorJSON(json) { + materialEditor.set(json); + if (materialEditor.hasOwnProperty('expandAll')) { + materialEditor.expandAll(); + } +} + +function deleteJSONMaterialEditor() { + if (materialEditor !== null) { + setJSONError('materialData', false); + materialEditor.destroy(); + materialEditor = null; + } +} + +let savedMaterialJSONTimer = null; + +/** + * @param {boolean} noUpdate - don't update the UI, but do send a property update. + * @param {Set.} [entityIDsToUpdate] - Entity IDs to update materialData for. + */ +function saveJSONMaterialData(noUpdate, entityIDsToUpdate) { + setMaterialDataFromEditor(noUpdate, entityIDsToUpdate ? entityIDsToUpdate : selectedEntityIDs); + $('#property-materialData-saved').show(); + $('#property-materialData-button-save').attr('disabled', true); + if (savedMaterialJSONTimer !== null) { + clearTimeout(savedMaterialJSONTimer); + } + savedMaterialJSONTimer = setTimeout(function() { + hideMaterialDataSaved(); + }, EDITOR_TIMEOUT_DURATION); +} + +function bindAllNonJSONEditorElements() { + let inputs = $('input'); + let i; + for (i = 0; i < inputs.length; ++i) { + let input = inputs[i]; + let field = $(input); + // TODO FIXME: (JSHint) Functions declared within loops referencing + // an outer scoped variable may lead to confusing semantics. + field.on('focus', function(e) { + if (e.target.id === "property-userData-button-edit" || e.target.id === "property-userData-button-clear" || + e.target.id === "property-materialData-button-edit" || e.target.id === "property-materialData-button-clear") { + return; + } + if ($('#property-userData-editor').css('height') !== "0px") { + saveUserData(); + } + if ($('#property-materialData-editor').css('height') !== "0px") { + saveMaterialData(); + } + }); + } +} + + +/** + * DROPDOWN FUNCTIONS + */ + +function setDropdownText(dropdown) { + let lis = dropdown.parentNode.getElementsByTagName("li"); + let text = ""; + for (let i = 0; i < lis.length; ++i) { + if (String(lis[i].getAttribute("value")) === String(dropdown.value)) { + text = lis[i].textContent; + } + } + dropdown.firstChild.textContent = text; +} + +function toggleDropdown(event) { + let element = event.target; + if (element.nodeName !== "DT") { + element = element.parentNode; + } + element = element.parentNode; + let isDropped = element.getAttribute("dropped"); + element.setAttribute("dropped", isDropped !== "true" ? "true" : "false"); +} + +function closeAllDropdowns() { + let elDropdowns = document.querySelectorAll("div.dropdown > dl"); + for (let i = 0; i < elDropdowns.length; ++i) { + elDropdowns[i].setAttribute('dropped', 'false'); + } +} + +function setDropdownValue(event) { + let dt = event.target.parentNode.parentNode.previousSibling.previousSibling; + dt.value = event.target.getAttribute("value"); + dt.firstChild.textContent = event.target.textContent; + + dt.parentNode.setAttribute("dropped", "false"); + + let evt = document.createEvent("HTMLEvents"); + evt.initEvent("change", true, true); + dt.dispatchEvent(evt); +} + + +/** + * TEXTAREA FUNCTIONS + */ + +function setTextareaScrolling(element) { + let isScrolling = element.scrollHeight > element.offsetHeight; + element.setAttribute("scrolling", isScrolling ? "true" : "false"); +} + + +/** + * MATERIAL TARGET FUNCTIONS + */ + +function requestMaterialTarget() { + EventBridge.emitWebEvent(JSON.stringify({ + type: 'materialTargetRequest', + entityID: getFirstSelectedID(), + })); +} + +function setMaterialTargetData(materialTargetData) { + let elDivOptions = getPropertyInputElement("parentMaterialName"); + resetDynamicMultiselectProperty(elDivOptions); + + if (materialTargetData === undefined) { + return; + } + + elDivOptions.firstChild.style.display = "none"; // hide "No Options" text + elDivOptions.parentNode.lastChild.style.display = null; // show Select/Clear all buttons + + let numMeshes = materialTargetData.numMeshes; + for (let i = 0; i < numMeshes; ++i) { + addMaterialTarget(elDivOptions, i, false); + } + + let materialNames = materialTargetData.materialNames; + let materialNamesAdded = []; + for (let i = 0; i < materialNames.length; ++i) { + let materialName = materialNames[i]; + if (materialNamesAdded.indexOf(materialName) === -1) { + addMaterialTarget(elDivOptions, materialName, true); + materialNamesAdded.push(materialName); + } + } + + materialTargetPropertyUpdate(elDivOptions.propertyValue); +} + +function addMaterialTarget(elDivOptions, targetID, isMaterialName) { + let elementID = elDivOptions.getAttribute("id"); + elementID += isMaterialName ? "-material-" : "-mesh-"; + elementID += targetID; + + let elDiv = document.createElement('div'); + elDiv.className = "materialTargetDiv"; + elDiv.onclick = onToggleMaterialTarget; + elDivOptions.appendChild(elDiv); + + let elInput = document.createElement('input'); + elInput.className = "materialTargetInput"; + elInput.setAttribute("type", "checkbox"); + elInput.setAttribute("id", elementID); + elInput.setAttribute("targetID", targetID); + elInput.setAttribute("isMaterialName", isMaterialName); + elDiv.appendChild(elInput); + + let elLabel = document.createElement('label'); + elLabel.setAttribute("for", elementID); + elLabel.innerText = isMaterialName ? "Material " + targetID : "Mesh Index " + targetID; + elDiv.appendChild(elLabel); + + return elDiv; +} + +function onToggleMaterialTarget(event) { + let elTarget = event.target; + if (elTarget instanceof HTMLInputElement) { + sendMaterialTargetProperty(); + } + event.stopPropagation(); +} + +function setAllMaterialTargetInputs(checked) { + let elDivOptions = getPropertyInputElement("parentMaterialName"); + let elInputs = elDivOptions.getElementsByClassName("materialTargetInput"); + for (let i = 0; i < elInputs.length; ++i) { + elInputs[i].checked = checked; + } +} + +function selectAllMaterialTarget() { + setAllMaterialTargetInputs(true); + sendMaterialTargetProperty(); +} + +function clearAllMaterialTarget() { + setAllMaterialTargetInputs(false); + sendMaterialTargetProperty(); +} + +function sendMaterialTargetProperty() { + let elDivOptions = getPropertyInputElement("parentMaterialName"); + let elInputs = elDivOptions.getElementsByClassName("materialTargetInput"); + + let materialTargetList = []; + for (let i = 0; i < elInputs.length; ++i) { + let elInput = elInputs[i]; + if (elInput.checked) { + let targetID = elInput.getAttribute("targetID"); + if (elInput.getAttribute("isMaterialName") === "true") { + materialTargetList.push(MATERIAL_PREFIX_STRING + targetID); + } else { + materialTargetList.push(targetID); + } + } + } + + let propertyValue = materialTargetList.join(","); + if (propertyValue.length > 1) { + propertyValue = "[" + propertyValue + "]"; + } + + updateProperty("parentMaterialName", propertyValue, false); +} + +function materialTargetPropertyUpdate(propertyValue) { + let elDivOptions = getPropertyInputElement("parentMaterialName"); + let elInputs = elDivOptions.getElementsByClassName("materialTargetInput"); + + if (propertyValue.startsWith('[')) { + propertyValue = propertyValue.substring(1, propertyValue.length); + } + if (propertyValue.endsWith(']')) { + propertyValue = propertyValue.substring(0, propertyValue.length - 1); + } + + let materialTargets = propertyValue.split(","); + for (let i = 0; i < elInputs.length; ++i) { + let elInput = elInputs[i]; + let targetID = elInput.getAttribute("targetID"); + let materialTargetName = targetID; + if (elInput.getAttribute("isMaterialName") === "true") { + materialTargetName = MATERIAL_PREFIX_STRING + targetID; + } + elInput.checked = materialTargets.indexOf(materialTargetName) >= 0; + } + + elDivOptions.propertyValue = propertyValue; +} + +function roundAndFixNumber(number, propertyData) { + let result = number; + if (propertyData.round !== undefined) { + result = Math.round(result * propertyData.round) / propertyData.round; + } + if (propertyData.decimals !== undefined) { + return result.toFixed(propertyData.decimals) + } + return result; +} + +function applyInputNumberPropertyModifiers(number, propertyData) { + const multiplier = propertyData.multiplier !== undefined ? propertyData.multiplier : 1; + return roundAndFixNumber(number / multiplier, propertyData); +} + +function applyOutputNumberPropertyModifiers(number, propertyData) { + const multiplier = propertyData.multiplier !== undefined ? propertyData.multiplier : 1; + return roundAndFixNumber(number * multiplier, propertyData); +} + +const areSetsEqual = (a, b) => a.size === b.size && [...a].every(value => b.has(value)); + + +function handleEntitySelectionUpdate(selections, isPropertiesToolUpdate) { + const previouslySelectedEntityIDs = selectedEntityIDs; + currentSelections = selections; + selectedEntityIDs = new Set(selections.map(selection => selection.id)); + const multipleSelections = currentSelections.length > 1; + const hasSelectedEntityChanged = !areSetsEqual(selectedEntityIDs, previouslySelectedEntityIDs); + + if (selections.length === 0) { + deleteJSONEditor(); + deleteJSONMaterialEditor(); + + resetProperties(); + showGroupsForType("None"); + + let elIcon = properties.type.elSpan; + elIcon.innerText = NO_SELECTION; + elIcon.style.display = 'inline-block'; + + getPropertyInputElement("userData").value = ""; + showUserDataTextArea(); + showSaveUserDataButton(); + showNewJSONEditorButton(); + + getPropertyInputElement("materialData").value = ""; + showMaterialDataTextArea(); + showSaveMaterialDataButton(); + showNewJSONMaterialEditorButton(); + + disableProperties(); + } else { + if (!isPropertiesToolUpdate && !hasSelectedEntityChanged && document.hasFocus()) { + // in case the selection has not changed and we still have focus on the properties page, + // we will ignore the event. + return; + } + + if (hasSelectedEntityChanged) { + if (!multipleSelections) { + resetServerScriptStatus(); + } + } + + const doSelectElement = !hasSelectedEntityChanged; + + // Get unique entity types, and convert the types Sphere and Box to Shape + const shapeTypes = ["Sphere", "Box"]; + const entityTypes = [...new Set(currentSelections.map(a => + shapeTypes.includes(a.properties.type) ? "Shape" : a.properties.type))]; + + const shownGroups = getGroupsForTypes(entityTypes); + showGroupsForTypes(entityTypes); + + const lockedMultiValue = getMultiplePropertyValue('locked'); + + if (lockedMultiValue.isMultiDiffValue || lockedMultiValue.value) { + disableProperties(); + getPropertyInputElement('locked').removeAttribute('disabled'); + } else { + enableProperties(); + disableSaveUserDataButton(); + disableSaveMaterialDataButton() + } + + const certificateIDMultiValue = getMultiplePropertyValue('certificateID'); + const hasCertifiedInSelection = certificateIDMultiValue.isMultiDiffValue || certificateIDMultiValue.value !== ""; + + Object.entries(properties).forEach(function([propertyID, property]) { + const propertyData = property.data; + const propertyName = property.name; + let propertyMultiValue = getMultiplePropertyValue(propertyName); + let isMultiDiffValue = propertyMultiValue.isMultiDiffValue; + let propertyValue = propertyMultiValue.value; + + if (propertyData.selectionVisibility !== undefined) { + let visibility = propertyData.selectionVisibility; + let propertyVisible = true; + if (!multipleSelections) { + propertyVisible = isFlagSet(visibility, PROPERTY_SELECTION_VISIBILITY.SINGLE_SELECTION); + } else if (isMultiDiffValue) { + propertyVisible = isFlagSet(visibility, PROPERTY_SELECTION_VISIBILITY.MULTI_DIFF_SELECTIONS); + } else { + propertyVisible = isFlagSet(visibility, PROPERTY_SELECTION_VISIBILITY.MULTIPLE_SELECTIONS); + } + setPropertyVisibility(property, propertyVisible); + } + + const isSubProperty = propertyData.subPropertyOf !== undefined; + if (propertyValue === undefined && !isMultiDiffValue && !isSubProperty) { + return; + } + + if (!shownGroups.includes(property.group_id)) { + const WANT_DEBUG_SHOW_HIDDEN_FROM_GROUPS = false; + if (WANT_DEBUG_SHOW_HIDDEN_FROM_GROUPS) { + console.log("Skipping property " + property.data.label + " [" + property.name + + "] from hidden group " + property.group_id); + } + return; + } + + if (propertyData.hideIfCertified && hasCertifiedInSelection) { + propertyValue = "** Certified **"; + property.elInput.disabled = true; + } + + if (propertyName === "type") { + propertyValue = entityTypes.length > 1 ? "Multiple" : propertyMultiValue.values[0]; + } + + switch (propertyData.type) { + case 'string': { + if (isMultiDiffValue) { + if (propertyData.readOnly && propertyData.multiDisplayMode + && propertyData.multiDisplayMode === PROPERTY_MULTI_DISPLAY_MODE.COMMA_SEPARATED_VALUES) { + property.elInput.value = propertyMultiValue.values.join(", "); + } else { + property.elInput.classList.add('multi-diff'); + property.elInput.value = ""; + } + } else { + property.elInput.classList.remove('multi-diff'); + property.elInput.value = propertyValue; + } + break; + } + case 'bool': { + const inverse = propertyData.inverse !== undefined ? propertyData.inverse : false; + if (isSubProperty) { + let subPropertyMultiValue = getMultiplePropertyValue(propertyData.subPropertyOf); + let propertyValue = subPropertyMultiValue.value; + isMultiDiffValue = subPropertyMultiValue.isMultiDiffValue; + if (isMultiDiffValue) { + let detailedSubProperty = getDetailedSubPropertyMPVDiff(subPropertyMultiValue, propertyName); + property.elInput.checked = detailedSubProperty.isChecked; + property.elInput.classList.toggle('multi-diff', detailedSubProperty.isMultiDiff); + } else { + let subProperties = propertyValue.split(","); + let subPropertyValue = subProperties.indexOf(propertyName) > -1; + property.elInput.checked = inverse ? !subPropertyValue : subPropertyValue; + property.elInput.classList.remove('multi-diff'); + } + + } else { + if (isMultiDiffValue) { + property.elInput.checked = false; + } else { + property.elInput.checked = inverse ? !propertyValue : propertyValue; + } + property.elInput.classList.toggle('multi-diff', isMultiDiffValue); + } + + break; + } + case 'number': { + property.elInput.value = isMultiDiffValue ? "" : propertyValue; + property.elInput.classList.toggle('multi-diff', isMultiDiffValue); + break; + } + case 'number-draggable': { + let detailedNumberDiff = getDetailedNumberMPVDiff(propertyMultiValue, propertyData); + property.elNumber.setValue(detailedNumberDiff.averagePerPropertyComponent[0], detailedNumberDiff.propertyComponentDiff[0]); + break; + } + case 'rect': { + let detailedNumberDiff = getDetailedNumberMPVDiff(propertyMultiValue, propertyData); + property.elNumberX.setValue(detailedNumberDiff.averagePerPropertyComponent.x, detailedNumberDiff.propertyComponentDiff.x); + property.elNumberY.setValue(detailedNumberDiff.averagePerPropertyComponent.y, detailedNumberDiff.propertyComponentDiff.y); + property.elNumberWidth.setValue(detailedNumberDiff.averagePerPropertyComponent.width, detailedNumberDiff.propertyComponentDiff.width); + property.elNumberHeight.setValue(detailedNumberDiff.averagePerPropertyComponent.height, detailedNumberDiff.propertyComponentDiff.height); + break; + } + case 'vec3': + case 'vec2': { + let detailedNumberDiff = getDetailedNumberMPVDiff(propertyMultiValue, propertyData); + property.elNumberX.setValue(detailedNumberDiff.averagePerPropertyComponent.x, detailedNumberDiff.propertyComponentDiff.x); + property.elNumberY.setValue(detailedNumberDiff.averagePerPropertyComponent.y, detailedNumberDiff.propertyComponentDiff.y); + if (property.elNumberZ !== undefined) { + property.elNumberZ.setValue(detailedNumberDiff.averagePerPropertyComponent.z, detailedNumberDiff.propertyComponentDiff.z); + } + break; + } + case 'color': { + let displayColor = propertyMultiValue.isMultiDiffValue ? propertyMultiValue.values[0] : propertyValue; + property.elColorPicker.style.backgroundColor = "rgb(" + displayColor.red + "," + + displayColor.green + "," + + displayColor.blue + ")"; + property.elColorPicker.classList.toggle('multi-diff', propertyMultiValue.isMultiDiffValue); + + if (hasSelectedEntityChanged && $(property.elColorPicker).attr('active') === 'true') { + // Set the color picker inactive before setting the color, + // otherwise an update will be sent directly after setting it here. + $(property.elColorPicker).attr('active', 'false'); + colorPickers['#' + property.elementID].colpickSetColor({ + "r": displayColor.red, + "g": displayColor.green, + "b": displayColor.blue + }); + $(property.elColorPicker).attr('active', 'true'); + } + + property.elNumberR.setValue(displayColor.red); + property.elNumberG.setValue(displayColor.green); + property.elNumberB.setValue(displayColor.blue); + break; + } + case 'dropdown': { + property.elInput.classList.toggle('multi-diff', isMultiDiffValue); + property.elInput.value = isMultiDiffValue ? "" : propertyValue; + setDropdownText(property.elInput); + break; + } + case 'textarea': { + property.elInput.value = propertyValue; + setTextareaScrolling(property.elInput); + break; + } + case 'icon': { + property.elSpan.innerHTML = propertyData.icons[propertyValue]; + property.elSpan.style.display = "inline-block"; + break; + } + case 'texture': { + property.elInput.value = isMultiDiffValue ? "" : propertyValue; + property.elInput.classList.toggle('multi-diff', isMultiDiffValue); + if (isMultiDiffValue) { + property.elInput.setMultipleValues(); + } else { + property.elInput.imageLoad(property.elInput.value); + } + break; + } + case 'dynamic-multiselect': { + if (!isMultiDiffValue && property.data.propertyUpdate) { + property.data.propertyUpdate(propertyValue); + } + break; + } + } + + let showPropertyRules = property.showPropertyRules; + if (showPropertyRules !== undefined) { + for (let propertyToShow in showPropertyRules) { + let showIfThisPropertyValue = showPropertyRules[propertyToShow]; + let show = String(propertyValue) === String(showIfThisPropertyValue); + showPropertyElement(propertyToShow, show); + } + } + }); + + updateVisibleSpaceModeProperties(); + + let userDataMultiValue = getMultiplePropertyValue("userData"); + let userDataTextArea = getPropertyInputElement("userData"); + let json = null; + if (!userDataMultiValue.isMultiDiffValue) { + try { + json = JSON.parse(userDataMultiValue.value); + } catch (e) { + + } + } + if (json !== null) { + if (editor === null) { + createJSONEditor(); + } + userDataTextArea.classList.remove('multi-diff'); + setEditorJSON(json); + showSaveUserDataButton(); + hideUserDataTextArea(); + hideNewJSONEditorButton(); + hideUserDataSaved(); + } else { + // normal text + deleteJSONEditor(); + userDataTextArea.classList.toggle('multi-diff', userDataMultiValue.isMultiDiffValue); + userDataTextArea.value = userDataMultiValue.isMultiDiffValue ? "" : userDataMultiValue.value; + + showUserDataTextArea(); + showNewJSONEditorButton(); + hideSaveUserDataButton(); + hideUserDataSaved(); + } + + let materialDataMultiValue = getMultiplePropertyValue("materialData"); + let materialDataTextArea = getPropertyInputElement("materialData"); + let materialJson = null; + if (!materialDataMultiValue.isMultiDiffValue) { + try { + materialJson = JSON.parse(materialDataMultiValue.value); + } catch (e) { + + } + } + if (materialJson !== null) { + if (materialEditor === null) { + createJSONMaterialEditor(); + } + materialDataTextArea.classList.remove('multi-diff'); + setMaterialEditorJSON(materialJson); + showSaveMaterialDataButton(); + hideMaterialDataTextArea(); + hideNewJSONMaterialEditorButton(); + hideMaterialDataSaved(); + } else { + // normal text + deleteJSONMaterialEditor(); + materialDataTextArea.classList.toggle('multi-diff', materialDataMultiValue.isMultiDiffValue); + materialDataTextArea.value = materialDataMultiValue.isMultiDiffValue ? "" : materialDataMultiValue.value; + showMaterialDataTextArea(); + showNewJSONMaterialEditorButton(); + hideSaveMaterialDataButton(); + hideMaterialDataSaved(); + } + + if (hasSelectedEntityChanged && selections.length === 1 && entityTypes[0] === "Material") { + requestMaterialTarget(); + } + + let activeElement = document.activeElement; + if (doSelectElement && typeof activeElement.select !== "undefined") { + activeElement.select(); + } + } +} + +function loaded() { + openEventBridge(function() { + let elPropertiesList = document.getElementById("properties-list"); + + GROUPS.forEach(function(group) { + let elGroup; + if (group.addToGroup !== undefined) { + let fieldset = document.getElementById("properties-" + group.addToGroup); + elGroup = document.createElement('div'); + fieldset.appendChild(elGroup); + } else { + elGroup = document.createElement('div'); + elGroup.className = 'section ' + (group.isMinor ? "minor" : "major"); + elGroup.setAttribute("id", "properties-" + group.id); + elPropertiesList.appendChild(elGroup); + } + + if (group.label !== undefined) { + let elLegend = document.createElement('div'); + elLegend.className = "section-header"; + + elLegend.appendChild(createElementFromHTML(`
${group.label}
`)); + + let elSpan = document.createElement('span'); + elSpan.className = "collapse-icon"; + elSpan.innerText = "M"; + elLegend.appendChild(elSpan); + elGroup.appendChild(elLegend); + } + + group.properties.forEach(function(propertyData) { + let propertyType = propertyData.type; + let propertyID = propertyData.propertyID; + let propertyName = propertyData.propertyName !== undefined ? propertyData.propertyName : propertyID; + let propertySpaceMode = propertyData.spaceMode !== undefined ? propertyData.spaceMode : PROPERTY_SPACE_MODE.ALL; + let propertyElementID = "property-" + propertyID; + propertyElementID = propertyElementID.replace('.', '-'); + + let elContainer, elLabel; + + if (propertyData.replaceID === undefined) { + // Create subheader, or create new property and append it. + if (propertyType === "sub-header") { + elContainer = createElementFromHTML( + `
${propertyData.label}
`); + } else { + elContainer = document.createElement('div'); + elContainer.setAttribute("id", "div-" + propertyElementID); + elContainer.className = 'property container'; + } + + if (group.twoColumn && propertyData.column !== undefined && propertyData.column !== -1) { + let columnName = group.id + "column" + propertyData.column; + let elColumn = document.getElementById(columnName); + if (!elColumn) { + let columnDivName = group.id + "columnDiv"; + let elColumnDiv = document.getElementById(columnDivName); + if (!elColumnDiv) { + elColumnDiv = document.createElement('div'); + elColumnDiv.className = "two-column"; + elColumnDiv.setAttribute("id", group.id + "columnDiv"); + elGroup.appendChild(elColumnDiv); + } + elColumn = document.createElement('fieldset'); + elColumn.className = "column"; + elColumn.setAttribute("id", columnName); + elColumnDiv.appendChild(elColumn); + } + elColumn.appendChild(elContainer); + } else { + elGroup.appendChild(elContainer); + } + + let labelText = propertyData.label !== undefined ? propertyData.label : ""; + let className = ''; + if (propertyData.indentedLabel || propertyData.showPropertyRule !== undefined) { + className = 'indented'; + } + elLabel = createElementFromHTML( + ``); + elContainer.appendChild(elLabel); + } else { + elContainer = document.getElementById(propertyData.replaceID); + } + + if (elLabel) { + createAppTooltip.registerTooltipElement(elLabel.childNodes[0], propertyID, propertyName); + } + + let elProperty = createElementFromHTML('
'); + elContainer.appendChild(elProperty); + + if (propertyType === 'triple') { + elProperty.className = 'flex-row'; + for (let i = 0; i < propertyData.properties.length; ++i) { + let innerPropertyData = propertyData.properties[i]; + + let elWrapper = createElementFromHTML('
'); + elProperty.appendChild(elWrapper); + + let propertyID = innerPropertyData.propertyID; + let propertyName = innerPropertyData.propertyName !== undefined ? innerPropertyData.propertyName : propertyID; + let propertyElementID = "property-" + propertyID; + propertyElementID = propertyElementID.replace('.', '-'); + + let property = createProperty(innerPropertyData, propertyElementID, propertyName, propertyID, elWrapper); + property.isParticleProperty = group.id.includes("particles"); + property.elContainer = elContainer; + property.spaceMode = propertySpaceMode; + property.group_id = group.id; + + let elLabel = createElementFromHTML(`
${innerPropertyData.label}
`); + createAppTooltip.registerTooltipElement(elLabel, propertyID, propertyName); + + elWrapper.appendChild(elLabel); + + if (property.type !== 'placeholder') { + properties[propertyID] = property; + } + if (innerPropertyData.type === 'number' || innerPropertyData.type === 'number-draggable') { + propertyRangeRequests.push(propertyID); + } + } + } else { + let property = createProperty(propertyData, propertyElementID, propertyName, propertyID, elProperty); + property.isParticleProperty = group.id.includes("particles"); + property.elContainer = elContainer; + property.spaceMode = propertySpaceMode; + property.group_id = group.id; + + if (property.type !== 'placeholder') { + properties[propertyID] = property; + } + if (propertyData.type === 'number' || propertyData.type === 'number-draggable' || + propertyData.type === 'vec2' || propertyData.type === 'vec3' || propertyData.type === 'rect') { + propertyRangeRequests.push(propertyID); + } + + let showPropertyRule = propertyData.showPropertyRule; + if (showPropertyRule !== undefined) { + let dependentProperty = Object.keys(showPropertyRule)[0]; + let dependentPropertyValue = showPropertyRule[dependentProperty]; + if (properties[dependentProperty] === undefined) { + properties[dependentProperty] = {}; + } + if (properties[dependentProperty].showPropertyRules === undefined) { + properties[dependentProperty].showPropertyRules = {}; + } + properties[dependentProperty].showPropertyRules[propertyID] = dependentPropertyValue; + } + } + }); + + elGroups[group.id] = elGroup; + }); + + let minorSections = document.querySelectorAll(".section.minor"); + minorSections[minorSections.length - 1].className += " last"; + + updateVisibleSpaceModeProperties(); + + if (window.EventBridge !== undefined) { + EventBridge.scriptEventReceived.connect(function(data) { + data = JSON.parse(data); + if (data.type === "server_script_status" && selectedEntityIDs.size === 1) { + let elServerScriptError = document.getElementById("property-serverScripts-error"); + let elServerScriptStatus = document.getElementById("property-serverScripts-status"); + elServerScriptError.value = data.errorInfo; + // If we just set elServerScriptError's display to block or none, we still end up with + // it's parent contributing 21px bottom padding even when elServerScriptError is display:none. + // So set it's parent to block or none + elServerScriptError.parentElement.style.display = data.errorInfo ? "block" : "none"; + if (data.statusRetrieved === false) { + elServerScriptStatus.innerText = "Failed to retrieve status"; + } else if (data.isRunning) { + elServerScriptStatus.innerText = ENTITY_SCRIPT_STATUS[data.status] || data.status; + } else { + elServerScriptStatus.innerText = NOT_RUNNING_SCRIPT_STATUS; + } + } else if (data.type === "update" && data.selections) { + if (data.spaceMode !== undefined) { + currentSpaceMode = data.spaceMode === "local" ? PROPERTY_SPACE_MODE.LOCAL : PROPERTY_SPACE_MODE.WORLD; + } + handleEntitySelectionUpdate(data.selections, data.isPropertiesToolUpdate); + } else if (data.type === 'tooltipsReply') { + createAppTooltip.setIsEnabled(!data.hmdActive); + createAppTooltip.setTooltipData(data.tooltips); + } else if (data.type === 'hmdActiveChanged') { + createAppTooltip.setIsEnabled(!data.hmdActive); + } else if (data.type === 'setSpaceMode') { + currentSpaceMode = data.spaceMode === "local" ? PROPERTY_SPACE_MODE.LOCAL : PROPERTY_SPACE_MODE.WORLD; + updateVisibleSpaceModeProperties(); + } else if (data.type === 'propertyRangeReply') { + let propertyRanges = data.propertyRanges; + for (let property in propertyRanges) { + let propertyRange = propertyRanges[property]; + if (propertyRange !== undefined) { + let propertyData = properties[property].data; + let multiplier = propertyData.multiplier; + if (propertyData.min === undefined && propertyRange.minimum !== "") { + propertyData.min = propertyRange.minimum; + if (multiplier !== undefined) { + propertyData.min /= multiplier; + } + } + if (propertyData.max === undefined && propertyRange.maximum !== "") { + propertyData.max = propertyRange.maximum; + if (multiplier !== undefined) { + propertyData.max /= multiplier; + } + } + switch (propertyData.type) { + case 'number': + updateNumberMinMax(properties[property]); + break; + case 'number-draggable': + updateNumberDraggableMinMax(properties[property]); + break; + case 'vec3': + case 'vec2': + updateVectorMinMax(properties[property]); + break; + case 'rect': + updateRectMinMax(properties[property]); + break; + } + } + } + } else if (data.type === 'materialTargetReply') { + if (data.entityID === getFirstSelectedID()) { + setMaterialTargetData(data.materialTargetData); + } + } + }); + + // Request tooltips and property ranges as soon as we can process a reply: + EventBridge.emitWebEvent(JSON.stringify({ type: 'tooltipsRequest' })); + EventBridge.emitWebEvent(JSON.stringify({ type: 'propertyRangeRequest', properties: propertyRangeRequests })); + } + + // Server Script Status + let elServerScriptStatusOuter = document.getElementById('div-property-serverScriptStatus'); + let elServerScriptStatusContainer = document.getElementById('div-property-serverScriptStatus').childNodes[1]; + let serverScriptStatusElementID = 'property-serverScripts-status'; + createAppTooltip.registerTooltipElement(elServerScriptStatusOuter.childNodes[0], "serverScriptsStatus"); + let elServerScriptStatus = document.createElement('div'); + elServerScriptStatus.setAttribute("id", serverScriptStatusElementID); + elServerScriptStatusContainer.appendChild(elServerScriptStatus); + + // Server Script Error + let elServerScripts = getPropertyInputElement("serverScripts"); + let elDiv = document.createElement('div'); + elDiv.className = "property"; + let elServerScriptError = document.createElement('textarea'); + let serverScriptErrorElementID = 'property-serverScripts-error'; + elServerScriptError.setAttribute("id", serverScriptErrorElementID); + elDiv.appendChild(elServerScriptError); + elServerScriptStatusContainer.appendChild(elDiv); + + let elScript = getPropertyInputElement("script"); + elScript.parentNode.className = "url refresh"; + elServerScripts.parentNode.className = "url refresh"; + + // User Data + let userDataProperty = properties["userData"]; + let elUserData = userDataProperty.elInput; + let userDataElementID = userDataProperty.elementID; + elDiv = elUserData.parentNode; + let elStaticUserData = document.createElement('div'); + elStaticUserData.setAttribute("id", userDataElementID + "-static"); + let elUserDataEditor = document.createElement('div'); + elUserDataEditor.setAttribute("id", userDataElementID + "-editor"); + let elUserDataEditorStatus = document.createElement('div'); + elUserDataEditorStatus.setAttribute("id", userDataElementID + "-editorStatus"); + let elUserDataSaved = document.createElement('span'); + elUserDataSaved.setAttribute("id", userDataElementID + "-saved"); + elUserDataSaved.innerText = "Saved!"; + elDiv.childNodes[JSON_EDITOR_ROW_DIV_INDEX].appendChild(elUserDataSaved); + elDiv.insertBefore(elStaticUserData, elUserData); + elDiv.insertBefore(elUserDataEditor, elUserData); + elDiv.insertBefore(elUserDataEditorStatus, elUserData); + + // Material Data + let materialDataProperty = properties["materialData"]; + let elMaterialData = materialDataProperty.elInput; + let materialDataElementID = materialDataProperty.elementID; + elDiv = elMaterialData.parentNode; + let elStaticMaterialData = document.createElement('div'); + elStaticMaterialData.setAttribute("id", materialDataElementID + "-static"); + let elMaterialDataEditor = document.createElement('div'); + elMaterialDataEditor.setAttribute("id", materialDataElementID + "-editor"); + let elMaterialDataEditorStatus = document.createElement('div'); + elMaterialDataEditorStatus.setAttribute("id", materialDataElementID + "-editorStatus"); + let elMaterialDataSaved = document.createElement('span'); + elMaterialDataSaved.setAttribute("id", materialDataElementID + "-saved"); + elMaterialDataSaved.innerText = "Saved!"; + elDiv.childNodes[JSON_EDITOR_ROW_DIV_INDEX].appendChild(elMaterialDataSaved); + elDiv.insertBefore(elStaticMaterialData, elMaterialData); + elDiv.insertBefore(elMaterialDataEditor, elMaterialData); + elDiv.insertBefore(elMaterialDataEditorStatus, elMaterialData); + + // Collapsible sections + let elCollapsible = document.getElementsByClassName("collapse-icon"); + + let toggleCollapsedEvent = function(event) { + let element = this.parentNode.parentNode; + let isCollapsed = element.dataset.collapsed !== "true"; + element.dataset.collapsed = isCollapsed ? "true" : false; + element.setAttribute("collapsed", isCollapsed ? "true" : "false"); + this.textContent = isCollapsed ? "L" : "M"; + }; + + for (let collapseIndex = 0, numCollapsibles = elCollapsible.length; collapseIndex < numCollapsibles; ++collapseIndex) { + let curCollapsibleElement = elCollapsible[collapseIndex]; + curCollapsibleElement.addEventListener("click", toggleCollapsedEvent, true); + } + + // Textarea scrollbars + let elTextareas = document.getElementsByTagName("TEXTAREA"); + + let textareaOnChangeEvent = function(event) { + setTextareaScrolling(event.target); + }; + + for (let textAreaIndex = 0, numTextAreas = elTextareas.length; textAreaIndex < numTextAreas; ++textAreaIndex) { + let curTextAreaElement = elTextareas[textAreaIndex]; + setTextareaScrolling(curTextAreaElement); + curTextAreaElement.addEventListener("input", textareaOnChangeEvent, false); + curTextAreaElement.addEventListener("change", textareaOnChangeEvent, false); + /* FIXME: Detect and update textarea scrolling attribute on resize. Unfortunately textarea doesn't have a resize + event; mouseup is a partial stand-in but doesn't handle resizing if mouse moves outside textarea rectangle. */ + curTextAreaElement.addEventListener("mouseup", textareaOnChangeEvent, false); + } + + // Dropdowns + // For each dropdown the following replacement is created in place of the original dropdown... + // Structure created: + //
+ //
display textcarat
+ //
+ //
    + //
  • 0) { + let el = elDropdowns[0]; + el.parentNode.removeChild(el); + elDropdowns = document.getElementsByTagName("select"); + } + + const KEY_CODES = { + BACKSPACE: 8, + DELETE: 46 + }; + + document.addEventListener("keyup", function (keyUpEvent) { + const FILTERED_NODE_NAMES = ["INPUT", "TEXTAREA"]; + if (FILTERED_NODE_NAMES.includes(keyUpEvent.target.nodeName)) { + return; + } + + if (elUserDataEditor.contains(keyUpEvent.target) || elMaterialDataEditor.contains(keyUpEvent.target)) { + return; + } + + let {code, key, keyCode, altKey, ctrlKey, metaKey, shiftKey} = keyUpEvent; + + let controlKey = window.navigator.platform.startsWith("Mac") ? metaKey : ctrlKey; + + let keyCodeString; + switch (keyCode) { + case KEY_CODES.DELETE: + keyCodeString = "Delete"; + break; + case KEY_CODES.BACKSPACE: + keyCodeString = "Backspace"; + break; + default: + keyCodeString = String.fromCharCode(keyUpEvent.keyCode); + break; + } + + EventBridge.emitWebEvent(JSON.stringify({ + type: 'keyUpEvent', + keyUpEvent: { + code, + key, + keyCode, + keyCodeString, + altKey, + controlKey, + shiftKey, + } + })); + }, false); + + window.onblur = function() { + // Fake a change event + let ev = document.createEvent("HTMLEvents"); + ev.initEvent("change", true, true); + document.activeElement.dispatchEvent(ev); + }; + + // For input and textarea elements, select all of the text on focus + let els = document.querySelectorAll("input, textarea"); + for (let i = 0; i < els.length; ++i) { + els[i].onfocus = function (e) { + e.target.select(); + }; + } + + bindAllNonJSONEditorElements(); + + showGroupsForType("None"); + resetProperties(); + disableProperties(); + }); + + augmentSpinButtons(); + disableDragDrop(); + + // Disable right-click context menu which is not visible in the HMD and makes it seem like the app has locked + document.addEventListener("contextmenu", function(event) { + event.preventDefault(); + }, false); + + setTimeout(function() { + EventBridge.emitWebEvent(JSON.stringify({ type: 'propertiesPageReady' })); + }, 1000); +} diff --git a/scripts/simplifiedUI/system/html/js/eventBridgeLoader.js b/scripts/simplifiedUI/system/html/js/eventBridgeLoader.js new file mode 100644 index 0000000000..411780853b --- /dev/null +++ b/scripts/simplifiedUI/system/html/js/eventBridgeLoader.js @@ -0,0 +1,19 @@ + +//public slots: +// void emitWebEvent(const QString& data); +// void emitScriptEvent(const QString& data); +// +//signals: +// void webEventReceived(const QString& data); +// void scriptEventReceived(const QString& data); +// + +var EventBridge; +var WebChannel; + +openEventBridge = function(callback) { + WebChannel = new QWebChannel(qt.webChannelTransport, function (channel) { + EventBridge = WebChannel.objects.eventBridge; + callback(EventBridge); + }); +} diff --git a/scripts/simplifiedUI/system/html/js/gridControls.js b/scripts/simplifiedUI/system/html/js/gridControls.js new file mode 100644 index 0000000000..e27dac522b --- /dev/null +++ b/scripts/simplifiedUI/system/html/js/gridControls.js @@ -0,0 +1,161 @@ +// gridControls.js +// +// Created by Ryan Huffman on 6 Nov 2014 +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html + +function loaded() { + openEventBridge(function() { + elPosY = document.getElementById("horiz-y"); + elMinorSpacing = document.getElementById("minor-spacing"); + elMajorSpacing = document.getElementById("major-spacing"); + elSnapToGrid = document.getElementById("snap-to-grid"); + elHorizontalGridVisible = document.getElementById("horiz-grid-visible"); + elMoveToSelection = document.getElementById("move-to-selection"); + elMoveToAvatar = document.getElementById("move-to-avatar"); + + if (window.EventBridge !== undefined) { + EventBridge.scriptEventReceived.connect(function(data) { + data = JSON.parse(data); + + if (data.origin) { + var origin = data.origin; + elPosY.value = origin.y; + } + + if (data.minorGridEvery !== undefined) { + elMinorSpacing.value = data.minorGridEvery; + } + + if (data.majorGridEvery !== undefined) { + elMajorSpacing.value = data.majorGridEvery; + } + + if (data.gridColor) { + gridColor = data.gridColor; + } + + if (data.snapToGrid !== undefined) { + elSnapToGrid.checked = data.snapToGrid == true; + } + + if (data.visible !== undefined) { + elHorizontalGridVisible.checked = data.visible == true; + } + }); + + function emitUpdate() { + EventBridge.emitWebEvent(JSON.stringify({ + type: "update", + origin: { + y: elPosY.value, + }, + minorGridEvery: elMinorSpacing.value, + majorGridEvery: elMajorSpacing.value, + gridColor: gridColor, + snapToGrid: elSnapToGrid.checked, + visible: elHorizontalGridVisible.checked, + })); + } + + } + + elPosY.addEventListener("change", emitUpdate); + elMinorSpacing.addEventListener("change", emitUpdate); + elMajorSpacing.addEventListener("change", emitUpdate); + elSnapToGrid.addEventListener("change", emitUpdate); + elHorizontalGridVisible.addEventListener("change", emitUpdate); + + elMoveToAvatar.addEventListener("click", function() { + EventBridge.emitWebEvent(JSON.stringify({ + type: "action", + action: "moveToAvatar", + })); + }); + elMoveToSelection.addEventListener("click", function() { + EventBridge.emitWebEvent(JSON.stringify({ + type: "action", + action: "moveToSelection", + })); + }); + + var gridColor = { red: 255, green: 255, blue: 255 }; + var elColor = document.getElementById("grid-color"); + elColor.style.backgroundColor = "rgb(" + gridColor.red + "," + gridColor.green + "," + gridColor.blue + ")"; + + var colorPickFunction = function (red, green, blue) { + gridColor = { red: red, green: green, blue: blue }; + emitUpdate(); + }; + + $('#grid-color').colpick({ + colorScheme: 'dark', + layout: 'rgbhex', + color: { r: gridColor.red, g: gridColor.green, b: gridColor.blue }, + submit: false, + onShow: function (colpick) { + $('#grid-color').attr('active', 'true'); + }, + onHide: function (colpick) { + $('#grid-color').attr('active', 'false'); + }, + onChange: function (hsb, hex, rgb, el) { + $(el).css('background-color', '#' + hex); + colorPickFunction(rgb.r, rgb.g, rgb.b); + } + }); + + augmentSpinButtons(); + disableDragDrop(); + + EventBridge.emitWebEvent(JSON.stringify({ type: 'init' })); + }); + + const KEY_CODES = { + BACKSPACE: 8, + DELETE: 46 + }; + + document.addEventListener("keyup", function (keyUpEvent) { + const FILTERED_NODE_NAMES = ["INPUT", "TEXTAREA"]; + if (FILTERED_NODE_NAMES.includes(keyUpEvent.target.nodeName)) { + return; + } + let {code, key, keyCode, altKey, ctrlKey, metaKey, shiftKey} = keyUpEvent; + + let controlKey = window.navigator.platform.startsWith("Mac") ? metaKey : ctrlKey; + + let keyCodeString; + switch (keyCode) { + case KEY_CODES.DELETE: + keyCodeString = "Delete"; + break; + case KEY_CODES.BACKSPACE: + keyCodeString = "Backspace"; + break; + default: + keyCodeString = String.fromCharCode(keyUpEvent.keyCode); + break; + } + + EventBridge.emitWebEvent(JSON.stringify({ + type: 'keyUpEvent', + keyUpEvent: { + code, + key, + keyCode, + keyCodeString, + altKey, + controlKey, + shiftKey, + } + })); + }, false); + + // Disable right-click context menu which is not visible in the HMD and makes it seem like the app has locked + document.addEventListener("contextmenu", function (event) { + event.preventDefault(); + }, false); +} diff --git a/scripts/simplifiedUI/system/html/js/includes.js b/scripts/simplifiedUI/system/html/js/includes.js new file mode 100644 index 0000000000..c604115f91 --- /dev/null +++ b/scripts/simplifiedUI/system/html/js/includes.js @@ -0,0 +1,27 @@ +// +// includes.js +// +// Created by David Back on 3 Jan 2019 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +const ENTITY_TYPE_ICON = { + Box: "m", + Grid: "", + Image: "", + Light: "p", + Material: "", + Model: "", + ParticleEffect: "", + PolyVox: "", + PolyLine: "", + Shape: "n", + Sphere: "n", + Text: "l", + Web: "q", + Zone: "o", + Multiple: "", +}; diff --git a/scripts/simplifiedUI/system/html/js/jquery-2.1.4.min.js b/scripts/simplifiedUI/system/html/js/jquery-2.1.4.min.js new file mode 100644 index 0000000000..49990d6e14 --- /dev/null +++ b/scripts/simplifiedUI/system/html/js/jquery-2.1.4.min.js @@ -0,0 +1,4 @@ +/*! jQuery v2.1.4 | (c) 2005, 2015 jQuery Foundation, Inc. | jquery.org/license */ +!function(a,b){"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){var c=[],d=c.slice,e=c.concat,f=c.push,g=c.indexOf,h={},i=h.toString,j=h.hasOwnProperty,k={},l=a.document,m="2.1.4",n=function(a,b){return new n.fn.init(a,b)},o=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,p=/^-ms-/,q=/-([\da-z])/gi,r=function(a,b){return b.toUpperCase()};n.fn=n.prototype={jquery:m,constructor:n,selector:"",length:0,toArray:function(){return d.call(this)},get:function(a){return null!=a?0>a?this[a+this.length]:this[a]:d.call(this)},pushStack:function(a){var b=n.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a,b){return n.each(this,a,b)},map:function(a){return this.pushStack(n.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(d.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor(null)},push:f,sort:c.sort,splice:c.splice},n.extend=n.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||n.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(a=arguments[h]))for(b in a)c=g[b],d=a[b],g!==d&&(j&&d&&(n.isPlainObject(d)||(e=n.isArray(d)))?(e?(e=!1,f=c&&n.isArray(c)?c:[]):f=c&&n.isPlainObject(c)?c:{},g[b]=n.extend(j,f,d)):void 0!==d&&(g[b]=d));return g},n.extend({expando:"jQuery"+(m+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===n.type(a)},isArray:Array.isArray,isWindow:function(a){return null!=a&&a===a.window},isNumeric:function(a){return!n.isArray(a)&&a-parseFloat(a)+1>=0},isPlainObject:function(a){return"object"!==n.type(a)||a.nodeType||n.isWindow(a)?!1:a.constructor&&!j.call(a.constructor.prototype,"isPrototypeOf")?!1:!0},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?h[i.call(a)]||"object":typeof a},globalEval:function(a){var b,c=eval;a=n.trim(a),a&&(1===a.indexOf("use strict")?(b=l.createElement("script"),b.text=a,l.head.appendChild(b).parentNode.removeChild(b)):c(a))},camelCase:function(a){return a.replace(p,"ms-").replace(q,r)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b,c){var d,e=0,f=a.length,g=s(a);if(c){if(g){for(;f>e;e++)if(d=b.apply(a[e],c),d===!1)break}else for(e in a)if(d=b.apply(a[e],c),d===!1)break}else if(g){for(;f>e;e++)if(d=b.call(a[e],e,a[e]),d===!1)break}else for(e in a)if(d=b.call(a[e],e,a[e]),d===!1)break;return a},trim:function(a){return null==a?"":(a+"").replace(o,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(s(Object(a))?n.merge(c,"string"==typeof a?[a]:a):f.call(c,a)),c},inArray:function(a,b,c){return null==b?-1:g.call(b,a,c)},merge:function(a,b){for(var c=+b.length,d=0,e=a.length;c>d;d++)a[e++]=b[d];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,f=0,g=a.length,h=s(a),i=[];if(h)for(;g>f;f++)d=b(a[f],f,c),null!=d&&i.push(d);else for(f in a)d=b(a[f],f,c),null!=d&&i.push(d);return e.apply([],i)},guid:1,proxy:function(a,b){var c,e,f;return"string"==typeof b&&(c=a[b],b=a,a=c),n.isFunction(a)?(e=d.call(arguments,2),f=function(){return a.apply(b||this,e.concat(d.call(arguments)))},f.guid=a.guid=a.guid||n.guid++,f):void 0},now:Date.now,support:k}),n.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(a,b){h["[object "+b+"]"]=b.toLowerCase()});function s(a){var b="length"in a&&a.length,c=n.type(a);return"function"===c||n.isWindow(a)?!1:1===a.nodeType&&b?!0:"array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a}var t=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+1*new Date,v=a.document,w=0,x=0,y=ha(),z=ha(),A=ha(),B=function(a,b){return a===b&&(l=!0),0},C=1<<31,D={}.hasOwnProperty,E=[],F=E.pop,G=E.push,H=E.push,I=E.slice,J=function(a,b){for(var c=0,d=a.length;d>c;c++)if(a[c]===b)return c;return-1},K="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",L="[\\x20\\t\\r\\n\\f]",M="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",N=M.replace("w","w#"),O="\\["+L+"*("+M+")(?:"+L+"*([*^$|!~]?=)"+L+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+N+"))|)"+L+"*\\]",P=":("+M+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+O+")*)|.*)\\)|)",Q=new RegExp(L+"+","g"),R=new RegExp("^"+L+"+|((?:^|[^\\\\])(?:\\\\.)*)"+L+"+$","g"),S=new RegExp("^"+L+"*,"+L+"*"),T=new RegExp("^"+L+"*([>+~]|"+L+")"+L+"*"),U=new RegExp("="+L+"*([^\\]'\"]*?)"+L+"*\\]","g"),V=new RegExp(P),W=new RegExp("^"+N+"$"),X={ID:new RegExp("^#("+M+")"),CLASS:new RegExp("^\\.("+M+")"),TAG:new RegExp("^("+M.replace("w","w*")+")"),ATTR:new RegExp("^"+O),PSEUDO:new RegExp("^"+P),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+L+"*(even|odd|(([+-]|)(\\d*)n|)"+L+"*(?:([+-]|)"+L+"*(\\d+)|))"+L+"*\\)|)","i"),bool:new RegExp("^(?:"+K+")$","i"),needsContext:new RegExp("^"+L+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+L+"*((?:-\\d)?\\d*)"+L+"*\\)|)(?=[^-]|$)","i")},Y=/^(?:input|select|textarea|button)$/i,Z=/^h\d$/i,$=/^[^{]+\{\s*\[native \w/,_=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,aa=/[+~]/,ba=/'|\\/g,ca=new RegExp("\\\\([\\da-f]{1,6}"+L+"?|("+L+")|.)","ig"),da=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:0>d?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)},ea=function(){m()};try{H.apply(E=I.call(v.childNodes),v.childNodes),E[v.childNodes.length].nodeType}catch(fa){H={apply:E.length?function(a,b){G.apply(a,I.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function ga(a,b,d,e){var f,h,j,k,l,o,r,s,w,x;if((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,d=d||[],k=b.nodeType,"string"!=typeof a||!a||1!==k&&9!==k&&11!==k)return d;if(!e&&p){if(11!==k&&(f=_.exec(a)))if(j=f[1]){if(9===k){if(h=b.getElementById(j),!h||!h.parentNode)return d;if(h.id===j)return d.push(h),d}else if(b.ownerDocument&&(h=b.ownerDocument.getElementById(j))&&t(b,h)&&h.id===j)return d.push(h),d}else{if(f[2])return H.apply(d,b.getElementsByTagName(a)),d;if((j=f[3])&&c.getElementsByClassName)return H.apply(d,b.getElementsByClassName(j)),d}if(c.qsa&&(!q||!q.test(a))){if(s=r=u,w=b,x=1!==k&&a,1===k&&"object"!==b.nodeName.toLowerCase()){o=g(a),(r=b.getAttribute("id"))?s=r.replace(ba,"\\$&"):b.setAttribute("id",s),s="[id='"+s+"'] ",l=o.length;while(l--)o[l]=s+ra(o[l]);w=aa.test(a)&&pa(b.parentNode)||b,x=o.join(",")}if(x)try{return H.apply(d,w.querySelectorAll(x)),d}catch(y){}finally{r||b.removeAttribute("id")}}}return i(a.replace(R,"$1"),b,d,e)}function ha(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function ia(a){return a[u]=!0,a}function ja(a){var b=n.createElement("div");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function ka(a,b){var c=a.split("|"),e=a.length;while(e--)d.attrHandle[c[e]]=b}function la(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&(~b.sourceIndex||C)-(~a.sourceIndex||C);if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function ma(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function na(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function oa(a){return ia(function(b){return b=+b,ia(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function pa(a){return a&&"undefined"!=typeof a.getElementsByTagName&&a}c=ga.support={},f=ga.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?"HTML"!==b.nodeName:!1},m=ga.setDocument=function(a){var b,e,g=a?a.ownerDocument||a:v;return g!==n&&9===g.nodeType&&g.documentElement?(n=g,o=g.documentElement,e=g.defaultView,e&&e!==e.top&&(e.addEventListener?e.addEventListener("unload",ea,!1):e.attachEvent&&e.attachEvent("onunload",ea)),p=!f(g),c.attributes=ja(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ja(function(a){return a.appendChild(g.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=$.test(g.getElementsByClassName),c.getById=ja(function(a){return o.appendChild(a).id=u,!g.getElementsByName||!g.getElementsByName(u).length}),c.getById?(d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c=b.getElementById(a);return c&&c.parentNode?[c]:[]}},d.filter.ID=function(a){var b=a.replace(ca,da);return function(a){return a.getAttribute("id")===b}}):(delete d.find.ID,d.filter.ID=function(a){var b=a.replace(ca,da);return function(a){var c="undefined"!=typeof a.getAttributeNode&&a.getAttributeNode("id");return c&&c.value===b}}),d.find.TAG=c.getElementsByTagName?function(a,b){return"undefined"!=typeof b.getElementsByTagName?b.getElementsByTagName(a):c.qsa?b.querySelectorAll(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){return p?b.getElementsByClassName(a):void 0},r=[],q=[],(c.qsa=$.test(g.querySelectorAll))&&(ja(function(a){o.appendChild(a).innerHTML="",a.querySelectorAll("[msallowcapture^='']").length&&q.push("[*^$]="+L+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+L+"*(?:value|"+K+")"),a.querySelectorAll("[id~="+u+"-]").length||q.push("~="),a.querySelectorAll(":checked").length||q.push(":checked"),a.querySelectorAll("a#"+u+"+*").length||q.push(".#.+[+~]")}),ja(function(a){var b=g.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+L+"*[*^$|!~]?="),a.querySelectorAll(":enabled").length||q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=$.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ja(function(a){c.disconnectedMatch=s.call(a,"div"),s.call(a,"[s!='']:x"),r.push("!=",P)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=$.test(o.compareDocumentPosition),t=b||$.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===g||a.ownerDocument===v&&t(v,a)?-1:b===g||b.ownerDocument===v&&t(v,b)?1:k?J(k,a)-J(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,e=a.parentNode,f=b.parentNode,h=[a],i=[b];if(!e||!f)return a===g?-1:b===g?1:e?-1:f?1:k?J(k,a)-J(k,b):0;if(e===f)return la(a,b);c=a;while(c=c.parentNode)h.unshift(c);c=b;while(c=c.parentNode)i.unshift(c);while(h[d]===i[d])d++;return d?la(h[d],i[d]):h[d]===v?-1:i[d]===v?1:0},g):n},ga.matches=function(a,b){return ga(a,null,null,b)},ga.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(U,"='$1']"),!(!c.matchesSelector||!p||r&&r.test(b)||q&&q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return ga(b,n,null,[a]).length>0},ga.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},ga.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&D.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},ga.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},ga.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=ga.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=ga.selectors={cacheLength:50,createPseudo:ia,match:X,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(ca,da),a[3]=(a[3]||a[4]||a[5]||"").replace(ca,da),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||ga.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&ga.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return X.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&V.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(ca,da).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+L+")"+a+"("+L+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||"undefined"!=typeof a.getAttribute&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=ga.attr(d,a);return null==e?"!="===b:b?(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e.replace(Q," ")+" ").indexOf(c)>-1:"|="===b?e===c||e.slice(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h;if(q){if(f){while(p){l=b;while(l=l[p])if(h?l.nodeName.toLowerCase()===r:1===l.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){k=q[u]||(q[u]={}),j=k[a]||[],n=j[0]===w&&j[1],m=j[0]===w&&j[2],l=n&&q.childNodes[n];while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if(1===l.nodeType&&++m&&l===b){k[a]=[w,n,m];break}}else if(s&&(j=(b[u]||(b[u]={}))[a])&&j[0]===w)m=j[1];else while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if((h?l.nodeName.toLowerCase()===r:1===l.nodeType)&&++m&&(s&&((l[u]||(l[u]={}))[a]=[w,m]),l===b))break;return m-=e,m===d||m%d===0&&m/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||ga.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?ia(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=J(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:ia(function(a){var b=[],c=[],d=h(a.replace(R,"$1"));return d[u]?ia(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),b[0]=null,!c.pop()}}),has:ia(function(a){return function(b){return ga(a,b).length>0}}),contains:ia(function(a){return a=a.replace(ca,da),function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:ia(function(a){return W.test(a||"")||ga.error("unsupported lang: "+a),a=a.replace(ca,da).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return Z.test(a.nodeName)},input:function(a){return Y.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:oa(function(){return[0]}),last:oa(function(a,b){return[b-1]}),eq:oa(function(a,b,c){return[0>c?c+b:c]}),even:oa(function(a,b){for(var c=0;b>c;c+=2)a.push(c);return a}),odd:oa(function(a,b){for(var c=1;b>c;c+=2)a.push(c);return a}),lt:oa(function(a,b,c){for(var d=0>c?c+b:c;--d>=0;)a.push(d);return a}),gt:oa(function(a,b,c){for(var d=0>c?c+b:c;++db;b++)d+=a[b].value;return d}function sa(a,b,c){var d=b.dir,e=c&&"parentNode"===d,f=x++;return b.first?function(b,c,f){while(b=b[d])if(1===b.nodeType||e)return a(b,c,f)}:function(b,c,g){var h,i,j=[w,f];if(g){while(b=b[d])if((1===b.nodeType||e)&&a(b,c,g))return!0}else while(b=b[d])if(1===b.nodeType||e){if(i=b[u]||(b[u]={}),(h=i[d])&&h[0]===w&&h[1]===f)return j[2]=h[2];if(i[d]=j,j[2]=a(b,c,g))return!0}}}function ta(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function ua(a,b,c){for(var d=0,e=b.length;e>d;d++)ga(a,b[d],c);return c}function va(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;i>h;h++)(f=a[h])&&(!c||c(f,d,e))&&(g.push(f),j&&b.push(h));return g}function wa(a,b,c,d,e,f){return d&&!d[u]&&(d=wa(d)),e&&!e[u]&&(e=wa(e,f)),ia(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||ua(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:va(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=va(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?J(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=va(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):H.apply(g,r)})}function xa(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=sa(function(a){return a===b},h,!0),l=sa(function(a){return J(b,a)>-1},h,!0),m=[function(a,c,d){var e=!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d));return b=null,e}];f>i;i++)if(c=d.relative[a[i].type])m=[sa(ta(m),c)];else{if(c=d.filter[a[i].type].apply(null,a[i].matches),c[u]){for(e=++i;f>e;e++)if(d.relative[a[e].type])break;return wa(i>1&&ta(m),i>1&&ra(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(R,"$1"),c,e>i&&xa(a.slice(i,e)),f>e&&xa(a=a.slice(e)),f>e&&ra(a))}m.push(c)}return ta(m)}function ya(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,h,i,k){var l,m,o,p=0,q="0",r=f&&[],s=[],t=j,u=f||e&&d.find.TAG("*",k),v=w+=null==t?1:Math.random()||.1,x=u.length;for(k&&(j=g!==n&&g);q!==x&&null!=(l=u[q]);q++){if(e&&l){m=0;while(o=a[m++])if(o(l,g,h)){i.push(l);break}k&&(w=v)}c&&((l=!o&&l)&&p--,f&&r.push(l))}if(p+=q,c&&q!==p){m=0;while(o=b[m++])o(r,s,g,h);if(f){if(p>0)while(q--)r[q]||s[q]||(s[q]=F.call(i));s=va(s)}H.apply(i,s),k&&!f&&s.length>0&&p+b.length>1&&ga.uniqueSort(i)}return k&&(w=v,j=t),r};return c?ia(f):f}return h=ga.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=xa(b[c]),f[u]?d.push(f):e.push(f);f=A(a,ya(e,d)),f.selector=a}return f},i=ga.select=function(a,b,e,f){var i,j,k,l,m,n="function"==typeof a&&a,o=!f&&g(a=n.selector||a);if(e=e||[],1===o.length){if(j=o[0]=o[0].slice(0),j.length>2&&"ID"===(k=j[0]).type&&c.getById&&9===b.nodeType&&p&&d.relative[j[1].type]){if(b=(d.find.ID(k.matches[0].replace(ca,da),b)||[])[0],!b)return e;n&&(b=b.parentNode),a=a.slice(j.shift().value.length)}i=X.needsContext.test(a)?0:j.length;while(i--){if(k=j[i],d.relative[l=k.type])break;if((m=d.find[l])&&(f=m(k.matches[0].replace(ca,da),aa.test(j[0].type)&&pa(b.parentNode)||b))){if(j.splice(i,1),a=f.length&&ra(j),!a)return H.apply(e,f),e;break}}}return(n||h(a,o))(f,b,!p,e,aa.test(a)&&pa(b.parentNode)||b),e},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ja(function(a){return 1&a.compareDocumentPosition(n.createElement("div"))}),ja(function(a){return a.innerHTML="","#"===a.firstChild.getAttribute("href")})||ka("type|href|height|width",function(a,b,c){return c?void 0:a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ja(function(a){return a.innerHTML="",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||ka("value",function(a,b,c){return c||"input"!==a.nodeName.toLowerCase()?void 0:a.defaultValue}),ja(function(a){return null==a.getAttribute("disabled")})||ka(K,function(a,b,c){var d;return c?void 0:a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),ga}(a);n.find=t,n.expr=t.selectors,n.expr[":"]=n.expr.pseudos,n.unique=t.uniqueSort,n.text=t.getText,n.isXMLDoc=t.isXML,n.contains=t.contains;var u=n.expr.match.needsContext,v=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,w=/^.[^:#\[\.,]*$/;function x(a,b,c){if(n.isFunction(b))return n.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return n.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(w.test(b))return n.filter(b,a,c);b=n.filter(b,a)}return n.grep(a,function(a){return g.call(b,a)>=0!==c})}n.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?n.find.matchesSelector(d,a)?[d]:[]:n.find.matches(a,n.grep(b,function(a){return 1===a.nodeType}))},n.fn.extend({find:function(a){var b,c=this.length,d=[],e=this;if("string"!=typeof a)return this.pushStack(n(a).filter(function(){for(b=0;c>b;b++)if(n.contains(e[b],this))return!0}));for(b=0;c>b;b++)n.find(a,e[b],d);return d=this.pushStack(c>1?n.unique(d):d),d.selector=this.selector?this.selector+" "+a:a,d},filter:function(a){return this.pushStack(x(this,a||[],!1))},not:function(a){return this.pushStack(x(this,a||[],!0))},is:function(a){return!!x(this,"string"==typeof a&&u.test(a)?n(a):a||[],!1).length}});var y,z=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,A=n.fn.init=function(a,b){var c,d;if(!a)return this;if("string"==typeof a){if(c="<"===a[0]&&">"===a[a.length-1]&&a.length>=3?[null,a,null]:z.exec(a),!c||!c[1]&&b)return!b||b.jquery?(b||y).find(a):this.constructor(b).find(a);if(c[1]){if(b=b instanceof n?b[0]:b,n.merge(this,n.parseHTML(c[1],b&&b.nodeType?b.ownerDocument||b:l,!0)),v.test(c[1])&&n.isPlainObject(b))for(c in b)n.isFunction(this[c])?this[c](b[c]):this.attr(c,b[c]);return this}return d=l.getElementById(c[2]),d&&d.parentNode&&(this.length=1,this[0]=d),this.context=l,this.selector=a,this}return a.nodeType?(this.context=this[0]=a,this.length=1,this):n.isFunction(a)?"undefined"!=typeof y.ready?y.ready(a):a(n):(void 0!==a.selector&&(this.selector=a.selector,this.context=a.context),n.makeArray(a,this))};A.prototype=n.fn,y=n(l);var B=/^(?:parents|prev(?:Until|All))/,C={children:!0,contents:!0,next:!0,prev:!0};n.extend({dir:function(a,b,c){var d=[],e=void 0!==c;while((a=a[b])&&9!==a.nodeType)if(1===a.nodeType){if(e&&n(a).is(c))break;d.push(a)}return d},sibling:function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c}}),n.fn.extend({has:function(a){var b=n(a,this),c=b.length;return this.filter(function(){for(var a=0;c>a;a++)if(n.contains(this,b[a]))return!0})},closest:function(a,b){for(var c,d=0,e=this.length,f=[],g=u.test(a)||"string"!=typeof a?n(a,b||this.context):0;e>d;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&n.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?n.unique(f):f)},index:function(a){return a?"string"==typeof a?g.call(n(a),this[0]):g.call(this,a.jquery?a[0]:a):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(n.unique(n.merge(this.get(),n(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function D(a,b){while((a=a[b])&&1!==a.nodeType);return a}n.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return n.dir(a,"parentNode")},parentsUntil:function(a,b,c){return n.dir(a,"parentNode",c)},next:function(a){return D(a,"nextSibling")},prev:function(a){return D(a,"previousSibling")},nextAll:function(a){return n.dir(a,"nextSibling")},prevAll:function(a){return n.dir(a,"previousSibling")},nextUntil:function(a,b,c){return n.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return n.dir(a,"previousSibling",c)},siblings:function(a){return n.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return n.sibling(a.firstChild)},contents:function(a){return a.contentDocument||n.merge([],a.childNodes)}},function(a,b){n.fn[a]=function(c,d){var e=n.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=n.filter(d,e)),this.length>1&&(C[a]||n.unique(e),B.test(a)&&e.reverse()),this.pushStack(e)}});var E=/\S+/g,F={};function G(a){var b=F[a]={};return n.each(a.match(E)||[],function(a,c){b[c]=!0}),b}n.Callbacks=function(a){a="string"==typeof a?F[a]||G(a):n.extend({},a);var b,c,d,e,f,g,h=[],i=!a.once&&[],j=function(l){for(b=a.memory&&l,c=!0,g=e||0,e=0,f=h.length,d=!0;h&&f>g;g++)if(h[g].apply(l[0],l[1])===!1&&a.stopOnFalse){b=!1;break}d=!1,h&&(i?i.length&&j(i.shift()):b?h=[]:k.disable())},k={add:function(){if(h){var c=h.length;!function g(b){n.each(b,function(b,c){var d=n.type(c);"function"===d?a.unique&&k.has(c)||h.push(c):c&&c.length&&"string"!==d&&g(c)})}(arguments),d?f=h.length:b&&(e=c,j(b))}return this},remove:function(){return h&&n.each(arguments,function(a,b){var c;while((c=n.inArray(b,h,c))>-1)h.splice(c,1),d&&(f>=c&&f--,g>=c&&g--)}),this},has:function(a){return a?n.inArray(a,h)>-1:!(!h||!h.length)},empty:function(){return h=[],f=0,this},disable:function(){return h=i=b=void 0,this},disabled:function(){return!h},lock:function(){return i=void 0,b||k.disable(),this},locked:function(){return!i},fireWith:function(a,b){return!h||c&&!i||(b=b||[],b=[a,b.slice?b.slice():b],d?i.push(b):j(b)),this},fire:function(){return k.fireWith(this,arguments),this},fired:function(){return!!c}};return k},n.extend({Deferred:function(a){var b=[["resolve","done",n.Callbacks("once memory"),"resolved"],["reject","fail",n.Callbacks("once memory"),"rejected"],["notify","progress",n.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return n.Deferred(function(c){n.each(b,function(b,f){var g=n.isFunction(a[b])&&a[b];e[f[1]](function(){var a=g&&g.apply(this,arguments);a&&n.isFunction(a.promise)?a.promise().done(c.resolve).fail(c.reject).progress(c.notify):c[f[0]+"With"](this===d?c.promise():this,g?[a]:arguments)})}),a=null}).promise()},promise:function(a){return null!=a?n.extend(a,d):d}},e={};return d.pipe=d.then,n.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[1^a][2].disable,b[2][2].lock),e[f[0]]=function(){return e[f[0]+"With"](this===e?d:this,arguments),this},e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=d.call(arguments),e=c.length,f=1!==e||a&&n.isFunction(a.promise)?e:0,g=1===f?a:n.Deferred(),h=function(a,b,c){return function(e){b[a]=this,c[a]=arguments.length>1?d.call(arguments):e,c===i?g.notifyWith(b,c):--f||g.resolveWith(b,c)}},i,j,k;if(e>1)for(i=new Array(e),j=new Array(e),k=new Array(e);e>b;b++)c[b]&&n.isFunction(c[b].promise)?c[b].promise().done(h(b,k,c)).fail(g.reject).progress(h(b,j,i)):--f;return f||g.resolveWith(k,c),g.promise()}});var H;n.fn.ready=function(a){return n.ready.promise().done(a),this},n.extend({isReady:!1,readyWait:1,holdReady:function(a){a?n.readyWait++:n.ready(!0)},ready:function(a){(a===!0?--n.readyWait:n.isReady)||(n.isReady=!0,a!==!0&&--n.readyWait>0||(H.resolveWith(l,[n]),n.fn.triggerHandler&&(n(l).triggerHandler("ready"),n(l).off("ready"))))}});function I(){l.removeEventListener("DOMContentLoaded",I,!1),a.removeEventListener("load",I,!1),n.ready()}n.ready.promise=function(b){return H||(H=n.Deferred(),"complete"===l.readyState?setTimeout(n.ready):(l.addEventListener("DOMContentLoaded",I,!1),a.addEventListener("load",I,!1))),H.promise(b)},n.ready.promise();var J=n.access=function(a,b,c,d,e,f,g){var h=0,i=a.length,j=null==c;if("object"===n.type(c)){e=!0;for(h in c)n.access(a,b,h,c[h],!0,f,g)}else if(void 0!==d&&(e=!0,n.isFunction(d)||(g=!0),j&&(g?(b.call(a,d),b=null):(j=b,b=function(a,b,c){return j.call(n(a),c)})),b))for(;i>h;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f};n.acceptData=function(a){return 1===a.nodeType||9===a.nodeType||!+a.nodeType};function K(){Object.defineProperty(this.cache={},0,{get:function(){return{}}}),this.expando=n.expando+K.uid++}K.uid=1,K.accepts=n.acceptData,K.prototype={key:function(a){if(!K.accepts(a))return 0;var b={},c=a[this.expando];if(!c){c=K.uid++;try{b[this.expando]={value:c},Object.defineProperties(a,b)}catch(d){b[this.expando]=c,n.extend(a,b)}}return this.cache[c]||(this.cache[c]={}),c},set:function(a,b,c){var d,e=this.key(a),f=this.cache[e];if("string"==typeof b)f[b]=c;else if(n.isEmptyObject(f))n.extend(this.cache[e],b);else for(d in b)f[d]=b[d];return f},get:function(a,b){var c=this.cache[this.key(a)];return void 0===b?c:c[b]},access:function(a,b,c){var d;return void 0===b||b&&"string"==typeof b&&void 0===c?(d=this.get(a,b),void 0!==d?d:this.get(a,n.camelCase(b))):(this.set(a,b,c),void 0!==c?c:b)},remove:function(a,b){var c,d,e,f=this.key(a),g=this.cache[f];if(void 0===b)this.cache[f]={};else{n.isArray(b)?d=b.concat(b.map(n.camelCase)):(e=n.camelCase(b),b in g?d=[b,e]:(d=e,d=d in g?[d]:d.match(E)||[])),c=d.length;while(c--)delete g[d[c]]}},hasData:function(a){return!n.isEmptyObject(this.cache[a[this.expando]]||{})},discard:function(a){a[this.expando]&&delete this.cache[a[this.expando]]}};var L=new K,M=new K,N=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,O=/([A-Z])/g;function P(a,b,c){var d;if(void 0===c&&1===a.nodeType)if(d="data-"+b.replace(O,"-$1").toLowerCase(),c=a.getAttribute(d),"string"==typeof c){try{c="true"===c?!0:"false"===c?!1:"null"===c?null:+c+""===c?+c:N.test(c)?n.parseJSON(c):c}catch(e){}M.set(a,b,c)}else c=void 0;return c}n.extend({hasData:function(a){return M.hasData(a)||L.hasData(a)},data:function(a,b,c){ +return M.access(a,b,c)},removeData:function(a,b){M.remove(a,b)},_data:function(a,b,c){return L.access(a,b,c)},_removeData:function(a,b){L.remove(a,b)}}),n.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=M.get(f),1===f.nodeType&&!L.get(f,"hasDataAttrs"))){c=g.length;while(c--)g[c]&&(d=g[c].name,0===d.indexOf("data-")&&(d=n.camelCase(d.slice(5)),P(f,d,e[d])));L.set(f,"hasDataAttrs",!0)}return e}return"object"==typeof a?this.each(function(){M.set(this,a)}):J(this,function(b){var c,d=n.camelCase(a);if(f&&void 0===b){if(c=M.get(f,a),void 0!==c)return c;if(c=M.get(f,d),void 0!==c)return c;if(c=P(f,d,void 0),void 0!==c)return c}else this.each(function(){var c=M.get(this,d);M.set(this,d,b),-1!==a.indexOf("-")&&void 0!==c&&M.set(this,a,b)})},null,b,arguments.length>1,null,!0)},removeData:function(a){return this.each(function(){M.remove(this,a)})}}),n.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=L.get(a,b),c&&(!d||n.isArray(c)?d=L.access(a,b,n.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=n.queue(a,b),d=c.length,e=c.shift(),f=n._queueHooks(a,b),g=function(){n.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return L.get(a,c)||L.access(a,c,{empty:n.Callbacks("once memory").add(function(){L.remove(a,[b+"queue",c])})})}}),n.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.lengthx",k.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue}();var U="undefined";k.focusinBubbles="onfocusin"in a;var V=/^key/,W=/^(?:mouse|pointer|contextmenu)|click/,X=/^(?:focusinfocus|focusoutblur)$/,Y=/^([^.]*)(?:\.(.+)|)$/;function Z(){return!0}function $(){return!1}function _(){try{return l.activeElement}catch(a){}}n.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=L.get(a);if(r){c.handler&&(f=c,c=f.handler,e=f.selector),c.guid||(c.guid=n.guid++),(i=r.events)||(i=r.events={}),(g=r.handle)||(g=r.handle=function(b){return typeof n!==U&&n.event.triggered!==b.type?n.event.dispatch.apply(a,arguments):void 0}),b=(b||"").match(E)||[""],j=b.length;while(j--)h=Y.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o&&(l=n.event.special[o]||{},o=(e?l.delegateType:l.bindType)||o,l=n.event.special[o]||{},k=n.extend({type:o,origType:q,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&n.expr.match.needsContext.test(e),namespace:p.join(".")},f),(m=i[o])||(m=i[o]=[],m.delegateCount=0,l.setup&&l.setup.call(a,d,p,g)!==!1||a.addEventListener&&a.addEventListener(o,g,!1)),l.add&&(l.add.call(a,k),k.handler.guid||(k.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,k):m.push(k),n.event.global[o]=!0)}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=L.hasData(a)&&L.get(a);if(r&&(i=r.events)){b=(b||"").match(E)||[""],j=b.length;while(j--)if(h=Y.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o){l=n.event.special[o]||{},o=(d?l.delegateType:l.bindType)||o,m=i[o]||[],h=h[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),g=f=m.length;while(f--)k=m[f],!e&&q!==k.origType||c&&c.guid!==k.guid||h&&!h.test(k.namespace)||d&&d!==k.selector&&("**"!==d||!k.selector)||(m.splice(f,1),k.selector&&m.delegateCount--,l.remove&&l.remove.call(a,k));g&&!m.length&&(l.teardown&&l.teardown.call(a,p,r.handle)!==!1||n.removeEvent(a,o,r.handle),delete i[o])}else for(o in i)n.event.remove(a,o+b[j],c,d,!0);n.isEmptyObject(i)&&(delete r.handle,L.remove(a,"events"))}},trigger:function(b,c,d,e){var f,g,h,i,k,m,o,p=[d||l],q=j.call(b,"type")?b.type:b,r=j.call(b,"namespace")?b.namespace.split("."):[];if(g=h=d=d||l,3!==d.nodeType&&8!==d.nodeType&&!X.test(q+n.event.triggered)&&(q.indexOf(".")>=0&&(r=q.split("."),q=r.shift(),r.sort()),k=q.indexOf(":")<0&&"on"+q,b=b[n.expando]?b:new n.Event(q,"object"==typeof b&&b),b.isTrigger=e?2:3,b.namespace=r.join("."),b.namespace_re=b.namespace?new RegExp("(^|\\.)"+r.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=d),c=null==c?[b]:n.makeArray(c,[b]),o=n.event.special[q]||{},e||!o.trigger||o.trigger.apply(d,c)!==!1)){if(!e&&!o.noBubble&&!n.isWindow(d)){for(i=o.delegateType||q,X.test(i+q)||(g=g.parentNode);g;g=g.parentNode)p.push(g),h=g;h===(d.ownerDocument||l)&&p.push(h.defaultView||h.parentWindow||a)}f=0;while((g=p[f++])&&!b.isPropagationStopped())b.type=f>1?i:o.bindType||q,m=(L.get(g,"events")||{})[b.type]&&L.get(g,"handle"),m&&m.apply(g,c),m=k&&g[k],m&&m.apply&&n.acceptData(g)&&(b.result=m.apply(g,c),b.result===!1&&b.preventDefault());return b.type=q,e||b.isDefaultPrevented()||o._default&&o._default.apply(p.pop(),c)!==!1||!n.acceptData(d)||k&&n.isFunction(d[q])&&!n.isWindow(d)&&(h=d[k],h&&(d[k]=null),n.event.triggered=q,d[q](),n.event.triggered=void 0,h&&(d[k]=h)),b.result}},dispatch:function(a){a=n.event.fix(a);var b,c,e,f,g,h=[],i=d.call(arguments),j=(L.get(this,"events")||{})[a.type]||[],k=n.event.special[a.type]||{};if(i[0]=a,a.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,a)!==!1){h=n.event.handlers.call(this,a,j),b=0;while((f=h[b++])&&!a.isPropagationStopped()){a.currentTarget=f.elem,c=0;while((g=f.handlers[c++])&&!a.isImmediatePropagationStopped())(!a.namespace_re||a.namespace_re.test(g.namespace))&&(a.handleObj=g,a.data=g.data,e=((n.event.special[g.origType]||{}).handle||g.handler).apply(f.elem,i),void 0!==e&&(a.result=e)===!1&&(a.preventDefault(),a.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,a),a.result}},handlers:function(a,b){var c,d,e,f,g=[],h=b.delegateCount,i=a.target;if(h&&i.nodeType&&(!a.button||"click"!==a.type))for(;i!==this;i=i.parentNode||this)if(i.disabled!==!0||"click"!==a.type){for(d=[],c=0;h>c;c++)f=b[c],e=f.selector+" ",void 0===d[e]&&(d[e]=f.needsContext?n(e,this).index(i)>=0:n.find(e,this,null,[i]).length),d[e]&&d.push(f);d.length&&g.push({elem:i,handlers:d})}return h]*)\/>/gi,ba=/<([\w:]+)/,ca=/<|&#?\w+;/,da=/<(?:script|style|link)/i,ea=/checked\s*(?:[^=]|=\s*.checked.)/i,fa=/^$|\/(?:java|ecma)script/i,ga=/^true\/(.*)/,ha=/^\s*\s*$/g,ia={option:[1,""],thead:[1,"","
    "],col:[2,"","
    "],tr:[2,"","
    "],td:[3,"","
    "],_default:[0,"",""]};ia.optgroup=ia.option,ia.tbody=ia.tfoot=ia.colgroup=ia.caption=ia.thead,ia.th=ia.td;function ja(a,b){return n.nodeName(a,"table")&&n.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function ka(a){return a.type=(null!==a.getAttribute("type"))+"/"+a.type,a}function la(a){var b=ga.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function ma(a,b){for(var c=0,d=a.length;d>c;c++)L.set(a[c],"globalEval",!b||L.get(b[c],"globalEval"))}function na(a,b){var c,d,e,f,g,h,i,j;if(1===b.nodeType){if(L.hasData(a)&&(f=L.access(a),g=L.set(b,f),j=f.events)){delete g.handle,g.events={};for(e in j)for(c=0,d=j[e].length;d>c;c++)n.event.add(b,e,j[e][c])}M.hasData(a)&&(h=M.access(a),i=n.extend({},h),M.set(b,i))}}function oa(a,b){var c=a.getElementsByTagName?a.getElementsByTagName(b||"*"):a.querySelectorAll?a.querySelectorAll(b||"*"):[];return void 0===b||b&&n.nodeName(a,b)?n.merge([a],c):c}function pa(a,b){var c=b.nodeName.toLowerCase();"input"===c&&T.test(a.type)?b.checked=a.checked:("input"===c||"textarea"===c)&&(b.defaultValue=a.defaultValue)}n.extend({clone:function(a,b,c){var d,e,f,g,h=a.cloneNode(!0),i=n.contains(a.ownerDocument,a);if(!(k.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||n.isXMLDoc(a)))for(g=oa(h),f=oa(a),d=0,e=f.length;e>d;d++)pa(f[d],g[d]);if(b)if(c)for(f=f||oa(a),g=g||oa(h),d=0,e=f.length;e>d;d++)na(f[d],g[d]);else na(a,h);return g=oa(h,"script"),g.length>0&&ma(g,!i&&oa(a,"script")),h},buildFragment:function(a,b,c,d){for(var e,f,g,h,i,j,k=b.createDocumentFragment(),l=[],m=0,o=a.length;o>m;m++)if(e=a[m],e||0===e)if("object"===n.type(e))n.merge(l,e.nodeType?[e]:e);else if(ca.test(e)){f=f||k.appendChild(b.createElement("div")),g=(ba.exec(e)||["",""])[1].toLowerCase(),h=ia[g]||ia._default,f.innerHTML=h[1]+e.replace(aa,"<$1>")+h[2],j=h[0];while(j--)f=f.lastChild;n.merge(l,f.childNodes),f=k.firstChild,f.textContent=""}else l.push(b.createTextNode(e));k.textContent="",m=0;while(e=l[m++])if((!d||-1===n.inArray(e,d))&&(i=n.contains(e.ownerDocument,e),f=oa(k.appendChild(e),"script"),i&&ma(f),c)){j=0;while(e=f[j++])fa.test(e.type||"")&&c.push(e)}return k},cleanData:function(a){for(var b,c,d,e,f=n.event.special,g=0;void 0!==(c=a[g]);g++){if(n.acceptData(c)&&(e=c[L.expando],e&&(b=L.cache[e]))){if(b.events)for(d in b.events)f[d]?n.event.remove(c,d):n.removeEvent(c,d,b.handle);L.cache[e]&&delete L.cache[e]}delete M.cache[c[M.expando]]}}}),n.fn.extend({text:function(a){return J(this,function(a){return void 0===a?n.text(this):this.empty().each(function(){(1===this.nodeType||11===this.nodeType||9===this.nodeType)&&(this.textContent=a)})},null,a,arguments.length)},append:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=ja(this,a);b.appendChild(a)}})},prepend:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=ja(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},remove:function(a,b){for(var c,d=a?n.filter(a,this):this,e=0;null!=(c=d[e]);e++)b||1!==c.nodeType||n.cleanData(oa(c)),c.parentNode&&(b&&n.contains(c.ownerDocument,c)&&ma(oa(c,"script")),c.parentNode.removeChild(c));return this},empty:function(){for(var a,b=0;null!=(a=this[b]);b++)1===a.nodeType&&(n.cleanData(oa(a,!1)),a.textContent="");return this},clone:function(a,b){return a=null==a?!1:a,b=null==b?a:b,this.map(function(){return n.clone(this,a,b)})},html:function(a){return J(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a&&1===b.nodeType)return b.innerHTML;if("string"==typeof a&&!da.test(a)&&!ia[(ba.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(aa,"<$1>");try{for(;d>c;c++)b=this[c]||{},1===b.nodeType&&(n.cleanData(oa(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=arguments[0];return this.domManip(arguments,function(b){a=this.parentNode,n.cleanData(oa(this)),a&&a.replaceChild(b,this)}),a&&(a.length||a.nodeType)?this:this.remove()},detach:function(a){return this.remove(a,!0)},domManip:function(a,b){a=e.apply([],a);var c,d,f,g,h,i,j=0,l=this.length,m=this,o=l-1,p=a[0],q=n.isFunction(p);if(q||l>1&&"string"==typeof p&&!k.checkClone&&ea.test(p))return this.each(function(c){var d=m.eq(c);q&&(a[0]=p.call(this,c,d.html())),d.domManip(a,b)});if(l&&(c=n.buildFragment(a,this[0].ownerDocument,!1,this),d=c.firstChild,1===c.childNodes.length&&(c=d),d)){for(f=n.map(oa(c,"script"),ka),g=f.length;l>j;j++)h=c,j!==o&&(h=n.clone(h,!0,!0),g&&n.merge(f,oa(h,"script"))),b.call(this[j],h,j);if(g)for(i=f[f.length-1].ownerDocument,n.map(f,la),j=0;g>j;j++)h=f[j],fa.test(h.type||"")&&!L.access(h,"globalEval")&&n.contains(i,h)&&(h.src?n._evalUrl&&n._evalUrl(h.src):n.globalEval(h.textContent.replace(ha,"")))}return this}}),n.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){n.fn[a]=function(a){for(var c,d=[],e=n(a),g=e.length-1,h=0;g>=h;h++)c=h===g?this:this.clone(!0),n(e[h])[b](c),f.apply(d,c.get());return this.pushStack(d)}});var qa,ra={};function sa(b,c){var d,e=n(c.createElement(b)).appendTo(c.body),f=a.getDefaultComputedStyle&&(d=a.getDefaultComputedStyle(e[0]))?d.display:n.css(e[0],"display");return e.detach(),f}function ta(a){var b=l,c=ra[a];return c||(c=sa(a,b),"none"!==c&&c||(qa=(qa||n("