Revert "Migrate QML based web views to Qt WebEngine"

This commit is contained in:
Brad Davis 2015-12-18 17:09:38 -08:00
parent 868d10651a
commit 1f3adeb666
24 changed files with 344 additions and 1112 deletions

View file

@ -40,7 +40,7 @@ macro(PACKAGE_LIBRARIES_FOR_DEPLOYMENT)
add_custom_command(
TARGET ${TARGET_NAME}
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)
find_program(MACDEPLOYQT_COMMAND macdeployqt PATHS ${QT_DIR}/bin NO_DEFAULT_PATH)

View file

@ -62,7 +62,7 @@ var directory = (function () {
function setUp() {
viewport = Controller.getViewportDimensions();
directoryWindow = new OverlayWebWindow('Directory', DIRECTORY_URL, 900, 700, false);
directoryWindow = new WebWindow('Directory', DIRECTORY_URL, 900, 700, false);
directoryWindow.setVisible(false);
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})
endif ()
find_package(Qt5 COMPONENTS
Gui Multimedia Network OpenGL Qml Quick Script Svg
WebChannel WebEngine WebEngineWidgets WebKitWidgets WebSockets)
find_package(Qt5 COMPONENTS Gui Multimedia Network OpenGL Qml Quick Script Svg WebKitWidgets WebSockets)
# grab the ui files in resources/ui
file (GLOB_RECURSE QT_UI_FILES ui/*.ui)
@ -177,17 +175,9 @@ include_directories("${PROJECT_SOURCE_DIR}/src")
target_link_libraries(
${TARGET_NAME}
Qt5::Gui Qt5::Network Qt5::Multimedia Qt5::OpenGL
Qt5::Qml Qt5::Quick Qt5::Script Qt5::Svg
Qt5::WebChannel Qt5::WebEngine Qt5::WebEngineWidgets Qt5::WebKitWidgets
Qt5::Gui Qt5::Network Qt5::Multimedia Qt5::OpenGL Qt5::Script Qt5::Svg 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
add_definitions(-DQT_NO_BEARERMANAGEMENT)
@ -219,9 +209,5 @@ else (APPLE)
endif()
endif (APPLE)
if (WIN32)
set(EXTRA_DEPLOY_OPTIONS "--qmldir ${PROJECT_SOURCE_DIR}/resources/qml")
endif()
package_libraries_for_deployment()
consolidate_stack_components()

View file

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

View file

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

View file

@ -2,7 +2,7 @@ import Hifi 1.0
import QtQuick 2.3
import QtQuick.Controls 1.2
import QtQuick.Controls.Styles 1.3
import QtWebEngine 1.1
import QtWebKit 3.0
import "controls"
VrDialog {
@ -24,22 +24,27 @@ VrDialog {
anchors.margins: parent.margins
anchors.topMargin: parent.topMargin
WebEngineView {
objectName: "WebView"
id: webview
url: "https://metaverse.highfidelity.com/marketplace"
ScrollView {
anchors.fill: parent
onNavigationRequested: {
console.log(request.url)
if (!marketplaceDialog.navigationRequested(request.url)) {
console.log("Application absorbed the request")
request.action = WebView.IgnoreRequest;
WebView {
objectName: "WebView"
id: webview
url: "https://metaverse.highfidelity.com/marketplace"
anchors.fill: parent
onNavigationRequested: {
console.log(request.url)
if (!marketplaceDialog.navigationRequested(request.url)) {
console.log("Application absorbed the request")
request.action = WebView.IgnoreRequest;
return;
}
console.log("Application passed on the request")
request.action = WebView.AcceptRequest;
return;
}
console.log("Application passed on the request")
request.action = WebView.AcceptRequest;
return;
}
}
}
}
}
}
}

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
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 {
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.Controls 1.2
import QtWebEngine 1.1
import QtWebKit 3.0
WebEngineView {
WebView {
id: root
anchors.fill: parent
objectName: "webview"
anchors.fill: parent
url: "about:blank"
}

View file

@ -101,7 +101,6 @@
#include <VrMenu.h>
#include <recording/Deck.h>
#include <recording/Recorder.h>
#include <QmlWebWindowClass.h>
#include "AnimDebugDraw.h"
#include "AudioClient.h"
@ -363,17 +362,6 @@ Cube3DOverlay* _keyboardFocusHighlight{ nullptr };
int _keyboardFocusHighlightID{ -1 };
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) :
QApplication(argc, argv),
_dependencyManagerIsSetup(setupEssentials(argc, argv)),
@ -635,11 +623,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
_glWidget->makeCurrent();
_glWidget->initializeGL();
_chromiumShareContext = new OffscreenGLCanvas();
_chromiumShareContext->create(_glWidget->context()->contextHandle());
_chromiumShareContext->makeCurrent();
qt_gl_set_global_share_context(_chromiumShareContext->getContext());
_offscreenContext = new OffscreenGLCanvas();
_offscreenContext->create(_glWidget->context()->contextHandle());
_offscreenContext->makeCurrent();

View file

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

View file

@ -40,8 +40,8 @@ public:
void create(QOpenGLContext* context);
void resize(const QSize& size);
QSize size() const;
Q_INVOKABLE 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 QUrl& qmlSource, 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);
}

View file

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

View file

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

View file

@ -1,3 +1,3 @@
set(TARGET_NAME script-engine)
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 <UUID.h>
#include <QmlWebWindowClass.h>
#include <controllers/ScriptingInterface.h>
#include <AnimationObject.h>
@ -351,8 +350,6 @@ void ScriptEngine::init() {
qScriptRegisterSequenceMetaType<QVector<glm::quat> >(this);
qScriptRegisterSequenceMetaType<QVector<QString> >(this);
registerFunction("OverlayWebWindow", QmlWebWindowClass::constructor);
QScriptValue xmlHttpRequestConstructorValue = newFunction(XMLHttpRequestClass::constructor);
globalObject().setProperty("XMLHttpRequest", xmlHttpRequestConstructorValue);

View file

@ -1,3 +1,3 @@
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)

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")
# 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/")
if (WIN32)
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()
# link in the shared libraries
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()

View file

@ -1,91 +1,41 @@
//
// Created by Bradley Austin Davis on 2015-04-22
// Copyright 2013-2015 High Fidelity, Inc.
// main.cpp
// tests/render-utils/src
//
// Copyright 2014 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 <gl/Config.h>
#include <gl/OglplusHelpers.h>
#include <gl/GLHelpers.h>
#include <memory>
#include <QtCore/QFile>
#include <QtCore/QDir>
#include <QtCore/QTime>
#include <QtCore/QTimer>
#include <QtCore/QElapsedTimer>
#include <QtCore/QLoggingCategory>
#include <QtCore/QThread>
#include <QtCore/QUuid>
#include <QtGui/QWindow>
#include <QtGui/QImage>
#include <QtGui/QGuiApplication>
#include <QtGui/QResizeEvent>
#include <QtGui/QScreen>
#include <gl/QOpenGLContextWrapper.h>
#include <QtScript/QScriptEngine>
#include <QtQml/QQmlContext>
#include "OffscreenUi.h"
#include <QWindow>
#include <QFile>
#include <QTime>
#include <QImage>
#include <QTimer>
#include <QElapsedTimer>
#include <QOpenGLContext>
#include <QOpenGLBuffer>
#include <QOpenGLShaderProgram>
#include <QResizeEvent>
#include <QLoggingCategory>
#include <QOpenGLTexture>
#include <QOpenGLVertexArrayObject>
#include <QApplication>
#include <QOpenGLDebugLogger>
#include <QOpenGLFunctions>
#include <QQmlContext>
#include <QtQml/QQmlApplicationEngine>
#include <GLMHelpers.h>
#include <gl/OffscreenGLCanvas.h>
#include <OffscreenUi.h>
#include <PathUtils.h>
#include <memory>
#include <glm/glm.hpp>
#include <PathUtils.h>
#include <MessageDialog.h>
#include <VrMenu.h>
#include <InfoView.h>
#include <QmlWebWindowClass.h>
#include <RegisteredMetaTypes.h>
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;
}
#include <QDir>
#include "MessageDialog.h"
#include "VrMenu.h"
#include "InfoView.h"
#include <QDesktopWidget>
class RateCounter {
std::vector<float> times;
@ -124,394 +74,142 @@ public:
};
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 {
class MenuConstants : public QObject{
Q_OBJECT
Q_ENUMS(Item)
public:
void loadFile(const QString& scriptPath) {
if (_isRunning) {
return;
}
qDebug() << "Loading script from " << scriptPath;
_fileNameString = scriptPath;
enum Item {
RenderLookAtTargets,
};
public:
MenuConstants(QObject* parent = nullptr) : QObject(parent) {
QFile file(scriptPath);
if (file.exists()) {
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;
};
ScriptEngine* loadScript(const QString& scriptFilename) {
ScriptEngine* scriptEngine = new ScriptEngine();
scriptEngine->loadFile(scriptFilename);
return scriptEngine;
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;
}
OffscreenGLCanvas* _chromiumShareContext { nullptr };
Q_GUI_EXPORT void qt_gl_set_global_share_context(QOpenGLContext *context);
const QString& getQmlDir() {
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
class QTestWindow : public QWindow {
class QTestWindow : public QWindow, private QOpenGLFunctions {
Q_OBJECT
QOpenGLContextWrapper* _context{ nullptr };
QOpenGLContext* _context{ nullptr };
QSize _size;
bool _altPressed{ false };
RateCounter fps;
QTimer _timer;
int testQmlTexture{ 0 };
ProgramPtr _program;
ShapeWrapperPtr _plane;
QScriptEngine* _scriptEngine { nullptr };
public:
QObject* rootMenu;
QTestWindow() {
_scriptEngine = new ScriptEngine();
_timer.setInterval(1);
QObject::connect(&_timer, &QTimer::timeout, this, &QTestWindow::draw);
connect(&_timer, &QTimer::timeout, [=] {
draw();
});
_chromiumShareContext = new OffscreenGLCanvas();
_chromiumShareContext->create();
_chromiumShareContext->makeCurrent();
qt_gl_set_global_share_context(_chromiumShareContext->getContext());
DependencyManager::set<OffscreenUi>();
setSurfaceType(QSurface::OpenGLSurface);
{
setSurfaceType(QSurface::OpenGLSurface);
QSurfaceFormat format = getDefaultOpenGLSurfaceFormat();
setFormat(format);
_context = new QOpenGLContextWrapper();
_context->setFormat(format);
_context->setShareContext(_chromiumShareContext->getContext());
}
QSurfaceFormat format;
format.setDepthBufferSize(16);
format.setStencilBufferSize(8);
format.setVersion(4, 1);
format.setProfile(QSurfaceFormat::OpenGLContextProfile::CompatibilityProfile);
format.setOption(QSurfaceFormat::DebugContext);
setFormat(format);
_context = new QOpenGLContext;
_context->setFormat(format);
if (!_context->create()) {
qFatal("Could not create OpenGL context");
}
show();
makeCurrent();
initializeOpenGLFunctions();
{
QOpenGLDebugLogger* logger = new QOpenGLDebugLogger(this);
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);
}
qDebug() << (const char*)this->glGetString(GL_VERSION);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glClearColor(0.2f, 0.2f, 0.2f, 1);
glDisable(GL_DEPTH_TEST);
MessageDialog::registerType();
VrMenu::registerType();
InfoView::registerType();
auto offscreenUi = DependencyManager::get<OffscreenUi>();
offscreenUi->create(_context);
connect(offscreenUi.data(), &OffscreenUi::textureUpdated, this, [this, offscreenUi](int textureId) {
testQmlTexture = textureId;
});
makeCurrent();
glewExperimental = true;
glewInit();
glGetError();
using namespace oglplus;
Context::Enable(Capability::Blend);
Context::BlendFunc(BlendFunction::SrcAlpha, BlendFunction::OneMinusSrcAlpha);
Context::Disable(Capability::DepthTest);
Context::Disable(Capability::CullFace);
Context::ClearColor(0.2f, 0.2f, 0.2f, 1);
MessageDialog::registerType();
InfoView::registerType();
auto offscreenUi = DependencyManager::set<OffscreenUi>();
{
offscreenUi->create(_context->getContext());
offscreenUi->setProxyWindow(this);
connect(offscreenUi.data(), &OffscreenUi::textureUpdated, this, [this, offscreenUi](int textureId) {
testQmlTexture = textureId;
});
makeCurrent();
}
auto primaryScreen = QGuiApplication::primaryScreen();
auto targetScreen = primaryScreen;
auto screens = QGuiApplication::screens();
if (screens.size() > 1) {
for (auto screen : screens) {
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));
offscreenUi->setProxyWindow(this);
QDesktopWidget* desktop = QApplication::desktop();
QRect rect = desktop->availableGeometry(desktop->screenCount() - 1);
int height = rect.height();
//rect.setHeight(height / 2);
rect.setY(rect.y() + height / 2);
setGeometry(rect);
// setFramePosition(QPoint(-1000, 0));
// resize(QSize(800, 600));
#ifdef QML_CONTROL_GALLERY
offscreenUi->setBaseUrl(QUrl::fromLocalFile(getTestQmlDir()));
offscreenUi->load(QUrl("main.qml"));
#else
offscreenUi->setBaseUrl(QUrl::fromLocalFile(getInterfaceQmlDir()));
offscreenUi->setBaseUrl(QUrl::fromLocalFile(getQmlDir()));
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
installEventFilter(offscreenUi.data());
offscreenUi->resume();
@ -529,35 +227,16 @@ private:
}
makeCurrent();
auto error = glGetError();
if (error != GL_NO_ERROR) {
qDebug() << "GL error in entering draw " << error;
}
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glViewport(0, 0, _size.width() * devicePixelRatio(), _size.height() * devicePixelRatio());
using namespace oglplus;
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);
}
renderQml();
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);
glFinish();
fps.increment();
if (fps.elapsed() >= 10.0f) {
if (fps.elapsed() >= 2.0f) {
qDebug() << "FPS: " << fps.rate();
fps.reset();
}
@ -567,6 +246,8 @@ private:
_context->makeCurrent(this);
}
void renderQml();
void resizeWindow(const QSize & size) {
_size = size;
DependencyManager::get<OffscreenUi>()->resize(_size);
@ -588,13 +269,11 @@ protected:
offscreenUi->load("Browser.qml");
}
break;
case Qt::Key_J:
case Qt::Key_L:
if (event->modifiers() & Qt::CTRL) {
loadScript(getExamplesDir() + "tests/qmlWebTest.js");
InfoView::show(getResourcesDir() + "html/interface-welcome.html", true);
}
break;
case Qt::Key_K:
if (event->modifiers() & Qt::CTRL) {
OffscreenUi::question("Message title", "Message contents", [](QMessageBox::Button b){
@ -602,9 +281,22 @@ protected:
});
}
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);
}
QQmlContext* menuContext{ nullptr };
void keyReleaseEvent(QKeyEvent *event) override {
if (_altPressed && Qt::Key_Alt == event->key()) {
VrMenu::toggle();
}
}
void moveEvent(QMoveEvent* event) override {
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(
hifi.offscreen.focus.debug=false
qt.quick.mouse.debug=false
)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) {
QGuiApplication app(argc, argv);
qInstallMessageHandler(messageHandler);
QApplication app(argc, argv);
QLoggingCategory::setFilterRules(LOG_FILTER_RULES);
QTestWindow window;
app.exec();