diff --git a/interface/resources/icons/+android/backward.svg b/interface/resources/icons/+android_interface/backward.svg old mode 100755 new mode 100644 similarity index 100% rename from interface/resources/icons/+android/backward.svg rename to interface/resources/icons/+android_interface/backward.svg diff --git a/interface/resources/icons/+android/bubble-a.svg b/interface/resources/icons/+android_interface/bubble-a.svg similarity index 100% rename from interface/resources/icons/+android/bubble-a.svg rename to interface/resources/icons/+android_interface/bubble-a.svg diff --git a/interface/resources/icons/+android/bubble-i.svg b/interface/resources/icons/+android_interface/bubble-i.svg similarity index 100% rename from interface/resources/icons/+android/bubble-i.svg rename to interface/resources/icons/+android_interface/bubble-i.svg diff --git a/interface/resources/icons/+android/button-a.svg b/interface/resources/icons/+android_interface/button-a.svg similarity index 100% rename from interface/resources/icons/+android/button-a.svg rename to interface/resources/icons/+android_interface/button-a.svg diff --git a/interface/resources/icons/+android/button.svg b/interface/resources/icons/+android_interface/button.svg similarity index 100% rename from interface/resources/icons/+android/button.svg rename to interface/resources/icons/+android_interface/button.svg diff --git a/interface/resources/icons/+android/forward.svg b/interface/resources/icons/+android_interface/forward.svg similarity index 100% rename from interface/resources/icons/+android/forward.svg rename to interface/resources/icons/+android_interface/forward.svg diff --git a/interface/resources/icons/+android/go-a.svg b/interface/resources/icons/+android_interface/go-a.svg old mode 100755 new mode 100644 similarity index 100% rename from interface/resources/icons/+android/go-a.svg rename to interface/resources/icons/+android_interface/go-a.svg diff --git a/interface/resources/icons/+android/go-i.svg b/interface/resources/icons/+android_interface/go-i.svg similarity index 100% rename from interface/resources/icons/+android/go-i.svg rename to interface/resources/icons/+android_interface/go-i.svg diff --git a/interface/resources/icons/+android/hand.svg b/interface/resources/icons/+android_interface/hand.svg similarity index 100% rename from interface/resources/icons/+android/hand.svg rename to interface/resources/icons/+android_interface/hand.svg diff --git a/interface/resources/icons/+android/hide.svg b/interface/resources/icons/+android_interface/hide.svg similarity index 100% rename from interface/resources/icons/+android/hide.svg rename to interface/resources/icons/+android_interface/hide.svg diff --git a/interface/resources/icons/+android/mic-mute-a.svg b/interface/resources/icons/+android_interface/mic-mute-a.svg similarity index 100% rename from interface/resources/icons/+android/mic-mute-a.svg rename to interface/resources/icons/+android_interface/mic-mute-a.svg diff --git a/interface/resources/icons/+android/mic-mute-i.svg b/interface/resources/icons/+android_interface/mic-mute-i.svg similarity index 100% rename from interface/resources/icons/+android/mic-mute-i.svg rename to interface/resources/icons/+android_interface/mic-mute-i.svg diff --git a/interface/resources/icons/+android/mic-unmute-a.svg b/interface/resources/icons/+android_interface/mic-unmute-a.svg old mode 100755 new mode 100644 similarity index 100% rename from interface/resources/icons/+android/mic-unmute-a.svg rename to interface/resources/icons/+android_interface/mic-unmute-a.svg diff --git a/interface/resources/icons/+android/mic-unmute-i.svg b/interface/resources/icons/+android_interface/mic-unmute-i.svg similarity index 100% rename from interface/resources/icons/+android/mic-unmute-i.svg rename to interface/resources/icons/+android_interface/mic-unmute-i.svg diff --git a/interface/resources/icons/+android/myview-a.svg b/interface/resources/icons/+android_interface/myview-a.svg old mode 100755 new mode 100644 similarity index 100% rename from interface/resources/icons/+android/myview-a.svg rename to interface/resources/icons/+android_interface/myview-a.svg diff --git a/interface/resources/icons/+android/myview-hover.svg b/interface/resources/icons/+android_interface/myview-hover.svg old mode 100755 new mode 100644 similarity index 100% rename from interface/resources/icons/+android/myview-hover.svg rename to interface/resources/icons/+android_interface/myview-hover.svg diff --git a/interface/resources/icons/+android/myview-i.svg b/interface/resources/icons/+android_interface/myview-i.svg old mode 100755 new mode 100644 similarity index 100% rename from interface/resources/icons/+android/myview-i.svg rename to interface/resources/icons/+android_interface/myview-i.svg diff --git a/interface/resources/icons/+android/show-up.svg b/interface/resources/icons/+android_interface/show-up.svg similarity index 100% rename from interface/resources/icons/+android/show-up.svg rename to interface/resources/icons/+android_interface/show-up.svg diff --git a/interface/resources/icons/+android/stats.svg b/interface/resources/icons/+android_interface/stats.svg similarity index 100% rename from interface/resources/icons/+android/stats.svg rename to interface/resources/icons/+android_interface/stats.svg diff --git a/interface/resources/icons/+android/tick.svg b/interface/resources/icons/+android_interface/tick.svg similarity index 100% rename from interface/resources/icons/+android/tick.svg rename to interface/resources/icons/+android_interface/tick.svg diff --git a/interface/resources/qml/+android_interface/StatText.qml b/interface/resources/qml/+android_interface/StatText.qml new file mode 100644 index 0000000000..5dc8377030 --- /dev/null +++ b/interface/resources/qml/+android_interface/StatText.qml @@ -0,0 +1,9 @@ +import QtQuick 2.3 +import QtQuick.Controls 1.2 + +Text { + color: "white"; + style: Text.Outline; + styleColor: "black"; + font.pixelSize: 15; +} diff --git a/interface/resources/qml/+android_interface/Stats.qml b/interface/resources/qml/+android_interface/Stats.qml new file mode 100644 index 0000000000..fe56f3797b --- /dev/null +++ b/interface/resources/qml/+android_interface/Stats.qml @@ -0,0 +1,410 @@ +import Hifi 1.0 as Hifi +import QtQuick 2.3 +import '.' + +Item { + id: stats + + anchors.leftMargin: 300 + objectName: "StatsItem" + property int modality: Qt.NonModal + implicitHeight: row.height + implicitWidth: row.width + visible: false + + Component.onCompleted: { + stats.parentChanged.connect(fill); + fill(); + } + Component.onDestruction: { + stats.parentChanged.disconnect(fill); + } + + function fill() { + // This will cause a warning at shutdown, need to find another way to remove + // the warning other than filling the anchors to the parent + anchors.horizontalCenter = parent.horizontalCenter + } + + Hifi.Stats { + id: root + objectName: "Stats" + implicitHeight: row.height + implicitWidth: row.width + + anchors.horizontalCenter: parent.horizontalCenter + readonly property string bgColor: "#AA111111" + + Row { + id: row + spacing: 8 + Rectangle { + width: generalCol.width + 8; + height: generalCol.height + 8; + color: root.bgColor; + + MouseArea { + anchors.fill: parent + onClicked: { root.expanded = !root.expanded; } + hoverEnabled: true + } + + Column { + id: generalCol + spacing: 4; x: 4; y: 4; + StatText { + text: "Servers: " + root.serverCount + } + StatText { + text: "Avatars: " + root.avatarCount + } + StatText { + text: "Game Rate: " + root.gameLoopRate + } + StatText { + visible: root.expanded + text: root.gameUpdateStats + } + StatText { + text: "Render Rate: " + root.renderrate.toFixed(2); + } + StatText { + text: "Present Rate: " + root.presentrate.toFixed(2); + } + StatText { + visible: root.expanded + text: " Present New Rate: " + root.presentnewrate.toFixed(2); + } + StatText { + visible: root.expanded + text: " Present Drop Rate: " + root.presentdroprate.toFixed(2); + } + StatText { + text: "Stutter Rate: " + root.stutterrate.toFixed(3); + visible: root.stutterrate != -1; + } + StatText { + text: "Missed Frame Count: " + root.appdropped; + visible: root.appdropped > 0; + } + StatText { + text: "Long Render Count: " + root.longrenders; + visible: root.longrenders > 0; + } + StatText { + text: "Long Submit Count: " + root.longsubmits; + visible: root.longsubmits > 0; + } + StatText { + text: "Long Frame Count: " + root.longframes; + visible: root.longframes > 0; + } + StatText { + text: "Packets In/Out: " + root.packetInCount + "/" + root.packetOutCount + } + StatText { + text: "Mbps In/Out: " + root.mbpsIn.toFixed(2) + "/" + root.mbpsOut.toFixed(2) + } + StatText { + visible: root.expanded + text: "Asset Mbps In/Out: " + root.assetMbpsIn.toFixed(2) + "/" + root.assetMbpsOut.toFixed(2) + } + StatText { + visible: root.expanded + text: "Avatars Updated: " + root.updatedAvatarCount + } + StatText { + visible: root.expanded + text: "Avatars NOT Updated: " + root.notUpdatedAvatarCount + } + } + } + + Rectangle { + width: pingCol.width + 8 + height: pingCol.height + 8 + color: root.bgColor; + visible: root.audioPing != -2 + MouseArea { + anchors.fill: parent + onClicked: { root.expanded = !root.expanded; } + hoverEnabled: true + } + Column { + id: pingCol + spacing: 4; x: 4; y: 4; + StatText { + text: "Audio ping/loss: " + root.audioPing + "/" + root.audioPacketLoss + "%" + } + StatText { + text: "Avatar ping: " + root.avatarPing + } + StatText { + text: "Entities avg ping: " + root.entitiesPing + } + StatText { + text: "Asset ping: " + root.assetPing + } + StatText { + visible: root.expanded; + text: "Messages max ping: " + root.messagePing + } + } + } + + Rectangle { + width: geoCol.width + 8 + height: geoCol.height + 8 + color: root.bgColor; + MouseArea { + anchors.fill: parent + onClicked: { root.expanded = !root.expanded; } + hoverEnabled: true + } + Column { + id: geoCol + spacing: 4; x: 4; y: 4; + StatText { + text: "Position: " + root.position.x.toFixed(1) + ", " + + root.position.y.toFixed(1) + ", " + root.position.z.toFixed(1) + } + StatText { + text: "Speed: " + root.speed.toFixed(1) + } + StatText { + text: "Yaw: " + root.yaw.toFixed(1) + } + StatText { + visible: root.expanded; + text: "Avatar Mixer In: " + root.avatarMixerInKbps + " kbps, " + + root.avatarMixerInPps + "pps"; + } + StatText { + visible: root.expanded; + text: "Avatar Mixer Out: " + root.avatarMixerOutKbps + " kbps, " + + root.avatarMixerOutPps + "pps, " + + root.myAvatarSendRate.toFixed(2) + "hz"; + } + StatText { + visible: root.expanded; + text: "Audio Mixer In: " + root.audioMixerInKbps + " kbps, " + + root.audioMixerInPps + "pps"; + } + StatText { + visible: root.expanded; + text: "Audio Mixer Out: " + root.audioMixerOutKbps + " kbps, " + + root.audioMixerOutPps + "pps"; + } + StatText { + visible: root.expanded; + text: "Audio In Audio: " + root.audioAudioInboundPPS + " pps, " + + "Silent: " + root.audioSilentInboundPPS + " pps"; + } + StatText { + visible: root.expanded; + text: "Audio Out Mic: " + root.audioOutboundPPS + " pps, " + + "Silent: " + root.audioSilentOutboundPPS + " pps"; + } + StatText { + visible: root.expanded; + text: "Audio Codec: " + root.audioCodec + " Noise Gate: " + + root.audioNoiseGate; + } + StatText { + visible: root.expanded; + text: "Entity Servers In: " + root.entityPacketsInKbps + " kbps"; + } + StatText { + visible: root.expanded; + text: "Downloads: " + root.downloads + "/" + root.downloadLimit + + ", Pending: " + root.downloadsPending; + } + StatText { + visible: root.expanded; + text: "Processing: " + root.processing + + ", Pending: " + root.processingPending; + } + StatText { + visible: root.expanded && root.downloadUrls.length > 0; + text: "Download URLs:" + } + ListView { + width: geoCol.width + height: root.downloadUrls.length * 15 + + visible: root.expanded && root.downloadUrls.length > 0; + + model: root.downloadUrls + delegate: StatText { + visible: root.expanded; + text: modelData.length > 30 + ? modelData.substring(0, 5) + "..." + modelData.substring(modelData.length - 22) + : modelData + } + } + } + } + Rectangle { + width: octreeCol.width + 8 + height: octreeCol.height + 8 + color: root.bgColor; + MouseArea { + anchors.fill: parent + onClicked: { root.expanded = !root.expanded; } + hoverEnabled: true + } + Column { + id: octreeCol + spacing: 4; x: 4; y: 4; + StatText { + text: "Render Engine: " + root.engineFrameTime.toFixed(1) + " ms" + } + StatText { + text: "Batch: " + root.batchFrameTime.toFixed(1) + " ms" + } + StatText { + text: "GPU: " + root.gpuFrameTime.toFixed(1) + " ms" + } + StatText { + text: "Drawcalls: " + root.drawcalls + } + StatText { + text: "Triangles: " + root.triangles + + " / Material Switches: " + root.materialSwitches + } + StatText { + visible: root.expanded; + text: "GPU Free Memory: " + root.gpuFreeMemory + " MB"; + } + StatText { + visible: root.expanded; + text: "GPU Textures: "; + } + StatText { + visible: root.expanded; + text: " Count: " + root.gpuTextures; + } + StatText { + visible: root.expanded; + text: " Pressure State: " + root.gpuTextureMemoryPressureState; + } + StatText { + text: " Resource Allocated / Populated / Pending: "; + } + StatText { + text: " " + root.gpuTextureResourceMemory + " / " + root.gpuTextureResourcePopulatedMemory + " / " + root.texturePendingTransfers + " MB"; + } + StatText { + visible: root.expanded; + text: " Resident Memory: " + root.gpuTextureResidentMemory + " MB"; + } + StatText { + visible: root.expanded; + text: " Framebuffer Memory: " + root.gpuTextureFramebufferMemory + " MB"; + } + StatText { + visible: root.expanded; + text: " External Memory: " + root.gpuTextureExternalMemory + " MB"; + } + StatText { + visible: root.expanded; + text: "GPU Buffers: " + } + StatText { + visible: root.expanded; + text: " Count: " + root.gpuBuffers; + } + StatText { + visible: root.expanded; + text: " Memory: " + root.gpuBufferMemory + " MB"; + } + StatText { + visible: root.expanded; + text: "GL Swapchain Memory: " + root.glContextSwapchainMemory + " MB"; + } + StatText { + visible: root.expanded; + text: "QML Texture Memory: " + root.qmlTextureMemory + " MB"; + } + StatText { + visible: root.expanded; + text: "Items rendered / considered: " + + root.itemRendered + " / " + root.itemConsidered; + } + StatText { + visible: root.expanded; + text: " out of view: " + root.itemOutOfView + + " too small: " + root.itemTooSmall; + } + StatText { + visible: root.expanded; + text: "Shadows rendered / considered: " + + root.shadowRendered + " / " + root.shadowConsidered; + } + StatText { + visible: root.expanded; + text: " out of view: " + root.shadowOutOfView + + " too small: " + root.shadowTooSmall; + } + StatText { + visible: !root.expanded + text: "Octree Elements Server: " + root.serverElements + + " Local: " + root.localElements; + } + StatText { + visible: root.expanded + text: "Octree Sending Mode: " + root.sendingMode; + } + StatText { + visible: root.expanded + text: "Octree Packets to Process: " + root.packetStats; + } + StatText { + visible: root.expanded + text: "Octree Elements - "; + } + StatText { + visible: root.expanded + text: "\tServer: " + root.serverElements + + " Internal: " + root.serverInternal + + " Leaves: " + root.serverLeaves; + } + StatText { + visible: root.expanded + text: "\tLocal: " + root.localElements + + " Internal: " + root.localInternal + + " Leaves: " + root.localLeaves; + } + StatText { + visible: root.expanded + text: "LOD: " + root.lodStatus; + } + } + } + } + + Rectangle { + y: 250 + visible: root.timingExpanded + width: perfText.width + 8 + height: perfText.height + 8 + color: root.bgColor; + StatText { + x: 4; y: 4 + id: perfText + font.family: root.monospaceFont + text: "------------------------------------------ Function " + + "--------------------------------------- --msecs- -calls--\n" + + root.timingStats; + } + } + + Connections { + target: root.parent + onWidthChanged: { + root.x = root.parent.width - root.width; + } + } + } + +} diff --git a/interface/resources/qml/+android/Web3DSurface.qml b/interface/resources/qml/+android_interface/Web3DSurface.qml similarity index 100% rename from interface/resources/qml/+android/Web3DSurface.qml rename to interface/resources/qml/+android_interface/Web3DSurface.qml diff --git a/interface/resources/qml/LoginDialog/+android_interface/LinkAccountBody.qml b/interface/resources/qml/LoginDialog/+android_interface/LinkAccountBody.qml new file mode 100644 index 0000000000..a40110b1e9 --- /dev/null +++ b/interface/resources/qml/LoginDialog/+android_interface/LinkAccountBody.qml @@ -0,0 +1,292 @@ +// +// LinkAccountBody.qml +// +// Created by Clement on 7/18/16 +// 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 +// + +import Hifi 1.0 +import QtQuick 2.4 +import QtQuick.Controls 1.4 +import QtQuick.Controls.Styles 1.4 as OriginalStyles + +import controlsUit 1.0 +import stylesUit 1.0 + +Item { + id: linkAccountBody + + clip: true + height: 300 + width: root.pane.width + property bool failAfterSignUp: false + function login() { + mainTextContainer.visible = false + toggleLoading(true) + loginDialog.login(usernameField.text, passwordField.text) + } + + property bool keyboardEnabled: false + property bool keyboardRaised: false + property bool punctuationMode: false + + onKeyboardRaisedChanged: d.resize(); + + QtObject { + id: d + readonly property int minWidth: 1440 + readonly property int maxWidth: 3840 + readonly property int minHeight: 150 + readonly property int maxHeight: 660 + + function resize() { + var targetWidth = Math.max(titleWidth, form.contentWidth); + var targetHeight = hifi.dimensions.contentSpacing.y + mainTextContainer.height + + 4 * hifi.dimensions.contentSpacing.y + form.height + + hifi.dimensions.contentSpacing.y + buttons.height; + + if (additionalInformation.visible) { + targetWidth = Math.max(targetWidth, additionalInformation.width); + targetHeight += hifi.dimensions.contentSpacing.y + additionalInformation.height + } + + parent.width = root.width = Math.max(d.minWidth, Math.min(d.maxWidth, targetWidth)); + parent.height = 420; + /*root.height = Math.max(d.minHeight, Math.min(d.maxHeight, targetHeight)) + + (keyboardEnabled && keyboardRaised ? (200 + 2 * hifi.dimensions.contentSpacing.y) : hifi.dimensions.contentSpacing.y);*/ + } + } + + function toggleLoading(isLoading) { + linkAccountSpinner.visible = isLoading + form.visible = !isLoading + + if (loginDialog.isSteamRunning()) { + additionalInformation.visible = !isLoading + } + + leftButton.visible = !isLoading + buttons.visible = !isLoading + } + + BusyIndicator { + id: linkAccountSpinner + + anchors { + top: parent.top + horizontalCenter: parent.horizontalCenter + topMargin: hifi.dimensions.contentSpacing.y + } + + visible: false + running: true + + width: 144 + height: 144 + } + + ShortcutText { + id: mainTextContainer + anchors { + top: parent.top + left: parent.left + margins: 0 + topMargin: hifi.dimensions.contentSpacing.y / 2 + } + + visible: false + + text: qsTr("Username or password incorrect.") + wrapMode: Text.WordWrap + color: hifi.colors.redAccent + lineHeight: 1 + lineHeightMode: Text.ProportionalHeight + horizontalAlignment: Text.AlignHCenter + } + + Column { + id: form + anchors { + top: mainTextContainer.bottom + left: parent.left + margins: 0 + topMargin: 0 // hifi.dimensions.contentSpacing.y + } + spacing: hifi.dimensions.contentSpacing.y / 2 + + TextField { + id: usernameField + anchors { + horizontalCenter: parent.horizontalCenter + } + width: 1080 + placeholderText: qsTr("Username or Email") + } + + TextField { + id: passwordField + anchors { + horizontalCenter: parent.horizontalCenter + } + width: 1080 + + placeholderText: qsTr("Password") + echoMode: TextInput.Password + + Keys.onReturnPressed: linkAccountBody.login() + } + } + + InfoItem { + id: additionalInformation + anchors { + top: form.bottom + left: parent.left + margins: 0 + topMargin: hifi.dimensions.contentSpacing.y + } + + visible: loginDialog.isSteamRunning() + + text: qsTr("Your steam account informations will not be exposed to other users.") + wrapMode: Text.WordWrap + color: hifi.colors.baseGrayHighlight + lineHeight: 3 + lineHeightMode: Text.ProportionalHeight + horizontalAlignment: Text.AlignHCenter + } + + // Override ScrollingWindow's keyboard that would be at very bottom of dialog. + Keyboard { + raised: keyboardEnabled && keyboardRaised + numeric: punctuationMode + anchors { + left: parent.left + right: parent.right + bottom: buttons.top + bottomMargin: keyboardRaised ? 2 * hifi.dimensions.contentSpacing.y : 0 + } + } + + Row { + id: leftButton + anchors { + left: parent.left + top: form.bottom + topMargin: hifi.dimensions.contentSpacing.y / 2 + } + + spacing: hifi.dimensions.contentSpacing.x + onHeightChanged: d.resize(); onWidthChanged: d.resize(); + + Button { + anchors.verticalCenter: parent.verticalCenter + + text: qsTr("Sign Up") + visible: !loginDialog.isSteamRunning() + + onClicked: { + bodyLoader.setSource("SignUpBody.qml") + bodyLoader.item.width = root.pane.width + bodyLoader.item.height = root.pane.height + } + } + } + + Row { + id: buttons + anchors { + right: parent.right + top: form.bottom + topMargin: hifi.dimensions.contentSpacing.y / 2 + } + spacing: hifi.dimensions.contentSpacing.x + onHeightChanged: d.resize(); onWidthChanged: d.resize(); + + Button { + id: linkAccountButton + anchors.verticalCenter: parent.verticalCenter + + text: qsTr(loginDialog.isSteamRunning() ? "Link Account" : "Login") + color: hifi.buttons.blue + + onClicked: { + Qt.inputMethod.hide(); + linkAccountBody.login(); + } + } + + Button { + anchors.verticalCenter: parent.verticalCenter + + text: qsTr("Cancel") + + onClicked: { + Qt.inputMethod.hide(); + root.tryDestroy(); + } + } + } + + Component.onCompleted: { + root.title = qsTr("Sign Into High Fidelity") + root.iconText = "<" + keyboardEnabled = HMD.active; + d.resize(); + + if (failAfterSignUp) { + mainTextContainer.text = "Account created successfully." + mainTextContainer.visible = true + } + + //usernameField.forceActiveFocus(); + } + + Connections { + target: loginDialog + onHandleLoginCompleted: { + console.log("Login Succeeded, linking steam account") + + if (loginDialog.isSteamRunning()) { + loginDialog.linkSteam() + } else { + bodyLoader.setSource("../WelcomeBody.qml", { "welcomeBack" : true }) + bodyLoader.item.width = root.pane.width + bodyLoader.item.height = root.pane.height + } + } + onHandleLoginFailed: { + console.log("Login Failed") + mainTextContainer.visible = true + toggleLoading(false) + } + onHandleLinkCompleted: { + console.log("Link Succeeded") + + bodyLoader.setSource("../WelcomeBody.qml", { "welcomeBack" : true }) + bodyLoader.item.width = root.pane.width + bodyLoader.item.height = root.pane.height + } + onHandleLinkFailed: { + console.log("Link Failed") + toggleLoading(false) + } + } + + Keys.onPressed: { + if (!visible) { + return + } + + switch (event.key) { + case Qt.Key_Enter: + case Qt.Key_Return: + event.accepted = true + linkAccountBody.login() + break + } + } +} diff --git a/interface/resources/qml/LoginDialog/+android_interface/SignUpBody.qml b/interface/resources/qml/LoginDialog/+android_interface/SignUpBody.qml new file mode 100644 index 0000000000..10909e4c85 --- /dev/null +++ b/interface/resources/qml/LoginDialog/+android_interface/SignUpBody.qml @@ -0,0 +1,297 @@ +// +// SignUpBody.qml +// +// Created by Stephen Birarda on 7 Dec 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 +// + +import Hifi 1.0 +import QtQuick 2.4 +import QtQuick.Controls 1.4 +import QtQuick.Controls.Styles 1.4 as OriginalStyles + +import controlsUit 1.0 +import stylesUit 1.0 + +Item { + id: signupBody + + clip: true + height: root.pane.height + width: root.pane.width + + function signup() { + mainTextContainer.visible = false + toggleLoading(true) + loginDialog.signup(emailField.text, usernameField.text, passwordField.text) + } + + property bool keyboardEnabled: false + property bool keyboardRaised: false + property bool punctuationMode: false + + onKeyboardRaisedChanged: d.resize(); + + QtObject { + id: d + readonly property int minWidth: 960 + readonly property int maxWidth: 2560 + readonly property int minHeight: 240 + readonly property int maxHeight: 1480 + + function resize() { + var targetWidth = Math.max(titleWidth, form.contentWidth); + var targetHeight = hifi.dimensions.contentSpacing.y + mainTextContainer.height + + 4 * hifi.dimensions.contentSpacing.y + form.height + + hifi.dimensions.contentSpacing.y + buttons.height; + + parent.width = root.width = Math.max(d.minWidth, Math.min(d.maxWidth, targetWidth)); + //parent.height = 650; + parent.height = root.height = Math.max(d.minHeight, Math.min(d.maxHeight, targetHeight)); + + } + } + + function toggleLoading(isLoading) { + linkAccountSpinner.visible = isLoading + form.visible = !isLoading + + leftButton.visible = !isLoading + buttons.visible = !isLoading + } + + BusyIndicator { + id: linkAccountSpinner + + anchors { + top: parent.top + horizontalCenter: parent.horizontalCenter + topMargin: hifi.dimensions.contentSpacing.y + } + + visible: false + running: true + + width: 48 + height: 48 + } + + ShortcutText { + id: mainTextContainer + anchors { + top: parent.top + left: parent.left + margins: 0 + topMargin: hifi.dimensions.contentSpacing.y + } + + visible: false + + text: qsTr("There was an unknown error while creating your account.") + wrapMode: Text.WordWrap + color: hifi.colors.redAccent + horizontalAlignment: Text.AlignLeft + } + + Column { + id: form + anchors { + top: mainTextContainer.bottom + left: parent.left + margins: 0 + topMargin: 0; // 2 * hifi.dimensions.contentSpacing.y + } + spacing: hifi.dimensions.contentSpacing.y / 2 + + Row { + spacing: hifi.dimensions.contentSpacing.x + + TextField { + id: emailField + anchors { + verticalCenter: parent.verticalCenter + } + width: 780 + + placeholderText: "Email" + } + } + + Row { + spacing: hifi.dimensions.contentSpacing.x + + TextField { + id: usernameField + anchors { + verticalCenter: parent.verticalCenter + } + width: 780 + + placeholderText: "Username" + } + + ShortcutText { + anchors { + verticalCenter: parent.verticalCenter + } + + text: qsTr("No spaces / special chars.") + + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter + + color: hifi.colors.blueAccent + } + } + + Row { + spacing: hifi.dimensions.contentSpacing.x + + TextField { + id: passwordField + anchors { + verticalCenter: parent.verticalCenter + } + width: 780 + + placeholderText: "Password" + echoMode: TextInput.Password + } + + ShortcutText { + anchors { + verticalCenter: parent.verticalCenter + } + + text: qsTr("At least 6 characters") + + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter + + color: hifi.colors.blueAccent + } + } + + } + + // Override ScrollingWindow's keyboard that would be at very bottom of dialog. + Keyboard { + raised: keyboardEnabled && keyboardRaised + numeric: punctuationMode + anchors { + left: parent.left + right: parent.right + bottom: buttons.top + bottomMargin: keyboardRaised ? 2 * hifi.dimensions.contentSpacing.y : 0 + } + } + + Row { + id: leftButton + anchors { + left: parent.left + top: form.bottom + topMargin: hifi.dimensions.contentSpacing.y// / 2 + } + + spacing: hifi.dimensions.contentSpacing.x + onHeightChanged: d.resize(); onWidthChanged: d.resize(); + + Button { + anchors.verticalCenter: parent.verticalCenter + + text: qsTr("Existing User") + + onClicked: { + bodyLoader.setSource("LinkAccountBody.qml") + bodyLoader.item.width = root.pane.width + bodyLoader.item.height = root.pane.height + } + } + } + + Row { + id: buttons + anchors { + right: parent.right + top: form.bottom + topMargin: hifi.dimensions.contentSpacing.y / 2 + } + spacing: hifi.dimensions.contentSpacing.x + onHeightChanged: d.resize(); onWidthChanged: d.resize(); + + Button { + id: linkAccountButton + anchors.verticalCenter: parent.verticalCenter + + text: qsTr("Sign Up") + color: hifi.buttons.blue + + onClicked: signupBody.signup() + } + + Button { + anchors.verticalCenter: parent.verticalCenter + + text: qsTr("Cancel") + + onClicked: root.destroy() + } + } + + Component.onCompleted: { + root.title = qsTr("Create an Account") + root.iconText = "<" + keyboardEnabled = HMD.active; + d.resize(); + + emailField.forceActiveFocus(); + } + + Connections { + target: loginDialog + onHandleSignupCompleted: { + console.log("Sign Up Succeeded"); + + // now that we have an account, login with that username and password + loginDialog.login(usernameField.text, passwordField.text) + } + onHandleSignupFailed: { + console.log("Sign Up Failed") + toggleLoading(false) + + mainTextContainer.text = errorString + mainTextContainer.visible = true + + d.resize(); + } + onHandleLoginCompleted: { + bodyLoader.setSource("../WelcomeBody.qml", { "welcomeBack": false }) + bodyLoader.item.width = root.pane.width + bodyLoader.item.height = root.pane.height + } + onHandleLoginFailed: { + // we failed to login, show the LoginDialog so the user will try again + bodyLoader.setSource("LinkAccountBody.qml", { "failAfterSignUp": true }) + bodyLoader.item.width = root.pane.width + bodyLoader.item.height = root.pane.height + } + } + + Keys.onPressed: { + if (!visible) { + return + } + + switch (event.key) { + case Qt.Key_Enter: + case Qt.Key_Return: + event.accepted = true + signupBody.signup() + break + } + } +} diff --git a/interface/resources/qml/controlsUit/+android_interface/ImageButton.qml b/interface/resources/qml/controlsUit/+android_interface/ImageButton.qml new file mode 100644 index 0000000000..88eaf95d76 --- /dev/null +++ b/interface/resources/qml/controlsUit/+android_interface/ImageButton.qml @@ -0,0 +1,82 @@ +// +// ImageButton.qml +// interface/resources/qml/controlsUit +// +// Created by Gabriel Calero & Cristian Duarte on 12 Oct 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 +// + +import QtQuick 2.5 +import QtQuick.Layouts 1.3 +import "../stylesUit" as HifiStyles + +Item { + id: button + + property string text: "" + property string source : "" + property string hoverSource : "" + property real fontSize: 10 + property string fontColor: "#FFFFFF" + property string hoverFontColor: "#000000" + + signal clicked(); + + Rectangle { + color: "transparent" + anchors.fill: parent + Image { + id: image + anchors.fill: parent + source: button.source + } + + HifiStyles.FiraSansRegular { + id: buttonText + anchors.centerIn: parent + text: button.text + color: button.fontColor + font.pixelSize: button.fontSize + } + + MouseArea { + anchors.fill: parent + onClicked: button.clicked(); + onEntered: { + button.state = "hover state"; + } + onExited: { + button.state = "base state"; + } + } + + + } + states: [ + State { + name: "hover state" + PropertyChanges { + target: image + source: button.hoverSource + } + PropertyChanges { + target: buttonText + color: button.hoverFontColor + } + }, + State { + name: "base state" + PropertyChanges { + target: image + source: button.source + } + PropertyChanges { + target: buttonText + color: button.fontColor + } + } + ] +} diff --git a/interface/resources/qml/desktop/+android_interface/FocusHack.qml b/interface/resources/qml/desktop/+android_interface/FocusHack.qml new file mode 100644 index 0000000000..38253fdec1 --- /dev/null +++ b/interface/resources/qml/desktop/+android_interface/FocusHack.qml @@ -0,0 +1,26 @@ +// +// FocusHack.qml +// +// Created by Bradley Austin Davis on 21 Jan 2015 +// 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 +// + +import QtQuick 2.5 + +FocusScope { + id: root + objectName: "FocusHack" + + TextInput { + id: textInput; + focus: true + width: 10; height: 10 + onActiveFocusChanged: root.destroy() + } + + function start() { + } +} diff --git a/interface/resources/qml/hifi/+android_interface/ActionBar.qml b/interface/resources/qml/hifi/+android_interface/ActionBar.qml new file mode 100644 index 0000000000..3c58156f30 --- /dev/null +++ b/interface/resources/qml/hifi/+android_interface/ActionBar.qml @@ -0,0 +1,71 @@ +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import QtQuick.Controls.Styles 1.4 +import QtQuick.Layouts 1.3 +import Qt.labs.settings 1.0 +import stylesUit 1.0 +import controlsUit 1.0 as HifiControlsUit +import "../../controls" as HifiControls +import ".." + +Item { + id: actionBar + x:0 + y:0 + width: 300 + height: 300 + z: -1 + + signal sendToScript(var message); + signal windowClosed(); + + property bool shown: true + + onShownChanged: { + actionBar.visible = shown; + } + + Rectangle { + anchors.fill : parent + color: "transparent" + Flow { + id: flowMain + spacing: 10 + flow: Flow.TopToBottom + layoutDirection: Flow.TopToBottom + anchors.fill: parent + anchors.margins: 4 + } + } + + Component.onCompleted: { + // put on bottom + x = 7; + y = 7; + width = 300; + height = 300; + } + + function addButton(properties) { + var component = Qt.createComponent("button.qml"); + if (component.status == Component.Ready) { + var button = component.createObject(flowMain); + // copy all properites to button + var keys = Object.keys(properties).forEach(function (key) { + button[key] = properties[key]; + }); + return button; + } else if( component.status == Component.Error) { + console.log("Load button errors " + component.errorString()); + } + } + + function urlHelper(src) { + if (src.match(/\bhttp/)) { + return src; + } else { + return "../../../" + src; + } + } + +} diff --git a/interface/resources/qml/hifi/+android_interface/AudioBar.qml b/interface/resources/qml/hifi/+android_interface/AudioBar.qml new file mode 100644 index 0000000000..912572fdf8 --- /dev/null +++ b/interface/resources/qml/hifi/+android_interface/AudioBar.qml @@ -0,0 +1,84 @@ +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import QtQuick.Controls.Styles 1.4 +import QtQuick.Layouts 1.3 +import Qt.labs.settings 1.0 +import stylesUit 1.0 +import controlsUit 1.0 as HifiControlsUit +import "../../controls" as HifiControls +import ".." + +Item { + id: bar + x:0 + y:0 + width: 300 + height: 300 + z: -1 + + signal sendToScript(var message); + signal windowClosed(); + + property bool shown: true + + onShownChanged: { + bar.visible = shown; + } + + Rectangle { + anchors.fill : parent + color: "transparent" + Flow { + id: flowMain + spacing: 10 + flow: Flow.TopToBottom + layoutDirection: Flow.TopToBottom + anchors.fill: parent + anchors.margins: 4 + } + } + + function relocateAndResize(newWindowWidth, newWindowHeight) { + x = newWindowWidth-328; + y = 19; + width = 300; + height = 300; + } + + function onWindowGeometryChanged(rect) { + relocateAndResize(rect.width, rect.height); + } + + Component.onCompleted: { + relocateAndResize(parent.width, parent.height); + Window.geometryChanged.connect(onWindowGeometryChanged); // In devices with bars appearing at startup we should listen for this + } + + Component.onDestruction: { + Window.geometryChanged.disconnect(onWindowGeometryChanged); + } + + + function addButton(properties) { + var component = Qt.createComponent("button.qml"); + if (component.status == Component.Ready) { + var button = component.createObject(flowMain); + // copy all properites to button + var keys = Object.keys(properties).forEach(function (key) { + button[key] = properties[key]; + }); + return button; + } else if( component.status == Component.Error) { + console.log("Load button errors " + component.errorString()); + } + } + + function urlHelper(src) { + if (src.match(/\bhttp/)) { + return src; + } else { + return "../../../" + src; + } + } + +} diff --git a/interface/resources/qml/hifi/+android_interface/AvatarOption.qml b/interface/resources/qml/hifi/+android_interface/AvatarOption.qml new file mode 100644 index 0000000000..7eba3c2a67 --- /dev/null +++ b/interface/resources/qml/hifi/+android_interface/AvatarOption.qml @@ -0,0 +1,117 @@ +// +// AvatarOption.qml +// interface/resources/qml/hifi/android +// +// Created by Cristian Duarte & Gabriel Calero on 12 Oct 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 +// + +import QtQuick.Layouts 1.3 +import QtQuick 2.5 +import controlsUit 1.0 as HifiControlsUit + +ColumnLayout { + id: itemRoot + + property string type: ""; + + property string thumbnailUrl: ""; + property string avatarUrl: ""; + property string avatarName: ""; + property bool avatarSelected: false; + + property string methodName: ""; + property string actionText: ""; + + spacing: 4 * 3 + signal sendToParentQml(var message); + + Image { + id: itemImage + Layout.preferredWidth: 250 * 3 + Layout.preferredHeight: 140 * 3 + source: thumbnailUrl + asynchronous: true + fillMode: Image.PreserveAspectFit + + MouseArea { + id: itemArea + anchors.fill: parent + hoverEnabled: true + enabled: true + onClicked: { + if (type=="avatar") { + if (!avatarSelected) sendToParentQml({ method: "selectAvatar", params: { avatarUrl: avatarUrl } }); + } else { + sendToParentQml({ method: methodName, params: { } }); + } + } + } + + } + + Text { + id: itemName + text: avatarName + color: "#FFFFFF" + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + anchors.horizontalCenter: itemImage.horizontalCenter + font.pointSize: 5*3 + wrapMode: Text.WordWrap + width: parent + MouseArea { + id: itemNameArea + anchors.fill: parent + hoverEnabled: true + enabled: true + onClicked: { + if (type=="avatar") { + if (!avatarSelected) sendToParentQml({ method: "selectAvatar", params: { avatarUrl: avatarUrl } }); + } else { + sendToParentQml({ method: methodName, params: { } }); + } + } + } + } + + HifiControlsUit.ImageButton { + width: 140*3 + height: 35*3 + text: type=="extra" ? actionText: "CHOOSE" + source: "../../../../icons/button.svg" + hoverSource: "../../../../icons/button-a.svg" + fontSize: 18*3 + fontColor: "#2CD8FF" + hoverFontColor: "#FFFFFF" + anchors { + horizontalCenter: itemName.horizontalCenter + } + visible: !avatarSelected + onClicked: { + if (type=="avatar") { + if (!avatarSelected) sendToParentQml({ method: "selectAvatar", params: { avatarUrl: avatarUrl } }); + } else { + sendToParentQml({ method: methodName, params: { } }); + } + } + } + + Image { + id: tickImage + width: 35 * 3 + height: 35 * 3 + source: "../../../icons/tick.svg" + anchors { + horizontalCenter: itemName.horizontalCenter + } + visible: avatarSelected + } + + Component.onCompleted:{ + sendToParentQml.connect(sendToScript); + } +} diff --git a/interface/resources/qml/hifi/+android_interface/Desktop.qml b/interface/resources/qml/hifi/+android_interface/Desktop.qml new file mode 100644 index 0000000000..99d792b664 --- /dev/null +++ b/interface/resources/qml/hifi/+android_interface/Desktop.qml @@ -0,0 +1,63 @@ +import QtQuick 2.5 +import QtQuick.Controls 1.4 + +import Qt.labs.settings 1.0 + +import "../desktop" as OriginalDesktop +import ".." +import "." +import "./toolbars" + +OriginalDesktop.Desktop { + id: desktop + + MouseArea { + id: hoverWatch + anchors.fill: parent + hoverEnabled: true + propagateComposedEvents: true + scrollGestureEnabled: false // we don't need/want these + onEntered: ApplicationCompositor.reticleOverDesktop = true + onExited: ApplicationCompositor.reticleOverDesktop = false + acceptedButtons: Qt.NoButton + + + } + + + Component { id: toolbarBuilder; Toolbar { } } + // This used to create sysToolbar dynamically with a call to getToolbar() within onCompleted. + // Beginning with QT 5.6, this stopped working, as anything added to toolbars too early got + // wiped during startup. + Toolbar { + id: sysToolbar; + objectName: "com.highfidelity.interface.toolbar.system"; + // Magic: sysToolbar.x and y come from settings, and are bound before the properties specified here are applied. + x: sysToolbar.x; + y: sysToolbar.y; + } + property var toolbars: (function (map) { // answer dictionary preloaded with sysToolbar + map[sysToolbar.objectName] = sysToolbar; + return map; })({}); + + Component.onCompleted: { + } + + // Accept a download through the webview + property bool webViewProfileSetup: false + property string currentUrl: "" + property string adaptedPath: "" + property string tempDir: "" + + // Create or fetch a toolbar with the given name + function getToolbar(name) { + var result = toolbars[name]; + if (!result) { + result = toolbars[name] = toolbarBuilder.createObject(desktop, {}); + result.objectName = name; + } + return result; + } +} + + diff --git a/interface/resources/qml/hifi/+android_interface/HifiConstants.qml b/interface/resources/qml/hifi/+android_interface/HifiConstants.qml new file mode 100644 index 0000000000..4c161da259 --- /dev/null +++ b/interface/resources/qml/hifi/+android_interface/HifiConstants.qml @@ -0,0 +1,56 @@ +// +// HifiAndroidConstants.qml +// interface/resources/qml/+android +// +// Created by Gabriel Calero & Cristian Duarte on 23 Oct 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 +// + +import QtQuick 2.4 +import QtQuick.Window 2.2 + +Item { + + id: android + + readonly property alias dimen: dimen + readonly property alias color: color + + Item { + id: dimen + readonly property bool atLeast1440p: Screen.width >= 2560 && Screen.height >= 1440 + readonly property real windowLessWidth: atLeast1440p ? 378 : 284 + readonly property real windowLessHeight: atLeast1440p ? 192 : 144 + + readonly property real windowZ: 100 + + readonly property real headerHeight: atLeast1440p ? 276 : 207 + + readonly property real headerIconPosX: atLeast1440p ? 90 : 67 + readonly property real headerIconPosY: atLeast1440p ? 108 : 81 + readonly property real headerIconWidth: atLeast1440p ? 111 : 83 + readonly property real headerIconHeight: atLeast1440p ? 111 : 83 + readonly property real headerIconTitleDistance: atLeast1440p ? 151 : 113 + + readonly property real headerHideWidth: atLeast1440p ? 150 : 112 + readonly property real headerHideHeight: atLeast1440p ? 150 : 112 + readonly property real headerHideRightMargin: atLeast1440p ? 110 : 82 + readonly property real headerHideTopMargin: atLeast1440p ? 90 : 67 + readonly property real headerHideIconWidth: atLeast1440p ? 70 : 52 + readonly property real headerHideIconHeight: atLeast1440p ? 45 : 33 + readonly property real headerHideTextTopMargin: atLeast1440p ? 36 : 27 + + readonly property real botomHudWidth: 366 + readonly property real botomHudHeight: 180 + + } + + Item { + id: color + readonly property color gradientTop: "#4E4E4E" + readonly property color gradientBottom: "#242424" + } +} diff --git a/interface/resources/qml/hifi/+android_interface/StatsBar.qml b/interface/resources/qml/hifi/+android_interface/StatsBar.qml new file mode 100644 index 0000000000..64e93b4a08 --- /dev/null +++ b/interface/resources/qml/hifi/+android_interface/StatsBar.qml @@ -0,0 +1,71 @@ +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import QtQuick.Controls.Styles 1.4 +import QtQuick.Layouts 1.3 +import Qt.labs.settings 1.0 +import stylesUit 1.0 +import controlsUit 1.0 as HifiControlsUit +import "../../controls" as HifiControls +import ".." + +Item { + id: bar + x:300 + y:0 + width: 300 + height: 300 + z: -1 + + signal sendToScript(var message); + signal windowClosed(); + + property bool shown: true + + onShownChanged: { + bar.visible = shown; + } + + Rectangle { + anchors.fill : parent + color: "transparent" + Flow { + id: flowMain + spacing: 10 + flow: Flow.TopToBottom + layoutDirection: Flow.TopToBottom + anchors.fill: parent + anchors.margins: 4 + } + } + + Component.onCompleted: { + // put on bottom + x = 300; + y = 0; + width = 300; + height = 300; + } + + function addButton(properties) { + var component = Qt.createComponent("button.qml"); + if (component.status == Component.Ready) { + var button = component.createObject(flowMain); + // copy all properites to button + var keys = Object.keys(properties).forEach(function (key) { + button[key] = properties[key]; + }); + return button; + } else if( component.status == Component.Error) { + console.log("Load button errors " + component.errorString()); + } + } + + function urlHelper(src) { + if (src.match(/\bhttp/)) { + return src; + } else { + return "../../../" + src; + } + } + +} diff --git a/interface/resources/qml/hifi/+android_interface/WindowHeader.qml b/interface/resources/qml/hifi/+android_interface/WindowHeader.qml new file mode 100644 index 0000000000..5316fc4786 --- /dev/null +++ b/interface/resources/qml/hifi/+android_interface/WindowHeader.qml @@ -0,0 +1,113 @@ +// +// WindowHeader.qml +// interface/resources/qml/android +// +// Created by Gabriel Calero & Cristian Duarte on 23 Oct 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 +// + +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import QtQuick.Controls.Styles 1.4 +import QtQuick.Layouts 1.3 +import Qt.labs.settings 1.0 +import "." +import "../styles" as HifiStyles +import stylesUit 1.0 +import controlsUit 1.0 as HifiControlsUit +import "../controls" as HifiControls +import ".." + + +// header +Rectangle { + id: header + + // properties + property string iconSource: "" + property string titleText: "" + property var extraItemInCenter: Item {} + + HifiStyles.HifiConstants { id: hifiStylesConstants } + + /*property var mockRectangle: Rectangle { + anchors.fill: parent + color: "#44FFFF00" + }*/ + color: "#00000000" + //color: "#55FF0000" + width: parent.width + height: android.dimen.headerHeight + anchors.top : parent.top + + Image { + id: windowIcon + source: iconSource + x: android.dimen.headerIconPosX + y: android.dimen.headerIconPosY + width: android.dimen.headerIconWidth + height: android.dimen.headerIconHeight + } + + /*HifiStylesUit.*/FiraSansSemiBold { + id: windowTitle + x: windowIcon.x + android.dimen.headerIconTitleDistance + anchors.verticalCenter: windowIcon.verticalCenter + text: titleText + color: "#FFFFFF" + font.letterSpacing: 2 + font.pixelSize: hifiStylesConstants.fonts.headerPixelSize * 2.15 + } + Item { + height: 60 + anchors { + left: windowTitle.right + right: hideButton.left + verticalCenter: windowIcon.verticalCenter + } + children: [ extraItemInCenter/*, mockRectangle */] + } + + Rectangle { + id: hideButton + height: android.dimen.headerHideWidth + width: android.dimen.headerHideHeight + color: "#00000000" + //color: "#CC00FF00" + anchors { + top: parent.top + right: parent.right + rightMargin: android.dimen.headerHideRightMargin + topMargin: android.dimen.headerHideTopMargin + } + Image { + id: hideIcon + source: "../../../icons/hide.svg" + width: android.dimen.headerHideIconWidth + height: android.dimen.headerHideIconHeight + anchors { + horizontalCenter: parent.horizontalCenter + } + } + /*HifiStyles.*/FiraSansRegular { + anchors { + top: hideIcon.bottom + horizontalCenter: hideIcon.horizontalCenter + topMargin: android.dimen.headerHideTextTopMargin + } + text: "HIDE" + color: "#FFFFFF" + font.pixelSize: hifiStylesConstants.fonts.pixelSize * 2.15 + } + + MouseArea { + anchors.fill: parent + onClicked: { + hide(); + } + } + } +} \ No newline at end of file diff --git a/interface/resources/qml/hifi/+android_interface/bottomHudOptions.qml b/interface/resources/qml/hifi/+android_interface/bottomHudOptions.qml new file mode 100644 index 0000000000..6b830d94c2 --- /dev/null +++ b/interface/resources/qml/hifi/+android_interface/bottomHudOptions.qml @@ -0,0 +1,86 @@ +// +// bottomHudOptions.qml +// interface/resources/qml/android +// +// Created by Cristian Duarte & Gabriel Calero on 24 Nov 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 +// + +import Hifi 1.0 +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import QtQuick.Controls.Styles 1.4 +import QtQuick.Layouts 1.3 +import Qt.labs.settings 1.0 +import "../../styles" as HifiStyles +import stylesUit 1.0 +import controlsUit 1.0 as HifiControlsUit +import "../../controls" as HifiControls +import ".." +import "." + +Item { + id: bottomHud + + property bool shown: false + + signal sendToScript(var message); + + HifiConstants { id: android } + + onShownChanged: { + bottomHud.visible = shown; + } + + function hide() { + shown = false; + } + + Rectangle { + anchors.fill : parent + color: "transparent" + Flow { + id: flowMain + spacing: 0 + flow: Flow.LeftToRight + layoutDirection: Flow.LeftToRight + anchors.horizontalCenter: parent.horizontalCenter + anchors.margins: 12 + + Rectangle { + id: hideButton + height: android.dimen.headerHideHeight + width: android.dimen.headerHideWidth + color: "#00000000" + Image { + id: hideIcon + source: "../../../icons/show-up.svg" + width: android.dimen.headerHideIconWidth + height: android.dimen.headerHideIconHeight + anchors { + horizontalCenter: parent.horizontalCenter + verticalCenter: parent.verticalCenter + } + } + + MouseArea { + anchors.fill: parent + onClicked: { + sendToScript ({ method: "showUpBar" }); + } + } + } + } + } + + Component.onCompleted: { + width = android.dimen.botomHudWidth; + height = android.dimen.botomHudHeight; + x=Window.innerWidth - width; + y=Window.innerHeight - height; + } + +} diff --git a/interface/resources/qml/hifi/+android_interface/button.qml b/interface/resources/qml/hifi/+android_interface/button.qml new file mode 100644 index 0000000000..a4c65b3c6f --- /dev/null +++ b/interface/resources/qml/hifi/+android_interface/button.qml @@ -0,0 +1,225 @@ +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import QtQuick.Controls.Styles 1.4 +import QtQuick.Layouts 1.3 + +Item { + id: button + property string icon: "icons/edit-icon.svg" + property string hoverIcon: button.icon + property string activeIcon: button.icon + property string activeHoverIcon: button.activeIcon + property int stableOrder: 0 + + property int iconSize: 165 + property string text: "." + property string hoverText: button.text + property string activeText: button.text + property string activeHoverText: button.activeText + + property string bgColor: "#ffffff" + property string hoverBgColor: button.bgColor + property string activeBgColor: button.bgColor + property string activeHoverBgColor: button.bgColor + + property real bgOpacity: 0 + property real hoverBgOpacity: 1 + property real activeBgOpacity: 0.5 + property real activeHoverBgOpacity: 1 + + property string textColor: "#ffffff" + property int textSize: 54 + property string hoverTextColor: "#ffffff" + property string activeTextColor: "#ffffff" + property string activeHoverTextColor: "#ffffff" + property string fontFamily: "FiraSans" + property bool fontBold: false + + property int bottomMargin: 30 + + property bool isEntered: false + property double sortOrder: 100 + + property bool isActive: false + + signal clicked() + signal entered() + signal exited() + + onIsActiveChanged: { + if (button.isEntered) { + button.state = (button.isActive) ? "hover active state" : "hover state"; + } else { + button.state = (button.isActive) ? "active state" : "base state"; + } + } + + function editProperties(props) { + for (var prop in props) { + button[prop] = props[prop]; + } + } + + + width: 300 + height: 300 + + Rectangle { + id: buttonBg + color: bgColor + opacity: bgOpacity + anchors.right: parent.right + anchors.rightMargin: 0 + anchors.left: parent.left + anchors.leftMargin: 0 + anchors.bottom: parent.bottom + anchors.bottomMargin: 0 + anchors.top: parent.top + anchors.topMargin: 0 + } + Image { + id: icon + width: iconSize + height: iconSize + anchors.bottom: text.top + anchors.bottomMargin: 6 + anchors.horizontalCenter: parent.horizontalCenter + fillMode: Image.Stretch + source: urlHelper(button.icon) + } + FontLoader { + id: firaSans + source: "../../../fonts/FiraSans-Regular.ttf" + } + Text { + id: text + color: "#ffffff" + text: button.text + font.family: button.fontFamily + font.bold: button.fontBold + font.pixelSize: textSize + anchors.bottom: parent.bottom + anchors.bottomMargin: bottomMargin + anchors.horizontalCenter: parent.horizontalCenter + horizontalAlignment: Text.AlignHCenter + } + MouseArea { + anchors.fill: parent + hoverEnabled: true + enabled: true + onClicked: { + console.log("Bottom bar button clicked!!"); + /*if (tabletButton.inDebugMode) { + if (tabletButton.isActive) { + tabletButton.isActive = false; + } else { + tabletButton.isActive = true; + } + }*/ + button.clicked(); + /*if (tabletRoot) { + tabletRoot.playButtonClickSound(); + }*/ + } + onPressed: { + button.isEntered = true; + button.entered(); + if (button.isActive) { + button.state = "hover active state"; + } else { + button.state = "hover state"; + } + } + onReleased: { + button.isEntered = false; + button.exited() + if (button.isActive) { + button.state = "active state"; + } else { + button.state = "base state"; + } + } + } + states: [ + State { + name: "hover state" + + PropertyChanges { + target: buttonBg + color: button.hoverBgColor + opacity: button.hoverBgOpacity + } + + PropertyChanges { + target: text + color: button.hoverTextColor + text: button.hoverText + } + + PropertyChanges { + target: icon + source: urlHelper(button.hoverIcon) + } + }, + State { + name: "active state" + + PropertyChanges { + target: buttonBg + color: button.activeBgColor + opacity: button.activeBgOpacity + } + + PropertyChanges { + target: text + color: button.activeTextColor + text: button.activeText + } + + PropertyChanges { + target: icon + source: urlHelper(button.activeIcon) + } + }, + State { + name: "hover active state" + + PropertyChanges { + target: buttonBg + color: button.activeHoverBgColor + opacity: button.activeHoverBgOpacity + } + + PropertyChanges { + target: text + color: button.activeHoverTextColor + text: button.activeHoverText + } + + PropertyChanges { + target: icon + source: urlHelper(button.activeHoverIcon) + } + }, + State { + name: "base state" + + PropertyChanges { + target: buttonBg + color: button.bgColor + opacity: button.bgOpacity + } + + PropertyChanges { + target: text + color: button.textColor + text: button.text + } + + PropertyChanges { + target: icon + source: urlHelper(button.icon) + } + } + ] +} \ No newline at end of file diff --git a/interface/resources/qml/hifi/+android_interface/modesbar.qml b/interface/resources/qml/hifi/+android_interface/modesbar.qml new file mode 100644 index 0000000000..1bf04fb8d9 --- /dev/null +++ b/interface/resources/qml/hifi/+android_interface/modesbar.qml @@ -0,0 +1,72 @@ +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import QtQuick.Controls.Styles 1.4 +import QtQuick.Layouts 1.3 +import Qt.labs.settings 1.0 +import stylesUit 1.0 +import controlsUit 1.0 as HifiControlsUit +import "../../controls" as HifiControls +import ".." + +Item { + id: modesbar + y:5 + + function relocateAndResize(newWindowWidth, newWindowHeight) { + width = 300; + height = 300; + x = newWindowWidth - 565; + } + + function onWindowGeometryChanged(rect) { + relocateAndResize(rect.width, rect.height); + } + + Component.onCompleted: { + relocateAndResize(parent.width, parent.height); + Window.geometryChanged.connect(onWindowGeometryChanged); // In devices with bars appearing at startup we should listen for this + } + + Component.onDestruction: { + Window.geometryChanged.disconnect(onWindowGeometryChanged); + } + + function addButton(properties) { + var component = Qt.createComponent("button.qml"); + console.log("load button"); + if (component.status == Component.Ready) { + console.log("load button 2"); + var button = component.createObject(modesbar); + // copy all properites to button + var keys = Object.keys(properties).forEach(function (key) { + button[key] = properties[key]; + }); + return button; + } else if( component.status == Component.Error) { + console.log("Load button errors " + component.errorString()); + } + } + + function removeButton(name) { + } + + function urlHelper(src) { + if (src.match(/\bhttp/)) { + return src; + } else { + return "../../../" + src; + } + } + + function fromScript(message) { + switch (message.type) { + case "switch": + // message.params.to + // still not needed + break; + default: + break; + } + } + +} diff --git a/interface/resources/qml/hifi/avatarapp/+android/TransparencyMask.qml b/interface/resources/qml/hifi/avatarapp/+android_interface/TransparencyMask.qml similarity index 100% rename from interface/resources/qml/hifi/avatarapp/+android/TransparencyMask.qml rename to interface/resources/qml/hifi/avatarapp/+android_interface/TransparencyMask.qml diff --git a/interface/resources/qml/stylesUit/+android_interface/HifiConstants.qml b/interface/resources/qml/stylesUit/+android_interface/HifiConstants.qml new file mode 100644 index 0000000000..d5fab57501 --- /dev/null +++ b/interface/resources/qml/stylesUit/+android_interface/HifiConstants.qml @@ -0,0 +1,358 @@ +// +// HiFiConstants.qml +// +// Created by Bradley Austin Davis on 28 Apr 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 +// + +import QtQuick 2.5 +import QtQuick.Window 2.2 + +Item { + readonly property alias colors: colors + readonly property alias colorSchemes: colorSchemes + readonly property alias dimensions: dimensions + readonly property alias fontSizes: fontSizes + readonly property alias glyphs: glyphs + readonly property alias icons: icons + readonly property alias buttons: buttons + readonly property alias effects: effects + + function glyphForIcon(icon) { + // Translates icon enum to glyph char. + var glyph; + switch (icon) { + case hifi.icons.information: + glyph = hifi.glyphs.info; + break; + case hifi.icons.question: + glyph = hifi.glyphs.question; + break; + case hifi.icons.warning: + glyph = hifi.glyphs.alert; + break; + case hifi.icons.critical: + glyph = hifi.glyphs.error; + break; + case hifi.icons.placemark: + glyph = hifi.glyphs.placemark; + break; + default: + glyph = hifi.glyphs.noIcon; + } + return glyph; + } + + Item { + id: colors + + // Base colors + readonly property color baseGray: "#393939" + readonly property color darkGray: "#121212" + readonly property color baseGrayShadow: "#252525" + readonly property color baseGrayHighlight: "#575757" + readonly property color lightGray: "#6a6a6a" + readonly property color lightGrayText: "#afafaf" + readonly property color faintGray: "#e3e3e3" + readonly property color primaryHighlight: "#00b4ef" + readonly property color blueHighlight: "#00b4ef" + readonly property color blueAccent: "#0093C5" + readonly property color redHighlight: "#EA4C5F" + readonly property color redAccent: "#C62147" + readonly property color greenHighlight: "#1ac567" + readonly property color greenShadow: "#359D85" + readonly property color orangeHighlight: "#FFC49C" + readonly property color orangeAccent: "#FF6309" + readonly property color indigoHighlight: "#C0D2FF" + readonly property color indigoAccent: "#9495FF" + readonly property color magentaHighlight: "#EF93D1" + readonly property color magentaAccent: "#A2277C" + readonly property color checkboxCheckedRed: "#FF0000" + readonly property color checkboxCheckedBorderRed: "#D00000" + readonly property color lightBlueHighlight: "#d6f6ff" + + // Semitransparent + readonly property color darkGray30: "#4d121212" + readonly property color darkGray0: "#00121212" + readonly property color baseGrayShadow60: "#99252525" + readonly property color baseGrayShadow50: "#80252525" + readonly property color baseGrayShadow25: "#40252525" + readonly property color baseGrayHighlight40: "#66575757" + readonly property color baseGrayHighlight15: "#26575757" + readonly property color lightGray50: "#806a6a6a" + readonly property color lightGrayText80: "#ccafafaf" + readonly property color faintGray80: "#cce3e3e3" + readonly property color faintGray50: "#80e3e3e3" + + // Other colors + readonly property color white: "#ffffff" + readonly property color gray: "#808080" + readonly property color black: "#000000" + readonly property color locked: "#252525" + // Semitransparent + readonly property color white50: "#80ffffff" + readonly property color white30: "#4dffffff" + readonly property color white25: "#40ffffff" + readonly property color transparent: "#00ffffff" + + // Control specific colors + readonly property color tableRowLightOdd: "#fafafa" + readonly property color tableRowLightEven: "#eeeeee" // Equivavlent to "#1a575757" over #e3e3e3 background + readonly property color tableRowDarkOdd: "#2e2e2e" // Equivalent to "#80393939" over #404040 background + readonly property color tableRowDarkEven: "#1c1c1c" // Equivalent to "#a6181818" over #404040 background + readonly property color tableBackgroundLight: tableRowLightEven + readonly property color tableBackgroundDark: tableRowDarkEven + readonly property color tableScrollHandleLight: "#DDDDDD" + readonly property color tableScrollHandleDark: "#707070" + readonly property color tableScrollBackgroundLight: tableRowLightOdd + readonly property color tableScrollBackgroundDark: "#323232" + readonly property color checkboxLightStart: "#ffffff" + readonly property color checkboxLightFinish: "#afafaf" + readonly property color checkboxDarkStart: "#7d7d7d" + readonly property color checkboxDarkFinish: "#6b6a6b" + readonly property color checkboxChecked: primaryHighlight + readonly property color checkboxCheckedBorder: "#36cdff" + readonly property color sliderGutterLight: "#d4d4d4" + readonly property color sliderGutterDark: "#252525" + readonly property color sliderBorderLight: "#afafaf" + readonly property color sliderBorderDark: "#7d7d7d" + readonly property color sliderLightStart: "#ffffff" + readonly property color sliderLightFinish: "#afafaf" + readonly property color sliderDarkStart: "#7d7d7d" + readonly property color sliderDarkFinish: "#6b6a6b" + readonly property color dropDownPressedLight: "#d4d4d4" + readonly property color dropDownPressedDark: "#afafaf" + readonly property color dropDownLightStart: "#ffffff" + readonly property color dropDownLightFinish: "#afafaf" + readonly property color dropDownDarkStart: "#7d7d7d" + readonly property color dropDownDarkFinish: "#6b6a6b" + readonly property color textFieldLightBackground: "#d4d4d4" + readonly property color tabBackgroundDark: "#252525" + readonly property color tabBackgroundLight: "#d4d4d4" + } + + Item { + id: colorSchemes + readonly property int light: 0 + readonly property int dark: 1 + readonly property int faintGray: 2 + } + + Item { + id: dimensions + readonly property bool largeScreen: Screen.width >= 1920 && Screen.height >= 1080 + readonly property real borderRadius: largeScreen ? 7.5 : 5.0 + readonly property real borderWidth: largeScreen ? 2 : 1 + readonly property vector2d contentMargin: Qt.vector2d(21, 21) + readonly property vector2d contentSpacing: Qt.vector2d(11, 14) + readonly property real labelPadding: 40 + readonly property real textPadding: 8 + readonly property real sliderHandleSize: 18 + readonly property real sliderGrooveHeight: 8 + readonly property real frameIconSize: 22 + readonly property real spinnerSize: 50 + readonly property real tablePadding: 12 + readonly property real tableRowHeight: largeScreen ? 26 : 23 + readonly property real tableHeaderHeight: 29 + readonly property vector2d modalDialogMargin: Qt.vector2d(50, 30) + readonly property real modalDialogTitleHeight: 120 + readonly property real controlLineHeight: 84 // Height of spinbox control on 1920 x 1080 monitor + readonly property real controlInterlineHeight: 21 // 75% of controlLineHeight + readonly property vector2d menuPadding: Qt.vector2d(14, 102) + readonly property real scrollbarBackgroundWidth: 20 + readonly property real scrollbarHandleWidth: scrollbarBackgroundWidth - 2 + readonly property real tabletMenuHeader: 90 + readonly property real buttonWidth: 360 + } + + Item { + id: fontSizes // In pixels + readonly property real overlayTitle: dimensions.largeScreen ? 54 : 42 + readonly property real tabName: dimensions.largeScreen ? 12 : 10 + readonly property real sectionName: dimensions.largeScreen ? 36 : 30 + readonly property real inputLabel: dimensions.largeScreen ? 14 : 10 + readonly property real textFieldInput: dimensions.largeScreen ? 48 : 36 + readonly property real textFieldInputLabel: dimensions.largeScreen ? 13 : 9 + readonly property real textFieldSearchIcon: dimensions.largeScreen ? 30 : 24 + readonly property real tableHeading: dimensions.largeScreen ? 12 : 10 + readonly property real tableHeadingIcon: dimensions.largeScreen ? 60 : 33 + readonly property real tableText: dimensions.largeScreen ? 15 : 12 + readonly property real buttonLabel: dimensions.largeScreen ? 42 : 27 + readonly property real iconButton: dimensions.largeScreen ? 13 : 9 + readonly property real listItem: dimensions.largeScreen ? 15 : 11 + readonly property real tabularData: dimensions.largeScreen ? 15 : 11 + readonly property real logs: dimensions.largeScreen ? 16 : 12 + readonly property real code: dimensions.largeScreen ? 16 : 12 + readonly property real rootMenu: dimensions.largeScreen ? 15 : 11 + readonly property real rootMenuDisclosure: dimensions.largeScreen ? 20 : 16 + readonly property real menuItem: dimensions.largeScreen ? 45 : 33 + readonly property real shortcutText: dimensions.largeScreen ? 39 : 27 + readonly property real carat: dimensions.largeScreen ? 38 : 30 + readonly property real disclosureButton: dimensions.largeScreen ? 30 : 22 + } + + Item { + id: icons + // Values per OffscreenUi::Icon + readonly property int none: 0 + readonly property int question: 1 + readonly property int information: 2 + readonly property int warning: 3 + readonly property int critical: 4 + readonly property int placemark: 5 + } + + Item { + id: buttons + readonly property int white: 0 + readonly property int blue: 1 + readonly property int red: 2 + readonly property int black: 3 + readonly property int none: 4 + readonly property int noneBorderless: 5 + readonly property int noneBorderlessWhite: 6 + readonly property int noneBorderlessGray: 7 + readonly property var textColor: [ colors.darkGray, colors.white, colors.white, colors.white, colors.white, colors.blueAccent, colors.white, colors.darkGray ] + readonly property var colorStart: [ colors.white, colors.primaryHighlight, "#d42043", "#343434", Qt.rgba(0, 0, 0, 0), Qt.rgba(0, 0, 0, 0), Qt.rgba(0, 0, 0, 0), Qt.rgba(0, 0, 0, 0) ] + readonly property var colorFinish: [ colors.lightGrayText, colors.blueAccent, "#94132e", colors.black, Qt.rgba(0, 0, 0, 0), Qt.rgba(0, 0, 0, 0), Qt.rgba(0, 0, 0, 0), Qt.rgba(0, 0, 0, 0) ] + readonly property var hoveredColor: [ colorStart[white], colorStart[blue], colorStart[red], colorFinish[black], colorStart[none], colorStart[noneBorderless], colorStart[noneBorderlessWhite], colorStart[noneBorderlessGray] ] + readonly property var pressedColor: [ colorFinish[white], colorFinish[blue], colorFinish[red], colorStart[black], colorStart[none], colorStart[noneBorderless], colorStart[noneBorderlessWhite], colorStart[noneBorderlessGray] ] + readonly property var disabledColorStart: [ colorStart[white], colors.baseGrayHighlight] + readonly property var disabledColorFinish: [ colorFinish[white], colors.baseGrayShadow] + readonly property var disabledTextColor: [ colors.lightGrayText, colors.baseGrayShadow] + readonly property int radius: 15 + } + + QtObject { + id: effects + readonly property int fadeInDuration: 300 + } + Item { + id: glyphs + readonly property string noIcon: "" + readonly property string hmd: "b" + readonly property string screen: "c" + readonly property string keyboard: "d" + readonly property string handControllers: "e" + readonly property string headphonesMic: "f" + readonly property string gamepad: "g" + readonly property string headphones: "h" + readonly property string mic: "i" + readonly property string upload: "j" + readonly property string script: "k" + readonly property string text: "l" + readonly property string cube: "m" + readonly property string sphere: "n" + readonly property string zone: "o" + readonly property string light: "p" + readonly property string web: "q" + readonly property string web2: "r" + readonly property string edit: "s" + readonly property string market: "t" + readonly property string directory: "u" + readonly property string menu: "v" + readonly property string close: "w" + readonly property string closeInverted: "x" + readonly property string pin: "y" + readonly property string pinInverted: "z" + readonly property string resizeHandle: "A" + readonly property string disclosureExpand: "B" + readonly property string reloadSmall: "a" + readonly property string closeSmall: "C" + readonly property string forward: "D" + readonly property string backward: "E" + readonly property string reload: "F" + readonly property string unmuted: "G" + readonly property string muted: "H" + readonly property string minimize: "I" + readonly property string maximize: "J" + readonly property string maximizeInverted: "K" + readonly property string disclosureButtonExpand: "L" + readonly property string disclosureButtonCollapse: "M" + readonly property string scriptStop: "N" + readonly property string scriptReload: "O" + readonly property string scriptRun: "P" + readonly property string scriptNew: "Q" + readonly property string hifiForum: "2" + readonly property string hifiLogoSmall: "S" + readonly property string avatar1: "T" + readonly property string placemark: "U" + readonly property string box: "V" + readonly property string community: "0" + readonly property string grabHandle: "X" + readonly property string search: "Y" + readonly property string disclosureCollapse: "Z" + readonly property string scriptUpload: "R" + readonly property string code: "W" + readonly property string avatar: "<" + readonly property string arrowsH: ":" + readonly property string arrowsV: ";" + readonly property string arrows: "`" + readonly property string compress: "!" + readonly property string expand: "\"" + readonly property string placemark1: "#" + readonly property string circle: "$" + readonly property string handPointer: "9" + readonly property string plusSquareO: "%" + readonly property string sliders: "&" + readonly property string square: "'" + readonly property string alignCenter: "8" + readonly property string alignJustify: ")" + readonly property string alignLeft: "*" + readonly property string alignRight: "^" + readonly property string bars: "7" + readonly property string circleSlash: "," + readonly property string sync: "()" + readonly property string key: "-" + readonly property string link: "." + readonly property string location: "/" + readonly property string caratR: "3" + readonly property string caratL: "4" + readonly property string caratDn: "5" + readonly property string caratUp: "6" + readonly property string folderLg: ">" + readonly property string folderSm: "?" + readonly property string levelUp: "1" + readonly property string info: "[" + readonly property string question: "]" + readonly property string alert: "+" + readonly property string home: "_" + readonly property string error: "=" + readonly property string settings: "@" + readonly property string trash: "{" + readonly property string objectGroup: "\ue000" + readonly property string cm: "}" + readonly property string msvg79: "~" + readonly property string deg: "\\" + readonly property string px: "|" + readonly property string editPencil: "\ue00d" + readonly property string vol_0: "\ue00e" + readonly property string vol_1: "\ue00f" + readonly property string vol_2: "\ue010" + readonly property string vol_3: "\ue011" + readonly property string vol_4: "\ue012" + readonly property string vol_x_0: "\ue013" + readonly property string vol_x_1: "\ue014" + readonly property string vol_x_2: "\ue015" + readonly property string vol_x_3: "\ue016" + readonly property string vol_x_4: "\ue017" + readonly property string source: "\ue01c" + readonly property string playback_play: "\ue01d" + readonly property string stop_square: "\ue01e" + readonly property string avatarTPose: "\ue01f" + readonly property string lock: "\ue006" + readonly property string checkmark: "\ue020" + readonly property string leftRightArrows: "\ue021" + readonly property string hfc: "\ue022" + readonly property string home2: "\ue023" + readonly property string walletKey: "\ue024" + readonly property string lightning: "\ue025" + readonly property string securityImage: "\ue026" + readonly property string wallet: "\ue027" + readonly property string paperPlane: "\ue028" + readonly property string passphrase: "\ue029" + } +} diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 4ec5057cbe..d9eae20752 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -6350,6 +6350,8 @@ void Application::update(float deltaTime) { _physicsEngine->processTransaction(transaction); myAvatar->getCharacterController()->handleProcessedPhysicsTransaction(transaction); myAvatar->prepareForPhysicsSimulation(); + _physicsEngine->enableGlobalContactAddedCallback(myAvatar->isFlying()); + _physicsEngine->forEachDynamic([&](EntityDynamicPointer dynamic) { dynamic->prepareForPhysicsSimulation(); }); diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index 1640e97ff4..c5d72d2d7b 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -88,13 +88,13 @@ EntityPropertyFlags EntityItem::getEntityProperties(EncodeBitstreamParams& param requestedProperties += PROP_REGISTRATION_POINT; requestedProperties += PROP_CREATED; requestedProperties += PROP_LAST_EDITED_BY; - //requestedProperties += PROP_ENTITY_HOST_TYPE; // not sent over the wire - //requestedProperties += PROP_OWNING_AVATAR_ID; // not sent over the wire + requestedProperties += PROP_ENTITY_HOST_TYPE; + requestedProperties += PROP_OWNING_AVATAR_ID; requestedProperties += PROP_PARENT_ID; requestedProperties += PROP_PARENT_JOINT_INDEX; requestedProperties += PROP_QUERY_AA_CUBE; requestedProperties += PROP_CAN_CAST_SHADOW; - // requestedProperties += PROP_VISIBLE_IN_SECONDARY_CAMERA; // not sent over the wire + requestedProperties += PROP_VISIBLE_IN_SECONDARY_CAMERA; requestedProperties += PROP_RENDER_LAYER; requestedProperties += PROP_PRIMITIVE_MODE; requestedProperties += PROP_IGNORE_PICK_INTERSECTION; @@ -180,6 +180,11 @@ OctreeElement::AppendState EntityItem::appendEntityData(OctreePacketData* packet EntityPropertyFlags propertyFlags(PROP_LAST_ITEM); EntityPropertyFlags requestedProperties = getEntityProperties(params); + // these properties are not sent over the wire + requestedProperties -= PROP_ENTITY_HOST_TYPE; + requestedProperties -= PROP_OWNING_AVATAR_ID; + requestedProperties -= PROP_VISIBLE_IN_SECONDARY_CAMERA; + // If we are being called for a subsequent pass at appendEntityData() that failed to completely encode this item, // then our entityTreeElementExtraEncodeData should include data about which properties we need to append. if (entityTreeElementExtraEncodeData && entityTreeElementExtraEncodeData->entities.contains(getEntityItemID())) { diff --git a/libraries/physics/src/CharacterController.cpp b/libraries/physics/src/CharacterController.cpp index 13a1328065..1ebb488458 100755 --- a/libraries/physics/src/CharacterController.cpp +++ b/libraries/physics/src/CharacterController.cpp @@ -124,6 +124,11 @@ void CharacterController::setDynamicsWorld(btDynamicsWorld* world) { _rigidBody->setGravity(_currentGravity * _currentUp); // set flag to enable custom contactAddedCallback _rigidBody->setCollisionFlags(btCollisionObject::CF_CUSTOM_MATERIAL_CALLBACK); + + // enable CCD + _rigidBody->setCcdSweptSphereRadius(_radius); + _rigidBody->setCcdMotionThreshold(_radius); + btCollisionShape* shape = _rigidBody->getCollisionShape(); assert(shape && shape->getShapeType() == CONVEX_HULL_SHAPE_PROXYTYPE); _ghost.setCharacterShape(static_cast(shape)); @@ -455,6 +460,12 @@ void CharacterController::setLocalBoundingBox(const glm::vec3& minCorner, const // it's ok to change offset immediately -- there are no thread safety issues here _shapeLocalOffset = minCorner + 0.5f * scale; + + if (_rigidBody) { + // update CCD with new _radius + _rigidBody->setCcdSweptSphereRadius(_radius); + _rigidBody->setCcdMotionThreshold(_radius); + } } void CharacterController::setCollisionless(bool collisionless) { diff --git a/libraries/physics/src/PhysicsEngine.cpp b/libraries/physics/src/PhysicsEngine.cpp index a571a81109..4453a7d9f0 100644 --- a/libraries/physics/src/PhysicsEngine.cpp +++ b/libraries/physics/src/PhysicsEngine.cpp @@ -27,6 +27,23 @@ #include "ThreadSafeDynamicsWorld.h" #include "PhysicsLogging.h" +static bool flipNormalsMyAvatarVsBackfacingTriangles(btManifoldPoint& cp, + const btCollisionObjectWrapper* colObj0Wrap, int partId0, int index0, + const btCollisionObjectWrapper* colObj1Wrap, int partId1, int index1) { + if (colObj1Wrap->getCollisionShape()->getShapeType() == TRIANGLE_SHAPE_PROXYTYPE) { + auto triShape = static_cast(colObj1Wrap->getCollisionShape()); + const btVector3* v = triShape->m_vertices1; + btVector3 faceNormal = colObj1Wrap->getWorldTransform().getBasis() * btCross(v[1] - v[0], v[2] - v[0]); + float nDotF = btDot(faceNormal, cp.m_normalWorldOnB); + if (nDotF <= 0.0f) { + faceNormal.normalize(); + // flip the contact normal to be aligned with the face normal + cp.m_normalWorldOnB += -2.0f * nDotF * faceNormal; + } + } + // return value is currently ignored but to be future-proof: return false when not modifying friction + return false; +} PhysicsEngine::PhysicsEngine(const glm::vec3& offset) : _originOffset(offset), @@ -904,6 +921,16 @@ void PhysicsEngine::setShowBulletConstraintLimits(bool value) { } } +void PhysicsEngine::enableGlobalContactAddedCallback(bool enabled) { + if (enabled) { + // register contact filter to help MyAvatar pass through backfacing triangles + gContactAddedCallback = flipNormalsMyAvatarVsBackfacingTriangles; + } else { + // deregister contact filter + gContactAddedCallback = nullptr; + } +} + struct AllContactsCallback : public btCollisionWorld::ContactResultCallback { AllContactsCallback(int32_t mask, int32_t group, const ShapeInfo& shapeInfo, const Transform& transform, btCollisionObject* myAvatarCollisionObject, float threshold) : btCollisionWorld::ContactResultCallback(), diff --git a/libraries/physics/src/PhysicsEngine.h b/libraries/physics/src/PhysicsEngine.h index d10be018b8..43cc0d2176 100644 --- a/libraries/physics/src/PhysicsEngine.h +++ b/libraries/physics/src/PhysicsEngine.h @@ -148,6 +148,8 @@ public: // See PhysicsCollisionGroups.h for mask flags. std::vector contactTest(uint16_t mask, const ShapeInfo& regionShapeInfo, const Transform& regionTransform, uint16_t group = USER_COLLISION_GROUP_DYNAMIC, float threshold = 0.0f) const; + void enableGlobalContactAddedCallback(bool enabled); + private: QList removeDynamicsForBody(btRigidBody* body); void addObjectToDynamicsWorld(ObjectMotionState* motionState); diff --git a/libraries/shared/src/shared/FileUtils.cpp b/libraries/shared/src/shared/FileUtils.cpp index 9a58fc3e78..f2a4925351 100644 --- a/libraries/shared/src/shared/FileUtils.cpp +++ b/libraries/shared/src/shared/FileUtils.cpp @@ -32,7 +32,7 @@ const QStringList& FileUtils::getFileSelectors() { std::call_once(once, [] { #if defined(Q_OS_ANDROID) - //extraSelectors << "android_" HIFI_ANDROID_APP; + extraSelectors << "android_" HIFI_ANDROID_APP; #endif #if defined(USE_GLES) diff --git a/scripts/+android_interface/defaultScripts.js b/scripts/+android_interface/defaultScripts.js new file mode 100644 index 0000000000..e6971f5a6b --- /dev/null +++ b/scripts/+android_interface/defaultScripts.js @@ -0,0 +1,128 @@ +"use strict"; +/* jslint vars: true, plusplus: true */ + +// +// defaultScripts.js +// examples +// +// 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 +// + +var DEFAULT_SCRIPTS_COMBINED = [ + "system/progress.js", + "system/+android_interface/touchscreenvirtualpad.js", + "system/+android_interface/actionbar.js", + "system/+android_interface/audio.js" , + "system/+android_interface/modes.js"/*, + "system/away.js", + "system/controllers/controllerDisplayManager.js", + "system/controllers/handControllerGrabAndroid.js", + "system/controllers/handControllerPointerAndroid.js", + "system/controllers/squeezeHands.js", + "system/controllers/grab.js", + "system/controllers/teleport.js", + "system/controllers/toggleAdvancedMovementForHandControllers.js", + "system/dialTone.js", + "system/firstPersonHMD.js", + "system/bubble.js", + "system/android.js", + "developer/debugging/debugAndroidMouse.js"*/ +]; + +var DEBUG_SCRIPTS = [ + "system/+android_interface/stats.js" +]; + +var DEFAULT_SCRIPTS_SEPARATE = [ ]; + +// add a menu item for debugging +var MENU_CATEGORY = "Developer"; +var MENU_ITEM = "Debug defaultScripts.js"; + +var SETTINGS_KEY = '_debugDefaultScriptsIsChecked'; +var previousSetting = Settings.getValue(SETTINGS_KEY); + +if (previousSetting === '' || previousSetting === false || previousSetting === 'false') { + previousSetting = false; +} + +if (previousSetting === true || previousSetting === 'true') { + previousSetting = true; +} + +if (Menu.menuExists(MENU_CATEGORY) && !Menu.menuItemExists(MENU_CATEGORY, MENU_ITEM)) { + Menu.addMenuItem({ + menuName: MENU_CATEGORY, + menuItemName: MENU_ITEM, + isCheckable: true, + isChecked: previousSetting, + grouping: "Advanced" + }); +} + +function loadSeparateDefaults() { + for (var i in DEFAULT_SCRIPTS_SEPARATE) { + Script.load(DEFAULT_SCRIPTS_SEPARATE[i]); + } +} + +function runDefaultsTogether() { + for (var i in DEFAULT_SCRIPTS_COMBINED) { + Script.include(DEFAULT_SCRIPTS_COMBINED[i]); + } + if (Script.isDebugMode()) { + for (var i in DEBUG_SCRIPTS) { + Script.include(DEBUG_SCRIPTS[i]); + } + } + loadSeparateDefaults(); +} + +function runDefaultsSeparately() { + for (var i in DEFAULT_SCRIPTS_COMBINED) { + Script.load(DEFAULT_SCRIPTS_COMBINED[i]); + } + if (Script.isDebugMode()) { + for (var i in DEBUG_SCRIPTS) { + Script.load(DEBUG_SCRIPTS[i]); + } + } + loadSeparateDefaults(); +} + +// start all scripts +if (Menu.isOptionChecked(MENU_ITEM)) { + // we're debugging individual default scripts + // so we load each into its own ScriptEngine instance + runDefaultsSeparately(); +} else { + // include all default scripts into this ScriptEngine + runDefaultsTogether(); +} + +function menuItemEvent(menuItem) { + if (menuItem === MENU_ITEM) { + var isChecked = Menu.isOptionChecked(MENU_ITEM); + if (isChecked === true) { + Settings.setValue(SETTINGS_KEY, true); + } else if (isChecked === false) { + Settings.setValue(SETTINGS_KEY, false); + } + Menu.triggerOption("Reload All Scripts"); + } +} + +function removeMenuItem() { + if (!Menu.isOptionChecked(MENU_ITEM)) { + Menu.removeMenuItem(MENU_CATEGORY, MENU_ITEM); + } +} + +Script.scriptEnding.connect(function() { + removeMenuItem(); +}); + +Menu.menuItemEvent.connect(menuItemEvent); diff --git a/scripts/+android_questInterface/defaultScripts.js b/scripts/+android_questInterface/defaultScripts.js index da50e4a182..d22716302c 100644 --- a/scripts/+android_questInterface/defaultScripts.js +++ b/scripts/+android_questInterface/defaultScripts.js @@ -25,6 +25,7 @@ var DEFAULT_SCRIPTS_COMBINED = [ "system/notifications.js", "system/commerce/wallet.js", "system/dialTone.js", + "system/quickGoto.js", "system/firstPersonHMD.js", "system/tablet-ui/tabletUI.js", "system/miniTablet.js" diff --git a/scripts/system/+android_interface/actionbar.js b/scripts/system/+android_interface/actionbar.js new file mode 100644 index 0000000000..74b3896a62 --- /dev/null +++ b/scripts/system/+android_interface/actionbar.js @@ -0,0 +1,59 @@ +"use strict"; +// +// backbutton.js +// scripts/system/+android +// +// Created by Gabriel Calero & Cristian Duarte on Apr 06, 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 +// +(function() { // BEGIN LOCAL_SCOPE + +var actionbar; +var backButton; + +var logEnabled = true; + +function printd(str) { + if (logEnabled) + print("[actionbar.js] " + str); +} + +function init() { + actionbar = new QmlFragment({ + qml: "hifi/ActionBar.qml" + }); + backButton = actionbar.addButton({ + icon: "icons/+android_interface/backward.svg", + activeIcon: "icons/+android_interface/backward.svg", + text: "", + bgOpacity: 0.0, + hoverBgOpacity: 0.0, + activeBgOpacity: 0.0 + }); + + backButton.entered.connect(onBackPressed); + backButton.clicked.connect(onBackClicked); +} + +function onBackPressed() { + Controller.triggerHapticPulseOnDevice(Controller.findDevice("TouchscreenVirtualPad"), 0.1, 40.0, 0); +} + +function onBackClicked() { + Window.openAndroidActivity("Home", false); +} + + +Script.scriptEnding.connect(function() { + if(backButton) { + backButton.entered.disconnect(onBackPressed); + backButton.clicked.disconnect(onBackClicked); + } +}); + +init(); + +}()); // END LOCAL_SCOPE diff --git a/scripts/system/+android_interface/audio.js b/scripts/system/+android_interface/audio.js new file mode 100644 index 0000000000..34dd52604a --- /dev/null +++ b/scripts/system/+android_interface/audio.js @@ -0,0 +1,71 @@ +"use strict"; +// +// audio.js +// scripts/system/ +// +// Created by Gabriel Calero & Cristian Duarte on Jan 16, 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 +// + +(function() { // BEGIN LOCAL_SCOPE + +var audiobar; +var audioButton; + +var logEnabled = false; + +function printd(str) { + if (logEnabled) + print("[audio.js] " + str); +} + +function init() { + audiobar = new QmlFragment({ + qml: "hifi/AudioBar.qml" + }); + + audioButton = audiobar.addButton({ + icon: "icons/mic-unmute-a.svg", + activeIcon: "icons/mic-mute-a.svg", + text: "", + bgOpacity: 0.0, + hoverBgOpacity: 0.0, + activeHoverBgOpacity: 0.0, + activeBgOpacity: 0.0 + }); + + onMuteToggled(); + + audioButton.clicked.connect(onMuteClicked); + audioButton.entered.connect(onMutePressed); + Audio.mutedChanged.connect(onMuteToggled); +} + +function onMuteClicked() { + Audio.muted = !Audio.muted; +} + +function onMutePressed() { + Controller.triggerHapticPulseOnDevice(Controller.findDevice("TouchscreenVirtualPad"), 0.1, 40.0, 0); +} + +function onMuteToggled() { + printd("On Mute Toggled"); + audioButton.isActive = Audio.muted; // Menu.isOptionChecked("Mute Microphone") + printd("Audio button is active: " + audioButton.isActive); +} + +Script.scriptEnding.connect(function () { + if(audioButton) { + audioButton.clicked.disconnect(onMuteClicked); + audioButton.entered.disconnect(onMutePressed); + Audio.mutedChanged.connect(onMuteToggled); + } +}); + +init(); + +}()); // END LOCAL_SCOPE diff --git a/scripts/system/+android_interface/clickWeb.js b/scripts/system/+android_interface/clickWeb.js new file mode 100644 index 0000000000..229b2b8b82 --- /dev/null +++ b/scripts/system/+android_interface/clickWeb.js @@ -0,0 +1,108 @@ +"use strict"; +// +// clickWeb.js +// scripts/system/+android +// +// Created by Gabriel Calero & Cristian Duarte on Jun 22, 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 +// + +(function() { // BEGIN LOCAL_SCOPE + +var logEnabled = false; +var touchOverlayID; +var touchEntityID; + +function printd(str) { + if (logEnabled) + print("[clickWeb.js] " + str); +} + +function intersectsWebOverlay(intersection) { + return intersection && intersection.intersects && intersection.overlayID && + Overlays.getOverlayType(intersection.overlayID) == "web3d"; +} + +function intersectsWebEntity(intersection) { + if (intersection && intersection.intersects && intersection.entityID) { + var properties = Entities.getEntityProperties(intersection.entityID, ["type", "sourceUrl"]); + return properties.type && properties.type == "Web" && properties.sourceUrl; + } + return false; +} + +function findRayIntersection(pickRay) { + // Check 3D overlays and entities. Argument is an object with origin and direction. + var overlayRayIntersection = Overlays.findRayIntersection(pickRay); + var entityRayIntersection = Entities.findRayIntersection(pickRay, true); + var isOverlayInters = intersectsWebOverlay(overlayRayIntersection); + var isEntityInters = intersectsWebEntity(entityRayIntersection); + if (isOverlayInters && + (!isEntityInters || + overlayRayIntersection.distance < entityRayIntersection.distance)) { + return { type: 'overlay', obj: overlayRayIntersection }; + } else if (isEntityInters && + (!isOverlayInters || + entityRayIntersection.distance < overlayRayIntersection.distance)) { + return { type: 'entity', obj: entityRayIntersection }; + } + return false; +} + +function touchBegin(event) { + var intersection = findRayIntersection(Camera.computePickRay(event.x, event.y)); + if (intersection && intersection.type == 'overlay') { + touchOverlayID = intersection.obj.overlayID; + touchEntityID = null; + } else if (intersection && intersection.type == 'entity') { + touchEntityID = intersection.obj.entityID; + touchOverlayID = null; + } +} + +function touchEnd(event) { + var intersection = findRayIntersection(Camera.computePickRay(event.x, event.y)); + if (intersection && intersection.type == 'overlay' && touchOverlayID == intersection.obj.overlayID) { + var propertiesToGet = {}; + propertiesToGet[overlayID] = ['url']; + var properties = Overlays.getOverlaysProperties(propertiesToGet); + if (properties[overlayID].url) { + Window.openUrl(properties[overlayID].url); + } + } else if (intersection && intersection.type == 'entity' && touchEntityID == intersection.obj.entityID) { + var properties = Entities.getEntityProperties(touchEntityID, ["sourceUrl"]); + if (properties.sourceUrl) { + Window.openUrl(properties.sourceUrl); + } + } + + touchOverlayID = null; + touchEntityID = null; +} + +function ending() { + Controller.touchBeginEvent.disconnect(touchBegin); + Controller.touchEndEvent.disconnect(touchEnd); +} + +function init() { + Controller.touchBeginEvent.connect(touchBegin); + Controller.touchEndEvent.connect(touchEnd); + + Script.scriptEnding.connect(function () { + ending(); + }); + +} + +module.exports = { + init: init, + ending: ending +} + +init(); + +}()); // END LOCAL_SCOPE diff --git a/scripts/system/+android_interface/displayNames.js b/scripts/system/+android_interface/displayNames.js new file mode 100644 index 0000000000..509d85cd2b --- /dev/null +++ b/scripts/system/+android_interface/displayNames.js @@ -0,0 +1,167 @@ +"use strict"; +// +// displayNames.js +// scripts/system/ +// +// Created by Cristian Duarte & Gabriel Calero on May 3, 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 +// +(function() { // BEGIN LOCAL_SCOPE + +var MAX_DISTANCE_PX = 20; // Should we use dp instead? +var UNKNOWN_NAME = "Unknown"; +var METERS_ABOVE_HEAD = 0.4; + +var TEXT_LINE_HEIGHT = .1; +var TEXT_MARGIN = 0.025; + +var HIDE_MS = 10000; + +var currentTouchToAnalyze = null; + +var currentlyShownAvatar = { + avatarID: null, + avatar: null, + overlay: null +}; + +var logEnabled = false; + +var hideTimer = null; + +function printd(str) { + if (logEnabled) { + print("[displayNames.js] " + str); + } +} + +function clearOverlay() { + currentlyShownAvatar.avatar = null; + if (currentlyShownAvatar.overlay) { + Overlays.editOverlay(currentlyShownAvatar.overlay, {visible: false}); + } +} + +function touchedAvatar(avatarID, avatarData) { + printd("[AVATARNAME] touchEnd FOUND " + JSON.stringify(avatarData)); + + if (hideTimer) { + Script.clearTimeout(hideTimer); + } + + // Case: touching an already selected avatar + if (currentlyShownAvatar.avatar && currentlyShownAvatar.avatarID == avatarID) { + clearOverlay(); + return; + } + + // Save currently selected avatar + currentlyShownAvatar.avatarID = avatarID; + currentlyShownAvatar.avatar = avatarData; + + if (currentlyShownAvatar.overlay == null) { + var over = Overlays.addOverlay("text3d", { + lineHeight: TEXT_LINE_HEIGHT, + color: { red: 255, green: 255, blue: 255}, + backgroundColor: {red: 0, green: 0, blue: 0}, + leftMargin: TEXT_MARGIN, + topMargin: TEXT_MARGIN, + rightMargin: TEXT_MARGIN, + bottomMargin: TEXT_MARGIN, + alpha: 0.6, + solid: true, + isFacingAvatar: true, + visible: false + }); + currentlyShownAvatar.overlay = over; + } + + var nameToShow = avatarData.displayName ? avatarData.displayName : + (avatarData.sessionDisplayName ? avatarData.sessionDisplayName : UNKNOWN_NAME); + var textSize = Overlays.textSize(currentlyShownAvatar.overlay, nameToShow); + + Overlays.editOverlay(currentlyShownAvatar.overlay, { + dimensions: { + x: textSize.width + 2 * TEXT_MARGIN, + y: TEXT_LINE_HEIGHT + 2 * TEXT_MARGIN + }, + localPosition: { x: 0, y: METERS_ABOVE_HEAD, z: 0 }, + text: nameToShow, + parentID: avatarData.sessionUUID, + parentJointIndex: avatarData.getJointIndex("Head"), + visible: true + }); + + hideTimer = Script.setTimeout(function() { + clearOverlay(); + }, HIDE_MS); +} + +function touchBegin(event) { + currentTouchToAnalyze = event; +} + +function touchEnd(event) { + if (Vec3.distance({x: event.x, y: event.y }, {x: currentTouchToAnalyze.x, y: currentTouchToAnalyze.y}) > MAX_DISTANCE_PX) { + printd("[AVATARNAME] touchEnd moved too much"); + currentTouchToAnalyze = null; + return; + } + + var pickRay = Camera.computePickRay(event.x, event.y); + var avatarRay = AvatarManager.findRayIntersection(pickRay, [], [MyAvatar.sessionUUID]) + + if (avatarRay.intersects) { + touchedAvatar(avatarRay.avatarID, AvatarManager.getAvatar(avatarRay.avatarID)); + } else { + printd("[AVATARNAME] touchEnd released outside the avatar"); + } + + currentTouchToAnalyze = null; +} + +var runAtLeastOnce = false; + +function ending() { + if (!runAtLeastOnce) { + return; + } + + Controller.touchBeginEvent.disconnect(touchBegin); + Controller.touchEndEvent.disconnect(touchEnd); + Controller.mousePressEvent.disconnect(touchBegin); + Controller.mouseReleaseEvent.disconnect(touchEnd); + + if (currentlyShownAvatar.overlay) { + Overlays.deleteOverlay(currentlyShownAvatar.overlay); + currentlyShownAvatar.overlay = null; + } + if (currentlyShownAvatar.avatar) { + currentlyShownAvatar.avatar = null; + } +} + +function init() { + Controller.touchBeginEvent.connect(touchBegin); + Controller.touchEndEvent.connect(touchEnd); + Controller.mousePressEvent.connect(touchBegin); + Controller.mouseReleaseEvent.connect(touchEnd); + + Script.scriptEnding.connect(function () { + ending(); + }); + + runAtLeastOnce = true; +} + +module.exports = { + init: init, + ending: ending +} + +//init(); // Enable to use in desktop as a standalone + +}()); // END LOCAL_SCOPE \ No newline at end of file diff --git a/scripts/system/+android_interface/modes.js b/scripts/system/+android_interface/modes.js new file mode 100644 index 0000000000..f495af3bba --- /dev/null +++ b/scripts/system/+android_interface/modes.js @@ -0,0 +1,124 @@ +"use strict"; +// +// modes.js +// scripts/system/ +// +// Created by Gabriel Calero & Cristian Duarte on Jan 23, 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 +// +(function() { // BEGIN LOCAL_SCOPE + +var modeButton; +var currentMode; +var barQml; + +var SETTING_CURRENT_MODE_KEY = 'Android/Mode'; +var MODE_VR = "VR", MODE_RADAR = "RADAR", MODE_MY_VIEW = "MY VIEW"; +var DEFAULT_MODE = MODE_MY_VIEW; +var nextMode = {}; +nextMode[MODE_RADAR]=MODE_MY_VIEW; +nextMode[MODE_MY_VIEW]=MODE_RADAR; +var modeLabel = {}; +modeLabel[MODE_RADAR]="TOP VIEW"; +modeLabel[MODE_MY_VIEW]="MY VIEW"; + +var logEnabled = false; +var radar = Script.require('./radar.js'); +var uniqueColor = Script.require('./uniqueColor.js'); +var displayNames = Script.require('./displayNames.js'); +var clickWeb = Script.require('./clickWeb.js'); + +function printd(str) { + if (logEnabled) { + print("[modes.js] " + str); + } +} + +function init() { + radar.setUniqueColor(uniqueColor); + radar.init(); + + barQml = new QmlFragment({ + qml: "hifi/modesbar.qml" + }); + modeButton = barQml.addButton({ + icon: "icons/myview-a.svg", + activeBgOpacity: 0.0, + hoverBgOpacity: 0.0, + activeHoverBgOpacity: 0.0, + text: "MODE", + height:240, + bottomMargin: 16, + textSize: 38, + fontFamily: "Raleway", + fontBold: true + + }); + + switchToMode(getCurrentModeSetting()); + + modeButton.entered.connect(modeButtonPressed); + modeButton.clicked.connect(modeButtonClicked); +} + +function shutdown() { + modeButton.entered.disconnect(modeButtonPressed); + modeButton.clicked.disconnect(modeButtonClicked); +} + +function modeButtonPressed() { + Controller.triggerHapticPulseOnDevice(Controller.findDevice("TouchscreenVirtualPad"), 0.1, 40.0, 0); +} + +function modeButtonClicked() { + switchToMode(nextMode[currentMode]); +} + +function saveCurrentModeSetting(mode) { + Settings.setValue(SETTING_CURRENT_MODE_KEY, mode); +} + +function getCurrentModeSetting(mode) { + return Settings.getValue(SETTING_CURRENT_MODE_KEY, DEFAULT_MODE); +} + +function switchToMode(newMode) { + // before leaving radar mode + if (currentMode == MODE_RADAR) { + radar.endRadarMode(); + } + currentMode = newMode; + modeButton.text = modeLabel[currentMode]; + + saveCurrentModeSetting(currentMode); + + if (currentMode == MODE_RADAR) { + radar.startRadarMode(); + displayNames.ending(); + clickWeb.ending(); + } else if (currentMode == MODE_MY_VIEW) { + // nothing to do yet + displayNames.init(); + clickWeb.init(); + } else { + printd("Unknown view mode " + currentMode); + } + +} + +function sendToQml(o) { + if(barQml) { + barQml.sendToQml(o); + } +} + +Script.scriptEnding.connect(function () { + shutdown(); +}); + +init(); + +}()); // END LOCAL_SCOPE \ No newline at end of file diff --git a/scripts/system/+android_interface/radar.js b/scripts/system/+android_interface/radar.js new file mode 100644 index 0000000000..1cbe721ad0 --- /dev/null +++ b/scripts/system/+android_interface/radar.js @@ -0,0 +1,1217 @@ +"use strict"; +// +// radar.js +// scripts/system/+android/ +// +// Created by Cristian Duarte & Gabriel Calero on 31 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 +// + +var radarModeInterface = {}; + +var logEnabled = false; +function printd(str) { + if (logEnabled) { + print("[radar.js] " + str); + } +} + +var radar = false; +var RADAR_HEIGHT_INIT_DELTA = 10; +var radarHeight = MyAvatar.position.y + RADAR_HEIGHT_INIT_DELTA; // camera position (absolute y) +var tablet; + +var RADAR_CAMERA_OFFSET = -1; // 1 meter below the avatar +var ABOVE_GROUND_DROP = 2; +var MOVE_BY = 1; + +// Swipe/Drag vars +var PINCH_INCREMENT_FIRST = 0.4; // 0.1 meters zoom in - out +var PINCH_INCREMENT = 0.4; // 0.1 meters zoom in - out +var RADAR_HEIGHT_MAX_PLUS_AVATAR = 80; +var RADAR_HEIGHT_MIN_PLUS_AVATAR = 2; +var RADAR_CAMERA_DISTANCE_TO_ICONS = 1.5; // Icons are near the camera to prevent the LOD manager dismissing them +var RADAR_ICONS_APPARENT_DISTANCE_TO_AVATAR_BASE = 1; // How much above the avatar base should the icon appear +var AVATAR_DISPLAY_NAME_HEIGHT = 106; +var AVATAR_DISPLAY_NAME_CHAR_WIDTH = 48; +var AVATAR_DISPLAY_NAME_FONT_SIZE = 50; +var lastDragAt; +var lastDeltaDrag; + +var uniqueColor; + +function moveTo(position) { + if (radar) { + MyAvatar.goToLocation(position, false); + Camera.position = { + x : position.x, + y : radarHeight, + z : position.z + }; + } +} + +function keyPressEvent(event) { + if (radar) { + switch (event.text) { + case "UP": + moveTo(Vec3.sum(MyAvatar.position, { + x : 0.0, + y : 0, + z : -1 * MOVE_BY + })); + break; + case "DOWN": + moveTo(Vec3.sum(MyAvatar.position, { + x : 0, + y : 0, + z : MOVE_BY + })); + break; + case "LEFT": + moveTo(Vec3.sum(MyAvatar.position, { + x : -1 * MOVE_BY, + y : 0, + z : 0 + })); + break; + case "RIGHT": + moveTo(Vec3.sum(MyAvatar.position, { + x : MOVE_BY, + y : 0, + z : 0 + })); + break; + } + } +} + +function toggleRadarMode() { + if (radar) { + endRadar(); + } else { + startRadar(); + } +} + +function fakeDoubleTap(event) { + // CLD - temporarily disable toggling mode through double tap + // * As we have a new UI for toggling between modes, it may be discarded + // completely in the future. + // toggleRadarMode(); + teleporter.dragTeleportUpdate(event); + teleporter.dragTeleportRelease(event); +} + +var DOUBLE_TAP_TIME = 300; +var fakeDoubleTapStart = Date.now(); +var touchEndCount = 0; + +/* + * Counts touchEnds and if there were 2 in the DOUBLE_TAP_TIME lapse, it + * triggers a fakeDoubleTap and returns true. Otherwise, returns false (no + * double tap yet) + */ +function analyzeDoubleTap(event) { + var fakeDoubleTapEnd = Date.now(); + var elapsed = fakeDoubleTapEnd - fakeDoubleTapStart; + if (elapsed > DOUBLE_TAP_TIME) { + touchEndCount = 0; + } + + // if this is our first "up" then record time so we can + // later determine if second "up" is a double tap + if (touchEndCount == 0) { + fakeDoubleTapStart = Date.now(); + } + touchEndCount++; + + if (touchEndCount >= 2) { + var fakeDoubleTapEnd = Date.now(); + var elapsed = fakeDoubleTapEnd - fakeDoubleTapStart; + printd("-- fakeDoubleTapEnd:" + fakeDoubleTapEnd + "-- elapsed:" + + elapsed) + if (elapsed <= DOUBLE_TAP_TIME) { + touchEndCount = 0; + fakeDoubleTap(event); + return true; // don't do the normal touch end processing + } + + touchEndCount = 0; + } + return false; +} + +function touchEnd(event) { + printd("touchEnd received " + JSON.stringify(event)); + // Clean up touch variables + lastDragAt = null; + lastDeltaDrag = null; + touchStartingCoordinates = null; // maybe in special cases it should be + // setup later? + startedDraggingCamera = false; + prevTouchPinchRadius = null; + draggingCamera = false; + + if (movingCamera) { + // if camera was indeed moving, we should not further process, it was + // just dragging + movingCamera = false; + dragModeFunc = null; + return; + } + + // teleport release analysis + if (teleporter && teleporter.dragTeleportUpdate == dragModeFunc) { + teleporter.dragTeleportRelease(event); + dragModeFunc = null; + return; + } + dragModeFunc = null; + + // if pinching or moving is still detected, cancel + if (event.isPinching) { + printd("touchEnd fail because isPinching"); + return; + } + if (event.isPinchOpening) { + printd("touchEnd fail because isPinchingOpening"); + return; + } + if (event.isMoved) { + printd("touchEnd fail because isMoved"); + return; + } + + if (analyzeDoubleTap(event)) + return; // double tap detected, finish + +} + +/** + * Polyfill for sign(x) + */ +if (!Math.sign) { + Math.sign = function(x) { + // If x is NaN, the result is NaN. + // If x is -0, the result is -0. + // If x is +0, the result is +0. + // If x is negative and not -0, the result is -1. + // If x is positive and not +0, the result is +1. + x = +x; // convert to a number + if (x === 0 || isNaN(x)) { + return Number(x); + } + return x > 0 ? 1 : -1; + }; +} + +/******************************************************************************* + * Line and Plane intersection methods + ******************************************************************************/ + +/** + * findLinePlaneIntersection Given points p {x: y: z:} and q that define a line, + * and the plane of formula ax+by+cz+d = 0, returns the intersection point or + * null if none. + */ +function findLinePlaneIntersection(p, q, a, b, c, d) { + return findLinePlaneIntersectionCoords(p.x, p.y, p.z, q.x, q.y, q.z, a, b, + c, d); +} + +/** + * findLineToHeightIntersection Given points p {x: y: z:} and q that define a + * line, and a planeY value that defines a plane paralel to 'the floor' xz + * plane, returns the intersection to that plane or null if none. + */ +function findLineToHeightIntersection(p, q, planeY) { + return findLinePlaneIntersection(p, q, 0, 1, 0, -planeY); +} + +/** + * findLinePlaneIntersectionCoords (to avoid requiring unnecessary + * instantiation) Given points p with px py pz and q that define a line, and the + * plane of formula ax+by+cz+d = 0, returns the intersection point or null if + * none. + */ +function findLinePlaneIntersectionCoords(px, py, pz, qx, qy, qz, a, b, c, d) { + var tDenom = a * (qx - px) + b * (qy - py) + c * (qz - pz); + if (tDenom == 0) + return null; + + var t = -(a * px + b * py + c * pz + d) / tDenom; + + return { + x : (px + t * (qx - px)), + y : (py + t * (qy - py)), + z : (pz + t * (qz - pz)) + }; +} + +/** + * findLineToHeightIntersection Given points p with px py pz and q that define a + * line, and a planeY value that defines a plane paralel to 'the floor' xz + * plane, returns the intersection to that plane or null if none. + */ +function findLineToHeightIntersectionCoords(px, py, pz, qx, qy, qz, planeY) { + return findLinePlaneIntersectionCoords(px, py, pz, qx, qy, qz, 0, 1, 0, + -planeY); +} + +function findRayIntersection(pickRay) { + // Check 3D overlays and entities. Argument is an object with origin and + // direction. + var result = Overlays.findRayIntersection(pickRay); + if (!result.intersects) { + result = Entities.findRayIntersection(pickRay, true); + } + return result; +} + +/** + * Given a 2d point (x,y) this function returns the intersection (x, y, z) of + * the computedPickRay for that point with the plane y = py + */ +function computePointAtPlaneY(x, y, py) { + var ray = Camera.computePickRay(x, y); + var p1 = ray.origin; + var p2 = Vec3.sum(p1, Vec3.multiply(ray.direction, 1)); + return findLineToHeightIntersectionCoords(p1.x, p1.y, p1.z, p2.x, p2.y, + p2.z, py); +} + +/******************************************************************************* + * + ******************************************************************************/ + +var touchStartingCoordinates = null; + +var KEEP_PRESSED_FOR_TELEPORT_MODE_TIME = 750; +var touchBeginTime; + +function touchBegin(event) { + var coords = { + x : event.x, + y : event.y + }; + touchStartingCoordinates = coords; + touchBeginTime = Date.now(); +} + +var startedDraggingCamera = false; // first time +var draggingCamera = false; // is trying +var movingCamera = false; // definitive + +var MIN_DRAG_DISTANCE_TO_CONSIDER = 100; // distance by axis, not real + // distance + +var prevTouchPinchRadius = null; + +function pinchUpdate(event) { + if (!event.isMoved) + return; + if (event.radius <= 0) + return; + + // pinch management + var avatarY = MyAvatar.position.y; + var pinchIncrement; + if (!!prevTouchPinchRadius) { + // no prev value + pinchIncrement = PINCH_INCREMENT + * Math.abs(event.radius - prevTouchPinchRadius) * 0.1; + } else { + pinchIncrement = PINCH_INCREMENT_FIRST; + } + + if (event.isPinching) { + if (radarHeight + pinchIncrement > RADAR_HEIGHT_MAX_PLUS_AVATAR + + avatarY) { + radarHeight = RADAR_HEIGHT_MAX_PLUS_AVATAR + avatarY; + } else { + radarHeight += pinchIncrement; + } + } else if (event.isPinchOpening) { + if (radarHeight - pinchIncrement < RADAR_HEIGHT_MIN_PLUS_AVATAR + + avatarY) { + radarHeight = RADAR_HEIGHT_MIN_PLUS_AVATAR + avatarY; + } else { + radarHeight -= pinchIncrement; + } + } + + Camera.position = { + x : Camera.position.x, + y : radarHeight, + z : Camera.position.z + }; + + if (!draggingCamera) { + startedDraggingCamera = true; + draggingCamera = true; + } + + prevTouchPinchRadius = event.radius; +} + +function isInsideSquare(coords0, coords1, halfside) { + return coords0 != undefined && coords1 != undefined && + Math.abs(coords0.x - coords1.x) <= halfside + && Math.abs(coords0.y - coords1.y) <= halfside; +} + +function dragScrollUpdate(event) { + if (!event.isMoved) + return; + + // drag management + var pickRay = Camera.computePickRay(event.x, event.y); + var dragAt = Vec3.sum(pickRay.origin, Vec3.multiply(pickRay.direction, + radarHeight - MyAvatar.position.y)); + + if (lastDragAt === undefined || lastDragAt === null) { + lastDragAt = dragAt; + return; + } + + var deltaDrag = { + x : (lastDragAt.x - dragAt.x), + y : 0, + z : (lastDragAt.z - dragAt.z) + }; + + lastDragAt = dragAt; + if (lastDeltaDrag === undefined || lastDeltaDrag === null) { + lastDeltaDrag = deltaDrag; + return; + } + + if (!draggingCamera) { + startedDraggingCamera = true; + draggingCamera = true; + } else { + if (!movingCamera) { + if (!isInsideSquare(touchStartingCoordinates, event, + MIN_DRAG_DISTANCE_TO_CONSIDER)) { + movingCamera = true; + } + } + + if (movingCamera) { + if (Math.sign(deltaDrag.x) == Math.sign(lastDeltaDrag.x) + && Math.sign(deltaDrag.z) == Math.sign(lastDeltaDrag.z)) { + // Process movement if direction of the movement is the same + // than the previous frame + // process delta + var moveCameraTo = Vec3.sum(Camera.position, deltaDrag); + // move camera + Camera.position = moveCameraTo; + } else { + // Do not move camera if it's changing direction in this case, + // wait until the next direction confirmation.. + } + lastDeltaDrag = deltaDrag; // save last + } + } +} + +/******************************************************************************* + * Teleport feature + ******************************************************************************/ + +function Teleporter() { + + var SURFACE_DETECTION_FOR_TELEPORT = true; // true if uses teleport.js + // similar logic to detect + // surfaces. false if uses plain + // teleport to avatar same + // height. + + var TELEPORT_TARGET_MODEL_URL = Script + .resolvePath("../assets/models/teleport-destination.fbx"); + var TELEPORT_TOO_CLOSE_MODEL_URL = Script + .resolvePath("../assets/models/teleport-cancel.fbx"); + + var TELEPORT_MODEL_DEFAULT_DIMENSIONS = { + x : 0.10, + y : 0.00001, + z : 0.10 + }; + + var teleportOverlay = Overlays.addOverlay("model", { + url : TELEPORT_TARGET_MODEL_URL, + dimensions : TELEPORT_MODEL_DEFAULT_DIMENSIONS, + orientation : Quat.fromPitchYawRollDegrees(0, 180, 0), + visible : false + }); + + var teleportCancelOverlay = Overlays.addOverlay("model", { + url : TELEPORT_TOO_CLOSE_MODEL_URL, + dimensions : TELEPORT_MODEL_DEFAULT_DIMENSIONS, + orientation : Quat.fromPitchYawRollDegrees(0, 180, 0), + visible : false + }); + + var TELEPORT_COLOR = { + red : 0, + green : 255, + blue : 255 + }; + var TELEPORT_CANCEL_COLOR = { + red : 255, + green : 255, + blue : 0 + }; + + var teleportLine = Overlays.addOverlay("line3d", { + start : { + x : 0, + y : 0, + z : 0 + }, + end : { + x : 0, + y : 0, + z : 0 + }, + color : TELEPORT_COLOR, + alpha : 1, + lineWidth : 2, + dashed : false, + visible : false + }); + + // code from teleport.js + var TELEPORT_TARGET = { + NONE : 'none', // Not currently targetting anything + INVISIBLE : 'invisible', // The current target is an invvsible + // surface + INVALID : 'invalid', // The current target is invalid (wall, ceiling, + // etc.) + SURFACE : 'surface', // The current target is a valid surface + SEAT : 'seat', // The current target is a seat + } + + var TELEPORT_CANCEL_RANGE = 1; + var teleportTargetType = TELEPORT_TARGET.NONE; + + function parseJSON(json) { + try { + return JSON.parse(json); + } catch (e) { + return undefined; + } + } + + /* + * Enhanced with intersection with terrain instead of using current avatar y + * position if SURFACE_DETECTION_FOR_TELEPORT is true + */ + function computeDestination(touchEventPos, avatarPosition, cameraPosition, + radarH) { + if (SURFACE_DETECTION_FOR_TELEPORT) { + var pickRay = Camera.computePickRay(touchEventPos.x, + touchEventPos.y); + printd("newTeleportDetect - pickRay " + JSON.stringify(pickRay)); + var destination = Entities.findRayIntersection(pickRay, true, [], + [], false, true); + printd("newTeleportDetect - destination " + + JSON.stringify(destination)); + return destination; + } else { + var pickRay = Camera.computePickRay(touchEventPos.x, + touchEventPos.y); + var pointingAt = Vec3.sum(pickRay.origin, Vec3.multiply( + pickRay.direction, radarH)); + var destination = { + x : pointingAt.x, + y : avatarPosition.y, + z : pointingAt.z + }; + return destination; + } + } + + function renderTeleportOverlays(destination) { + var overlayPosition = findLineToHeightIntersection(destination, + Camera.position, Camera.position.y + - RADAR_CAMERA_DISTANCE_TO_ICONS); + printd("[newTeleport] TELEPORT ! render overlay at " + + JSON.stringify(overlayPosition)); + + // CLD note Oct 11, 2017 + // Version of teleport.js 3c109f294f88ba7573bd1221f907f2605893c509 + // doesn't allow invisible surfaces, let's allow it for now + if (teleportTargetType == TELEPORT_TARGET.SURFACE + || teleportTargetType == TELEPORT_TARGET.INVISIBLE) { + Overlays.editOverlay(teleportOverlay, { + visible : true, + position : overlayPosition + }); + Overlays.editOverlay(teleportCancelOverlay, { + visible : false + }); + Overlays.editOverlay(teleportLine, { + start : MyAvatar.position, + end : destination, + color : TELEPORT_COLOR, + visible : true + }); + } else if (teleportTargetType == TELEPORT_TARGET.INVALID) { + Overlays.editOverlay(teleportOverlay, { + visible : false + }); + Overlays.editOverlay(teleportCancelOverlay, { + visible : true, + position : overlayPosition + }); + Overlays.editOverlay(teleportLine, { + start : MyAvatar.position, + end : destination, + color : TELEPORT_CANCEL_COLOR, + visible : true + }); + } else { // TELEPORT_TARGET:NONE? + Overlays.editOverlay(teleportOverlay, { + visible : false + }); + Overlays.editOverlay(teleportCancelOverlay, { + visible : false + }); + Overlays.editOverlay(teleportLine, { + visible : false + }); + } + } + + var BORDER_DISTANCE_PX = 100; + var border_top = 0; + var border_left = 0; + var border_right = Window.innerWidth; + var border_bottom = Window.innerHeight; + + function moveOnBorders(event) { + var xDelta = 0; + var zDelta = 0; + + if (event.y <= border_top + BORDER_DISTANCE_PX) { + zDelta = -0.1; + } else if (event.y >= border_bottom - BORDER_DISTANCE_PX) { + zDelta = 0.1; + } + if (event.x <= border_left + BORDER_DISTANCE_PX) { + xDelta = -0.1; + } else if (event.x >= border_right - BORDER_DISTANCE_PX) { + xDelta = 0.1; + } + if (xDelta == 0 && zDelta == 0) { + draggingCamera = false; + return; + } + + + Camera.position = Vec3.sum(Camera.position, { + x : xDelta, + y : 0, + z : zDelta + }); + draggingCamera = true; + } + + // When determininig whether you can teleport to a location, the normal of + // the + // point that is being intersected with is looked at. If this normal is more + // than MAX_ANGLE_FROM_UP_TO_TELEPORT degrees from <0, 1, 0> (straight up), + // then + // you can't teleport there. + const MAX_ANGLE_FROM_UP_TO_TELEPORT = 70; + function getTeleportTargetType(intersection) { + if (SURFACE_DETECTION_FOR_TELEPORT) { + if (!intersection.intersects) { + return TELEPORT_TARGET.NONE; + } + var props = Entities.getEntityProperties(intersection.entityID, [ + 'userData', 'visible' ]); + var data = parseJSON(props.userData); + if (data !== undefined && data.seat !== undefined) { + return TELEPORT_TARGET.SEAT; + } + + if (!props.visible) { + return TELEPORT_TARGET.INVISIBLE; + } + + var surfaceNormal = intersection.surfaceNormal; + var adj = Math.sqrt(surfaceNormal.x * surfaceNormal.x + + surfaceNormal.z * surfaceNormal.z); + var angleUp = Math.atan2(surfaceNormal.y, adj) * (180 / Math.PI); + + if (angleUp < (90 - MAX_ANGLE_FROM_UP_TO_TELEPORT) + || angleUp > (90 + MAX_ANGLE_FROM_UP_TO_TELEPORT) + || Vec3.distance(MyAvatar.position, + intersection.intersection) <= TELEPORT_CANCEL_RANGE) { + return TELEPORT_TARGET.INVALID; + } else { + return TELEPORT_TARGET.SURFACE; + } + } else { + var destination = intersection; + if (Vec3.distance(MyAvatar.position, destination) <= TELEPORT_CANCEL_RANGE) { + return TELEPORT_TARGET.INVALID; + } else { + return TELEPORT_TARGET.SURFACE; + } + } + } + ; + + function moveToFromEvent(event) { + var destination = computeDestination(event, MyAvatar.position, + Camera.position, radarHeight); + moveTo(SURFACE_DETECTION_FOR_TELEPORT ? Vec3.sum( + destination.intersection, { + y : 1 + }) : destination); + return true; + } + + return { + dragTeleportBegin : function(event) { + printd("[newTeleport] TELEPORT began"); + var overlayDimensions = teleportIconModelDimensions(MyAvatar.position.y); + // var destination = computeDestination(event, MyAvatar.position, + // Camera.position, radarHeight); + // Dimension teleport and cancel overlays (not show them yet) + Overlays.editOverlay(teleportOverlay, { + dimensions : overlayDimensions + }); + Overlays.editOverlay(teleportCancelOverlay, { + dimensions : overlayDimensions + }); + // Position line + Overlays.editOverlay(teleportLine, { + visible : true, + start : 0, + end : 0 + }); + }, + + dragTeleportUpdate : function(event) { + // if in border, move camera + moveOnBorders(event); + + var destination = computeDestination(event, MyAvatar.position, + Camera.position, radarHeight); + + teleportTargetType = getTeleportTargetType(destination); + renderTeleportOverlays(SURFACE_DETECTION_FOR_TELEPORT ? destination.intersection + : destination); + }, + + dragTeleportRelease : function(event) { + printd("[newTeleport] TELEPORT released at " + + JSON.stringify(event)); + // CLD note Oct 11, 2017 + // Version of teleport.js 3c109f294f88ba7573bd1221f907f2605893c509 + // doesn't allow invisible surfaces, let's allow it for now + if (teleportTargetType == TELEPORT_TARGET.SURFACE + || teleportTargetType == TELEPORT_TARGET.INVISIBLE) { + moveToFromEvent(event); + } + teleportTargetType = TELEPORT_TARGET.NONE; + + Overlays.editOverlay(teleportOverlay, { + visible : false + }); + Overlays.editOverlay(teleportLine, { + visible : false + }); + Overlays.editOverlay(teleportCancelOverlay, { + visible : false + }); + } + }; + +} + +var teleporter = Teleporter(); + +/******************************************************************************* + * + ******************************************************************************/ + +var dragModeFunc = null; // by default is nothing + +function oneFingerTouchUpdate(event) { + if (dragModeFunc) { + dragModeFunc(event); + } else { + if (!isInsideSquare(touchStartingCoordinates, event, + MIN_DRAG_DISTANCE_TO_CONSIDER)) { + dragModeFunc = dragScrollUpdate; + dragModeFunc(event); + } else { + var now = Date.now(); // check time + if (now - touchBeginTime >= KEEP_PRESSED_FOR_TELEPORT_MODE_TIME) { + teleporter.dragTeleportBegin(event); + dragModeFunc = teleporter.dragTeleportUpdate; + dragModeFunc(event); + } else { + // not defined yet, let's wait for time or movement to happen + } + } + } +} + +function touchUpdate(event) { + if (event.isPinching || event.isPinchOpening) { + pinchUpdate(event); + } else { + oneFingerTouchUpdate(event); + } +} + +/******************************************************************************* + * Avatar cache structure for showing avatars markers + ******************************************************************************/ + +// by QUuid +var avatarsData = {}; +var avatarsIcons = []; // a parallel list of icons (overlays) to easily run + // through +var avatarsNames = []; // a parallel list of names (overlays) to easily run + // through + +function getAvatarIconForUser(uid) { + var color = uniqueColor.getColor(uid); + if (color.charAt(0) == '#') { + color = color.substring(1, color.length); + } + // FIXME: this is a temporary solution until we can use circle3d with + // lineWidth + return Script.resolvePath("assets/images/circle-" + color + ".svg"); +} + +var avatarIconDimensionsVal = { + x : 0, + y : 0, + z : 0.00001 +}; +function avatarIconPlaneDimensions() { + // given the current height, give a size + var xy = -0.003531 * (radarHeight - MyAvatar.position.y) + 0.1; + avatarIconDimensionsVal.x = Math.abs(xy); + avatarIconDimensionsVal.y = Math.abs(xy); + // reuse object + return avatarIconDimensionsVal; +} + +function currentOverlayIconForAvatar(QUuid) { + if (avatarsData[QUuid] != undefined) { + return avatarsData[QUuid].icon; + } else { + return null; + } +} + +function currentOverlayNameForAvatar(QUuid) { + if (avatarsData[QUuid] != undefined) { + return avatarsData[QUuid].name; + } else { + return null; + } +} + +function saveAvatarData(QUuid) { + if (QUuid == null) + return; + var avat = AvatarList.getAvatar(QUuid); + printd("avatar added save avatar " + QUuid); + + if (!avat) + return; + + if (avatarsData[QUuid] != undefined) { + avatarsData[QUuid].position = avat.position; + } else { + var avatarIcon = Overlays.addOverlay("circle3d", { + color: uniqueColor.convertHexToRGB(uniqueColor.getColor(QUuid)), + dimensions: ICON_ENTITY_DEFAULT_DIMENSIONS, + rotation: Quat.fromPitchYawRollDegrees(90, 0, 0), + innerRadius: 1.8, + outerRadius: 2, + isSolid: true, + visible: false + }); + + var needRefresh = !avat || !avat.displayName; + var displayName = avat && avat.displayName ? avat.displayName + : "Unknown"; + var textWidth = displayName.length * AVATAR_DISPLAY_NAME_CHAR_WIDTH; + var avatarName = Overlays.addOverlay("text", { + width: textWidth, + height: AVATAR_DISPLAY_NAME_HEIGHT, + color: { red: 255, green: 255, blue: 255}, + backgroundAlpha: 0.0, + textRaiseColor: { red: 0, green: 0, blue: 0}, + font: {size: AVATAR_DISPLAY_NAME_FONT_SIZE, bold: true}, + visible: false, + text: displayName, + textAlignCenter: true + }); + avatarsIcons.push(avatarIcon); + avatarsNames.push(avatarName); + avatarsData[QUuid] = { + position : avat.position, + icon : avatarIcon, + name : avatarName, + textWidth : textWidth, + needRefresh : needRefresh + }; + } +} + +function removeAvatarData(QUuid) { + if (QUuid == null) + return; + + var itsOverlay = currentOverlayIconForAvatar(QUuid); + if (itsOverlay != null) { + Overlays.deleteOverlay(itsOverlay); + } + var itsNameOverlay = currentOverlayNameForAvatar(QUuid); + if (itsNameOverlay != null) { + Overlays.deleteOverlay(itsNameOverlay); + } + + var idx = avatarsIcons.indexOf(itsOverlay); + avatarsIcons.splice(idx, 1); + idx = avatarsNames.indexOf(itsNameOverlay); + avatarsNames.splice(idx, 1); + + delete avatarsData[QUuid]; +} + +function saveAllOthersAvatarsData() { + var avatarIds = AvatarList.getAvatarIdentifiers(); + var len = avatarIds.length; + for (var i = 0; i < len; i++) { + if (avatarIds[i]) { + saveAvatarData(avatarIds[i]); + } + } +} + +function avatarAdded(QUuid) { + printd("avatar added " + QUuid);// + " at " + + // JSON.stringify(AvatarList.getAvatar(QUuid).position)); + saveAvatarData(QUuid); +} + +function avatarRemoved(QUuid) { + printd("avatar removed " + QUuid); + removeAvatarData(QUuid); +} + +/******************************************************************************* + * Avatar Icon/Markers rendering + ******************************************************************************/ +var myAvatarIcon; +var myAvatarName; +function distanceForCameraHeight(h) { + if (h < 30) return 1; + if (h < 40) return 2; + if (h < 50) return 2.5; + return 5; +} +function renderMyAvatarIcon() { + var commonY = Camera.position.y - distanceForCameraHeight(Camera.position.y); + var iconPos = findLineToHeightIntersectionCoords( MyAvatar.position.x, + MyAvatar.position.y + RADAR_ICONS_APPARENT_DISTANCE_TO_AVATAR_BASE, + MyAvatar.position.z, + Camera.position.x, Camera.position.y, Camera.position.z, + commonY); + if (!iconPos) { printd("avatarmy icon pos null"); return;} + var iconDimensions = avatarIconPlaneDimensions(); + + var avatarPos = MyAvatar.position; + var cameraPos = Camera.position; + var borderPoints = [ + computePointAtPlaneY(0, 0, commonY), + computePointAtPlaneY(Window.innerWidth, Window.innerHeight, commonY) ]; + + var p1 = findLineToHeightIntersectionCoords(avatarPos.x, avatarPos.y, + avatarPos.z, cameraPos.x, cameraPos.y, cameraPos.z, commonY); + var x = (p1.x - borderPoints[0].x) * (Window.innerWidth) + / (borderPoints[1].x - borderPoints[0].x); + var y = (p1.z - borderPoints[0].z) * (Window.innerHeight) + / (borderPoints[1].z - borderPoints[0].z); + + if (!myAvatarIcon && MyAvatar.SELF_ID) { + myAvatarIcon = Overlays.addOverlay("circle3d", { + color: uniqueColor.convertHexToRGB(uniqueColor.getColor(MyAvatar.SELF_ID)), + dimensions: ICON_ENTITY_DEFAULT_DIMENSIONS, + rotation: Quat.fromPitchYawRollDegrees(90, 0, 0), + innerRadius: 1.8, + outerRadius: 2, + isSolid: true, + visible: false + }); + } + + if (!myAvatarName) { + myAvatarName = Overlays.addOverlay("text", { + width: 100, + height: AVATAR_DISPLAY_NAME_HEIGHT, + textAlignCenter: true, + color: { red: 255, green: 255, blue: 255}, + backgroundAlpha: 0.0, + font: {size: AVATAR_DISPLAY_NAME_FONT_SIZE, bold: true}, + textRaiseColor: { red: 0, green: 0, blue: 0}, + visible: false, + text: "Me" + }); + } + + if (myAvatarIcon) { + Overlays.editOverlay(myAvatarIcon, { + visible : true, + dimensions : iconDimensions, + position : iconPos + }); + + } + var textSize = (14 + (iconDimensions.y - 0.03) * 15 / 0.06); + + Overlays.editOverlay(myAvatarName, { + visible : true, + x : x - 18 + (iconDimensions.y - 0.03) * 2 / 0.06, + y : y + iconDimensions.y * 550, + font : { + size : textSize, + bold : true + }, + }); + +} + +function hideAllAvatarIcons() { + var len = avatarsIcons.length; + for (var i = 0; i < len; i++) { + Overlays.editOverlay(avatarsIcons[i], { + visible : false + }); + } + len = avatarsNames.length; + for (var j = 0; j < len; j++) { + Overlays.editOverlay(avatarsNames[j], { + visible : false + }); + } + if (myAvatarIcon) { + Overlays.editOverlay(myAvatarIcon, { + visible : false + }); + } + Overlays.editOverlay(myAvatarName, { + visible : false + }) +} + +function renderAllOthersAvatarIcons() { + var avatarPos; + var iconDimensions = avatarIconPlaneDimensions(); + var commonY = Camera.position.y - distanceForCameraHeight(Camera.position.y); + var borderPoints = [ + computePointAtPlaneY(0, 0, commonY), + computePointAtPlaneY(Window.innerWidth, Window.innerHeight, commonY) ]; + + for ( var QUuid in avatarsData) { + if (avatarsData.hasOwnProperty(QUuid)) { + if (AvatarList.getAvatar(QUuid) != null) { + avatarPos = AvatarList.getAvatar(QUuid).position; + + var cameraPos = Camera.position; + var p1 = findLineToHeightIntersectionCoords(avatarPos.x, avatarPos.y, avatarPos.z, + cameraPos.x, cameraPos.y, cameraPos.z, + commonY); + + var x = (p1.x - borderPoints[0].x) * (Window.innerWidth) / (borderPoints[1].x - borderPoints[0].x); + var y = (p1.z - borderPoints[0].z) * (Window.innerHeight) / (borderPoints[1].z - borderPoints[0].z); + + if (avatarsData[QUuid].icon != undefined) { + var iconPos = findLineToHeightIntersectionCoords( avatarPos.x, avatarPos.y + RADAR_ICONS_APPARENT_DISTANCE_TO_AVATAR_BASE, avatarPos.z, + Camera.position.x, Camera.position.y, Camera.position.z, + commonY); + if (!iconPos) { print ("avatar icon pos bad for " + QUuid); continue; } + if (avatarsData[QUuid].needRefresh) { + var avat = AvatarList.getAvatar(QUuid); + if (avat && avat.displayName) { + Overlays.editOverlay(avatarsData[QUuid].name, { + width : avat.displayName.length + * AVATAR_DISPLAY_NAME_CHAR_WIDTH, + text : avat.displayName, + textAlignCenter : true + }); + avatarsData[QUuid].needRefresh = false; + } + } + var textSize = (14 + (iconDimensions.y - 0.03) * 15 / 0.06); + Overlays.editOverlay(avatarsData[QUuid].icon, { + visible : true, + dimensions : iconDimensions, + position : iconPos + }); + Overlays.editOverlay(avatarsData[QUuid].name, { + visible : true, + x : x - avatarsData[QUuid].textWidth * 0.5, + y : y + iconDimensions.y * 550, + font : { + size : textSize, + bold : true + } + }); + } + } + } + } +} + +var ICON_ENTITY_DEFAULT_DIMENSIONS = { + x : 0.10, + y : 0.00001, + z : 0.10 +}; + + +function teleportIconModelDimensions(y) { + var teleportModelDimensions = ICON_ENTITY_DEFAULT_DIMENSIONS; + var xz = -0.002831 * (radarHeight - y) + 0.1; + teleportModelDimensions.x = xz; + teleportModelDimensions.z = xz; + // reuse object + return teleportModelDimensions; +} + +/******************************************************************************* + * + ******************************************************************************/ + +function startRadar() { + printd("avatar added my avatar is " + MyAvatar.sessionUUID); + saveAllOthersAvatarsData(); + Camera.mode = "independent"; + + initCameraOverMyAvatar(); + + Camera.orientation = Quat.fromPitchYawRollDegrees(-90, 0, 0); + radar = true; + + Controller.setVPadEnabled(false); // this was done before in CompositeExtra in the DisplayPlugin (Checking for camera not independent, not radar mode) + + connectRadarModeEvents(); +} + +function endRadar() { + printd("-- endRadar"); + Camera.mode = "third person"; + radar = false; + + Controller.setVPadEnabled(true); + + disconnectRadarModeEvents(); + hideAllAvatarIcons(); +} + +function onRadarModeClicked() { + startRadar(); +} + +function onMyViewModeClicked() { + endRadar(); +} + +radarModeInterface.startRadarMode = function() { + startRadar(); +}; + +radarModeInterface.endRadarMode = function() { + endRadar(); +}; + +radarModeInterface.init = function() { + init(); +} + +radarModeInterface.setUniqueColor = function(c) { + uniqueColor = c; +}; + +module.exports = radarModeInterface; + +function updateRadar() { + // Update avatar icons + if (startedDraggingCamera) { + hideAllAvatarIcons(); + startedDraggingCamera = false; + } else if (!draggingCamera) { + renderMyAvatarIcon(); + renderAllOthersAvatarIcons(); + } +} + +function valueIfDefined(value) { + return value !== undefined ? value : ""; +} + +function connectRadarModeEvents() { + Script.update.connect(updateRadar); // 60Hz loop + Controller.keyPressEvent.connect(keyPressEvent); + Controller.touchUpdateEvent.connect(touchUpdate); + Window.domainChanged.connect(domainChanged); + MyAvatar.positionGoneTo.connect(positionGoneTo); +} + +function initCameraOverMyAvatar() { + radarHeight = MyAvatar.position.y + RADAR_HEIGHT_INIT_DELTA; + Camera.position = { + x : MyAvatar.position.x, + y : radarHeight, + z : MyAvatar.position.z + }; +} + +function domainChanged() { + initCameraOverMyAvatar(); +} + +function positionGoneTo() { + Camera.position = { + x : MyAvatar.position.x, + y : radarHeight, + z : MyAvatar.position.z + }; +} + +function disconnectRadarModeEvents() { + Script.update.disconnect(updateRadar); + Controller.keyPressEvent.disconnect(keyPressEvent); + Controller.touchUpdateEvent.disconnect(touchUpdate); + MyAvatar.positionGoneTo.disconnect(positionGoneTo); + Window.domainChanged.disconnect(domainChanged); +} + +function init() { + tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + + Controller.touchBeginEvent.connect(touchBegin); + Controller.touchEndEvent.connect(touchEnd); + + AvatarList.avatarAddedEvent.connect(avatarAdded); + AvatarList.avatarRemovedEvent.connect(avatarRemoved); +} diff --git a/scripts/system/+android_interface/stats.js b/scripts/system/+android_interface/stats.js new file mode 100644 index 0000000000..0731684291 --- /dev/null +++ b/scripts/system/+android_interface/stats.js @@ -0,0 +1,39 @@ +"use strict"; +// +// stats.js +// scripts/system/ +// +// Created by Sam Gondelman on 3/14/18 +// 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 +// + +(function() { // BEGIN LOCAL_SCOPE + +var statsbar; +var statsButton; + +function init() { + statsbar = new QmlFragment({ + qml: "hifi/StatsBar.qml" + }); + + statsButton = statsbar.addButton({ + icon: "icons/stats.svg", + activeIcon: "icons/stats.svg", + textSize: 45, + bgOpacity: 0.0, + activeBgOpacity: 0.0, + bgColor: "#FFFFFF", + text: "STATS" + }); + statsButton.clicked.connect(function() { + Menu.triggerOption("Show Statistics"); + }); +} + +init(); + +}()); // END LOCAL_SCOPE diff --git a/scripts/system/+android_interface/touchscreenvirtualpad.js b/scripts/system/+android_interface/touchscreenvirtualpad.js new file mode 100644 index 0000000000..d48b623e03 --- /dev/null +++ b/scripts/system/+android_interface/touchscreenvirtualpad.js @@ -0,0 +1,22 @@ +"use strict"; +// +// touchscreenvirtualpad.js +// scripts/system/ +// +// Created by Gabriel Calero & Cristian Duarte on Jan 16, 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 +// + +(function() { // BEGIN LOCAL_SCOPE + +function init() { + Controller.setVPadEnabled(true); + Controller.setVPadHidden(false); +} + +init(); + +}()); // END LOCAL_SCOPE diff --git a/scripts/system/+android_interface/uniqueColor.js b/scripts/system/+android_interface/uniqueColor.js new file mode 100644 index 0000000000..a2741642d2 --- /dev/null +++ b/scripts/system/+android_interface/uniqueColor.js @@ -0,0 +1,54 @@ +"use strict"; +// +// uniqueColor.js +// scripts/system/ +// +// Created by Gabriel Calero & Cristian Duarte on 17 Oct 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 +// + +var colorsMap = {}; +var colorsCount = 0; + // 'red', 'orange', 'yellow', 'green', 'cyan', 'blue', 'magenta' +var baseColors = [ '#EB3345', '#F0851F', '#FFCD29', '#94C338', '#11A6C5', '#294C9F', '#C01D84' ]; + +function getNextColor(n) { + var N = baseColors.length; + /*if (n < baseColors.length) { + return baseColors[n]; + } else { + var baseColor = baseColors[n % N]; + var d = (n / N) % 10; + var c2 = "" + Qt.lighter(baseColor, 1 + d / 10); + return c2; + }*/ + return baseColors[n%N]; +} + +function getColorForId(uuid) { + if (colorsMap == undefined) { + colorsMap = {}; + } + if (!colorsMap.hasOwnProperty(uuid)) { + colorsMap[uuid] = getNextColor(colorsCount); + colorsCount = colorsCount + 1; + } + return colorsMap[uuid]; +} + +module.exports = { + getColor: function(id) { + return getColorForId(id); + }, + convertHexToRGB: function(hex) { + var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); + return result ? { + red: parseInt(result[1], 16), + green: parseInt(result[2], 16), + blue: parseInt(result[3], 16) + } : null; + } +}; diff --git a/scripts/system/quickGoto.js b/scripts/system/quickGoto.js new file mode 100644 index 0000000000..c5560cce83 --- /dev/null +++ b/scripts/system/quickGoto.js @@ -0,0 +1,36 @@ +"use strict"; + +// +// quickGoto.js +// scripts/system/ +// +// Created by Dante Ruiz +// 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 + + function addGotoButton(destination) { + 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: destination + }); + var buttonDestination = destination; + button.clicked.connect(function() { + Window.location = "hifi://" + buttonDestination; + }); + Script.scriptEnding.connect(function () { + tablet.removeButton(button); + }); + } + + addGotoButton("dev-mobile"); + addGotoButton("quest-dev"); + +}()); // END LOCAL_SCOPE