Merge branch 'master' of github.com:highfidelity/hifi into near-grab-via-parenting

This commit is contained in:
Seth Alves 2015-12-18 17:15:29 -08:00
commit 4c996885e5
24 changed files with 344 additions and 1112 deletions

View file

@ -40,7 +40,7 @@ macro(PACKAGE_LIBRARIES_FOR_DEPLOYMENT)
add_custom_command( add_custom_command(
TARGET ${TARGET_NAME} TARGET ${TARGET_NAME}
POST_BUILD POST_BUILD
COMMAND CMD /C "SET PATH=%PATH%;${QT_DIR}/bin && ${WINDEPLOYQT_COMMAND} ${EXTRA_DEPLOY_OPTIONS} $<$<OR:$<CONFIG:Release>,$<CONFIG:MinSizeRel>,$<CONFIG:RelWithDebInfo>>:--release> $<TARGET_FILE:${TARGET_NAME}>" COMMAND CMD /C "SET PATH=%PATH%;${QT_DIR}/bin && ${WINDEPLOYQT_COMMAND} $<$<OR:$<CONFIG:Release>,$<CONFIG:MinSizeRel>,$<CONFIG:RelWithDebInfo>>:--release> $<TARGET_FILE:${TARGET_NAME}>"
) )
elseif (DEFINED BUILD_BUNDLE AND BUILD_BUNDLE AND APPLE) elseif (DEFINED BUILD_BUNDLE AND BUILD_BUNDLE AND APPLE)
find_program(MACDEPLOYQT_COMMAND macdeployqt PATHS ${QT_DIR}/bin NO_DEFAULT_PATH) find_program(MACDEPLOYQT_COMMAND macdeployqt PATHS ${QT_DIR}/bin NO_DEFAULT_PATH)

View file

@ -62,7 +62,7 @@ var directory = (function () {
function setUp() { function setUp() {
viewport = Controller.getViewportDimensions(); viewport = Controller.getViewportDimensions();
directoryWindow = new OverlayWebWindow('Directory', DIRECTORY_URL, 900, 700, false); directoryWindow = new WebWindow('Directory', DIRECTORY_URL, 900, 700, false);
directoryWindow.setVisible(false); directoryWindow.setVisible(false);
directoryButton = Overlays.addOverlay("image", { directoryButton = Overlays.addOverlay("image", {

View file

@ -1,59 +0,0 @@
//public slots:
// void emitWebEvent(const QString& data);
// void emitScriptEvent(const QString& data);
//
//signals:
// void webEventReceived(const QString& data);
// void scriptEventReceived(const QString& data);
//
EventBridgeConnectionProxy = function(parent) {
this.parent = parent;
this.realSignal = this.parent.realBridge.scriptEventReceived
this.webWindowId = this.parent.webWindow.windowId;
}
EventBridgeConnectionProxy.prototype.connect = function(callback) {
var that = this;
this.realSignal.connect(function(id, message) {
if (id === that.webWindowId) { callback(message); }
});
}
EventBridgeProxy = function(webWindow) {
this.webWindow = webWindow;
this.realBridge = this.webWindow.eventBridge;
this.scriptEventReceived = new EventBridgeConnectionProxy(this);
}
EventBridgeProxy.prototype.emitWebEvent = function(data) {
this.realBridge.emitWebEvent(data);
}
openEventBridge = function(callback) {
EVENT_BRIDGE_URI = "ws://localhost:51016";
socket = new WebSocket(this.EVENT_BRIDGE_URI);
socket.onclose = function() {
console.error("web channel closed");
};
socket.onerror = function(error) {
console.error("web channel error: " + error);
};
socket.onopen = function() {
channel = new QWebChannel(socket, function(channel) {
console.log("Document url is " + document.URL);
for(var key in channel.objects){
console.log("registered object: " + key);
}
var webWindow = channel.objects[document.URL.toLowerCase()];
console.log("WebWindow is " + webWindow)
eventBridgeProxy = new EventBridgeProxy(webWindow);
if (callback) { callback(eventBridgeProxy); }
});
}
}

View file

@ -1,31 +0,0 @@
<html>
<head>
<title>Properties</title>
<script type="text/javascript" src="jquery-2.1.4.min.js"></script>
<script type="text/javascript" src="qrc:///qtwebchannel/qwebchannel.js"></script>
<script type="text/javascript" src="eventBridgeLoader.js"></script>
<script>
var myBridge;
window.onload = function() {
openEventBridge(function(eventBridge) {
myBridge = eventBridge;
myBridge.scriptEventReceived.connect(function(message) {
console.log("HTML side received message: " + message);
});
});
}
testClick = function() {
myBridge.emitWebEvent("HTML side sending message - button click");
}
</script>
</head>
<body class="properties">
<button name="Test" title="Test" onclick="testClick()">Test</button>
</body>
</html>
</body>

View file

@ -1,32 +0,0 @@
print("Launching web window");
webWindow = new OverlayWebWindow('Test Event Bridge', "file:///C:/Users/bdavis/Git/hifi/examples/html/qmlWebTest.html", 320, 240, false);
print("JS Side window: " + webWindow);
print("JS Side bridge: " + webWindow.eventBridge);
webWindow.eventBridge.webEventReceived.connect(function(data) {
print("JS Side event received: " + data);
});
var titles = ["A", "B", "C"];
var titleIndex = 0;
Script.setInterval(function() {
webWindow.eventBridge.emitScriptEvent("JS Event sent");
var size = webWindow.size;
var position = webWindow.position;
print("Window url: " + webWindow.url)
print("Window visible: " + webWindow.visible)
print("Window size: " + size.x + "x" + size.y)
print("Window pos: " + position.x + "x" + position.y)
webWindow.setVisible(!webWindow.visible);
webWindow.setTitle(titles[titleIndex]);
webWindow.setSize(320 + Math.random() * 100, 240 + Math.random() * 100);
titleIndex += 1;
titleIndex %= titles.length;
}, 2 * 1000);
Script.setTimeout(function() {
print("Closing script");
webWindow.close();
Script.stop();
}, 15 * 1000)

View file

@ -45,9 +45,7 @@ else ()
list(REMOVE_ITEM INTERFACE_SRCS ${SPEECHRECOGNIZER_CPP}) list(REMOVE_ITEM INTERFACE_SRCS ${SPEECHRECOGNIZER_CPP})
endif () endif ()
find_package(Qt5 COMPONENTS find_package(Qt5 COMPONENTS Gui Multimedia Network OpenGL Qml Quick Script Svg WebKitWidgets WebSockets)
Gui Multimedia Network OpenGL Qml Quick Script Svg
WebChannel WebEngine WebEngineWidgets WebKitWidgets WebSockets)
# grab the ui files in resources/ui # grab the ui files in resources/ui
file (GLOB_RECURSE QT_UI_FILES ui/*.ui) file (GLOB_RECURSE QT_UI_FILES ui/*.ui)
@ -177,17 +175,9 @@ include_directories("${PROJECT_SOURCE_DIR}/src")
target_link_libraries( target_link_libraries(
${TARGET_NAME} ${TARGET_NAME}
Qt5::Gui Qt5::Network Qt5::Multimedia Qt5::OpenGL Qt5::Gui Qt5::Network Qt5::Multimedia Qt5::OpenGL Qt5::Script Qt5::Svg Qt5::WebKitWidgets
Qt5::Qml Qt5::Quick Qt5::Script Qt5::Svg
Qt5::WebChannel Qt5::WebEngine Qt5::WebEngineWidgets Qt5::WebKitWidgets
) )
# Issue causes build failure unless we add this directory.
# See https://bugreports.qt.io/browse/QTBUG-43351
if (WIN32)
add_paths_to_fixup_libs(${Qt5_DIR}/../../../plugins/qtwebengine)
endif()
# assume we are using a Qt build without bearer management # assume we are using a Qt build without bearer management
add_definitions(-DQT_NO_BEARERMANAGEMENT) add_definitions(-DQT_NO_BEARERMANAGEMENT)
@ -219,9 +209,5 @@ else (APPLE)
endif() endif()
endif (APPLE) endif (APPLE)
if (WIN32)
set(EXTRA_DEPLOY_OPTIONS "--qmldir ${PROJECT_SOURCE_DIR}/resources/qml")
endif()
package_libraries_for_deployment() package_libraries_for_deployment()
consolidate_stack_components() consolidate_stack_components()

View file

@ -1,6 +1,6 @@
import QtQuick 2.3 import QtQuick 2.3
import QtQuick.Controls 1.2 import QtQuick.Controls 1.2
import QtWebEngine 1.1 import QtWebKit 3.0
import "controls" import "controls"
import "styles" import "styles"
@ -39,10 +39,9 @@ VrDialog {
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.top: parent.top anchors.top: parent.top
anchors.bottom: webview.top anchors.bottom: scrollView.top
color: "white" color: "white"
} }
Row { Row {
id: buttons id: buttons
spacing: 4 spacing: 4
@ -113,21 +112,25 @@ VrDialog {
} }
} }
WebEngineView { ScrollView {
id: webview id: scrollView
url: "http://highfidelity.com"
anchors.top: buttons.bottom anchors.top: buttons.bottom
anchors.topMargin: 8 anchors.topMargin: 8
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
WebView {
id: webview
url: "http://highfidelity.com"
anchors.fill: parent
onLoadingChanged: { onLoadingChanged: {
if (loadRequest.status == WebEngineView.LoadSucceededStatus) { if (loadRequest.status == WebView.LoadSucceededStarted) {
addressBar.text = loadRequest.url addressBar.text = loadRequest.url
} }
} }
onIconChanged: { onIconChanged: {
console.log("New icon: " + icon) barIcon.source = icon
}
} }
} }
} // item } // item
@ -143,4 +146,5 @@ VrDialog {
break; break;
} }
} }
} // dialog } // dialog

View file

@ -2,7 +2,7 @@ import Hifi 1.0 as Hifi
import QtQuick 2.3 import QtQuick 2.3
import QtQuick.Controls 1.2 import QtQuick.Controls 1.2
import QtQuick.Controls.Styles 1.3 import QtQuick.Controls.Styles 1.3
import QtWebEngine 1.1 import QtWebKit 3.0
import "controls" import "controls"
VrDialog { VrDialog {
@ -18,11 +18,15 @@ VrDialog {
anchors.margins: parent.margins anchors.margins: parent.margins
anchors.topMargin: parent.topMargin anchors.topMargin: parent.topMargin
WebEngineView { ScrollView {
id: webview
objectName: "WebView"
anchors.fill: parent anchors.fill: parent
WebView {
objectName: "WebView"
id: webview
url: infoView.url url: infoView.url
anchors.fill: parent
} }
} }
}
} }

View file

@ -2,7 +2,7 @@ import Hifi 1.0
import QtQuick 2.3 import QtQuick 2.3
import QtQuick.Controls 1.2 import QtQuick.Controls 1.2
import QtQuick.Controls.Styles 1.3 import QtQuick.Controls.Styles 1.3
import QtWebEngine 1.1 import QtWebKit 3.0
import "controls" import "controls"
VrDialog { VrDialog {
@ -24,7 +24,10 @@ VrDialog {
anchors.margins: parent.margins anchors.margins: parent.margins
anchors.topMargin: parent.topMargin anchors.topMargin: parent.topMargin
WebEngineView {
ScrollView {
anchors.fill: parent
WebView {
objectName: "WebView" objectName: "WebView"
id: webview id: webview
url: "https://metaverse.highfidelity.com/marketplace" url: "https://metaverse.highfidelity.com/marketplace"
@ -42,4 +45,6 @@ VrDialog {
} }
} }
} }
}
} }

View file

@ -1,63 +0,0 @@
import QtQuick 2.3
import QtQuick.Controls 1.2
import QtWebEngine 1.1
import QtWebChannel 1.0
import QtWebSockets 1.0
import "qwebchannel.js" as WebChannel
import "controls"
import "styles"
VrDialog {
id: root
HifiConstants { id: hifi }
title: "WebWindow"
resizable: true
contentImplicitWidth: clientArea.implicitWidth
contentImplicitHeight: clientArea.implicitHeight
backgroundColor: "#7f000000"
property url source: "about:blank"
signal navigating(string url)
Component.onCompleted: {
enabled = true
console.log("Web Window Created " + root);
webview.javaScriptConsoleMessage.connect(function(level, message, lineNumber, sourceID) {
console.log("Web Window JS message: " + sourceID + " " + lineNumber + " " + message);
});
webview.loadingChanged.connect(handleWebviewLoading)
}
function handleWebviewLoading(loadRequest) {
var HIFI_URL_PATTERN = /^hifi:\/\//;
if (WebEngineView.LoadStartedStatus == loadRequest.status) {
var newUrl = loadRequest.url.toString();
if (newUrl.match(HIFI_URL_PATTERN)) {
root.navigating(newUrl);
}
}
}
Item {
id: clientArea
implicitHeight: 600
implicitWidth: 800
x: root.clientX
y: root.clientY
width: root.clientWidth
height: root.clientHeight
WebEngineView {
id: webview
url: root.source
anchors.fill: parent
profile: WebEngineProfile {
httpUserAgent: "Mozilla/5.0 (HighFidelityInterface)"
}
}
} // item
} // dialog

View file

@ -4,7 +4,116 @@ import Hifi 1.0
// Currently for testing a pure QML replacement menu // Currently for testing a pure QML replacement menu
Item { Item {
Item {
objectName: "AllActions"
Action {
id: aboutApp
objectName: "HifiAction_" + MenuConstants.AboutApp
text: qsTr("About Interface")
}
//
// File Menu
//
Action {
id: login
objectName: "HifiAction_" + MenuConstants.Login
text: qsTr("Login")
}
Action {
id: quit
objectName: "HifiAction_" + MenuConstants.Quit
text: qsTr("Quit")
//shortcut: StandardKey.Quit
shortcut: "Ctrl+Q"
}
//
// Edit menu
//
Action {
id: undo
text: "Undo"
shortcut: StandardKey.Undo
}
Action {
id: redo
text: "Redo"
shortcut: StandardKey.Redo
}
Action {
id: animations
objectName: "HifiAction_" + MenuConstants.Animations
text: qsTr("Animations...")
}
Action {
id: attachments
text: qsTr("Attachments...")
}
Action {
id: explode
text: qsTr("Explode on quit")
checkable: true
checked: true
}
Action {
id: freeze
text: qsTr("Freeze on quit")
checkable: true
checked: false
}
ExclusiveGroup {
Action {
id: visibleToEveryone
objectName: "HifiAction_" + MenuConstants.VisibleToEveryone
text: qsTr("Everyone")
checkable: true
checked: true
}
Action {
id: visibleToFriends
objectName: "HifiAction_" + MenuConstants.VisibleToFriends
text: qsTr("Friends")
checkable: true
}
Action {
id: visibleToNoOne
objectName: "HifiAction_" + MenuConstants.VisibleToNoOne
text: qsTr("No one")
checkable: true
}
}
}
Menu { Menu {
objectName: "rootMenu"; objectName: "rootMenu";
Menu {
title: "File"
MenuItem { action: login }
MenuItem { action: explode }
MenuItem { action: freeze }
MenuItem { action: quit }
}
Menu {
title: "Tools"
Menu {
title: "I Am Visible To"
MenuItem { action: visibleToEveryone }
MenuItem { action: visibleToFriends }
MenuItem { action: visibleToNoOne }
}
MenuItem { action: animations }
}
Menu {
title: "Long menu name top menu"
MenuItem { action: aboutApp }
}
Menu {
title: "Help"
MenuItem { action: aboutApp }
}
} }
} }

View file

@ -1,10 +1,10 @@
import QtQuick 2.3 import QtQuick 2.3
import QtQuick.Controls 1.2 import QtQuick.Controls 1.2
import QtWebEngine 1.1 import QtWebKit 3.0
WebEngineView { WebView {
id: root id: root
anchors.fill: parent
objectName: "webview" objectName: "webview"
anchors.fill: parent
url: "about:blank" url: "about:blank"
} }

View file

@ -101,7 +101,6 @@
#include <VrMenu.h> #include <VrMenu.h>
#include <recording/Deck.h> #include <recording/Deck.h>
#include <recording/Recorder.h> #include <recording/Recorder.h>
#include <QmlWebWindowClass.h>
#include "AnimDebugDraw.h" #include "AnimDebugDraw.h"
#include "AudioClient.h" #include "AudioClient.h"
@ -363,17 +362,6 @@ Cube3DOverlay* _keyboardFocusHighlight{ nullptr };
int _keyboardFocusHighlightID{ -1 }; int _keyboardFocusHighlightID{ -1 };
PluginContainer* _pluginContainer; PluginContainer* _pluginContainer;
// FIXME hack access to the internal share context for the Chromium helper
// Normally we'd want to use QWebEngine::initialize(), but we can't because
// our primary context is a QGLWidget, which can't easily be initialized to share
// from a QOpenGLContext.
//
// So instead we create a new offscreen context to share with the QGLWidget,
// and manually set THAT to be the shared context for the Chromium helper
OffscreenGLCanvas* _chromiumShareContext { nullptr };
Q_GUI_EXPORT void qt_gl_set_global_share_context(QOpenGLContext *context);
Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
QApplication(argc, argv), QApplication(argc, argv),
_dependencyManagerIsSetup(setupEssentials(argc, argv)), _dependencyManagerIsSetup(setupEssentials(argc, argv)),
@ -635,11 +623,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
_glWidget->makeCurrent(); _glWidget->makeCurrent();
_glWidget->initializeGL(); _glWidget->initializeGL();
_chromiumShareContext = new OffscreenGLCanvas();
_chromiumShareContext->create(_glWidget->context()->contextHandle());
_chromiumShareContext->makeCurrent();
qt_gl_set_global_share_context(_chromiumShareContext->getContext());
_offscreenContext = new OffscreenGLCanvas(); _offscreenContext = new OffscreenGLCanvas();
_offscreenContext->create(_glWidget->context()->contextHandle()); _offscreenContext->create(_glWidget->context()->contextHandle());
_offscreenContext->makeCurrent(); _offscreenContext->makeCurrent();

View file

@ -320,14 +320,11 @@ void OffscreenQmlSurface::create(QOpenGLContext* shareContext) {
void OffscreenQmlSurface::resize(const QSize& newSize) { void OffscreenQmlSurface::resize(const QSize& newSize) {
if (!_renderer || !_renderer->_quickWindow) { if (!_renderer || !_renderer->_quickWindow) {
return;
}
QSize currentSize = _renderer->_quickWindow->geometry().size(); QSize currentSize = _renderer->_quickWindow->geometry().size();
if (newSize == currentSize) { if (newSize == currentSize) {
return; return;
} }
}
_qmlEngine->rootContext()->setContextProperty("surfaceSize", newSize); _qmlEngine->rootContext()->setContextProperty("surfaceSize", newSize);
@ -440,9 +437,7 @@ void OffscreenQmlSurface::updateQuick() {
} }
if (_render) { if (_render) {
QMutexLocker lock(&(_renderer->_mutex));
_renderer->post(RENDER); _renderer->post(RENDER);
_renderer->_cond.wait(&(_renderer->_mutex));
_render = false; _render = false;
} }

View file

@ -40,8 +40,8 @@ public:
void create(QOpenGLContext* context); void create(QOpenGLContext* context);
void resize(const QSize& size); void resize(const QSize& size);
QSize size() const; QSize size() const;
Q_INVOKABLE QObject* load(const QUrl& qmlSource, std::function<void(QQmlContext*, QObject*)> f = [](QQmlContext*, QObject*) {}); QObject* load(const QUrl& qmlSource, std::function<void(QQmlContext*, QObject*)> f = [](QQmlContext*, QObject*) {});
Q_INVOKABLE QObject* load(const QString& qmlSourceFile, std::function<void(QQmlContext*, QObject*)> f = [](QQmlContext*, QObject*) {}) { QObject* load(const QString& qmlSourceFile, std::function<void(QQmlContext*, QObject*)> f = [](QQmlContext*, QObject*) {}) {
return load(QUrl(qmlSourceFile), f); return load(QUrl(qmlSourceFile), f);
} }

View file

@ -39,11 +39,6 @@ void QOpenGLContextWrapper::doneCurrent() {
_context->doneCurrent(); _context->doneCurrent();
} }
void QOpenGLContextWrapper::setShareContext(QOpenGLContext* otherContext) {
_context->setShareContext(otherContext);
}
bool isCurrentContext(QOpenGLContext* context) { bool isCurrentContext(QOpenGLContext* context) {
return QOpenGLContext::currentContext() == context; return QOpenGLContext::currentContext() == context;
} }

View file

@ -25,12 +25,6 @@ public:
void swapBuffers(QSurface* surface); void swapBuffers(QSurface* surface);
bool makeCurrent(QSurface* surface); bool makeCurrent(QSurface* surface);
void doneCurrent(); void doneCurrent();
void setShareContext(QOpenGLContext* otherContext);
QOpenGLContext* getContext() {
return _context;
}
private: private:
QOpenGLContext* _context { nullptr }; QOpenGLContext* _context { nullptr };

View file

@ -1,3 +1,3 @@
set(TARGET_NAME script-engine) set(TARGET_NAME script-engine)
setup_hifi_library(Gui Network Script WebSockets Widgets) setup_hifi_library(Gui Network Script WebSockets Widgets)
link_hifi_libraries(shared networking ui octree gpu procedural model model-networking recording avatars fbx entities controllers animation audio physics) link_hifi_libraries(shared networking octree gpu procedural model model-networking recording avatars fbx entities controllers animation audio physics)

View file

@ -30,7 +30,6 @@
#include <udt/PacketHeaders.h> #include <udt/PacketHeaders.h>
#include <UUID.h> #include <UUID.h>
#include <QmlWebWindowClass.h>
#include <controllers/ScriptingInterface.h> #include <controllers/ScriptingInterface.h>
#include <AnimationObject.h> #include <AnimationObject.h>
@ -351,8 +350,6 @@ void ScriptEngine::init() {
qScriptRegisterSequenceMetaType<QVector<glm::quat> >(this); qScriptRegisterSequenceMetaType<QVector<glm::quat> >(this);
qScriptRegisterSequenceMetaType<QVector<QString> >(this); qScriptRegisterSequenceMetaType<QVector<QString> >(this);
registerFunction("OverlayWebWindow", QmlWebWindowClass::constructor);
QScriptValue xmlHttpRequestConstructorValue = newFunction(XMLHttpRequestClass::constructor); QScriptValue xmlHttpRequestConstructorValue = newFunction(XMLHttpRequestClass::constructor);
globalObject().setProperty("XMLHttpRequest", xmlHttpRequestConstructorValue); globalObject().setProperty("XMLHttpRequest", xmlHttpRequestConstructorValue);

View file

@ -1,3 +1,3 @@
set(TARGET_NAME ui) set(TARGET_NAME ui)
setup_hifi_library(OpenGL Network Qml Quick Script WebChannel WebSockets XmlPatterns) setup_hifi_library(OpenGL Network Qml Quick Script XmlPatterns)
link_hifi_libraries(shared networking gl) link_hifi_libraries(shared networking gl)

View file

@ -1,240 +0,0 @@
//
// Created by Bradley Austin Davis on 2015-12-15
// 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 "QmlWebWindowClass.h"
#include <mutex>
#include <QtCore/QThread>
#include <QtScript/QScriptContext>
#include <QtScript/QScriptEngine>
#include <QtQuick/QQuickItem>
#include <QtWebSockets/QWebSocketServer>
#include <QtWebSockets/QWebSocket>
#include <QtWebChannel/QWebChannel>
#include <QtCore/QJsonDocument>
#include <QtCore/QJsonObject>
#include <AddressManager.h>
#include <DependencyManager.h>
#include "OffscreenUi.h"
QWebSocketServer* QmlWebWindowClass::_webChannelServer { nullptr };
static QWebChannel webChannel;
static const uint16_t WEB_CHANNEL_PORT = 51016;
static std::atomic<int> nextWindowId;
static const char* const URL_PROPERTY = "source";
static const char* const TITLE_PROPERTY = "title";
static const QRegExp HIFI_URL_PATTERN { "^hifi://" };
void QmlScriptEventBridge::emitWebEvent(const QString& data) {
QMetaObject::invokeMethod(this, "webEventReceived", Qt::QueuedConnection, Q_ARG(QString, data));
}
void QmlScriptEventBridge::emitScriptEvent(const QString& data) {
QMetaObject::invokeMethod(this, "scriptEventReceived", Qt::QueuedConnection,
Q_ARG(int, _webWindow->getWindowId()), Q_ARG(QString, data));
}
class QmlWebTransport : public QWebChannelAbstractTransport {
Q_OBJECT
public:
QmlWebTransport(QWebSocket* webSocket) : _webSocket(webSocket) {
// Translate from the websocket layer to the webchannel layer
connect(webSocket, &QWebSocket::textMessageReceived, [this](const QString& message) {
QJsonParseError error;
QJsonDocument document = QJsonDocument::fromJson(message.toUtf8(), &error);
if (error.error || !document.isObject()) {
qWarning() << "Unable to parse incoming JSON message" << message;
return;
}
emit messageReceived(document.object(), this);
});
}
virtual void sendMessage(const QJsonObject &message) override {
// Translate from the webchannel layer to the websocket layer
_webSocket->sendTextMessage(QJsonDocument(message).toJson(QJsonDocument::Compact));
}
private:
QWebSocket* const _webSocket;
};
void QmlWebWindowClass::setupServer() {
if (!_webChannelServer) {
_webChannelServer = new QWebSocketServer("EventBridge Server", QWebSocketServer::NonSecureMode);
if (!_webChannelServer->listen(QHostAddress::LocalHost, WEB_CHANNEL_PORT)) {
qFatal("Failed to open web socket server.");
}
QObject::connect(_webChannelServer, &QWebSocketServer::newConnection, [] {
webChannel.connectTo(new QmlWebTransport(_webChannelServer->nextPendingConnection()));
});
}
}
// Method called by Qt scripts to create a new web window in the overlay
QScriptValue QmlWebWindowClass::constructor(QScriptContext* context, QScriptEngine* engine) {
QmlWebWindowClass* retVal { nullptr };
const QString title = context->argument(0).toString();
QString url = context->argument(1).toString();
if (!url.startsWith("http") && !url.startsWith("file://")) {
url = QUrl::fromLocalFile(url).toString();
}
const int width = std::max(100, std::min(1280, context->argument(2).toInt32()));;
const int height = std::max(100, std::min(720, context->argument(3).toInt32()));;
// Build the event bridge and wrapper on the main thread
QMetaObject::invokeMethod(DependencyManager::get<OffscreenUi>().data(), "load", Qt::BlockingQueuedConnection,
Q_ARG(const QString&, "QmlWebWindow.qml"),
Q_ARG(std::function<void(QQmlContext*, QObject*)>, [&](QQmlContext* context, QObject* object) {
setupServer();
retVal = new QmlWebWindowClass(object);
webChannel.registerObject(url.toLower(), retVal);
retVal->setTitle(title);
retVal->setURL(url);
retVal->setSize(width, height);
}));
connect(engine, &QScriptEngine::destroyed, retVal, &QmlWebWindowClass::deleteLater);
return engine->newQObject(retVal);
}
QmlWebWindowClass::QmlWebWindowClass(QObject* qmlWindow)
: _windowId(++nextWindowId), _qmlWindow(qmlWindow)
{
qDebug() << "Created window with ID " << _windowId;
Q_ASSERT(_qmlWindow);
Q_ASSERT(dynamic_cast<const QQuickItem*>(_qmlWindow));
QObject::connect(_qmlWindow, SIGNAL(navigating(QString)), this, SLOT(handleNavigation(QString)));
}
void QmlWebWindowClass::handleNavigation(const QString& url) {
DependencyManager::get<AddressManager>()->handleLookupString(url);
}
void QmlWebWindowClass::setVisible(bool visible) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "setVisible", Qt::AutoConnection, Q_ARG(bool, visible));
return;
}
auto qmlWindow = asQuickItem();
if (qmlWindow->isEnabled() != visible) {
qmlWindow->setEnabled(visible);
emit visibilityChanged(visible);
}
}
QQuickItem* QmlWebWindowClass::asQuickItem() const {
return dynamic_cast<QQuickItem*>(_qmlWindow);
}
bool QmlWebWindowClass::isVisible() const {
if (QThread::currentThread() != thread()) {
bool result;
QMetaObject::invokeMethod(const_cast<QmlWebWindowClass*>(this), "isVisible", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, result));
return result;
}
return asQuickItem()->isEnabled();
}
glm::vec2 QmlWebWindowClass::getPosition() const {
if (QThread::currentThread() != thread()) {
glm::vec2 result;
QMetaObject::invokeMethod(const_cast<QmlWebWindowClass*>(this), "getPosition", Qt::BlockingQueuedConnection, Q_RETURN_ARG(glm::vec2, result));
return result;
}
return glm::vec2(asQuickItem()->x(), asQuickItem()->y());
}
void QmlWebWindowClass::setPosition(const glm::vec2& position) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "setPosition", Qt::QueuedConnection, Q_ARG(glm::vec2, position));
return;
}
asQuickItem()->setPosition(QPointF(position.x, position.y));
}
void QmlWebWindowClass::setPosition(int x, int y) {
setPosition(glm::vec2(x, y));
}
glm::vec2 QmlWebWindowClass::getSize() const {
if (QThread::currentThread() != thread()) {
glm::vec2 result;
QMetaObject::invokeMethod(const_cast<QmlWebWindowClass*>(this), "getSize", Qt::BlockingQueuedConnection, Q_RETURN_ARG(glm::vec2, result));
return result;
}
return glm::vec2(asQuickItem()->width(), asQuickItem()->height());
}
void QmlWebWindowClass::setSize(const glm::vec2& size) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "setSize", Qt::QueuedConnection, Q_ARG(glm::vec2, size));
}
asQuickItem()->setSize(QSizeF(size.x, size.y));
}
void QmlWebWindowClass::setSize(int width, int height) {
setSize(glm::vec2(width, height));
}
QString QmlWebWindowClass::getURL() const {
if (QThread::currentThread() != thread()) {
QString result;
QMetaObject::invokeMethod(const_cast<QmlWebWindowClass*>(this), "getURL", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QString, result));
return result;
}
return _qmlWindow->property(URL_PROPERTY).toString();
}
void QmlWebWindowClass::setURL(const QString& urlString) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "setURL", Qt::QueuedConnection, Q_ARG(QString, urlString));
}
_qmlWindow->setProperty(URL_PROPERTY, urlString);
}
void QmlWebWindowClass::setTitle(const QString& title) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "setTitle", Qt::QueuedConnection, Q_ARG(QString, title));
}
_qmlWindow->setProperty(TITLE_PROPERTY, title);
}
void QmlWebWindowClass::close() {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "close", Qt::QueuedConnection);
}
_qmlWindow->setProperty("destroyOnInvisible", true);
_qmlWindow->setProperty("visible", false);
_qmlWindow->deleteLater();
}
void QmlWebWindowClass::hasClosed() {
}
void QmlWebWindowClass::raise() {
// FIXME
}
#include "QmlWebWindowClass.moc"

View file

@ -1,104 +0,0 @@
//
// Created by Bradley Austin Davis on 2015-12-15
// 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
//
#ifndef hifi_ui_QmlWebWindowClass_h
#define hifi_ui_QmlWebWindowClass_h
#include <QtCore/QObject>
#include <GLMHelpers.h>
#include <QtScript/QScriptValue>
#include <QtQuick/QQuickItem>
#include <QtWebChannel/QWebChannelAbstractTransport>
class QScriptEngine;
class QScriptContext;
class QmlWebWindowClass;
class QWebSocketServer;
class QWebSocket;
class QmlScriptEventBridge : public QObject {
Q_OBJECT
public:
QmlScriptEventBridge(const QmlWebWindowClass* webWindow) : _webWindow(webWindow) {}
public slots :
void emitWebEvent(const QString& data);
void emitScriptEvent(const QString& data);
signals:
void webEventReceived(const QString& data);
void scriptEventReceived(int windowId, const QString& data);
private:
const QmlWebWindowClass* _webWindow { nullptr };
QWebSocket *_socket { nullptr };
};
// FIXME refactor this class to be a QQuickItem derived type and eliminate the needless wrapping
class QmlWebWindowClass : public QObject {
Q_OBJECT
Q_PROPERTY(QObject* eventBridge READ getEventBridge CONSTANT)
Q_PROPERTY(int windowId READ getWindowId CONSTANT)
Q_PROPERTY(QString url READ getURL CONSTANT)
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 NOTIFY visibilityChanged)
public:
static QScriptValue constructor(QScriptContext* context, QScriptEngine* engine);
QmlWebWindowClass(QObject* qmlWindow);
public slots:
bool isVisible() const;
void setVisible(bool visible);
glm::vec2 getPosition() const;
void setPosition(const glm::vec2& position);
void setPosition(int x, int y);
glm::vec2 getSize() const;
void setSize(const glm::vec2& size);
void setSize(int width, int height);
QString getURL() const;
void setURL(const QString& url);
void setTitle(const QString& title);
// Ugh.... do not want to do
Q_INVOKABLE void raise();
Q_INVOKABLE void close();
Q_INVOKABLE int getWindowId() const { return _windowId; };
Q_INVOKABLE QmlScriptEventBridge* getEventBridge() const { return _eventBridge; };
signals:
void visibilityChanged(bool visible); // Tool window
void urlChanged();
void moved(glm::vec2 position);
void resized(QSizeF size);
void closed();
private slots:
void hasClosed();
void handleNavigation(const QString& url);
private:
static void setupServer();
static QWebSocketServer* _webChannelServer;
QQuickItem* asQuickItem() const;
QmlScriptEventBridge* const _eventBridge { new QmlScriptEventBridge(this) };
// FIXME needs to be initialized in the ctor once we have support
// for tool window panes in QML
const bool _isToolWindow { false };
const int _windowId;
QObject* const _qmlWindow;
};
#endif

View file

@ -2,32 +2,15 @@
set(TARGET_NAME "ui-test") set(TARGET_NAME "ui-test")
# This is not a testcase -- just set it up as a regular hifi project # This is not a testcase -- just set it up as a regular hifi project
setup_hifi_project(Network OpenGL Qml Quick Script WebChannel WebEngine WebSockets) setup_hifi_project(Widgets OpenGL Network Qml Quick Script)
set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Tests/manual-tests/") set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Tests/manual-tests/")
if (WIN32) if (WIN32)
target_link_libraries(${TARGET_NAME} wsock32.lib opengl32.lib Winmm.lib) target_link_libraries(${TARGET_NAME} wsock32.lib opengl32.lib Winmm.lib)
# Issue causes build failure unless we add this directory.
# See https://bugreports.qt.io/browse/QTBUG-43351
add_paths_to_fixup_libs(${Qt5_DIR}/../../../plugins/qtwebengine)
endif() endif()
# link in the shared libraries # link in the shared libraries
link_hifi_libraries(shared networking gl gpu ui) link_hifi_libraries(shared networking gl gpu ui)
# copy the resources files beside the executable
add_custom_command(TARGET ${TARGET_NAME} POST_BUILD
COMMAND "${CMAKE_COMMAND}" -E copy_directory
"${PROJECT_SOURCE_DIR}/qml"
$<TARGET_FILE_DIR:${TARGET_NAME}>/qml
)
target_glew()
if (WIN32)
set(EXTRA_DEPLOY_OPTIONS "--qmldir ${PROJECT_SOURCE_DIR}/../../interface/resources/qml")
endif()
package_libraries_for_deployment() package_libraries_for_deployment()

View file

@ -1,91 +1,41 @@
// //
// Created by Bradley Austin Davis on 2015-04-22 // main.cpp
// Copyright 2013-2015 High Fidelity, Inc. // tests/render-utils/src
//
// Copyright 2014 High Fidelity, Inc.
// //
// Distributed under the Apache License, Version 2.0. // Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
// //
#include <gl/Config.h> #include "OffscreenUi.h"
#include <gl/OglplusHelpers.h> #include <QWindow>
#include <gl/GLHelpers.h> #include <QFile>
#include <memory> #include <QTime>
#include <QImage>
#include <QtCore/QFile> #include <QTimer>
#include <QtCore/QDir> #include <QElapsedTimer>
#include <QtCore/QTime> #include <QOpenGLContext>
#include <QtCore/QTimer> #include <QOpenGLBuffer>
#include <QtCore/QElapsedTimer> #include <QOpenGLShaderProgram>
#include <QtCore/QLoggingCategory> #include <QResizeEvent>
#include <QtCore/QThread> #include <QLoggingCategory>
#include <QtCore/QUuid> #include <QOpenGLTexture>
#include <QOpenGLVertexArrayObject>
#include <QtGui/QWindow> #include <QApplication>
#include <QtGui/QImage> #include <QOpenGLDebugLogger>
#include <QtGui/QGuiApplication> #include <QOpenGLFunctions>
#include <QtGui/QResizeEvent> #include <QQmlContext>
#include <QtGui/QScreen>
#include <gl/QOpenGLContextWrapper.h>
#include <QtScript/QScriptEngine>
#include <QtQml/QQmlContext>
#include <QtQml/QQmlApplicationEngine> #include <QtQml/QQmlApplicationEngine>
#include <GLMHelpers.h>
#include <gl/OffscreenGLCanvas.h>
#include <OffscreenUi.h>
#include <PathUtils.h> #include <PathUtils.h>
#include <memory>
#include <glm/glm.hpp>
#include <PathUtils.h> #include <PathUtils.h>
#include <MessageDialog.h> #include <QDir>
#include <VrMenu.h> #include "MessageDialog.h"
#include <InfoView.h> #include "VrMenu.h"
#include <QmlWebWindowClass.h> #include "InfoView.h"
#include <RegisteredMetaTypes.h> #include <QDesktopWidget>
const QString& getResourcesDir() {
static QString dir;
if (dir.isEmpty()) {
QDir path(__FILE__);
path.cdUp();
dir = path.cleanPath(path.absoluteFilePath("../../../interface/resources/")) + "/";
qDebug() << "Resources Path: " << dir;
}
return dir;
}
const QString& getExamplesDir() {
static QString dir;
if (dir.isEmpty()) {
QDir path(__FILE__);
path.cdUp();
dir = path.cleanPath(path.absoluteFilePath("../../../examples/")) + "/";
qDebug() << "Resources Path: " << dir;
}
return dir;
}
const QString& getInterfaceQmlDir() {
static QString dir;
if (dir.isEmpty()) {
dir = getResourcesDir() + "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;
}
class RateCounter { class RateCounter {
std::vector<float> times; std::vector<float> times;
@ -124,394 +74,142 @@ public:
}; };
class MenuConstants : public QObject{
extern QOpenGLContext* qt_gl_global_share_context();
static bool hadUncaughtExceptions(QScriptEngine& engine, const QString& fileName) {
if (engine.hasUncaughtException()) {
const auto backtrace = engine.uncaughtExceptionBacktrace();
const auto exception = engine.uncaughtException().toString();
const auto line = QString::number(engine.uncaughtExceptionLineNumber());
engine.clearExceptions();
auto message = QString("[UncaughtException] %1 in %2:%3").arg(exception, fileName, line);
if (!backtrace.empty()) {
static const auto lineSeparator = "\n ";
message += QString("\n[Backtrace]%1%2").arg(lineSeparator, backtrace.join(lineSeparator));
}
qWarning() << qPrintable(message);
return true;
}
return false;
}
const unsigned int SCRIPT_DATA_CALLBACK_USECS = floor(((1.0f / 60.0f) * 1000 * 1000) + 0.5f);
static QScriptValue debugPrint(QScriptContext* context, QScriptEngine* engine) {
QString message = "";
for (int i = 0; i < context->argumentCount(); i++) {
if (i > 0) {
message += " ";
}
message += context->argument(i).toString();
}
qDebug().noquote() << "script:print()<<" << message; // noquote() so that \n is treated as newline
message = message.replace("\\", "\\\\")
.replace("\n", "\\n")
.replace("\r", "\\r")
.replace("'", "\\'");
engine->evaluate("Script.print('" + message + "')");
return QScriptValue();
}
class ScriptEngine : public QScriptEngine {
Q_OBJECT Q_OBJECT
Q_ENUMS(Item)
public: public:
void loadFile(const QString& scriptPath) { enum Item {
if (_isRunning) { RenderLookAtTargets,
return; };
}
qDebug() << "Loading script from " << scriptPath;
_fileNameString = scriptPath;
QFile file(scriptPath); public:
if (file.exists()) { MenuConstants(QObject* parent = nullptr) : QObject(parent) {
file.open(QIODevice::ReadOnly);
_scriptContents = file.readAll();
} else {
qFatal("Missing file ");
}
runInThread();
}
Q_INVOKABLE void stop() {
if (!_isFinished) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "stop");
return;
} }
_isFinished = true;
if (_wantSignals) {
emit runningStateChanged();
}
}
}
Q_INVOKABLE void print(const QString& message) {
if (_wantSignals) {
emit printedMessage(message);
}
}
Q_INVOKABLE QObject* setupTimerWithInterval(const QScriptValue& function, int intervalMS, bool isSingleShot) {
// create the timer, add it to the map, and start it
QTimer* newTimer = new QTimer(this);
newTimer->setSingleShot(isSingleShot);
connect(newTimer, &QTimer::timeout, this, &ScriptEngine::timerFired);
// make sure the timer stops when the script does
connect(this, &ScriptEngine::scriptEnding, newTimer, &QTimer::stop);
_timerFunctionMap.insert(newTimer, function);
newTimer->start(intervalMS);
return newTimer;
}
Q_INVOKABLE QObject* setInterval(const QScriptValue& function, int intervalMS) {
return setupTimerWithInterval(function, intervalMS, false);
}
Q_INVOKABLE QObject* setTimeout(const QScriptValue& function, int timeoutMS) {
return setupTimerWithInterval(function, timeoutMS, true);
}
private:
void runInThread() {
QThread* workerThread = new QThread();
connect(workerThread, &QThread::finished, workerThread, &QThread::deleteLater);
connect(workerThread, &QThread::started, this, &ScriptEngine::run);
connect(workerThread, &QThread::finished, this, &ScriptEngine::deleteLater);
connect(this, &ScriptEngine::doneRunning, workerThread, &QThread::quit);
moveToThread(workerThread);
workerThread->start();
}
void init() {
_isInitialized = true;
registerMetaTypes(this);
registerGlobalObject("Script", this);
qScriptRegisterSequenceMetaType<QVector<QUuid>>(this);
qScriptRegisterSequenceMetaType<QVector<QString>>(this);
globalObject().setProperty("OverlayWebWindow", newFunction(QmlWebWindowClass::constructor));
QScriptValue printConstructorValue = newFunction(debugPrint);
globalObject().setProperty("print", printConstructorValue);
}
void timerFired() {
QTimer* callingTimer = reinterpret_cast<QTimer*>(sender());
QScriptValue timerFunction = _timerFunctionMap.value(callingTimer);
if (!callingTimer->isActive()) {
// this timer is done, we can kill it
_timerFunctionMap.remove(callingTimer);
delete callingTimer;
}
// call the associated JS function, if it exists
if (timerFunction.isValid()) {
timerFunction.call();
}
}
void run() {
if (!_isInitialized) {
init();
}
_isRunning = true;
if (_wantSignals) {
emit runningStateChanged();
}
QScriptValue result = evaluate(_scriptContents, _fileNameString);
QElapsedTimer startTime;
startTime.start();
int thisFrame = 0;
qint64 lastUpdate = usecTimestampNow();
while (!_isFinished) {
int usecToSleep = (thisFrame++ * SCRIPT_DATA_CALLBACK_USECS) - startTime.nsecsElapsed() / 1000; // nsec to usec
if (usecToSleep > 0) {
usleep(usecToSleep);
}
if (_isFinished) {
break;
}
QCoreApplication::processEvents();
if (_isFinished) {
break;
}
qint64 now = usecTimestampNow();
float deltaTime = (float)(now - lastUpdate) / (float)USECS_PER_SECOND;
if (!_isFinished) {
if (_wantSignals) {
emit update(deltaTime);
}
}
lastUpdate = now;
// Debug and clear exceptions
hadUncaughtExceptions(*this, _fileNameString);
}
if (_wantSignals) {
emit scriptEnding();
}
if (_wantSignals) {
emit finished(_fileNameString, this);
}
_isRunning = false;
if (_wantSignals) {
emit runningStateChanged();
emit doneRunning();
}
}
void registerGlobalObject(const QString& name, QObject* object) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "registerGlobalObject",
Q_ARG(const QString&, name),
Q_ARG(QObject*, object));
return;
}
if (!globalObject().property(name).isValid()) {
if (object) {
QScriptValue value = newQObject(object);
globalObject().setProperty(name, value);
} else {
globalObject().setProperty(name, QScriptValue());
}
}
}
void registerFunction(const QString& name, QScriptEngine::FunctionSignature functionSignature, int numArguments) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "registerFunction",
Q_ARG(const QString&, name),
Q_ARG(QScriptEngine::FunctionSignature, functionSignature),
Q_ARG(int, numArguments));
return;
}
QScriptValue scriptFun = newFunction(functionSignature, numArguments);
globalObject().setProperty(name, scriptFun);
}
void registerFunction(const QString& parent, const QString& name, QScriptEngine::FunctionSignature functionSignature, int numArguments) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "registerFunction",
Q_ARG(const QString&, name),
Q_ARG(QScriptEngine::FunctionSignature, functionSignature),
Q_ARG(int, numArguments));
return;
}
QScriptValue object = globalObject().property(parent);
if (object.isValid()) {
QScriptValue scriptFun = newFunction(functionSignature, numArguments);
object.setProperty(name, scriptFun);
}
}
signals:
void scriptLoaded(const QString& scriptFilename);
void errorLoadingScript(const QString& scriptFilename);
void update(float deltaTime);
void scriptEnding();
void finished(const QString& fileNameString, ScriptEngine* engine);
void cleanupMenuItem(const QString& menuItemString);
void printedMessage(const QString& message);
void errorMessage(const QString& message);
void runningStateChanged();
void evaluationFinished(QScriptValue result, bool isException);
void loadScript(const QString& scriptName, bool isUserLoaded);
void reloadScript(const QString& scriptName, bool isUserLoaded);
void doneRunning();
private:
QString _scriptContents;
QString _fileNameString;
QString _parentURL;
bool _isInitialized { false };
std::atomic<bool> _isFinished { false };
std::atomic<bool> _isRunning { false };
bool _wantSignals { true };
QHash<QTimer*, QScriptValue> _timerFunctionMap;
}; };
const QString& getResourcesDir() {
static QString dir;
ScriptEngine* loadScript(const QString& scriptFilename) { if (dir.isEmpty()) {
ScriptEngine* scriptEngine = new ScriptEngine(); QDir path(__FILE__);
scriptEngine->loadFile(scriptFilename); path.cdUp();
return scriptEngine; dir = path.cleanPath(path.absoluteFilePath("../../../interface/resources/")) + "/";
qDebug() << "Resources Path: " << dir;
}
return dir;
} }
OffscreenGLCanvas* _chromiumShareContext { nullptr }; const QString& getQmlDir() {
Q_GUI_EXPORT void qt_gl_set_global_share_context(QOpenGLContext *context); static QString dir;
if (dir.isEmpty()) {
dir = getResourcesDir() + "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 // Create a simple OpenGL window that renders text in various ways
class QTestWindow : public QWindow { class QTestWindow : public QWindow, private QOpenGLFunctions {
Q_OBJECT Q_OBJECT
QOpenGLContextWrapper* _context{ nullptr }; QOpenGLContext* _context{ nullptr };
QSize _size; QSize _size;
bool _altPressed{ false }; bool _altPressed{ false };
RateCounter fps; RateCounter fps;
QTimer _timer; QTimer _timer;
int testQmlTexture{ 0 }; int testQmlTexture{ 0 };
ProgramPtr _program;
ShapeWrapperPtr _plane;
QScriptEngine* _scriptEngine { nullptr };
public: public:
QObject* rootMenu; QObject* rootMenu;
QTestWindow() { QTestWindow() {
_scriptEngine = new ScriptEngine();
_timer.setInterval(1); _timer.setInterval(1);
QObject::connect(&_timer, &QTimer::timeout, this, &QTestWindow::draw); connect(&_timer, &QTimer::timeout, [=] {
draw();
});
_chromiumShareContext = new OffscreenGLCanvas(); DependencyManager::set<OffscreenUi>();
_chromiumShareContext->create();
_chromiumShareContext->makeCurrent();
qt_gl_set_global_share_context(_chromiumShareContext->getContext());
{
setSurfaceType(QSurface::OpenGLSurface); setSurfaceType(QSurface::OpenGLSurface);
QSurfaceFormat format = getDefaultOpenGLSurfaceFormat();
QSurfaceFormat format;
format.setDepthBufferSize(16);
format.setStencilBufferSize(8);
format.setVersion(4, 1);
format.setProfile(QSurfaceFormat::OpenGLContextProfile::CompatibilityProfile);
format.setOption(QSurfaceFormat::DebugContext);
setFormat(format); setFormat(format);
_context = new QOpenGLContextWrapper();
_context = new QOpenGLContext;
_context->setFormat(format); _context->setFormat(format);
_context->setShareContext(_chromiumShareContext->getContext());
}
if (!_context->create()) { if (!_context->create()) {
qFatal("Could not create OpenGL context"); qFatal("Could not create OpenGL context");
} }
show(); show();
makeCurrent(); makeCurrent();
initializeOpenGLFunctions();
glewExperimental = true; {
glewInit(); QOpenGLDebugLogger* logger = new QOpenGLDebugLogger(this);
glGetError(); 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);
}
using namespace oglplus; qDebug() << (const char*)this->glGetString(GL_VERSION);
Context::Enable(Capability::Blend); glEnable(GL_BLEND);
Context::BlendFunc(BlendFunction::SrcAlpha, BlendFunction::OneMinusSrcAlpha); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
Context::Disable(Capability::DepthTest); glClearColor(0.2f, 0.2f, 0.2f, 1);
Context::Disable(Capability::CullFace); glDisable(GL_DEPTH_TEST);
Context::ClearColor(0.2f, 0.2f, 0.2f, 1);
MessageDialog::registerType(); MessageDialog::registerType();
VrMenu::registerType();
InfoView::registerType(); InfoView::registerType();
auto offscreenUi = DependencyManager::set<OffscreenUi>();
{
offscreenUi->create(_context->getContext());
offscreenUi->setProxyWindow(this);
auto offscreenUi = DependencyManager::get<OffscreenUi>();
offscreenUi->create(_context);
connect(offscreenUi.data(), &OffscreenUi::textureUpdated, this, [this, offscreenUi](int textureId) { connect(offscreenUi.data(), &OffscreenUi::textureUpdated, this, [this, offscreenUi](int textureId) {
testQmlTexture = textureId; testQmlTexture = textureId;
}); });
makeCurrent(); makeCurrent();
}
offscreenUi->setProxyWindow(this);
auto primaryScreen = QGuiApplication::primaryScreen(); QDesktopWidget* desktop = QApplication::desktop();
auto targetScreen = primaryScreen; QRect rect = desktop->availableGeometry(desktop->screenCount() - 1);
auto screens = QGuiApplication::screens(); int height = rect.height();
if (screens.size() > 1) { //rect.setHeight(height / 2);
for (auto screen : screens) { rect.setY(rect.y() + height / 2);
if (screen != targetScreen) {
targetScreen = screen;
break;
}
}
}
auto rect = targetScreen->availableGeometry();
rect.setWidth(rect.width() * 0.8f);
rect.setHeight(rect.height() * 0.8f);
rect.moveTo(QPoint(20, 20));
setGeometry(rect); setGeometry(rect);
// setFramePosition(QPoint(-1000, 0));
// resize(QSize(800, 600));
#ifdef QML_CONTROL_GALLERY #ifdef QML_CONTROL_GALLERY
offscreenUi->setBaseUrl(QUrl::fromLocalFile(getTestQmlDir())); offscreenUi->setBaseUrl(QUrl::fromLocalFile(getTestQmlDir()));
offscreenUi->load(QUrl("main.qml")); offscreenUi->load(QUrl("main.qml"));
#else #else
offscreenUi->setBaseUrl(QUrl::fromLocalFile(getInterfaceQmlDir())); offscreenUi->setBaseUrl(QUrl::fromLocalFile(getQmlDir()));
offscreenUi->load(QUrl("TestRoot.qml")); offscreenUi->load(QUrl("TestRoot.qml"));
offscreenUi->load(QUrl("TestMenu.qml"));
// Requires a root menu to have been loaded before it can load
VrMenu::load();
#endif #endif
installEventFilter(offscreenUi.data()); installEventFilter(offscreenUi.data());
offscreenUi->resume(); offscreenUi->resume();
@ -529,35 +227,16 @@ private:
} }
makeCurrent(); makeCurrent();
auto error = glGetError(); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
if (error != GL_NO_ERROR) { glViewport(0, 0, _size.width() * devicePixelRatio(), _size.height() * devicePixelRatio());
qDebug() << "GL error in entering draw " << error;
}
using namespace oglplus; renderQml();
Context::Clear().ColorBuffer().DepthBuffer();
ivec2 size(_size.width(), _size.height());
size *= devicePixelRatio();
size = glm::max(size, ivec2(100, 100));
Context::Viewport(size.x, size.y);
if (!_program) {
_program = loadDefaultShader();
_plane = loadPlane(_program);
}
if (testQmlTexture > 0) {
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);
}
_program->Bind();
_plane->Use();
_plane->Draw();
_context->swapBuffers(this); _context->swapBuffers(this);
glFinish();
fps.increment(); fps.increment();
if (fps.elapsed() >= 10.0f) { if (fps.elapsed() >= 2.0f) {
qDebug() << "FPS: " << fps.rate(); qDebug() << "FPS: " << fps.rate();
fps.reset(); fps.reset();
} }
@ -567,6 +246,8 @@ private:
_context->makeCurrent(this); _context->makeCurrent(this);
} }
void renderQml();
void resizeWindow(const QSize & size) { void resizeWindow(const QSize & size) {
_size = size; _size = size;
DependencyManager::get<OffscreenUi>()->resize(_size); DependencyManager::get<OffscreenUi>()->resize(_size);
@ -588,13 +269,11 @@ protected:
offscreenUi->load("Browser.qml"); offscreenUi->load("Browser.qml");
} }
break; break;
case Qt::Key_L:
case Qt::Key_J:
if (event->modifiers() & Qt::CTRL) { if (event->modifiers() & Qt::CTRL) {
loadScript(getExamplesDir() + "tests/qmlWebTest.js"); InfoView::show(getResourcesDir() + "html/interface-welcome.html", true);
} }
break; break;
case Qt::Key_K: case Qt::Key_K:
if (event->modifiers() & Qt::CTRL) { if (event->modifiers() & Qt::CTRL) {
OffscreenUi::question("Message title", "Message contents", [](QMessageBox::Button b){ OffscreenUi::question("Message title", "Message contents", [](QMessageBox::Button b){
@ -602,9 +281,22 @@ protected:
}); });
} }
break; break;
case Qt::Key_J:
if (event->modifiers() & Qt::CTRL) {
auto offscreenUi = DependencyManager::get<OffscreenUi>();
rootMenu = offscreenUi->getRootItem()->findChild<QObject*>("rootMenu");
QMetaObject::invokeMethod(rootMenu, "popup");
}
break;
} }
QWindow::keyPressEvent(event); QWindow::keyPressEvent(event);
} }
QQmlContext* menuContext{ nullptr };
void keyReleaseEvent(QKeyEvent *event) override {
if (_altPressed && Qt::Key_Alt == event->key()) {
VrMenu::toggle();
}
}
void moveEvent(QMoveEvent* event) override { void moveEvent(QMoveEvent* event) override {
static qreal oldPixelRatio = 0.0; static qreal oldPixelRatio = 0.0;
@ -616,26 +308,40 @@ protected:
} }
}; };
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( const char * LOG_FILTER_RULES = R"V0G0N(
hifi.offscreen.focus.debug=false hifi.offscreen.focus.debug=false
qt.quick.mouse.debug=false qt.quick.mouse.debug=false
)V0G0N"; )V0G0N";
void messageHandler(QtMsgType type, const QMessageLogContext& context, const QString& message) {
QString logMessage = message;
#ifdef Q_OS_WIN
if (!logMessage.isEmpty()) {
OutputDebugStringA(logMessage.toLocal8Bit().constData());
OutputDebugStringA("\n");
}
#endif
}
int main(int argc, char** argv) { int main(int argc, char** argv) {
QGuiApplication app(argc, argv); QApplication app(argc, argv);
qInstallMessageHandler(messageHandler);
QLoggingCategory::setFilterRules(LOG_FILTER_RULES); QLoggingCategory::setFilterRules(LOG_FILTER_RULES);
QTestWindow window; QTestWindow window;
app.exec(); app.exec();