From 16efa2a46fc850772b1723e502421afa1924954a Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Fri, 17 Apr 2015 16:58:29 -0700 Subject: [PATCH] Improving toggle visibility and loading behavior --- interface/resources/qml/AddressBarDialog.qml | 9 +- interface/resources/qml/CustomDialog.qml | 26 +++- interface/resources/qml/LoginDialog.qml | 6 + interface/resources/qml/Root.qml | 9 +- interface/resources/qml/TestDialog.qml | 10 +- interface/resources/qml/TestRoot.qml | 25 ++-- interface/resources/qml/componentCreation.js | 4 +- interface/src/Application.cpp | 4 +- interface/src/ui/AddressBarDialog.cpp | 7 +- libraries/render-utils/src/OffscreenUi.cpp | 133 +++++++++++++------ libraries/render-utils/src/OffscreenUi.h | 50 +++++-- tests/render-utils/src/main.cpp | 46 ++++--- 12 files changed, 231 insertions(+), 98 deletions(-) diff --git a/interface/resources/qml/AddressBarDialog.qml b/interface/resources/qml/AddressBarDialog.qml index 9508481309..ec1480928a 100644 --- a/interface/resources/qml/AddressBarDialog.qml +++ b/interface/resources/qml/AddressBarDialog.qml @@ -5,16 +5,21 @@ import QtQuick.Window 2.2 import QtQuick.Controls.Styles 1.3 CustomDialog { - id: dialog title: "Go to..." objectName: "AddressBarDialog" - SystemPalette { id: myPalette; colorGroup: SystemPalette.Active } height: 128 width: 512 + destroyOnCloseButton: false onVisibleChanged: { if (!visible) { reset(); + } + } + + onEnabledChanged: { + if (enabled) { + addressLine.forceActiveFocus(); } } diff --git a/interface/resources/qml/CustomDialog.qml b/interface/resources/qml/CustomDialog.qml index a681612f2c..dd051566e3 100644 --- a/interface/resources/qml/CustomDialog.qml +++ b/interface/resources/qml/CustomDialog.qml @@ -12,7 +12,9 @@ Item { height: 256 scale: 0.0 enabled: false - visible: false + property int animationDuration: 400 + property bool destroyOnInvisible: false + property bool destroyOnCloseButton: true onEnabledChanged: { scale = enabled ? 1.0 : 0.0 @@ -22,14 +24,24 @@ Item { visible = (scale != 0.0); } - Component.onCompleted: { - scale = 1.0 + onVisibleChanged: { + if (!visible && destroyOnInvisible) { + console.log("Destroying closed component"); + destroy(); + } + } + + function close() { + if (destroyOnCloseButton) { + destroyOnInvisible = true + } + enabled = false; } Behavior on scale { NumberAnimation { //This specifies how long the animation takes - duration: 400 + duration: dialog.animationDuration //This selects an easing curve to interpolate with, the default is Easing.Linear easing.type: Easing.InOutBounce } @@ -81,8 +93,8 @@ Item { target: dialog minimumX: 0 minimumY: 0 - maximumX: dialog.parent.width - dialog.width - maximumY: dialog.parent.height - dialog.height + maximumX: dialog.parent ? dialog.parent.width - dialog.width : 0 + maximumY: dialog.parent ? dialog.parent.height - dialog.height : 0 } } Image { @@ -97,7 +109,7 @@ Item { MouseArea { anchors.fill: parent onClicked: { - dialog.destroy() + dialog.close(); } } } diff --git a/interface/resources/qml/LoginDialog.qml b/interface/resources/qml/LoginDialog.qml index e96309f625..be69b65ef7 100644 --- a/interface/resources/qml/LoginDialog.qml +++ b/interface/resources/qml/LoginDialog.qml @@ -18,6 +18,12 @@ CustomDialog { } } + onEnabledChanged: { + if (enabled) { + username.forceActiveFocus(); + } + } + function reset() { username.text = "" password.text = "" diff --git a/interface/resources/qml/Root.qml b/interface/resources/qml/Root.qml index f290a8b5ca..9422ef123d 100644 --- a/interface/resources/qml/Root.qml +++ b/interface/resources/qml/Root.qml @@ -1,14 +1,9 @@ +import Hifi 1.0 import QtQuick 2.3 -import "componentCreation.js" as Creator - -Item { +Root { id: root width: 1280 height: 720 - - function loadChild(url) { - Creator.createObject(root, url) - } } diff --git a/interface/resources/qml/TestDialog.qml b/interface/resources/qml/TestDialog.qml index 69aad4cdc8..1fe8676bc6 100644 --- a/interface/resources/qml/TestDialog.qml +++ b/interface/resources/qml/TestDialog.qml @@ -5,12 +5,19 @@ import QtQuick.Dialogs 1.2 import QtQuick.Controls.Styles 1.3 CustomDialog { - title: "Test Dlg" + title: "Test Dialog" id: testDialog objectName: "TestDialog" width: 512 height: 512 + animationDuration: 200 + onEnabledChanged: { + if (enabled) { + edit.forceActiveFocus(); + } + } + Item { id: clientArea // The client area @@ -31,6 +38,7 @@ CustomDialog { CustomTextEdit { + id: edit anchors.left: parent.left anchors.leftMargin: 12 anchors.right: parent.right diff --git a/interface/resources/qml/TestRoot.qml b/interface/resources/qml/TestRoot.qml index a3ef5f8fe9..158c0b7a54 100644 --- a/interface/resources/qml/TestRoot.qml +++ b/interface/resources/qml/TestRoot.qml @@ -1,25 +1,34 @@ +import Hifi 1.0 import QtQuick 2.3 -import "componentCreation.js" as Creator - -Item { +Root { id: root width: 1280 height: 720 - function loadChild(url) { - Creator.createObject(root, url) + CustomButton { + id: messageBox + anchors.right: createDialog.left + anchors.rightMargin: 24 + anchors.bottom: parent.bottom + anchors.bottomMargin: 24 + text: "Message" + onClicked: { + console.log("Foo") + root.information("a") + console.log("Bar") + } } - CustomButton { + id: createDialog anchors.right: parent.right anchors.rightMargin: 24 anchors.bottom: parent.bottom anchors.bottomMargin: 24 - text: "Test" + text: "Create" onClicked: { - loadChild("TestDialog.qml"); + root.loadChild("TestDialog.qml"); } } } diff --git a/interface/resources/qml/componentCreation.js b/interface/resources/qml/componentCreation.js index 6e6469adfb..15a828d6f8 100644 --- a/interface/resources/qml/componentCreation.js +++ b/interface/resources/qml/componentCreation.js @@ -18,10 +18,12 @@ function finishCreation() { // Error Handling console.log("Error creating object"); } else { - instance.focus = true; + 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/src/Application.cpp b/interface/src/Application.cpp index d0ae13ce21..18a325080c 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -754,8 +754,8 @@ void Application::initializeUi() { offscreenUi->create(_glWidget->context()->contextHandle()); offscreenUi->resize(_glWidget->size()); offscreenUi->setProxyWindow(_window->windowHandle()); - auto rootQml = PathUtils::resourcesPath() + "qml/Root.qml"; - offscreenUi->loadQml(QUrl::fromLocalFile(rootQml)); + offscreenUi->setBaseUrl(QUrl::fromLocalFile(PathUtils::resourcesPath() + "/qml/")); + offscreenUi->load("Root.qml"); offscreenUi->setMouseTranslator([this](const QPointF& p){ if (OculusManager::isConnected()) { glm::vec2 pos = _applicationOverlay.screenToOverlay(toGlm(p)); diff --git a/interface/src/ui/AddressBarDialog.cpp b/interface/src/ui/AddressBarDialog.cpp index befeb40cce..5ef9b930ac 100644 --- a/interface/src/ui/AddressBarDialog.cpp +++ b/interface/src/ui/AddressBarDialog.cpp @@ -35,14 +35,13 @@ void AddressBarDialog::loadAddress(const QString & address) { } } -// TODO port to a QML based message box void AddressBarDialog::displayAddressOfflineMessage() { - QMessageBox::information(nullptr, "Address offline", + OffscreenUi::information("Address offline", "That user or place is currently offline."); } -// TODO port to a QML based message box void AddressBarDialog::displayAddressNotFoundMessage() { - QMessageBox::information(nullptr, "Address not found", + OffscreenUi::information("Address not found", "There is no address information for that user or place."); } + diff --git a/libraries/render-utils/src/OffscreenUi.cpp b/libraries/render-utils/src/OffscreenUi.cpp index 68bb3eeacd..d9dd030758 100644 --- a/libraries/render-utils/src/OffscreenUi.cpp +++ b/libraries/render-utils/src/OffscreenUi.cpp @@ -12,8 +12,29 @@ #include #include #include +#include + +class OffscreenUiRoot : public QQuickItem { + Q_OBJECT +public: + + OffscreenUiRoot(QQuickItem *parent = 0); + Q_INVOKABLE void information(const QString & title, const QString & text); + Q_INVOKABLE void loadChild(const QUrl & url) { + DependencyManager::get()->load(url); + } +}; + + +OffscreenUiRoot::OffscreenUiRoot(QQuickItem *parent) : QQuickItem(parent) { +} + +void OffscreenUiRoot::information(const QString & title, const QString & text = "foo") { + OffscreenUi::information(title, text); +} OffscreenUi::OffscreenUi() { + ::qmlRegisterType("Hifi", 1, 0, "Root"); } OffscreenUi::~OffscreenUi() { @@ -62,19 +83,18 @@ void OffscreenUi::create(QOpenGLContext * shareContext) { // is needed too). connect(_renderControl, &QQuickRenderControl::renderRequested, this, &OffscreenUi::requestRender); connect(_renderControl, &QQuickRenderControl::sceneChanged, this, &OffscreenUi::requestUpdate); - connect(_quickWindow, &QQuickWindow::focusObjectChanged, this, [this](QObject *object){ - OffscreenUi * p = this; - qDebug() << "Focus changed to " << object; - }); _quickWindow->focusObject(); _qmlComponent = new QQmlComponent(_qmlEngine); - // Initialize the render control and our OpenGL resources. makeCurrent(); _renderControl->initialize(&_context); } +void OffscreenUi::addImportPath(const QString & path) { + _qmlEngine->addImportPath(path); +} + void OffscreenUi::resize(const QSize & newSize) { makeCurrent(); @@ -102,10 +122,15 @@ QQmlContext * OffscreenUi::qmlContext() { return QQmlEngine::contextForObject(_rootItem); } -void OffscreenUi::loadQml(const QUrl & qmlSource, std::function f) { +void OffscreenUi::setBaseUrl(const QUrl & baseUrl) { + _qmlEngine->setBaseUrl(baseUrl); +} + +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, &OffscreenUi::finishQmlLoad); + connect(_qmlComponent, &QQmlComponent::statusChanged, this, [] {}); else finishQmlLoad(); } @@ -131,30 +156,43 @@ void OffscreenUi::finishQmlLoad() { return; } - QObject *rootObject = _qmlComponent->create(); + QObject *newObject = _qmlComponent->create(); if (_qmlComponent->isError()) { QList errorList = _qmlComponent->errors(); foreach(const QQmlError &error, errorList) qWarning() << error.url() << error.line() << error; - qFatal("Unable to finish loading QML"); + if (!_rootItem) { + qFatal("Unable to finish loading QML root"); + } return; } - _rootItem = qobject_cast(rootObject); - if (!_rootItem) { + QQuickItem * newItem = qobject_cast(newObject); + if (!newItem) { qWarning("run: Not a QQuickItem"); - delete rootObject; - qFatal("Unable to find root QQuickItem"); + delete newObject; + if (!_rootItem) { + qFatal("Unable to find root QQuickItem"); + } return; } - // Make sure we can assign focus to the root item (critical for + // Make sure we make items focusable (critical for // supporting keyboard shortcuts) - _rootItem->setFlag(QQuickItem::ItemIsFocusScope, true); - // The root item is ready. Associate it with the window. - _rootItem->setParentItem(_quickWindow->contentItem()); - _rootItem->setSize(_quickWindow->renderTargetSize()); - qDebug() << "Finished setting up QML provider"; + 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()); + } else { + // Allow child windows to be destroyed from JS + QQmlEngine::setObjectOwnership(newItem, QQmlEngine::JavaScriptOwnership); + newItem->setParent(_rootItem); + newItem->setParentItem(_rootItem); + newItem->setEnabled(true); + } } @@ -240,7 +278,7 @@ bool OffscreenUi::eventFilter(QObject * dest, QEvent * e) { case QEvent::KeyRelease: { e->ignore(); - if (QApplication::sendEvent(_quickWindow, e)) { + if (QCoreApplication::sendEvent(_quickWindow, e)) { return e->isAccepted(); } } @@ -302,38 +340,55 @@ void OffscreenUi::setProxyWindow(QWindow * window) { void OffscreenUi::show(const QUrl & url, const QString & name) { QQuickItem * item = _rootItem->findChild(name); + // First load? if (nullptr == item) { load(url); - item = _rootItem->findChild(name); - } - - if (nullptr != item) { - item->setEnabled(true); + return; } + item->setEnabled(true); } void OffscreenUi::toggle(const QUrl & url, const QString & name) { QQuickItem * item = _rootItem->findChild(name); // First load? if (nullptr == item) { - show(url, name); + load(url); return; } + item->setEnabled(!item->isEnabled()); +} - // Toggle the visibity AND the enabled flag (otherwise invisible - // dialogs can still swallow keyboard input) - bool newFlag = !item->isEnabled(); - item->setEnabled(newFlag); +void OffscreenUi::messageBox(const QString &title, const QString &text, + QMessageBox::Icon icon, + QMessageBox::StandardButtons buttons, + ButtonCallback f) { +} + +void OffscreenUi::information(const QString &title, const QString &text, + QMessageBox::StandardButtons buttons, + ButtonCallback callback) { + callback(QMessageBox::information(nullptr, title, text, buttons)); +} + +void OffscreenUi::question(const QString &title, const QString &text, + QMessageBox::StandardButtons buttons, + ButtonCallback callback) { + callback(QMessageBox::question(nullptr, title, text, buttons)); +} + +void OffscreenUi::warning(const QString &title, const QString &text, + QMessageBox::StandardButtons buttons, + ButtonCallback callback) { + callback(QMessageBox::warning(nullptr, title, text, buttons)); +} + +void OffscreenUi::critical(const QString &title, const QString &text, + QMessageBox::StandardButtons buttons, + ButtonCallback callback) { + callback(QMessageBox::critical(nullptr, title, text, buttons)); } -void OffscreenUi::load(const QUrl & url) { - qDebug() << "Loading from url: " << url; - QVariant returnedValue; - QVariant msg = url; - QMetaObject::invokeMethod(_rootItem, "loadChild", - Q_RETURN_ARG(QVariant, returnedValue), - Q_ARG(QVariant, msg)); - qDebug() << "QML function returned:" << returnedValue.toString(); -} +OffscreenUi::ButtonCallback OffscreenUi::NO_OP_CALLBACK = [](QMessageBox::StandardButton) {}; +#include "OffscreenUi.moc" \ No newline at end of file diff --git a/libraries/render-utils/src/OffscreenUi.h b/libraries/render-utils/src/OffscreenUi.h index b9cf5d2226..73c022e4be 100644 --- a/libraries/render-utils/src/OffscreenUi.h +++ b/libraries/render-utils/src/OffscreenUi.h @@ -12,26 +12,25 @@ #ifndef hifi_OffscreenUi_h #define hifi_OffscreenUi_h - -#include -#include #include #include #include #include #include #include -#include -#include -#include #include -#include +#include + #include #include + +#include +#include #include -#include "FboCache.h" #include "OffscreenGlCanvas.h" +#include "FboCache.h" +#include class OffscreenUi : public OffscreenGlCanvas, public Dependency { @@ -60,11 +59,14 @@ public: virtual ~OffscreenUi(); void create(QOpenGLContext * context); void resize(const QSize & size); - void loadQml(const QUrl & qmlSource, std::function f = [](QQmlContext*) {}); - void load(const QUrl & url); + void load(const QUrl & qmlSource, std::function f = [](QQmlContext*) {}); + void load(const QString & qmlSourceFile, std::function f = [](QQmlContext*) {}) { + load(QUrl(qmlSourceFile), f); + } void show(const QUrl & url, const QString & name); void toggle(const QUrl & url, const QString & name); - + void setBaseUrl(const QUrl & baseUrl); + void addImportPath(const QString & path); QQmlContext * qmlContext(); void pause(); @@ -77,6 +79,32 @@ public: _mouseTranslator = mt; } + + // Messagebox replacement functions + using ButtonCallback = std::function < void(QMessageBox::StandardButton) >; + static ButtonCallback NO_OP_CALLBACK; + + static void messageBox(const QString &title, const QString &text, + QMessageBox::Icon icon, + QMessageBox::StandardButtons buttons, + ButtonCallback f); + + static void information(const QString &title, const QString &text, + QMessageBox::StandardButtons buttons = QMessageBox::Ok, + ButtonCallback callback = NO_OP_CALLBACK); + + static void question(const QString &title, const QString &text, + QMessageBox::StandardButtons buttons = QMessageBox::StandardButtons(QMessageBox::Yes | QMessageBox::No), + ButtonCallback callback = [](QMessageBox::StandardButton) {}); + + static void warning(const QString &title, const QString &text, + QMessageBox::StandardButtons buttons = QMessageBox::Ok, + ButtonCallback callback = [](QMessageBox::StandardButton) {}); + + static void critical(const QString &title, const QString &text, + QMessageBox::StandardButtons buttons = QMessageBox::Ok, + ButtonCallback callback = [](QMessageBox::StandardButton) {}); + protected: private slots: diff --git a/tests/render-utils/src/main.cpp b/tests/render-utils/src/main.cpp index 556fef0561..eb61fd1f72 100644 --- a/tests/render-utils/src/main.cpp +++ b/tests/render-utils/src/main.cpp @@ -69,6 +69,17 @@ public: } }; + +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; +} // Create a simple OpenGL window that renders text in various ways class QTestWindow : public QWindow { Q_OBJECT @@ -77,7 +88,6 @@ class QTestWindow : public QWindow { QSize _size; TextRenderer* _textRenderer[4]; RateCounter fps; - OffscreenUi _offscreenUi; int testQmlTexture{ 0 }; //ProgramPtr _planeProgam; //ShapeWrapperPtr _planeShape; @@ -90,11 +100,12 @@ protected: private: void resizeWindow(const QSize & size) { _size = size; - _offscreenUi.resize(_size); + DependencyManager::get()->resize(_size); } public: QTestWindow() { + DependencyManager::set(); setSurfaceType(QSurface::OpenGLSurface); QSurfaceFormat format; @@ -154,30 +165,30 @@ public: glClearColor(0.2f, 0.2f, 0.2f, 1); glDisable(GL_DEPTH_TEST); - _offscreenUi.create(_context); + auto offscreenUi = DependencyManager::get(); + offscreenUi->create(_context); // FIXME, need to switch to a QWindow for mouse and keyboard input to work - _offscreenUi.setProxyWindow(this); + offscreenUi->setProxyWindow(this); // "#0e7077" setFramePosition(QPoint(-1000, 0)); resize(QSize(800, 600)); - QApplication::applicationDirPath(); - QDir path(__FILE__); - path.cdUp(); - static const QString f(path.cleanPath(path.absoluteFilePath("../../../interface/resources/qml/TestRoot.qml"))); + offscreenUi->setBaseUrl(QUrl::fromLocalFile(getQmlDir())); + offscreenUi->load(QUrl("TestRoot.qml")); + offscreenUi->addImportPath(getQmlDir()); + offscreenUi->addImportPath("."); - _offscreenUi.loadQml(QUrl::fromLocalFile(f)); - connect(&_offscreenUi, &OffscreenUi::textureUpdated, this, [&](int textureId) { - _offscreenUi.lockTexture(textureId); + 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); + offscreenUi->releaseTexture(oldTexture); } }); - installEventFilter(&_offscreenUi); - _offscreenUi.resume(); + installEventFilter(offscreenUi.data()); + offscreenUi->resume(); } virtual ~QTestWindow() { @@ -197,8 +208,10 @@ protected: void keyPressEvent(QKeyEvent *event) { switch (event->key()) { - case Qt::Key_Slash: - _offscreenUi.toggle(QString("TestDialog.qml"), "TestDialog"); + case Qt::Key_L: + if (event->modifiers() & Qt::CTRL) { + DependencyManager::get()->toggle(QString("TestDialog.qml"), "TestDialog"); + } break; } QWindow::keyPressEvent(event); @@ -319,6 +332,7 @@ int main(int argc, char** argv) { QApplication app(argc, argv); //QLoggingCategory::setFilterRules("qt.quick.mouse.debug = true"); QTestWindow window; + QTimer timer; timer.setInterval(1); app.connect(&timer, &QTimer::timeout, &app, [&] {