Merge pull request #15507 from danteruiz/dockable-windows

case LILY-30: Dockable Interactive Windows
This commit is contained in:
Shannon Romano 2019-05-08 12:16:04 -07:00 committed by GitHub
commit 15484436ca
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 239 additions and 47 deletions

View file

@ -136,7 +136,6 @@
#include <SoundCacheScriptingInterface.h>
#include <ui/TabletScriptingInterface.h>
#include <ui/ToolbarScriptingInterface.h>
#include <InteractiveWindow.h>
#include <Tooltip.h>
#include <udt/PacketHeaders.h>
#include <UserActivityLogger.h>
@ -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"

View file

@ -22,6 +22,13 @@
#include <DependencyManager.h>
#include <OffscreenUi.h>
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);
}

View file

@ -17,7 +17,7 @@
#include <DependencyManager.h>
#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();
};

View file

@ -11,12 +11,15 @@
#include "InteractiveWindow.h"
#include "Application.h"
#include <QtQml/QQmlContext>
#include <QtCore/QThread>
#include <QtGui/QGuiApplication>
#include <QtQuick/QQuickWindow>
#include <QQuickView>
#include <DependencyManager.h>
#include <DockWidget.h>
#include <RegisteredMetaTypes.h>
#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<OffscreenUi>();
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<DockWidget>(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<OffscreenUi>();
// 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;
}

View file

@ -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<QObject> _qmlWindow;
std::shared_ptr<DockWidget> _dockWidget { nullptr };
};
typedef InteractiveWindow* InteractiveWindowPointer;

View file

@ -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 <QtQml/QQmlEngine>
#include <QtQml/QQmlContext>
#include <QQuickView>
#include <PathUtils.h>
static void quickViewDeleter(QQuickView* quickView) {
quickView->deleteLater();
}
DockWidget::DockWidget(const QString& title, QWidget* parent) : QDockWidget(title, parent) {
auto offscreenUi = DependencyManager::get<OffscreenUi>();
auto qmlEngine = offscreenUi->getSurfaceContext()->engine();
_quickView = std::shared_ptr<QQuickView>(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<QQuickView> DockWidget::getQuickView() const {
return _quickView;
}

View file

@ -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 <QDockWidget>
#include <memory>
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<QQuickView> getQuickView() const;
private:
std::shared_ptr<QQuickView> _quickView;
};
#endif

View file

@ -26,6 +26,10 @@
#include <QDebug>
#include "ui/Logging.h"
#include "DockWidget.h"
#include <QSizePolicy>
#include <QLayout>
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() {

View file

@ -16,6 +16,7 @@
#include <SettingHandle.h>
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<QRect> _windowGeometry;
Setting::Handle<int> _windowState;