From 99a6e1f86c0f7d1498130c2f9dcae2d71a16b19c Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Fri, 24 Apr 2015 13:21:21 -0700 Subject: [PATCH] Breaking up offscreen UI work --- interface/CMakeLists.txt | 2 +- .../resources/fonts/fontawesome-webfont.ttf | Bin 0 -> 122092 bytes interface/resources/qml/AddressBarDialog.qml | 11 +- interface/resources/qml/Browser.qml | 21 +- interface/resources/qml/CustomButton.qml | 23 -- interface/resources/qml/CustomTextArea.qml | 10 - interface/resources/qml/HifiAction.qml | 20 + interface/resources/qml/HifiMenu.qml | 272 +++++++++++++ interface/resources/qml/Icon.qml | 8 - interface/resources/qml/LoginDialog.qml | 30 +- interface/resources/qml/MarketplaceDialog.qml | 50 +++ interface/resources/qml/MessageDialog.qml | 359 ++++++++++++++++++ interface/resources/qml/Palettes.qml | 86 ----- interface/resources/qml/Root.qml | 5 +- interface/resources/qml/RootMenu.qml | 9 + interface/resources/qml/TestDialog.qml | 22 +- interface/resources/qml/TestRoot.qml | 29 +- interface/resources/qml/componentCreation.js | 29 -- interface/resources/qml/controls/Button.qml | 10 + .../{CustomDialog.qml => controls/Dialog.qml} | 241 +++++++----- .../resources/qml/controls/FontAwesome.qml | 16 + .../IconButton.qml} | 0 .../resources/qml/controls/MenuButton.qml | 5 + interface/resources/qml/controls/README.md | 2 + .../{CustomTextEdit.qml => controls/Text.qml} | 4 +- .../{CustomText.qml => controls/TextArea.qml} | 4 +- interface/resources/qml/controls/TextEdit.qml | 7 + .../TextInput.qml} | 4 +- interface/resources/qml/hifiConstants.js | 4 - interface/resources/qml/images/critical.png | Bin 0 -> 253 bytes .../resources/qml/images/information.png | Bin 0 -> 254 bytes interface/resources/qml/images/question.png | Bin 0 -> 257 bytes interface/resources/qml/images/warning.png | Bin 0 -> 224 bytes .../{CustomBorder.qml => styles/Border.qml} | 1 - .../resources/qml/styles/ButtonStyle.qml | 24 ++ .../resources/qml/styles/HifiPalette.qml | 5 + .../resources/qml/styles/IconButtonStyle.qml | 15 + .../resources/qml/styles/MenuButtonStyle.qml | 22 ++ interface/src/Application.cpp | 35 +- interface/src/Application.h | 3 + interface/src/ui/ApplicationOverlay.cpp | 2 +- .../render-utils/src/OffscreenQmlDialog.h | 56 --- libraries/shared/src/PathUtils.cpp | 12 +- libraries/ui/CMakeLists.txt | 12 + libraries/ui/src/HifiMenu.cpp | 279 ++++++++++++++ libraries/ui/src/HifiMenu.h | 83 ++++ libraries/ui/src/MessageDialog.cpp | 142 +++++++ libraries/ui/src/MessageDialog.h | 98 +++++ .../src/OffscreenQmlDialog.cpp | 24 ++ libraries/ui/src/OffscreenQmlDialog.h | 104 +++++ .../{render-utils => ui}/src/OffscreenUi.cpp | 108 ++++-- .../{render-utils => ui}/src/OffscreenUi.h | 97 ++++- tests/render-utils/src/main.cpp | 93 +---- tests/ui/CMakeLists.txt | 15 + tests/ui/main.qml | 161 ++++++++ tests/ui/qml/ButtonPage.qml | 128 +++++++ tests/ui/qml/InputPage.qml | 114 ++++++ tests/ui/qml/ProgressPage.qml | 90 +++++ tests/ui/qml/UI.js | 45 +++ tests/ui/src/main.cpp | 336 ++++++++++++++++ 60 files changed, 2847 insertions(+), 540 deletions(-) create mode 100644 interface/resources/fonts/fontawesome-webfont.ttf delete mode 100644 interface/resources/qml/CustomButton.qml delete mode 100644 interface/resources/qml/CustomTextArea.qml create mode 100644 interface/resources/qml/HifiAction.qml create mode 100644 interface/resources/qml/HifiMenu.qml delete mode 100644 interface/resources/qml/Icon.qml create mode 100644 interface/resources/qml/MarketplaceDialog.qml create mode 100644 interface/resources/qml/MessageDialog.qml create mode 100644 interface/resources/qml/RootMenu.qml delete mode 100644 interface/resources/qml/componentCreation.js create mode 100644 interface/resources/qml/controls/Button.qml rename interface/resources/qml/{CustomDialog.qml => controls/Dialog.qml} (55%) create mode 100644 interface/resources/qml/controls/FontAwesome.qml rename interface/resources/qml/{IconControl.qml => controls/IconButton.qml} (100%) create mode 100644 interface/resources/qml/controls/MenuButton.qml create mode 100644 interface/resources/qml/controls/README.md rename interface/resources/qml/{CustomTextEdit.qml => controls/Text.qml} (54%) rename interface/resources/qml/{CustomText.qml => controls/TextArea.qml} (52%) create mode 100644 interface/resources/qml/controls/TextEdit.qml rename interface/resources/qml/{CustomTextInput.qml => controls/TextInput.qml} (90%) delete mode 100644 interface/resources/qml/hifiConstants.js create mode 100644 interface/resources/qml/images/critical.png create mode 100644 interface/resources/qml/images/information.png create mode 100644 interface/resources/qml/images/question.png create mode 100644 interface/resources/qml/images/warning.png rename interface/resources/qml/{CustomBorder.qml => styles/Border.qml} (99%) create mode 100644 interface/resources/qml/styles/ButtonStyle.qml create mode 100644 interface/resources/qml/styles/HifiPalette.qml create mode 100644 interface/resources/qml/styles/IconButtonStyle.qml create mode 100644 interface/resources/qml/styles/MenuButtonStyle.qml delete mode 100644 libraries/render-utils/src/OffscreenQmlDialog.h create mode 100644 libraries/ui/CMakeLists.txt create mode 100644 libraries/ui/src/HifiMenu.cpp create mode 100644 libraries/ui/src/HifiMenu.h create mode 100644 libraries/ui/src/MessageDialog.cpp create mode 100644 libraries/ui/src/MessageDialog.h rename libraries/{render-utils => ui}/src/OffscreenQmlDialog.cpp (55%) create mode 100644 libraries/ui/src/OffscreenQmlDialog.h rename libraries/{render-utils => ui}/src/OffscreenUi.cpp (83%) rename libraries/{render-utils => ui}/src/OffscreenUi.h (52%) create mode 100644 tests/ui/CMakeLists.txt create mode 100644 tests/ui/main.qml create mode 100644 tests/ui/qml/ButtonPage.qml create mode 100644 tests/ui/qml/InputPage.qml create mode 100644 tests/ui/qml/ProgressPage.qml create mode 100644 tests/ui/qml/UI.js create mode 100644 tests/ui/src/main.cpp diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index 7688036c94..b4e0e3a244 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -128,7 +128,7 @@ target_link_libraries(${TARGET_NAME} ${BULLET_LIBRARIES}) # link required hifi libraries link_hifi_libraries(shared octree environment gpu model fbx networking entities avatars audio audio-client animation script-engine physics - render-utils entities-renderer) + render-utils entities-renderer ui) add_dependency_external_projects(sdl2) diff --git a/interface/resources/fonts/fontawesome-webfont.ttf b/interface/resources/fonts/fontawesome-webfont.ttf new file mode 100644 index 0000000000000000000000000000000000000000..ed9372f8ea0fbaa04f42630a48887e4b38945345 GIT binary patch literal 122092 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 literal 0 HcmV?d00001 diff --git a/interface/resources/qml/AddressBarDialog.qml b/interface/resources/qml/AddressBarDialog.qml index ec1480928a..d12737081e 100644 --- a/interface/resources/qml/AddressBarDialog.qml +++ b/interface/resources/qml/AddressBarDialog.qml @@ -1,10 +1,9 @@ import Hifi 1.0 import QtQuick 2.3 -import QtQuick.Controls 1.2 -import QtQuick.Window 2.2 -import QtQuick.Controls.Styles 1.3 +import "controls" +import "styles" -CustomDialog { +Dialog { title: "Go to..." objectName: "AddressBarDialog" height: 128 @@ -36,14 +35,14 @@ CustomDialog { anchors.margins: parent.margins anchors.topMargin: parent.topMargin - CustomBorder { + Border { height: 64 anchors.left: parent.left anchors.leftMargin: 0 anchors.right: goButton.left anchors.rightMargin: 8 anchors.verticalCenter: parent.verticalCenter - CustomTextInput { + TextInput { id: addressLine anchors.fill: parent helperText: "domain, location, @user, /x,y,z" diff --git a/interface/resources/qml/Browser.qml b/interface/resources/qml/Browser.qml index d7e08fbd97..a439f9114c 100644 --- a/interface/resources/qml/Browser.qml +++ b/interface/resources/qml/Browser.qml @@ -1,12 +1,10 @@ import QtQuick 2.3 import QtQuick.Controls 1.2 -import QtQuick.Window 2.2 -import QtQuick.Dialogs 1.2 -import QtQuick.Controls.Styles 1.3 import QtWebKit 3.0 +import "controls" -CustomDialog { - title: "Test Dlg" +Dialog { + title: "Browser Window" id: testDialog objectName: "Browser" width: 1280 @@ -18,7 +16,6 @@ CustomDialog { anchors.fill: parent anchors.margins: parent.margins anchors.topMargin: parent.topMargin - ScrollView { anchors.fill: parent @@ -30,16 +27,4 @@ CustomDialog { } } - - } - - -/* - -// This is the behavior, and it applies a NumberAnimation to any attempt to set the x property - -MouseArea { - anchors.fill: parent -} -*/ diff --git a/interface/resources/qml/CustomButton.qml b/interface/resources/qml/CustomButton.qml deleted file mode 100644 index ce57d7ce5e..0000000000 --- a/interface/resources/qml/CustomButton.qml +++ /dev/null @@ -1,23 +0,0 @@ -import QtQuick 2.3 -import QtQuick.Controls 1.3 -import QtQuick.Window 2.2 -import QtQuick.Controls.Styles 1.3 - -Button { - SystemPalette { id: myPalette; colorGroup: SystemPalette.Active } - text: "Text" - width: 128 - height: 64 - style: ButtonStyle { - background: CustomBorder { - anchors.fill: parent - } - label: CustomText { - renderType: Text.NativeRendering - verticalAlignment: Text.AlignVCenter - horizontalAlignment: Text.AlignHCenter - text: control.text - color: control.enabled ? myPalette.text : myPalette.dark - } - } -} diff --git a/interface/resources/qml/CustomTextArea.qml b/interface/resources/qml/CustomTextArea.qml deleted file mode 100644 index cf3308e2b7..0000000000 --- a/interface/resources/qml/CustomTextArea.qml +++ /dev/null @@ -1,10 +0,0 @@ -import QtQuick 2.3 -import QtQuick.Controls 1.2 - -TextArea { - font.family: "Helvetica" - font.pointSize: 18 - backgroundVisible: false - readOnly: true -} - diff --git a/interface/resources/qml/HifiAction.qml b/interface/resources/qml/HifiAction.qml new file mode 100644 index 0000000000..0cecff91d7 --- /dev/null +++ b/interface/resources/qml/HifiAction.qml @@ -0,0 +1,20 @@ +import QtQuick 2.4 +import QtQuick.Controls 1.3 + +Action { + property string name + objectName: name + "HifiAction" + text: qsTr(name) + + signal triggeredByName(string name); + signal toggledByName(string name); + + onTriggered: { + triggeredByName(name); + } + + onToggled: { + toggledByName(name, checked); + } +} + diff --git a/interface/resources/qml/HifiMenu.qml b/interface/resources/qml/HifiMenu.qml new file mode 100644 index 0000000000..d86821601a --- /dev/null +++ b/interface/resources/qml/HifiMenu.qml @@ -0,0 +1,272 @@ +import Hifi 1.0 as Hifi +import QtQuick 2.4 +import QtQuick.Controls 1.3 +import QtQuick.Controls.Styles 1.3 +import "controls" +import "styles" + +Hifi.HifiMenu { + id: root + anchors.fill: parent + objectName: "HifiMenu" + enabled: false + opacity: 0.0 + property int animationDuration: 200 + HifiPalette { id: hifiPalette } + z: 10000 + + onEnabledChanged: { + if (enabled && columns.length == 0) { + pushColumn(rootMenu.items); + } + opacity = enabled ? 1.0 : 0.0 + if (enabled) { + forceActiveFocus() + } + } + + // The actual animator + Behavior on opacity { + NumberAnimation { + duration: root.animationDuration + easing.type: Easing.InOutBounce + } + } + + onOpacityChanged: { + visible = (opacity != 0.0); + } + + onVisibleChanged: { + if (!visible) reset(); + } + + + property var menu: Menu {} + property var models: [] + property var columns: [] + property var itemBuilder: Component { + Text { + SystemPalette { id: sp; colorGroup: SystemPalette.Active } + id: thisText + x: 32 + property var source + property var root + property var listViewIndex + property var listView + text: typedText() + height: implicitHeight + width: implicitWidth + color: source.enabled ? "black" : "gray" + + onImplicitWidthChanged: { + if (listView) { + listView.minWidth = Math.max(listView.minWidth, implicitWidth + 64); + listView.recalculateSize(); + } + } + + FontAwesome { + visible: source.type == 1 && source.checkable + x: -32 + text: (source.type == 1 && source.checked) ? "\uF05D" : "\uF10C" + } + + FontAwesome { + visible: source.type == 2 + x: listView.width - 64 + text: "\uF0DA" + } + + + function typedText() { + switch(source.type) { + case 2: + return source.title; + case 1: + return source.text; + case 0: + return "-----" + } + } + + MouseArea { + id: mouseArea + acceptedButtons: Qt.LeftButton + anchors.bottom: parent.bottom + anchors.bottomMargin: 0 + anchors.top: parent.top + anchors.topMargin: 0 + width: listView.width + onClicked: { + listView.currentIndex = listViewIndex + parent.root.selectItem(parent.source); + } + } + } + } + + + property var menuBuilder: Component { + Border { + SystemPalette { id: sysPalette; colorGroup: SystemPalette.Active } + x: root.models.length == 1 ? + (root.width / 2 - width / 2) : + root.columns[root.models.length - 2].x + 60; + anchors.verticalCenter: parent.verticalCenter + border.color: hifiPalette.hifiBlue + color: sysPalette.window + + ListView { + spacing: 6 + property int outerMargin: 8 + property real minWidth: 0 + anchors.fill: parent + anchors.margins: outerMargin + id: listView + height: root.height + currentIndex: -1 + + onCountChanged: { + recalculateSize() + } + + function recalculateSize() { + var newHeight = 0 + var newWidth = minWidth; + for (var i = 0; i < children.length; ++i) { + var item = children[i]; + newHeight += item.height + } + parent.height = newHeight + outerMargin * 2; + parent.width = newWidth + outerMargin * 2 + } + + highlight: Rectangle { + width: listView.minWidth; height: 32 + color: sysPalette.highlight + y: (listView.currentItem) ? listView.currentItem.y : 0; + x: 32 + Behavior on y { + NumberAnimation { + duration: 100 + easing.type: Easing.InOutQuint + } + } + } + + + property int columnIndex: root.models.length - 1 + model: root.models[columnIndex] + delegate: Loader { + id: loader + sourceComponent: root.itemBuilder + Binding { + target: loader.item + property: "root" + value: root + when: loader.status == Loader.Ready + } + Binding { + target: loader.item + property: "source" + value: modelData + when: loader.status == Loader.Ready + } + Binding { + target: loader.item + property: "listViewIndex" + value: index + when: loader.status == Loader.Ready + } + Binding { + target: loader.item + property: "listView" + value: listView + when: loader.status == Loader.Ready + } + } + + } + + } + } + + + function lastColumn() { + return columns[root.columns.length - 1]; + } + + function pushColumn(items) { + models.push(items) + if (columns.length) { + var oldColumn = lastColumn(); + oldColumn.enabled = false; + oldColumn.opacity = 0.5; + } + var newColumn = menuBuilder.createObject(root); + columns.push(newColumn); + newColumn.forceActiveFocus(); + } + + function popColumn() { + if (columns.length > 0) { + var curColumn = columns.pop(); + console.log(curColumn); + curColumn.visible = false; + curColumn.destroy(); + models.pop(); + } + + if (columns.length == 0) { + enabled = false; + return; + } + + curColumn = lastColumn(); + curColumn.enabled = true; + curColumn.opacity = 1.0; + curColumn.forceActiveFocus(); + } + + function selectItem(source) { + switch (source.type) { + case 2: + pushColumn(source.items) + break; + case 1: + source.trigger() + enabled = false + break; + case 0: + break; + } + } + + function reset() { + while (columns.length > 0) { + popColumn(); + } + } + + Keys.onPressed: { + switch (event.key) { + case Qt.Key_Escape: + root.popColumn() + event.accepted = true; + } + } + + MouseArea { + anchors.fill: parent + id: mouseArea + acceptedButtons: Qt.LeftButton | Qt.RightButton + onClicked: { + if (mouse.button == Qt.RightButton) { + root.popColumn(); + } else { + root.enabled = false; + } + } + } +} diff --git a/interface/resources/qml/Icon.qml b/interface/resources/qml/Icon.qml deleted file mode 100644 index 0d60afb2b7..0000000000 --- a/interface/resources/qml/Icon.qml +++ /dev/null @@ -1,8 +0,0 @@ -import QtQuick 1.0 - -Image { -id: icon -width: 64 -height: 64 -source: "file.svg" -} \ No newline at end of file diff --git a/interface/resources/qml/LoginDialog.qml b/interface/resources/qml/LoginDialog.qml index be69b65ef7..b3b926bbe3 100644 --- a/interface/resources/qml/LoginDialog.qml +++ b/interface/resources/qml/LoginDialog.qml @@ -1,12 +1,12 @@ import Hifi 1.0 import QtQuick 2.3 -import QtQuick.Controls 1.2 -import QtQuick.Window 2.2 import QtQuick.Controls.Styles 1.3 -import "hifiConstants.js" as HifiConstants +import "controls" +import "styles" -CustomDialog { +Dialog { title: "Login" + HifiPalette { id: hifiPalette } SystemPalette { id: myPalette; colorGroup: SystemPalette.Active } objectName: "LoginDialog" height: 512 @@ -50,11 +50,11 @@ CustomDialog { source: "../images/hifi-logo.svg" } - CustomBorder { + Border { width: 304 height: 64 anchors.horizontalCenter: parent.horizontalCenter - CustomTextInput { + TextInput { id: username anchors.fill: parent helperText: "Username or Email" @@ -67,11 +67,11 @@ CustomDialog { } } - CustomBorder { + Border { width: 304 height: 64 anchors.horizontalCenter: parent.horizontalCenter - CustomTextInput { + TextInput { id: password anchors.fill: parent echoMode: TextInput.Password @@ -94,7 +94,7 @@ CustomDialog { } } - CustomText { + Text { anchors.horizontalCenter: parent.horizontalCenter textFormat: Text.StyledText width: parent.width @@ -117,7 +117,7 @@ CustomDialog { width: 192 height: 64 anchors.horizontalCenter: parent.horizontalCenter - color: HifiConstants.color + color: hifiPalette.hifiBlue border.width: 0 radius: 10 @@ -142,7 +142,7 @@ CustomDialog { width: 32 source: "../images/login.svg" } - CustomText { + Text { text: "Login" color: "white" width: 64 @@ -152,7 +152,7 @@ CustomDialog { } - CustomText { + Text { width: parent.width height: 24 horizontalAlignment: Text.AlignHCenter @@ -160,7 +160,7 @@ CustomDialog { text:"Create Account" font.pointSize: 12 font.bold: true - color: HifiConstants.color + color: hifiPalette.hifiBlue MouseArea { anchors.fill: parent @@ -170,14 +170,14 @@ CustomDialog { } } - CustomText { + Text { width: parent.width height: 24 horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter font.pointSize: 12 text: "Recover Password" - color: HifiConstants.color + color: hifiPalette.hifiBlue MouseArea { anchors.fill: parent diff --git a/interface/resources/qml/MarketplaceDialog.qml b/interface/resources/qml/MarketplaceDialog.qml new file mode 100644 index 0000000000..58bb3e6183 --- /dev/null +++ b/interface/resources/qml/MarketplaceDialog.qml @@ -0,0 +1,50 @@ +import Hifi 1.0 +import QtQuick 2.3 +import QtQuick.Controls 1.2 +import QtQuick.Controls.Styles 1.3 +import QtWebKit 3.0 +import "controls" + +Dialog { + title: "Test Dlg" + id: testDialog + objectName: "Browser" + width: 720 + height: 720 + resizable: true + + MarketplaceDialog { + id: marketplaceDialog + } + + Item { + id: clientArea + // The client area + anchors.fill: parent + anchors.margins: parent.margins + anchors.topMargin: parent.topMargin + + + ScrollView { + anchors.fill: parent + WebView { + objectName: "WebView" + id: webview + url: "https://metaverse.highfidelity.com/marketplace" + anchors.fill: parent + onNavigationRequested: { + console.log(request.url) + if (!marketplaceDialog.navigationRequested(request.url)) { + console.log("Application absorbed the request") + request.action = WebView.IgnoreRequest; + return; + } + console.log("Application passed on the request") + request.action = WebView.AcceptRequest; + return; + } + } + } + + } +} diff --git a/interface/resources/qml/MessageDialog.qml b/interface/resources/qml/MessageDialog.qml new file mode 100644 index 0000000000..26fd30db27 --- /dev/null +++ b/interface/resources/qml/MessageDialog.qml @@ -0,0 +1,359 @@ +/***************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtQuick.Dialogs module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names +** of its contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +*****************************************************************************/ + +import Hifi 1.0 as Hifi +import QtQuick 2.2 +import QtQuick.Controls 1.2 +import QtQuick.Dialogs 1.2 +import "controls" + +Dialog { + id: root + property real spacing: 8 + property real outerSpacing: 16 + + destroyOnCloseButton: true + destroyOnInvisible: true + implicitHeight: content.implicitHeight + outerSpacing * 2 + 48 + implicitWidth: Math.min(200, Math.max(mainText.implicitWidth, content.buttonsRowImplicitWidth) + outerSpacing * 2); + + onImplicitHeightChanged: root.height = implicitHeight + onImplicitWidthChanged: root.width = implicitWidth + + SystemPalette { id: palette } + + function calculateImplicitWidth() { + if (buttons.visibleChildren.length < 2) + return; + var calcWidth = 0; + for (var i = 0; i < buttons.visibleChildren.length; ++i) { + calcWidth += Math.max(100, buttons.visibleChildren[i].implicitWidth) + root.spacing + } + content.buttonsRowImplicitWidth = outerSpacing + calcWidth + 48 + } + + onEnabledChanged: { + if (enabled) { + content.forceActiveFocus(); + } + } + + Hifi.MessageDialog { + id: content + clip: true + anchors.fill: parent + anchors.topMargin: parent.topMargin + root.outerSpacing + anchors.leftMargin: parent.margins + root.outerSpacing + anchors.rightMargin: parent.margins + root.outerSpacing + anchors.bottomMargin: parent.margins + root.outerSpacing + implicitHeight: contentColumn.implicitHeight + outerSpacing * 2 + implicitWidth: Math.max(mainText.implicitWidth, buttonsRowImplicitWidth); + property real buttonsRowImplicitWidth: Screen.pixelDensity * 50 + + Keys.onPressed: { + console.log("Key press at content") + event.accepted = true + if (event.modifiers === Qt.ControlModifier) + switch (event.key) { + case Qt.Key_A: + console.log("Select All") + detailedText.selectAll() + break + case Qt.Key_C: + console.log("Copy") + detailedText.copy() + break + case Qt.Key_Period: + if (Qt.platform.os === "osx") + reject() + break + } else switch (event.key) { + case Qt.Key_Escape: + case Qt.Key_Back: + console.log("Rejecting") + reject() + break + case Qt.Key_Enter: + case Qt.Key_Return: + console.log("Accepting") + accept() + break + } + } + + onImplicitWidthChanged: root.width = implicitWidth + + Component.onCompleted: { + root.title = title + } + + onTitleChanged: { + root.title = title + } + + Column { + id: contentColumn + spacing: root.outerSpacing + anchors { + top: parent.top + left: parent.left + right: parent.right + } + + Item { + width: parent.width + height: Math.max(icon.height, mainText.height + informativeText.height + root.spacing) + Image { + id: icon + source: content.standardIconSource + } + + Text { + id: mainText + anchors { + left: icon.right + leftMargin: root.spacing + right: parent.right + } + text: content.text + font.pointSize: 14 + font.weight: Font.Bold + wrapMode: Text.WordWrap + } + + Text { + id: informativeText + anchors { + left: icon.right + right: parent.right + top: mainText.bottom + leftMargin: root.spacing + topMargin: root.spacing + } + text: content.informativeText + font.pointSize: 14 + wrapMode: Text.WordWrap + } + } + + + Flow { + id: buttons + spacing: root.spacing + layoutDirection: Qt.RightToLeft + width: parent.width + Button { + id: okButton + text: qsTr("OK") + onClicked: content.click(StandardButton.Ok) + visible: content.standardButtons & StandardButton.Ok + } + Button { + id: openButton + text: qsTr("Open") + onClicked: content.click(StandardButton.Open) + visible: content.standardButtons & StandardButton.Open + } + Button { + id: saveButton + text: qsTr("Save") + onClicked: content.click(StandardButton.Save) + visible: content.standardButtons & StandardButton.Save + } + Button { + id: saveAllButton + text: qsTr("Save All") + onClicked: content.click(StandardButton.SaveAll) + visible: content.standardButtons & StandardButton.SaveAll + } + Button { + id: retryButton + text: qsTr("Retry") + onClicked: content.click(StandardButton.Retry) + visible: content.standardButtons & StandardButton.Retry + } + Button { + id: ignoreButton + text: qsTr("Ignore") + onClicked: content.click(StandardButton.Ignore) + visible: content.standardButtons & StandardButton.Ignore + } + Button { + id: applyButton + text: qsTr("Apply") + onClicked: content.click(StandardButton.Apply) + visible: content.standardButtons & StandardButton.Apply + } + Button { + id: yesButton + text: qsTr("Yes") + onClicked: content.click(StandardButton.Yes) + visible: content.standardButtons & StandardButton.Yes + } + Button { + id: yesAllButton + text: qsTr("Yes to All") + onClicked: content.click(StandardButton.YesToAll) + visible: content.standardButtons & StandardButton.YesToAll + } + Button { + id: noButton + text: qsTr("No") + onClicked: content.click(StandardButton.No) + visible: content.standardButtons & StandardButton.No + } + Button { + id: noAllButton + text: qsTr("No to All") + onClicked: content.click(StandardButton.NoToAll) + visible: content.standardButtons & StandardButton.NoToAll + } + Button { + id: discardButton + text: qsTr("Discard") + onClicked: content.click(StandardButton.Discard) + visible: content.standardButtons & StandardButton.Discard + } + Button { + id: resetButton + text: qsTr("Reset") + onClicked: content.click(StandardButton.Reset) + visible: content.standardButtons & StandardButton.Reset + } + Button { + id: restoreDefaultsButton + text: qsTr("Restore Defaults") + onClicked: content.click(StandardButton.RestoreDefaults) + visible: content.standardButtons & StandardButton.RestoreDefaults + } + Button { + id: cancelButton + text: qsTr("Cancel") + onClicked: content.click(StandardButton.Cancel) + visible: content.standardButtons & StandardButton.Cancel + } + Button { + id: abortButton + text: qsTr("Abort") + onClicked: content.click(StandardButton.Abort) + visible: content.standardButtons & StandardButton.Abort + } + Button { + id: closeButton + text: qsTr("Close") + onClicked: content.click(StandardButton.Close) + visible: content.standardButtons & StandardButton.Close + } + Button { + id: moreButton + text: qsTr("Show Details...") + onClicked: content.state = (content.state === "" ? "expanded" : "") + visible: content.detailedText.length > 0 + } + Button { + id: helpButton + text: qsTr("Help") + onClicked: content.click(StandardButton.Help) + visible: content.standardButtons & StandardButton.Help + } + onVisibleChildrenChanged: root.calculateImplicitWidth() + } + } + + Item { + id: details + width: parent.width + implicitHeight: detailedText.implicitHeight + root.spacing + height: 0 + clip: true + + anchors { + left: parent.left + right: parent.right + top: contentColumn.bottom + topMargin: root.spacing + leftMargin: root.outerSpacing + rightMargin: root.outerSpacing + } + + Flickable { + id: flickable + contentHeight: detailedText.height + anchors.fill: parent + anchors.topMargin: root.spacing + anchors.bottomMargin: root.outerSpacing + TextEdit { + id: detailedText + text: content.detailedText + width: details.width + wrapMode: Text.WordWrap + readOnly: true + selectByMouse: true + } + } + } + + states: [ + State { + name: "expanded" + PropertyChanges { + target: details + height: root.height - contentColumn.height - root.spacing - root.outerSpacing + } + PropertyChanges { + target: content + implicitHeight: contentColumn.implicitHeight + root.spacing * 2 + + detailedText.implicitHeight + root.outerSpacing * 2 + } + PropertyChanges { + target: moreButton + text: qsTr("Hide Details") + } + } + ] + +/* + Rectangle { + + } + Component.onCompleted: calculateImplicitWidth() + */ + } +} diff --git a/interface/resources/qml/Palettes.qml b/interface/resources/qml/Palettes.qml index c4b0953df7..2bdf6eba8b 100644 --- a/interface/resources/qml/Palettes.qml +++ b/interface/resources/qml/Palettes.qml @@ -1,8 +1,5 @@ import QtQuick 2.3 import QtQuick.Controls 1.2 -import QtQuick.Window 2.2 -import QtQuick.Dialogs 1.2 -import QtQuick.Controls.Styles 1.3 Rectangle { color: "teal" @@ -150,87 +147,4 @@ Rectangle { Rectangle { height: parent.height; width: 16; color: spd.highlightedText} } } - - -/* - CustomDialog { - title: "Test Dlg" - anchors.fill: parent - - Rectangle { - property int d: 100 - id: square - objectName: "testRect" - width: d - height: d - anchors.centerIn: parent - color: "red" - NumberAnimation on rotation { from: 0; to: 360; duration: 2000; loops: Animation.Infinite; } - } - - - CustomTextEdit { - anchors.left: parent.left - anchors.leftMargin: 12 - anchors.right: parent.right - anchors.rightMargin: 12 - clip: true - text: "test edit" - anchors.top: parent.top - anchors.topMargin: parent.titleSize + 12 - } - - CustomButton { - x: 128 - y: 192 - anchors.bottom: parent.bottom - anchors.bottomMargin: 12 - anchors.right: parent.right - anchors.rightMargin: 12 - onClicked: { - console.log("Click"); - if (square.visible) { - square.visible = false - } else { - square.visible = true - } - } - } - - CustomButton { - id: customButton2 - y: 192 - text: "Close" - anchors.left: parent.left - anchors.leftMargin: 12 - anchors.bottom: parent.bottom - anchors.bottomMargin: 12 - onClicked: { - onClicked: testDialog.x == 0 ? testDialog.x = 200 : testDialog.x = 0 - } - } - - Keys.onPressed: { - console.log("Key " + event.key); - switch (event.key) { - case Qt.Key_Q: - if (Qt.ControlModifier == event.modifiers) { - event.accepted = true; - break; - } - } - } - } -*/ - } - - -/* - -// This is the behavior, and it applies a NumberAnimation to any attempt to set the x property - -MouseArea { - anchors.fill: parent -} -*/ diff --git a/interface/resources/qml/Root.qml b/interface/resources/qml/Root.qml index 9422ef123d..b2db7d18bf 100644 --- a/interface/resources/qml/Root.qml +++ b/interface/resources/qml/Root.qml @@ -1,9 +1,10 @@ import Hifi 1.0 import QtQuick 2.3 +// This is our primary 'window' object to which all dialogs and controls will +// be childed. Root { id: root - width: 1280 - height: 720 + anchors.fill: parent } diff --git a/interface/resources/qml/RootMenu.qml b/interface/resources/qml/RootMenu.qml new file mode 100644 index 0000000000..b8c81a6589 --- /dev/null +++ b/interface/resources/qml/RootMenu.qml @@ -0,0 +1,9 @@ +import QtQuick 2.4 +import QtQuick.Controls 1.3 + +Item { + Menu { + id: root + objectName: "rootMenu" + } +} diff --git a/interface/resources/qml/TestDialog.qml b/interface/resources/qml/TestDialog.qml index 1fe8676bc6..15bd790c22 100644 --- a/interface/resources/qml/TestDialog.qml +++ b/interface/resources/qml/TestDialog.qml @@ -1,10 +1,9 @@ import QtQuick 2.3 import QtQuick.Controls 1.2 -import QtQuick.Window 2.2 -import QtQuick.Dialogs 1.2 import QtQuick.Controls.Styles 1.3 +import "controls" -CustomDialog { +Dialog { title: "Test Dialog" id: testDialog objectName: "TestDialog" @@ -37,7 +36,7 @@ CustomDialog { } - CustomTextEdit { + TextEdit { id: edit anchors.left: parent.left anchors.leftMargin: 12 @@ -49,7 +48,7 @@ CustomDialog { anchors.topMargin: 12 } - CustomButton { + Button { x: 128 y: 192 text: "Test" @@ -68,7 +67,7 @@ CustomDialog { } } - CustomButton { + Button { id: customButton2 y: 192 text: "Move" @@ -92,15 +91,4 @@ CustomDialog { } } } - } - - -/* - -// This is the behavior, and it applies a NumberAnimation to any attempt to set the x property - -MouseArea { - anchors.fill: parent -} -*/ diff --git a/interface/resources/qml/TestRoot.qml b/interface/resources/qml/TestRoot.qml index 158c0b7a54..80f8c900e3 100644 --- a/interface/resources/qml/TestRoot.qml +++ b/interface/resources/qml/TestRoot.qml @@ -1,12 +1,27 @@ import Hifi 1.0 import QtQuick 2.3 +import QtQuick.Controls 1.3 +// Import local folder last so that our own control customizations override +// the built in ones +import "controls" Root { id: root - width: 1280 - height: 720 + anchors.fill: parent - CustomButton { + onWidthChanged: { + console.log("Root width: " + width) + } + onHeightChanged: { + console.log("Root height: " + height) + } + + Component.onCompleted: { + console.log("Completed root") + root.forceActiveFocus() + } + + Button { id: messageBox anchors.right: createDialog.left anchors.rightMargin: 24 @@ -20,7 +35,7 @@ Root { } } - CustomButton { + Button { id: createDialog anchors.right: parent.right anchors.rightMargin: 24 @@ -28,8 +43,12 @@ Root { anchors.bottomMargin: 24 text: "Create" onClicked: { - root.loadChild("TestDialog.qml"); + root.loadChild("MenuTest.qml"); } } + + Keys.onPressed: { + console.log(event.key); + } } diff --git a/interface/resources/qml/componentCreation.js b/interface/resources/qml/componentCreation.js deleted file mode 100644 index 15a828d6f8..0000000000 --- a/interface/resources/qml/componentCreation.js +++ /dev/null @@ -1,29 +0,0 @@ -var component; -var instance; -var parent; - -function createObject(parentObject, url) { - parent = parentObject; - component = Qt.createComponent(url); - if (component.status == Component.Ready) - finishCreation(); - else - component.statusChanged.connect(finishCreation); -} - -function finishCreation() { - if (component.status == Component.Ready) { - instance = component.createObject(parent, {"x": 100, "y": 100}); - if (instance == null) { - // Error Handling - console.log("Error creating object"); - } else { - instance.enabled = true - } - } else if (component.status == Component.Error) { - // Error Handling - console.log("Error loading component:", component.errorString()); - } else { - console.log("Unknown component status: " + component.status); - } -} \ No newline at end of file diff --git a/interface/resources/qml/controls/Button.qml b/interface/resources/qml/controls/Button.qml new file mode 100644 index 0000000000..215e0542f7 --- /dev/null +++ b/interface/resources/qml/controls/Button.qml @@ -0,0 +1,10 @@ +import QtQuick 2.3 +import QtQuick.Controls 1.3 as Original +import QtQuick.Controls.Styles 1.3 +import "." +import "../styles" + +Original.Button { + style: ButtonStyle { + } +} diff --git a/interface/resources/qml/CustomDialog.qml b/interface/resources/qml/controls/Dialog.qml similarity index 55% rename from interface/resources/qml/CustomDialog.qml rename to interface/resources/qml/controls/Dialog.qml index 1e0351af4f..07162ad1d8 100644 --- a/interface/resources/qml/CustomDialog.qml +++ b/interface/resources/qml/controls/Dialog.qml @@ -1,40 +1,79 @@ import QtQuick 2.3 import QtQuick.Controls 1.2 -import QtQuick.Window 2.2 -import QtQuick.Dialogs 1.2 -import QtQuick.Controls.Styles 1.3 -import "hifiConstants.js" as HifiConstants +import "." +import "../styles" +/* + * FIXME Need to create a client property here so that objects can be + * placed in it without having to think about positioning within the outer + * window. + * + * Examine the QML ApplicationWindow.qml source for how it does this + * + */ Item { - SystemPalette { id: myPalette; colorGroup: SystemPalette.Active } - id: dialog - width: 256 - height: 256 - scale: 0.0 - enabled: false + id: root + + HifiPalette { id: hifiPalette } + SystemPalette { id: sysPalette; colorGroup: SystemPalette.Active } + x: parent ? parent.width / 2 - width / 2 : 0 + y: parent ? parent.height / 2 - height / 2 : 0 + property int animationDuration: 400 property bool destroyOnInvisible: false property bool destroyOnCloseButton: true property bool resizable: false property int minX: 256 property int minY: 256 + property int topMargin: root.height - clientBorder.height + 8 + property int margins: 8 + property string title + property int titleSize: titleBorder.height + 12 + property string frameColor: hifiPalette.hifiBlue + property string backgroundColor: sysPalette.window + property string headerBackgroundColor: sysPalette.dark clip: true + + /* + * Support for animating the dialog in and out. + */ + enabled: false + scale: 0.0 + // The offscreen UI will enable an object, rather than manipulating it's + // visibility, so that we can do animations in both directions. Because + // visibility and enabled are boolean flags, they cannot be animated. So when + // enabled is change, we modify a property that can be animated, like scale or + // opacity. onEnabledChanged: { scale = enabled ? 1.0 : 0.0 } - + + // The actual animator + Behavior on scale { + NumberAnimation { + duration: root.animationDuration + easing.type: Easing.InOutBounce + } + } + + // We remove any load the dialog might have on the QML by toggling it's + // visibility based on the state of the animated property onScaleChanged: { visible = (scale != 0.0); } + // Some dialogs should be destroyed when they become invisible, so handle that onVisibleChanged: { if (!visible && destroyOnInvisible) { - console.log("Destroying closed component"); destroy(); } } + // our close function performs the same way as the OffscreenUI class: + // don't do anything but manipulate the enabled flag and let the other + // mechanisms decide if the window should be destoryed after the close + // animation completes function close() { if (destroyOnCloseButton) { destroyOnInvisible = true @@ -42,102 +81,14 @@ Item { enabled = false; } + /* + * Resize support + */ function deltaSize(dx, dy) { width = Math.max(width + dx, minX) height = Math.max(height + dy, minY) } - Behavior on scale { - NumberAnimation { - //This specifies how long the animation takes - duration: dialog.animationDuration - //This selects an easing curve to interpolate with, the default is Easing.Linear - easing.type: Easing.InOutBounce - } - } - - property int topMargin: dialog.height - clientBorder.height + 8 - property int margins: 8 - property string title - property int titleSize: titleBorder.height + 12 - property string frameColor: HifiConstants.color - property string backgroundColor: myPalette.window - property string headerBackgroundColor: myPalette.dark - - CustomBorder { - id: windowBorder - anchors.fill: parent - border.color: dialog.frameColor - color: dialog.backgroundColor - - CustomBorder { - id: titleBorder - height: 48 - anchors.right: parent.right - anchors.rightMargin: 0 - anchors.left: parent.left - anchors.leftMargin: 0 - border.color: dialog.frameColor - color: dialog.headerBackgroundColor - - CustomText { - id: titleText - color: "white" - text: dialog.title - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - anchors.fill: parent - } - - MouseArea { - id: titleDrag - anchors.right: closeButton.left - anchors.bottom: parent.bottom - anchors.left: parent.left - anchors.top: parent.top - anchors.rightMargin: 4 - drag { - target: dialog - minimumX: 0 - minimumY: 0 - maximumX: dialog.parent ? dialog.parent.width - dialog.width : 0 - maximumY: dialog.parent ? dialog.parent.height - dialog.height : 0 - } - } - Image { - id: closeButton - x: 360 - height: 16 - anchors.verticalCenter: parent.verticalCenter - width: 16 - anchors.right: parent.right - anchors.rightMargin: 12 - source: "../styles/close.svg" - MouseArea { - anchors.fill: parent - onClicked: { - dialog.close(); - } - } - } - } // header border - - CustomBorder { - id: clientBorder - border.color: dialog.frameColor - color: "#00000000" - anchors.bottom: parent.bottom - anchors.bottomMargin: 0 - anchors.top: titleBorder.bottom - anchors.topMargin: -titleBorder.border.width - anchors.right: parent.right - anchors.rightMargin: 0 - anchors.left: parent.left - anchors.leftMargin: 0 - clip: true - } // client border - } // window border - MouseArea { id: sizeDrag property int startX @@ -152,11 +103,91 @@ Item { startY = mouseY } onPositionChanged: { - if (pressed && dialog.resizable) { - dialog.deltaSize((mouseX - startX), (mouseY - startY)) + if (pressed && root.resizable) { + root.deltaSize((mouseX - startX), (mouseY - startY)) startX = mouseX startY = mouseY } } } + + /* + * Window decorations, with a title bar and frames + */ + Border { + id: windowBorder + anchors.fill: parent + border.color: root.frameColor + color: root.backgroundColor + + Border { + id: titleBorder + height: 48 + anchors.right: parent.right + anchors.rightMargin: 0 + anchors.left: parent.left + anchors.leftMargin: 0 + border.color: root.frameColor + color: root.headerBackgroundColor + + Text { + id: titleText + // FIXME move all constant colors to our own palette class HifiPalette + color: "white" + text: root.title + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + anchors.fill: parent + } + + MouseArea { + id: titleDrag + anchors.right: closeButton.left + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.top: parent.top + anchors.rightMargin: 4 + drag { + target: root + minimumX: 0 + minimumY: 0 + maximumX: root.parent ? root.parent.width - root.width : 0 + maximumY: root.parent ? root.parent.height - root.height : 0 + } + } + Image { + id: closeButton + x: 360 + height: 16 + anchors.verticalCenter: parent.verticalCenter + width: 16 + anchors.right: parent.right + anchors.rightMargin: 12 + source: "../../styles/close.svg" + MouseArea { + anchors.fill: parent + onClicked: { + root.close(); + } + } + } + } // header border + + Border { + id: clientBorder + border.color: root.frameColor + // FIXME move all constant colors to our own palette class HifiPalette + color: "#00000000" + anchors.bottom: parent.bottom + anchors.bottomMargin: 0 + anchors.top: titleBorder.bottom + anchors.topMargin: -titleBorder.border.width + anchors.right: parent.right + anchors.rightMargin: 0 + anchors.left: parent.left + anchors.leftMargin: 0 + clip: true + } // client border + } // window border + } diff --git a/interface/resources/qml/controls/FontAwesome.qml b/interface/resources/qml/controls/FontAwesome.qml new file mode 100644 index 0000000000..e975c0342b --- /dev/null +++ b/interface/resources/qml/controls/FontAwesome.qml @@ -0,0 +1,16 @@ +import QtQuick 2.3 +import QtQuick.Controls 1.3 +import QtQuick.Controls.Styles 1.3 + +Text { + id: root + FontLoader { id: iconFont; source: "../../fonts/fontawesome-webfont.ttf"; } + property int size: 32 + width: size + height: size + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter + font.family: iconFont.name + font.pointSize: 18 +} + diff --git a/interface/resources/qml/IconControl.qml b/interface/resources/qml/controls/IconButton.qml similarity index 100% rename from interface/resources/qml/IconControl.qml rename to interface/resources/qml/controls/IconButton.qml diff --git a/interface/resources/qml/controls/MenuButton.qml b/interface/resources/qml/controls/MenuButton.qml new file mode 100644 index 0000000000..740995a199 --- /dev/null +++ b/interface/resources/qml/controls/MenuButton.qml @@ -0,0 +1,5 @@ +import QtQuick 2.3 +import QtQuick.Controls 1.3 as Original +import "../styles" +import "../controls" + diff --git a/interface/resources/qml/controls/README.md b/interface/resources/qml/controls/README.md new file mode 100644 index 0000000000..7f05f32a63 --- /dev/null +++ b/interface/resources/qml/controls/README.md @@ -0,0 +1,2 @@ +These are our own custom controls with the same names as existing controls, but +customized for readability / usability in VR. diff --git a/interface/resources/qml/CustomTextEdit.qml b/interface/resources/qml/controls/Text.qml similarity index 54% rename from interface/resources/qml/CustomTextEdit.qml rename to interface/resources/qml/controls/Text.qml index 0602bbc150..a9c19e70b4 100644 --- a/interface/resources/qml/CustomTextEdit.qml +++ b/interface/resources/qml/controls/Text.qml @@ -1,6 +1,6 @@ -import QtQuick 2.3 +import QtQuick 2.3 as Original -TextEdit { +Original.Text { font.family: "Helvetica" font.pointSize: 18 } diff --git a/interface/resources/qml/CustomText.qml b/interface/resources/qml/controls/TextArea.qml similarity index 52% rename from interface/resources/qml/CustomText.qml rename to interface/resources/qml/controls/TextArea.qml index 83229b783e..dfa177bcb6 100644 --- a/interface/resources/qml/CustomText.qml +++ b/interface/resources/qml/controls/TextArea.qml @@ -1,6 +1,6 @@ -import QtQuick 2.3 +import QtQuick 2.3 as Original -Text { +Original.TextArea { font.family: "Helvetica" font.pointSize: 18 } diff --git a/interface/resources/qml/controls/TextEdit.qml b/interface/resources/qml/controls/TextEdit.qml new file mode 100644 index 0000000000..28551bb171 --- /dev/null +++ b/interface/resources/qml/controls/TextEdit.qml @@ -0,0 +1,7 @@ +import QtQuick 2.3 as Original + +Original.TextEdit { + font.family: "Helvetica" + font.pointSize: 18 +} + diff --git a/interface/resources/qml/CustomTextInput.qml b/interface/resources/qml/controls/TextInput.qml similarity index 90% rename from interface/resources/qml/CustomTextInput.qml rename to interface/resources/qml/controls/TextInput.qml index a706187376..8ce3d85d81 100644 --- a/interface/resources/qml/CustomTextInput.qml +++ b/interface/resources/qml/controls/TextInput.qml @@ -3,7 +3,7 @@ import QtQuick.Controls 1.2 TextInput { SystemPalette { id: myPalette; colorGroup: SystemPalette.Active } - property string helperText: "" + property string helperText font.family: "Helvetica" font.pointSize: 18 width: 256 @@ -24,7 +24,7 @@ TextInput { id: helperText anchors.fill: parent font.pointSize: parent.font.pointSize - font.family: "Helvetica" + font.family: parent.font.family verticalAlignment: TextInput.AlignVCenter text: parent.helperText color: myPalette.dark diff --git a/interface/resources/qml/hifiConstants.js b/interface/resources/qml/hifiConstants.js deleted file mode 100644 index 860226c963..0000000000 --- a/interface/resources/qml/hifiConstants.js +++ /dev/null @@ -1,4 +0,0 @@ -var color = "#0e7077" -var Colors = { - hifiBlue: "#0e7077" -} diff --git a/interface/resources/qml/images/critical.png b/interface/resources/qml/images/critical.png new file mode 100644 index 0000000000000000000000000000000000000000..dc9c5aebf453dd92dba60c48f060b34f0087e02c GIT binary patch literal 253 zcmVkNDWE^AyB8@ZEC00000NkvXXu0mjf DKfYr$ literal 0 HcmV?d00001 diff --git a/interface/resources/qml/images/information.png b/interface/resources/qml/images/information.png new file mode 100644 index 0000000000000000000000000000000000000000..0a2eb87d108d2a24b71559998627570a252ebe69 GIT binary patch literal 254 zcmeAS@N?(olHy`uVBq!ia0y~yU{C;I7G?$phQ^Te;|vT8`~f~8uBj!u3?T4-=FFM@ z|NrludHNay0|R48kY6x^!?PP{3=9l&JzX3_G|sP`c#-$80*{Mh+yV`sgwlqP>#QC( z>X#+u7`Xm@Q`=BsWqr<)MUc^=nfrazYu@_ss|q3QYrOu5+ux47bKp#oZChf4k#&aW z_6qNdiK4+zLkc4MIa@Ri#52@3K4zcMoZ;%Na4X>suaM`rgBP8<|7A}r*;L-N)XQ^~ z*Qd}2Q;+ng3Z7t^78=O>`qh(QX^u+vLUu-L1y=d3FMU5VRZj!?kipZ{&t;ucLK6Te CT3=!S literal 0 HcmV?d00001 diff --git a/interface/resources/qml/images/question.png b/interface/resources/qml/images/question.png new file mode 100644 index 0000000000000000000000000000000000000000..2dd92fd7915a09de670b8b6022ddcf02d4cc90e1 GIT binary patch literal 257 zcmV+c0sj7pP)NklQ}7)nBez2^$+jK{Nw%L3xK4m{g6jm}2|@>7z7zk+b}w*O&>f9X*ioevByCmJtfv0xgg* zX&;ac>>nrw_75mp9NLlK=){M}g!K&|3b4W-F*J23VSVK);JN=%IJYXN$un1x$~;9b a#PS7=(1SoC6O>K>0000a literal 0 HcmV?d00001 diff --git a/interface/resources/qml/CustomBorder.qml b/interface/resources/qml/styles/Border.qml similarity index 99% rename from interface/resources/qml/CustomBorder.qml rename to interface/resources/qml/styles/Border.qml index 1bb30d1ebc..7d38e7d277 100644 --- a/interface/resources/qml/CustomBorder.qml +++ b/interface/resources/qml/styles/Border.qml @@ -1,6 +1,5 @@ import QtQuick 2.3 - Rectangle { SystemPalette { id: myPalette; colorGroup: SystemPalette.Active } property int margin: 5 diff --git a/interface/resources/qml/styles/ButtonStyle.qml b/interface/resources/qml/styles/ButtonStyle.qml new file mode 100644 index 0000000000..8d866390a0 --- /dev/null +++ b/interface/resources/qml/styles/ButtonStyle.qml @@ -0,0 +1,24 @@ +import QtQuick 2.4 as Original +import QtQuick.Controls.Styles 1.3 as OriginalStyles +import "." +import "../controls" + +OriginalStyles.ButtonStyle { + Original.SystemPalette { id: myPalette; colorGroup: Original.SystemPalette.Active } + padding { + top: 8 + left: 12 + right: 12 + bottom: 8 + } + background: Border { + anchors.fill: parent + } + label: Text { + renderType: Original.Text.NativeRendering + verticalAlignment: Original.Text.AlignVCenter + horizontalAlignment: Original.Text.AlignHCenter + text: control.text + color: control.enabled ? myPalette.text : myPalette.dark + } +} diff --git a/interface/resources/qml/styles/HifiPalette.qml b/interface/resources/qml/styles/HifiPalette.qml new file mode 100644 index 0000000000..46ef0ef14e --- /dev/null +++ b/interface/resources/qml/styles/HifiPalette.qml @@ -0,0 +1,5 @@ +import QtQuick 2.4 + +QtObject { + property string hifiBlue: "#0e7077" +} \ No newline at end of file diff --git a/interface/resources/qml/styles/IconButtonStyle.qml b/interface/resources/qml/styles/IconButtonStyle.qml new file mode 100644 index 0000000000..b341e5d6dd --- /dev/null +++ b/interface/resources/qml/styles/IconButtonStyle.qml @@ -0,0 +1,15 @@ +ButtonStyle { + background: Item { anchors.fill: parent } + label: Text { + id: icon + width: height + verticalAlignment: Text.AlignVCenter + renderType: Text.NativeRendering + font.family: iconFont.name + font.pointSize: 18 + property alias unicode: icon.text + FontLoader { id: iconFont; source: "../../fonts/fontawesome-webfont.ttf"; } + text: control.text + color: control.enabled ? "white" : "dimgray" + } +} diff --git a/interface/resources/qml/styles/MenuButtonStyle.qml b/interface/resources/qml/styles/MenuButtonStyle.qml new file mode 100644 index 0000000000..fd21e88d86 --- /dev/null +++ b/interface/resources/qml/styles/MenuButtonStyle.qml @@ -0,0 +1,22 @@ +import QtQuick 2.4 +import QtQuick.Controls.Styles 1.3 +import "../controls" +import "." + +ButtonStyle { + HifiPalette { id: hifiPalette } + padding { + top: 2 + left: 4 + right: 4 + bottom: 2 + } + background: Item {} + label: Text { + renderType: Text.NativeRendering + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter + text: control.text + color: control.enabled ? "yellow" : "brown" + } +} diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 9ae54ac83e..5a8b3ecee9 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -144,6 +144,23 @@ extern "C" { } #endif +enum CustomEventTypes { + Lambda = QEvent::User + 1 +}; + +class LambdaEvent : public QEvent { + std::function _fun; +public: + LambdaEvent(const std::function & fun) : + QEvent(static_cast(Lambda)), _fun(fun) { + } + LambdaEvent(std::function && fun) : + QEvent(static_cast(Lambda)), _fun(fun) { + } + void call() { _fun(); } +}; + + using namespace std; // Starfield information @@ -707,6 +724,13 @@ void Application::initializeGL() { initDisplay(); qCDebug(interfaceapp, "Initialized Display."); + // The UI can't be created until the primary OpenGL + // context is created, because it needs to share + // texture resources + initializeUi(); + qCDebug(interfaceapp, "Initialized Offscreen UI."); + _glWidget->makeCurrent(); + init(); qCDebug(interfaceapp, "init() complete."); @@ -735,17 +759,13 @@ void Application::initializeGL() { // update before the first render update(1.0f / _fps); - // The UI can't be created until the primary OpenGL - // context is created, because it needs to share - // texture resources - initializeUi(); - InfoView::showFirstTime(INFO_HELP_PATH); } void Application::initializeUi() { AddressBarDialog::registerType(); LoginDialog::registerType(); + MessageDialog::registerType(); auto offscreenUi = DependencyManager::get(); offscreenUi->create(_glWidget->context()->contextHandle()); @@ -753,6 +773,7 @@ void Application::initializeUi() { offscreenUi->setProxyWindow(_window->windowHandle()); offscreenUi->setBaseUrl(QUrl::fromLocalFile(PathUtils::resourcesPath() + "/qml/")); offscreenUi->load("Root.qml"); + offscreenUi->load("RootMenu.qml"); offscreenUi->setMouseTranslator([this](const QPointF& p){ if (OculusManager::isConnected()) { glm::vec2 pos = _applicationOverlay.screenToOverlay(toGlm(p)); @@ -964,6 +985,10 @@ bool Application::importSVOFromURL(const QString& urlString) { bool Application::event(QEvent* event) { switch (event->type()) { + case Lambda: + ((LambdaEvent*)event)->call(); + return true; + case QEvent::MouseMove: mouseMoveEvent((QMouseEvent*)event); return true; diff --git a/interface/src/Application.h b/interface/src/Application.h index 89de18dde5..688cf23876 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -21,6 +21,7 @@ #include #include #include +#include #include #include @@ -147,6 +148,8 @@ public: Application(int& argc, char** argv, QElapsedTimer &startup_time); ~Application(); + void postLambdaEvent(std::function f); + void loadScripts(); QString getPreviousScriptLocation(); void setPreviousScriptLocation(const QString& previousScriptLocation); diff --git a/interface/src/ui/ApplicationOverlay.cpp b/interface/src/ui/ApplicationOverlay.cpp index 2a8f01aafc..7940a06993 100644 --- a/interface/src/ui/ApplicationOverlay.cpp +++ b/interface/src/ui/ApplicationOverlay.cpp @@ -323,7 +323,7 @@ void ApplicationOverlay::displayOverlayTextureOculus(Camera& whichCamera) { //Render magnifier, but dont show border for mouse magnifier glm::vec2 projection = screenToOverlay(glm::vec2(_reticlePosition[MOUSE].x(), _reticlePosition[MOUSE].y())); - with_each_texture(_overlays.getTexture(), _newUiTexture, [&] { + with_each_texture(_overlays.getTexture(), 0, [&] { renderMagnifier(projection, _magSizeMult[i], i != MOUSE); }); } diff --git a/libraries/render-utils/src/OffscreenQmlDialog.h b/libraries/render-utils/src/OffscreenQmlDialog.h deleted file mode 100644 index eca82261c0..0000000000 --- a/libraries/render-utils/src/OffscreenQmlDialog.h +++ /dev/null @@ -1,56 +0,0 @@ -// -// OffscreenQmlDialog.h -// -// Created by Bradley 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 -// - -#pragma once -#ifndef hifi_OffscreenQmlDialog_h -#define hifi_OffscreenQmlDialog_h - -#include -#include "OffscreenUi.h" - -#define QML_DIALOG_DECL \ -private: \ - static const QString NAME; \ - static const QUrl QML; \ -public: \ - static void registerType(); \ - static void show(); \ - static void toggle(); \ -private: - -#define QML_DIALOG_DEF(x) \ - const QUrl x::QML = QUrl(#x ".qml"); \ - const QString x::NAME = #x; \ - \ - void x::registerType() { \ - qmlRegisterType("Hifi", 1, 0, NAME.toLocal8Bit().constData()); \ - } \ - \ - void x::show() { \ - auto offscreenUi = DependencyManager::get(); \ - offscreenUi->show(QML, NAME); \ - } \ - \ - void x::toggle() { \ - auto offscreenUi = DependencyManager::get(); \ - offscreenUi->toggle(QML, NAME); \ - } - -class OffscreenQmlDialog : public QQuickItem -{ - Q_OBJECT -public: - OffscreenQmlDialog(QQuickItem* parent = nullptr); - -protected: - void hide(); -}; - -#endif diff --git a/libraries/shared/src/PathUtils.cpp b/libraries/shared/src/PathUtils.cpp index bf846c0bf2..47e5659c60 100644 --- a/libraries/shared/src/PathUtils.cpp +++ b/libraries/shared/src/PathUtils.cpp @@ -14,16 +14,26 @@ #include #include #include - +#include #include "PathUtils.h" QString& PathUtils::resourcesPath() { +#ifdef DEBUG + static QString staticResourcePath; + if (staticResourcePath.isEmpty()) { + QDir path(__FILE__); + path.cdUp(); + staticResourcePath = path.cleanPath(path.absoluteFilePath("../../../interface/resources/")) + "/"; + } +#else #ifdef Q_OS_MAC static QString staticResourcePath = QCoreApplication::applicationDirPath() + "/../Resources/"; #else static QString staticResourcePath = QCoreApplication::applicationDirPath() + "/resources/"; #endif +#endif + return staticResourcePath; } diff --git a/libraries/ui/CMakeLists.txt b/libraries/ui/CMakeLists.txt new file mode 100644 index 0000000000..36a0a1a846 --- /dev/null +++ b/libraries/ui/CMakeLists.txt @@ -0,0 +1,12 @@ +set(TARGET_NAME ui) + +# use setup_hifi_library macro to setup our project and link appropriate Qt modules +setup_hifi_library(OpenGL Network Qml Quick Script) + +link_hifi_libraries(render-utils shared) + +add_dependency_external_projects(glm) +find_package(GLM REQUIRED) +target_include_directories(${TARGET_NAME} PUBLIC ${GLM_INCLUDE_DIRS}) + + diff --git a/libraries/ui/src/HifiMenu.cpp b/libraries/ui/src/HifiMenu.cpp new file mode 100644 index 0000000000..27517f99d7 --- /dev/null +++ b/libraries/ui/src/HifiMenu.cpp @@ -0,0 +1,279 @@ +#include "HifiMenu.h" +#include + +// FIXME can this be made a class member? +static const QString MENU_SUFFIX{ "__Menu" }; + +HIFI_QML_DEF_LAMBDA(HifiMenu, [=](QQmlContext* context, QObject* newItem) { + auto offscreenUi = DependencyManager::get(); + QObject * rootMenu = offscreenUi->getRootItem()->findChild("rootMenu"); + Q_ASSERT(rootMenu); + static_cast(newItem)->setRootMenu(rootMenu); + context->setContextProperty("rootMenu", rootMenu); +}); + +HifiMenu::HifiMenu(QQuickItem* parent) : QQuickItem(parent), _triggerMapper(this), _toggleMapper(this) { + this->setEnabled(false); + connect(&_triggerMapper, SIGNAL(mapped(QString)), this, SLOT(onTriggeredByName(const QString &))); + connect(&_toggleMapper, SIGNAL(mapped(QString)), this, SLOT(onToggledByName(const QString &))); +} + +void HifiMenu::onTriggeredByName(const QString & name) { + qDebug() << name << " triggered"; + if (_triggerActions.count(name)) { + _triggerActions[name](); + } +} + +void HifiMenu::onToggledByName(const QString & name) { + qDebug() << name << " toggled"; + if (_toggleActions.count(name)) { + QObject* menu = findMenuObject(name); + bool checked = menu->property("checked").toBool(); + _toggleActions[name](checked); + } +} + +void HifiMenu::setToggleAction(const QString & name, std::function f) { + _toggleActions[name] = f; +} + +void HifiMenu::setTriggerAction(const QString & name, std::function f) { + _triggerActions[name] = f; +} + +QObject* addMenu(QObject* parent, const QString & text) { + // FIXME add more checking here to ensure no name conflicts + QVariant returnedValue; + QMetaObject::invokeMethod(parent, "addMenu", Qt::DirectConnection, + Q_RETURN_ARG(QVariant, returnedValue), + Q_ARG(QVariant, text)); + QObject* result = returnedValue.value(); + if (result) { + result->setObjectName(text + MENU_SUFFIX); + } + return result; +} + +class QQuickMenuItem; +QObject* addItem(QObject* parent, const QString& text) { + // FIXME add more checking here to ensure no name conflicts + QQuickMenuItem* returnedValue{ nullptr }; + bool invokeResult = QMetaObject::invokeMethod(parent, "addItem", Qt::DirectConnection, + Q_RETURN_ARG(QQuickMenuItem*, returnedValue), + Q_ARG(QString, text)); + Q_ASSERT(invokeResult); + QObject* result = reinterpret_cast(returnedValue); + return result; +} + +const QObject* HifiMenu::findMenuObject(const QString & menuOption) const { + if (menuOption.isEmpty()) { + return _rootMenu; + } + const QObject* result = _rootMenu->findChild(menuOption + MENU_SUFFIX); + return result; +} + +QObject* HifiMenu::findMenuObject(const QString & menuOption) { + if (menuOption.isEmpty()) { + return _rootMenu; + } + QObject* result = _rootMenu->findChild(menuOption + MENU_SUFFIX); + return result; +} + +void HifiMenu::addMenu(const QString & parentMenu, const QString & menuOption) { + QObject* parent = findMenuObject(parentMenu); + QObject* result = ::addMenu(parent, menuOption); + Q_ASSERT(result); + result->setObjectName(menuOption + MENU_SUFFIX); + Q_ASSERT(findMenuObject(menuOption)); +} + +void HifiMenu::removeMenu(const QString& menuName) { + QObject* menu = findMenuObject(menuName); + Q_ASSERT(menu); + Q_ASSERT(menu != _rootMenu); + QMetaObject::invokeMethod(menu->parent(), "removeItem", + Q_ARG(QVariant, QVariant::fromValue(menu))); +} + +bool HifiMenu::menuExists(const QString& menuName) const { + return findMenuObject(menuName); +} + +void HifiMenu::addSeparator(const QString& parentMenu, const QString& separatorName) { + QObject * parent = findMenuObject(parentMenu); + bool invokeResult = QMetaObject::invokeMethod(parent, "addSeparator", Qt::DirectConnection); + Q_ASSERT(invokeResult); + addItem(parentMenu, separatorName); + enableItem(separatorName, false); +} + +void HifiMenu::removeSeparator(const QString& parentMenu, const QString& separatorName) { +} + +void HifiMenu::addItem(const QString & parentMenu, const QString & menuOption) { + QObject* parent = findMenuObject(parentMenu); + Q_ASSERT(parent); + QObject* result = ::addItem(parent, menuOption); + Q_ASSERT(result); + result->setObjectName(menuOption + MENU_SUFFIX); + Q_ASSERT(findMenuObject(menuOption)); + + _triggerMapper.setMapping(result, menuOption); + connect(result, SIGNAL(triggered()), &_triggerMapper, SLOT(map())); + + _toggleMapper.setMapping(result, menuOption); + connect(result, SIGNAL(toggled(bool)), &_toggleMapper, SLOT(map())); +} + +void HifiMenu::addItem(const QString & parentMenu, const QString & menuOption, std::function f) { + setTriggerAction(menuOption, f); + addItem(parentMenu, menuOption); +} + +void HifiMenu::addItem(const QString & parentMenu, const QString & menuOption, QObject* receiver, const char* slot) { + addItem(parentMenu, menuOption); + connectItem(menuOption, receiver, slot); +} + +void HifiMenu::removeItem(const QString& menuOption) { + removeMenu(menuOption); +} + +bool HifiMenu::itemExists(const QString& menuName, const QString& menuitem) const { + return findMenuObject(menuName); +} + +void HifiMenu::triggerItem(const QString& menuOption) { + QObject* menuItem = findMenuObject(menuOption); + Q_ASSERT(menuItem); + Q_ASSERT(menuItem != _rootMenu); + QMetaObject::invokeMethod(menuItem, "trigger"); +} + +QHash warned; +void warn(const QString & menuOption) { + if (!warned.contains(menuOption)) { + warned[menuOption] = menuOption; + qWarning() << "No menu item: " << menuOption; + } +} + +bool HifiMenu::isChecked(const QString& menuOption) const { + const QObject* menuItem = findMenuObject(menuOption); + if (!menuItem) { + warn(menuOption); + return false; + } + return menuItem->property("checked").toBool(); +} + +void HifiMenu::setChecked(const QString& menuOption, bool isChecked) { + QObject* menuItem = findMenuObject(menuOption); + if (!menuItem) { + warn(menuOption); + return; + } + if (menuItem->property("checked").toBool() != isChecked) { + menuItem->setProperty("checked", QVariant::fromValue(isChecked)); + Q_ASSERT(menuItem->property("checked").toBool() == isChecked); + } +} + +void HifiMenu::setCheckable(const QString& menuOption, bool checkable) { + QObject* menuItem = findMenuObject(menuOption); + if (!menuItem) { + warn(menuOption); + return; + } + + menuItem->setProperty("checkable", QVariant::fromValue(checkable)); + Q_ASSERT(menuItem->property("checkable").toBool() == checkable); +} + +void HifiMenu::setItemText(const QString& menuOption, const QString& text) { + QObject* menuItem = findMenuObject(menuOption); + if (!menuItem) { + warn(menuOption); + return; + } + if (menuItem->property("type").toInt() == 2) { + menuItem->setProperty("title", QVariant::fromValue(text)); + } else { + menuItem->setProperty("text", QVariant::fromValue(text)); + } +} + +void HifiMenu::setRootMenu(QObject* rootMenu) { + _rootMenu = rootMenu; +} + +void HifiMenu::enableItem(const QString & menuOption, bool enabled) { + QObject* menuItem = findMenuObject(menuOption); + if (!menuItem) { + warn(menuOption); + return; + } + menuItem->setProperty("enabled", QVariant::fromValue(enabled)); +} + +void HifiMenu::addCheckableItem(const QString& parentMenu, const QString& menuOption, bool checked) { + addItem(parentMenu, menuOption); + setCheckable(menuOption); + if (checked) { + setChecked(menuOption, checked); + } +} + +void HifiMenu::addCheckableItem(const QString& parentMenu, const QString& menuOption, bool checked, std::function f) { + setToggleAction(menuOption, f); + addCheckableItem(parentMenu, menuOption, checked); +} + +void HifiMenu::setItemVisible(const QString& menuOption, bool visible) { + QObject* result = findMenuObject(menuOption); + if (result) { + result->setProperty("visible", visible); + } +} + +bool HifiMenu::isItemVisible(const QString& menuOption) { + QObject* result = findMenuObject(menuOption); + if (result) { + return result->property("visible").toBool(); + } + return false; +} + +void HifiMenu::setItemShortcut(const QString& menuOption, const QString& shortcut) { + QObject* result = findMenuObject(menuOption); + if (result) { + result->setProperty("shortcut", shortcut); + } +} + +QString HifiMenu::getItemShortcut(const QString& menuOption) { + QObject* result = findMenuObject(menuOption); + if (result) { + return result->property("shortcut").toString(); + } + return QString(); +} + +void HifiMenu::addCheckableItem(const QString& parentMenu, const QString& menuOption, bool checked, QObject* receiver, const char* slot) { + addCheckableItem(parentMenu, menuOption, checked); + connectItem(menuOption, receiver, slot); +} + +void HifiMenu::connectCheckable(const QString& menuOption, QObject* receiver, const char* slot) { + QObject* result = findMenuObject(menuOption); + connect(result, SIGNAL(toggled(bool)), receiver, slot); +} + +void HifiMenu::connectItem(const QString& menuOption, QObject* receiver, const char* slot) { + QObject* result = findMenuObject(menuOption); + connect(result, SIGNAL(triggered()), receiver, slot); +} diff --git a/libraries/ui/src/HifiMenu.h b/libraries/ui/src/HifiMenu.h new file mode 100644 index 0000000000..c89c91b028 --- /dev/null +++ b/libraries/ui/src/HifiMenu.h @@ -0,0 +1,83 @@ +// +// MenuConstants.h +// +// Created by Bradley Austin Davis on 2015/04/21 +// 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 +// + +#pragma once +#ifndef hifi_MenuContants_h +#define hifi_MenuConstants_h + +#include +#include +#include +#include +#include "OffscreenUi.h" + +class HifiMenu : public QQuickItem { + Q_OBJECT + HIFI_QML_DECL_LAMBDA + +public: + HifiMenu(QQuickItem* parent = nullptr); + + void setToggleAction(const QString& name, std::function f); + void setTriggerAction(const QString& name, std::function f); + + void addMenu(const QString& parentMenu, const QString& menuOption); + void removeMenu(const QString& menuName); + bool menuExists(const QString& menuName) const; + + void addSeparator(const QString& menuName, const QString& separatorName); + void removeSeparator(const QString& menuName, const QString& separatorName); + + void addItem(const QString& parentMenu, const QString& menuOption); + void addItem(const QString& parentMenu, const QString& menuOption, std::function f); + void addItem(const QString& parentMenu, const QString& menuOption, QObject* receiver, const char* slot); + + void addCheckableItem(const QString& parentMenu, const QString& menuOption, bool checked = false); + void addCheckableItem(const QString& parentMenu, const QString& menuOption, bool checked, std::function f); + void addCheckableItem(const QString& parentMenu, const QString& menuOption, bool checked, QObject* receiver, const char* slot); + void connectCheckable(const QString& menuOption, QObject* receiver, const char* slot); + void connectItem(const QString& menuOption, QObject* receiver, const char* slot); + + void removeItem(const QString& menuitem); + bool itemExists(const QString& menuName, const QString& menuitem) const; + void triggerItem(const QString& menuOption); + void enableItem(const QString& menuOption, bool enabled = true); + bool isChecked(const QString& menuOption) const; + void setChecked(const QString& menuOption, bool checked = true); + void setCheckable(const QString& menuOption, bool checkable = true); + void setExclusiveGroup(const QString& menuOption, const QString& groupName); + void setItemText(const QString& menuOption, const QString& text); + void setItemVisible(const QString& menuOption, bool visible = true); + bool isItemVisible(const QString& menuOption); + + void setItemShortcut(const QString& menuOption, const QString& shortcut); + QString getItemShortcut(const QString& menuOption); + + void setRootMenu(QObject* rootMenu); + +private slots: + void onTriggeredByName(const QString& name); + void onToggledByName(const QString& name); + +protected: + QHash> _triggerActions; + QHash> _toggleActions; + QObject* findMenuObject(const QString& name); + const QObject* findMenuObject(const QString& name) const; + QObject* _rootMenu{ nullptr }; + QSignalMapper _triggerMapper; + QSignalMapper _toggleMapper; +}; + +#endif // hifi_MenuConstants_h + + + + diff --git a/libraries/ui/src/MessageDialog.cpp b/libraries/ui/src/MessageDialog.cpp new file mode 100644 index 0000000000..c16f5653e5 --- /dev/null +++ b/libraries/ui/src/MessageDialog.cpp @@ -0,0 +1,142 @@ +// +// +// MessageDialog.cpp +// +// Created by Bradley 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 +// +#include "MessageDialog.h" + +QML_DIALOG_DEF(MessageDialog) + + +MessageDialog::MessageDialog(QQuickItem *parent) : OffscreenQmlDialog(parent) { + _buttons = StandardButtons(Ok | Cancel); +} + +MessageDialog::~MessageDialog() { +} + +QString MessageDialog::text() const { + return _text; +} + +QString MessageDialog::informativeText() const { + return _informativeText; +} + +QString MessageDialog::detailedText() const { + return _detailedText; +} + +MessageDialog::Icon MessageDialog::icon() const { + return _icon; +} + +void MessageDialog::setVisible(bool v) { + OffscreenQmlDialog::setVisible(v); +} + +void MessageDialog::setText(const QString &arg) { + if (arg != _text) { + _text = arg; + emit textChanged(); + } +} + +void MessageDialog::setInformativeText(const QString &arg) { + if (arg != _informativeText) { + _informativeText = arg; + emit informativeTextChanged(); + } +} + +void MessageDialog::setDetailedText(const QString &arg) { + if (arg != _detailedText) { + _detailedText = arg; + emit detailedTextChanged(); + } +} + +void MessageDialog::setIcon(MessageDialog::Icon icon) { + if (icon != _icon) { + _icon = icon; + emit iconChanged(); + } +} + +void MessageDialog::setStandardButtons(StandardButtons buttons) { + if (buttons != _buttons) { + _buttons = buttons; + emit standardButtonsChanged(); + } +} + +void MessageDialog::click(StandardButton button) { + click(static_cast(button), + static_cast( + QPlatformDialogHelper::buttonRole(static_cast(button)))); +} + +QUrl MessageDialog::standardIconSource() { + switch (icon()) { + case QMessageDialogOptions::Information: + return QUrl("images/information.png"); + break; + case QMessageDialogOptions::Warning: + return QUrl("images/warning.png"); + break; + case QMessageDialogOptions::Critical: + return QUrl("images/critical.png"); + break; + case QMessageDialogOptions::Question: + return QUrl("images/question.png"); + break; + default: + return QUrl(); + break; + } +} + +MessageDialog::StandardButtons MessageDialog::standardButtons() const { + return _buttons; +} + +MessageDialog::StandardButton MessageDialog::clickedButton() const { + return _clickedButton; +} + +void MessageDialog::click(StandardButton button, QPlatformDialogHelper::ButtonRole) { + _clickedButton = button; + if (_resultCallback) { + _resultCallback(QMessageBox::StandardButton(_clickedButton)); + } + hide(); +} + +void MessageDialog::accept() { + // enter key is treated like OK + if (_clickedButton == NoButton) + _clickedButton = Ok; + if (_resultCallback) { + _resultCallback(QMessageBox::StandardButton(_clickedButton)); + } + OffscreenQmlDialog::accept(); +} + +void MessageDialog::reject() { + // escape key is treated like cancel + if (_clickedButton == NoButton) + _clickedButton = Cancel; + if (_resultCallback) { + _resultCallback(QMessageBox::StandardButton(_clickedButton)); + } + OffscreenQmlDialog::reject(); +} + +void MessageDialog::setResultCallback(OffscreenUi::ButtonCallback callback) { + _resultCallback = callback; +} diff --git a/libraries/ui/src/MessageDialog.h b/libraries/ui/src/MessageDialog.h new file mode 100644 index 0000000000..8b5a895068 --- /dev/null +++ b/libraries/ui/src/MessageDialog.h @@ -0,0 +1,98 @@ +// +// MessageDialog.h +// +// Created by Bradley 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 +// + +#pragma once +#ifndef hifi_MessageDialog_h +#define hifi_MessageDialog_h + +#include "OffscreenQmlDialog.h" +#include <5.4.1/QtGui/qpa/qplatformdialoghelper.h> + +class MessageDialog : public OffscreenQmlDialog +{ + Q_OBJECT + QML_DIALOG_DECL + +private: + Q_ENUMS(Icon) + Q_PROPERTY(QString text READ text WRITE setText NOTIFY textChanged) + Q_PROPERTY(QString informativeText READ informativeText WRITE setInformativeText NOTIFY informativeTextChanged) + Q_PROPERTY(QString detailedText READ detailedText WRITE setDetailedText NOTIFY detailedTextChanged) + Q_PROPERTY(Icon icon READ icon WRITE setIcon NOTIFY iconChanged) + Q_PROPERTY(QUrl standardIconSource READ standardIconSource NOTIFY iconChanged) + Q_PROPERTY(StandardButtons standardButtons READ standardButtons WRITE setStandardButtons NOTIFY standardButtonsChanged) + Q_PROPERTY(StandardButton clickedButton READ clickedButton NOTIFY buttonClicked) + +public: + enum Icon { + NoIcon = QMessageDialogOptions::NoIcon, + Information = QMessageDialogOptions::Information, + Warning = QMessageDialogOptions::Warning, + Critical = QMessageDialogOptions::Critical, + Question = QMessageDialogOptions::Question + }; + + + MessageDialog(QQuickItem *parent = 0); + virtual ~MessageDialog(); + + QString text() const; + QString informativeText() const; + QString detailedText() const; + Icon icon() const; + +public slots: + virtual void setVisible(bool v); + void setText(const QString &arg); + void setInformativeText(const QString &arg); + void setDetailedText(const QString &arg); + void setIcon(Icon icon); + void setStandardButtons(StandardButtons buttons); + void setResultCallback(OffscreenUi::ButtonCallback callback); + void click(StandardButton button); + QUrl standardIconSource(); + StandardButtons standardButtons() const; + StandardButton clickedButton() const; + +signals: + void textChanged(); + void informativeTextChanged(); + void detailedTextChanged(); + void iconChanged(); + void standardButtonsChanged(); + void buttonClicked(); + void discard(); + void help(); + void yes(); + void no(); + void apply(); + void reset(); + +protected slots: + virtual void click(StandardButton button, QPlatformDialogHelper::ButtonRole); + virtual void accept(); + virtual void reject(); + +private: + QString _title; + QString _text; + QString _informativeText; + QString _detailedText; + Icon _icon{ Information }; + StandardButtons _buttons; + StandardButton _clickedButton{ NoButton }; + OffscreenUi::ButtonCallback _resultCallback; +}; + +#endif // hifi_MessageDialog_h + + + + diff --git a/libraries/render-utils/src/OffscreenQmlDialog.cpp b/libraries/ui/src/OffscreenQmlDialog.cpp similarity index 55% rename from libraries/render-utils/src/OffscreenQmlDialog.cpp rename to libraries/ui/src/OffscreenQmlDialog.cpp index d1e060245d..dbd621ad85 100644 --- a/libraries/render-utils/src/OffscreenQmlDialog.cpp +++ b/libraries/ui/src/OffscreenQmlDialog.cpp @@ -13,6 +13,30 @@ OffscreenQmlDialog::OffscreenQmlDialog(QQuickItem* parent) : QQuickItem(parent) { } +OffscreenQmlDialog::~OffscreenQmlDialog() { +} + void OffscreenQmlDialog::hide() { static_cast(parent())->setEnabled(false); } + +QString OffscreenQmlDialog::title() const { + return _title; +} + +void OffscreenQmlDialog::setTitle(const QString &arg) { + if (arg != _title) { + _title = arg; + emit titleChanged(); + } +} + +void OffscreenQmlDialog::accept() { + hide(); + emit accepted(); +} + +void OffscreenQmlDialog::reject() { + hide(); + emit rejected(); +} diff --git a/libraries/ui/src/OffscreenQmlDialog.h b/libraries/ui/src/OffscreenQmlDialog.h new file mode 100644 index 0000000000..7b7c83ad65 --- /dev/null +++ b/libraries/ui/src/OffscreenQmlDialog.h @@ -0,0 +1,104 @@ +// +// OffscreenQmlDialog.h +// +// Created by Bradley 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 +// + +#pragma once +#ifndef hifi_OffscreenQmlDialog_h +#define hifi_OffscreenQmlDialog_h + +#include +#include <5.4.1/QtGui/qpa/qplatformdialoghelper.h> + +#include "OffscreenUi.h" + +#define QML_DIALOG_DECL \ +private: \ + static const QString NAME; \ + static const QUrl QML; \ +public: \ + static void registerType(); \ + static void show(std::function f = [](QQmlContext*, QObject*) {}); \ + static void toggle(std::function f = [](QQmlContext*, QObject*) {}); \ +private: + +#define QML_DIALOG_DEF(x) \ + const QUrl x::QML = QUrl(#x ".qml"); \ + const QString x::NAME = #x; \ + \ + void x::registerType() { \ + qmlRegisterType("Hifi", 1, 0, NAME.toLocal8Bit().constData()); \ + } \ + \ + void x::show(std::function f) { \ + auto offscreenUi = DependencyManager::get(); \ + offscreenUi->show(QML, NAME, f); \ + } \ + \ + void x::toggle(std::function f) { \ + auto offscreenUi = DependencyManager::get(); \ + offscreenUi->toggle(QML, NAME, f); \ + } + +class OffscreenQmlDialog : public QQuickItem +{ + Q_OBJECT + Q_PROPERTY(QString title READ title WRITE setTitle NOTIFY titleChanged) + Q_ENUMS(StandardButton) + Q_FLAGS(StandardButtons) + +public: + OffscreenQmlDialog(QQuickItem* parent = nullptr); + virtual ~OffscreenQmlDialog(); + + enum StandardButton { + NoButton = QPlatformDialogHelper::NoButton, + Ok = QPlatformDialogHelper::Ok, + Save = QPlatformDialogHelper::Save, + SaveAll = QPlatformDialogHelper::SaveAll, + Open = QPlatformDialogHelper::Open, + Yes = QPlatformDialogHelper::Yes, + YesToAll = QPlatformDialogHelper::YesToAll, + No = QPlatformDialogHelper::No, + NoToAll = QPlatformDialogHelper::NoToAll, + Abort = QPlatformDialogHelper::Abort, + Retry = QPlatformDialogHelper::Retry, + Ignore = QPlatformDialogHelper::Ignore, + Close = QPlatformDialogHelper::Close, + Cancel = QPlatformDialogHelper::Cancel, + Discard = QPlatformDialogHelper::Discard, + Help = QPlatformDialogHelper::Help, + Apply = QPlatformDialogHelper::Apply, + Reset = QPlatformDialogHelper::Reset, + RestoreDefaults = QPlatformDialogHelper::RestoreDefaults, + NButtons + }; + Q_DECLARE_FLAGS(StandardButtons, StandardButton) + +protected: + void hide(); + virtual void accept(); + virtual void reject(); + +public: + QString title() const; + void setTitle(const QString &arg); + +signals: + void accepted(); + void rejected(); + void titleChanged(); + +private: + QString _title; + +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(OffscreenQmlDialog::StandardButtons) + +#endif diff --git a/libraries/render-utils/src/OffscreenUi.cpp b/libraries/ui/src/OffscreenUi.cpp similarity index 83% rename from libraries/render-utils/src/OffscreenUi.cpp rename to libraries/ui/src/OffscreenUi.cpp index 837affab59..7b84bb763f 100644 --- a/libraries/render-utils/src/OffscreenUi.cpp +++ b/libraries/ui/src/OffscreenUi.cpp @@ -13,6 +13,11 @@ #include #include #include +#include "MessageDialog.h" + + +Q_DECLARE_LOGGING_CATEGORY(offscreenFocus) +Q_LOGGING_CATEGORY(offscreenFocus, "hifi.offscreen.focus") // Time between receiving a request to render the offscreen UI actually triggering // the render. Could possibly be increased depending on the framerate we expect to @@ -92,10 +97,10 @@ void OffscreenUi::create(QOpenGLContext* shareContext) { #ifdef DEBUG connect(_quickWindow, &QQuickWindow::focusObjectChanged, [this]{ - qDebug() << "New focus item " << _quickWindow->focusObject(); + qCDebug(offscreenFocus) << "New focus item " << _quickWindow->focusObject(); }); connect(_quickWindow, &QQuickWindow::activeFocusItemChanged, [this] { - qDebug() << "New active focus item " << _quickWindow->activeFocusItem(); + qCDebug(offscreenFocus) << "New active focus item " << _quickWindow->activeFocusItem(); }); #endif @@ -117,36 +122,45 @@ void OffscreenUi::resize(const QSize& newSize) { qDebug() << "Offscreen UI resizing to " << newSize.width() << "x" << newSize.height() << " with pixel ratio " << pixelRatio; _fboCache.setSize(newSize * pixelRatio); + if (_quickWindow) { + _quickWindow->setGeometry(QRect(QPoint(), newSize)); + } + + _quickWindow->contentItem()->setSize(newSize); + // Update our members if (_rootItem) { _rootItem->setSize(newSize); } - if (_quickWindow) { - _quickWindow->setGeometry(QRect(QPoint(), newSize)); - } - doneCurrent(); } -QQmlContext* OffscreenUi::qmlContext() { - if (nullptr == _rootItem) { - return _qmlComponent->creationContext(); - } - return QQmlEngine::contextForObject(_rootItem); +QQuickItem* OffscreenUi::getRootItem() { + return _rootItem; } +//QQmlContext* OffscreenUi::qmlContext() { +// if (nullptr == _rootItem) { +// return _qmlComponent->creationContext(); +// } +// return QQmlEngine::contextForObject(_rootItem); +//} + void OffscreenUi::setBaseUrl(const QUrl& baseUrl) { _qmlEngine->setBaseUrl(baseUrl); } -void OffscreenUi::load(const QUrl& qmlSource, std::function f) { +void OffscreenUi::load(const QUrl& qmlSource, std::function f) { qDebug() << "Loading QML from URL " << qmlSource; _qmlComponent->loadUrl(qmlSource); - if (_qmlComponent->isLoading()) { - connect(_qmlComponent, &QQmlComponent::statusChanged, this, []{}); - } else { - finishQmlLoad(); + if (_qmlComponent->isLoading()) + connect(_qmlComponent, &QQmlComponent::statusChanged, this, + [this, f](QQmlComponent::Status){ + finishQmlLoad(f); + }); + else { + finishQmlLoad(f); } } @@ -163,8 +177,8 @@ void OffscreenUi::requestRender() { } } -void OffscreenUi::finishQmlLoad() { - disconnect(_qmlComponent, &QQmlComponent::statusChanged, this, &OffscreenUi::finishQmlLoad); +void OffscreenUi::finishQmlLoad(std::function f) { + disconnect(_qmlComponent, &QQmlComponent::statusChanged, this, 0); if (_qmlComponent->isError()) { QList errorList = _qmlComponent->errors(); foreach(const QQmlError &error, errorList) { @@ -173,7 +187,8 @@ void OffscreenUi::finishQmlLoad() { return; } - QObject* newObject = _qmlComponent->create(); + QQmlContext * newContext = new QQmlContext(_qmlEngine, qApp); + QObject* newObject = _qmlComponent->beginCreate(newContext); if (_qmlComponent->isError()) { QList errorList = _qmlComponent->errors(); foreach(const QQmlError &error, errorList) @@ -184,9 +199,13 @@ void OffscreenUi::finishQmlLoad() { return; } + f(newContext, newObject); + _qmlComponent->completeCreate(); + QQuickItem* newItem = qobject_cast(newObject); if (!newItem) { qWarning("run: Not a QQuickItem"); + return; delete newObject; if (!_rootItem) { qFatal("Unable to find root QQuickItem"); @@ -197,18 +216,17 @@ void OffscreenUi::finishQmlLoad() { // Make sure we make items focusable (critical for // supporting keyboard shortcuts) newItem->setFlag(QQuickItem::ItemIsFocusScope, true); - if (!_rootItem) { // The root item is ready. Associate it with the window. _rootItem = newItem; _rootItem->setParentItem(_quickWindow->contentItem()); _rootItem->setSize(_quickWindow->renderTargetSize()); + _rootItem->forceActiveFocus(); } else { // Allow child windows to be destroyed from JS QQmlEngine::setObjectOwnership(newItem, QQmlEngine::JavaScriptOwnership); newItem->setParent(_rootItem); newItem->setParentItem(_rootItem); - newItem->setEnabled(true); } } @@ -390,54 +408,62 @@ void OffscreenUi::setProxyWindow(QWindow* window) { _renderControl->_renderWindow = window; } -void OffscreenUi::show(const QUrl& url, const QString& name) { +void OffscreenUi::show(const QUrl& url, const QString& name, std::function f) { QQuickItem* item = _rootItem->findChild(name); // First load? if (!item) { - load(url); - return; + load(url, f); + item = _rootItem->findChild(name); } item->setEnabled(true); } -void OffscreenUi::toggle(const QUrl& url, const QString& name) { +void OffscreenUi::toggle(const QUrl& url, const QString& name, std::function f) { QQuickItem* item = _rootItem->findChild(name); // First load? if (!item) { - load(url); - return; + load(url, f); + item = _rootItem->findChild(name); } item->setEnabled(!item->isEnabled()); } void OffscreenUi::messageBox(const QString& title, const QString& text, + ButtonCallback callback, QMessageBox::Icon icon, - QMessageBox::StandardButtons buttons, - ButtonCallback f) { + QMessageBox::StandardButtons buttons) { + MessageDialog::show([=](QQmlContext*ctx, QObject*item) { + MessageDialog * pDialog = item->findChild(); + pDialog->setIcon((MessageDialog::Icon)icon); + pDialog->setTitle(title); + pDialog->setText(text); + pDialog->setStandardButtons(MessageDialog::StandardButtons((int)buttons)); + pDialog->setResultCallback(callback); + }); } void OffscreenUi::information(const QString& title, const QString& text, - QMessageBox::StandardButtons buttons, - ButtonCallback callback) { - callback(QMessageBox::information(nullptr, title, text, buttons)); + ButtonCallback callback, + QMessageBox::StandardButtons buttons) { + messageBox(title, text, callback, (QMessageBox::Icon)MessageDialog::Information, buttons); } void OffscreenUi::question(const QString& title, const QString& text, - QMessageBox::StandardButtons buttons, - ButtonCallback callback) { - callback(QMessageBox::question(nullptr, title, text, buttons)); + ButtonCallback callback, + QMessageBox::StandardButtons buttons) { + messageBox(title, text, callback, (QMessageBox::Icon)MessageDialog::Question, buttons); } void OffscreenUi::warning(const QString& title, const QString& text, - QMessageBox::StandardButtons buttons, - ButtonCallback callback) { - callback(QMessageBox::warning(nullptr, title, text, buttons)); + ButtonCallback callback, + QMessageBox::StandardButtons buttons) { + messageBox(title, text, callback, (QMessageBox::Icon)MessageDialog::Warning, buttons); } void OffscreenUi::critical(const QString& title, const QString& text, - QMessageBox::StandardButtons buttons, - ButtonCallback callback) { - callback(QMessageBox::critical(nullptr, title, text, buttons)); + ButtonCallback callback, + QMessageBox::StandardButtons buttons) { + messageBox(title, text, callback, (QMessageBox::Icon)MessageDialog::Critical, buttons); } diff --git a/libraries/render-utils/src/OffscreenUi.h b/libraries/ui/src/OffscreenUi.h similarity index 52% rename from libraries/render-utils/src/OffscreenUi.h rename to libraries/ui/src/OffscreenUi.h index c64d0d833c..b58b4e59cb 100644 --- a/libraries/render-utils/src/OffscreenUi.h +++ b/libraries/ui/src/OffscreenUi.h @@ -32,6 +32,69 @@ #include "FboCache.h" #include +#define HIFI_QML_DECL \ +private: \ + static const QString NAME; \ + static const QUrl QML; \ +public: \ + static void registerType(); \ + static void show(std::function f = [](QQmlContext*, QQuickItem*) {}); \ + static void toggle(std::function f = [](QQmlContext*, QQuickItem*) {}); \ + static void load(std::function f = [](QQmlContext*, QQuickItem*) {}); \ +private: + +#define HIFI_QML_DECL_LAMBDA \ +protected: \ + static const QString NAME; \ + static const QUrl QML; \ +public: \ + static void registerType(); \ + static void show(); \ + static void toggle(); \ + static void load(); \ +private: + +#define HIFI_QML_DEF(x) \ + const QUrl x::QML = QUrl(#x ".qml"); \ + const QString x::NAME = #x; \ + \ + void x::registerType() { \ + qmlRegisterType("Hifi", 1, 0, NAME.toLocal8Bit().constData()); \ + } \ + \ + void x::show(std::function f) { \ + auto offscreenUi = DependencyManager::get(); \ + offscreenUi->show(QML, NAME, f); \ + } \ + \ + void x::toggle(std::function f) { \ + auto offscreenUi = DependencyManager::get(); \ + offscreenUi->toggle(QML, NAME, f); \ + } \ + void x::load(std::function f) { \ + auto offscreenUi = DependencyManager::get(); \ + offscreenUi->load(QML, f); \ + } + +#define HIFI_QML_DEF_LAMBDA(x, f) \ + const QUrl x::QML = QUrl(#x ".qml"); \ + const QString x::NAME = #x; \ + \ + void x::registerType() { \ + qmlRegisterType("Hifi", 1, 0, NAME.toLocal8Bit().constData()); \ + } \ + void x::show() { \ + auto offscreenUi = DependencyManager::get(); \ + offscreenUi->show(QML, NAME, f); \ + } \ + void x::toggle() { \ + auto offscreenUi = DependencyManager::get(); \ + offscreenUi->toggle(QML, NAME, f); \ + } \ + void x::load() { \ + auto offscreenUi = DependencyManager::get(); \ + offscreenUi->load(QML, f); \ + } class OffscreenUi : public OffscreenGlCanvas, public Dependency { Q_OBJECT @@ -59,16 +122,16 @@ public: virtual ~OffscreenUi(); void create(QOpenGLContext* context); void resize(const QSize& size); - void load(const QUrl& qmlSource, std::function f = [](QQmlContext*) {}); - void load(const QString& qmlSourceFile, std::function f = [](QQmlContext*) {}) { + void load(const QUrl& qmlSource, std::function f = [](QQmlContext*, QObject*) {}); + void load(const QString& qmlSourceFile, std::function f = [](QQmlContext*, QObject*) {}) { load(QUrl(qmlSourceFile), f); } - void show(const QUrl& url, const QString& name); - void toggle(const QUrl& url, const QString& name); + void show(const QUrl& url, const QString& name, std::function f = [](QQmlContext*, QObject*) {}); + void toggle(const QUrl& url, const QString& name, std::function f = [](QQmlContext*, QObject*) {}); void setBaseUrl(const QUrl& baseUrl); void addImportPath(const QString& path); - QQmlContext* qmlContext(); - + //QQmlContext* getQmlContext(); + QQuickItem* getRootItem(); void pause(); void resume(); bool isPaused() const; @@ -86,31 +149,31 @@ public: static ButtonCallback NO_OP_CALLBACK; static void messageBox(const QString& title, const QString& text, + ButtonCallback f, QMessageBox::Icon icon, - QMessageBox::StandardButtons buttons, - ButtonCallback f); + QMessageBox::StandardButtons buttons); static void information(const QString& title, const QString& text, - QMessageBox::StandardButtons buttons = QMessageBox::Ok, - ButtonCallback callback = NO_OP_CALLBACK); + ButtonCallback callback = NO_OP_CALLBACK, + QMessageBox::StandardButtons buttons = QMessageBox::Ok); static void question(const QString& title, const QString& text, - QMessageBox::StandardButtons buttons = QMessageBox::StandardButtons(QMessageBox::Yes | QMessageBox::No), - ButtonCallback callback = [](QMessageBox::StandardButton) {}); + ButtonCallback callback = NO_OP_CALLBACK, + QMessageBox::StandardButtons buttons = QMessageBox::StandardButtons(QMessageBox::Yes | QMessageBox::No)); static void warning(const QString& title, const QString& text, - QMessageBox::StandardButtons buttons = QMessageBox::Ok, - ButtonCallback callback = [](QMessageBox::StandardButton) {}); + ButtonCallback callback = NO_OP_CALLBACK, + QMessageBox::StandardButtons buttons = QMessageBox::Ok); static void critical(const QString& title, const QString& text, - QMessageBox::StandardButtons buttons = QMessageBox::Ok, - ButtonCallback callback = [](QMessageBox::StandardButton) {}); + ButtonCallback callback = NO_OP_CALLBACK, + QMessageBox::StandardButtons buttons = QMessageBox::Ok); protected: private slots: void updateQuick(); - void finishQmlLoad(); + void finishQmlLoad(std::function f); public slots: void requestUpdate(); diff --git a/tests/render-utils/src/main.cpp b/tests/render-utils/src/main.cpp index 9d7363c241..5e45bf23a2 100644 --- a/tests/render-utils/src/main.cpp +++ b/tests/render-utils/src/main.cpp @@ -10,7 +10,6 @@ #include "TextRenderer.h" #include "MatrixStack.h" -#include "OffscreenUi.h" #include #include @@ -27,6 +26,7 @@ #include #include #include + #include #include #include @@ -80,6 +80,7 @@ const QString& getQmlDir() { } return dir; } + // Create a simple OpenGL window that renders text in various ways class QTestWindow : public QWindow { Q_OBJECT @@ -88,24 +89,17 @@ class QTestWindow : public QWindow { QSize _size; TextRenderer* _textRenderer[4]; RateCounter fps; - int testQmlTexture{ 0 }; - //ProgramPtr _planeProgam; - //ShapeWrapperPtr _planeShape; protected: - void renderText(); - void renderQml(); private: void resizeWindow(const QSize& size) { _size = size; - DependencyManager::get()->resize(_size); } public: QTestWindow() { - DependencyManager::set(); setSurfaceType(QSurface::OpenGLSurface); QSurfaceFormat format; @@ -165,30 +159,10 @@ public: glClearColor(0.2f, 0.2f, 0.2f, 1); glDisable(GL_DEPTH_TEST); - auto offscreenUi = DependencyManager::get(); - offscreenUi->create(_context); - // FIXME, need to switch to a QWindow for mouse and keyboard input to work - offscreenUi->setProxyWindow(this); - // "#0e7077" + makeCurrent(); + setFramePosition(QPoint(-1000, 0)); resize(QSize(800, 600)); - - offscreenUi->setBaseUrl(QUrl::fromLocalFile(getQmlDir())); - offscreenUi->load(QUrl("TestRoot.qml")); - offscreenUi->addImportPath(getQmlDir()); - offscreenUi->addImportPath("."); - - connect(offscreenUi.data(), &OffscreenUi::textureUpdated, this, [this, offscreenUi](int textureId) { - offscreenUi->lockTexture(textureId); - assert(!glGetError()); - GLuint oldTexture = testQmlTexture; - testQmlTexture = textureId; - if (oldTexture) { - offscreenUi->releaseTexture(oldTexture); - } - }); - installEventFilter(offscreenUi.data()); - offscreenUi->resume(); } virtual ~QTestWindow() { @@ -204,28 +178,6 @@ protected: void resizeEvent(QResizeEvent* ev) override { resizeWindow(ev->size()); } - - - void keyPressEvent(QKeyEvent* event) { - switch (event->key()) { - case Qt::Key_L: - if (event->modifiers() & Qt::CTRL) { - DependencyManager::get()->toggle(QString("TestDialog.qml"), "TestDialog"); - } - break; - } - QWindow::keyPressEvent(event); - } - - void moveEvent(QMoveEvent* event) { - static qreal oldPixelRatio = 0.0; - if (devicePixelRatio() != oldPixelRatio) { - oldPixelRatio = devicePixelRatio(); - resizeWindow(size()); - } - - QWindow::moveEvent(event); - } }; #ifndef SERIF_FONT_FAMILY @@ -282,39 +234,16 @@ void QTestWindow::renderText() { } } -void QTestWindow::renderQml() { - glMatrixMode(GL_PROJECTION); - glLoadIdentity(); - glMatrixMode(GL_MODELVIEW); - glLoadIdentity(); - if (testQmlTexture > 0) { - glEnable(GL_TEXTURE_2D); - glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D, testQmlTexture); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - } - glBegin(GL_QUADS); - { - glTexCoord2f(0, 0); - glVertex2f(-1, -1); - glTexCoord2f(0, 1); - glVertex2f(-1, 1); - glTexCoord2f(1, 1); - glVertex2f(1, 1); - glTexCoord2f(1, 0); - glVertex2f(1, -1); - } - glEnd(); -} - void QTestWindow::draw() { + if (!isVisible()) { + return; + } + makeCurrent(); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glViewport(0, 0, _size.width() * devicePixelRatio(), _size.height() * devicePixelRatio()); - //renderText(); - renderQml(); + renderText(); _context->swapBuffers(this); glFinish(); @@ -327,10 +256,8 @@ void QTestWindow::draw() { } int main(int argc, char** argv) { - QApplication app(argc, argv); - //QLoggingCategory::setFilterRules("qt.quick.mouse.debug = true"); + QGuiApplication app(argc, argv); QTestWindow window; - QTimer timer; timer.setInterval(1); app.connect(&timer, &QTimer::timeout, &app, [&] { diff --git a/tests/ui/CMakeLists.txt b/tests/ui/CMakeLists.txt new file mode 100644 index 0000000000..3ff8555fa2 --- /dev/null +++ b/tests/ui/CMakeLists.txt @@ -0,0 +1,15 @@ +set(TARGET_NAME ui-tests) + +setup_hifi_project(Widgets OpenGL Network Qml Quick Script) + +if (WIN32) + add_dependency_external_projects(glew) + find_package(GLEW REQUIRED) + target_include_directories(${TARGET_NAME} PRIVATE ${GLEW_INCLUDE_DIRS}) + target_link_libraries(${TARGET_NAME} ${GLEW_LIBRARIES} wsock32.lib opengl32.lib Winmm.lib) +endif() + +# link in the shared libraries +link_hifi_libraries(ui render-utils gpu shared) + +copy_dlls_beside_windows_executable() \ No newline at end of file diff --git a/tests/ui/main.qml b/tests/ui/main.qml new file mode 100644 index 0000000000..168b9fb291 --- /dev/null +++ b/tests/ui/main.qml @@ -0,0 +1,161 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Quick Controls module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names +** of its contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.2 +import QtQuick.Layouts 1.1 +import QtQuick.Dialogs 1.1 +import QtQuick.Controls 1.2 +import "qml/UI.js" as UI +import "qml" +//import "/Users/bdavis/Git/hifi/interface/resources/qml" + +Item { + anchors.fill: parent + visible: true + //title: "Qt Quick Controls Gallery" + + MessageDialog { + id: aboutDialog + icon: StandardIcon.Information + title: "About" + text: "Qt Quick Controls Gallery" + informativeText: "This example demonstrates most of the available Qt Quick Controls." + } + + Action { + id: copyAction + text: "&Copy" + shortcut: StandardKey.Copy + iconName: "edit-copy" + enabled: (!!activeFocusItem && !!activeFocusItem["copy"]) + onTriggered: activeFocusItem.copy() + } + + Action { + id: cutAction + text: "Cu&t" + shortcut: StandardKey.Cut + iconName: "edit-cut" + enabled: (!!activeFocusItem && !!activeFocusItem["cut"]) + onTriggered: activeFocusItem.cut() + } + + Action { + id: pasteAction + text: "&Paste" + shortcut: StandardKey.Paste + iconName: "edit-paste" + enabled: (!!activeFocusItem && !!activeFocusItem["paste"]) + onTriggered: activeFocusItem.paste() + } + +// toolBar: ToolBar { +// RowLayout { +// anchors.fill: parent +// anchors.margins: spacing +// Label { +// text: UI.label +// } +// Item { Layout.fillWidth: true } +// CheckBox { +// id: enabler +// text: "Enabled" +// checked: true +// } +// } +// } + +// menuBar: MenuBar { +// Menu { +// title: "&File" +// MenuItem { +// text: "E&xit" +// shortcut: StandardKey.Quit +// onTriggered: Qt.quit() +// } +// } +// Menu { +// title: "&Edit" +// visible: tabView.currentIndex == 2 +// MenuItem { action: cutAction } +// MenuItem { action: copyAction } +// MenuItem { action: pasteAction } +// } +// Menu { +// title: "&Help" +// MenuItem { +// text: "About..." +// onTriggered: aboutDialog.open() +// } +// } +// } + + TabView { + id: tabView + + anchors.fill: parent + anchors.margins: UI.margin + tabPosition: UI.tabPosition + + Layout.minimumWidth: 360 + Layout.minimumHeight: 360 + Layout.preferredWidth: 480 + Layout.preferredHeight: 640 + + Tab { + title: "Buttons" + ButtonPage { + enabled: enabler.checked + } + } + Tab { + title: "Progress" + ProgressPage { + enabled: enabler.checked + } + } + Tab { + title: "Input" + InputPage { + enabled: enabler.checked + } + } + } +} diff --git a/tests/ui/qml/ButtonPage.qml b/tests/ui/qml/ButtonPage.qml new file mode 100644 index 0000000000..0ed7e2d6ad --- /dev/null +++ b/tests/ui/qml/ButtonPage.qml @@ -0,0 +1,128 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Quick Controls module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names +** of its contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.2 +import QtQuick.Layouts 1.1 +import QtQuick.Controls 1.2 + +ScrollView { + id: page + + horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOff + + Item { + id: content + + width: Math.max(page.viewport.width, grid.implicitWidth + 2 * grid.rowSpacing) + height: Math.max(page.viewport.height, grid.implicitHeight + 2 * grid.columnSpacing) + + GridLayout { + id: grid + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.leftMargin: grid.rowSpacing + anchors.rightMargin: grid.rowSpacing + anchors.topMargin: grid.columnSpacing + + columns: page.width < page.height ? 1 : 2 + + GroupBox { + title: "Button" + Layout.fillWidth: true + Layout.columnSpan: grid.columns + RowLayout { + anchors.fill: parent + Button { text: "OK"; isDefault: true } + Button { text: "Cancel" } + Item { Layout.fillWidth: true } + Button { + text: "Attach" + menu: Menu { + MenuItem { text: "Image" } + MenuItem { text: "Document" } + } + } + } + } + + GroupBox { + title: "CheckBox" + Layout.fillWidth: true + ColumnLayout { + anchors.fill: parent + CheckBox { text: "E-mail"; checked: true } + CheckBox { text: "Calendar"; checked: true } + CheckBox { text: "Contacts" } + } + } + + GroupBox { + title: "RadioButton" + Layout.fillWidth: true + ColumnLayout { + anchors.fill: parent + ExclusiveGroup { id: radioGroup } + RadioButton { text: "Portrait"; exclusiveGroup: radioGroup } + RadioButton { text: "Landscape"; exclusiveGroup: radioGroup } + RadioButton { text: "Automatic"; exclusiveGroup: radioGroup; checked: true } + } + } + + GroupBox { + title: "Switch" + Layout.fillWidth: true + Layout.columnSpan: grid.columns + ColumnLayout { + anchors.fill: parent + RowLayout { + Label { text: "Wi-Fi"; Layout.fillWidth: true } + Switch { checked: true } + } + RowLayout { + Label { text: "Bluetooth"; Layout.fillWidth: true } + Switch { checked: false } + } + } + } + } + } +} diff --git a/tests/ui/qml/InputPage.qml b/tests/ui/qml/InputPage.qml new file mode 100644 index 0000000000..cb1878d023 --- /dev/null +++ b/tests/ui/qml/InputPage.qml @@ -0,0 +1,114 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Quick Controls module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names +** of its contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.2 +import QtQuick.Layouts 1.1 +import QtQuick.Controls 1.2 + +ScrollView { + id: page + + horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOff + + Item { + id: content + + width: Math.max(page.viewport.width, column.implicitWidth + 2 * column.spacing) + height: Math.max(page.viewport.height, column.implicitHeight + 2 * column.spacing) + + ColumnLayout { + id: column + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.margins: column.spacing + + GroupBox { + title: "TextField" + Layout.fillWidth: true + ColumnLayout { + anchors.fill: parent + TextField { placeholderText: "..."; Layout.fillWidth: true; z: 1 } + TextField { placeholderText: "Password"; echoMode: TextInput.Password; Layout.fillWidth: true } + } + } + + GroupBox { + title: "ComboBox" + Layout.fillWidth: true + ColumnLayout { + anchors.fill: parent + ComboBox { + model: Qt.fontFamilies() + Layout.fillWidth: true + } + ComboBox { + editable: true + model: ListModel { + id: listModel + ListElement { text: "Apple" } + ListElement { text: "Banana" } + ListElement { text: "Coconut" } + ListElement { text: "Orange" } + } + onAccepted: { + if (find(currentText) === -1) { + listModel.append({text: editText}) + currentIndex = find(editText) + } + } + Layout.fillWidth: true + } + } + } + + GroupBox { + title: "SpinBox" + Layout.fillWidth: true + ColumnLayout { + anchors.fill: parent + SpinBox { value: 99; Layout.fillWidth: true; z: 1 } + SpinBox { decimals: 2; Layout.fillWidth: true } + } + } + } + } +} diff --git a/tests/ui/qml/ProgressPage.qml b/tests/ui/qml/ProgressPage.qml new file mode 100644 index 0000000000..a1fa596f79 --- /dev/null +++ b/tests/ui/qml/ProgressPage.qml @@ -0,0 +1,90 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Quick Controls module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names +** of its contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.2 +import QtQuick.Layouts 1.1 +import QtQuick.Controls 1.2 + +ScrollView { + id: page + + horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOff + + Item { + id: content + + width: Math.max(page.viewport.width, column.implicitWidth + 2 * column.spacing) + height: Math.max(page.viewport.height, column.implicitHeight + 2 * column.spacing) + + ColumnLayout { + id: column + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.margins: column.spacing + + GroupBox { + title: "ProgressBar" + Layout.fillWidth: true + ColumnLayout { + anchors.fill: parent + ProgressBar { indeterminate: true; Layout.fillWidth: true } + ProgressBar { value: slider.value; Layout.fillWidth: true } + } + } + + GroupBox { + title: "Slider" + Layout.fillWidth: true + ColumnLayout { + anchors.fill: parent + Slider { id: slider; value: 0.5; Layout.fillWidth: true } + } + } + + GroupBox { + title: "BusyIndicator" + Layout.fillWidth: true + BusyIndicator { running: true } + } + } + } +} diff --git a/tests/ui/qml/UI.js b/tests/ui/qml/UI.js new file mode 100644 index 0000000000..0286ac56a6 --- /dev/null +++ b/tests/ui/qml/UI.js @@ -0,0 +1,45 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Quick Controls module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names +** of its contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +.pragma library + +var margin = 2 +var tabPosition = Qt.TopEdge +var label = "" diff --git a/tests/ui/src/main.cpp b/tests/ui/src/main.cpp new file mode 100644 index 0000000000..4189282de1 --- /dev/null +++ b/tests/ui/src/main.cpp @@ -0,0 +1,336 @@ +// +// main.cpp +// tests/render-utils/src +// +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "OffscreenUi.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "MessageDialog.h" +#include "HifiMenu.h" + +class RateCounter { + std::vector times; + QElapsedTimer timer; +public: + RateCounter() { + timer.start(); + } + + void reset() { + times.clear(); + } + + unsigned int count() const { + return times.size() - 1; + } + + float elapsed() const { + if (times.size() < 1) { + return 0.0f; + } + float elapsed = *times.rbegin() - *times.begin(); + return elapsed; + } + + void increment() { + times.push_back(timer.elapsed() / 1000.0f); + } + + float rate() const { + if (elapsed() == 0.0f) { + return NAN; + } + return (float) count() / elapsed(); + } +}; + + +const QString & getQmlDir() { + static QString dir; + if (dir.isEmpty()) { + QDir path(__FILE__); + path.cdUp(); + dir = path.cleanPath(path.absoluteFilePath("../../../interface/resources/qml/")) + "/"; + qDebug() << "Qml Path: " << dir; + } + return dir; +} + +const QString & getTestQmlDir() { + static QString dir; + if (dir.isEmpty()) { + QDir path(__FILE__); + path.cdUp(); + dir = path.cleanPath(path.absoluteFilePath("../")) + "/"; + qDebug() << "Qml Test Path: " << dir; + } + return dir; +} + +// Create a simple OpenGL window that renders text in various ways +class QTestWindow : public QWindow, private QOpenGLFunctions { + Q_OBJECT + + QOpenGLContext * _context{ nullptr }; + QSize _size; + bool _altPressed{ false }; + RateCounter fps; + QTimer _timer; + int testQmlTexture{ 0 }; + +public: + QObject * rootMenu; + + QTestWindow() { + _timer.setInterval(1); + connect(&_timer, &QTimer::timeout, [=] { + draw(); + }); + + DependencyManager::set(); + setSurfaceType(QSurface::OpenGLSurface); + + QSurfaceFormat format; + format.setDepthBufferSize(16); + format.setStencilBufferSize(8); + format.setVersion(4, 1); + format.setProfile(QSurfaceFormat::OpenGLContextProfile::CompatibilityProfile); + format.setOption(QSurfaceFormat::DebugContext); + + setFormat(format); + + _context = new QOpenGLContext; + _context->setFormat(format); + if (!_context->create()) { + qFatal("Could not create OpenGL context"); + } + + show(); + makeCurrent(); + initializeOpenGLFunctions(); + + { + QOpenGLDebugLogger *logger = new QOpenGLDebugLogger(this); + logger->initialize(); // initializes in the current context, i.e. ctx + logger->enableMessages(); + connect(logger, &QOpenGLDebugLogger::messageLogged, this, [&](const QOpenGLDebugMessage & debugMessage) { + qDebug() << debugMessage; + }); + // logger->startLogging(QOpenGLDebugLogger::SynchronousLogging); + } + + qDebug() << (const char*)this->glGetString(GL_VERSION); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glClearColor(0.2f, 0.2f, 0.2f, 1); + glDisable(GL_DEPTH_TEST); + + MessageDialog::registerType(); + HifiMenu::registerType(); + + auto offscreenUi = DependencyManager::get(); + offscreenUi->create(_context); + connect(offscreenUi.data(), &OffscreenUi::textureUpdated, this, [this, offscreenUi](int textureId) { + offscreenUi->lockTexture(textureId); + assert(!glGetError()); + GLuint oldTexture = testQmlTexture; + testQmlTexture = textureId; + if (oldTexture) { + offscreenUi->releaseTexture(oldTexture); + } + }); + + makeCurrent(); + + offscreenUi->setProxyWindow(this); + setFramePosition(QPoint(-1000, 0)); + resize(QSize(800, 600)); + +#ifdef QML_CONTROL_GALLERY + offscreenUi->setBaseUrl(QUrl::fromLocalFile(getTestQmlDir())); + offscreenUi->load(QUrl("main.qml")); +#else + offscreenUi->setBaseUrl(QUrl::fromLocalFile(getQmlDir())); + offscreenUi->load(QUrl("TestRoot.qml")); + offscreenUi->load(QUrl("RootMenu.qml")); + HifiMenu::load(); + QObject* menuObject = offscreenUi->getRootItem()->findChild("HifiMenu"); + HifiMenu* menu = offscreenUi->getRootItem()->findChild(); + menu->addMenu("", "File"); + menu->addMenuItem("File", "Quit", []{ + QApplication::quit(); + }); + menu->addCheckableMenuItem("File", "Toggle", false, [](bool toggled) { + qDebug() << "Toggle is " << toggled; + }); + menu->addMenu("", "Edit"); + menu->addMenuItem("Edit", "Undo"); + menu->addMenuItem("Edit", "Redo"); + menu->addMenuItem("Edit", "Copy"); + menu->addMenuItem("Edit", "Cut"); + menu->addMenuItem("Edit", "Paste"); + menu->addMenu("", "Long Menu Name..."); +#endif + installEventFilter(offscreenUi.data()); + offscreenUi->resume(); + _timer.start(); + } + + virtual ~QTestWindow() { + DependencyManager::destroy(); + } + +private: + void draw() { + if (!isVisible()) { + return; + } + + makeCurrent(); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + glViewport(0, 0, _size.width() * devicePixelRatio(), _size.height() * devicePixelRatio()); + + renderQml(); + + _context->swapBuffers(this); + glFinish(); + + fps.increment(); + if (fps.elapsed() >= 2.0f) { + qDebug() << "FPS: " << fps.rate(); + fps.reset(); + } + } + + void makeCurrent() { + _context->makeCurrent(this); + } + + void renderQml(); + + void resizeWindow(const QSize & size) { + _size = size; + DependencyManager::get()->resize(_size); + } + + +protected: + void resizeEvent(QResizeEvent * ev) override { + resizeWindow(ev->size()); + } + + + void keyPressEvent(QKeyEvent *event) { + _altPressed = Qt::Key_Alt == event->key(); + switch (event->key()) { + case Qt::Key_L: + if (event->modifiers() & Qt::CTRL) { + auto offscreenUi = DependencyManager::get(); + HifiMenu * menu = offscreenUi->findChild(); + menu->addMenuItem("", "Test 3"); + menu->addMenuItem("File", "Test 3"); + } + break; + case Qt::Key_K: + if (event->modifiers() & Qt::CTRL) { + OffscreenUi::question("Message title", "Message contents", [](QMessageBox::Button b){ + qDebug() << b; + }); + } + break; + case Qt::Key_J: + if (event->modifiers() & Qt::CTRL) { + auto offscreenUi = DependencyManager::get(); + rootMenu = offscreenUi->getRootItem()->findChild("rootMenu"); + QMetaObject::invokeMethod(rootMenu, "popup"); + } + break; + } + QWindow::keyPressEvent(event); + } + QQmlContext* menuContext{ nullptr }; + void keyReleaseEvent(QKeyEvent *event) { + if (_altPressed && Qt::Key_Alt == event->key()) { + HifiMenu::toggle(); + } + } + + void moveEvent(QMoveEvent *event) { + static qreal oldPixelRatio = 0.0; + if (devicePixelRatio() != oldPixelRatio) { + oldPixelRatio = devicePixelRatio(); + resizeWindow(size()); + } + QWindow::moveEvent(event); + } +}; + +void QTestWindow::renderQml() { + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + if (testQmlTexture > 0) { + glEnable(GL_TEXTURE_2D); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, testQmlTexture); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + } + glBegin(GL_QUADS); + { + glTexCoord2f(0, 0); + glVertex2f(-1, -1); + glTexCoord2f(0, 1); + glVertex2f(-1, 1); + glTexCoord2f(1, 1); + glVertex2f(1, 1); + glTexCoord2f(1, 0); + glVertex2f(1, -1); + } + glEnd(); +} + + +const char * LOG_FILTER_RULES = R"V0G0N( +*.debug=false +qt.quick.mouse.debug=false +)V0G0N"; + +int main(int argc, char** argv) { + QGuiApplication app(argc, argv); +// QLoggingCategory::setFilterRules(LOG_FILTER_RULES); + QTestWindow window; + app.exec(); + return 0; +} + +#include "main.moc"