diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index b13fd3dda9..0594acec6a 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -136,7 +136,6 @@ #include #include #include -#include #include #include #include @@ -213,6 +212,7 @@ #include "ui/UpdateDialog.h" #include "ui/DomainConnectionModel.h" #include "ui/Keyboard.h" +#include "ui/InteractiveWindow.h" #include "Util.h" #include "InterfaceParentFinder.h" #include "ui/OctreeStatsProvider.h" diff --git a/interface/src/scripting/DesktopScriptingInterface.cpp b/interface/src/scripting/DesktopScriptingInterface.cpp index bda06cda48..42a51dbda3 100644 --- a/interface/src/scripting/DesktopScriptingInterface.cpp +++ b/interface/src/scripting/DesktopScriptingInterface.cpp @@ -22,6 +22,13 @@ #include #include +static const QVariantMap DOCK_AREA { + { "TOP", DockArea::TOP }, + { "BOTTOM", DockArea::BOTTOM }, + { "LEFT", DockArea::LEFT }, + { "RIGHT", DockArea::RIGHT } +}; + int DesktopScriptingInterface::getWidth() { QSize size = qApp->getWindow()->windowHandle()->screen()->virtualSize(); return size.width(); @@ -39,6 +46,10 @@ QVariantMap DesktopScriptingInterface::getPresentationMode() { return presentationModes; } +QVariantMap DesktopScriptingInterface::getDockArea() { + return DOCK_AREA; +} + void DesktopScriptingInterface::setHUDAlpha(float alpha) { qApp->getApplicationCompositor().setAlpha(alpha); } diff --git a/interface/src/scripting/DesktopScriptingInterface.h b/interface/src/scripting/DesktopScriptingInterface.h index c8e251eb3e..c14baa7861 100644 --- a/interface/src/scripting/DesktopScriptingInterface.h +++ b/interface/src/scripting/DesktopScriptingInterface.h @@ -17,7 +17,7 @@ #include -#include "InteractiveWindow.h" +#include "ui/InteractiveWindow.h" /**jsdoc * @namespace Desktop @@ -37,10 +37,11 @@ class DesktopScriptingInterface : public QObject, public Dependency { Q_PROPERTY(int height READ getHeight) // Physical height of screen(s) including task bars and system menus Q_PROPERTY(QVariantMap PresentationMode READ getPresentationMode CONSTANT FINAL) + Q_PROPERTY(QVariantMap DockArea READ getDockArea CONSTANT FINAL) Q_PROPERTY(int ALWAYS_ON_TOP READ flagAlwaysOnTop CONSTANT FINAL) Q_PROPERTY(int CLOSE_BUTTON_HIDES READ flagCloseButtonHides CONSTANT FINAL) -public: +public: Q_INVOKABLE void setHUDAlpha(float alpha); Q_INVOKABLE void show(const QString& path, const QString& title); @@ -54,6 +55,8 @@ private: static int flagAlwaysOnTop() { return AlwaysOnTop; } static int flagCloseButtonHides() { return CloseButtonHides; } + static QVariantMap getDockArea(); + Q_INVOKABLE static QVariantMap getPresentationMode(); }; diff --git a/libraries/ui/src/InteractiveWindow.cpp b/interface/src/ui/InteractiveWindow.cpp similarity index 58% rename from libraries/ui/src/InteractiveWindow.cpp rename to interface/src/ui/InteractiveWindow.cpp index 5f7999f826..3e0aee47c7 100644 --- a/libraries/ui/src/InteractiveWindow.cpp +++ b/interface/src/ui/InteractiveWindow.cpp @@ -11,12 +11,15 @@ #include "InteractiveWindow.h" +#include "Application.h" #include #include #include #include +#include #include +#include #include #include "OffscreenUi.h" @@ -40,9 +43,17 @@ static const char* const VISIBLE_PROPERTY = "visible"; static const char* const INTERACTIVE_WINDOW_VISIBLE_PROPERTY = "interactiveWindowVisible"; static const char* const EVENT_BRIDGE_PROPERTY = "eventBridge"; static const char* const PRESENTATION_MODE_PROPERTY = "presentationMode"; +static const char* const DOCKED_PROPERTY = "presentationWindowInfo"; +static const char* const DOCK_AREA_PROPERTY = "dockArea"; static const QStringList KNOWN_SCHEMES = QStringList() << "http" << "https" << "file" << "about" << "atp" << "qrc"; +static const int DEFAULT_HEIGHT = 60; + +static void dockWidgetDeleter(DockWidget* dockWidget) { + dockWidget->deleteLater(); +} + void registerInteractiveWindowMetaType(QScriptEngine* engine) { qScriptRegisterMetaType(engine, interactiveWindowPointerToScriptValue, interactiveWindowPointerFromScriptValue); } @@ -58,55 +69,116 @@ void interactiveWindowPointerFromScriptValue(const QScriptValue& object, Interac } InteractiveWindow::InteractiveWindow(const QString& sourceUrl, const QVariantMap& properties) { - auto offscreenUi = DependencyManager::get(); + bool docked = false; + InteractiveWindowPresentationMode presentationMode = InteractiveWindowPresentationMode::Native; + + if (properties.contains(PRESENTATION_MODE_PROPERTY)) { + presentationMode = (InteractiveWindowPresentationMode) properties[PRESENTATION_MODE_PROPERTY].toInt(); + } + + if (properties.contains(DOCKED_PROPERTY) && presentationMode == InteractiveWindowPresentationMode::Native) { + QVariantMap nativeWindowInfo = properties[DOCKED_PROPERTY].toMap(); + Qt::DockWidgetArea dockArea = Qt::TopDockWidgetArea; + QString title; + QSize windowSize(DEFAULT_HEIGHT, DEFAULT_HEIGHT); - // Build the event bridge and wrapper on the main thread - offscreenUi->loadInNewContext(CONTENT_WINDOW_QML, [&](QQmlContext* context, QObject* object) { - _qmlWindow = object; - context->setContextProperty(EVENT_BRIDGE_PROPERTY, this); - if (properties.contains(FLAGS_PROPERTY)) { - object->setProperty(FLAGS_PROPERTY, properties[FLAGS_PROPERTY].toUInt()); - } - if (properties.contains(PRESENTATION_MODE_PROPERTY)) { - object->setProperty(PRESENTATION_MODE_PROPERTY, properties[PRESENTATION_MODE_PROPERTY].toInt()); - } if (properties.contains(TITLE_PROPERTY)) { - object->setProperty(TITLE_PROPERTY, properties[TITLE_PROPERTY].toString()); + title = properties[TITLE_PROPERTY].toString(); } if (properties.contains(SIZE_PROPERTY)) { const auto size = vec2FromVariant(properties[SIZE_PROPERTY]); - object->setProperty(INTERACTIVE_WINDOW_SIZE_PROPERTY, QSize(size.x, size.y)); - } - if (properties.contains(POSITION_PROPERTY)) { - const auto position = vec2FromVariant(properties[POSITION_PROPERTY]); - object->setProperty(INTERACTIVE_WINDOW_POSITION_PROPERTY, QPointF(position.x, position.y)); - } - if (properties.contains(VISIBLE_PROPERTY)) { - object->setProperty(VISIBLE_PROPERTY, properties[INTERACTIVE_WINDOW_VISIBLE_PROPERTY].toBool()); + windowSize = QSize(size.x, size.y); } - connect(object, SIGNAL(sendToScript(QVariant)), this, SLOT(qmlToScript(const QVariant&)), Qt::QueuedConnection); - connect(object, SIGNAL(interactiveWindowPositionChanged()), this, SIGNAL(positionChanged()), Qt::QueuedConnection); - connect(object, SIGNAL(interactiveWindowSizeChanged()), this, SIGNAL(sizeChanged()), Qt::QueuedConnection); - connect(object, SIGNAL(interactiveWindowVisibleChanged()), this, SIGNAL(visibleChanged()), Qt::QueuedConnection); - connect(object, SIGNAL(presentationModeChanged()), this, SIGNAL(presentationModeChanged()), Qt::QueuedConnection); - connect(object, SIGNAL(titleChanged()), this, SIGNAL(titleChanged()), Qt::QueuedConnection); - connect(object, SIGNAL(windowClosed()), this, SIGNAL(closed()), Qt::QueuedConnection); - connect(object, SIGNAL(selfDestruct()), this, SLOT(close()), Qt::QueuedConnection); + auto mainWindow = qApp->getWindow(); + _dockWidget = std::shared_ptr(new DockWidget(title, mainWindow), dockWidgetDeleter); + + if (nativeWindowInfo.contains(DOCK_AREA_PROPERTY)) { + DockArea dockedArea = (DockArea) nativeWindowInfo[DOCK_AREA_PROPERTY].toInt(); + switch (dockedArea) { + case DockArea::TOP: + dockArea = Qt::TopDockWidgetArea; + _dockWidget->setFixedHeight(windowSize.height()); + break; + case DockArea::BOTTOM: + dockArea = Qt::BottomDockWidgetArea; + _dockWidget->setFixedHeight(windowSize.height()); + break; + case DockArea::LEFT: + dockArea = Qt::LeftDockWidgetArea; + _dockWidget->setFixedWidth(windowSize.width()); + break; + case DockArea::RIGHT: + dockArea = Qt::RightDockWidgetArea; + _dockWidget->setFixedWidth(windowSize.width()); + break; + + default: + _dockWidget->setFixedHeight(DEFAULT_HEIGHT); + break; + } + } + + auto quickView = _dockWidget->getQuickView(); + QObject::connect(quickView.get(), &QQuickView::statusChanged, [&, this] (QQuickView::Status status) { + if (status == QQuickView::Ready) { + QQuickItem* rootItem = _dockWidget->getRootItem(); + QObject::connect(rootItem, SIGNAL(sendToScript(QVariant)), this, SLOT(qmlToScript(const QVariant&)), Qt::QueuedConnection); + } + }); + _dockWidget->setSource(QUrl(sourceUrl)); + mainWindow->addDockWidget(dockArea, _dockWidget.get()); + _dockedWindow = docked; + } else { + auto offscreenUi = DependencyManager::get(); + // Build the event bridge and wrapper on the main thread + offscreenUi->loadInNewContext(CONTENT_WINDOW_QML, [&](QQmlContext* context, QObject* object) { + _qmlWindow = object; + context->setContextProperty(EVENT_BRIDGE_PROPERTY, this); + if (properties.contains(FLAGS_PROPERTY)) { + object->setProperty(FLAGS_PROPERTY, properties[FLAGS_PROPERTY].toUInt()); + } + if (properties.contains(PRESENTATION_MODE_PROPERTY)) { + object->setProperty(PRESENTATION_MODE_PROPERTY, properties[PRESENTATION_MODE_PROPERTY].toInt()); + } + if (properties.contains(TITLE_PROPERTY)) { + object->setProperty(TITLE_PROPERTY, properties[TITLE_PROPERTY].toString()); + } + if (properties.contains(SIZE_PROPERTY)) { + const auto size = vec2FromVariant(properties[SIZE_PROPERTY]); + object->setProperty(INTERACTIVE_WINDOW_SIZE_PROPERTY, QSize(size.x, size.y)); + } + if (properties.contains(POSITION_PROPERTY)) { + const auto position = vec2FromVariant(properties[POSITION_PROPERTY]); + object->setProperty(INTERACTIVE_WINDOW_POSITION_PROPERTY, QPointF(position.x, position.y)); + } + if (properties.contains(VISIBLE_PROPERTY)) { + object->setProperty(VISIBLE_PROPERTY, properties[INTERACTIVE_WINDOW_VISIBLE_PROPERTY].toBool()); + } + + connect(object, SIGNAL(sendToScript(QVariant)), this, SLOT(qmlToScript(const QVariant&)), Qt::QueuedConnection); + connect(object, SIGNAL(interactiveWindowPositionChanged()), this, SIGNAL(positionChanged()), Qt::QueuedConnection); + connect(object, SIGNAL(interactiveWindowSizeChanged()), this, SIGNAL(sizeChanged()), Qt::QueuedConnection); + connect(object, SIGNAL(interactiveWindowVisibleChanged()), this, SIGNAL(visibleChanged()), Qt::QueuedConnection); + connect(object, SIGNAL(presentationModeChanged()), this, SIGNAL(presentationModeChanged()), Qt::QueuedConnection); + connect(object, SIGNAL(titleChanged()), this, SIGNAL(titleChanged()), Qt::QueuedConnection); + connect(object, SIGNAL(windowClosed()), this, SIGNAL(closed()), Qt::QueuedConnection); + connect(object, SIGNAL(selfDestruct()), this, SLOT(close()), Qt::QueuedConnection); #ifdef Q_OS_WIN - connect(object, SIGNAL(nativeWindowChanged()), this, SLOT(parentNativeWindowToMainWindow()), Qt::QueuedConnection); - connect(object, SIGNAL(interactiveWindowVisibleChanged()), this, SLOT(parentNativeWindowToMainWindow()), Qt::QueuedConnection); - connect(object, SIGNAL(presentationModeChanged()), this, SLOT(parentNativeWindowToMainWindow()), Qt::QueuedConnection); + connect(object, SIGNAL(nativeWindowChanged()), this, SLOT(parentNativeWindowToMainWindow()), Qt::QueuedConnection); + connect(object, SIGNAL(interactiveWindowVisibleChanged()), this, SLOT(parentNativeWindowToMainWindow()), Qt::QueuedConnection); + connect(object, SIGNAL(presentationModeChanged()), this, SLOT(parentNativeWindowToMainWindow()), Qt::QueuedConnection); #endif - QUrl sourceURL{ sourceUrl }; - // If the passed URL doesn't correspond to a known scheme, assume it's a local file path - if (!KNOWN_SCHEMES.contains(sourceURL.scheme(), Qt::CaseInsensitive)) { - sourceURL = QUrl::fromLocalFile(sourceURL.toString()).toString(); - } - object->setProperty(SOURCE_PROPERTY, sourceURL); - }); + QUrl sourceURL{ sourceUrl }; + // If the passed URL doesn't correspond to a known scheme, assume it's a local file path + if (!KNOWN_SCHEMES.contains(sourceURL.scheme(), Qt::CaseInsensitive)) { + sourceURL = QUrl::fromLocalFile(sourceURL.toString()).toString(); + } + object->setProperty(SOURCE_PROPERTY, sourceURL); + }); + } } InteractiveWindow::~InteractiveWindow() { @@ -115,7 +187,14 @@ InteractiveWindow::~InteractiveWindow() { void InteractiveWindow::sendToQml(const QVariant& message) { // Forward messages received from the script on to QML - QMetaObject::invokeMethod(_qmlWindow, "fromScript", Qt::QueuedConnection, Q_ARG(QVariant, message)); + if (_dockedWindow) { + QQuickItem* rootItem = _dockWidget->getRootItem(); + if (rootItem) { + QMetaObject::invokeMethod(_qmlWindow, "fromScript", Qt::QueuedConnection, Q_ARG(QVariant, message)); + } + } else { + QMetaObject::invokeMethod(_qmlWindow, "fromScript", Qt::QueuedConnection, Q_ARG(QVariant, message)); + } } void InteractiveWindow::emitScriptEvent(const QVariant& scriptMessage) { @@ -143,6 +222,9 @@ void InteractiveWindow::close() { if (_qmlWindow) { _qmlWindow->deleteLater(); } + + qApp->getWindow()->removeDockWidget(_dockWidget.get()); + _dockWidget = nullptr; _qmlWindow = nullptr; } diff --git a/libraries/ui/src/InteractiveWindow.h b/interface/src/ui/InteractiveWindow.h similarity index 95% rename from libraries/ui/src/InteractiveWindow.h rename to interface/src/ui/InteractiveWindow.h index 22a3df7530..816babc56a 100644 --- a/libraries/ui/src/InteractiveWindow.h +++ b/interface/src/ui/InteractiveWindow.h @@ -36,6 +36,14 @@ namespace InteractiveWindowEnums { Native }; Q_ENUM_NS(InteractiveWindowPresentationMode); + + enum DockArea { + TOP, + BOTTOM, + LEFT, + RIGHT + }; + Q_ENUM_NS(DockArea); } using namespace InteractiveWindowEnums; @@ -52,8 +60,10 @@ using namespace InteractiveWindowEnums; * @property {Vec2} size * @property {boolean} visible * @property {Desktop.PresentationMode} presentationMode - * + * */ + +class DockWidget; class InteractiveWindow : public QObject { Q_OBJECT @@ -65,7 +75,6 @@ class InteractiveWindow : public QObject { public: InteractiveWindow(const QString& sourceUrl, const QVariantMap& properties); - ~InteractiveWindow(); private: @@ -194,7 +203,9 @@ protected slots: void qmlToScript(const QVariant& message); private: + bool _dockedWindow { false }; QPointer _qmlWindow; + std::shared_ptr _dockWidget { nullptr }; }; typedef InteractiveWindow* InteractiveWindowPointer; diff --git a/libraries/ui/src/DockWidget.cpp b/libraries/ui/src/DockWidget.cpp new file mode 100644 index 0000000000..3bcd479d61 --- /dev/null +++ b/libraries/ui/src/DockWidget.cpp @@ -0,0 +1,47 @@ +// +// DockWidget.cpp +// libraries/ui/src +// +// Created by Dante Ruiz 05-07-2019 +// Copyright 2019 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 "DockWidget.h" + +#include "OffscreenUi.h" + +#include +#include +#include + +#include + +static void quickViewDeleter(QQuickView* quickView) { + quickView->deleteLater(); +} + +DockWidget::DockWidget(const QString& title, QWidget* parent) : QDockWidget(title, parent) { + auto offscreenUi = DependencyManager::get(); + auto qmlEngine = offscreenUi->getSurfaceContext()->engine(); + _quickView = std::shared_ptr(new QQuickView(qmlEngine, nullptr), quickViewDeleter); + QWidget* widget = QWidget::createWindowContainer(_quickView.get()); + setWidget(widget); + QWidget* headerWidget = new QWidget(); + setTitleBarWidget(headerWidget); +} + +void DockWidget::setSource(const QUrl& url) { + _quickView->setSource(url); +} + +QQuickItem* DockWidget::getRootItem() const { + return _quickView->rootObject(); +} + +std::shared_ptr DockWidget::getQuickView() const { + return _quickView; +} diff --git a/libraries/ui/src/DockWidget.h b/libraries/ui/src/DockWidget.h new file mode 100644 index 0000000000..e9669264e7 --- /dev/null +++ b/libraries/ui/src/DockWidget.h @@ -0,0 +1,33 @@ +// +// DockWidget.h +// libraries/ui/src +// +// Created by Dante Ruiz 05-07-2019 +// Copyright 2019 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + + +#ifndef hifi_DockWidget_h +#define hifi_DockWidget_h + +#include +#include + +class QQuickView; +class QQuickItem; +class DockWidget : public QDockWidget { +public: + DockWidget(const QString& title, QWidget* parent = nullptr); + ~DockWidget() = default; + + void setSource(const QUrl& url); + QQuickItem* getRootItem() const; + std::shared_ptr getQuickView() const; +private: + std::shared_ptr _quickView; +}; + +#endif diff --git a/libraries/ui/src/MainWindow.cpp b/libraries/ui/src/MainWindow.cpp index e5b0a8c542..124e25675a 100644 --- a/libraries/ui/src/MainWindow.cpp +++ b/libraries/ui/src/MainWindow.cpp @@ -26,6 +26,10 @@ #include #include "ui/Logging.h" +#include "DockWidget.h" + +#include +#include MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), @@ -34,6 +38,7 @@ MainWindow::MainWindow(QWidget* parent) : { setAttribute(Qt::WA_NoSystemBackground); setAcceptDrops(true); + setStyleSheet("QMainWindow::separator {width: 1px; border: none;}"); } MainWindow::~MainWindow() { diff --git a/libraries/ui/src/MainWindow.h b/libraries/ui/src/MainWindow.h index fbd48e5eb1..543f8ce9af 100644 --- a/libraries/ui/src/MainWindow.h +++ b/libraries/ui/src/MainWindow.h @@ -16,6 +16,7 @@ #include +class DockWidget; class MainWindow : public QMainWindow { Q_OBJECT public: @@ -23,11 +24,10 @@ public: ~MainWindow(); static QWindow* findMainWindow(); - public slots: void restoreGeometry(); void saveGeometry(); - + signals: void windowGeometryChanged(QRect geometry); void windowShown(bool shown); @@ -42,7 +42,7 @@ protected: virtual void changeEvent(QEvent* event) override; virtual void dragEnterEvent(QDragEnterEvent *e) override; virtual void dropEvent(QDropEvent *e) override; - + private: Setting::Handle _windowGeometry; Setting::Handle _windowState;