mirror of
https://github.com/overte-org/overte.git
synced 2025-04-08 09:33:49 +02:00
Merge pull request #13496 from thoys/feat/interactive-window
InteractiveWindow scripting API
This commit is contained in:
commit
7aaf17a3c8
9 changed files with 885 additions and 4 deletions
287
interface/resources/qml/InteractiveWindow.qml
Normal file
287
interface/resources/qml/InteractiveWindow.qml
Normal file
|
@ -0,0 +1,287 @@
|
|||
//
|
||||
// InteractiveWindow.qml
|
||||
//
|
||||
// Created by Thijs Wenker on 2018-06-25
|
||||
// Copyright 2018 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
import QtQuick 2.3
|
||||
|
||||
import "windows" as Windows
|
||||
import "controls"
|
||||
import "controls-uit" as Controls
|
||||
import "styles"
|
||||
import "styles-uit"
|
||||
|
||||
Windows.Window {
|
||||
id: root;
|
||||
HifiConstants { id: hifi }
|
||||
title: "InteractiveWindow";
|
||||
resizable: true;
|
||||
// Virtual window visibility
|
||||
shown: false;
|
||||
focus: true;
|
||||
property var channel;
|
||||
// Don't destroy on close... otherwise the JS/C++ will have a dangling pointer
|
||||
destroyOnCloseButton: false;
|
||||
|
||||
signal selfDestruct();
|
||||
|
||||
property var flags: 0;
|
||||
|
||||
property var source;
|
||||
property var dynamicContent;
|
||||
property var nativeWindow;
|
||||
|
||||
// custom visibility flag for interactiveWindow to proxy virtualWindow.shown / nativeWindow.visible
|
||||
property var interactiveWindowVisible: true;
|
||||
|
||||
property point interactiveWindowPosition;
|
||||
|
||||
property size interactiveWindowSize;
|
||||
|
||||
// Keyboard control properties in case needed by QML content.
|
||||
property bool keyboardEnabled: false;
|
||||
property bool keyboardRaised: false;
|
||||
property bool punctuationMode: false;
|
||||
|
||||
property int presentationMode: 0;
|
||||
|
||||
property var initialized: false;
|
||||
onSourceChanged: {
|
||||
if (dynamicContent) {
|
||||
dynamicContent.destroy();
|
||||
dynamicContent = null;
|
||||
}
|
||||
QmlSurface.load(source, contentHolder, function(newObject) {
|
||||
dynamicContent = newObject;
|
||||
if (dynamicContent && dynamicContent.anchors) {
|
||||
dynamicContent.anchors.fill = contentHolder;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function updateInteractiveWindowPositionForMode() {
|
||||
if (presentationMode === Desktop.PresentationMode.VIRTUAL) {
|
||||
x = interactiveWindowPosition.x;
|
||||
y = interactiveWindowPosition.y;
|
||||
} else if (presentationMode === Desktop.PresentationMode.NATIVE && nativeWindow) {
|
||||
if (interactiveWindowPosition.x === 0 && interactiveWindowPosition.y === 0) {
|
||||
// default position for native window in center of main application window
|
||||
nativeWindow.x = Math.floor(Window.x + (Window.innerWidth / 2) - (interactiveWindowSize.width / 2));
|
||||
nativeWindow.y = Math.floor(Window.y + (Window.innerHeight / 2) - (interactiveWindowSize.height / 2));
|
||||
} else {
|
||||
nativeWindow.x = interactiveWindowPosition.x;
|
||||
nativeWindow.y = interactiveWindowPosition.y;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function updateInteractiveWindowSizeForMode() {
|
||||
if (presentationMode === Desktop.PresentationMode.VIRTUAL) {
|
||||
width = interactiveWindowSize.width;
|
||||
height = interactiveWindowSize.height;
|
||||
} else if (presentationMode === Desktop.PresentationMode.NATIVE && nativeWindow) {
|
||||
nativeWindow.width = interactiveWindowSize.width;
|
||||
nativeWindow.height = interactiveWindowSize.height;
|
||||
}
|
||||
}
|
||||
|
||||
function updateContentParent() {
|
||||
if (presentationMode === Desktop.PresentationMode.VIRTUAL) {
|
||||
contentHolder.parent = root;
|
||||
} else if (presentationMode === Desktop.PresentationMode.NATIVE && nativeWindow) {
|
||||
contentHolder.parent = nativeWindow.contentItem;
|
||||
}
|
||||
}
|
||||
|
||||
function setupPresentationMode() {
|
||||
if (presentationMode === Desktop.PresentationMode.VIRTUAL) {
|
||||
if (nativeWindow) {
|
||||
nativeWindow.setVisible(false);
|
||||
}
|
||||
updateContentParent();
|
||||
updateInteractiveWindowPositionForMode();
|
||||
shown = interactiveWindowVisible;
|
||||
} else if (presentationMode === Desktop.PresentationMode.NATIVE) {
|
||||
shown = false;
|
||||
if (nativeWindow) {
|
||||
updateContentParent();
|
||||
updateInteractiveWindowPositionForMode();
|
||||
nativeWindow.setVisible(interactiveWindowVisible);
|
||||
}
|
||||
} else if (presentationMode === modeNotSet) {
|
||||
console.error("presentationMode should be set.");
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
// Fix for parent loss on OSX:
|
||||
parent.heightChanged.connect(function() {
|
||||
updateContentParent();
|
||||
});
|
||||
parent.widthChanged.connect(function() {
|
||||
updateContentParent();
|
||||
});
|
||||
|
||||
x = interactiveWindowPosition.x;
|
||||
y = interactiveWindowPosition.y;
|
||||
width = interactiveWindowSize.width;
|
||||
height = interactiveWindowSize.height;
|
||||
|
||||
nativeWindow = Qt.createQmlObject('
|
||||
import QtQuick 2.3;
|
||||
import QtQuick.Window 2.3;
|
||||
|
||||
Window {
|
||||
id: root;
|
||||
Rectangle {
|
||||
color: hifi.colors.baseGray
|
||||
anchors.fill: parent
|
||||
}
|
||||
}', root, 'InteractiveWindow.qml->nativeWindow');
|
||||
nativeWindow.title = root.title;
|
||||
var nativeWindowFlags = Qt.Window |
|
||||
Qt.WindowTitleHint |
|
||||
Qt.WindowSystemMenuHint |
|
||||
Qt.WindowCloseButtonHint |
|
||||
Qt.WindowMaximizeButtonHint |
|
||||
Qt.WindowMinimizeButtonHint;
|
||||
if ((flags & Desktop.ALWAYS_ON_TOP) === Desktop.ALWAYS_ON_TOP) {
|
||||
nativeWindowFlags |= Qt.WindowStaysOnTopHint;
|
||||
}
|
||||
nativeWindow.flags = nativeWindowFlags;
|
||||
|
||||
nativeWindow.x = interactiveWindowPosition.x;
|
||||
nativeWindow.y = interactiveWindowPosition.y;
|
||||
|
||||
nativeWindow.width = interactiveWindowSize.width;
|
||||
nativeWindow.height = interactiveWindowSize.height;
|
||||
|
||||
nativeWindow.xChanged.connect(function() {
|
||||
if (presentationMode === Desktop.PresentationMode.NATIVE && nativeWindow.visible) {
|
||||
interactiveWindowPosition = Qt.point(nativeWindow.x, interactiveWindowPosition.y);
|
||||
}
|
||||
});
|
||||
nativeWindow.yChanged.connect(function() {
|
||||
if (presentationMode === Desktop.PresentationMode.NATIVE && nativeWindow.visible) {
|
||||
interactiveWindowPosition = Qt.point(interactiveWindowPosition.x, nativeWindow.y);
|
||||
}
|
||||
});
|
||||
|
||||
nativeWindow.widthChanged.connect(function() {
|
||||
if (presentationMode === Desktop.PresentationMode.NATIVE && nativeWindow.visible) {
|
||||
interactiveWindowSize = Qt.size(nativeWindow.width, interactiveWindowSize.height);
|
||||
}
|
||||
});
|
||||
nativeWindow.heightChanged.connect(function() {
|
||||
if (presentationMode === Desktop.PresentationMode.NATIVE && nativeWindow.visible) {
|
||||
interactiveWindowSize = Qt.size(interactiveWindowSize.width, nativeWindow.height);
|
||||
}
|
||||
});
|
||||
|
||||
nativeWindow.closing.connect(function(closeEvent) {
|
||||
closeEvent.accepted = false;
|
||||
windowClosed();
|
||||
});
|
||||
|
||||
// finally set the initial window mode:
|
||||
setupPresentationMode();
|
||||
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
// Handle message traffic from the script that launched us to the loaded QML
|
||||
function fromScript(message) {
|
||||
if (root.dynamicContent && root.dynamicContent.fromScript) {
|
||||
root.dynamicContent.fromScript(message);
|
||||
}
|
||||
}
|
||||
|
||||
function show() {
|
||||
interactiveWindowVisible = true;
|
||||
raiseWindow();
|
||||
}
|
||||
|
||||
function raiseWindow() {
|
||||
if (presentationMode === Desktop.PresentationMode.VIRTUAL) {
|
||||
raise();
|
||||
} else if (presentationMode === Desktop.PresentationMode.NATIVE && nativeWindow) {
|
||||
nativeWindow.raise();
|
||||
}
|
||||
}
|
||||
|
||||
// Handle message traffic from our loaded QML to the script that launched us
|
||||
signal sendToScript(var message);
|
||||
|
||||
onDynamicContentChanged: {
|
||||
if (dynamicContent && dynamicContent.sendToScript) {
|
||||
dynamicContent.sendToScript.connect(sendToScript);
|
||||
}
|
||||
}
|
||||
|
||||
onInteractiveWindowVisibleChanged: {
|
||||
if (presentationMode === Desktop.PresentationMode.VIRTUAL) {
|
||||
shown = interactiveWindowVisible;
|
||||
} else if (presentationMode === Desktop.PresentationMode.NATIVE && nativeWindow) {
|
||||
if (!nativeWindow.visible && interactiveWindowVisible) {
|
||||
nativeWindow.showNormal();
|
||||
} else {
|
||||
nativeWindow.setVisible(interactiveWindowVisible);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onTitleChanged: {
|
||||
if (nativeWindow) {
|
||||
nativeWindow.title = title;
|
||||
}
|
||||
}
|
||||
|
||||
onXChanged: {
|
||||
if (presentationMode === Desktop.PresentationMode.VIRTUAL) {
|
||||
interactiveWindowPosition = Qt.point(x, interactiveWindowPosition.y);
|
||||
}
|
||||
}
|
||||
|
||||
onYChanged: {
|
||||
if (presentationMode === Desktop.PresentationMode.VIRTUAL) {
|
||||
interactiveWindowPosition = Qt.point(interactiveWindowPosition.x, y);
|
||||
}
|
||||
}
|
||||
|
||||
onWidthChanged: {
|
||||
if (presentationMode === Desktop.PresentationMode.VIRTUAL) {
|
||||
interactiveWindowSize = Qt.size(width, interactiveWindowSize.height);
|
||||
}
|
||||
}
|
||||
|
||||
onHeightChanged: {
|
||||
if (presentationMode === Desktop.PresentationMode.VIRTUAL) {
|
||||
interactiveWindowSize = Qt.size(interactiveWindowSize.width, height);
|
||||
}
|
||||
}
|
||||
|
||||
onPresentationModeChanged: {
|
||||
if (initialized) {
|
||||
setupPresentationMode();
|
||||
}
|
||||
}
|
||||
|
||||
onWindowClosed: {
|
||||
// set invisible on close, to make it not re-appear unintended after switching PresentationMode
|
||||
interactiveWindowVisible = false;
|
||||
|
||||
if ((flags & Desktop.CLOSE_BUTTON_HIDES) !== Desktop.CLOSE_BUTTON_HIDES) {
|
||||
selfDestruct();
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: contentHolder
|
||||
anchors.fill: parent
|
||||
}
|
||||
}
|
|
@ -12,7 +12,7 @@ Rectangle {
|
|||
}
|
||||
|
||||
Label {
|
||||
text: OverlayWindowTestString
|
||||
text: "OverlayWindowTestString"
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
}
|
||||
|
|
|
@ -129,6 +129,7 @@
|
|||
#include <SoundCache.h>
|
||||
#include <ui/TabletScriptingInterface.h>
|
||||
#include <ui/ToolbarScriptingInterface.h>
|
||||
#include <InteractiveWindow.h>
|
||||
#include <Tooltip.h>
|
||||
#include <udt/PacketHeaders.h>
|
||||
#include <UserActivityLogger.h>
|
||||
|
@ -2979,6 +2980,7 @@ void Application::onDesktopRootContextCreated(QQmlContext* surfaceContext) {
|
|||
|
||||
surfaceContext->setContextProperty("Overlays", &_overlays);
|
||||
surfaceContext->setContextProperty("Window", DependencyManager::get<WindowScriptingInterface>().data());
|
||||
surfaceContext->setContextProperty("Desktop", DependencyManager::get<DesktopScriptingInterface>().data());
|
||||
surfaceContext->setContextProperty("MenuInterface", MenuScriptingInterface::getInstance());
|
||||
surfaceContext->setContextProperty("Settings", SettingsScriptingInterface::getInstance());
|
||||
surfaceContext->setContextProperty("ScriptDiscoveryService", DependencyManager::get<ScriptEngines>().data());
|
||||
|
@ -4019,7 +4021,18 @@ void Application::mousePressEvent(QMouseEvent* event) {
|
|||
return;
|
||||
}
|
||||
|
||||
#if defined(Q_OS_MAC)
|
||||
// Fix for OSX right click dragging on window when coming from a native window
|
||||
bool isFocussed = hasFocus();
|
||||
if (!isFocussed && event->button() == Qt::MouseButton::RightButton) {
|
||||
setFocus();
|
||||
isFocussed = true;
|
||||
}
|
||||
|
||||
if (isFocussed) {
|
||||
#else
|
||||
if (hasFocus()) {
|
||||
#endif
|
||||
if (_keyboardMouseDevice->isActive()) {
|
||||
_keyboardMouseDevice->mousePressEvent(event);
|
||||
}
|
||||
|
@ -6636,6 +6649,8 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEnginePointe
|
|||
|
||||
qScriptRegisterMetaType(scriptEngine.data(), OverlayIDtoScriptValue, OverlayIDfromScriptValue);
|
||||
|
||||
registerInteractiveWindowMetaType(scriptEngine.data());
|
||||
|
||||
DependencyManager::get<PickScriptingInterface>()->registerMetaTypes(scriptEngine.data());
|
||||
|
||||
// connect this script engines printedMessage signal to the global ScriptEngines these various messages
|
||||
|
|
|
@ -14,6 +14,8 @@
|
|||
#include <QWindow>
|
||||
#include <QScreen>
|
||||
|
||||
#include <shared/QtHelpers.h>
|
||||
|
||||
#include "Application.h"
|
||||
#include "MainWindow.h"
|
||||
#include <display-plugins/CompositorHelper.h>
|
||||
|
@ -29,6 +31,14 @@ int DesktopScriptingInterface::getHeight() {
|
|||
return size.height();
|
||||
}
|
||||
|
||||
QVariantMap DesktopScriptingInterface::getPresentationMode() {
|
||||
static QVariantMap presentationModes {
|
||||
{ "VIRTUAL", Virtual },
|
||||
{ "NATIVE", Native }
|
||||
};
|
||||
return presentationModes;
|
||||
}
|
||||
|
||||
void DesktopScriptingInterface::setHUDAlpha(float alpha) {
|
||||
qApp->getApplicationCompositor().setAlpha(alpha);
|
||||
}
|
||||
|
@ -41,3 +51,14 @@ void DesktopScriptingInterface::show(const QString& path, const QString& title)
|
|||
DependencyManager::get<OffscreenUi>()->show(path, title);
|
||||
}
|
||||
|
||||
InteractiveWindowPointer DesktopScriptingInterface::createWindow(const QString& sourceUrl, const QVariantMap& properties) {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
InteractiveWindowPointer interactiveWindow = nullptr;
|
||||
BLOCKING_INVOKE_METHOD(this, "createWindow",
|
||||
Q_RETURN_ARG(InteractiveWindowPointer, interactiveWindow),
|
||||
Q_ARG(QString, sourceUrl),
|
||||
Q_ARG(QVariantMap, properties));
|
||||
return interactiveWindow;
|
||||
}
|
||||
return new InteractiveWindow(sourceUrl, properties);;
|
||||
}
|
||||
|
|
|
@ -13,20 +13,48 @@
|
|||
#define hifi_DesktopScriptingInterface_h
|
||||
|
||||
#include <QObject>
|
||||
#include <QtScript/QScriptValue>
|
||||
|
||||
#include <DependencyManager.h>
|
||||
|
||||
#include "InteractiveWindow.h"
|
||||
|
||||
/**jsdoc
|
||||
* @namespace Desktop
|
||||
*
|
||||
* @hifi-interface
|
||||
* @hifi-client-entity
|
||||
*
|
||||
* @property {number} width
|
||||
* @property {number} height
|
||||
* @property {number} ALWAYS_ON_TOP - InteractiveWindow flag for always showing a window on top
|
||||
* @property {number} CLOSE_BUTTON_HIDES - InteractiveWindow flag for hiding the window instead of closing on window close by user
|
||||
*/
|
||||
class DesktopScriptingInterface : public QObject, public Dependency {
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(int width READ getWidth) // Physical width of screen(s) including task bars and system menus
|
||||
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(int ALWAYS_ON_TOP READ flagAlwaysOnTop CONSTANT FINAL)
|
||||
Q_PROPERTY(int CLOSE_BUTTON_HIDES READ flagCloseButtonHides CONSTANT FINAL)
|
||||
|
||||
public:
|
||||
Q_INVOKABLE void setHUDAlpha(float alpha);
|
||||
Q_INVOKABLE void show(const QString& path, const QString& title);
|
||||
|
||||
Q_INVOKABLE InteractiveWindowPointer createWindow(const QString& sourceUrl, const QVariantMap& properties = QVariantMap());
|
||||
|
||||
int getWidth();
|
||||
int getHeight();
|
||||
|
||||
|
||||
private:
|
||||
static int flagAlwaysOnTop() { return AlwaysOnTop; }
|
||||
static int flagCloseButtonHides() { return CloseButtonHides; }
|
||||
|
||||
Q_INVOKABLE static QVariantMap getPresentationMode();
|
||||
};
|
||||
|
||||
|
||||
#endif // hifi_DesktopScriptingInterface_h
|
||||
|
|
|
@ -393,9 +393,6 @@ void OffscreenSurface::finishQmlLoad(QQmlComponent* qmlComponent,
|
|||
_sharedObject->setRootItem(newItem);
|
||||
}
|
||||
|
||||
qmlComponent->completeCreate();
|
||||
qmlComponent->deleteLater();
|
||||
|
||||
onItemCreated(qmlContext, newItem);
|
||||
|
||||
if (!rootCreated) {
|
||||
|
@ -405,6 +402,8 @@ void OffscreenSurface::finishQmlLoad(QQmlComponent* qmlComponent,
|
|||
// Call this callback after rootitem is set, otherwise VrMenu wont work
|
||||
callback(qmlContext, newItem);
|
||||
}
|
||||
qmlComponent->completeCreate();
|
||||
qmlComponent->deleteLater();
|
||||
}
|
||||
|
||||
QQmlContext* OffscreenSurface::contextForUrl(const QUrl& qmlSource, QQuickItem* parent, bool forceNewContext) {
|
||||
|
|
291
libraries/ui/src/InteractiveWindow.cpp
Normal file
291
libraries/ui/src/InteractiveWindow.cpp
Normal file
|
@ -0,0 +1,291 @@
|
|||
//
|
||||
// InteractiveWindow.cpp
|
||||
// libraries/ui/src
|
||||
//
|
||||
// Created by Thijs Wenker on 2018-06-25
|
||||
// Copyright 2018 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include "InteractiveWindow.h"
|
||||
|
||||
#include <QtQml/QQmlContext>
|
||||
#include <QtCore/QThread>
|
||||
|
||||
#include <DependencyManager.h>
|
||||
#include <RegisteredMetaTypes.h>
|
||||
|
||||
#include "OffscreenUi.h"
|
||||
#include "shared/QtHelpers.h"
|
||||
|
||||
static auto CONTENT_WINDOW_QML = QUrl("InteractiveWindow.qml");
|
||||
|
||||
static const char* const FLAGS_PROPERTY = "flags";
|
||||
static const char* const SOURCE_PROPERTY = "source";
|
||||
static const char* const TITLE_PROPERTY = "title";
|
||||
static const char* const POSITION_PROPERTY = "position";
|
||||
static const char* const INTERACTIVE_WINDOW_POSITION_PROPERTY = "interactiveWindowPosition";
|
||||
static const char* const SIZE_PROPERTY = "size";
|
||||
static const char* const INTERACTIVE_WINDOW_SIZE_PROPERTY = "interactiveWindowSize";
|
||||
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 QStringList KNOWN_SCHEMES = QStringList() << "http" << "https" << "file" << "about" << "atp" << "qrc";
|
||||
|
||||
void registerInteractiveWindowMetaType(QScriptEngine* engine) {
|
||||
qScriptRegisterMetaType(engine, interactiveWindowPointerToScriptValue, interactiveWindowPointerFromScriptValue);
|
||||
}
|
||||
|
||||
QScriptValue interactiveWindowPointerToScriptValue(QScriptEngine* engine, const InteractiveWindowPointer& in) {
|
||||
return engine->newQObject(in, QScriptEngine::ScriptOwnership);
|
||||
}
|
||||
|
||||
void interactiveWindowPointerFromScriptValue(const QScriptValue& object, InteractiveWindowPointer& out) {
|
||||
if (const auto interactiveWindow = qobject_cast<InteractiveWindowPointer>(object.toQObject())) {
|
||||
out = interactiveWindow;
|
||||
}
|
||||
}
|
||||
|
||||
InteractiveWindow::InteractiveWindow(const QString& sourceUrl, const QVariantMap& properties) {
|
||||
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);
|
||||
|
||||
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() {
|
||||
close();
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
void InteractiveWindow::emitScriptEvent(const QVariant& scriptMessage) {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QMetaObject::invokeMethod(this, "emitScriptEvent", Qt::QueuedConnection, Q_ARG(QVariant, scriptMessage));
|
||||
} else {
|
||||
emit scriptEventReceived(scriptMessage);
|
||||
}
|
||||
}
|
||||
|
||||
void InteractiveWindow::emitWebEvent(const QVariant& webMessage) {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QMetaObject::invokeMethod(this, "emitWebEvent", Qt::QueuedConnection, Q_ARG(QVariant, webMessage));
|
||||
} else {
|
||||
emit webEventReceived(webMessage);
|
||||
}
|
||||
}
|
||||
|
||||
void InteractiveWindow::close() {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QMetaObject::invokeMethod(this, "close");
|
||||
return;
|
||||
}
|
||||
|
||||
if (_qmlWindow) {
|
||||
_qmlWindow->deleteLater();
|
||||
}
|
||||
_qmlWindow = nullptr;
|
||||
}
|
||||
|
||||
void InteractiveWindow::show() {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QMetaObject::invokeMethod(this, "show");
|
||||
return;
|
||||
}
|
||||
|
||||
if (_qmlWindow) {
|
||||
QMetaObject::invokeMethod(_qmlWindow, "show", Qt::DirectConnection);
|
||||
}
|
||||
}
|
||||
|
||||
void InteractiveWindow::raise() {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QMetaObject::invokeMethod(this, "raise");
|
||||
return;
|
||||
}
|
||||
|
||||
if (_qmlWindow) {
|
||||
QMetaObject::invokeMethod(_qmlWindow, "raiseWindow", Qt::DirectConnection);
|
||||
}
|
||||
}
|
||||
|
||||
void InteractiveWindow::qmlToScript(const QVariant& message) {
|
||||
if (message.canConvert<QJSValue>()) {
|
||||
emit fromQml(qvariant_cast<QJSValue>(message).toVariant());
|
||||
} else if (message.canConvert<QString>()) {
|
||||
emit fromQml(message.toString());
|
||||
} else {
|
||||
qWarning() << "Unsupported message type " << message;
|
||||
}
|
||||
}
|
||||
|
||||
void InteractiveWindow::setVisible(bool visible) {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QMetaObject::invokeMethod(this, "setVisible", Q_ARG(bool, visible));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_qmlWindow.isNull()) {
|
||||
_qmlWindow->setProperty(INTERACTIVE_WINDOW_VISIBLE_PROPERTY, visible);
|
||||
}
|
||||
}
|
||||
|
||||
bool InteractiveWindow::isVisible() const {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
bool result = false;
|
||||
BLOCKING_INVOKE_METHOD(const_cast<InteractiveWindow*>(this), "isVisible", Q_RETURN_ARG(bool, result));
|
||||
return result;
|
||||
}
|
||||
|
||||
if (_qmlWindow.isNull()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return _qmlWindow->property(INTERACTIVE_WINDOW_VISIBLE_PROPERTY).toBool();
|
||||
}
|
||||
|
||||
glm::vec2 InteractiveWindow::getPosition() const {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
glm::vec2 result;
|
||||
BLOCKING_INVOKE_METHOD(const_cast<InteractiveWindow*>(this), "getPosition", Q_RETURN_ARG(glm::vec2, result));
|
||||
return result;
|
||||
}
|
||||
|
||||
if (_qmlWindow.isNull()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return toGlm(_qmlWindow->property(INTERACTIVE_WINDOW_POSITION_PROPERTY).toPointF());
|
||||
}
|
||||
|
||||
void InteractiveWindow::setPosition(const glm::vec2& position) {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QMetaObject::invokeMethod(this, "setPosition", Q_ARG(const glm::vec2&, position));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_qmlWindow.isNull()) {
|
||||
_qmlWindow->setProperty(INTERACTIVE_WINDOW_POSITION_PROPERTY, QPointF(position.x, position.y));
|
||||
QMetaObject::invokeMethod(_qmlWindow, "updateInteractiveWindowPositionForMode", Qt::DirectConnection);
|
||||
}
|
||||
}
|
||||
|
||||
glm::vec2 InteractiveWindow::getSize() const {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
glm::vec2 result;
|
||||
BLOCKING_INVOKE_METHOD(const_cast<InteractiveWindow*>(this), "getSize", Q_RETURN_ARG(glm::vec2, result));
|
||||
return result;
|
||||
}
|
||||
|
||||
if (_qmlWindow.isNull()) {
|
||||
return {};
|
||||
}
|
||||
return toGlm(_qmlWindow->property(INTERACTIVE_WINDOW_SIZE_PROPERTY).toSize());
|
||||
}
|
||||
|
||||
void InteractiveWindow::setSize(const glm::vec2& size) {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QMetaObject::invokeMethod(this, "setSize", Q_ARG(const glm::vec2&, size));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_qmlWindow.isNull()) {
|
||||
_qmlWindow->setProperty(INTERACTIVE_WINDOW_SIZE_PROPERTY, QSize(size.x, size.y));
|
||||
QMetaObject::invokeMethod(_qmlWindow, "updateInteractiveWindowSizeForMode", Qt::DirectConnection);
|
||||
}
|
||||
}
|
||||
|
||||
QString InteractiveWindow::getTitle() const {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QString result;
|
||||
BLOCKING_INVOKE_METHOD(const_cast<InteractiveWindow*>(this), "getTitle", Q_RETURN_ARG(QString, result));
|
||||
return result;
|
||||
}
|
||||
|
||||
if (_qmlWindow.isNull()) {
|
||||
return QString();
|
||||
}
|
||||
return _qmlWindow->property(TITLE_PROPERTY).toString();
|
||||
}
|
||||
|
||||
void InteractiveWindow::setTitle(const QString& title) {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QMetaObject::invokeMethod(this, "setTitle", Q_ARG(const QString&, title));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_qmlWindow.isNull()) {
|
||||
_qmlWindow->setProperty(TITLE_PROPERTY, title);
|
||||
}
|
||||
}
|
||||
|
||||
int InteractiveWindow::getPresentationMode() const {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
int result;
|
||||
BLOCKING_INVOKE_METHOD(const_cast<InteractiveWindow*>(this), "getPresentationMode",
|
||||
Q_RETURN_ARG(int, result));
|
||||
return result;
|
||||
}
|
||||
|
||||
if (_qmlWindow.isNull()) {
|
||||
return Virtual;
|
||||
}
|
||||
return _qmlWindow->property(PRESENTATION_MODE_PROPERTY).toInt();
|
||||
}
|
||||
|
||||
void InteractiveWindow::setPresentationMode(int presentationMode) {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QMetaObject::invokeMethod(this, "setPresentationMode", Q_ARG(int, presentationMode));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_qmlWindow.isNull()) {
|
||||
_qmlWindow->setProperty(PRESENTATION_MODE_PROPERTY, presentationMode);
|
||||
}
|
||||
}
|
206
libraries/ui/src/InteractiveWindow.h
Normal file
206
libraries/ui/src/InteractiveWindow.h
Normal file
|
@ -0,0 +1,206 @@
|
|||
//
|
||||
// InteractiveWindow.h
|
||||
// libraries/ui/src
|
||||
//
|
||||
// Created by Thijs Wenker on 2018-06-25
|
||||
// Copyright 2018 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifndef hifi_InteractiveWindow_h
|
||||
#define hifi_InteractiveWindow_h
|
||||
|
||||
#include <QtCore/QObject>
|
||||
#include <QtCore/QPointer>
|
||||
#include <QtScript/QScriptValue>
|
||||
#include <QQmlEngine>
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#include <GLMHelpers.h>
|
||||
|
||||
namespace InteractiveWindowEnums {
|
||||
Q_NAMESPACE
|
||||
|
||||
enum InteractiveWindowFlags : uint8_t {
|
||||
AlwaysOnTop = 1 << 0,
|
||||
CloseButtonHides = 1 << 1
|
||||
};
|
||||
Q_ENUM_NS(InteractiveWindowFlags);
|
||||
|
||||
enum InteractiveWindowPresentationMode {
|
||||
Virtual,
|
||||
Native
|
||||
};
|
||||
Q_ENUM_NS(InteractiveWindowPresentationMode);
|
||||
}
|
||||
|
||||
using namespace InteractiveWindowEnums;
|
||||
|
||||
/**jsdoc
|
||||
* @class InteractiveWindow
|
||||
*
|
||||
* @hifi-interface
|
||||
* @hifi-client-en
|
||||
*
|
||||
* @property {string} title
|
||||
* @property {Vec2} position
|
||||
* @property {Vec2} size
|
||||
* @property {boolean} visible
|
||||
* @property {Desktop.PresentationMode} presentationMode
|
||||
*
|
||||
*/
|
||||
class InteractiveWindow : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY(QString title READ getTitle WRITE setTitle)
|
||||
Q_PROPERTY(glm::vec2 position READ getPosition WRITE setPosition)
|
||||
Q_PROPERTY(glm::vec2 size READ getSize WRITE setSize)
|
||||
Q_PROPERTY(bool visible READ isVisible WRITE setVisible)
|
||||
Q_PROPERTY(int presentationMode READ getPresentationMode WRITE setPresentationMode)
|
||||
|
||||
public:
|
||||
InteractiveWindow(const QString& sourceUrl, const QVariantMap& properties);
|
||||
|
||||
~InteractiveWindow();
|
||||
|
||||
private:
|
||||
// define property getters and setters as private to not expose them to the JS API
|
||||
Q_INVOKABLE QString getTitle() const;
|
||||
Q_INVOKABLE void setTitle(const QString& title);
|
||||
|
||||
Q_INVOKABLE glm::vec2 getPosition() const;
|
||||
Q_INVOKABLE void setPosition(const glm::vec2& position);
|
||||
|
||||
Q_INVOKABLE glm::vec2 getSize() const;
|
||||
Q_INVOKABLE void setSize(const glm::vec2& size);
|
||||
|
||||
Q_INVOKABLE void setVisible(bool visible);
|
||||
Q_INVOKABLE bool isVisible() const;
|
||||
|
||||
Q_INVOKABLE void setPresentationMode(int presentationMode);
|
||||
Q_INVOKABLE int getPresentationMode() const;
|
||||
|
||||
public slots:
|
||||
|
||||
/**jsdoc
|
||||
* @function InteractiveWindow.sendToQml
|
||||
* @param {object} message
|
||||
*/
|
||||
// Scripts can use this to send a message to the QML object
|
||||
void sendToQml(const QVariant& message);
|
||||
|
||||
/**jsdoc
|
||||
* @function InteractiveWindow.emitScriptEvent
|
||||
* @param {object} message
|
||||
*/
|
||||
// QmlWindow content may include WebView requiring EventBridge.
|
||||
void emitScriptEvent(const QVariant& scriptMessage);
|
||||
|
||||
/**jsdoc
|
||||
* @function InteractiveWindow.emitWebEvent
|
||||
* @param {object} message
|
||||
*/
|
||||
void emitWebEvent(const QVariant& webMessage);
|
||||
|
||||
/**jsdoc
|
||||
* @function InteractiveWindow.close
|
||||
*/
|
||||
Q_INVOKABLE void close();
|
||||
|
||||
/**jsdoc
|
||||
* @function InteractiveWindow.show
|
||||
*/
|
||||
Q_INVOKABLE void show();
|
||||
|
||||
/**jsdoc
|
||||
* @function InteractiveWindow.raise
|
||||
*/
|
||||
Q_INVOKABLE void raise();
|
||||
|
||||
signals:
|
||||
|
||||
/**jsdoc
|
||||
* @function InteractiveWindow.visibleChanged
|
||||
* @returns {Signal}
|
||||
*/
|
||||
void visibleChanged();
|
||||
|
||||
/**jsdoc
|
||||
* @function InteractiveWindow.positionChanged
|
||||
* @returns {Signal}
|
||||
*/
|
||||
void positionChanged();
|
||||
|
||||
/**jsdoc
|
||||
* @function InteractiveWindow.sizeChanged
|
||||
* @returns {Signal}
|
||||
*/
|
||||
void sizeChanged();
|
||||
|
||||
/**jsdoc
|
||||
* @function InteractiveWindow.presentationModeChanged
|
||||
* @returns {Signal}
|
||||
*/
|
||||
void presentationModeChanged();
|
||||
|
||||
/**jsdoc
|
||||
* @function InteractiveWindow.titleChanged
|
||||
* @returns {Signal}
|
||||
*/
|
||||
void titleChanged();
|
||||
|
||||
/**jsdoc
|
||||
* @function InteractiveWindow.closed
|
||||
* @returns {Signal}
|
||||
*/
|
||||
void closed();
|
||||
|
||||
/**jsdoc
|
||||
* @function InteractiveWindow.fromQml
|
||||
* @param {object} message
|
||||
* @returns {Signal}
|
||||
*/
|
||||
// Scripts can connect to this signal to receive messages from the QML object
|
||||
void fromQml(const QVariant& message);
|
||||
|
||||
/**jsdoc
|
||||
* @function InteractiveWindow.scriptEventReceived
|
||||
* @param {object} message
|
||||
* @returns {Signal}
|
||||
*/
|
||||
// InteractiveWindow content may include WebView requiring EventBridge.
|
||||
void scriptEventReceived(const QVariant& message);
|
||||
|
||||
/**jsdoc
|
||||
* @function InteractiveWindow.webEventReceived
|
||||
* @param {object} message
|
||||
* @returns {Signal}
|
||||
*/
|
||||
void webEventReceived(const QVariant& message);
|
||||
|
||||
protected slots:
|
||||
/**jsdoc
|
||||
* @function InteractiveWindow.qmlToScript
|
||||
* @param {object} message
|
||||
* @returns {Signal}
|
||||
*/
|
||||
void qmlToScript(const QVariant& message);
|
||||
|
||||
private:
|
||||
QPointer<QObject> _qmlWindow;
|
||||
};
|
||||
|
||||
typedef InteractiveWindow* InteractiveWindowPointer;
|
||||
|
||||
QScriptValue interactiveWindowPointerToScriptValue(QScriptEngine* engine, const InteractiveWindowPointer& in);
|
||||
void interactiveWindowPointerFromScriptValue(const QScriptValue& object, InteractiveWindowPointer& out);
|
||||
|
||||
void registerInteractiveWindowMetaType(QScriptEngine* engine);
|
||||
|
||||
Q_DECLARE_METATYPE(InteractiveWindowPointer)
|
||||
|
||||
#endif // hifi_InteractiveWindow_h
|
34
scripts/developer/tests/interactiveWindowTest.js
Normal file
34
scripts/developer/tests/interactiveWindowTest.js
Normal file
|
@ -0,0 +1,34 @@
|
|||
//
|
||||
// interactiveWindowTest.js
|
||||
//
|
||||
// Created by Thijs Wenker on 2018-07-03
|
||||
// Copyright 2018 High Fidelity, Inc.
|
||||
//
|
||||
// An example of an interactive window that toggles presentation mode when toggling HMD on/off
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
|
||||
function getPreferredPresentationMode() {
|
||||
return HMD.active ? Desktop.PresentationMode.VIRTUAL : Desktop.PresentationMode.NATIVE;
|
||||
}
|
||||
|
||||
function getPreferredTitle() {
|
||||
return HMD.active ? 'Virtual Desktop Window' : 'Native Desktop Window';
|
||||
}
|
||||
|
||||
var virtualWindow = Desktop.createWindow(Script.resourcesPath() + 'qml/OverlayWindowTest.qml', {
|
||||
title: getPreferredTitle(),
|
||||
flags: Desktop.ALWAYS_ON_TOP,
|
||||
presentationMode: getPreferredPresentationMode(),
|
||||
size: {x: 500, y: 400}
|
||||
});
|
||||
|
||||
HMD.displayModeChanged.connect(function() {
|
||||
virtualWindow.presentationMode = getPreferredPresentationMode();
|
||||
virtualWindow.title = getPreferredTitle();
|
||||
});
|
||||
|
||||
Script.scriptEnding.connect(function() {
|
||||
virtualWindow.close();
|
||||
});
|
Loading…
Reference in a new issue