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();