mirror of
https://github.com/HifiExperiments/overte.git
synced 2025-08-09 20:32:21 +02:00
Support web content inside QML
This commit is contained in:
parent
0c1ae5bb61
commit
fa5bab08b1
24 changed files with 1878 additions and 327 deletions
|
@ -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} $<$<OR:$<CONFIG:Release>,$<CONFIG:MinSizeRel>,$<CONFIG:RelWithDebInfo>>:--release> $<TARGET_FILE:${TARGET_NAME}>"
|
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}>"
|
||||||
)
|
)
|
||||||
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)
|
||||||
|
|
59
examples/html/eventBridgeLoader.js
Normal file
59
examples/html/eventBridgeLoader.js
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
|
||||||
|
//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); }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
31
examples/html/qmlWebTest.html
Normal file
31
examples/html/qmlWebTest.html
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
<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>
|
32
examples/tests/qmlWebTest.js
Normal file
32
examples/tests/qmlWebTest.js
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
print("Launching web window");
|
||||||
|
|
||||||
|
webWindow = new QmlWebWindow('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)
|
|
@ -45,7 +45,9 @@ else ()
|
||||||
list(REMOVE_ITEM INTERFACE_SRCS ${SPEECHRECOGNIZER_CPP})
|
list(REMOVE_ITEM INTERFACE_SRCS ${SPEECHRECOGNIZER_CPP})
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
find_package(Qt5 COMPONENTS Gui Multimedia Network OpenGL Qml Quick Script Svg WebKitWidgets WebSockets)
|
find_package(Qt5 COMPONENTS
|
||||||
|
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)
|
||||||
|
@ -175,9 +177,17 @@ 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::Script Qt5::Svg Qt5::WebKitWidgets
|
Qt5::Gui Qt5::Network Qt5::Multimedia Qt5::OpenGL
|
||||||
|
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)
|
||||||
|
|
||||||
|
@ -209,5 +219,9 @@ 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()
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import QtQuick 2.3
|
import QtQuick 2.3
|
||||||
import QtQuick.Controls 1.2
|
import QtQuick.Controls 1.2
|
||||||
import QtWebKit 3.0
|
import QtWebEngine 1.1
|
||||||
import "controls"
|
import "controls"
|
||||||
import "styles"
|
import "styles"
|
||||||
|
|
||||||
|
@ -39,9 +39,10 @@ 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: scrollView.top
|
anchors.bottom: webview.top
|
||||||
color: "white"
|
color: "white"
|
||||||
}
|
}
|
||||||
|
|
||||||
Row {
|
Row {
|
||||||
id: buttons
|
id: buttons
|
||||||
spacing: 4
|
spacing: 4
|
||||||
|
@ -112,26 +113,22 @@ VrDialog {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ScrollView {
|
WebEngineView {
|
||||||
id: scrollView
|
id: webview
|
||||||
|
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 {
|
onLoadingChanged: {
|
||||||
id: webview
|
if (loadRequest.status == WebEngineView.LoadSucceededStatus) {
|
||||||
url: "http://highfidelity.com"
|
addressBar.text = loadRequest.url
|
||||||
anchors.fill: parent
|
|
||||||
onLoadingChanged: {
|
|
||||||
if (loadRequest.status == WebView.LoadSucceededStarted) {
|
|
||||||
addressBar.text = loadRequest.url
|
|
||||||
}
|
|
||||||
}
|
|
||||||
onIconChanged: {
|
|
||||||
barIcon.source = icon
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
onIconChanged: {
|
||||||
|
console.log("New icon: " + icon)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} // item
|
} // item
|
||||||
|
|
||||||
|
@ -146,5 +143,4 @@ VrDialog {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} // dialog
|
} // dialog
|
||||||
|
|
|
@ -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 QtWebKit 3.0
|
import QtWebEngine 1.1
|
||||||
import "controls"
|
import "controls"
|
||||||
|
|
||||||
VrDialog {
|
VrDialog {
|
||||||
|
@ -18,15 +18,11 @@ VrDialog {
|
||||||
anchors.margins: parent.margins
|
anchors.margins: parent.margins
|
||||||
anchors.topMargin: parent.topMargin
|
anchors.topMargin: parent.topMargin
|
||||||
|
|
||||||
ScrollView {
|
WebEngineView {
|
||||||
|
id: webview
|
||||||
|
objectName: "WebView"
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
WebView {
|
url: infoView.url
|
||||||
objectName: "WebView"
|
}
|
||||||
id: webview
|
|
||||||
url: infoView.url
|
|
||||||
anchors.fill: parent
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 QtWebKit 3.0
|
import QtWebEngine 1.1
|
||||||
import "controls"
|
import "controls"
|
||||||
|
|
||||||
VrDialog {
|
VrDialog {
|
||||||
|
@ -24,27 +24,22 @@ VrDialog {
|
||||||
anchors.margins: parent.margins
|
anchors.margins: parent.margins
|
||||||
anchors.topMargin: parent.topMargin
|
anchors.topMargin: parent.topMargin
|
||||||
|
|
||||||
|
WebEngineView {
|
||||||
ScrollView {
|
objectName: "WebView"
|
||||||
|
id: webview
|
||||||
|
url: "https://metaverse.highfidelity.com/marketplace"
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
WebView {
|
onNavigationRequested: {
|
||||||
objectName: "WebView"
|
console.log(request.url)
|
||||||
id: webview
|
if (!marketplaceDialog.navigationRequested(request.url)) {
|
||||||
url: "https://metaverse.highfidelity.com/marketplace"
|
console.log("Application absorbed the request")
|
||||||
anchors.fill: parent
|
request.action = WebView.IgnoreRequest;
|
||||||
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;
|
return;
|
||||||
}
|
}
|
||||||
|
console.log("Application passed on the request")
|
||||||
|
request.action = WebView.AcceptRequest;
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
45
interface/resources/qml/QmlWebWindow.qml
Normal file
45
interface/resources/qml/QmlWebWindow.qml
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
|
||||||
|
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"
|
||||||
|
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
} // item
|
||||||
|
} // dialog
|
|
@ -4,116 +4,7 @@ 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 }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import QtQuick 2.3
|
import QtQuick 2.3
|
||||||
import QtQuick.Controls 1.2
|
import QtQuick.Controls 1.2
|
||||||
import QtWebKit 3.0
|
import QtWebEngine 1.1
|
||||||
|
|
||||||
WebView {
|
WebEngineView {
|
||||||
id: root
|
id: root
|
||||||
objectName: "webview"
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
objectName: "webview"
|
||||||
url: "about:blank"
|
url: "about:blank"
|
||||||
}
|
}
|
||||||
|
|
413
interface/resources/qml/qwebchannel.js
Normal file
413
interface/resources/qml/qwebchannel.js
Normal file
|
@ -0,0 +1,413 @@
|
||||||
|
/****************************************************************************
|
||||||
|
**
|
||||||
|
** Copyright (C) 2015 The Qt Company Ltd.
|
||||||
|
** Copyright (C) 2014 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Milian Wolff <milian.wolff@kdab.com>
|
||||||
|
** Contact: http://www.qt.io/licensing/
|
||||||
|
**
|
||||||
|
** This file is part of the QtWebChannel module of the Qt Toolkit.
|
||||||
|
**
|
||||||
|
** $QT_BEGIN_LICENSE:LGPL21$
|
||||||
|
** Commercial License Usage
|
||||||
|
** Licensees holding valid commercial Qt licenses may use this file in
|
||||||
|
** accordance with the commercial license agreement provided with the
|
||||||
|
** Software or, alternatively, in accordance with the terms contained in
|
||||||
|
** a written agreement between you and The Qt Company. For licensing terms
|
||||||
|
** and conditions see http://www.qt.io/terms-conditions. For further
|
||||||
|
** information use the contact form at http://www.qt.io/contact-us.
|
||||||
|
**
|
||||||
|
** GNU Lesser General Public License Usage
|
||||||
|
** Alternatively, this file may be used under the terms of the GNU Lesser
|
||||||
|
** General Public License version 2.1 or version 3 as published by the Free
|
||||||
|
** Software Foundation and appearing in the file LICENSE.LGPLv21 and
|
||||||
|
** LICENSE.LGPLv3 included in the packaging of this file. Please review the
|
||||||
|
** following information to ensure the GNU Lesser General Public License
|
||||||
|
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
|
||||||
|
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
|
||||||
|
**
|
||||||
|
** As a special exception, The Qt Company gives you certain additional
|
||||||
|
** rights. These rights are described in The Qt Company LGPL Exception
|
||||||
|
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
|
||||||
|
**
|
||||||
|
** $QT_END_LICENSE$
|
||||||
|
**
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var QWebChannelMessageTypes = {
|
||||||
|
signal: 1,
|
||||||
|
propertyUpdate: 2,
|
||||||
|
init: 3,
|
||||||
|
idle: 4,
|
||||||
|
debug: 5,
|
||||||
|
invokeMethod: 6,
|
||||||
|
connectToSignal: 7,
|
||||||
|
disconnectFromSignal: 8,
|
||||||
|
setProperty: 9,
|
||||||
|
response: 10,
|
||||||
|
};
|
||||||
|
|
||||||
|
var QWebChannel = function(transport, initCallback)
|
||||||
|
{
|
||||||
|
if (typeof transport !== "object" || typeof transport.send !== "function") {
|
||||||
|
console.error("The QWebChannel expects a transport object with a send function and onmessage callback property." +
|
||||||
|
" Given is: transport: " + typeof(transport) + ", transport.send: " + typeof(transport.send));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var channel = this;
|
||||||
|
this.transport = transport;
|
||||||
|
|
||||||
|
this.send = function(data)
|
||||||
|
{
|
||||||
|
if (typeof(data) !== "string") {
|
||||||
|
data = JSON.stringify(data);
|
||||||
|
}
|
||||||
|
channel.transport.send(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.transport.onmessage = function(message)
|
||||||
|
{
|
||||||
|
var data = message.data;
|
||||||
|
if (typeof data === "string") {
|
||||||
|
data = JSON.parse(data);
|
||||||
|
}
|
||||||
|
switch (data.type) {
|
||||||
|
case QWebChannelMessageTypes.signal:
|
||||||
|
channel.handleSignal(data);
|
||||||
|
break;
|
||||||
|
case QWebChannelMessageTypes.response:
|
||||||
|
channel.handleResponse(data);
|
||||||
|
break;
|
||||||
|
case QWebChannelMessageTypes.propertyUpdate:
|
||||||
|
channel.handlePropertyUpdate(data);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
console.error("invalid message received:", message.data);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.execCallbacks = {};
|
||||||
|
this.execId = 0;
|
||||||
|
this.exec = function(data, callback)
|
||||||
|
{
|
||||||
|
if (!callback) {
|
||||||
|
// if no callback is given, send directly
|
||||||
|
channel.send(data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (channel.execId === Number.MAX_VALUE) {
|
||||||
|
// wrap
|
||||||
|
channel.execId = Number.MIN_VALUE;
|
||||||
|
}
|
||||||
|
if (data.hasOwnProperty("id")) {
|
||||||
|
console.error("Cannot exec message with property id: " + JSON.stringify(data));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
data.id = channel.execId++;
|
||||||
|
channel.execCallbacks[data.id] = callback;
|
||||||
|
channel.send(data);
|
||||||
|
};
|
||||||
|
|
||||||
|
this.objects = {};
|
||||||
|
|
||||||
|
this.handleSignal = function(message)
|
||||||
|
{
|
||||||
|
var object = channel.objects[message.object];
|
||||||
|
if (object) {
|
||||||
|
object.signalEmitted(message.signal, message.args);
|
||||||
|
} else {
|
||||||
|
console.warn("Unhandled signal: " + message.object + "::" + message.signal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.handleResponse = function(message)
|
||||||
|
{
|
||||||
|
if (!message.hasOwnProperty("id")) {
|
||||||
|
console.error("Invalid response message received: ", JSON.stringify(message));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
channel.execCallbacks[message.id](message.data);
|
||||||
|
delete channel.execCallbacks[message.id];
|
||||||
|
}
|
||||||
|
|
||||||
|
this.handlePropertyUpdate = function(message)
|
||||||
|
{
|
||||||
|
for (var i in message.data) {
|
||||||
|
var data = message.data[i];
|
||||||
|
var object = channel.objects[data.object];
|
||||||
|
if (object) {
|
||||||
|
object.propertyUpdate(data.signals, data.properties);
|
||||||
|
} else {
|
||||||
|
console.warn("Unhandled property update: " + data.object + "::" + data.signal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
channel.exec({type: QWebChannelMessageTypes.idle});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.debug = function(message)
|
||||||
|
{
|
||||||
|
channel.send({type: QWebChannelMessageTypes.debug, data: message});
|
||||||
|
};
|
||||||
|
|
||||||
|
channel.exec({type: QWebChannelMessageTypes.init}, function(data) {
|
||||||
|
for (var objectName in data) {
|
||||||
|
var object = new QObject(objectName, data[objectName], channel);
|
||||||
|
}
|
||||||
|
// now unwrap properties, which might reference other registered objects
|
||||||
|
for (var objectName in channel.objects) {
|
||||||
|
channel.objects[objectName].unwrapProperties();
|
||||||
|
}
|
||||||
|
if (initCallback) {
|
||||||
|
initCallback(channel);
|
||||||
|
}
|
||||||
|
channel.exec({type: QWebChannelMessageTypes.idle});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
function QObject(name, data, webChannel)
|
||||||
|
{
|
||||||
|
this.__id__ = name;
|
||||||
|
webChannel.objects[name] = this;
|
||||||
|
|
||||||
|
// List of callbacks that get invoked upon signal emission
|
||||||
|
this.__objectSignals__ = {};
|
||||||
|
|
||||||
|
// Cache of all properties, updated when a notify signal is emitted
|
||||||
|
this.__propertyCache__ = {};
|
||||||
|
|
||||||
|
var object = this;
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
this.unwrapQObject = function(response)
|
||||||
|
{
|
||||||
|
if (response instanceof Array) {
|
||||||
|
// support list of objects
|
||||||
|
var ret = new Array(response.length);
|
||||||
|
for (var i = 0; i < response.length; ++i) {
|
||||||
|
ret[i] = object.unwrapQObject(response[i]);
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
if (!response
|
||||||
|
|| !response["__QObject*__"]
|
||||||
|
|| response.id === undefined) {
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
var objectId = response.id;
|
||||||
|
if (webChannel.objects[objectId])
|
||||||
|
return webChannel.objects[objectId];
|
||||||
|
|
||||||
|
if (!response.data) {
|
||||||
|
console.error("Cannot unwrap unknown QObject " + objectId + " without data.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var qObject = new QObject( objectId, response.data, webChannel );
|
||||||
|
qObject.destroyed.connect(function() {
|
||||||
|
if (webChannel.objects[objectId] === qObject) {
|
||||||
|
delete webChannel.objects[objectId];
|
||||||
|
// reset the now deleted QObject to an empty {} object
|
||||||
|
// just assigning {} though would not have the desired effect, but the
|
||||||
|
// below also ensures all external references will see the empty map
|
||||||
|
// NOTE: this detour is necessary to workaround QTBUG-40021
|
||||||
|
var propertyNames = [];
|
||||||
|
for (var propertyName in qObject) {
|
||||||
|
propertyNames.push(propertyName);
|
||||||
|
}
|
||||||
|
for (var idx in propertyNames) {
|
||||||
|
delete qObject[propertyNames[idx]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// here we are already initialized, and thus must directly unwrap the properties
|
||||||
|
qObject.unwrapProperties();
|
||||||
|
return qObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.unwrapProperties = function()
|
||||||
|
{
|
||||||
|
for (var propertyIdx in object.__propertyCache__) {
|
||||||
|
object.__propertyCache__[propertyIdx] = object.unwrapQObject(object.__propertyCache__[propertyIdx]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function addSignal(signalData, isPropertyNotifySignal)
|
||||||
|
{
|
||||||
|
var signalName = signalData[0];
|
||||||
|
var signalIndex = signalData[1];
|
||||||
|
object[signalName] = {
|
||||||
|
connect: function(callback) {
|
||||||
|
if (typeof(callback) !== "function") {
|
||||||
|
console.error("Bad callback given to connect to signal " + signalName);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
object.__objectSignals__[signalIndex] = object.__objectSignals__[signalIndex] || [];
|
||||||
|
object.__objectSignals__[signalIndex].push(callback);
|
||||||
|
|
||||||
|
if (!isPropertyNotifySignal && signalName !== "destroyed") {
|
||||||
|
// only required for "pure" signals, handled separately for properties in propertyUpdate
|
||||||
|
// also note that we always get notified about the destroyed signal
|
||||||
|
webChannel.exec({
|
||||||
|
type: QWebChannelMessageTypes.connectToSignal,
|
||||||
|
object: object.__id__,
|
||||||
|
signal: signalIndex
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
disconnect: function(callback) {
|
||||||
|
if (typeof(callback) !== "function") {
|
||||||
|
console.error("Bad callback given to disconnect from signal " + signalName);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
object.__objectSignals__[signalIndex] = object.__objectSignals__[signalIndex] || [];
|
||||||
|
var idx = object.__objectSignals__[signalIndex].indexOf(callback);
|
||||||
|
if (idx === -1) {
|
||||||
|
console.error("Cannot find connection of signal " + signalName + " to " + callback.name);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
object.__objectSignals__[signalIndex].splice(idx, 1);
|
||||||
|
if (!isPropertyNotifySignal && object.__objectSignals__[signalIndex].length === 0) {
|
||||||
|
// only required for "pure" signals, handled separately for properties in propertyUpdate
|
||||||
|
webChannel.exec({
|
||||||
|
type: QWebChannelMessageTypes.disconnectFromSignal,
|
||||||
|
object: object.__id__,
|
||||||
|
signal: signalIndex
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invokes all callbacks for the given signalname. Also works for property notify callbacks.
|
||||||
|
*/
|
||||||
|
function invokeSignalCallbacks(signalName, signalArgs)
|
||||||
|
{
|
||||||
|
var connections = object.__objectSignals__[signalName];
|
||||||
|
if (connections) {
|
||||||
|
connections.forEach(function(callback) {
|
||||||
|
callback.apply(callback, signalArgs);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.propertyUpdate = function(signals, propertyMap)
|
||||||
|
{
|
||||||
|
// update property cache
|
||||||
|
for (var propertyIndex in propertyMap) {
|
||||||
|
var propertyValue = propertyMap[propertyIndex];
|
||||||
|
object.__propertyCache__[propertyIndex] = propertyValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var signalName in signals) {
|
||||||
|
// Invoke all callbacks, as signalEmitted() does not. This ensures the
|
||||||
|
// property cache is updated before the callbacks are invoked.
|
||||||
|
invokeSignalCallbacks(signalName, signals[signalName]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.signalEmitted = function(signalName, signalArgs)
|
||||||
|
{
|
||||||
|
invokeSignalCallbacks(signalName, signalArgs);
|
||||||
|
}
|
||||||
|
|
||||||
|
function addMethod(methodData)
|
||||||
|
{
|
||||||
|
var methodName = methodData[0];
|
||||||
|
var methodIdx = methodData[1];
|
||||||
|
object[methodName] = function() {
|
||||||
|
var args = [];
|
||||||
|
var callback;
|
||||||
|
for (var i = 0; i < arguments.length; ++i) {
|
||||||
|
if (typeof arguments[i] === "function")
|
||||||
|
callback = arguments[i];
|
||||||
|
else
|
||||||
|
args.push(arguments[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
webChannel.exec({
|
||||||
|
"type": QWebChannelMessageTypes.invokeMethod,
|
||||||
|
"object": object.__id__,
|
||||||
|
"method": methodIdx,
|
||||||
|
"args": args
|
||||||
|
}, function(response) {
|
||||||
|
if (response !== undefined) {
|
||||||
|
var result = object.unwrapQObject(response);
|
||||||
|
if (callback) {
|
||||||
|
(callback)(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function bindGetterSetter(propertyInfo)
|
||||||
|
{
|
||||||
|
var propertyIndex = propertyInfo[0];
|
||||||
|
var propertyName = propertyInfo[1];
|
||||||
|
var notifySignalData = propertyInfo[2];
|
||||||
|
// initialize property cache with current value
|
||||||
|
// NOTE: if this is an object, it is not directly unwrapped as it might
|
||||||
|
// reference other QObject that we do not know yet
|
||||||
|
object.__propertyCache__[propertyIndex] = propertyInfo[3];
|
||||||
|
|
||||||
|
if (notifySignalData) {
|
||||||
|
if (notifySignalData[0] === 1) {
|
||||||
|
// signal name is optimized away, reconstruct the actual name
|
||||||
|
notifySignalData[0] = propertyName + "Changed";
|
||||||
|
}
|
||||||
|
addSignal(notifySignalData, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.defineProperty(object, propertyName, {
|
||||||
|
configurable: true,
|
||||||
|
get: function () {
|
||||||
|
var propertyValue = object.__propertyCache__[propertyIndex];
|
||||||
|
if (propertyValue === undefined) {
|
||||||
|
// This shouldn't happen
|
||||||
|
console.warn("Undefined value in property cache for property \"" + propertyName + "\" in object " + object.__id__);
|
||||||
|
}
|
||||||
|
|
||||||
|
return propertyValue;
|
||||||
|
},
|
||||||
|
set: function(value) {
|
||||||
|
if (value === undefined) {
|
||||||
|
console.warn("Property setter for " + propertyName + " called with undefined value!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
object.__propertyCache__[propertyIndex] = value;
|
||||||
|
webChannel.exec({
|
||||||
|
"type": QWebChannelMessageTypes.setProperty,
|
||||||
|
"object": object.__id__,
|
||||||
|
"property": propertyIndex,
|
||||||
|
"value": value
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
data.methods.forEach(addMethod);
|
||||||
|
|
||||||
|
data.properties.forEach(bindGetterSetter);
|
||||||
|
|
||||||
|
data.signals.forEach(function(signal) { addSignal(signal, false); });
|
||||||
|
|
||||||
|
for (var name in data.enums) {
|
||||||
|
object[name] = data.enums[name];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//required for use with nodejs
|
||||||
|
if (typeof module === 'object') {
|
||||||
|
module.exports = {
|
||||||
|
QWebChannel: QWebChannel
|
||||||
|
};
|
||||||
|
}
|
|
@ -101,6 +101,7 @@
|
||||||
#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"
|
||||||
|
@ -362,6 +363,17 @@ 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)),
|
||||||
|
@ -623,6 +635,11 @@ 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();
|
||||||
|
@ -4138,6 +4155,7 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri
|
||||||
scriptEngine->registerGetterSetter("location", LocationScriptingInterface::locationGetter,
|
scriptEngine->registerGetterSetter("location", LocationScriptingInterface::locationGetter,
|
||||||
LocationScriptingInterface::locationSetter);
|
LocationScriptingInterface::locationSetter);
|
||||||
|
|
||||||
|
scriptEngine->registerFunction("QmlWebWindow", QmlWebWindowClass::constructor);
|
||||||
scriptEngine->registerFunction("WebWindow", WebWindowClass::constructor, 1);
|
scriptEngine->registerFunction("WebWindow", WebWindowClass::constructor, 1);
|
||||||
|
|
||||||
scriptEngine->registerGlobalObject("Menu", MenuScriptingInterface::getInstance());
|
scriptEngine->registerGlobalObject("Menu", MenuScriptingInterface::getInstance());
|
||||||
|
|
|
@ -320,10 +320,13 @@ 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) {
|
||||||
QSize currentSize = _renderer->_quickWindow->geometry().size();
|
return;
|
||||||
if (newSize == currentSize) {
|
}
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
QSize currentSize = _renderer->_quickWindow->geometry().size();
|
||||||
|
if (newSize == currentSize) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_qmlEngine->rootContext()->setContextProperty("surfaceSize", newSize);
|
_qmlEngine->rootContext()->setContextProperty("surfaceSize", newSize);
|
||||||
|
@ -437,7 +440,9 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
QObject* load(const QUrl& qmlSource, std::function<void(QQmlContext*, QObject*)> f = [](QQmlContext*, QObject*) {});
|
Q_INVOKABLE 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*) {}) {
|
Q_INVOKABLE QObject* load(const QString& qmlSourceFile, std::function<void(QQmlContext*, QObject*)> f = [](QQmlContext*, QObject*) {}) {
|
||||||
return load(QUrl(qmlSourceFile), f);
|
return load(QUrl(qmlSourceFile), f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
set(TARGET_NAME ui)
|
set(TARGET_NAME ui)
|
||||||
setup_hifi_library(OpenGL Network Qml Quick Script XmlPatterns)
|
setup_hifi_library(OpenGL Network Qml Quick Script WebChannel WebSockets XmlPatterns)
|
||||||
link_hifi_libraries(shared networking gl)
|
link_hifi_libraries(shared networking gl)
|
||||||
|
|
344
libraries/ui/src/QmlWebWindowClass.cpp
Normal file
344
libraries/ui/src/QmlWebWindowClass.cpp
Normal file
|
@ -0,0 +1,344 @@
|
||||||
|
//
|
||||||
|
// 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 <QtWebChannel/QWebChannel>
|
||||||
|
|
||||||
|
#include <DependencyManager.h>
|
||||||
|
|
||||||
|
#include "impl/websocketclientwrapper.h"
|
||||||
|
#include "impl/websockettransport.h"
|
||||||
|
#include "OffscreenUi.h"
|
||||||
|
|
||||||
|
static QWebSocketServer * webChannelServer { nullptr };
|
||||||
|
static WebSocketClientWrapper * webChannelClientWrapper { nullptr };
|
||||||
|
static QWebChannel webChannel;
|
||||||
|
static std::once_flag webChannelSetup;
|
||||||
|
static const uint16_t WEB_CHANNEL_PORT = 51016;
|
||||||
|
static std::atomic<int> nextWindowId;
|
||||||
|
|
||||||
|
void initWebChannelServer() {
|
||||||
|
std::call_once(webChannelSetup, [] {
|
||||||
|
webChannelServer = new QWebSocketServer("EventBridge Server", QWebSocketServer::NonSecureMode);
|
||||||
|
webChannelClientWrapper = new WebSocketClientWrapper(webChannelServer);
|
||||||
|
if (!webChannelServer->listen(QHostAddress::LocalHost, WEB_CHANNEL_PORT)) {
|
||||||
|
qFatal("Failed to open web socket server.");
|
||||||
|
}
|
||||||
|
QObject::connect(webChannelClientWrapper, &WebSocketClientWrapper::clientConnected, &webChannel, &QWebChannel::connectTo);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
initWebChannelServer();
|
||||||
|
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)
|
||||||
|
: _isToolWindow(false), _windowId(++nextWindowId), _eventBridge(new QmlScriptEventBridge(this)), _qmlWindow(qmlWindow)
|
||||||
|
{
|
||||||
|
qDebug() << "Created window with ID " << _windowId;
|
||||||
|
Q_ASSERT(_qmlWindow);
|
||||||
|
Q_ASSERT(dynamic_cast<const QQuickItem*>(_qmlWindow));
|
||||||
|
}
|
||||||
|
|
||||||
|
void QmlWebWindowClass::setVisible(bool visible) {
|
||||||
|
if (QThread::currentThread() != thread()) {
|
||||||
|
QMetaObject::invokeMethod(this, "setVisible", Qt::AutoConnection, Q_ARG(bool, visible));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto qmlWindow = (QQuickItem*)_qmlWindow;
|
||||||
|
if (qmlWindow->isEnabled() != visible) {
|
||||||
|
qmlWindow->setEnabled(visible);
|
||||||
|
emit visibilityChanged(visible);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 ((QQuickItem*)_qmlWindow)->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(((QQuickItem*)_qmlWindow)->x(), ((QQuickItem*)_qmlWindow)->y());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void QmlWebWindowClass::setPosition(const glm::vec2& position) {
|
||||||
|
if (QThread::currentThread() != thread()) {
|
||||||
|
QMetaObject::invokeMethod(this, "setPosition", Qt::QueuedConnection, Q_ARG(glm::vec2, position));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
((QQuickItem*)_qmlWindow)->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(((QQuickItem*)_qmlWindow)->width(), ((QQuickItem*)_qmlWindow)->height());
|
||||||
|
}
|
||||||
|
|
||||||
|
void QmlWebWindowClass::setSize(const glm::vec2& size) {
|
||||||
|
if (QThread::currentThread() != thread()) {
|
||||||
|
QMetaObject::invokeMethod(this, "setSize", Qt::QueuedConnection, Q_ARG(glm::vec2, size));
|
||||||
|
}
|
||||||
|
|
||||||
|
((QQuickItem*)_qmlWindow)->setSize(QSizeF(size.x, size.y));
|
||||||
|
}
|
||||||
|
|
||||||
|
void QmlWebWindowClass::setSize(int width, int height) {
|
||||||
|
setSize(glm::vec2(width, height));
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char* const URL_PROPERTY = "source";
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char* const TITLE_PROPERTY = "title";
|
||||||
|
|
||||||
|
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() {
|
||||||
|
}
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
|
||||||
|
#include <QtCore/QThread>
|
||||||
|
|
||||||
|
#include <QtScript/QScriptEngine>
|
||||||
|
|
||||||
|
WebWindowClass::WebWindowClass(const QString& title, const QString& url, int width, int height, bool isToolWindow)
|
||||||
|
: QObject(NULL), _eventBridge(new ScriptEventBridge(this)), _isToolWindow(isToolWindow) {
|
||||||
|
/*
|
||||||
|
if (_isToolWindow) {
|
||||||
|
ToolWindow* toolWindow = qApp->getToolWindow();
|
||||||
|
|
||||||
|
auto dockWidget = new QDockWidget(title, toolWindow);
|
||||||
|
dockWidget->setFeatures(QDockWidget::DockWidgetMovable);
|
||||||
|
connect(dockWidget, &QDockWidget::visibilityChanged, this, &WebWindowClass::visibilityChanged);
|
||||||
|
|
||||||
|
_webView = new QWebView(dockWidget);
|
||||||
|
addEventBridgeToWindowObject();
|
||||||
|
|
||||||
|
dockWidget->setWidget(_webView);
|
||||||
|
|
||||||
|
auto titleWidget = new QWidget(dockWidget);
|
||||||
|
dockWidget->setTitleBarWidget(titleWidget);
|
||||||
|
|
||||||
|
toolWindow->addDockWidget(Qt::TopDockWidgetArea, dockWidget, Qt::Horizontal);
|
||||||
|
|
||||||
|
_windowWidget = dockWidget;
|
||||||
|
} else {
|
||||||
|
auto dialogWidget = new QDialog(qApp->getWindow(), Qt::Window);
|
||||||
|
dialogWidget->setWindowTitle(title);
|
||||||
|
dialogWidget->resize(width, height);
|
||||||
|
dialogWidget->installEventFilter(this);
|
||||||
|
connect(dialogWidget, &QDialog::finished, this, &WebWindowClass::hasClosed);
|
||||||
|
|
||||||
|
auto layout = new QVBoxLayout(dialogWidget);
|
||||||
|
layout->setContentsMargins(0, 0, 0, 0);
|
||||||
|
dialogWidget->setLayout(layout);
|
||||||
|
|
||||||
|
_webView = new QWebView(dialogWidget);
|
||||||
|
|
||||||
|
layout->addWidget(_webView);
|
||||||
|
|
||||||
|
addEventBridgeToWindowObject();
|
||||||
|
|
||||||
|
_windowWidget = dialogWidget;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto style = QStyleFactory::create("fusion");
|
||||||
|
if (style) {
|
||||||
|
_webView->setStyle(style);
|
||||||
|
}
|
||||||
|
|
||||||
|
_webView->setPage(new DataWebPage());
|
||||||
|
if (!url.startsWith("http") && !url.startsWith("file://")) {
|
||||||
|
_webView->setUrl(QUrl::fromLocalFile(url));
|
||||||
|
} else {
|
||||||
|
_webView->setUrl(url);
|
||||||
|
}
|
||||||
|
connect(this, &WebWindowClass::destroyed, _windowWidget, &QWidget::deleteLater);
|
||||||
|
connect(_webView->page()->mainFrame(), &QWebFrame::javaScriptWindowObjectCleared,
|
||||||
|
this, &WebWindowClass::addEventBridgeToWindowObject);
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebWindowClass::hasClosed() {
|
||||||
|
emit closed();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void WebWindowClass::setVisible(bool visible) {
|
||||||
|
}
|
||||||
|
|
||||||
|
QString WebWindowClass::getURL() const {
|
||||||
|
return QString();
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebWindowClass::setURL(const QString& url) {
|
||||||
|
if (QThread::currentThread() != thread()) {
|
||||||
|
QMetaObject::invokeMethod(this, "setURL", Qt::AutoConnection, Q_ARG(QString, url));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QSizeF WebWindowClass::getSize() const {
|
||||||
|
QSizeF size;
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebWindowClass::setSize(const QSizeF& size) {
|
||||||
|
setSize(size.width(), size.height());
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebWindowClass::setSize(int width, int height) {
|
||||||
|
if (QThread::currentThread() != thread()) {
|
||||||
|
QMetaObject::invokeMethod(this, "setSize", Qt::AutoConnection, Q_ARG(int, width), Q_ARG(int, height));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
glm::vec2 WebWindowClass::getPosition() const {
|
||||||
|
return glm::vec2();
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebWindowClass::setPosition(const glm::vec2& position) {
|
||||||
|
setPosition(position.x, position.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebWindowClass::setPosition(int x, int y) {
|
||||||
|
if (QThread::currentThread() != thread()) {
|
||||||
|
QMetaObject::invokeMethod(this, "setPosition", Qt::AutoConnection, Q_ARG(int, x), Q_ARG(int, y));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebWindowClass::raise() {
|
||||||
|
}
|
||||||
|
|
||||||
|
QScriptValue WebWindowClass::constructor(QScriptContext* context, QScriptEngine* engine) {
|
||||||
|
WebWindowClass* retVal { nullptr };
|
||||||
|
//QString file = context->argument(0).toString();
|
||||||
|
//QMetaObject::invokeMethod(DependencyManager::get<WindowScriptingInterface>().data(), "doCreateWebWindow", Qt::BlockingQueuedConnection,
|
||||||
|
// Q_RETURN_ARG(WebWindowClass*, retVal),
|
||||||
|
// Q_ARG(const QString&, file),
|
||||||
|
// Q_ARG(QString, context->argument(1).toString()),
|
||||||
|
// Q_ARG(int, context->argument(2).toInteger()),
|
||||||
|
// Q_ARG(int, context->argument(3).toInteger()),
|
||||||
|
// Q_ARG(bool, context->argument(4).toBool()));
|
||||||
|
|
||||||
|
//connect(engine, &QScriptEngine::destroyed, retVal, &WebWindowClass::deleteLater);
|
||||||
|
|
||||||
|
return engine->newQObject(retVal);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebWindowClass::setTitle(const QString& title) {
|
||||||
|
if (QThread::currentThread() != thread()) {
|
||||||
|
QMetaObject::invokeMethod(this, "setTitle", Qt::AutoConnection, Q_ARG(QString, title));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
92
libraries/ui/src/QmlWebWindowClass.h
Normal file
92
libraries/ui/src/QmlWebWindowClass.h
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
//
|
||||||
|
// 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>
|
||||||
|
|
||||||
|
class QScriptEngine;
|
||||||
|
class QScriptContext;
|
||||||
|
class QmlWebWindowClass;
|
||||||
|
|
||||||
|
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 };
|
||||||
|
};
|
||||||
|
|
||||||
|
// 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();
|
||||||
|
|
||||||
|
private:
|
||||||
|
QmlScriptEventBridge* _eventBridge;
|
||||||
|
bool _isToolWindow { false };
|
||||||
|
QObject* _qmlWindow;
|
||||||
|
const int _windowId;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
72
libraries/ui/src/impl/websocketclientwrapper.cpp
Normal file
72
libraries/ui/src/impl/websocketclientwrapper.cpp
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
/****************************************************************************
|
||||||
|
**
|
||||||
|
** Copyright (C) 2014 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Milian Wolff <milian.wolff@kdab.com>
|
||||||
|
** Contact: http://www.qt.io/licensing/
|
||||||
|
**
|
||||||
|
** This file is part of the QtWebChannel module of the Qt Toolkit.
|
||||||
|
**
|
||||||
|
** $QT_BEGIN_LICENSE:LGPL21$
|
||||||
|
** Commercial License Usage
|
||||||
|
** Licensees holding valid commercial Qt licenses may use this file in
|
||||||
|
** accordance with the commercial license agreement provided with the
|
||||||
|
** Software or, alternatively, in accordance with the terms contained in
|
||||||
|
** a written agreement between you and The Qt Company. For licensing terms
|
||||||
|
** and conditions see http://www.qt.io/terms-conditions. For further
|
||||||
|
** information use the contact form at http://www.qt.io/contact-us.
|
||||||
|
**
|
||||||
|
** GNU Lesser General Public License Usage
|
||||||
|
** Alternatively, this file may be used under the terms of the GNU Lesser
|
||||||
|
** General Public License version 2.1 or version 3 as published by the Free
|
||||||
|
** Software Foundation and appearing in the file LICENSE.LGPLv21 and
|
||||||
|
** LICENSE.LGPLv3 included in the packaging of this file. Please review the
|
||||||
|
** following information to ensure the GNU Lesser General Public License
|
||||||
|
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
|
||||||
|
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
|
||||||
|
**
|
||||||
|
** As a special exception, The Qt Company gives you certain additional
|
||||||
|
** rights. These rights are described in The Qt Company LGPL Exception
|
||||||
|
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
|
||||||
|
**
|
||||||
|
** $QT_END_LICENSE$
|
||||||
|
**
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
#include "websocketclientwrapper.h"
|
||||||
|
|
||||||
|
#include <QtWebSockets/QWebSocketServer>
|
||||||
|
|
||||||
|
#include "websockettransport.h"
|
||||||
|
|
||||||
|
/*!
|
||||||
|
\brief Wrapps connected QWebSockets clients in WebSocketTransport objects.
|
||||||
|
|
||||||
|
This code is all that is required to connect incoming WebSockets to the WebChannel. Any kind
|
||||||
|
of remote JavaScript client that supports WebSockets can thus receive messages and access the
|
||||||
|
published objects.
|
||||||
|
*/
|
||||||
|
|
||||||
|
QT_BEGIN_NAMESPACE
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Construct the client wrapper with the given parent.
|
||||||
|
|
||||||
|
All clients connecting to the QWebSocketServer will be automatically wrapped
|
||||||
|
in WebSocketTransport objects.
|
||||||
|
*/
|
||||||
|
WebSocketClientWrapper::WebSocketClientWrapper(QWebSocketServer *server, QObject *parent)
|
||||||
|
: QObject(parent)
|
||||||
|
, m_server(server)
|
||||||
|
{
|
||||||
|
connect(server, &QWebSocketServer::newConnection,
|
||||||
|
this, &WebSocketClientWrapper::handleNewConnection);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Wrap an incoming WebSocket connection in a WebSocketTransport object.
|
||||||
|
*/
|
||||||
|
void WebSocketClientWrapper::handleNewConnection()
|
||||||
|
{
|
||||||
|
emit clientConnected(new WebSocketTransport(m_server->nextPendingConnection()));
|
||||||
|
}
|
||||||
|
|
||||||
|
QT_END_NAMESPACE
|
63
libraries/ui/src/impl/websocketclientwrapper.h
Normal file
63
libraries/ui/src/impl/websocketclientwrapper.h
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
/****************************************************************************
|
||||||
|
**
|
||||||
|
** Copyright (C) 2014 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Milian Wolff <milian.wolff@kdab.com>
|
||||||
|
** Contact: http://www.qt.io/licensing/
|
||||||
|
**
|
||||||
|
** This file is part of the QtWebChannel module of the Qt Toolkit.
|
||||||
|
**
|
||||||
|
** $QT_BEGIN_LICENSE:LGPL21$
|
||||||
|
** Commercial License Usage
|
||||||
|
** Licensees holding valid commercial Qt licenses may use this file in
|
||||||
|
** accordance with the commercial license agreement provided with the
|
||||||
|
** Software or, alternatively, in accordance with the terms contained in
|
||||||
|
** a written agreement between you and The Qt Company. For licensing terms
|
||||||
|
** and conditions see http://www.qt.io/terms-conditions. For further
|
||||||
|
** information use the contact form at http://www.qt.io/contact-us.
|
||||||
|
**
|
||||||
|
** GNU Lesser General Public License Usage
|
||||||
|
** Alternatively, this file may be used under the terms of the GNU Lesser
|
||||||
|
** General Public License version 2.1 or version 3 as published by the Free
|
||||||
|
** Software Foundation and appearing in the file LICENSE.LGPLv21 and
|
||||||
|
** LICENSE.LGPLv3 included in the packaging of this file. Please review the
|
||||||
|
** following information to ensure the GNU Lesser General Public License
|
||||||
|
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
|
||||||
|
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
|
||||||
|
**
|
||||||
|
** As a special exception, The Qt Company gives you certain additional
|
||||||
|
** rights. These rights are described in The Qt Company LGPL Exception
|
||||||
|
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
|
||||||
|
**
|
||||||
|
** $QT_END_LICENSE$
|
||||||
|
**
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
#ifndef WEBSOCKETTRANSPORTSERVER_H
|
||||||
|
#define WEBSOCKETTRANSPORTSERVER_H
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
|
||||||
|
QT_BEGIN_NAMESPACE
|
||||||
|
|
||||||
|
class QWebSocketServer;
|
||||||
|
class WebSocketTransport;
|
||||||
|
|
||||||
|
class WebSocketClientWrapper : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
WebSocketClientWrapper(QWebSocketServer *server, QObject *parent = 0);
|
||||||
|
|
||||||
|
Q_SIGNALS:
|
||||||
|
void clientConnected(WebSocketTransport* client);
|
||||||
|
|
||||||
|
private Q_SLOTS:
|
||||||
|
void handleNewConnection();
|
||||||
|
|
||||||
|
private:
|
||||||
|
QWebSocketServer *m_server;
|
||||||
|
};
|
||||||
|
|
||||||
|
QT_END_NAMESPACE
|
||||||
|
|
||||||
|
#endif // WEBSOCKETTRANSPORTSERVER_H
|
100
libraries/ui/src/impl/websockettransport.cpp
Normal file
100
libraries/ui/src/impl/websockettransport.cpp
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
/****************************************************************************
|
||||||
|
**
|
||||||
|
** Copyright (C) 2014 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Milian Wolff <milian.wolff@kdab.com>
|
||||||
|
** Contact: http://www.qt.io/licensing/
|
||||||
|
**
|
||||||
|
** This file is part of the QtWebChannel module of the Qt Toolkit.
|
||||||
|
**
|
||||||
|
** $QT_BEGIN_LICENSE:LGPL21$
|
||||||
|
** Commercial License Usage
|
||||||
|
** Licensees holding valid commercial Qt licenses may use this file in
|
||||||
|
** accordance with the commercial license agreement provided with the
|
||||||
|
** Software or, alternatively, in accordance with the terms contained in
|
||||||
|
** a written agreement between you and The Qt Company. For licensing terms
|
||||||
|
** and conditions see http://www.qt.io/terms-conditions. For further
|
||||||
|
** information use the contact form at http://www.qt.io/contact-us.
|
||||||
|
**
|
||||||
|
** GNU Lesser General Public License Usage
|
||||||
|
** Alternatively, this file may be used under the terms of the GNU Lesser
|
||||||
|
** General Public License version 2.1 or version 3 as published by the Free
|
||||||
|
** Software Foundation and appearing in the file LICENSE.LGPLv21 and
|
||||||
|
** LICENSE.LGPLv3 included in the packaging of this file. Please review the
|
||||||
|
** following information to ensure the GNU Lesser General Public License
|
||||||
|
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
|
||||||
|
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
|
||||||
|
**
|
||||||
|
** As a special exception, The Qt Company gives you certain additional
|
||||||
|
** rights. These rights are described in The Qt Company LGPL Exception
|
||||||
|
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
|
||||||
|
**
|
||||||
|
** $QT_END_LICENSE$
|
||||||
|
**
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
#include "websockettransport.h"
|
||||||
|
|
||||||
|
#include <QJsonDocument>
|
||||||
|
#include <QJsonObject>
|
||||||
|
#include <QDebug>
|
||||||
|
|
||||||
|
#include <QtWebSockets/QWebSocket>
|
||||||
|
|
||||||
|
/*!
|
||||||
|
\brief QWebChannelAbstractSocket implementation that uses a QWebSocket internally.
|
||||||
|
|
||||||
|
The transport delegates all messages received over the QWebSocket over its
|
||||||
|
textMessageReceived signal. Analogously, all calls to sendTextMessage will
|
||||||
|
be send over the QWebSocket to the remote client.
|
||||||
|
*/
|
||||||
|
|
||||||
|
QT_BEGIN_NAMESPACE
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Construct the transport object and wrap the given socket.
|
||||||
|
|
||||||
|
The socket is also set as the parent of the transport object.
|
||||||
|
*/
|
||||||
|
WebSocketTransport::WebSocketTransport(QWebSocket *socket)
|
||||||
|
: QWebChannelAbstractTransport(socket)
|
||||||
|
, m_socket(socket)
|
||||||
|
{
|
||||||
|
connect(socket, &QWebSocket::textMessageReceived,
|
||||||
|
this, &WebSocketTransport::textMessageReceived);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Destroys the WebSocketTransport.
|
||||||
|
*/
|
||||||
|
WebSocketTransport::~WebSocketTransport()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Serialize the JSON message and send it as a text message via the WebSocket to the client.
|
||||||
|
*/
|
||||||
|
void WebSocketTransport::sendMessage(const QJsonObject &message)
|
||||||
|
{
|
||||||
|
QJsonDocument doc(message);
|
||||||
|
m_socket->sendTextMessage(QString::fromUtf8(doc.toJson(QJsonDocument::Compact)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Deserialize the stringified JSON messageData and emit messageReceived.
|
||||||
|
*/
|
||||||
|
void WebSocketTransport::textMessageReceived(const QString &messageData)
|
||||||
|
{
|
||||||
|
QJsonParseError error;
|
||||||
|
QJsonDocument message = QJsonDocument::fromJson(messageData.toUtf8(), &error);
|
||||||
|
if (error.error) {
|
||||||
|
qWarning() << "Failed to parse text message as JSON object:" << messageData
|
||||||
|
<< "Error is:" << error.errorString();
|
||||||
|
return;
|
||||||
|
} else if (!message.isObject()) {
|
||||||
|
qWarning() << "Received JSON message that is not an object: " << messageData;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
emit messageReceived(message.object(), this);
|
||||||
|
}
|
||||||
|
|
||||||
|
QT_END_NAMESPACE
|
60
libraries/ui/src/impl/websockettransport.h
Normal file
60
libraries/ui/src/impl/websockettransport.h
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
/****************************************************************************
|
||||||
|
**
|
||||||
|
** Copyright (C) 2014 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Milian Wolff <milian.wolff@kdab.com>
|
||||||
|
** Contact: http://www.qt.io/licensing/
|
||||||
|
**
|
||||||
|
** This file is part of the QtWebChannel module of the Qt Toolkit.
|
||||||
|
**
|
||||||
|
** $QT_BEGIN_LICENSE:LGPL21$
|
||||||
|
** Commercial License Usage
|
||||||
|
** Licensees holding valid commercial Qt licenses may use this file in
|
||||||
|
** accordance with the commercial license agreement provided with the
|
||||||
|
** Software or, alternatively, in accordance with the terms contained in
|
||||||
|
** a written agreement between you and The Qt Company. For licensing terms
|
||||||
|
** and conditions see http://www.qt.io/terms-conditions. For further
|
||||||
|
** information use the contact form at http://www.qt.io/contact-us.
|
||||||
|
**
|
||||||
|
** GNU Lesser General Public License Usage
|
||||||
|
** Alternatively, this file may be used under the terms of the GNU Lesser
|
||||||
|
** General Public License version 2.1 or version 3 as published by the Free
|
||||||
|
** Software Foundation and appearing in the file LICENSE.LGPLv21 and
|
||||||
|
** LICENSE.LGPLv3 included in the packaging of this file. Please review the
|
||||||
|
** following information to ensure the GNU Lesser General Public License
|
||||||
|
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
|
||||||
|
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
|
||||||
|
**
|
||||||
|
** As a special exception, The Qt Company gives you certain additional
|
||||||
|
** rights. These rights are described in The Qt Company LGPL Exception
|
||||||
|
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
|
||||||
|
**
|
||||||
|
** $QT_END_LICENSE$
|
||||||
|
**
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
#ifndef WEBSOCKETTRANSPORT_H
|
||||||
|
#define WEBSOCKETTRANSPORT_H
|
||||||
|
|
||||||
|
#include <QtWebChannel/QWebChannelAbstractTransport>
|
||||||
|
|
||||||
|
QT_BEGIN_NAMESPACE
|
||||||
|
|
||||||
|
class QWebSocket;
|
||||||
|
class WebSocketTransport : public QWebChannelAbstractTransport
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit WebSocketTransport(QWebSocket *socket);
|
||||||
|
virtual ~WebSocketTransport();
|
||||||
|
|
||||||
|
void sendMessage(const QJsonObject &message) Q_DECL_OVERRIDE;
|
||||||
|
|
||||||
|
private Q_SLOTS:
|
||||||
|
void textMessageReceived(const QString &message);
|
||||||
|
|
||||||
|
private:
|
||||||
|
QWebSocket *m_socket;
|
||||||
|
};
|
||||||
|
|
||||||
|
QT_END_NAMESPACE
|
||||||
|
|
||||||
|
#endif // WEBSOCKETTRANSPORT_H
|
|
@ -2,15 +2,32 @@
|
||||||
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(Widgets OpenGL Network Qml Quick Script)
|
setup_hifi_project(Network OpenGL Qml Quick Script WebChannel WebEngine WebSockets)
|
||||||
|
|
||||||
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()
|
||||||
|
|
|
@ -1,41 +1,92 @@
|
||||||
//
|
//
|
||||||
// main.cpp
|
// Created by Bradley Austin Davis on 2015-04-22
|
||||||
// tests/render-utils/src
|
// Copyright 2013-2015 High Fidelity, Inc.
|
||||||
//
|
|
||||||
// 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 "OffscreenUi.h"
|
#include <gl/Config.h>
|
||||||
#include <QWindow>
|
#include <gl/OglplusHelpers.h>
|
||||||
#include <QFile>
|
#include <gl/GLHelpers.h>
|
||||||
#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 <PathUtils.h>
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <glm/glm.hpp>
|
|
||||||
|
#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 <QtGui/QOpenGLContext>
|
||||||
|
#include <QtGui/QOpenGLDebugLogger>
|
||||||
|
|
||||||
|
#include <QtScript/QScriptEngine>
|
||||||
|
|
||||||
|
#include <QtQml/QQmlContext>
|
||||||
|
#include <QtQml/QQmlApplicationEngine>
|
||||||
|
|
||||||
|
#include <GLMHelpers.h>
|
||||||
|
#include <gl/OffscreenGLCanvas.h>
|
||||||
|
#include <OffscreenUi.h>
|
||||||
#include <PathUtils.h>
|
#include <PathUtils.h>
|
||||||
#include <QDir>
|
#include <PathUtils.h>
|
||||||
#include "MessageDialog.h"
|
#include <MessageDialog.h>
|
||||||
#include "VrMenu.h"
|
#include <VrMenu.h>
|
||||||
#include "InfoView.h"
|
#include <InfoView.h>
|
||||||
#include <QDesktopWidget>
|
#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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class RateCounter {
|
class RateCounter {
|
||||||
std::vector<float> times;
|
std::vector<float> times;
|
||||||
|
@ -74,54 +125,302 @@ public:
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
class MenuConstants : public QObject{
|
|
||||||
Q_OBJECT
|
|
||||||
Q_ENUMS(Item)
|
|
||||||
|
|
||||||
public:
|
|
||||||
enum Item {
|
|
||||||
RenderLookAtTargets,
|
|
||||||
};
|
|
||||||
|
|
||||||
public:
|
extern QOpenGLContext* qt_gl_global_share_context();
|
||||||
MenuConstants(QObject* parent = nullptr) : QObject(parent) {
|
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
public:
|
||||||
|
void loadFile(const QString& scriptPath) {
|
||||||
|
if (_isRunning) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
qDebug() << "Loading script from " << scriptPath;
|
||||||
|
_fileNameString = scriptPath;
|
||||||
|
|
||||||
|
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("QmlWebWindow", 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 };
|
||||||
|
bool _isThreaded { false };
|
||||||
|
QHash<QTimer*, QScriptValue> _timerFunctionMap;
|
||||||
};
|
};
|
||||||
|
|
||||||
const QString& getResourcesDir() {
|
|
||||||
static QString dir;
|
|
||||||
if (dir.isEmpty()) {
|
ScriptEngine* loadScript(const QString& scriptFilename) {
|
||||||
QDir path(__FILE__);
|
ScriptEngine* scriptEngine = new ScriptEngine();
|
||||||
path.cdUp();
|
scriptEngine->loadFile(scriptFilename);
|
||||||
dir = path.cleanPath(path.absoluteFilePath("../../../interface/resources/")) + "/";
|
return scriptEngine;
|
||||||
qDebug() << "Resources Path: " << dir;
|
|
||||||
}
|
|
||||||
return dir;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const QString& getQmlDir() {
|
OffscreenGLCanvas* _chromiumShareContext { nullptr };
|
||||||
static QString dir;
|
Q_GUI_EXPORT void qt_gl_set_global_share_context(QOpenGLContext *context);
|
||||||
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, private QOpenGLFunctions {
|
class QTestWindow : public QWindow {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
QOpenGLContext* _context{ nullptr };
|
QOpenGLContext* _context{ nullptr };
|
||||||
|
@ -130,86 +429,103 @@ class QTestWindow : public QWindow, private QOpenGLFunctions {
|
||||||
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);
|
||||||
connect(&_timer, &QTimer::timeout, [=] {
|
QObject::connect(&_timer, &QTimer::timeout, this, &QTestWindow::draw);
|
||||||
draw();
|
|
||||||
});
|
|
||||||
|
|
||||||
DependencyManager::set<OffscreenUi>();
|
_chromiumShareContext = new OffscreenGLCanvas();
|
||||||
setSurfaceType(QSurface::OpenGLSurface);
|
_chromiumShareContext->create();
|
||||||
|
_chromiumShareContext->makeCurrent();
|
||||||
|
qt_gl_set_global_share_context(_chromiumShareContext->getContext());
|
||||||
|
|
||||||
QSurfaceFormat format;
|
{
|
||||||
format.setDepthBufferSize(16);
|
setSurfaceType(QSurface::OpenGLSurface);
|
||||||
format.setStencilBufferSize(8);
|
QSurfaceFormat format = getDefaultOpenGLSurfaceFormat();
|
||||||
format.setVersion(4, 1);
|
setFormat(format);
|
||||||
format.setProfile(QSurfaceFormat::OpenGLContextProfile::CompatibilityProfile);
|
_context = new QOpenGLContext;
|
||||||
format.setOption(QSurfaceFormat::DebugContext);
|
_context->setFormat(format);
|
||||||
|
_context->setShareContext(_chromiumShareContext->getContext());
|
||||||
|
}
|
||||||
|
|
||||||
setFormat(format);
|
|
||||||
|
|
||||||
_context = new QOpenGLContext;
|
|
||||||
_context->setFormat(format);
|
|
||||||
if (!_context->create()) {
|
if (!_context->create()) {
|
||||||
qFatal("Could not create OpenGL context");
|
qFatal("Could not create OpenGL context");
|
||||||
}
|
}
|
||||||
|
|
||||||
show();
|
show();
|
||||||
|
|
||||||
makeCurrent();
|
makeCurrent();
|
||||||
initializeOpenGLFunctions();
|
|
||||||
|
|
||||||
{
|
{
|
||||||
|
qDebug() << (const char*)glGetString(GL_VERSION);
|
||||||
QOpenGLDebugLogger* logger = new QOpenGLDebugLogger(this);
|
QOpenGLDebugLogger* logger = new QOpenGLDebugLogger(this);
|
||||||
logger->initialize(); // initializes in the current context, i.e. ctx
|
logger->initialize(); // initializes in the current context, i.e. ctx
|
||||||
logger->enableMessages();
|
logger->enableMessages();
|
||||||
connect(logger, &QOpenGLDebugLogger::messageLogged, this, [&](const QOpenGLDebugMessage & debugMessage) {
|
connect(logger, &QOpenGLDebugLogger::messageLogged, this, [&](const QOpenGLDebugMessage & debugMessage) {
|
||||||
qDebug() << debugMessage;
|
qDebug() << debugMessage;
|
||||||
});
|
});
|
||||||
// logger->startLogging(QOpenGLDebugLogger::SynchronousLogging);
|
//logger->startLogging(QOpenGLDebugLogger::SynchronousLogging);
|
||||||
}
|
}
|
||||||
|
|
||||||
qDebug() << (const char*)this->glGetString(GL_VERSION);
|
glewExperimental = true;
|
||||||
glEnable(GL_BLEND);
|
glewInit();
|
||||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
glGetError();
|
||||||
glClearColor(0.2f, 0.2f, 0.2f, 1);
|
|
||||||
glDisable(GL_DEPTH_TEST);
|
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();
|
MessageDialog::registerType();
|
||||||
VrMenu::registerType();
|
|
||||||
InfoView::registerType();
|
InfoView::registerType();
|
||||||
|
|
||||||
|
auto offscreenUi = DependencyManager::set<OffscreenUi>();
|
||||||
|
{
|
||||||
|
offscreenUi->create(_context);
|
||||||
|
offscreenUi->setProxyWindow(this);
|
||||||
|
|
||||||
auto offscreenUi = DependencyManager::get<OffscreenUi>();
|
connect(offscreenUi.data(), &OffscreenUi::textureUpdated, this, [this, offscreenUi](int textureId) {
|
||||||
offscreenUi->create(_context);
|
testQmlTexture = textureId;
|
||||||
connect(offscreenUi.data(), &OffscreenUi::textureUpdated, this, [this, offscreenUi](int textureId) {
|
});
|
||||||
testQmlTexture = textureId;
|
|
||||||
});
|
|
||||||
|
|
||||||
makeCurrent();
|
makeCurrent();
|
||||||
|
}
|
||||||
|
|
||||||
offscreenUi->setProxyWindow(this);
|
|
||||||
QDesktopWidget* desktop = QApplication::desktop();
|
auto primaryScreen = QGuiApplication::primaryScreen();
|
||||||
QRect rect = desktop->availableGeometry(desktop->screenCount() - 1);
|
auto targetScreen = primaryScreen;
|
||||||
int height = rect.height();
|
auto screens = QGuiApplication::screens();
|
||||||
//rect.setHeight(height / 2);
|
if (screens.size() > 1) {
|
||||||
rect.setY(rect.y() + height / 2);
|
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));
|
||||||
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(getQmlDir()));
|
offscreenUi->setBaseUrl(QUrl::fromLocalFile(getInterfaceQmlDir()));
|
||||||
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();
|
||||||
|
@ -227,16 +543,35 @@ private:
|
||||||
}
|
}
|
||||||
|
|
||||||
makeCurrent();
|
makeCurrent();
|
||||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
auto error = glGetError();
|
||||||
glViewport(0, 0, _size.width() * devicePixelRatio(), _size.height() * devicePixelRatio());
|
if (error != GL_NO_ERROR) {
|
||||||
|
qDebug() << "GL error in entering draw " << error;
|
||||||
|
}
|
||||||
|
|
||||||
renderQml();
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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() >= 2.0f) {
|
if (fps.elapsed() >= 10.0f) {
|
||||||
qDebug() << "FPS: " << fps.rate();
|
qDebug() << "FPS: " << fps.rate();
|
||||||
fps.reset();
|
fps.reset();
|
||||||
}
|
}
|
||||||
|
@ -246,8 +581,6 @@ 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);
|
||||||
|
@ -269,11 +602,13 @@ 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) {
|
||||||
InfoView::show(getResourcesDir() + "html/interface-welcome.html", true);
|
loadScript(getExamplesDir() + "tests/qmlWebTest.js");
|
||||||
}
|
}
|
||||||
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){
|
||||||
|
@ -281,22 +616,9 @@ 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;
|
||||||
|
@ -308,40 +630,26 @@ 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) {
|
||||||
QApplication app(argc, argv);
|
QGuiApplication app(argc, argv);
|
||||||
|
qInstallMessageHandler(messageHandler);
|
||||||
QLoggingCategory::setFilterRules(LOG_FILTER_RULES);
|
QLoggingCategory::setFilterRules(LOG_FILTER_RULES);
|
||||||
QTestWindow window;
|
QTestWindow window;
|
||||||
app.exec();
|
app.exec();
|
||||||
|
|
Loading…
Reference in a new issue