From 0f07d0b49378ae578fd653876d2e48e00b2baada Mon Sep 17 00:00:00 2001 From: Gabriel Calero Date: Tue, 23 Jan 2018 11:43:29 -0300 Subject: [PATCH 01/29] Add bottom bar and address dialog (Android) --- android/app/build.gradle | 2 +- .../resources/icons/+android/backward.svg | 70 ++ .../resources/icons/+android/forward.svg | 71 ++ interface/resources/icons/+android/go-a.svg | 40 + interface/resources/icons/+android/go-i.svg | 40 + interface/resources/icons/+android/goto-a.svg | 28 + interface/resources/icons/+android/goto-i.svg | 28 + interface/resources/icons/+android/hide.svg | 947 ++++++++++++++++++ interface/resources/icons/+android/home.svg | 82 ++ .../resources/icons/+android/show-up.svg | 8 + .../qml/+android/AddressBarDialog.qml | 230 +++++ .../qml/hifi/+android/HifiConstants.qml | 54 + .../qml/hifi/+android/WindowHeader.qml | 113 +++ .../qml/hifi/+android/bottomHudOptions.qml | 89 ++ .../resources/qml/hifi/+android/bottombar.qml | 135 +++ .../resources/qml/hifi/+android/button.qml | 230 +++++ interface/src/Application.cpp | 2 + libraries/ui/src/QmlFragmentClass.cpp | 88 ++ libraries/ui/src/QmlFragmentClass.h | 45 + libraries/ui/src/ui/OffscreenQmlSurface.cpp | 13 +- scripts/+android/defaultScripts.js | 3 +- scripts/system/bottombar.js | 159 +++ scripts/system/goto-android.js | 96 ++ 23 files changed, 2570 insertions(+), 3 deletions(-) create mode 100644 interface/resources/icons/+android/backward.svg create mode 100644 interface/resources/icons/+android/forward.svg create mode 100755 interface/resources/icons/+android/go-a.svg create mode 100644 interface/resources/icons/+android/go-i.svg create mode 100755 interface/resources/icons/+android/goto-a.svg create mode 100644 interface/resources/icons/+android/goto-i.svg create mode 100644 interface/resources/icons/+android/hide.svg create mode 100644 interface/resources/icons/+android/home.svg create mode 100644 interface/resources/icons/+android/show-up.svg create mode 100644 interface/resources/qml/+android/AddressBarDialog.qml create mode 100644 interface/resources/qml/hifi/+android/HifiConstants.qml create mode 100644 interface/resources/qml/hifi/+android/WindowHeader.qml create mode 100644 interface/resources/qml/hifi/+android/bottomHudOptions.qml create mode 100644 interface/resources/qml/hifi/+android/bottombar.qml create mode 100644 interface/resources/qml/hifi/+android/button.qml create mode 100644 libraries/ui/src/QmlFragmentClass.cpp create mode 100644 libraries/ui/src/QmlFragmentClass.h create mode 100644 scripts/system/bottombar.js create mode 100644 scripts/system/goto-android.js diff --git a/android/app/build.gradle b/android/app/build.gradle index a2f008beef..97267258e2 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -23,7 +23,7 @@ android { '-DRELEASE_NUMBER=' + RELEASE_NUMBER, '-DRELEASE_TYPE=' + RELEASE_TYPE, '-DBUILD_BRANCH=' + BUILD_BRANCH, - '-DDISABLE_QML=ON', + '-DDISABLE_QML=OFF', '-DDISABLE_KTX_CACHE=OFF' } } diff --git a/interface/resources/icons/+android/backward.svg b/interface/resources/icons/+android/backward.svg new file mode 100644 index 0000000000..ae0893fc66 --- /dev/null +++ b/interface/resources/icons/+android/backward.svg @@ -0,0 +1,70 @@ + + + +image/svg+xml \ No newline at end of file diff --git a/interface/resources/icons/+android/forward.svg b/interface/resources/icons/+android/forward.svg new file mode 100644 index 0000000000..d03c4097d7 --- /dev/null +++ b/interface/resources/icons/+android/forward.svg @@ -0,0 +1,71 @@ + + + +image/svg+xml \ No newline at end of file diff --git a/interface/resources/icons/+android/go-a.svg b/interface/resources/icons/+android/go-a.svg new file mode 100755 index 0000000000..faecb15292 --- /dev/null +++ b/interface/resources/icons/+android/go-a.svg @@ -0,0 +1,40 @@ + + + + + + + + + + + + + diff --git a/interface/resources/icons/+android/go-i.svg b/interface/resources/icons/+android/go-i.svg new file mode 100644 index 0000000000..0f1298d573 --- /dev/null +++ b/interface/resources/icons/+android/go-i.svg @@ -0,0 +1,40 @@ + + + + + + + + + + + + + diff --git a/interface/resources/icons/+android/goto-a.svg b/interface/resources/icons/+android/goto-a.svg new file mode 100755 index 0000000000..5fb3e52e4c --- /dev/null +++ b/interface/resources/icons/+android/goto-a.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + diff --git a/interface/resources/icons/+android/goto-i.svg b/interface/resources/icons/+android/goto-i.svg new file mode 100644 index 0000000000..7613beb9e7 --- /dev/null +++ b/interface/resources/icons/+android/goto-i.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + diff --git a/interface/resources/icons/+android/hide.svg b/interface/resources/icons/+android/hide.svg new file mode 100644 index 0000000000..e09d517b08 --- /dev/null +++ b/interface/resources/icons/+android/hide.svg @@ -0,0 +1,947 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/interface/resources/icons/+android/home.svg b/interface/resources/icons/+android/home.svg new file mode 100644 index 0000000000..414c179e79 --- /dev/null +++ b/interface/resources/icons/+android/home.svg @@ -0,0 +1,82 @@ + + + +image/svg+xml \ No newline at end of file diff --git a/interface/resources/icons/+android/show-up.svg b/interface/resources/icons/+android/show-up.svg new file mode 100644 index 0000000000..0736b9a794 --- /dev/null +++ b/interface/resources/icons/+android/show-up.svg @@ -0,0 +1,8 @@ + + + + + + diff --git a/interface/resources/qml/+android/AddressBarDialog.qml b/interface/resources/qml/+android/AddressBarDialog.qml new file mode 100644 index 0000000000..e07af24b8d --- /dev/null +++ b/interface/resources/qml/+android/AddressBarDialog.qml @@ -0,0 +1,230 @@ +// +// AddressBarDialog.qml +// +// Created by Austin Davis on 2015/04/14 +// 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 "../controls" +import "../styles" +import "../hifi" as QmlHifi +import "../hifi/toolbars" +import "../styles-uit" as HifiStyles +import "../controls-uit" as HifiControls + +Item { + QmlHifi.HifiConstants { id: android } + + width: parent ? parent.width - android.dimen.windowLessWidth : 0 + height: parent ? parent.height - android.dimen.windowLessHeight : 0 + z: android.dimen.windowZ + anchors { horizontalCenter: parent.horizontalCenter; bottom: parent.bottom } + + id: bar + property bool isCursorVisible: false // Override default cursor visibility. + property bool shown: true + + onShownChanged: { + bar.visible = shown; + sendToScript({method: 'shownChanged', params: { shown: shown }}); + if (shown) { + updateLocationText(false); + } + } + + function hide() { + shown = false; + sendToScript ({ type: "hide" }); + } + + Component.onCompleted: { + updateLocationText(false); + } + + HifiConstants { id: hifi } + HifiStyles.HifiConstants { id: hifiStyleConstants } + + signal sendToScript(var message); + + AddressBarDialog { + id: addressBarDialog + } + + + Rectangle { + id: background + gradient: Gradient { + GradientStop { position: 0.0; color: android.color.gradientTop } + GradientStop { position: 1.0; color: android.color.gradientBottom } + } + anchors { + fill: parent + } + + QmlHifi.WindowHeader { + id: header + iconSource: "../../../icons/goto-i.svg" + titleText: "GO TO" + } + + HifiStyles.RalewayRegular { + id: notice + text: "YOUR LOCATION" + font.pixelSize: hifi.fonts.pixelSize * 2.15; + color: "#2CD7FF" + anchors { + bottom: addressBackground.top + bottomMargin: 45 + left: addressBackground.left + leftMargin: 60 + } + + } + + property int inputAreaHeight: 210 + property int inputAreaStep: (height - inputAreaHeight) / 2 + + ToolbarButton { + id: homeButton + y: 280 + imageURL: "../../icons/home.svg" + onClicked: { + addressBarDialog.loadHome(); + bar.shown = false; + } + anchors { + leftMargin: 75 + left: parent.left + } + size: 150 + } + + ToolbarButton { + id: backArrow; + imageURL: "../../icons/backward.svg"; + onClicked: addressBarDialog.loadBack(); + anchors { + left: homeButton.right + leftMargin: 70 + verticalCenter: homeButton.verticalCenter + } + size: 150 + } + ToolbarButton { + id: forwardArrow; + imageURL: "../../icons/forward.svg"; + onClicked: addressBarDialog.loadForward(); + anchors { + left: backArrow.right + leftMargin: 60 + verticalCenter: homeButton.verticalCenter + } + size: 150 + } + + HifiStyles.FiraSansRegular { + id: location; + font.pixelSize: addressLine.font.pixelSize; + color: "gray"; + clip: true; + anchors.fill: addressLine; + visible: addressLine.text.length === 0 + z: 1 + } + + Rectangle { + id: addressBackground + x: 780 + y: 280 + width: 1440 + height: 150 + color: "#FFFFFF" + } + + TextInput { + id: addressLine + focus: true + x: 870 + y: 450 + width: 1350 + height: 120 + inputMethodHints: Qt.ImhNoPredictiveText + //helperText: "Hint is here" + anchors { + verticalCenter: homeButton.verticalCenter + } + font.pixelSize: hifi.fonts.pixelSize * 3.75 + onTextChanged: { + //filterChoicesByText(); + updateLocationText(addressLine.text.length > 0); + if (!isCursorVisible && text.length > 0) { + isCursorVisible = true; + cursorVisible = true; + } + } + + onActiveFocusChanged: { + //cursorVisible = isCursorVisible && focus; + } + } + + + + function toggleOrGo() { + if (addressLine.text !== "") { + addressBarDialog.loadAddress(addressLine.text); + } + bar.shown = false; + } + + Keys.onPressed: { + switch (event.key) { + case Qt.Key_Escape: + case Qt.Key_Back: + clearAddressLineTimer.start(); + event.accepted = true + bar.shown = false; + break + case Qt.Key_Enter: + case Qt.Key_Return: + toggleOrGo(); + clearAddressLineTimer.start(); + event.accepted = true + break + } + } + + } + + Timer { + // Delay clearing address line so as to avoid flicker of "not connected" being displayed after entering an address. + id: clearAddressLineTimer + running: false + interval: 100 // ms + repeat: false + onTriggered: { + addressLine.text = ""; + isCursorVisible = false; + } + } + + function updateLocationText(enteringAddress) { + if (enteringAddress) { + notice.text = "Go to a place, @user, path or network address"; + notice.color = "#ffffff"; // hifiStyleConstants.colors.baseGrayHighlight; + location.visible = false; + } else { + notice.text = AddressManager.isConnected ? "YOUR LOCATION:" : "NOT CONNECTED"; + notice.color = AddressManager.isConnected ? hifiStyleConstants.colors.turquoise : hifiStyleConstants.colors.redHighlight; + // Display hostname, which includes ip address, localhost, and other non-placenames. + location.text = (AddressManager.placename || AddressManager.hostname || '') + (AddressManager.pathname ? AddressManager.pathname.match(/\/[^\/]+/)[0] : ''); + location.visible = true; + } + } + +} \ No newline at end of file diff --git a/interface/resources/qml/hifi/+android/HifiConstants.qml b/interface/resources/qml/hifi/+android/HifiConstants.qml new file mode 100644 index 0000000000..ee6d92ed38 --- /dev/null +++ b/interface/resources/qml/hifi/+android/HifiConstants.qml @@ -0,0 +1,54 @@ +// +// 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 + +Item { + + id: android + + readonly property alias dimen: dimen + readonly property alias color: color + + Item { + id: dimen + readonly property real windowLessWidth: 126 + readonly property real windowLessHeight: 64 + + readonly property real windowZ: 100 + + readonly property real headerHeight: 276 + + readonly property real headerIconPosX: 90 + readonly property real headerIconPosY: 108 + readonly property real headerIconWidth: 111 + readonly property real headerIconHeight: 111 + readonly property real headerIconTitleDistance: 151 + + readonly property real headerHideWidth: 150 + readonly property real headerHideHeight: 150 + readonly property real headerHideRightMargin: 110 + readonly property real headerHideTopMargin: 90 + readonly property real headerHideIconWidth: 70 + readonly property real headerHideIconHeight: 45 + readonly property real headerHideTextTopMargin: 36 + + 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/WindowHeader.qml b/interface/resources/qml/hifi/+android/WindowHeader.qml new file mode 100644 index 0000000000..4ec0a0c6e6 --- /dev/null +++ b/interface/resources/qml/hifi/+android/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 "../styles-uit" +import "../controls-uit" 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/bottomHudOptions.qml b/interface/resources/qml/hifi/+android/bottomHudOptions.qml new file mode 100644 index 0000000000..860298149f --- /dev/null +++ b/interface/resources/qml/hifi/+android/bottomHudOptions.qml @@ -0,0 +1,89 @@ +// +// 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 "../../styles-uit" +import "../../controls-uit" 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.fill: parent + anchors.margins: 12 + + Rectangle { + id: hideButton + height: android.dimen.headerHideWidth + width: android.dimen.headerHideHeight + color: "#00000000" + anchors { + horizontalCenter: parent.horizontalCenter + } + 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/bottombar.qml b/interface/resources/qml/hifi/+android/bottombar.qml new file mode 100644 index 0000000000..2a34b7fe19 --- /dev/null +++ b/interface/resources/qml/hifi/+android/bottombar.qml @@ -0,0 +1,135 @@ +// +// bottomHudOptions.qml +// interface/resources/qml/android +// +// Created by Gabriel Calero & Cristian Duarte on 19 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 +// + +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 Styles +import "../../styles-uit" +import "../../controls-uit" as HifiControlsUit +import "../../controls" as HifiControls +import ".." +import "." + +Item { + id: bar + x:0 + + property bool shown: true + + signal sendToScript(var message); + + onShownChanged: { + bar.visible = shown; + } + + function hide() { + //shown = false; + sendToScript({ method: "hide" }); + } + + Styles.HifiConstants { id: hifi } + HifiConstants { id: android } + + Rectangle { + id: background + anchors.fill : parent + color: "#FF000000" + border.color: "#FFFFFF" + anchors.bottomMargin: -1 + anchors.leftMargin: -1 + anchors.rightMargin: -1 + Flow { + id: flowMain + spacing: 10 + anchors.fill: parent + anchors.topMargin: 12 + anchors.bottomMargin: 12 + anchors.rightMargin: 12 + anchors.leftMargin: 72 + } + + + Rectangle { + id: hideButton + height: android.dimen.headerHideWidth + width: android.dimen.headerHideHeight + color: "#00000000" + anchors { + right: parent.right + rightMargin: android.dimen.headerHideRightMargin + top: parent.top + topMargin: android.dimen.headerHideTopMargin + } + + Image { + id: hideIcon + source: "../../../icons/hide.svg" + width: android.dimen.headerHideIconWidth + height: android.dimen.headerHideIconHeight + anchors { + horizontalCenter: parent.horizontalCenter + top: parent.top + } + } + FiraSansRegular { + anchors { + top: hideIcon.bottom + horizontalCenter: hideIcon.horizontalCenter + topMargin: 12 + } + text: "HIDE" + color: "#FFFFFF" + font.pixelSize: hifi.fonts.pixelSize * 2.5; + } + + MouseArea { + anchors.fill: parent + onClicked: { + hide(); + } + } + } + } + + Component.onCompleted: { + // put on bottom + width = Window.innerWidth; + height = 255; + y = Window.innerHeight - height; + } + + 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/button.qml b/interface/resources/qml/hifi/+android/button.qml new file mode 100644 index 0000000000..ec7af2ab92 --- /dev/null +++ b/interface/resources/qml/hifi/+android/button.qml @@ -0,0 +1,230 @@ +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 int bottomMargin: 30 + + property bool isEntered: false + property double sortOrder: 100 + + property bool isActive: false + + signal clicked() + + 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: "FiraSans" + //font.bold: true + 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(); + }*/ + } + onEntered: { + button.isEntered = true; + if (button.isActive) { + button.state = "hover active state"; + } else { + button.state = "hover state"; + } + } + onExited: { + button.isEntered = false; + if (button.isActive) { + button.state = "active state"; + } else { + button.state = "base state"; + } + } + } + states: [ + State { + name: "hover state" + + PropertyChanges { + target: buttonBg + //color: "#cfcfcf" + //opacity: 1 + color: button.hoverBgColor + opacity: button.hoverBgOpacity + } + + PropertyChanges { + target: text + //color: "#ffffff" + color: button.hoverTextColor + text: button.hoverText + } + + PropertyChanges { + target: icon + source: urlHelper(button.hoverIcon) + } + }, + State { + name: "active state" + + PropertyChanges { + target: buttonBg + //color: "#1fc6a6" + //opacity: 1 + color: button.activeBgColor + opacity: button.activeBgOpacity + } + + PropertyChanges { + target: text + //color: "#333333" + color: button.activeTextColor + text: button.activeText + } + + PropertyChanges { + target: icon + source: urlHelper(button.activeIcon) + } + }, + State { + name: "hover active state" + + PropertyChanges { + target: buttonBg + //color: "#ff0000" + //opacity: 1 + color: button.activeHoverBgColor + opacity: button.activeHoverBgOpacity + } + + PropertyChanges { + target: text + //color: "#333333" + color: button.activeHoverTextColor + text: button.activeHoverText + } + + PropertyChanges { + target: icon + source: urlHelper(button.activeHoverIcon) + } + }, + State { + name: "base state" + + PropertyChanges { + target: buttonBg + //color: "#9A9A9A" + //opacity: 0.1 + color: button.bgColor + opacity: button.bgOpacity + } + + PropertyChanges { + target: text + //color: "#ffffff" + color: button.textColor + text: button.text + } + + PropertyChanges { + target: icon + source: urlHelper(button.icon) + } + } + ] +} \ No newline at end of file diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 48c15439ab..00c0dd8387 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -138,6 +138,7 @@ #include #include #include +#include #include #include #include @@ -5863,6 +5864,7 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEnginePointe scriptEngine->registerFunction("OverlayWebWindow", QmlWebWindowClass::constructor); #endif scriptEngine->registerFunction("OverlayWindow", QmlWindowClass::constructor); + scriptEngine->registerFunction("QmlFragment", QmlFragmentClass::constructor); scriptEngine->registerGlobalObject("Menu", MenuScriptingInterface::getInstance()); scriptEngine->registerGlobalObject("DesktopPreviewProvider", DependencyManager::get().data()); diff --git a/libraries/ui/src/QmlFragmentClass.cpp b/libraries/ui/src/QmlFragmentClass.cpp new file mode 100644 index 0000000000..ff67022305 --- /dev/null +++ b/libraries/ui/src/QmlFragmentClass.cpp @@ -0,0 +1,88 @@ +// +// Created by Gabriel Calero & Cristian Duarte on Aug 25, 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 +// + +#include "QmlFragmentClass.h" + +#include +#include +#include + +#include + +#include "OffscreenUi.h" + + +std::mutex QmlFragmentClass::_mutex; +std::map QmlFragmentClass::_fragments; + +QmlFragmentClass::QmlFragmentClass(QString id) : + qml(id) { +} +// Method called by Qt scripts to create a new bottom menu bar in Android +QScriptValue QmlFragmentClass::constructor(QScriptContext* context, QScriptEngine* engine) { + + std::lock_guard guard(_mutex); + auto qml = context->argument(0).toVariant().toMap().value("qml"); + if (qml.isValid()) { + // look up tabletId in the map. + auto iter = _fragments.find(qml.toString()); + if (iter != _fragments.end()) { + //qDebug() << "[QML-ANDROID] QmlFragmentClass menu already exists"; + return iter->second; + } + } else { + qWarning() << "QmlFragmentClass could not build instance " << qml; + return QScriptValue(); + } + + auto properties = parseArguments(context); + auto offscreenUi = DependencyManager::get(); + QmlFragmentClass* retVal = new QmlFragmentClass(qml.toString()); + Q_ASSERT(retVal); + if (QThread::currentThread() != qApp->thread()) { + retVal->moveToThread(qApp->thread()); + BLOCKING_INVOKE_METHOD(retVal, "initQml", Q_ARG(QVariantMap, properties)); + } else { + retVal->initQml(properties); + } + connect(engine, &QScriptEngine::destroyed, retVal, &QmlWindowClass::deleteLater); + QScriptValue scriptObject = engine->newQObject(retVal); + _fragments[qml.toString()] = scriptObject; + return scriptObject; +} + +void QmlFragmentClass::close() { + QmlWindowClass::close(); + _fragments.erase(qml); +} + +QObject* QmlFragmentClass::addButton(const QVariant& properties) { + QVariant resultVar; + Qt::ConnectionType connectionType = Qt::AutoConnection; + + if (QThread::currentThread() != _qmlWindow->thread()) { + connectionType = Qt::BlockingQueuedConnection; + } + bool hasResult = QMetaObject::invokeMethod(_qmlWindow, "addButton", connectionType, + Q_RETURN_ARG(QVariant, resultVar), Q_ARG(QVariant, properties)); + if (!hasResult) { + qWarning() << "QmlFragmentClass addButton has no result"; + return NULL; + } + + QObject* qmlButton = qvariant_cast(resultVar); + if (!qmlButton) { + qWarning() << "QmlFragmentClass addButton result not a QObject"; + return NULL; + } + + return qmlButton; +} + +void QmlFragmentClass::removeButton(QObject* button) { +} diff --git a/libraries/ui/src/QmlFragmentClass.h b/libraries/ui/src/QmlFragmentClass.h new file mode 100644 index 0000000000..87c18a49ad --- /dev/null +++ b/libraries/ui/src/QmlFragmentClass.h @@ -0,0 +1,45 @@ +// +// Created by Gabriel Calero & Cristian Duarte on Aug 25, 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 +// + +#ifndef hifi_ui_QmlFragmentClass_h +#define hifi_ui_QmlFragmentClass_h + +#include "QmlWindowClass.h" + +class QmlFragmentClass : public QmlWindowClass { + Q_OBJECT +public: + QmlFragmentClass(QString id); + static QScriptValue constructor(QScriptContext* context, QScriptEngine* engine); + + /**jsdoc + * Creates a new button, adds it to this and returns it. + * @function QmlFragmentClass#addButton + * @param properties {Object} button properties + * @returns {TabletButtonProxy} + */ + Q_INVOKABLE QObject* addButton(const QVariant& properties); + + /* + * TODO - not yet implemented + */ + Q_INVOKABLE void removeButton(QObject* tabletButtonProxy); +public slots: + Q_INVOKABLE void close(); + +protected: + QString qmlSource() const override { return qml; } + + static std::mutex _mutex; + static std::map _fragments; +private: + QString qml; + +}; + +#endif diff --git a/libraries/ui/src/ui/OffscreenQmlSurface.cpp b/libraries/ui/src/ui/OffscreenQmlSurface.cpp index db3df34dc5..a22a5e5a5f 100644 --- a/libraries/ui/src/ui/OffscreenQmlSurface.cpp +++ b/libraries/ui/src/ui/OffscreenQmlSurface.cpp @@ -28,7 +28,7 @@ #include #include #include - +#include #include #include #include @@ -1136,6 +1136,17 @@ bool OffscreenQmlSurface::eventFilter(QObject* originalDestination, QEvent* even } break; } + case QEvent::InputMethod: + case QEvent::InputMethodQuery: { + if (_quickWindow && _quickWindow->activeFocusItem()) { + event->ignore(); + if (QCoreApplication::sendEvent(_quickWindow->activeFocusItem(), event)) { + return event->isAccepted(); + } + return false; + } + break; + } #endif default: break; diff --git a/scripts/+android/defaultScripts.js b/scripts/+android/defaultScripts.js index 54c899d1da..a85b7e2208 100644 --- a/scripts/+android/defaultScripts.js +++ b/scripts/+android/defaultScripts.js @@ -13,7 +13,8 @@ var DEFAULT_SCRIPTS_COMBINED = [ "system/progress.js", - "system/touchscreenvirtualpad.js"/*, + "system/touchscreenvirtualpad.js", + "system/bottombar.js"/*, "system/away.js", "system/controllers/controllerDisplayManager.js", "system/controllers/handControllerGrabAndroid.js", diff --git a/scripts/system/bottombar.js b/scripts/system/bottombar.js new file mode 100644 index 0000000000..064025f392 --- /dev/null +++ b/scripts/system/bottombar.js @@ -0,0 +1,159 @@ +"use strict"; +// +// bottombar.js +// scripts/system/ +// +// Created by Gabriel Calero & Cristian Duarte on Jan 18, 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 bottombar; +var bottomHudOptionsBar; +var gotoBtn; + +var gotoScript = Script.require('./goto-android.js'); + +var logEnabled = false; + +function printd(str) { + if (logEnabled) { + print("[bottombar.js] " + str); + } +} + +function init() { + gotoScript.init(); + gotoScript.setOnShownChange(function (shown) { + if (shown) { + showAddressBar(); + } else { + hideAddressBar(); + } + }); + + setupBottomBar(); + setupBottomHudOptionsBar(); + + raiseBottomBar(); + +} + +function shutdown() { +} + +function setupBottomBar() { + bottombar = new QmlFragment({ + qml: "hifi/bottombar.qml" + }); + + bottombar.fromQml.connect(function(message) { + switch (message.method) { + case 'hide': + lowerBottomBar(); + break; + default: + print('[bottombar.js] Unrecognized message from bottomHud.qml:', JSON.stringify(message)); + } + }); + + + gotoBtn = bottombar.addButton({ + icon: "icons/goto-i.svg", + activeIcon: "icons/goto-a.svg", + bgOpacity: 0, + hoverBgOpacity: 0, + activeBgOpacity: 0, + activeHoverBgOpacity: 0, + height: 240, + width: 300, + iconSize: 108, + textSize: 45, + text: "GO TO" + }); + + gotoBtn.clicked.connect(function() { + if (!gotoScript.isVisible()) { + showAddressBar(); + } else { + hideAddressBar(); + } + }); + + // TODO: setup all the buttons or provide a dynamic interface + + raiseBottomBar(); + + +} + +var setupBottomHudOptionsBar = function() { + var bottomHud = new QmlFragment({ + qml: "hifi/bottomHudOptions.qml" + }); + + bottomHudOptionsBar = { + show: function() { + bottomHud.setVisible(true); + }, + hide: function() { + bottomHud.setVisible(false); + }, + qmlFragment: bottomHud + }; + bottomHud.fromQml.connect( + function(message) { + switch (message.method) { + case 'showUpBar': + printd('[bottombar.js] showUpBar message from bottomHudOptions.qml: ', JSON.stringify(message)); + raiseBottomBar(); + break; + default: + print('[bottombar.js] Unrecognized message from bottomHudOptions.qml:', JSON.stringify(message)); + } + } + ); +} + +function lowerBottomBar() { + if (bottombar) { + bottombar.setVisible(false); + } + if (bottomHudOptionsBar) { + bottomHudOptionsBar.show(); + } +} + +function raiseBottomBar() { + print('[bottombar.js] raiseBottomBar begin'); + if (bottombar) { + bottombar.setVisible(true); + } + if (bottomHudOptionsBar) { + bottomHudOptionsBar.hide(); + } + print('[bottombar.js] raiseBottomBar end'); +} + +function showAddressBar() { + gotoScript.show(); + gotoBtn.isActive = true; +} + +function hideAddressBar() { + gotoScript.hide(); + gotoBtn.isActive = false; +} + + + +Script.scriptEnding.connect(function () { + shutdown(); +}); + +init(); + +}()); // END LOCAL_SCOPE diff --git a/scripts/system/goto-android.js b/scripts/system/goto-android.js new file mode 100644 index 0000000000..e917455128 --- /dev/null +++ b/scripts/system/goto-android.js @@ -0,0 +1,96 @@ +"use strict"; +// +// goto-android.js +// scripts/system/ +// +// Created by Gabriel Calero & Cristian Duarte on 12 Sep 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 window; + + +var logEnabled = false; +function printd(str) { + if (logEnabled) + print("[goto-android.js] " + str); +} + +function init() { +} + +function fromQml(message) { // messages are {method, params}, like json-rpc. See also sendToQml. + switch (message.method) { + case 'shownChanged': + if (notifyShownChange) { + notifyShownChange(message.params.shown); + } ; + break; + case 'hide': + module.exports.hide(); + module.exports.onHidden(); + break; + default: + print('[goto-android.js] Unrecognized message from AddressBarDialog.qml:', JSON.stringify(message)); + } +} + +function sendToQml(message) { + window.sendToQml(message); +} + +var isVisible = false; +var notifyShownChange; +module.exports = { + init: function() { + window = new QmlFragment({ + qml: "AddressBarDialog.qml", + visible: false + }); + }, + show: function() { + Controller.setVPadEnabled(false); + if (window) { + window.fromQml.connect(fromQml); + window.setVisible(true); + isVisible = true; + } + }, + hide: function() { + Controller.setVPadEnabled(true); + if (window) { + window.fromQml.disconnect(fromQml); + window.setVisible(false); + } + isVisible = false; + }, + destroy: function() { + if (window) { + window.close(); + window = null; + } + }, + isVisible: function() { + return isVisible; + }, + width: function() { + return window ? window.size.x : 0; + }, + height: function() { + return window ? window.size.y : 0; + }, + position: function() { + return window && isVisible ? window.position : null; + }, + setOnShownChange: function(f) { + notifyShownChange = f; + }, + onHidden: function() { } + + +}; + +init(); From a7c77a5247987b93de35f683e63eafa4d693a80f Mon Sep 17 00:00:00 2001 From: Gabriel Calero Date: Thu, 25 Jan 2018 12:36:56 -0300 Subject: [PATCH 02/29] Fix undefined color in address bar --- interface/resources/qml/+android/AddressBarDialog.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/resources/qml/+android/AddressBarDialog.qml b/interface/resources/qml/+android/AddressBarDialog.qml index e07af24b8d..0b12301561 100644 --- a/interface/resources/qml/+android/AddressBarDialog.qml +++ b/interface/resources/qml/+android/AddressBarDialog.qml @@ -220,7 +220,7 @@ Item { location.visible = false; } else { notice.text = AddressManager.isConnected ? "YOUR LOCATION:" : "NOT CONNECTED"; - notice.color = AddressManager.isConnected ? hifiStyleConstants.colors.turquoise : hifiStyleConstants.colors.redHighlight; + notice.color = AddressManager.isConnected ? hifiStyleConstants.colors.blueHighlight : hifiStyleConstants.colors.redHighlight; // Display hostname, which includes ip address, localhost, and other non-placenames. location.text = (AddressManager.placename || AddressManager.hostname || '') + (AddressManager.pathname ? AddressManager.pathname.match(/\/[^\/]+/)[0] : ''); location.visible = true; From 8093ed7c5ca92954f51d66f4e08b5d70cad644cc Mon Sep 17 00:00:00 2001 From: Gabriel Calero Date: Fri, 26 Jan 2018 17:49:08 -0300 Subject: [PATCH 03/29] Add audio bar to mute/unmute the mic --- .../resources/icons/+android/mic-mute-a.svg | 18 +++++ .../resources/icons/+android/mic-mute-i.svg | 21 ++++++ .../resources/icons/+android/mic-unmute-a.svg | 70 ++++++++++++++++++ .../resources/icons/+android/mic-unmute-i.svg | 22 ++++++ .../resources/qml/hifi/+android/AudioBar.qml | 71 +++++++++++++++++++ scripts/+android/defaultScripts.js | 5 +- scripts/system/+android/audio.js | 67 +++++++++++++++++ scripts/system/{ => +android}/bottombar.js | 2 +- .../{goto-android.js => +android/goto.js} | 0 .../{ => +android}/touchscreenvirtualpad.js | 2 +- 10 files changed, 274 insertions(+), 4 deletions(-) create mode 100644 interface/resources/icons/+android/mic-mute-a.svg create mode 100644 interface/resources/icons/+android/mic-mute-i.svg create mode 100644 interface/resources/icons/+android/mic-unmute-a.svg create mode 100644 interface/resources/icons/+android/mic-unmute-i.svg create mode 100644 interface/resources/qml/hifi/+android/AudioBar.qml create mode 100644 scripts/system/+android/audio.js rename scripts/system/{ => +android}/bottombar.js (98%) rename scripts/system/{goto-android.js => +android/goto.js} (100%) rename scripts/system/{ => +android}/touchscreenvirtualpad.js (93%) diff --git a/interface/resources/icons/+android/mic-mute-a.svg b/interface/resources/icons/+android/mic-mute-a.svg new file mode 100644 index 0000000000..73555ce21b --- /dev/null +++ b/interface/resources/icons/+android/mic-mute-a.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + diff --git a/interface/resources/icons/+android/mic-mute-i.svg b/interface/resources/icons/+android/mic-mute-i.svg new file mode 100644 index 0000000000..56e33eab6b --- /dev/null +++ b/interface/resources/icons/+android/mic-mute-i.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + diff --git a/interface/resources/icons/+android/mic-unmute-a.svg b/interface/resources/icons/+android/mic-unmute-a.svg new file mode 100644 index 0000000000..bb28dc0f2b --- /dev/null +++ b/interface/resources/icons/+android/mic-unmute-a.svg @@ -0,0 +1,70 @@ + + + +image/svg+xml \ No newline at end of file diff --git a/interface/resources/icons/+android/mic-unmute-i.svg b/interface/resources/icons/+android/mic-unmute-i.svg new file mode 100644 index 0000000000..76f98d7f0c --- /dev/null +++ b/interface/resources/icons/+android/mic-unmute-i.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + diff --git a/interface/resources/qml/hifi/+android/AudioBar.qml b/interface/resources/qml/hifi/+android/AudioBar.qml new file mode 100644 index 0000000000..f524595ef5 --- /dev/null +++ b/interface/resources/qml/hifi/+android/AudioBar.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 "../../styles-uit" +import "../../controls-uit" 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 + } + } + + Component.onCompleted: { + // put on bottom + x = 0; + 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/scripts/+android/defaultScripts.js b/scripts/+android/defaultScripts.js index a85b7e2208..2a4af9afbf 100644 --- a/scripts/+android/defaultScripts.js +++ b/scripts/+android/defaultScripts.js @@ -13,8 +13,9 @@ var DEFAULT_SCRIPTS_COMBINED = [ "system/progress.js", - "system/touchscreenvirtualpad.js", - "system/bottombar.js"/*, + "system/+android/touchscreenvirtualpad.js", + "system/+android/bottombar.js", + "system/+android/audio.js" /*, "system/away.js", "system/controllers/controllerDisplayManager.js", "system/controllers/handControllerGrabAndroid.js", diff --git a/scripts/system/+android/audio.js b/scripts/system/+android/audio.js new file mode 100644 index 0000000000..b4f156d4bf --- /dev/null +++ b/scripts/system/+android/audio.js @@ -0,0 +1,67 @@ +"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 = true; + +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, + activeBgOpacity: 0.0, + bgColor: "#FFFFFF" + }); + + onMuteToggled(); + + audioButton.clicked.connect(onMuteClicked); + Audio.mutedChanged.connect(onMuteToggled); +} + +function onMuteClicked() { + printd("On Mute Clicked"); + //Menu.setIsOptionChecked("Mute Microphone", !Menu.isOptionChecked("Mute Microphone")); + Audio.muted = !Audio.muted; + onMuteToggled(); +} + +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); + Audio.mutedChanged.connect(onMuteToggled); + } +}); + +init(); + +}()); // END LOCAL_SCOPE diff --git a/scripts/system/bottombar.js b/scripts/system/+android/bottombar.js similarity index 98% rename from scripts/system/bottombar.js rename to scripts/system/+android/bottombar.js index 064025f392..e58840ad6f 100644 --- a/scripts/system/bottombar.js +++ b/scripts/system/+android/bottombar.js @@ -15,7 +15,7 @@ var bottombar; var bottomHudOptionsBar; var gotoBtn; -var gotoScript = Script.require('./goto-android.js'); +var gotoScript = Script.require('./goto.js'); var logEnabled = false; diff --git a/scripts/system/goto-android.js b/scripts/system/+android/goto.js similarity index 100% rename from scripts/system/goto-android.js rename to scripts/system/+android/goto.js diff --git a/scripts/system/touchscreenvirtualpad.js b/scripts/system/+android/touchscreenvirtualpad.js similarity index 93% rename from scripts/system/touchscreenvirtualpad.js rename to scripts/system/+android/touchscreenvirtualpad.js index e6f9204d4d..fa41a2b5e0 100644 --- a/scripts/system/touchscreenvirtualpad.js +++ b/scripts/system/+android/touchscreenvirtualpad.js @@ -1,6 +1,6 @@ "use strict"; // -// android.js +// touchscreenvirtualpad.js // scripts/system/ // // Created by Gabriel Calero & Cristian Duarte on Jan 16, 2018 From 917cbd73b19622933ae7cc16ca871d4f90b4c75a Mon Sep 17 00:00:00 2001 From: Gabriel Calero Date: Fri, 26 Jan 2018 17:50:16 -0300 Subject: [PATCH 04/29] Don't show help on android. Hide menu bar --- interface/src/Application.cpp | 2 ++ .../src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.cpp | 3 +++ 2 files changed, 5 insertions(+) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 00c0dd8387..6fec6f1d72 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2826,7 +2826,9 @@ void Application::handleSandboxStatus(QNetworkReply* reply) { // If this is a first run we short-circuit the address passed in if (firstRun.get()) { +#if !defined(Q_OS_ANDROID) showHelp(); +#endif if (sandboxIsRunning) { qCDebug(interfaceapp) << "Home sandbox appears to be running, going to Home."; DependencyManager::get()->goToLocalSandbox(); diff --git a/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.cpp index f1fefc9425..1a1714ad56 100644 --- a/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.cpp @@ -72,6 +72,9 @@ void Basic2DWindowOpenGLDisplayPlugin::uncustomizeContext() { bool Basic2DWindowOpenGLDisplayPlugin::internalActivate() { _framerateActions.clear(); +#if defined(Q_OS_ANDROID) + _container->setFullscreen(nullptr, true); +#endif _container->addMenuItem(PluginType::DISPLAY_PLUGIN, MENU_PATH(), FULLSCREEN, [this](bool clicked) { if (clicked) { From d5e101c2e8f4ae35c52f75a410a3932521fdb646 Mon Sep 17 00:00:00 2001 From: Gabriel Calero Date: Wed, 24 Jan 2018 12:29:58 -0300 Subject: [PATCH 05/29] Add mode selector (my view / radar), not finished --- .../resources/icons/+android/myview-a.svg | 56 ++++++ .../resources/icons/+android/myview-hover.svg | 54 +++++ .../resources/icons/+android/myview-i.svg | 56 ++++++ .../resources/icons/+android/radar-a.svg | 1 + .../resources/icons/+android/radar-hover.svg | 1 + .../resources/icons/+android/radar-i.svg | 1 + .../resources/qml/hifi/+android/modesbar.qml | 73 +++++++ scripts/+android/defaultScripts.js | 3 +- scripts/system/+android/modes.js | 187 ++++++++++++++++++ 9 files changed, 431 insertions(+), 1 deletion(-) create mode 100755 interface/resources/icons/+android/myview-a.svg create mode 100755 interface/resources/icons/+android/myview-hover.svg create mode 100755 interface/resources/icons/+android/myview-i.svg create mode 100755 interface/resources/icons/+android/radar-a.svg create mode 100755 interface/resources/icons/+android/radar-hover.svg create mode 100755 interface/resources/icons/+android/radar-i.svg create mode 100644 interface/resources/qml/hifi/+android/modesbar.qml create mode 100644 scripts/system/+android/modes.js diff --git a/interface/resources/icons/+android/myview-a.svg b/interface/resources/icons/+android/myview-a.svg new file mode 100755 index 0000000000..307b559c95 --- /dev/null +++ b/interface/resources/icons/+android/myview-a.svg @@ -0,0 +1,56 @@ + + + +image/svg+xmlAsset 3 \ No newline at end of file diff --git a/interface/resources/icons/+android/myview-hover.svg b/interface/resources/icons/+android/myview-hover.svg new file mode 100755 index 0000000000..49656ad294 --- /dev/null +++ b/interface/resources/icons/+android/myview-hover.svg @@ -0,0 +1,54 @@ + + + +image/svg+xmlAsset 3 \ No newline at end of file diff --git a/interface/resources/icons/+android/myview-i.svg b/interface/resources/icons/+android/myview-i.svg new file mode 100755 index 0000000000..574f51c615 --- /dev/null +++ b/interface/resources/icons/+android/myview-i.svg @@ -0,0 +1,56 @@ + + + +image/svg+xmlAsset 3 \ No newline at end of file diff --git a/interface/resources/icons/+android/radar-a.svg b/interface/resources/icons/+android/radar-a.svg new file mode 100755 index 0000000000..e4b157f827 --- /dev/null +++ b/interface/resources/icons/+android/radar-a.svg @@ -0,0 +1 @@ +Asset 1 \ No newline at end of file diff --git a/interface/resources/icons/+android/radar-hover.svg b/interface/resources/icons/+android/radar-hover.svg new file mode 100755 index 0000000000..e4b157f827 --- /dev/null +++ b/interface/resources/icons/+android/radar-hover.svg @@ -0,0 +1 @@ +Asset 1 \ No newline at end of file diff --git a/interface/resources/icons/+android/radar-i.svg b/interface/resources/icons/+android/radar-i.svg new file mode 100755 index 0000000000..3994a775d3 --- /dev/null +++ b/interface/resources/icons/+android/radar-i.svg @@ -0,0 +1 @@ +Asset 1 \ No newline at end of file diff --git a/interface/resources/qml/hifi/+android/modesbar.qml b/interface/resources/qml/hifi/+android/modesbar.qml new file mode 100644 index 0000000000..8ce455c2c1 --- /dev/null +++ b/interface/resources/qml/hifi/+android/modesbar.qml @@ -0,0 +1,73 @@ +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-uit" +import "../../controls-uit" as HifiControlsUit +import "../../controls" as HifiControls +import ".." + +Item { + id: modesbar + y:60 + Rectangle { + anchors.fill : parent + color: "transparent" + Flow { + id: flowMain + spacing: 0 + flow: Flow.TopToBottom + layoutDirection: Flow.TopToBottom + anchors.fill: parent + anchors.margins: 4 + } + } + + Component.onCompleted: { + width = 330; + height = 330; + x=Window.innerWidth - width; + } + + 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(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 removeButton(name) { + } + + function urlHelper(src) { + if (src.match(/\bhttp/)) { + return src; + } else { + return "../../../" + src; + } + } + + function fromScript(message) { + switch (message.type) { + case "allButtonsShown": + modesbar.height = flowMain.children.length * 100 + 10; + break; + case "inactiveButtonsHidden": + modesbar.height = 100 + 10; + break; + default: + break; + } + } + +} diff --git a/scripts/+android/defaultScripts.js b/scripts/+android/defaultScripts.js index 2a4af9afbf..a61c205980 100644 --- a/scripts/+android/defaultScripts.js +++ b/scripts/+android/defaultScripts.js @@ -15,7 +15,8 @@ var DEFAULT_SCRIPTS_COMBINED = [ "system/progress.js", "system/+android/touchscreenvirtualpad.js", "system/+android/bottombar.js", - "system/+android/audio.js" /*, + "system/+android/audio.js" /, + "system/+android/modes.js"*, "system/away.js", "system/controllers/controllerDisplayManager.js", "system/controllers/handControllerGrabAndroid.js", diff --git a/scripts/system/+android/modes.js b/scripts/system/+android/modes.js new file mode 100644 index 0000000000..0be53c2fc4 --- /dev/null +++ b/scripts/system/+android/modes.js @@ -0,0 +1,187 @@ +"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 modesbar; +var modesButtons; +var currentSelectedBtn; + +var SETTING_CURRENT_MODE_KEY = 'Android/Mode'; +var MODE_VR = "VR", MODE_RADAR = "RADAR", MODE_MY_VIEW = "MY VIEW"; +var DEFAULT_MODE = MODE_RADAR; +var logEnabled = true; + +function printd(str) { + if (logEnabled) { + print("[modes.js] " + str); + } +} + +function init() { + setupModesBar(); +} + +function shutdown() { + +} + +function setupModesBar() { + + var bar = new QmlFragment({ + qml: "hifi/modesbar.qml" + }); + var buttonRadarMode = bar.addButton({ + icon: "icons/radar-i.svg", + activeIcon: "icons/radar-a.svg", + hoverIcon: "icons/radar-a.svg", + activeBgOpacity: 0.0, + hoverBgOpacity: 0.0, + activeHoverBgOpacity: 0.0, + text: "RADAR", + height:240, + bottomMargin: 6, + textSize: 45 + }); + var buttonMyViewMode = bar.addButton({ + icon: "icons/myview-i.svg", + activeIcon: "icons/myview-a.svg", + hoverIcon: "icons/myview-a.svg", + activeBgOpacity: 0.0, + hoverBgOpacity: 0.0, + activeHoverBgOpacity: 0.0, + text: "MY VIEW", + height: 240, + bottomMargin: 6, + textSize: 45 + }); + + modesButtons = [buttonRadarMode, buttonMyViewMode]; + + var mode = getCurrentModeSetting(); + + var buttonsRevealed = false; + bar.sendToQml({type: "inactiveButtonsHidden"}); + + modesbar = { + restoreMyViewButton: function() { + switchModeButtons(buttonMyViewMode); + saveCurrentModeSetting(MODE_MY_VIEW); + }, + sendToQml: function(o) { bar.sendToQml(o); }, + qmlFragment: bar + }; + + buttonRadarMode.clicked.connect(function() { + //if (connections.isVisible()) return; + saveCurrentModeSetting(MODE_RADAR); + printd("Radar clicked"); + onButtonClicked(buttonRadarMode, function() { + //radar.startRadarMode(); + }); + }); + buttonMyViewMode.clicked.connect(function() { + //if (connections.isVisible()) return; + saveCurrentModeSetting(MODE_MY_VIEW); + printd("My View clicked"); + onButtonClicked(buttonMyViewMode, function() { + if (currentSelectedBtn == buttonRadarMode) { + //radar.endRadarMode(); + } + }); + }); + + var savedButton; + if (mode == MODE_MY_VIEW) { + savedButton = buttonMyViewMode; + } else { + savedButton = buttonRadarMode; + } + printd("[MODE] previous mode " + mode); + + savedButton.clicked(); +} + +function saveCurrentModeSetting(mode) { + Settings.setValue(SETTING_CURRENT_MODE_KEY, mode); +} + +function getCurrentModeSetting(mode) { + return Settings.getValue(SETTING_CURRENT_MODE_KEY, DEFAULT_MODE); +} + +function showAllButtons() { + for (var i=0; i Date: Wed, 31 Jan 2018 15:12:44 -0300 Subject: [PATCH 06/29] Fix +android/defaultScripts array of filenames. --- scripts/+android/defaultScripts.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/+android/defaultScripts.js b/scripts/+android/defaultScripts.js index a61c205980..a8f6bf42a1 100644 --- a/scripts/+android/defaultScripts.js +++ b/scripts/+android/defaultScripts.js @@ -15,8 +15,8 @@ var DEFAULT_SCRIPTS_COMBINED = [ "system/progress.js", "system/+android/touchscreenvirtualpad.js", "system/+android/bottombar.js", - "system/+android/audio.js" /, - "system/+android/modes.js"*, + "system/+android/audio.js" , + "system/+android/modes.js"/*, "system/away.js", "system/controllers/controllerDisplayManager.js", "system/controllers/handControllerGrabAndroid.js", From b6ce3409d8619615907d1c2c9a37377f4752e6cf Mon Sep 17 00:00:00 2001 From: Cristian Luis Duarte Date: Wed, 31 Jan 2018 15:33:14 -0300 Subject: [PATCH 07/29] Android - Make it possible to use the touchpad for walk/fly (and orientate/see) when not in independent camera mode (allowing first person and third person). --- .../resources/controllers/touchscreenvirtualpad.json | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/interface/resources/controllers/touchscreenvirtualpad.json b/interface/resources/controllers/touchscreenvirtualpad.json index 9f65994e98..8c21044c3b 100644 --- a/interface/resources/controllers/touchscreenvirtualpad.json +++ b/interface/resources/controllers/touchscreenvirtualpad.json @@ -1,15 +1,12 @@ { "name": "TouchscreenVirtualPad to Actions", "channels": [ - { "from": "TouchscreenVirtualPad.LY", "when": "Application.CameraFirstPerson", "filters": { "type": "deadZone", "min": 0.05 }, "to": "Actions.TranslateZ" }, - { "from": "TouchscreenVirtualPad.LX", "when": "Application.CameraFirstPerson", "filters": { "type": "deadZone", "min": 0.05 }, "to": "Actions.TranslateX" }, + { "from": "TouchscreenVirtualPad.LY", "when": "!Application.CameraIndependent", "filters": { "type": "deadZone", "min": 0.05 }, "to": "Actions.TranslateZ" }, + { "from": "TouchscreenVirtualPad.LX", "when": "!Application.CameraIndependent", "filters": { "type": "deadZone", "min": 0.05 }, "to": "Actions.TranslateX" }, - { "from": "TouchscreenVirtualPad.RX", "when": "Application.CameraFirstPerson", "filters": { "type": "deadZone", "min": 0.05 }, "to": "Actions.Yaw" }, + { "from": "TouchscreenVirtualPad.RX", "when": "!Application.CameraIndependent", "filters": { "type": "deadZone", "min": 0.05 }, "to": "Actions.Yaw" }, - { "from": "TouchscreenVirtualPad.RY", - "when": "Application.CameraFirstPerson", - "to": "Actions.Pitch" - } + { "from": "TouchscreenVirtualPad.RY", "when": "!Application.CameraIndependent", "to": "Actions.Pitch" } ] } From e8312c72be21906f833a0a29f3fec71139223f34 Mon Sep 17 00:00:00 2001 From: Cristian Luis Duarte Date: Fri, 2 Feb 2018 12:57:41 -0300 Subject: [PATCH 08/29] Android - Radar mode implemented. --- scripts/system/+android/modes.js | 37 +- scripts/system/+android/radar.js | 1212 ++++++++++++++++++++++++ scripts/system/+android/uniqueColor.js | 46 + 3 files changed, 1293 insertions(+), 2 deletions(-) create mode 100644 scripts/system/+android/radar.js create mode 100644 scripts/system/+android/uniqueColor.js diff --git a/scripts/system/+android/modes.js b/scripts/system/+android/modes.js index 0be53c2fc4..b29548094f 100644 --- a/scripts/system/+android/modes.js +++ b/scripts/system/+android/modes.js @@ -20,6 +20,9 @@ var MODE_VR = "VR", MODE_RADAR = "RADAR", MODE_MY_VIEW = "MY VIEW"; var DEFAULT_MODE = MODE_RADAR; var logEnabled = true; +var radar = Script.require('./radar.js'); +var uniqueColor = Script.require('./uniqueColor.js'); + function printd(str) { if (logEnabled) { print("[modes.js] " + str); @@ -27,7 +30,10 @@ function printd(str) { } function init() { + radar.setUniqueColor(uniqueColor); + radar.init(); setupModesBar(); + radar.isTouchValid = isRadarModeValidTouch; } function shutdown() { @@ -85,7 +91,7 @@ function setupModesBar() { saveCurrentModeSetting(MODE_RADAR); printd("Radar clicked"); onButtonClicked(buttonRadarMode, function() { - //radar.startRadarMode(); + radar.startRadarMode(); }); }); buttonMyViewMode.clicked.connect(function() { @@ -94,7 +100,7 @@ function setupModesBar() { printd("My View clicked"); onButtonClicked(buttonMyViewMode, function() { if (currentSelectedBtn == buttonRadarMode) { - //radar.endRadarMode(); + radar.endRadarMode(); } }); }); @@ -177,6 +183,33 @@ function onButtonClicked(clickedButton, whatToDo, hideAllAfter) { } } +function isRadarModeValidTouch(coords) { + var qmlFragments = [modesbar.qmlFragment]; + var windows = []; + for (var i=0; i < qmlFragments.length; i++) { + var aQmlFrag = qmlFragments[i]; + if (aQmlFrag != null && aQmlFrag.isVisible() && + coords.x >= aQmlFrag.position.x * 3 && coords.x <= aQmlFrag.position.x * 3 + aQmlFrag.size.x * 3 && + coords.y >= aQmlFrag.position.y * 3 && coords.y <= aQmlFrag.position.y * 3 + aQmlFrag.size.y * 3 + ) { + printd("godViewModeTouchValid- false because of qmlFragments!? idx " + i); + return false; + } + } + + for (var i=0; i < windows.length; i++) { + var aWin = windows[i]; + if (aWin != null && aWin.position() != null && + coords.x >= aWin.position().x * 3 && coords.x <= aWin.position().x * 3 + aWin.width() * 3 && + coords.y >= aWin.position().y * 3 && coords.y <= aWin.position().y * 3 + aWin.height() * 3 + ) { + printd("godViewModeTouchValid- false because of windows!?"); + return false; + } + } + printd("godViewModeTouchValid- true by default "); + return true; +} Script.scriptEnding.connect(function () { shutdown(); diff --git a/scripts/system/+android/radar.js b/scripts/system/+android/radar.js new file mode 100644 index 0000000000..9fa07178f6 --- /dev/null +++ b/scripts/system/+android/radar.js @@ -0,0 +1,1212 @@ +"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 = true; +function printd(str) { + if (logEnabled) + print("[radar.js] " + str); +} + +var radar = false; +var radarHeight = 10; // camera position meters above the avatar +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 = 40; +var RADAR_HEIGHT_MIN_PLUS_AVATAR = 2; +var RADAR_CAMERA_DISTANCE_TO_ICONS = 0.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 = 38; +var AVATAR_DISPLAY_NAME_CHAR_WIDTH = 18; +var lastDragAt; +var lastDeltaDrag; + +var uniqueColor; + +function moveTo(position) { + if (radar) { + MyAvatar.position = position; + Camera.position = Vec3.sum(MyAvatar.position, {x:0, y: radarHeight, z: 0}); + } +} + +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 actionOnObjectFromEvent(event) { + var rayIntersection = findRayIntersection(Camera.computePickRay(event.x, event.y)); + if (rayIntersection && rayIntersection.intersects && rayIntersection.overlayID) { + printd("found overlayID touched " + rayIntersection.overlayID); + if (entitiesByOverlayID[rayIntersection.overlayID]) { + var entity = Entities.getEntityProperties(entitiesByOverlayID[rayIntersection.overlayID], ["sourceUrl"]); + App.openUrl(entity.sourceUrl); + return true; + } + } + if (rayIntersection && rayIntersection.intersects && rayIntersection.entityID && rayIntersection.properties) { + printd("found " + rayIntersection.entityID + " of type " + rayIntersection.properties.type) ; + if (rayIntersection.properties.type == "Web") { + printd("found web element to " + rayIntersection.properties.sourceUrl); + App.openUrl(rayIntersection.properties.sourceUrl); + return true; + } + } + return false; +} + +function mousePress(event) { + if (!isTouchValid(coords)) { + currentTouchIsValid = false; + return; + } else { + currentTouchIsValid = true; + } + mousePressOrTouchEnd(event); +} + +function mousePressOrTouchEnd(event) { + if (!currentTouchIsValid) { + return; + } + if (radar) { + if (actionOnObjectFromEvent(event)) return; + } +} + +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 currentTouchIsValid = false; // Currently used to know if touch hasn't started on a UI overlay + +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 touch is invalid, cancel + if (!currentTouchIsValid) { printd("touchEnd fail because !currentTouchIsValid");return;} + + if (analyzeDoubleTap(event)) return; // double tap detected, finish + + if (radar) { + mousePressOrTouchEnd(event); + } +} + +/** +* 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); +} + +/******************************************************************************************************** + * + ********************************************************************************************************/ + +function isTouchValid(coords) { + // TODO: Extend to the detection of touches on new menu bars + var radarModeTouchValid = radarModeInterface.isTouchValid(coords); + + // getItemAtPoint does not exist anymore, look for another way to know if we are touching buttons + // is it still needed? + return /*!tablet.getItemAtPoint(coords) && */radarModeTouchValid; +} + +/******************************************************************************************************** + * + ********************************************************************************************************/ + +var touchStartingCoordinates = null; + +var KEEP_PRESSED_FOR_TELEPORT_MODE_TIME = 750; +var touchBeginTime; + +function touchBegin(event) { + var coords = { x: event.x, y: event.y }; + if (!isTouchValid(coords) ) { + printd("analyze touch - RADAR_TOUCH - INVALID"); + currentTouchIsValid = false; + touchStartingCoordinates = null; + } else { + printd("analyze touch - RADAR_TOUCH - ok"); + currentTouchIsValid = true; + 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; + } + } + var deltaHeight = avatarY + radarHeight - Camera.position.y; + Camera.position = Vec3.sum(Camera.position, {x:0, y: deltaHeight, z: 0}); + if (!draggingCamera) { + startedDraggingCamera = true; + draggingCamera = true; + } + + prevTouchPinchRadius = event.radius; +} + +function isInsideSquare(coords0, coords1, halfside) { + return 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)); + + 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 = entityIconModelDimensions(); + //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 (!currentTouchIsValid) { + return; // avoid moving and zooming when tap is over UI entities + } + 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 + 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("image3d", { + subImage: { x: 0, y: 0, width: 150, height: 142}, + url: getAvatarIconForUser(QUuid), + dimensions: ICON_ENTITY_DEFAULT_DIMENSIONS, + visible: false, + ignoreRayIntersection: false, + orientation: Quat.fromPitchYawRollDegrees(-90,0,0) + }); + + 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: 68, 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 renderMyAvatarIcon() { + 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, + Camera.position.y - RADAR_CAMERA_DISTANCE_TO_ICONS); + if (!iconPos) { printd("avatarmy icon pos null"); return;} + var iconDimensions = avatarIconPlaneDimensions(); + + var avatarPos = MyAvatar.position; + var cameraPos = Camera.position; + var commonY = Camera.position.y - RADAR_CAMERA_DISTANCE_TO_ICONS; + 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.sessionUUID) { + myAvatarIcon = Overlays.addOverlay("image3d", { + subImage: { x: 0, y: 0, width: 150, height: 142}, + url: getAvatarIconForUser(MyAvatar.sessionUUID), + dimensions: ICON_ENTITY_DEFAULT_DIMENSIONS, + visible: false, + ignoreRayIntersection: false, + orientation: Quat.fromPitchYawRollDegrees(-90,0,0) + }); + } + + if (!myAvatarName) { + myAvatarName = Overlays.addOverlay("text", { + width: 40, + height: AVATAR_DISPLAY_NAME_HEIGHT, + textAlignCenter: true, + color: { red: 255, green: 255, blue: 255}, + backgroundAlpha: 0.0, + font: {size: 68, 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 - RADAR_CAMERA_DISTANCE_TO_ICONS; + 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, + Camera.position.y - RADAR_CAMERA_DISTANCE_TO_ICONS); + 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} + }); + } + } + } + } +} + +function entityAdded(entityID) { + printd ("Entity added " + entityID); + var props = Entities.getEntityProperties(entityID, ["type"]); + printd ("Entity added " + entityID + " PROPS " + JSON.stringify(props)); + if (props && props.type == "Web") { + printd ("Entity Web added " + entityID); + saveEntityData(entityID, true); + } +} + +function entityRemoved(entityID) { + printd ("Entity removed " + entityID); + var props = Entities.getEntityProperties(entityID, ["type"]); + if (props && props.type == "Web") { + print ("Entity Web removed " + entityID); + removeEntityData(entityID); + } +} + +/******************************************************************************************************** + * Entities (to remark) cache structure for showing entities markers + ********************************************************************************************************/ + +var entitiesData = {}; // by entityID +var entitiesByOverlayID = {}; // by overlayID +var entitiesIcons = []; // a parallel list of icons (overlays) to easily run through + +var ICON_ENTITY_WEB_MODEL_URL = Script.resolvePath("../assets/images/web.svg"); +var ICON_ENTITY_IMG_MODEL_URL = Script.resolvePath("../assets/models/teleport-cancel.fbx"); // FIXME - use correct model&texture +var ICON_ENTITY_DEFAULT_DIMENSIONS = { + x: 0.10, + y: 0.00001, + z: 0.10 +}; + +var entityIconModelDimensionsVal = { x: 0, y: 0.00001, z: 0}; +function entityIconModelDimensions() { + // given the current height, give a size + var xz = -0.002831 * radarHeight + 0.1; + entityIconModelDimensionsVal.x = xz; + entityIconModelDimensionsVal.z = xz; + // reuse object + return entityIconModelDimensionsVal; +} +/* + * entityIconPlaneDimensions: similar to entityIconModelDimensions but using xy plane + */ +function entityIconPlaneDimensions() { + var dim = entityIconModelDimensions(); + var z = dim.z; + dim.z = dim.y; + dim.y = z; + return dim; +} + +function currentOverlayForEntity(QUuid) { + if (entitiesData[QUuid] != undefined) { + return entitiesData[QUuid].icon; + } else { + return null; + } +} + +function saveEntityData(QUuid, planar) { + if (QUuid == null) return; + var entity = Entities.getEntityProperties(QUuid, ["position"]); + printd("entity added save entity " + QUuid); + if (entitiesData[QUuid] != undefined) { + entitiesData[QUuid].position = entity.position; + } else { + var entityIcon = Overlays.addOverlay("image3d", { + subImage: { x: 0, y: 0, width: 150, height: 150}, + url: ICON_ENTITY_WEB_MODEL_URL, + dimensions: ICON_ENTITY_DEFAULT_DIMENSIONS, + visible: false, + ignoreRayIntersection: false, + orientation: Quat.fromPitchYawRollDegrees(-90,0,0) + }); + + + entitiesIcons.push(entityIcon); + entitiesData[QUuid] = { position: entity.position, icon: entityIcon}; + entitiesByOverlayID[entityIcon] = QUuid; + } +} + +function removeEntityData(QUuid) { + if (QUuid == null) return; + + var itsOverlay = currentOverlayForEntity(QUuid); + if (itsOverlay != null) { + Overlays.deleteOverlay(itsOverlay); + delete entitiesByOverlayID[itsOverlay]; + } + var idx = entitiesIcons.indexOf(itsOverlay); + entitiesIcons.splice(idx, 1); + + delete entitiesData[QUuid]; +} + +/******************************************************************************************************** + * Entities to remark Icon/Markers rendering + ********************************************************************************************************/ + +function hideAllEntitiesIcons() { + var len = entitiesIcons.length; + for (var i = 0; i < len; i++) { + Overlays.editOverlay(entitiesIcons[i], {visible: false}); + } +} + +function renderAllEntitiesIcons() { + var entityPos; + var entityProps; + var iconDimensions = entityIconModelDimensions(); + var planeDimensions = entityIconPlaneDimensions(); // plane overlays uses xy instead of xz + for (var QUuid in entitiesData) { + if (entitiesData.hasOwnProperty(QUuid)) { + entityProps = Entities.getEntityProperties(QUuid, ["position","visible"]); + if (entityProps != null) { + entityPos = entityProps.position; + if (entitiesData[QUuid].icon != undefined && entityPos) { + var iconPos = findLineToHeightIntersectionCoords( entityPos.x, entityPos.y + RADAR_ICONS_APPARENT_DISTANCE_TO_AVATAR_BASE, entityPos.z, + Camera.position.x, Camera.position.y, Camera.position.z, + Camera.position.y - RADAR_CAMERA_DISTANCE_TO_ICONS); + if (!iconPos) { printd ("entity icon pos bad for " + QUuid); continue; } + var dimensions = entitiesData[QUuid].planar? planeDimensions : iconDimensions; + Overlays.editOverlay(entitiesData[QUuid].icon, { + visible: entityProps.visible, + dimensions: dimensions, + position: iconPos + }); + } + } + } + } +} + +/******************************************************************************************************** + * + ********************************************************************************************************/ + +function startRadar() { + printd("avatar added my avatar is " + MyAvatar.sessionUUID); + saveAllOthersAvatarsData(); + Camera.mode = "independent"; + + Camera.position = Vec3.sum(MyAvatar.position, {x:0, y: radarHeight, z: 0}); + Camera.orientation = Quat.fromPitchYawRollDegrees(-90,0,0); + radar = true; + + connectRadarModeEvents(); +} + +function endRadar() { + printd("-- endRadar"); + Camera.mode = "first person"; + radar = false; + + disconnectRadarModeEvents(); + hideAllEntitiesIcons(); + 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(); + hideAllEntitiesIcons(); + startedDraggingCamera = false; + } else if (!draggingCamera) { + renderMyAvatarIcon(); + renderAllOthersAvatarIcons(); + renderAllEntitiesIcons(); + } +} + +function valueIfDefined(value) { + return value !== undefined ? value : ""; +} + +function entitiesAnalysis() { + var ids = Entities.findEntitiesInFrustum(Camera.frustum); + var entities = []; + for (var i = 0; i < ids.length; i++) { + var id = ids[i]; + var properties = Entities.getEntityProperties(id); + entities.push({ + id: id, + name: properties.name, + type: properties.type, + url: properties.type == "Model" ? properties.modelURL : "", + sourceUrl: properties.sourceUrl, + locked: properties.locked, + visible: properties.visible, + drawCalls: valueIfDefined(properties.renderInfo.drawCalls), + hasScript: properties.script !== "" + }); + } +} + +function connectRadarModeEvents() { + Script.update.connect(updateRadar); // 60Hz loop + Controller.keyPressEvent.connect(keyPressEvent); + Controller.mousePressEvent.connect(mousePress); // single click/touch + Controller.touchUpdateEvent.connect(touchUpdate); + MyAvatar.positionGoneTo.connect(positionGoneTo); +} + +function positionGoneTo() { + Camera.position = Vec3.sum(MyAvatar.position, {x:0, y: radarHeight, z: 0}); +} + +function disconnectRadarModeEvents() { + Script.update.disconnect(updateRadar); + Controller.keyPressEvent.disconnect(keyPressEvent); + Controller.mousePressEvent.disconnect(mousePress); + Controller.touchUpdateEvent.disconnect(touchUpdate); + MyAvatar.positionGoneTo.disconnect(positionGoneTo); +} + +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); + + Entities.addingEntity.connect(entityAdded); + Entities.deletingEntity.connect(entityRemoved); +} + + diff --git a/scripts/system/+android/uniqueColor.js b/scripts/system/+android/uniqueColor.js new file mode 100644 index 0000000000..c296b6c87d --- /dev/null +++ b/scripts/system/+android/uniqueColor.js @@ -0,0 +1,46 @@ +"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); + } +}; From bb4f57f982e41eefeb27320ae3c885462fd842a8 Mon Sep 17 00:00:00 2001 From: Cristian Luis Duarte Date: Mon, 5 Feb 2018 20:51:26 -0300 Subject: [PATCH 09/29] Android - Avatar selection window accessible from the bottom bar. Only hardcoded avatars, no marketplace. --- .../resources/icons/+android/avatar-a.svg | 38 + .../resources/icons/+android/avatar-i.svg | 38 + .../resources/icons/+android/button-a.svg | 949 +++++++++++++++++ interface/resources/icons/+android/button.svg | 949 +++++++++++++++++ interface/resources/icons/+android/tick.svg | 950 ++++++++++++++++++ .../qml/controls-uit/+android/ImageButton.qml | 82 ++ .../qml/hifi/+android/AvatarOption.qml | 117 +++ .../qml/hifi/+android/HifiConstants.qml | 4 +- .../qml/hifi/+android/avatarSelection.qml | 175 ++++ interface/src/Application.cpp | 2 + interface/src/Application.h | 2 + scripts/system/+android/avatarSelection.js | 159 +++ scripts/system/+android/bottombar.js | 47 +- 13 files changed, 3509 insertions(+), 3 deletions(-) create mode 100755 interface/resources/icons/+android/avatar-a.svg create mode 100755 interface/resources/icons/+android/avatar-i.svg create mode 100644 interface/resources/icons/+android/button-a.svg create mode 100644 interface/resources/icons/+android/button.svg create mode 100644 interface/resources/icons/+android/tick.svg create mode 100644 interface/resources/qml/controls-uit/+android/ImageButton.qml create mode 100644 interface/resources/qml/hifi/+android/AvatarOption.qml create mode 100644 interface/resources/qml/hifi/+android/avatarSelection.qml create mode 100644 scripts/system/+android/avatarSelection.js diff --git a/interface/resources/icons/+android/avatar-a.svg b/interface/resources/icons/+android/avatar-a.svg new file mode 100755 index 0000000000..165b39943e --- /dev/null +++ b/interface/resources/icons/+android/avatar-a.svg @@ -0,0 +1,38 @@ + + + + + + + + + + + diff --git a/interface/resources/icons/+android/avatar-i.svg b/interface/resources/icons/+android/avatar-i.svg new file mode 100755 index 0000000000..c1557487ea --- /dev/null +++ b/interface/resources/icons/+android/avatar-i.svg @@ -0,0 +1,38 @@ + + + + + + + + + + + diff --git a/interface/resources/icons/+android/button-a.svg b/interface/resources/icons/+android/button-a.svg new file mode 100644 index 0000000000..d469154775 --- /dev/null +++ b/interface/resources/icons/+android/button-a.svg @@ -0,0 +1,949 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/interface/resources/icons/+android/button.svg b/interface/resources/icons/+android/button.svg new file mode 100644 index 0000000000..8c19332064 --- /dev/null +++ b/interface/resources/icons/+android/button.svg @@ -0,0 +1,949 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/interface/resources/icons/+android/tick.svg b/interface/resources/icons/+android/tick.svg new file mode 100644 index 0000000000..2c451c0994 --- /dev/null +++ b/interface/resources/icons/+android/tick.svg @@ -0,0 +1,950 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/interface/resources/qml/controls-uit/+android/ImageButton.qml b/interface/resources/qml/controls-uit/+android/ImageButton.qml new file mode 100644 index 0000000000..5ebf7cd3e9 --- /dev/null +++ b/interface/resources/qml/controls-uit/+android/ImageButton.qml @@ -0,0 +1,82 @@ +// +// ImageButton.qml +// interface/resources/qml/controls-uit +// +// 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 "../styles-uit" 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 + } + } + ] +} \ No newline at end of file diff --git a/interface/resources/qml/hifi/+android/AvatarOption.qml b/interface/resources/qml/hifi/+android/AvatarOption.qml new file mode 100644 index 0000000000..e7056baa36 --- /dev/null +++ b/interface/resources/qml/hifi/+android/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 "../controls-uit" 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); + } +} \ No newline at end of file diff --git a/interface/resources/qml/hifi/+android/HifiConstants.qml b/interface/resources/qml/hifi/+android/HifiConstants.qml index ee6d92ed38..f74cc554aa 100644 --- a/interface/resources/qml/hifi/+android/HifiConstants.qml +++ b/interface/resources/qml/hifi/+android/HifiConstants.qml @@ -20,8 +20,8 @@ Item { Item { id: dimen - readonly property real windowLessWidth: 126 - readonly property real windowLessHeight: 64 + readonly property real windowLessWidth: 126*3 + readonly property real windowLessHeight: 64*3 readonly property real windowZ: 100 diff --git a/interface/resources/qml/hifi/+android/avatarSelection.qml b/interface/resources/qml/hifi/+android/avatarSelection.qml new file mode 100644 index 0000000000..3090204308 --- /dev/null +++ b/interface/resources/qml/hifi/+android/avatarSelection.qml @@ -0,0 +1,175 @@ +// +// avatarSelection.qml +// interface/resources/qml/android +// +// Created by Gabriel Calero & Cristian Duarte on 21 Sep 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 Hifi 1.0 + +import "../../styles" +import "." +import ".." +import ".." as QmlHifi +import "../../styles-uit" as HifiStyles + + +Item { + + id: top + + HifiConstants { id: android } + width: parent ? parent.width - android.dimen.windowLessWidth : 0 + height: parent ? parent.height - android.dimen.windowLessHeight : 0 + z: android.dimen.windowZ + anchors { horizontalCenter: parent.horizontalCenter; bottom: parent.bottom } + + signal sendToScript(var message); + + property bool shown: true + + onShownChanged: { + top.visible = shown; + } + + + HifiConstants { id: hifi } + HifiStyles.HifiConstants { id: hifiStyleConstants } + + property int cardWidth: 250 *3; + property int cardHeight: 240 *3; + property int gap: 14 *3; + + property var avatarsArray: []; + property var extraOptionsArray: []; + + function hide() { + shown = false; + sendToScript ({ method: "hide" }); + } + + Rectangle { + + width: parent ? parent.width : 0 + height: parent ? parent.height : 0 + + gradient: Gradient { + GradientStop { position: 0.0; color: android.color.gradientTop } + GradientStop { position: 1.0; color: android.color.gradientBottom } + } + + QmlHifi.WindowHeader { + id: header + iconSource: "../../../../icons/avatar-i.svg" + titleText: "AVATAR" + } + + ListModel { id: avatars } + + ListView { + id: scroll + height: 250*3 + property int stackedCardShadowHeight: 10*3; + spacing: gap; + clip: true; + anchors { + left: parent.left + right: parent.right + top: header.bottom + topMargin: gap + leftMargin: gap + rightMargin: gap + } + model: avatars; + orientation: ListView.Horizontal; + delegate: QmlHifi.AvatarOption { + type: model.type; + thumbnailUrl: model.thumbnailUrl; + avatarUrl: model.avatarUrl; + avatarName: model.avatarName; + avatarSelected: model.avatarSelected; + methodName: model.methodName; + actionText: model.actionText; + } + highlightMoveDuration: -1; + highlightMoveVelocity: -1; + } + + } + + function escapeRegExp(str) { + return str.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1"); + } + function replaceAll(str, find, replace) { + return str.replace(new RegExp(escapeRegExp(find), 'g'), replace); + } + + function refreshSelected(selectedAvatarUrl) { + // URL as ID? + avatarsArray.forEach(function (avatarData) { + avatarData.avatarSelected = (selectedAvatarUrl == avatarData.avatarUrl); + console.log('[avatarSelection] avatar : ', avatarData.avatarName, ' is selected? ' , avatarData.avatarSelected); + }); + } + + function addAvatar(name, thumbnailUrl, avatarUrl) { + avatarsArray.push({ + type: "avatar", + thumbnailUrl: thumbnailUrl, + avatarUrl: avatarUrl, + avatarName: name, + avatarSelected: false, + methodName: "", + actionText: "" + }); + } + + function showAvatars() { + avatars.clear(); + avatarsArray.forEach(function (avatarData) { + avatars.append(avatarData); + console.log('[avatarSelection] adding avatar to model: ', JSON.stringify(avatarData)); + }); + extraOptionsArray.forEach(function (extraData) { + avatars.append(extraData); + console.log('[avatarSelection] adding extra option to model: ', JSON.stringify(extraData)); + }); + } + + function addExtraOption(showName, thumbnailUrl, methodNameWhenClicked, actionText) { + extraOptionsArray.push({ + type: "extra", + thumbnailUrl: thumbnailUrl, + avatarUrl: "", + avatarName: showName, + avatarSelected: false, + methodName: methodNameWhenClicked, + actionText: actionText + }); + } + + function fromScript(message) { + //console.log("[CHAT] fromScript " + JSON.stringify(message)); + switch (message.type) { + case "addAvatar": + addAvatar(message.name, message.thumbnailUrl, message.avatarUrl); + break; + case "addExtraOption": + //(showName, thumbnailUrl, methodNameWhenClicked, actionText) + addExtraOption(message.showName, message.thumbnailUrl, message.methodNameWhenClicked, message.actionText); + break; + case "refreshSelected": + refreshSelected(message.selectedAvatarUrl); + break; + case "showAvatars": + showAvatars(); + break; + default: + } + } +} \ No newline at end of file diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 6fec6f1d72..cbcd08c667 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -5939,6 +5939,8 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEnginePointe scriptEngine->registerGlobalObject("ContextOverlay", DependencyManager::get().data()); scriptEngine->registerGlobalObject("Wallet", DependencyManager::get().data()); + scriptEngine->registerGlobalObject("App", this); + qScriptRegisterMetaType(scriptEngine.data(), OverlayIDtoScriptValue, OverlayIDfromScriptValue); DependencyManager::get()->registerMetaTypes(scriptEngine.data()); diff --git a/interface/src/Application.h b/interface/src/Application.h index 8f0690bda1..5161c62a65 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -389,6 +389,8 @@ public slots: const QString getPreferredCursor() const { return _preferredCursor.get(); } void setPreferredCursor(const QString& cursor); + Q_INVOKABLE bool askBeforeSetAvatarUrl(const QString& avatarUrl) { return askToSetAvatarUrl(avatarUrl); } + private slots: void showDesktop(); void clearDomainOctreeDetails(); diff --git a/scripts/system/+android/avatarSelection.js b/scripts/system/+android/avatarSelection.js new file mode 100644 index 0000000000..fd938236fb --- /dev/null +++ b/scripts/system/+android/avatarSelection.js @@ -0,0 +1,159 @@ +"use strict"; +// +// avatarSelection.js +// scripts/system/ +// +// Created by Gabriel Calero & Cristian Duarte on 21 Sep 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 window; + +var logEnabled = true; +var isVisible = false; + +function printd(str) { + if (logEnabled) + print("[avatarSelection.js] " + str); +} + +function fromQml(message) { // messages are {method, params}, like json-rpc. See also sendToQml. + var data; + printd("fromQml " + JSON.stringify(message)); + switch (message.method) { + case 'selectAvatar': + // use this message.params.avatarUrl + printd("Selected Avatar: [" + message.params.avatarUrl + "]"); + App.askBeforeSetAvatarUrl(message.params.avatarUrl); + break; + case 'openAvatarMarket': + // good + App.openUrl("https://metaverse.highfidelity.com/marketplace?category=avatars"); + break; + case 'hide': + module.exports.onHidden(); + break; + default: + print('[avatarSelection.js] Unrecognized message from avatarSelection.qml:', JSON.stringify(message)); + } +} + +function sendToQml(message) { + if (!window) { + print("[avatarSelection.js] There is no window object"); + return; + } + window.sendToQml(message); +} + +function refreshSelected(currentAvatarURL) { + sendToQml({ + type: "refreshSelected", + selectedAvatarUrl: currentAvatarURL + }); + + sendToQml({ + type: "showAvatars" + }); +} + +function init() { + if (!window) { + print("[avatarSelection.js] There is no window object for init()"); + return; + } + var DEFAULT_AVATAR_URL = "http://mpassets.highfidelity.com/f14bf7c9-49a1-4249-988a-0a577ed78957-v1/beingOfLight.fst"; + sendToQml({ + type: "addAvatar", + name: "Being of Light Avatar", + thumbnailUrl: "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/previews/f14bf7c9-49a1-4249-988a-0a577ed78957/thumbnail/hifi-mp-f14bf7c9-49a1-4249-988a-0a577ed78957.jpg", + avatarUrl: DEFAULT_AVATAR_URL + }); + sendToQml({ + type: "addAvatar", + name: "Cody", + thumbnailUrl: "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/previews/8c859fca-4cbd-4e82-aad1-5f4cb0ca5d53/thumbnail/hifi-mp-8c859fca-4cbd-4e82-aad1-5f4cb0ca5d53.jpg", + avatarUrl: "http://mpassets.highfidelity.com/8c859fca-4cbd-4e82-aad1-5f4cb0ca5d53-v1/cody.fst" + }); + sendToQml({ + type: "addAvatar", + name: "Mixamo Will", + thumbnailUrl: "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/previews/d029ae8d-2905-4eb7-ba46-4bd1b8cb9d73/thumbnail/hifi-mp-d029ae8d-2905-4eb7-ba46-4bd1b8cb9d73.jpg", + avatarUrl: "http://mpassets.highfidelity.com/d029ae8d-2905-4eb7-ba46-4bd1b8cb9d73-v1/4618d52e711fbb34df442b414da767bb.fst" + }); + sendToQml({ + type: "addAvatar", + name: "Albert", + thumbnailUrl: "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/previews/1e57c395-612e-4acd-9561-e79dbda0bc49/thumbnail/hifi-mp-1e57c395-612e-4acd-9561-e79dbda0bc49.jpg", + avatarUrl: "http://mpassets.highfidelity.com/1e57c395-612e-4acd-9561-e79dbda0bc49-v1/albert.fst" + }); + /* We need to implement the wallet, so let's skip this for the moment + sendToQml({ + type: "addExtraOption", + showName: "More choices", + thumbnailUrl: "../../../images/moreAvatars.png", + methodNameWhenClicked: "openAvatarMarket", + actionText: "MARKETPLACE" + }); + */ + var currentAvatarURL = Settings.getValue('Avatar/fullAvatarURL', DEFAULT_AVATAR_URL); + printd("Default Avatar: [" + DEFAULT_AVATAR_URL + "]"); + printd("Current Avatar: [" + currentAvatarURL + "]"); + if (!currentAvatarURL || 0 === currentAvatarURL.length) { + currentAvatarURL = DEFAULT_AVATAR_URL; + } + refreshSelected(currentAvatarURL); +} + +module.exports = { + init: function() { + window = new QmlFragment({ + qml: "hifi/avatarSelection.qml", + visible: false + }); + /*, + visible: false*/ + if (window) { + window.fromQml.connect(fromQml); + } + init(); + }, + show: function() { + if (window) { + window.setVisible(true); + isVisible = true; + } + }, + hide: function() { + if (window) { + window.setVisible(false); + } + isVisible = false; + }, + destroy: function() { + if (window) { + window.fromQml.disconnect(fromQml); + window.close(); + window = null; + } + }, + isVisible: function() { + return isVisible; + }, + width: function() { + return window ? window.size.x : 0; + }, + height: function() { + return window ? window.size.y : 0; + }, + position: function() { + return window && isVisible ? window.position : null; + }, + refreshSelectedAvatar: function(currentAvatarURL) { + refreshSelected(currentAvatarURL); + }, + onHidden: function() { } +}; diff --git a/scripts/system/+android/bottombar.js b/scripts/system/+android/bottombar.js index e58840ad6f..db05b88b04 100644 --- a/scripts/system/+android/bottombar.js +++ b/scripts/system/+android/bottombar.js @@ -14,8 +14,10 @@ var bottombar; var bottomHudOptionsBar; var gotoBtn; +var avatarBtn; var gotoScript = Script.require('./goto.js'); +var avatarSelection = Script.require('./avatarSelection.js'); var logEnabled = false; @@ -34,6 +36,8 @@ function init() { hideAddressBar(); } }); + avatarSelection.init(); + App.fullAvatarURLChanged.connect(processedNewAvatar); setupBottomBar(); setupBottomHudOptionsBar(); @@ -43,6 +47,7 @@ function init() { } function shutdown() { + App.fullAvatarURLChanged.disconnect(processedNewAvatar); } function setupBottomBar() { @@ -60,6 +65,32 @@ function setupBottomBar() { } }); + avatarBtn = bottombar.addButton({ + icon: "icons/avatar-i.svg", + activeIcon: "icons/avatar-a.svg", + bgOpacity: 0, + height: 240, + width: 294, + hoverBgOpacity: 0, + activeBgOpacity: 0, + activeHoverBgOpacity: 0, + iconSize: 108, + textSize: 45, + text: "AVATAR" + }); + avatarBtn.clicked.connect(function() { + printd("Avatar button clicked"); + if (!avatarSelection.isVisible()) { + showAvatarSelection(); + } else { + hideAvatarSelection(); + } + }); + avatarSelection.onHidden = function() { + if (avatarBtn) { + avatarBtn.isActive = false; + } + }; gotoBtn = bottombar.addButton({ icon: "icons/goto-i.svg", @@ -69,7 +100,7 @@ function setupBottomBar() { activeBgOpacity: 0, activeHoverBgOpacity: 0, height: 240, - width: 300, + width: 294, iconSize: 108, textSize: 45, text: "GO TO" @@ -148,7 +179,21 @@ function hideAddressBar() { gotoBtn.isActive = false; } +function showAvatarSelection() { + avatarSelection.show(); + avatarBtn.isActive = true; +} +function hideAvatarSelection() { + avatarSelection.hide(); + avatarBtn.isActive = false; +} + +// TODO: Move to avatarSelection.js and make it possible to hide the window from there AND switch the button state here too +function processedNewAvatar(url, modelName) { + avatarSelection.refreshSelectedAvatar(url); + hideAvatarSelection(); +} Script.scriptEnding.connect(function () { shutdown(); From fe5075bff2695559cc7a4269ed50685824012cc9 Mon Sep 17 00:00:00 2001 From: Cristian Luis Duarte Date: Tue, 6 Feb 2018 18:25:06 -0300 Subject: [PATCH 10/29] Android - Use the Wooden Mannequin avatar instead of being of light in the Avatar window --- scripts/system/+android/avatarSelection.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/system/+android/avatarSelection.js b/scripts/system/+android/avatarSelection.js index fd938236fb..be58f61ac2 100644 --- a/scripts/system/+android/avatarSelection.js +++ b/scripts/system/+android/avatarSelection.js @@ -65,11 +65,11 @@ function init() { print("[avatarSelection.js] There is no window object for init()"); return; } - var DEFAULT_AVATAR_URL = "http://mpassets.highfidelity.com/f14bf7c9-49a1-4249-988a-0a577ed78957-v1/beingOfLight.fst"; + var DEFAULT_AVATAR_URL = "http://mpassets.highfidelity.com/7fe80a1e-f445-4800-9e89-40e677b03bee-v3/mannequin.fst"; sendToQml({ type: "addAvatar", - name: "Being of Light Avatar", - thumbnailUrl: "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/previews/f14bf7c9-49a1-4249-988a-0a577ed78957/thumbnail/hifi-mp-f14bf7c9-49a1-4249-988a-0a577ed78957.jpg", + name: "Wooden Mannequin", + thumbnailUrl: "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/previews/7fe80a1e-f445-4800-9e89-40e677b03bee/thumbnail/hifi-mp-7fe80a1e-f445-4800-9e89-40e677b03bee.jpg", avatarUrl: DEFAULT_AVATAR_URL }); sendToQml({ From 8d6d078ea2c46e20f34ccb0de5962e91e8d10ade Mon Sep 17 00:00:00 2001 From: Cristian Luis Duarte Date: Tue, 6 Feb 2018 22:52:29 -0300 Subject: [PATCH 11/29] Android - Make radar turn of the virtual pad --- scripts/system/+android/radar.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/scripts/system/+android/radar.js b/scripts/system/+android/radar.js index 9fa07178f6..baf4441749 100644 --- a/scripts/system/+android/radar.js +++ b/scripts/system/+android/radar.js @@ -1100,6 +1100,8 @@ function startRadar() { 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(); } @@ -1108,6 +1110,8 @@ function endRadar() { Camera.mode = "first person"; radar = false; + Controller.setVPadEnabled(true); + disconnectRadarModeEvents(); hideAllEntitiesIcons(); hideAllAvatarIcons(); From 8c1a1eba02aa635e6085d2e5289cbef89de9fdae Mon Sep 17 00:00:00 2001 From: Cristian Luis Duarte Date: Wed, 7 Feb 2018 16:26:46 -0300 Subject: [PATCH 12/29] Android - Fix modes bar layout when expanded --- interface/resources/qml/hifi/+android/modesbar.qml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/interface/resources/qml/hifi/+android/modesbar.qml b/interface/resources/qml/hifi/+android/modesbar.qml index 8ce455c2c1..fe71314ece 100644 --- a/interface/resources/qml/hifi/+android/modesbar.qml +++ b/interface/resources/qml/hifi/+android/modesbar.qml @@ -25,8 +25,8 @@ Item { } Component.onCompleted: { - width = 330; - height = 330; + width = 300 + 30; // That 30 is extra regardless the qty of items shown + height = 300 + 30; x=Window.innerWidth - width; } @@ -60,10 +60,10 @@ Item { function fromScript(message) { switch (message.type) { case "allButtonsShown": - modesbar.height = flowMain.children.length * 100 + 10; + modesbar.height = flowMain.children.length * 300 + 30; // That 30 is extra regardless the qty of items shown break; case "inactiveButtonsHidden": - modesbar.height = 100 + 10; + modesbar.height = 300 + 30; break; default: break; From 017f99837a7de2a728b72965489a9ae4495eee11 Mon Sep 17 00:00:00 2001 From: Cristian Luis Duarte Date: Fri, 9 Feb 2018 17:27:55 -0300 Subject: [PATCH 13/29] Android - Sign in, Sign up and Sign out. Note: It's still desktop scaled, being some components small for a phone --- .../resources/icons/+android/login-a.svg | 990 ++++++++++++++++++ .../resources/icons/+android/login-i.svg | 990 ++++++++++++++++++ .../resources/qml/+android/LoginDialog.qml | 95 ++ .../LoginDialog/+android/LinkAccountBody.qml | 330 ++++++ .../qml/LoginDialog/+android/SignUpBody.qml | 297 ++++++ scripts/system/+android/bottombar.js | 39 + 6 files changed, 2741 insertions(+) create mode 100755 interface/resources/icons/+android/login-a.svg create mode 100755 interface/resources/icons/+android/login-i.svg create mode 100644 interface/resources/qml/+android/LoginDialog.qml create mode 100644 interface/resources/qml/LoginDialog/+android/LinkAccountBody.qml create mode 100644 interface/resources/qml/LoginDialog/+android/SignUpBody.qml diff --git a/interface/resources/icons/+android/login-a.svg b/interface/resources/icons/+android/login-a.svg new file mode 100755 index 0000000000..8a7f097ed7 --- /dev/null +++ b/interface/resources/icons/+android/login-a.svg @@ -0,0 +1,990 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/interface/resources/icons/+android/login-i.svg b/interface/resources/icons/+android/login-i.svg new file mode 100755 index 0000000000..6f011e1d13 --- /dev/null +++ b/interface/resources/icons/+android/login-i.svg @@ -0,0 +1,990 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/interface/resources/qml/+android/LoginDialog.qml b/interface/resources/qml/+android/LoginDialog.qml new file mode 100644 index 0000000000..4badd555fb --- /dev/null +++ b/interface/resources/qml/+android/LoginDialog.qml @@ -0,0 +1,95 @@ +// +// LoginDialog.qml +// +// Created by David Rowe on 3 Jun 2015 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import Hifi 1.0 +import QtQuick 2.4 + +import "controls-uit" +import "styles-uit" +import "windows" + +import "LoginDialog" + +ModalWindow { + id: root + HifiConstants { id: hifi } + + property int scale: 3 + + objectName: "LoginDialog" + implicitWidth: 520 *scale + implicitHeight: 150 *scale + y:0 + destroyOnCloseButton: true + destroyOnHidden: true + visible: true + + property string iconText: "" + property int iconSize: 35 *scale + + property string title: "" + property int titleWidth: 0 + + keyboardOverride: true // Disable ModalWindow's keyboard. + + function tryDestroy() { + root.destroy() + } + + LoginDialog { + id: loginDialog + + Loader { + id: bodyLoader + source: loginDialog.isSteamRunning() ? "LoginDialog/+android/SignInBody.qml" : "LoginDialog/+android/LinkAccountBody.qml" + } + } + + Component.onCompleted: { + this.anchors.centerIn = undefined; + this.y=50 *scale; + this.x=150 *scale; + } + + Keys.onPressed: { + if (!visible) { + return + } + + if (event.modifiers === Qt.ControlModifier) + switch (event.key) { + case Qt.Key_A: + event.accepted = true + detailedText.selectAll() + break + case Qt.Key_C: + event.accepted = true + detailedText.copy() + break + case Qt.Key_Period: + if (Qt.platform.os === "osx") { + event.accepted = true + content.reject() + } + break + } else switch (event.key) { + case Qt.Key_Escape: + case Qt.Key_Back: + event.accepted = true + destroy() + break + + case Qt.Key_Enter: + case Qt.Key_Return: + event.accepted = true + break + } + } +} diff --git a/interface/resources/qml/LoginDialog/+android/LinkAccountBody.qml b/interface/resources/qml/LoginDialog/+android/LinkAccountBody.qml new file mode 100644 index 0000000000..3d8bcedb38 --- /dev/null +++ b/interface/resources/qml/LoginDialog/+android/LinkAccountBody.qml @@ -0,0 +1,330 @@ +// +// 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 "../../controls-uit" +import "../../styles-uit" + +Item { + id: linkAccountBody + + property int scale: 3 + + clip: true + height: 100 *scale + 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: 480 *scale + readonly property int maxWidth: 1280 *scale + readonly property int minHeight: 50 *scale + readonly property int maxHeight: 220 *scale + + 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 = 140 *scale; + /*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: 48 *scale + height: 48 *scale + } + + 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 *scale + 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 + + Row { + spacing: hifi.dimensions.contentSpacing.x + + TextField { + id: usernameField + anchors { + verticalCenter: parent.verticalCenter + } + width: 350 *scale + + placeholderText: qsTr("Username or Email") + } + + ShortcutText { + anchors { + verticalCenter: parent.verticalCenter + } + + text: "Forgot Username?" + + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter + linkColor: hifi.colors.blueAccent + + onLinkActivated: loginDialog.openUrl(link) + } + } + Row { + spacing: hifi.dimensions.contentSpacing.x + + TextField { + id: passwordField + anchors { + verticalCenter: parent.verticalCenter + } + width: 350 *scale + + placeholderText: qsTr("Password") + echoMode: TextInput.Password + } + + ShortcutText { + anchors { + verticalCenter: parent.verticalCenter + } + + text: "Forgot Password?" + + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter + linkColor: hifi.colors.blueAccent + + onLinkActivated: loginDialog.openUrl(link) + } + } + + } + + 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: 1 *scale + 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 + width: 200 *scale + + 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 Pepe") + + onClicked: { + Qt.inputMethod.hide(); + root.destroy(); + } + } + } + + 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/SignUpBody.qml b/interface/resources/qml/LoginDialog/+android/SignUpBody.qml new file mode 100644 index 0000000000..b9f36c7ad7 --- /dev/null +++ b/interface/resources/qml/LoginDialog/+android/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 "../../controls-uit" +import "../../styles-uit" + +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: 480 + readonly property int maxWidth: 1280 + readonly property int minHeight: 100 + readonly property int maxHeight: 200 + + 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: 350 + + placeholderText: "Email" + } + } + + Row { + spacing: hifi.dimensions.contentSpacing.x + + TextField { + id: usernameField + anchors { + verticalCenter: parent.verticalCenter + } + width: 350 + + 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: 350 + + 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-android.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 + width: 200 + + 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/scripts/system/+android/bottombar.js b/scripts/system/+android/bottombar.js index db05b88b04..72ba1c1e50 100644 --- a/scripts/system/+android/bottombar.js +++ b/scripts/system/+android/bottombar.js @@ -15,6 +15,7 @@ var bottombar; var bottomHudOptionsBar; var gotoBtn; var avatarBtn; +var loginBtn; var gotoScript = Script.require('./goto.js'); var avatarSelection = Script.require('./avatarSelection.js'); @@ -44,6 +45,8 @@ function init() { raiseBottomBar(); + GlobalServices.connected.connect(handleLogin); + GlobalServices.disconnected.connect(handleLogout); } function shutdown() { @@ -114,6 +117,23 @@ function setupBottomBar() { } }); + loginBtn = bottombar.addButton({ + icon: "icons/login-i.svg", + activeIcon: "icons/login-a.svg", + height: 240, + width: 294, + iconSize: 108, + textSize: 45, + text: Account.isLoggedIn() ? "LOG OUT" : "LOG IN" + }); + loginBtn.clicked.connect(function() { + if (!Account.isLoggedIn()) { + Account.checkAndSignalForAccessToken(); + } else { + Menu.triggerOption("Login / Sign Up"); + } + }); + // TODO: setup all the buttons or provide a dynamic interface raiseBottomBar(); @@ -195,8 +215,27 @@ function processedNewAvatar(url, modelName) { hideAvatarSelection(); } +function handleLogin() { + Script.setTimeout(function() { + if (Account.isLoggedIn()) { + MyAvatar.displayName=Account.getUsername(); + } + }, 2000); + if (loginBtn) { + loginBtn.editProperties({text: "LOG OUT"}); + } +} +function handleLogout() { + MyAvatar.displayName=""; + if (loginBtn) { + loginBtn.editProperties({text: "LOG IN"}); + } +} + Script.scriptEnding.connect(function () { shutdown(); + GlobalServices.connected.disconnect(handleLogin); + GlobalServices.disconnected.disconnect(handleLogout); }); init(); From 674af54a821b4b620ca48b18fad7934c7cab20cf Mon Sep 17 00:00:00 2001 From: Cristian Luis Duarte Date: Wed, 14 Feb 2018 16:57:24 -0300 Subject: [PATCH 14/29] Android - Scale up Sign In and Sign Up windows. --- .../resources/qml/+android/LoginDialog.qml | 12 +- .../LoginDialog/+android/LinkAccountBody.qml | 45 +-- .../qml/LoginDialog/+android/SignUpBody.qml | 20 +- .../resources/qml/controls-uit/Button.qml | 2 +- .../qml/styles-uit/+android/HifiConstants.qml | 358 ++++++++++++++++++ .../qml/styles-uit/HifiConstants.qml | 1 + 6 files changed, 396 insertions(+), 42 deletions(-) create mode 100644 interface/resources/qml/styles-uit/+android/HifiConstants.qml diff --git a/interface/resources/qml/+android/LoginDialog.qml b/interface/resources/qml/+android/LoginDialog.qml index 4badd555fb..c60864cd1d 100644 --- a/interface/resources/qml/+android/LoginDialog.qml +++ b/interface/resources/qml/+android/LoginDialog.qml @@ -21,18 +21,16 @@ ModalWindow { id: root HifiConstants { id: hifi } - property int scale: 3 - objectName: "LoginDialog" - implicitWidth: 520 *scale - implicitHeight: 150 *scale + implicitWidth: 1560 + implicitHeight: 450 y:0 destroyOnCloseButton: true destroyOnHidden: true visible: true property string iconText: "" - property int iconSize: 35 *scale + property int iconSize: 105 property string title: "" property int titleWidth: 0 @@ -54,8 +52,8 @@ ModalWindow { Component.onCompleted: { this.anchors.centerIn = undefined; - this.y=50 *scale; - this.x=150 *scale; + this.y=150; + this.x=450; } Keys.onPressed: { diff --git a/interface/resources/qml/LoginDialog/+android/LinkAccountBody.qml b/interface/resources/qml/LoginDialog/+android/LinkAccountBody.qml index 3d8bcedb38..8f76c6ed99 100644 --- a/interface/resources/qml/LoginDialog/+android/LinkAccountBody.qml +++ b/interface/resources/qml/LoginDialog/+android/LinkAccountBody.qml @@ -19,10 +19,8 @@ import "../../styles-uit" Item { id: linkAccountBody - property int scale: 3 - clip: true - height: 100 *scale + height: 300 width: root.pane.width property bool failAfterSignUp: false function login() { @@ -39,10 +37,10 @@ Item { QtObject { id: d - readonly property int minWidth: 480 *scale - readonly property int maxWidth: 1280 *scale - readonly property int minHeight: 50 *scale - readonly property int maxHeight: 220 *scale + 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); @@ -56,7 +54,7 @@ Item { } parent.width = root.width = Math.max(d.minWidth, Math.min(d.maxWidth, targetWidth)); - parent.height = 140 *scale; + 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);*/ } @@ -86,8 +84,8 @@ Item { visible: false running: true - width: 48 *scale - height: 48 *scale + width: 144 + height: 144 } ShortcutText { @@ -104,7 +102,7 @@ Item { text: qsTr("Username or password incorrect.") wrapMode: Text.WordWrap color: hifi.colors.redAccent - lineHeight: 1 *scale + lineHeight: 1 lineHeightMode: Text.ProportionalHeight horizontalAlignment: Text.AlignHCenter } @@ -127,7 +125,7 @@ Item { anchors { verticalCenter: parent.verticalCenter } - width: 350 *scale + width: 780 placeholderText: qsTr("Username or Email") } @@ -154,7 +152,7 @@ Item { anchors { verticalCenter: parent.verticalCenter } - width: 350 *scale + width: 780 placeholderText: qsTr("Password") echoMode: TextInput.Password @@ -191,7 +189,7 @@ Item { text: qsTr("Your steam account informations will not be exposed to other users.") wrapMode: Text.WordWrap color: hifi.colors.baseGrayHighlight - lineHeight: 1 *scale + lineHeight: 3 lineHeightMode: Text.ProportionalHeight horizontalAlignment: Text.AlignHCenter } @@ -220,16 +218,16 @@ Item { onHeightChanged: d.resize(); onWidthChanged: d.resize(); Button { - anchors.verticalCenter: parent.verticalCenter + anchors.verticalCenter: parent.verticalCenter - text: qsTr("Sign Up") - visible: !loginDialog.isSteamRunning() + text: qsTr("Sign Up") + visible: !loginDialog.isSteamRunning() - onClicked: { - bodyLoader.setSource("SignUpBody.qml") - bodyLoader.item.width = root.pane.width - bodyLoader.item.height = root.pane.height - } + onClicked: { + bodyLoader.setSource("SignUpBody.qml") + bodyLoader.item.width = root.pane.width + bodyLoader.item.height = root.pane.height + } } } @@ -246,7 +244,6 @@ Item { Button { id: linkAccountButton anchors.verticalCenter: parent.verticalCenter - width: 200 *scale text: qsTr(loginDialog.isSteamRunning() ? "Link Account" : "Login") color: hifi.buttons.blue @@ -260,7 +257,7 @@ Item { Button { anchors.verticalCenter: parent.verticalCenter - text: qsTr("Cancel Pepe") + text: qsTr("Cancel") onClicked: { Qt.inputMethod.hide(); diff --git a/interface/resources/qml/LoginDialog/+android/SignUpBody.qml b/interface/resources/qml/LoginDialog/+android/SignUpBody.qml index b9f36c7ad7..3a44a8d741 100644 --- a/interface/resources/qml/LoginDialog/+android/SignUpBody.qml +++ b/interface/resources/qml/LoginDialog/+android/SignUpBody.qml @@ -18,6 +18,7 @@ import "../../styles-uit" Item { id: signupBody + clip: true height: root.pane.height width: root.pane.width @@ -36,10 +37,10 @@ Item { QtObject { id: d - readonly property int minWidth: 480 - readonly property int maxWidth: 1280 - readonly property int minHeight: 100 - readonly property int maxHeight: 200 + 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); @@ -113,7 +114,7 @@ Item { anchors { verticalCenter: parent.verticalCenter } - width: 350 + width: 780 placeholderText: "Email" } @@ -127,7 +128,7 @@ Item { anchors { verticalCenter: parent.verticalCenter } - width: 350 + width: 780 placeholderText: "Username" } @@ -154,7 +155,7 @@ Item { anchors { verticalCenter: parent.verticalCenter } - width: 350 + width: 780 placeholderText: "Password" echoMode: TextInput.Password @@ -193,7 +194,7 @@ Item { anchors { left: parent.left top: form.bottom - topMargin: hifi.dimensions.contentSpacing.y / 2 + topMargin: hifi.dimensions.contentSpacing.y// / 2 } spacing: hifi.dimensions.contentSpacing.x @@ -205,7 +206,7 @@ Item { text: qsTr("Existing User") onClicked: { - bodyLoader.setSource("LinkAccountBody-android.qml") + bodyLoader.setSource("LinkAccountBody.qml") bodyLoader.item.width = root.pane.width bodyLoader.item.height = root.pane.height } @@ -225,7 +226,6 @@ Item { Button { id: linkAccountButton anchors.verticalCenter: parent.verticalCenter - width: 200 text: qsTr("Sign Up") color: hifi.buttons.blue diff --git a/interface/resources/qml/controls-uit/Button.qml b/interface/resources/qml/controls-uit/Button.qml index 02c6181952..ba604a0404 100644 --- a/interface/resources/qml/controls-uit/Button.qml +++ b/interface/resources/qml/controls-uit/Button.qml @@ -22,7 +22,7 @@ Original.Button { property int colorScheme: hifi.colorSchemes.light property string buttonGlyph: ""; - width: 120 + width: hifi.dimensions.buttonWidth height: hifi.dimensions.controlLineHeight HifiConstants { id: hifi } diff --git a/interface/resources/qml/styles-uit/+android/HifiConstants.qml b/interface/resources/qml/styles-uit/+android/HifiConstants.qml new file mode 100644 index 0000000000..d5fab57501 --- /dev/null +++ b/interface/resources/qml/styles-uit/+android/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/resources/qml/styles-uit/HifiConstants.qml b/interface/resources/qml/styles-uit/HifiConstants.qml index cf800cb62e..d676f032b7 100644 --- a/interface/resources/qml/styles-uit/HifiConstants.qml +++ b/interface/resources/qml/styles-uit/HifiConstants.qml @@ -165,6 +165,7 @@ Item { readonly property real scrollbarBackgroundWidth: 20 readonly property real scrollbarHandleWidth: scrollbarBackgroundWidth - 2 readonly property real tabletMenuHeader: 90 + readonly property real buttonWidth: 120 } Item { From 28c7ed761ef1d1e35b3cfbcc3dc7e039576832bc Mon Sep 17 00:00:00 2001 From: Gabriel Calero Date: Thu, 15 Feb 2018 12:17:13 -0300 Subject: [PATCH 15/29] Add precision qualifier in fragment shaders for struct fields --- libraries/graphics/src/graphics/Light.slh | 10 ++++++- .../src/graphics/LightIrradiance.shared.slh | 12 ++++++--- .../src/graphics/LightVolume.shared.slh | 12 +++++++-- libraries/graphics/src/graphics/Material.slh | 17 +++++------- .../graphics/SphericalHarmonics.shared.slh | 26 ++++++++++++------- 5 files changed, 52 insertions(+), 25 deletions(-) diff --git a/libraries/graphics/src/graphics/Light.slh b/libraries/graphics/src/graphics/Light.slh index e1202ed6a0..cda19aadfb 100644 --- a/libraries/graphics/src/graphics/Light.slh +++ b/libraries/graphics/src/graphics/Light.slh @@ -32,8 +32,16 @@ vec3 getLightIrradiance(Light l) { return lightIrradiance_getIrradiance(l.irradi // AMbient lighting needs extra info provided from a different Buffer <@include graphics/SphericalHarmonics.shared.slh@> // Light Ambient +#ifndef PRECISIONQ +#ifdef GL_ES +#define PRECISIONQ highp +#else +#define PRECISIONQ +#endif +#endif + struct LightAmbient { - vec4 _ambient; + PRECISIONQ vec4 _ambient; SphericalHarmonics _ambientSphere; }; diff --git a/libraries/graphics/src/graphics/LightIrradiance.shared.slh b/libraries/graphics/src/graphics/LightIrradiance.shared.slh index 13d6cf4b93..ff42f57fd9 100644 --- a/libraries/graphics/src/graphics/LightIrradiance.shared.slh +++ b/libraries/graphics/src/graphics/LightIrradiance.shared.slh @@ -12,11 +12,17 @@ #define LightIrradianceConstRef LightIrradiance - +#ifndef PRECISIONQ +#ifdef GL_ES +#define PRECISIONQ highp +#else +#define PRECISIONQ +#endif +#endif struct LightIrradiance { - vec4 colorIntensity; + PRECISIONQ vec4 colorIntensity; // falloffRadius, cutoffRadius, falloffSpot, spare - vec4 attenuation; + PRECISIONQ vec4 attenuation; }; diff --git a/libraries/graphics/src/graphics/LightVolume.shared.slh b/libraries/graphics/src/graphics/LightVolume.shared.slh index 3f0cb135f5..48249e766f 100644 --- a/libraries/graphics/src/graphics/LightVolume.shared.slh +++ b/libraries/graphics/src/graphics/LightVolume.shared.slh @@ -15,9 +15,17 @@ #define LightVolumeConstRef LightVolume +#ifndef PRECISIONQ +#ifdef GL_ES +#define PRECISIONQ highp +#else +#define PRECISIONQ +#endif +#endif + struct LightVolume { - vec4 positionRadius; - vec4 directionSpotCos; + PRECISIONQ vec4 positionRadius; + PRECISIONQ vec4 directionSpotCos; }; bool lightVolume_isPoint(LightVolume lv) { return bool(lv.directionSpotCos.w < 0.f); } diff --git a/libraries/graphics/src/graphics/Material.slh b/libraries/graphics/src/graphics/Material.slh index 36bee3fb71..bdb87ff014 100644 --- a/libraries/graphics/src/graphics/Material.slh +++ b/libraries/graphics/src/graphics/Material.slh @@ -14,17 +14,17 @@ // The material values (at least the material key) must be precisely bitwise accurate // to what is provided by the uniform buffer, or the material key has the wrong bits #ifdef GL_ES -precision highp float; #define BITFIELD highp int +#define PRECISIONQ highp #else #define BITFIELD int +#define PRECISIONQ #endif - -struct Material { - vec4 _emissiveOpacity; - vec4 _albedoRoughness; - vec4 _fresnelMetallic; - vec4 _scatteringSpare2Key; +struct Material { + PRECISIONQ vec4 _emissiveOpacity; + PRECISIONQ vec4 _albedoRoughness; + PRECISIONQ vec4 _fresnelMetallic; + PRECISIONQ vec4 _scatteringSpare2Key; }; uniform materialBuffer { @@ -70,8 +70,5 @@ const BITFIELD OCCLUSION_MAP_BIT = 0x00004000; const BITFIELD LIGHTMAP_MAP_BIT = 0x00008000; const BITFIELD SCATTERING_MAP_BIT = 0x00010000; -#ifdef GL_ES -precision lowp float; -#endif <@endif@> diff --git a/libraries/graphics/src/graphics/SphericalHarmonics.shared.slh b/libraries/graphics/src/graphics/SphericalHarmonics.shared.slh index 312824c5a3..72a505fa25 100644 --- a/libraries/graphics/src/graphics/SphericalHarmonics.shared.slh +++ b/libraries/graphics/src/graphics/SphericalHarmonics.shared.slh @@ -15,16 +15,24 @@ #define SphericalHarmonicsConstRef SphericalHarmonics +#ifndef PRECISIONQ +#ifdef GL_ES +#define PRECISIONQ highp +#else +#define PRECISIONQ +#endif +#endif + struct SphericalHarmonics { - vec4 L00; - vec4 L1m1; - vec4 L10; - vec4 L11; - vec4 L2m2; - vec4 L2m1; - vec4 L20; - vec4 L21; - vec4 L22; + PRECISIONQ vec4 L00; + PRECISIONQ vec4 L1m1; + PRECISIONQ vec4 L10; + PRECISIONQ vec4 L11; + PRECISIONQ vec4 L2m2; + PRECISIONQ vec4 L2m1; + PRECISIONQ vec4 L20; + PRECISIONQ vec4 L21; + PRECISIONQ vec4 L22; }; vec4 sphericalHarmonics_evalSphericalLight(SphericalHarmonicsConstRef sh, vec3 direction) { From 1b08ce6ac209bb1240d5bfb6189d4bf805c879b7 Mon Sep 17 00:00:00 2001 From: Gabriel Calero Date: Thu, 15 Feb 2018 15:31:02 -0300 Subject: [PATCH 16/29] Set default location to pikachu/160.45,-2.45,159.106 for non-stable branches --- libraries/networking/src/AddressManager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/networking/src/AddressManager.cpp b/libraries/networking/src/AddressManager.cpp index 322ca7167a..65a5e0c837 100644 --- a/libraries/networking/src/AddressManager.cpp +++ b/libraries/networking/src/AddressManager.cpp @@ -32,7 +32,7 @@ #if USE_STABLE_GLOBAL_SERVICES const QString DEFAULT_HIFI_ADDRESS = "hifi://welcome/hello"; #else -const QString DEFAULT_HIFI_ADDRESS = "hifi://dev-welcome/hello"; +const QString DEFAULT_HIFI_ADDRESS = "hifi://pikachu/160.45,-2.45,159.106"; #endif const QString ADDRESS_MANAGER_SETTINGS_GROUP = "AddressManager"; From 82f9416b3f071b2a07e3b49ca7c9939e34f9a593 Mon Sep 17 00:00:00 2001 From: Gabriel Calero Date: Thu, 15 Feb 2018 17:39:01 -0300 Subject: [PATCH 17/29] Restore default precision qualifier (needed for pixel) --- libraries/graphics/src/graphics/Material.slh | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/libraries/graphics/src/graphics/Material.slh b/libraries/graphics/src/graphics/Material.slh index bdb87ff014..f9f4144748 100644 --- a/libraries/graphics/src/graphics/Material.slh +++ b/libraries/graphics/src/graphics/Material.slh @@ -14,6 +14,7 @@ // The material values (at least the material key) must be precisely bitwise accurate // to what is provided by the uniform buffer, or the material key has the wrong bits #ifdef GL_ES +precision highp float; #define BITFIELD highp int #define PRECISIONQ highp #else @@ -70,5 +71,7 @@ const BITFIELD OCCLUSION_MAP_BIT = 0x00004000; const BITFIELD LIGHTMAP_MAP_BIT = 0x00008000; const BITFIELD SCATTERING_MAP_BIT = 0x00010000; - +#ifdef GL_ES +precision lowp float; +#endif <@endif@> From 5f1fc37684d1cc0da70a11deab3831195cba98ef Mon Sep 17 00:00:00 2001 From: Cristian Luis Duarte Date: Fri, 16 Feb 2018 18:29:22 -0300 Subject: [PATCH 18/29] Android - Support display with smaller resolution than 1440p for Go To window --- .../qml/+android/AddressBarDialog.qml | 40 +++++++++---------- .../qml/hifi/+android/HifiConstants.qml | 32 ++++++++------- 2 files changed, 37 insertions(+), 35 deletions(-) diff --git a/interface/resources/qml/+android/AddressBarDialog.qml b/interface/resources/qml/+android/AddressBarDialog.qml index 0b12301561..8033840f6c 100644 --- a/interface/resources/qml/+android/AddressBarDialog.qml +++ b/interface/resources/qml/+android/AddressBarDialog.qml @@ -75,33 +75,33 @@ Item { HifiStyles.RalewayRegular { id: notice text: "YOUR LOCATION" - font.pixelSize: hifi.fonts.pixelSize * 2.15; + font.pixelSize: (hifi.fonts.pixelSize * 2.15)*(android.dimen.atLeast1440p?1:.75); color: "#2CD7FF" anchors { bottom: addressBackground.top - bottomMargin: 45 + bottomMargin: android.dimen.atLeast1440p?45:34 left: addressBackground.left - leftMargin: 60 + leftMargin: android.dimen.atLeast1440p?60:45 } } - property int inputAreaHeight: 210 + property int inputAreaHeight: android.dimen.atLeast1440p?210:156 property int inputAreaStep: (height - inputAreaHeight) / 2 ToolbarButton { id: homeButton - y: 280 + y: android.dimen.atLeast1440p?280:210 imageURL: "../../icons/home.svg" onClicked: { addressBarDialog.loadHome(); bar.shown = false; } anchors { - leftMargin: 75 + leftMargin: android.dimen.atLeast1440p?75:56 left: parent.left } - size: 150 + size: android.dimen.atLeast1440p?150:150//112 } ToolbarButton { @@ -110,10 +110,10 @@ Item { onClicked: addressBarDialog.loadBack(); anchors { left: homeButton.right - leftMargin: 70 + leftMargin: android.dimen.atLeast1440p?70:52 verticalCenter: homeButton.verticalCenter } - size: 150 + size: android.dimen.atLeast1440p?150:150 } ToolbarButton { id: forwardArrow; @@ -121,10 +121,10 @@ Item { onClicked: addressBarDialog.loadForward(); anchors { left: backArrow.right - leftMargin: 60 + leftMargin: android.dimen.atLeast1440p?60:45 verticalCenter: homeButton.verticalCenter } - size: 150 + size: android.dimen.atLeast1440p?150:150 } HifiStyles.FiraSansRegular { @@ -139,24 +139,24 @@ Item { Rectangle { id: addressBackground - x: 780 - y: 280 - width: 1440 - height: 150 + x: android.dimen.atLeast1440p?780:585 + y: android.dimen.atLeast1440p?280:235 // tweaking by hand + width: android.dimen.atLeast1440p?1270:952 + height: android.dimen.atLeast1440p?150:112 color: "#FFFFFF" } TextInput { id: addressLine focus: true - x: 870 - y: 450 - width: 1350 - height: 120 + x: android.dimen.atLeast1440p?870:652 + y: android.dimen.atLeast1440p?300:245 // tweaking by hand + width: android.dimen.atLeast1440p?1200:900 + height: android.dimen.atLeast1440p?120:90 inputMethodHints: Qt.ImhNoPredictiveText //helperText: "Hint is here" anchors { - verticalCenter: homeButton.verticalCenter + //verticalCenter: addressBackground.verticalCenter } font.pixelSize: hifi.fonts.pixelSize * 3.75 onTextChanged: { diff --git a/interface/resources/qml/hifi/+android/HifiConstants.qml b/interface/resources/qml/hifi/+android/HifiConstants.qml index f74cc554aa..fbdf60dcb0 100644 --- a/interface/resources/qml/hifi/+android/HifiConstants.qml +++ b/interface/resources/qml/hifi/+android/HifiConstants.qml @@ -10,6 +10,7 @@ // import QtQuick 2.4 +import QtQuick.Window 2.2 Item { @@ -20,26 +21,27 @@ Item { Item { id: dimen - readonly property real windowLessWidth: 126*3 - readonly property real windowLessHeight: 64*3 + 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: 276 + readonly property real headerHeight: atLeast1440p?276:207 - readonly property real headerIconPosX: 90 - readonly property real headerIconPosY: 108 - readonly property real headerIconWidth: 111 - readonly property real headerIconHeight: 111 - readonly property real headerIconTitleDistance: 151 + 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: 150 - readonly property real headerHideHeight: 150 - readonly property real headerHideRightMargin: 110 - readonly property real headerHideTopMargin: 90 - readonly property real headerHideIconWidth: 70 - readonly property real headerHideIconHeight: 45 - readonly property real headerHideTextTopMargin: 36 + 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 From 779e45aae749185b350c60a4bb011a75f644299d Mon Sep 17 00:00:00 2001 From: Gabriel Calero Date: Mon, 19 Feb 2018 16:12:41 -0300 Subject: [PATCH 19/29] Restore avatar overlays --- .../render-utils/src/RenderForwardTask.cpp | 2 + scripts/system/+android/radar.js | 51 ++++++++++--------- scripts/system/+android/uniqueColor.js | 10 +++- 3 files changed, 38 insertions(+), 25 deletions(-) diff --git a/libraries/render-utils/src/RenderForwardTask.cpp b/libraries/render-utils/src/RenderForwardTask.cpp index f587cccd1a..fa3c9c86ab 100755 --- a/libraries/render-utils/src/RenderForwardTask.cpp +++ b/libraries/render-utils/src/RenderForwardTask.cpp @@ -37,6 +37,7 @@ using namespace render; extern void initForwardPipelines(ShapePlumber& plumber, const render::ShapePipeline::BatchSetter& batchSetter, const render::ShapePipeline::ItemSetter& itemSetter); +extern void initOverlay3DPipelines(render::ShapePlumber& plumber, bool depthTest = false); void RenderForwardTask::build(JobModel& task, const render::Varying& input, render::Varying& output) { auto items = input.get(); @@ -45,6 +46,7 @@ void RenderForwardTask::build(JobModel& task, const render::Varying& input, rend // Prepare the ShapePipelines ShapePlumberPointer shapePlumber = std::make_shared(); initForwardPipelines(*shapePlumber, fadeEffect->getBatchSetter(), fadeEffect->getItemUniformSetter()); + initOverlay3DPipelines(*shapePlumber); // Extract opaques / transparents / lights / metas / overlays / background const auto& opaques = items.get0()[RenderFetchCullSortTask::OPAQUE_SHAPE]; diff --git a/scripts/system/+android/radar.js b/scripts/system/+android/radar.js index baf4441749..9682962eda 100644 --- a/scripts/system/+android/radar.js +++ b/scripts/system/+android/radar.js @@ -31,10 +31,11 @@ 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 = 40; var RADAR_HEIGHT_MIN_PLUS_AVATAR = 2; -var RADAR_CAMERA_DISTANCE_TO_ICONS = 0.5; // Icons are near the camera to prevent the LOD manager dismissing them +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 = 38; var AVATAR_DISPLAY_NAME_CHAR_WIDTH = 18; +var AVATAR_DISPLAY_NAME_FONT_SIZE = 350; var lastDragAt; var lastDeltaDrag; @@ -734,14 +735,15 @@ function saveAvatarData(QUuid) { if (avatarsData[QUuid] != undefined) { avatarsData[QUuid].position = avat.position; } else { - var avatarIcon = Overlays.addOverlay("image3d", { - subImage: { x: 0, y: 0, width: 150, height: 142}, - url: getAvatarIconForUser(QUuid), - dimensions: ICON_ENTITY_DEFAULT_DIMENSIONS, - visible: false, - ignoreRayIntersection: false, - orientation: Quat.fromPitchYawRollDegrees(-90,0,0) - }); + var avatarIcon = Overlays.addOverlay("circle3d", { + color: uniqueColor.convertHexToRGB(uniqueColor.getColor(QUuid)), + dimensions: ICON_ENTITY_DEFAULT_DIMENSIONS, + rotation: Quat.fromPitchYawRollDegrees(90, 0, 0), + innerRadius: 1.6, + outerRadius: 2, + isSolid: true, + visible: false + }); var needRefresh = !avat || !avat.displayName; var displayName = avat && avat.displayName ? avat.displayName : "Unknown"; @@ -752,7 +754,7 @@ function saveAvatarData(QUuid) { color: { red: 255, green: 255, blue: 255}, backgroundAlpha: 0.0, textRaiseColor: { red: 0, green: 0, blue: 0}, - font: {size: 68, bold: true}, + font: {size: AVATAR_DISPLAY_NAME_FONT_SIZE, bold: true}, visible: false, text: displayName, textAlignCenter: true @@ -833,15 +835,16 @@ function renderMyAvatarIcon() { 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.sessionUUID) { - myAvatarIcon = Overlays.addOverlay("image3d", { - subImage: { x: 0, y: 0, width: 150, height: 142}, - url: getAvatarIconForUser(MyAvatar.sessionUUID), - dimensions: ICON_ENTITY_DEFAULT_DIMENSIONS, - visible: false, - ignoreRayIntersection: false, - orientation: Quat.fromPitchYawRollDegrees(-90,0,0) - }); + 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.6, + outerRadius: 2, + isSolid: true, + visible: false + }); } if (!myAvatarName) { @@ -851,7 +854,7 @@ function renderMyAvatarIcon() { textAlignCenter: true, color: { red: 255, green: 255, blue: 255}, backgroundAlpha: 0.0, - font: {size: 68, bold: true}, + font: {size: AVATAR_DISPLAY_NAME_FONT_SIZE, bold: true}, textRaiseColor: { red: 0, green: 0, blue: 0}, visible: false, text: "Me" @@ -907,13 +910,13 @@ function renderAllOthersAvatarIcons() { if (AvatarList.getAvatar(QUuid) != null) { avatarPos = AvatarList.getAvatar(QUuid).position; - var cameraPos = Camera.position; - var p1 = findLineToHeightIntersectionCoords(avatarPos.x, avatarPos.y, avatarPos.z, + 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); + 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, diff --git a/scripts/system/+android/uniqueColor.js b/scripts/system/+android/uniqueColor.js index c296b6c87d..a2741642d2 100644 --- a/scripts/system/+android/uniqueColor.js +++ b/scripts/system/+android/uniqueColor.js @@ -41,6 +41,14 @@ function getColorForId(uuid) { module.exports = { getColor: function(id) { - return getColorForId(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; } }; From 94a8aad7128957950f75ba31ad019adacf6638fc Mon Sep 17 00:00:00 2001 From: Gabriel Calero Date: Mon, 19 Feb 2018 16:52:51 -0300 Subject: [PATCH 20/29] Avoid keyboard appearing on startup --- .../qml/desktop/+android/FocusHack.qml | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 interface/resources/qml/desktop/+android/FocusHack.qml diff --git a/interface/resources/qml/desktop/+android/FocusHack.qml b/interface/resources/qml/desktop/+android/FocusHack.qml new file mode 100644 index 0000000000..db703bab52 --- /dev/null +++ b/interface/resources/qml/desktop/+android/FocusHack.qml @@ -0,0 +1,24 @@ +// +// 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() + } + +} From 12770142fa44cd26cbd56078d8c001d676c7b55d Mon Sep 17 00:00:00 2001 From: Gabriel Calero Date: Mon, 12 Feb 2018 16:45:44 -0300 Subject: [PATCH 21/29] Add bubble button to android bottombar --- .../resources/icons/+android/bubble-a.svg | 1022 +++++++++++++++++ .../resources/icons/+android/bubble-i.svg | 1022 +++++++++++++++++ scripts/system/+android/bottombar.js | 20 + 3 files changed, 2064 insertions(+) create mode 100644 interface/resources/icons/+android/bubble-a.svg create mode 100644 interface/resources/icons/+android/bubble-i.svg diff --git a/interface/resources/icons/+android/bubble-a.svg b/interface/resources/icons/+android/bubble-a.svg new file mode 100644 index 0000000000..fccc9c07ff --- /dev/null +++ b/interface/resources/icons/+android/bubble-a.svg @@ -0,0 +1,1022 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/interface/resources/icons/+android/bubble-i.svg b/interface/resources/icons/+android/bubble-i.svg new file mode 100644 index 0000000000..80c97d2704 --- /dev/null +++ b/interface/resources/icons/+android/bubble-i.svg @@ -0,0 +1,1022 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/scripts/system/+android/bottombar.js b/scripts/system/+android/bottombar.js index 72ba1c1e50..5f82886c8a 100644 --- a/scripts/system/+android/bottombar.js +++ b/scripts/system/+android/bottombar.js @@ -15,6 +15,7 @@ var bottombar; var bottomHudOptionsBar; var gotoBtn; var avatarBtn; +var bubbleBtn; var loginBtn; var gotoScript = Script.require('./goto.js'); @@ -117,6 +118,25 @@ function setupBottomBar() { } }); + bubbleBtn = bottombar.addButton({ + icon: "icons/bubble-i.svg", + activeIcon: "icons/bubble-a.svg", + bgOpacity: 0, + hoverBgOpacity: 0, + activeBgOpacity: 0, + activeHoverBgOpacity: 0, + height: 240, + width: 294, + iconSize: 108, + textSize: 45, + text: "BUBBLE" + }); + + bubbleBtn.clicked.connect(function() { + Users.toggleIgnoreRadius(); + bubbleBtn.editProperties({isActive: Users.getIgnoreRadiusEnabled()}); + }); + loginBtn = bottombar.addButton({ icon: "icons/login-i.svg", activeIcon: "icons/login-a.svg", From c19664e3ca8010113a28d450e0326c85775c0a70 Mon Sep 17 00:00:00 2001 From: Gabriel Calero Date: Tue, 20 Feb 2018 12:01:10 -0300 Subject: [PATCH 22/29] Remove the shrink cap in radar mode --- scripts/system/+android/radar.js | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/scripts/system/+android/radar.js b/scripts/system/+android/radar.js index 9682962eda..bd2299f27d 100644 --- a/scripts/system/+android/radar.js +++ b/scripts/system/+android/radar.js @@ -29,7 +29,7 @@ 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 = 40; +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 @@ -811,19 +811,23 @@ function avatarRemoved(QUuid) { ********************************************************************************************************/ var myAvatarIcon; var myAvatarName; - +function distanceForCameraHeight(h) { + if (h < 10) return 1; + if (h < 50) return 2; + 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, - Camera.position.y - RADAR_CAMERA_DISTANCE_TO_ICONS); + commonY); if (!iconPos) { printd("avatarmy icon pos null"); return;} var iconDimensions = avatarIconPlaneDimensions(); var avatarPos = MyAvatar.position; var cameraPos = Camera.position; - var commonY = Camera.position.y - RADAR_CAMERA_DISTANCE_TO_ICONS; var borderPoints = [ computePointAtPlaneY(0, 0, commonY), computePointAtPlaneY(Window.innerWidth, Window.innerHeight, commonY) @@ -899,7 +903,7 @@ function hideAllAvatarIcons() { function renderAllOthersAvatarIcons() { var avatarPos; var iconDimensions = avatarIconPlaneDimensions(); - var commonY = Camera.position.y - RADAR_CAMERA_DISTANCE_TO_ICONS; + var commonY = Camera.position.y - distanceForCameraHeight(Camera.position.y); var borderPoints = [ computePointAtPlaneY(0, 0, commonY), computePointAtPlaneY(Window.innerWidth, Window.innerHeight, commonY) @@ -921,7 +925,7 @@ function renderAllOthersAvatarIcons() { 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, - Camera.position.y - RADAR_CAMERA_DISTANCE_TO_ICONS); + commonY); if (!iconPos) { print ("avatar icon pos bad for " + QUuid); continue; } if (avatarsData[QUuid].needRefresh) { var avat = AvatarList.getAvatar(QUuid); From 4839a8c72049a8968c13814ed42c8b61b9b27164 Mon Sep 17 00:00:00 2001 From: Gabriel Calero Date: Tue, 20 Feb 2018 12:59:06 -0300 Subject: [PATCH 23/29] Remove Forgot labels in login dialog --- .../LoginDialog/+android/LinkAccountBody.qml | 45 ++----------------- 1 file changed, 4 insertions(+), 41 deletions(-) diff --git a/interface/resources/qml/LoginDialog/+android/LinkAccountBody.qml b/interface/resources/qml/LoginDialog/+android/LinkAccountBody.qml index 8f76c6ed99..7eced0c751 100644 --- a/interface/resources/qml/LoginDialog/+android/LinkAccountBody.qml +++ b/interface/resources/qml/LoginDialog/+android/LinkAccountBody.qml @@ -117,62 +117,25 @@ Item { } spacing: hifi.dimensions.contentSpacing.y / 2 - Row { - spacing: hifi.dimensions.contentSpacing.x - TextField { id: usernameField anchors { - verticalCenter: parent.verticalCenter + horizontalCenter: parent.horizontalCenter } - width: 780 - + width: 1080 placeholderText: qsTr("Username or Email") } - ShortcutText { - anchors { - verticalCenter: parent.verticalCenter - } - - text: "Forgot Username?" - - verticalAlignment: Text.AlignVCenter - horizontalAlignment: Text.AlignHCenter - linkColor: hifi.colors.blueAccent - - onLinkActivated: loginDialog.openUrl(link) - } - } - Row { - spacing: hifi.dimensions.contentSpacing.x - TextField { id: passwordField anchors { - verticalCenter: parent.verticalCenter + horizontalCenter: parent.horizontalCenter } - width: 780 + width: 1080 placeholderText: qsTr("Password") echoMode: TextInput.Password } - - ShortcutText { - anchors { - verticalCenter: parent.verticalCenter - } - - text: "Forgot Password?" - - verticalAlignment: Text.AlignVCenter - horizontalAlignment: Text.AlignHCenter - linkColor: hifi.colors.blueAccent - - onLinkActivated: loginDialog.openUrl(link) - } - } - } InfoItem { From 5e3ad3fe9a3b05cb3e43a0f621c9f3d77d8510f8 Mon Sep 17 00:00:00 2001 From: Gabriel Calero Date: Tue, 20 Feb 2018 17:20:48 -0300 Subject: [PATCH 24/29] Disable VPAD on bottom bar tapping and modes bar tapping --- .../resources/qml/hifi/+android/bottombar.qml | 9 +++++++++ .../resources/qml/hifi/+android/button.qml | 4 ++++ .../resources/qml/hifi/+android/modesbar.qml | 6 ++++++ .../TouchscreenVirtualPadDevice.cpp | 18 ++++++++++++++++++ 4 files changed, 37 insertions(+) diff --git a/interface/resources/qml/hifi/+android/bottombar.qml b/interface/resources/qml/hifi/+android/bottombar.qml index 2a34b7fe19..e861d441d9 100644 --- a/interface/resources/qml/hifi/+android/bottombar.qml +++ b/interface/resources/qml/hifi/+android/bottombar.qml @@ -41,6 +41,15 @@ Item { Styles.HifiConstants { id: hifi } HifiConstants { id: android } + MouseArea { + anchors.fill: parent + onEntered: { + Controller.setVPadEnabled(false); + } + onExited: { + Controller.setVPadEnabled(true); + } + } Rectangle { id: background diff --git a/interface/resources/qml/hifi/+android/button.qml b/interface/resources/qml/hifi/+android/button.qml index ec7af2ab92..4822b6bf33 100644 --- a/interface/resources/qml/hifi/+android/button.qml +++ b/interface/resources/qml/hifi/+android/button.qml @@ -40,6 +40,8 @@ Item { property bool isActive: false signal clicked() + signal entered() + signal exited() onIsActiveChanged: { if (button.isEntered) { @@ -118,6 +120,7 @@ Item { } onEntered: { button.isEntered = true; + button.entered(); if (button.isActive) { button.state = "hover active state"; } else { @@ -126,6 +129,7 @@ Item { } onExited: { button.isEntered = false; + button.exited() if (button.isActive) { button.state = "active state"; } else { diff --git a/interface/resources/qml/hifi/+android/modesbar.qml b/interface/resources/qml/hifi/+android/modesbar.qml index fe71314ece..552ca80778 100644 --- a/interface/resources/qml/hifi/+android/modesbar.qml +++ b/interface/resources/qml/hifi/+android/modesbar.qml @@ -40,6 +40,12 @@ Item { var keys = Object.keys(properties).forEach(function (key) { button[key] = properties[key]; }); + button.entered.connect(function() { + Controller.setVPadEnabled(false); + }); + button.exited.connect(function() { + Controller.setVPadEnabled(true); + }); return button; } else if( component.status == Component.Error) { console.log("Load button errors " + component.errorString()); diff --git a/libraries/input-plugins/src/input-plugins/TouchscreenVirtualPadDevice.cpp b/libraries/input-plugins/src/input-plugins/TouchscreenVirtualPadDevice.cpp index 8e1e6c2fba..53683870df 100644 --- a/libraries/input-plugins/src/input-plugins/TouchscreenVirtualPadDevice.cpp +++ b/libraries/input-plugins/src/input-plugins/TouchscreenVirtualPadDevice.cpp @@ -127,6 +127,10 @@ void TouchscreenVirtualPadDevice::debugPoints(const QTouchEvent* event, QString void TouchscreenVirtualPadDevice::touchBeginEvent(const QTouchEvent* event) { // touch begin here is a big begin -> begins both pads? maybe it does nothing debugPoints(event, " BEGIN ++++++++++++++++"); + auto& virtualPadManager = VirtualPad::Manager::instance(); + if (!virtualPadManager.isEnabled()) { + return; + } KeyboardMouseDevice::enableTouch(false); QScreen* eventScreen = event->window()->screen(); _screenWidthCenter = eventScreen->size().width() / 2; @@ -138,6 +142,10 @@ void TouchscreenVirtualPadDevice::touchBeginEvent(const QTouchEvent* event) { } void TouchscreenVirtualPadDevice::touchEndEvent(const QTouchEvent* event) { + auto& virtualPadManager = VirtualPad::Manager::instance(); + if (!virtualPadManager.isEnabled()) { + return; + } // touch end here is a big reset -> resets both pads _touchPointCount = 0; KeyboardMouseDevice::enableTouch(true); @@ -148,6 +156,12 @@ void TouchscreenVirtualPadDevice::touchEndEvent(const QTouchEvent* event) { } void TouchscreenVirtualPadDevice::touchUpdateEvent(const QTouchEvent* event) { + auto& virtualPadManager = VirtualPad::Manager::instance(); + if (!virtualPadManager.isEnabled()) { + touchLeftEnd(); + touchRightEnd(); + return; + } _touchPointCount = event->touchPoints().count(); const QList& tPoints = event->touchPoints(); @@ -224,6 +238,10 @@ void TouchscreenVirtualPadDevice::touchRightEnd() { } void TouchscreenVirtualPadDevice::touchGestureEvent(const QGestureEvent* event) { + auto& virtualPadManager = VirtualPad::Manager::instance(); + if (!virtualPadManager.isEnabled()) { + return; + } if (QGesture* gesture = event->gesture(Qt::PinchGesture)) { QPinchGesture* pinch = static_cast(gesture); _pinchScale = pinch->totalScaleFactor(); From bd9d8ac4f99b4ad8f8a8656d98745fec43958fbb Mon Sep 17 00:00:00 2001 From: Gabriel Calero Date: Tue, 20 Feb 2018 19:07:41 -0300 Subject: [PATCH 25/29] Tune radar styles --- libraries/networking/src/AddressManager.cpp | 2 +- scripts/system/+android/radar.js | 17 +++++++++-------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/libraries/networking/src/AddressManager.cpp b/libraries/networking/src/AddressManager.cpp index 65a5e0c837..30ed20de68 100644 --- a/libraries/networking/src/AddressManager.cpp +++ b/libraries/networking/src/AddressManager.cpp @@ -107,7 +107,7 @@ void AddressManager::loadSettings(const QString& lookupString) { #if defined(USE_GLES) && defined(Q_OS_WIN) handleUrl(QUrl("hifi://127.0.0.0"), LookupTrigger::StartupFromSettings); #elif defined(Q_OS_ANDROID) - handleUrl(QUrl("hifi://dev-android"), LookupTrigger::StartupFromSettings); + handleUrl(QUrl(DEFAULT_HIFI_ADDRESS), LookupTrigger::StartupFromSettings); #else if (lookupString.isEmpty()) { handleUrl(currentAddressHandle.get(), LookupTrigger::StartupFromSettings); diff --git a/scripts/system/+android/radar.js b/scripts/system/+android/radar.js index bd2299f27d..54e1e51945 100644 --- a/scripts/system/+android/radar.js +++ b/scripts/system/+android/radar.js @@ -33,9 +33,9 @@ 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 = 38; -var AVATAR_DISPLAY_NAME_CHAR_WIDTH = 18; -var AVATAR_DISPLAY_NAME_FONT_SIZE = 350; +var AVATAR_DISPLAY_NAME_HEIGHT = 106; +var AVATAR_DISPLAY_NAME_CHAR_WIDTH = 48; +var AVATAR_DISPLAY_NAME_FONT_SIZE = 50; var lastDragAt; var lastDeltaDrag; @@ -739,7 +739,7 @@ function saveAvatarData(QUuid) { color: uniqueColor.convertHexToRGB(uniqueColor.getColor(QUuid)), dimensions: ICON_ENTITY_DEFAULT_DIMENSIONS, rotation: Quat.fromPitchYawRollDegrees(90, 0, 0), - innerRadius: 1.6, + innerRadius: 1.8, outerRadius: 2, isSolid: true, visible: false @@ -812,8 +812,9 @@ function avatarRemoved(QUuid) { var myAvatarIcon; var myAvatarName; function distanceForCameraHeight(h) { - if (h < 10) return 1; - if (h < 50) return 2; + if (h < 30) return 1; + if (h < 40) return 2; + if (h < 50) return 2.5; return 5; } function renderMyAvatarIcon() { @@ -844,7 +845,7 @@ function renderMyAvatarIcon() { color: uniqueColor.convertHexToRGB(uniqueColor.getColor(MyAvatar.SELF_ID)), dimensions: ICON_ENTITY_DEFAULT_DIMENSIONS, rotation: Quat.fromPitchYawRollDegrees(90, 0, 0), - innerRadius: 1.6, + innerRadius: 1.8, outerRadius: 2, isSolid: true, visible: false @@ -853,7 +854,7 @@ function renderMyAvatarIcon() { if (!myAvatarName) { myAvatarName = Overlays.addOverlay("text", { - width: 40, + width: 100, height: AVATAR_DISPLAY_NAME_HEIGHT, textAlignCenter: true, color: { red: 255, green: 255, blue: 255}, From 00958965bc2da6e56a86cc8608daf8140d41af2e Mon Sep 17 00:00:00 2001 From: Cristian Luis Duarte Date: Tue, 20 Feb 2018 19:25:41 -0300 Subject: [PATCH 26/29] Android- Center login dialog in both big (Pixel XL, Huawei Mate 9 Pro) and smaller screens (Pixel) --- interface/resources/qml/+android/LoginDialog.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/resources/qml/+android/LoginDialog.qml b/interface/resources/qml/+android/LoginDialog.qml index c60864cd1d..9eb2c74147 100644 --- a/interface/resources/qml/+android/LoginDialog.qml +++ b/interface/resources/qml/+android/LoginDialog.qml @@ -53,7 +53,7 @@ ModalWindow { Component.onCompleted: { this.anchors.centerIn = undefined; this.y=150; - this.x=450; + this.x=(parent.width - this.width)/2; } Keys.onPressed: { From 6411d1b35b009ac97385fe78f5229259d5910d35 Mon Sep 17 00:00:00 2001 From: Cristian Luis Duarte Date: Wed, 21 Feb 2018 16:52:57 -0300 Subject: [PATCH 27/29] Android - Avoid overlapping between hint and input in Go To by clearing text when closing the window --- interface/resources/qml/+android/AddressBarDialog.qml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/interface/resources/qml/+android/AddressBarDialog.qml b/interface/resources/qml/+android/AddressBarDialog.qml index 8033840f6c..50552f0d3b 100644 --- a/interface/resources/qml/+android/AddressBarDialog.qml +++ b/interface/resources/qml/+android/AddressBarDialog.qml @@ -33,7 +33,8 @@ Item { bar.visible = shown; sendToScript({method: 'shownChanged', params: { shown: shown }}); if (shown) { - updateLocationText(false); + addressLine.text=""; + updateLocationText(addressLine.text.length > 0); } } @@ -43,7 +44,7 @@ Item { } Component.onCompleted: { - updateLocationText(false); + updateLocationText(addressLine.text.length > 0); } HifiConstants { id: hifi } @@ -130,7 +131,7 @@ Item { HifiStyles.FiraSansRegular { id: location; font.pixelSize: addressLine.font.pixelSize; - color: "gray"; + color: "lightgray"; clip: true; anchors.fill: addressLine; visible: addressLine.text.length === 0 From f2ce3d026874b2ae28f9fcc426e07c0ad9c5f6a3 Mon Sep 17 00:00:00 2001 From: Gabriel Calero Date: Wed, 21 Feb 2018 20:10:03 -0300 Subject: [PATCH 28/29] Fix focus hack. Add QML debug info --- interface/resources/qml/desktop/+android/FocusHack.qml | 2 ++ interface/resources/qml/windows/Frame.qml | 2 +- libraries/ui/src/OffscreenUi.cpp | 2 ++ 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/interface/resources/qml/desktop/+android/FocusHack.qml b/interface/resources/qml/desktop/+android/FocusHack.qml index db703bab52..38253fdec1 100644 --- a/interface/resources/qml/desktop/+android/FocusHack.qml +++ b/interface/resources/qml/desktop/+android/FocusHack.qml @@ -21,4 +21,6 @@ FocusScope { onActiveFocusChanged: root.destroy() } + function start() { + } } diff --git a/interface/resources/qml/windows/Frame.qml b/interface/resources/qml/windows/Frame.qml index c3b8399e01..52ca53b818 100644 --- a/interface/resources/qml/windows/Frame.qml +++ b/interface/resources/qml/windows/Frame.qml @@ -43,7 +43,7 @@ Item { Text { id: debugZ visible: DebugQML - text: window ? "Z: " + window.z : "" + text: (window ? "Z: " + window.z : "") + DebugQMLFile y: window ? window.height + 4 : 0 } diff --git a/libraries/ui/src/OffscreenUi.cpp b/libraries/ui/src/OffscreenUi.cpp index 221f5013bf..287978bbd3 100644 --- a/libraries/ui/src/OffscreenUi.cpp +++ b/libraries/ui/src/OffscreenUi.cpp @@ -658,8 +658,10 @@ void OffscreenUi::createDesktop(const QUrl& url) { #ifdef DEBUG getSurfaceContext()->setContextProperty("DebugQML", QVariant(true)); + getSurfaceContext()->setContextProperty("DebugQMLFile", QVariant(url.toString())); #else getSurfaceContext()->setContextProperty("DebugQML", QVariant(false)); + getSurfaceContext()->setContextProperty("DebugQMLFile", QVariant("")); #endif load(url, [=](QQmlContext* context, QObject* newObject) { From 2d4bb3bad39c10f95590a9f25fc003df4bb035d8 Mon Sep 17 00:00:00 2001 From: Gabriel Calero Date: Wed, 21 Feb 2018 20:23:54 -0300 Subject: [PATCH 29/29] Change color to QML debug info --- interface/resources/qml/windows/Frame.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/interface/resources/qml/windows/Frame.qml b/interface/resources/qml/windows/Frame.qml index 52ca53b818..4391c902ed 100644 --- a/interface/resources/qml/windows/Frame.qml +++ b/interface/resources/qml/windows/Frame.qml @@ -43,6 +43,7 @@ Item { Text { id: debugZ visible: DebugQML + color: "red" text: (window ? "Z: " + window.z : "") + DebugQMLFile y: window ? window.height + 4 : 0 }