diff --git a/CMakeLists.txt b/CMakeLists.txt index 81d532fbe2..d27774e169 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -92,8 +92,17 @@ else () if (NOT QT_CMAKE_PREFIX_PATH) set(QT_CMAKE_PREFIX_PATH $ENV{QT_CMAKE_PREFIX_PATH}) endif () + if (NOT QT_CMAKE_PREFIX_PATH) + get_filename_component(QT_CMAKE_PREFIX_PATH "${Qt5_DIR}/.." REALPATH) + endif () endif () +if (WIN32) + if (NOT EXISTS ${QT_CMAKE_PREFIX_PATH}) + message(FATAL_ERROR "Could not determine QT_CMAKE_PREFIX_PATH.") + endif () +endif() + # figure out where the qt dir is get_filename_component(QT_DIR "${QT_CMAKE_PREFIX_PATH}/../../" ABSOLUTE) diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index f4f390607b..5d2fe5ae38 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -54,7 +54,7 @@ else () list(REMOVE_ITEM INTERFACE_SRCS ${SPEECHRECOGNIZER_CPP}) endif () -find_package(Qt5 COMPONENTS Gui Multimedia Network OpenGL Script Svg WebKitWidgets) +find_package(Qt5 COMPONENTS Gui Multimedia Network OpenGL Qml Quick Script Svg WebKitWidgets) # grab the ui files in resources/ui file (GLOB_RECURSE QT_UI_FILES ui/*.ui) diff --git a/interface/resources/qml/AddressBarDialog.qml b/interface/resources/qml/AddressBarDialog.qml new file mode 100644 index 0000000000..df06fabbe6 --- /dev/null +++ b/interface/resources/qml/AddressBarDialog.qml @@ -0,0 +1,84 @@ +import Hifi 1.0 +import QtQuick 2.3 +import QtQuick.Controls 1.2 +import QtQuick.Window 2.2 +import QtQuick.Controls.Styles 1.3 + +AddressBarDialog { + id: addressBarDialog + objectName: "AddressBarDialog" + SystemPalette { id: myPalette; colorGroup: SystemPalette.Active } + height: 128 + width: 512 + + onVisibleChanged: { + if (!visible) { + reset(); + } else { + addressLine.focus = true + addressLine.forceActiveFocus() + } + } + + Component.onCompleted: { + addressLine.focus = true + addressLine.forceActiveFocus() + } + + function reset() { + addressLine.text = "" + goButton.source = "../images/address-bar-submit.svg" + } + + CustomDialog { + id: dialog + anchors.fill: parent + title: "Go to..." + + // The client area + Item { + id: item1 + anchors.fill: parent + anchors.margins: parent.margins + anchors.topMargin: parent.topMargin + + CustomBorder { + height: 64 + anchors.left: parent.left + anchors.leftMargin: 0 + anchors.right: goButton.left + anchors.rightMargin: 8 + anchors.verticalCenter: parent.verticalCenter + CustomTextInput { + id: addressLine + anchors.fill: parent + helperText: "domain, location, @user, /x,y,z" + anchors.margins: 8 + onAccepted: { + addressBarDialog.loadAddress(addressLine.text) + } + } + } + + Image { + id: goButton + width: 32 + height: 32 + anchors.right: parent.right + anchors.rightMargin: 8 + source: "../images/address-bar-submit.svg" + anchors.verticalCenter: parent.verticalCenter + + MouseArea { + anchors.fill: parent + onClicked: { + parent.source = "../images/address-bar-submit-active.svg" + addressBarDialog.loadAddress(addressLine.text) + } + } + } + + } + } +} + diff --git a/interface/resources/qml/CustomBorder.qml b/interface/resources/qml/CustomBorder.qml new file mode 100644 index 0000000000..1bb30d1ebc --- /dev/null +++ b/interface/resources/qml/CustomBorder.qml @@ -0,0 +1,12 @@ +import QtQuick 2.3 + + +Rectangle { + SystemPalette { id: myPalette; colorGroup: SystemPalette.Active } + property int margin: 5 + color: myPalette.window + border.color: myPalette.dark + border.width: 5 + radius: border.width * 2 +} + diff --git a/interface/resources/qml/CustomButton.qml b/interface/resources/qml/CustomButton.qml new file mode 100644 index 0000000000..ce57d7ce5e --- /dev/null +++ b/interface/resources/qml/CustomButton.qml @@ -0,0 +1,23 @@ +import QtQuick 2.3 +import QtQuick.Controls 1.3 +import QtQuick.Window 2.2 +import QtQuick.Controls.Styles 1.3 + +Button { + SystemPalette { id: myPalette; colorGroup: SystemPalette.Active } + text: "Text" + width: 128 + height: 64 + style: ButtonStyle { + background: CustomBorder { + anchors.fill: parent + } + label: CustomText { + renderType: Text.NativeRendering + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter + text: control.text + color: control.enabled ? myPalette.text : myPalette.dark + } + } +} diff --git a/interface/resources/qml/CustomDialog.qml b/interface/resources/qml/CustomDialog.qml new file mode 100644 index 0000000000..71f36b4108 --- /dev/null +++ b/interface/resources/qml/CustomDialog.qml @@ -0,0 +1,98 @@ +import QtQuick 2.3 +import QtQuick.Controls 1.2 +import QtQuick.Window 2.2 +import QtQuick.Dialogs 1.2 +import QtQuick.Controls.Styles 1.3 +import "hifiConstants.js" as HifiConstants + +Item { + SystemPalette { id: myPalette; colorGroup: SystemPalette.Active } + + id: dialog + width: 256 + height: 256 + property rect clientArea: clientBorder + property int topMargin: dialog.height - clientBorder.height + 8 + property int margins: 8 + property string title + property int titleSize: titleBorder.height + 12 + property string frameColor: HifiConstants.color + property string backgroundColor: myPalette.window + property string headerBackgroundColor: myPalette.dark + + CustomBorder { + id: windowBorder + anchors.fill: parent + border.color: dialog.frameColor + color: dialog.backgroundColor + + CustomBorder { + id: titleBorder + height: 48 + anchors.right: parent.right + anchors.rightMargin: 0 + anchors.left: parent.left + anchors.leftMargin: 0 + border.color: dialog.frameColor + color: dialog.headerBackgroundColor + + CustomText { + id: titleText + color: "white" + text: dialog.title + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + anchors.fill: parent + } + + MouseArea { + id: titleDrag + property int startX + property int startY + anchors.right: closeButton.left + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.top: parent.top + anchors.rightMargin: 4 + drag { + target: dialog.parent + minimumX: 0 + minimumY: 0 + maximumX: dialog.parent.parent.width - dialog.parent.width + maximumY: dialog.parent.parent.height - dialog.parent.height + } + } + Image { + id: closeButton + x: 360 + height: 16 + anchors.verticalCenter: parent.verticalCenter + width: 16 + anchors.right: parent.right + anchors.rightMargin: 12 + source: "../styles/close.svg" + MouseArea { + anchors.fill: parent + onClicked: { + dialog.parent.destroy() + } + } + } + + } // header border + + CustomBorder { + id: clientBorder + border.color: dialog.frameColor + color: "#00000000" + anchors.bottom: parent.bottom + anchors.bottomMargin: 0 + anchors.top: titleBorder.bottom + anchors.topMargin: -titleBorder.border.width + anchors.right: parent.right + anchors.rightMargin: 0 + anchors.left: parent.left + anchors.leftMargin: 0 + } // client border + } // window border +} diff --git a/interface/resources/qml/CustomText.qml b/interface/resources/qml/CustomText.qml new file mode 100644 index 0000000000..83229b783e --- /dev/null +++ b/interface/resources/qml/CustomText.qml @@ -0,0 +1,7 @@ +import QtQuick 2.3 + +Text { + font.family: "Helvetica" + font.pointSize: 18 +} + diff --git a/interface/resources/qml/CustomTextArea.qml b/interface/resources/qml/CustomTextArea.qml new file mode 100644 index 0000000000..cf3308e2b7 --- /dev/null +++ b/interface/resources/qml/CustomTextArea.qml @@ -0,0 +1,10 @@ +import QtQuick 2.3 +import QtQuick.Controls 1.2 + +TextArea { + font.family: "Helvetica" + font.pointSize: 18 + backgroundVisible: false + readOnly: true +} + diff --git a/interface/resources/qml/CustomTextEdit.qml b/interface/resources/qml/CustomTextEdit.qml new file mode 100644 index 0000000000..0602bbc150 --- /dev/null +++ b/interface/resources/qml/CustomTextEdit.qml @@ -0,0 +1,7 @@ +import QtQuick 2.3 + +TextEdit { + font.family: "Helvetica" + font.pointSize: 18 +} + diff --git a/interface/resources/qml/CustomTextInput.qml b/interface/resources/qml/CustomTextInput.qml new file mode 100644 index 0000000000..a706187376 --- /dev/null +++ b/interface/resources/qml/CustomTextInput.qml @@ -0,0 +1,34 @@ +import QtQuick 2.3 +import QtQuick.Controls 1.2 + +TextInput { + SystemPalette { id: myPalette; colorGroup: SystemPalette.Active } + property string helperText: "" + font.family: "Helvetica" + font.pointSize: 18 + width: 256 + height: 64 + color: myPalette.text + clip: true + verticalAlignment: TextInput.AlignVCenter + + onTextChanged: { + if (text == "") { + helperText.visible = true; + } else { + helperText.visible = false; + } + } + + Text { + id: helperText + anchors.fill: parent + font.pointSize: parent.font.pointSize + font.family: "Helvetica" + verticalAlignment: TextInput.AlignVCenter + text: parent.helperText + color: myPalette.dark + clip: true + } +} + diff --git a/interface/resources/qml/Icon.qml b/interface/resources/qml/Icon.qml new file mode 100644 index 0000000000..0d60afb2b7 --- /dev/null +++ b/interface/resources/qml/Icon.qml @@ -0,0 +1,8 @@ +import QtQuick 1.0 + +Image { +id: icon +width: 64 +height: 64 +source: "file.svg" +} \ No newline at end of file diff --git a/interface/resources/qml/IconControl.qml b/interface/resources/qml/IconControl.qml new file mode 100644 index 0000000000..346865aacb --- /dev/null +++ b/interface/resources/qml/IconControl.qml @@ -0,0 +1,24 @@ +import QtQuick 2.3 +import QtQuick.Controls 1.3 +import QtQuick.Window 2.2 +import QtQuick.Controls.Styles 1.3 + +Button { + text: "Text" + style: ButtonStyle { + background: Item { anchors.fill: parent } + label: Text { + id: icon + width: height + verticalAlignment: Text.AlignVCenter + renderType: Text.NativeRendering + font.family: iconFont.name + font.pointSize: 18 + property alias unicode: icon.text + FontLoader { id: iconFont; source: "/fonts/fontawesome-webfont.ttf"; } + text: control.text + color: control.enabled ? "white" : "dimgray" + } + } +} + diff --git a/interface/resources/qml/LoginDialog.qml b/interface/resources/qml/LoginDialog.qml new file mode 100644 index 0000000000..c306f4ed7e --- /dev/null +++ b/interface/resources/qml/LoginDialog.qml @@ -0,0 +1,191 @@ +import Hifi 1.0 +import QtQuick 2.3 +import QtQuick.Controls 1.2 +import QtQuick.Window 2.2 +import QtQuick.Controls.Styles 1.3 +import "hifiConstants.js" as HifiConstants + +LoginDialog { + SystemPalette { id: myPalette; colorGroup: SystemPalette.Active } + id: loginDialog + objectName: "LoginDialog" + height: 512 + width: 384 + + onVisibleChanged: { + if (!visible) { + reset() + } else { + username.forceActiveFocus() + } + } + + function reset() { + username.text = "" + password.text = "" + loginDialog.statusText = "" + } + + CustomDialog { + anchors.fill: parent + title: "Login" + Item { + id: item1 + anchors.fill: parent + anchors.margins: parent.margins + anchors.topMargin: parent.topMargin + Column { + anchors.topMargin: 8 + anchors.right: parent.right + anchors.rightMargin: 0 + anchors.left: parent.left + anchors.top: parent.top + spacing: 8 + + Image { + height: 64 + anchors.horizontalCenter: parent.horizontalCenter + width: 64 + source: "../images/hifi-logo.svg" + } + + CustomBorder { + width: 304 + height: 64 + anchors.horizontalCenter: parent.horizontalCenter + CustomTextInput { + id: username + anchors.fill: parent + helperText: "Username or Email" + anchors.margins: 8 + KeyNavigation.tab: password + KeyNavigation.backtab: password + onAccepted: { + password.forceActiveFocus() + } + } + } + + CustomBorder { + width: 304 + height: 64 + anchors.horizontalCenter: parent.horizontalCenter + CustomTextInput { + id: password + anchors.fill: parent + echoMode: TextInput.Password + helperText: "Password" + anchors.margins: 8 + KeyNavigation.tab: username + KeyNavigation.backtab: username + onAccepted: { + if (username.text == "") { + username.forceActiveFocus() + } else { + loginDialog.login(username.text, password.text) + } + } + onFocusChanged: { + if (password.focus) { + password.selectAll() + } + } + } + } + + CustomText { + anchors.horizontalCenter: parent.horizontalCenter + textFormat: Text.StyledText + width: parent.width + height: 96 + wrapMode: Text.WordWrap + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter + text: loginDialog.statusText + } + } + + Column { + anchors.bottomMargin: 5 + anchors.right: parent.right + anchors.rightMargin: 0 + anchors.left: parent.left + anchors.bottom: parent.bottom + + Rectangle { + width: 192 + height: 64 + anchors.horizontalCenter: parent.horizontalCenter + color: HifiConstants.color + border.width: 0 + radius: 10 + + MouseArea { + anchors.bottom: parent.bottom + anchors.bottomMargin: 0 + anchors.top: parent.top + anchors.right: parent.right + anchors.left: parent.left + onClicked: { + loginDialog.login(username.text, password.text) + } + } + + Row { + anchors.centerIn: parent + anchors.verticalCenter: parent.verticalCenter + spacing: 8 + Image { + id: loginIcon + height: 32 + width: 32 + source: "../images/login.svg" + } + CustomText { + text: "Login" + color: "white" + width: 64 + height: parent.height + } + } + + } + + CustomText { + width: parent.width + height: 24 + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + text:"Create Account" + font.pointSize: 12 + font.bold: true + color: HifiConstants.color + + MouseArea { + anchors.fill: parent + onClicked: { + loginDialog.openUrl(loginDialog.rootUrl + "/signup") + } + } + } + + CustomText { + width: parent.width + height: 24 + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + font.pointSize: 12 + text: "Recover Password" + color: HifiConstants.color + + MouseArea { + anchors.fill: parent + onClicked: { + loginDialog.openUrl(loginDialog.rootUrl + "/users/password/new") + } + } + } + } + } + } +} diff --git a/interface/resources/qml/Root.qml b/interface/resources/qml/Root.qml new file mode 100644 index 0000000000..f290a8b5ca --- /dev/null +++ b/interface/resources/qml/Root.qml @@ -0,0 +1,14 @@ +import QtQuick 2.3 +import "componentCreation.js" as Creator + + +Item { + id: root + width: 1280 + height: 720 + + function loadChild(url) { + Creator.createObject(root, url) + } +} + diff --git a/interface/resources/qml/componentCreation.js b/interface/resources/qml/componentCreation.js new file mode 100644 index 0000000000..6e6469adfb --- /dev/null +++ b/interface/resources/qml/componentCreation.js @@ -0,0 +1,27 @@ +var component; +var instance; +var parent; + +function createObject(parentObject, url) { + parent = parentObject; + component = Qt.createComponent(url); + if (component.status == Component.Ready) + finishCreation(); + else + component.statusChanged.connect(finishCreation); +} + +function finishCreation() { + if (component.status == Component.Ready) { + instance = component.createObject(parent, {"x": 100, "y": 100}); + if (instance == null) { + // Error Handling + console.log("Error creating object"); + } else { + instance.focus = true; + } + } else if (component.status == Component.Error) { + // Error Handling + console.log("Error loading component:", component.errorString()); + } +} \ No newline at end of file diff --git a/interface/resources/qml/hifiConstants.js b/interface/resources/qml/hifiConstants.js new file mode 100644 index 0000000000..860226c963 --- /dev/null +++ b/interface/resources/qml/hifiConstants.js @@ -0,0 +1,4 @@ +var color = "#0e7077" +var Colors = { + hifiBlue: "#0e7077" +} diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 82447257fb..0766d45ff3 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -137,6 +137,7 @@ #include "ui/Snapshot.h" #include "ui/StandAloneJSConsole.h" #include "ui/Stats.h" +#include "ui/AddressBarDialog.h" // ON WIndows PC, NVidia Optimus laptop, we want to enable NVIDIA GPU #if defined(Q_OS_WIN) @@ -209,8 +210,12 @@ public: void messageHandler(QtMsgType type, const QMessageLogContext& context, const QString& message) { QString logMessage = LogHandler::getInstance().printMessage((LogMsgType) type, context, message); - + if (!logMessage.isEmpty()) { +#ifdef Q_OS_WIN + OutputDebugStringA(logMessage.toLocal8Bit().constData()); + OutputDebugStringA("\n"); +#endif Application::getInstance()->getLogger()->addMessage(qPrintable(logMessage + "\n")); } } @@ -260,6 +265,7 @@ bool setupEssentials(int& argc, char** argv) { #endif auto discoverabilityManager = DependencyManager::set(); auto sceneScriptingInterface = DependencyManager::set(); + auto offscreenUi = DependencyManager::set(); return true; } @@ -316,8 +322,10 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : #ifdef Q_OS_WIN installNativeEventFilter(&MyNativeEventFilter::getInstance()); #endif + _logger = new FileLogger(this); // After setting organization name in order to get correct directory + qInstallMessageHandler(messageHandler); QFontDatabase::addApplicationFont(PathUtils::resourcesPath() + "styles/Inconsolata.otf"); @@ -562,8 +570,13 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : #endif this->installEventFilter(this); + // The offscreen UI needs to intercept the mouse and keyboard + // events coming from the onscreen window + _glWidget->installEventFilter( + DependencyManager::get().data()); } + void Application::aboutToQuit() { emit beforeAboutToQuit(); @@ -720,10 +733,35 @@ void Application::initializeGL() { // update before the first render update(1.0f / _fps); + + // The UI can't be created until the primary OpenGL + // context is created, because it needs to share + // texture resources + initializeUi(); InfoView::showFirstTime(INFO_HELP_PATH); } +void Application::initializeUi() { + AddressBarDialog::registerType(); + LoginDialog::registerType(); + + auto offscreenUi = DependencyManager::get(); + offscreenUi->create(_glWidget->context()->contextHandle()); + offscreenUi->resize(_glWidget->size()); + offscreenUi->setProxyWindow(_window->windowHandle()); + auto rootQml = PathUtils::resourcesPath() + "qml/Root.qml"; + offscreenUi->loadQml(QUrl::fromLocalFile(rootQml)); + offscreenUi->setMouseTranslator([this](const QPointF& p){ + if (OculusManager::isConnected()) { + glm::vec2 pos = _applicationOverlay.screenToOverlay(toGlm(p)); + return QPointF(pos.x, pos.y); + } + return QPointF(p); + }); + offscreenUi->resume(); +} + void Application::paintGL() { PROFILE_RANGE(__FUNCTION__); PerformanceTimer perfTimer("paintGL"); @@ -818,7 +856,7 @@ void Application::paintGL() { { PerformanceTimer perfTimer("renderOverlay"); - _applicationOverlay.renderOverlay(true); + _applicationOverlay.renderOverlay(); _applicationOverlay.displayOverlayTexture(); } } @@ -863,6 +901,9 @@ void Application::resizeGL(int width, int height) { updateProjectionMatrix(); glLoadIdentity(); + auto offscreenUi = DependencyManager::get(); + offscreenUi->resize(QSize(width, height)); + // update Stats width // let's set horizontal offset to give stats some margin to mirror int horizontalOffset = MIRROR_VIEW_WIDTH + MIRROR_VIEW_LEFT_PADDING * 2; @@ -911,6 +952,44 @@ bool Application::importSVOFromURL(const QString& urlString) { } bool Application::event(QEvent* event) { + switch (event->type()) { + case QEvent::MouseMove: + mouseMoveEvent((QMouseEvent*)event); + return true; + case QEvent::MouseButtonPress: + mousePressEvent((QMouseEvent*)event); + return true; + case QEvent::MouseButtonRelease: + mouseReleaseEvent((QMouseEvent*)event); + return true; + case QEvent::KeyPress: + keyPressEvent((QKeyEvent*)event); + return true; + case QEvent::KeyRelease: + keyReleaseEvent((QKeyEvent*)event); + return true; + case QEvent::FocusOut: + focusOutEvent((QFocusEvent*)event); + return true; + case QEvent::TouchBegin: + touchBeginEvent(static_cast(event)); + event->accept(); + return true; + case QEvent::TouchEnd: + touchEndEvent(static_cast(event)); + return true; + case QEvent::TouchUpdate: + touchUpdateEvent(static_cast(event)); + return true; + case QEvent::Wheel: + wheelEvent(static_cast(event)); + return true; + case QEvent::Drop: + dropEvent(static_cast(event)); + return true; + default: + break; + } // handle custom URL if (event->type() == QEvent::FileOpen) { @@ -931,7 +1010,7 @@ bool Application::event(QEvent* event) { if (HFActionEvent::types().contains(event->type())) { _controllerScriptingInterface.handleMetaEvent(static_cast(event)); } - + return QApplication::event(event); } @@ -963,14 +1042,17 @@ void Application::keyPressEvent(QKeyEvent* event) { bool isShifted = event->modifiers().testFlag(Qt::ShiftModifier); bool isMeta = event->modifiers().testFlag(Qt::ControlModifier); bool isOption = event->modifiers().testFlag(Qt::AltModifier); + bool isKeypad = event->modifiers().testFlag(Qt::KeypadModifier); switch (event->key()) { break; case Qt::Key_L: - if (isShifted) { - Menu::getInstance()->triggerOption(MenuOption::LodTools); - } else if (isMeta) { + if (isShifted && isMeta) { Menu::getInstance()->triggerOption(MenuOption::Log); - } + } else if (isMeta) { + Menu::getInstance()->triggerOption(MenuOption::AddressBar); + } else if (isShifted) { + Menu::getInstance()->triggerOption(MenuOption::LodTools); + } break; case Qt::Key_E: @@ -1030,11 +1112,6 @@ void Application::keyPressEvent(QKeyEvent* event) { } break; - case Qt::Key_Return: - case Qt::Key_Enter: - Menu::getInstance()->triggerOption(MenuOption::AddressBar); - break; - case Qt::Key_Backslash: Menu::getInstance()->triggerOption(MenuOption::Chat); break; @@ -1494,6 +1571,17 @@ void Application::dropEvent(QDropEvent *event) { } } +void Application::dragEnterEvent(QDragEnterEvent* event) { + const QMimeData* mimeData = event->mimeData(); + foreach(QUrl url, mimeData->urls()) { + auto urlString = url.toString(); + if (canAcceptURL(urlString)) { + event->acceptProposedAction(); + break; + } + } +} + bool Application::acceptSnapshot(const QString& urlString) { QUrl url(urlString); QString snapshotPath = url.toLocalFile(); diff --git a/interface/src/Application.h b/interface/src/Application.h index cf047f02d4..57c6530ad4 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -151,6 +152,7 @@ public: void setPreviousScriptLocation(const QString& previousScriptLocation); void clearScriptsBeforeRunning(); void initializeGL(); + void initializeUi(); void paintGL(); void resizeGL(int width, int height); @@ -170,6 +172,7 @@ public: void wheelEvent(QWheelEvent* event); void dropEvent(QDropEvent *event); + void dragEnterEvent(QDragEnterEvent *event); bool event(QEvent* event); bool eventFilter(QObject* object, QEvent* event); diff --git a/interface/src/GLCanvas.cpp b/interface/src/GLCanvas.cpp index 12a10681ce..d0c0cb6713 100644 --- a/interface/src/GLCanvas.cpp +++ b/interface/src/GLCanvas.cpp @@ -82,30 +82,6 @@ void GLCanvas::resizeGL(int width, int height) { Application::getInstance()->resizeGL(width, height); } -void GLCanvas::keyPressEvent(QKeyEvent* event) { - Application::getInstance()->keyPressEvent(event); -} - -void GLCanvas::keyReleaseEvent(QKeyEvent* event) { - Application::getInstance()->keyReleaseEvent(event); -} - -void GLCanvas::focusOutEvent(QFocusEvent* event) { - Application::getInstance()->focusOutEvent(event); -} - -void GLCanvas::mouseMoveEvent(QMouseEvent* event) { - Application::getInstance()->mouseMoveEvent(event); -} - -void GLCanvas::mousePressEvent(QMouseEvent* event) { - Application::getInstance()->mousePressEvent(event); -} - -void GLCanvas::mouseReleaseEvent(QMouseEvent* event) { - Application::getInstance()->mouseReleaseEvent(event); -} - void GLCanvas::activeChanged(Qt::ApplicationState state) { switch (state) { case Qt::ApplicationActive: @@ -151,40 +127,37 @@ void GLCanvas::throttleRender() { int updateTime = 0; bool GLCanvas::event(QEvent* event) { switch (event->type()) { + case QEvent::MouseMove: + case QEvent::MouseButtonPress: + case QEvent::MouseButtonRelease: + case QEvent::KeyPress: + case QEvent::KeyRelease: + case QEvent::FocusIn: + case QEvent::FocusOut: + case QEvent::Resize: case QEvent::TouchBegin: - Application::getInstance()->touchBeginEvent(static_cast(event)); - event->accept(); - return true; case QEvent::TouchEnd: - Application::getInstance()->touchEndEvent(static_cast(event)); - return true; case QEvent::TouchUpdate: - Application::getInstance()->touchUpdateEvent(static_cast(event)); - return true; + case QEvent::Wheel: + case QEvent::DragEnter: + case QEvent::Drop: + if (QCoreApplication::sendEvent(QCoreApplication::instance(), event)) { + return true; + } + break; + case QEvent::Paint: + // Ignore paint events that occur after we've decided to quit + if (Application::getInstance()->isAboutToQuit()) { + return true; + } + break; + default: break; } return QGLWidget::event(event); } -void GLCanvas::wheelEvent(QWheelEvent* event) { - Application::getInstance()->wheelEvent(event); -} - -void GLCanvas::dragEnterEvent(QDragEnterEvent* event) { - const QMimeData* mimeData = event->mimeData(); - foreach (QUrl url, mimeData->urls()) { - auto urlString = url.toString(); - if (Application::getInstance()->canAcceptURL(urlString)) { - event->acceptProposedAction(); - break; - } - } -} - -void GLCanvas::dropEvent(QDropEvent* event) { - Application::getInstance()->dropEvent(event); -} // Pressing Alt (and Meta) key alone activates the menubar because its style inherits the // SHMenuBarAltKeyNavigation from QWindowsStyle. This makes it impossible for a scripts to diff --git a/interface/src/GLCanvas.h b/interface/src/GLCanvas.h index 7b86f983e9..6c53a17e04 100644 --- a/interface/src/GLCanvas.h +++ b/interface/src/GLCanvas.h @@ -40,23 +40,8 @@ protected: virtual void initializeGL(); virtual void paintGL(); virtual void resizeGL(int width, int height); - - virtual void keyPressEvent(QKeyEvent* event); - virtual void keyReleaseEvent(QKeyEvent* event); - - virtual void focusOutEvent(QFocusEvent* event); - - virtual void mouseMoveEvent(QMouseEvent* event); - virtual void mousePressEvent(QMouseEvent* event); - virtual void mouseReleaseEvent(QMouseEvent* event); - virtual bool event(QEvent* event); - virtual void wheelEvent(QWheelEvent* event); - - virtual void dragEnterEvent(QDragEnterEvent *event); - virtual void dropEvent(QDropEvent* event); - private slots: void activeChanged(Qt::ApplicationState state); void throttleRender(); diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 1cce0b1f56..64e5accb1b 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -97,7 +97,7 @@ Menu::Menu() { addActionToQMenuAndActionHash(fileMenu, MenuOption::AddressBar, - Qt::Key_Enter, + Qt::CTRL | Qt::Key_L, dialogsManager.data(), SLOT(toggleAddressBar())); auto addressManager = DependencyManager::get(); @@ -151,7 +151,8 @@ Menu::Menu() { connect(speechRecognizer.data(), SIGNAL(enabledUpdated(bool)), speechRecognizerAction, SLOT(setChecked(bool))); #endif - addActionToQMenuAndActionHash(toolsMenu, MenuOption::Chat, Qt::Key_Backslash, + addActionToQMenuAndActionHash(toolsMenu, MenuOption::Chat, + 0, // QML Qt::Key_Backslash, dialogsManager.data(), SLOT(showIRCLink())); addActionToQMenuAndActionHash(toolsMenu, MenuOption::AddRemoveFriends, 0, qApp, SLOT(showFriendsWindow())); @@ -194,7 +195,7 @@ Menu::Menu() { addActionToQMenuAndActionHash(toolsMenu, MenuOption::ResetSensors, - Qt::Key_Apostrophe, + 0, // QML Qt::Key_Apostrophe, qApp, SLOT(resetSensors())); @@ -207,17 +208,17 @@ Menu::Menu() { QMenu* avatarSizeMenu = avatarMenu->addMenu("Size"); addActionToQMenuAndActionHash(avatarSizeMenu, MenuOption::IncreaseAvatarSize, - Qt::Key_Plus, + 0, // QML Qt::Key_Plus, avatar, SLOT(increaseSize())); addActionToQMenuAndActionHash(avatarSizeMenu, MenuOption::DecreaseAvatarSize, - Qt::Key_Minus, + 0, // QML Qt::Key_Minus, avatar, SLOT(decreaseSize())); addActionToQMenuAndActionHash(avatarSizeMenu, MenuOption::ResetAvatarSize, - Qt::Key_Equal, + 0, // QML Qt::Key_Equal, avatar, SLOT(resetSize())); @@ -250,13 +251,17 @@ Menu::Menu() { qApp, SLOT(setFullscreen(bool))); #endif - addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::FirstPerson, Qt::Key_P, true, - qApp,SLOT(cameraMenuChanged())); - addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::Mirror, Qt::SHIFT | Qt::Key_H, true); - addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::FullscreenMirror, Qt::Key_H, false, - qApp, SLOT(cameraMenuChanged())); + addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::FirstPerson, + 0, // QML Qt::Key_P, + true, qApp, SLOT(cameraMenuChanged())); + addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::Mirror, + 0, //QML Qt::SHIFT | Qt::Key_H, + true); + addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::FullscreenMirror, + 0, // QML Qt::Key_H, + false, qApp, SLOT(cameraMenuChanged())); - addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::HMDTools, Qt::META | Qt::Key_H, + addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::HMDTools, Qt::CTRL | Qt::SHIFT | Qt::Key_H, false, dialogsManager.data(), SLOT(hmdTools(bool))); @@ -283,8 +288,12 @@ Menu::Menu() { addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::TurnWithHead, 0, false); - addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::Stats, Qt::Key_Slash); - addActionToQMenuAndActionHash(viewMenu, MenuOption::Log, Qt::CTRL | Qt::Key_L, qApp, SLOT(toggleLogDialog())); + addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::Stats, + 0); // QML Qt::Key_Slash); + addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::Stats); + addActionToQMenuAndActionHash(viewMenu, MenuOption::Log, + Qt::CTRL | Qt::SHIFT | Qt::Key_L, + qApp, SLOT(toggleLogDialog())); addActionToQMenuAndActionHash(viewMenu, MenuOption::BandwidthDetails, 0, dialogsManager.data(), SLOT(bandwidthDetails())); addActionToQMenuAndActionHash(viewMenu, MenuOption::OctreeStats, 0, @@ -294,7 +303,9 @@ Menu::Menu() { QMenu* developerMenu = addMenu("Developer"); QMenu* renderOptionsMenu = developerMenu->addMenu("Render"); - addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Atmosphere, Qt::SHIFT | Qt::Key_A, true); + addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Atmosphere, + 0, // QML Qt::SHIFT | Qt::Key_A, + true); addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::AmbientOcclusion); addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::DontFadeOnOctreeServerChanges); @@ -346,13 +357,16 @@ Menu::Menu() { resolutionGroup->addAction(addCheckableActionToQMenuAndActionHash(resolutionMenu, MenuOption::RenderResolutionThird, 0, false)); resolutionGroup->addAction(addCheckableActionToQMenuAndActionHash(resolutionMenu, MenuOption::RenderResolutionQuarter, 0, false)); - addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Stars, Qt::Key_Asterisk, true); + addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Stars, + 0, // QML Qt::Key_Asterisk, + true); addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::EnableGlowEffect, 0, true, DependencyManager::get().data(), SLOT(toggleGlowEffect(bool))); addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Wireframe, Qt::ALT | Qt::Key_W, false); - addActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::LodTools, Qt::SHIFT | Qt::Key_L, - dialogsManager.data(), SLOT(lodTools())); + addActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::LodTools, + 0, // QML Qt::SHIFT | Qt::Key_L, + dialogsManager.data(), SLOT(lodTools())); QMenu* avatarDebugMenu = developerMenu->addMenu("Avatar"); diff --git a/interface/src/devices/OculusManager.cpp b/interface/src/devices/OculusManager.cpp index d56ece12fb..a309831c14 100644 --- a/interface/src/devices/OculusManager.cpp +++ b/interface/src/devices/OculusManager.cpp @@ -114,8 +114,11 @@ void OculusManager::initSdk() { } void OculusManager::shutdownSdk() { - ovrHmd_Destroy(_ovrHmd); - ovr_Shutdown(); + if (_ovrHmd) { + ovrHmd_Destroy(_ovrHmd); + _ovrHmd = nullptr; + ovr_Shutdown(); + } } void OculusManager::init() { @@ -124,6 +127,12 @@ void OculusManager::init() { #endif } +void OculusManager::deinit() { +#ifdef OVR_DIRECT_MODE + shutdownSdk(); +#endif +} + void OculusManager::connect() { #ifndef OVR_DIRECT_MODE initSdk(); @@ -515,7 +524,7 @@ void OculusManager::display(const glm::quat &bodyOrientation, const glm::vec3 &p // We only need to render the overlays to a texture once, then we just render the texture on the hemisphere // PrioVR will only work if renderOverlay is called, calibration is connected to Application::renderingOverlay() - applicationOverlay.renderOverlay(true); + applicationOverlay.renderOverlay(); //Bind our framebuffer object. If we are rendering the glow effect, we let the glow effect shader take care of it if (Menu::getInstance()->isOptionChecked(MenuOption::EnableGlowEffect)) { diff --git a/interface/src/devices/OculusManager.h b/interface/src/devices/OculusManager.h index fe2da31231..4b1bc98fb2 100644 --- a/interface/src/devices/OculusManager.h +++ b/interface/src/devices/OculusManager.h @@ -51,6 +51,7 @@ class Text3DOverlay; class OculusManager { public: static void init(); + static void deinit(); static void connect(); static void disconnect(); static bool isConnected(); diff --git a/interface/src/devices/TV3DManager.cpp b/interface/src/devices/TV3DManager.cpp index f082c6de47..4f6e566d8c 100644 --- a/interface/src/devices/TV3DManager.cpp +++ b/interface/src/devices/TV3DManager.cpp @@ -103,7 +103,7 @@ void TV3DManager::display(Camera& whichCamera) { // We only need to render the overlays to a texture once, then we just render the texture as a quad // PrioVR will only work if renderOverlay is called, calibration is connected to Application::renderingOverlay() - applicationOverlay.renderOverlay(true); + applicationOverlay.renderOverlay(); DependencyManager::get()->prepare(); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); diff --git a/interface/src/main.cpp b/interface/src/main.cpp index b4486ceb2b..7d80b077b8 100644 --- a/interface/src/main.cpp +++ b/interface/src/main.cpp @@ -112,6 +112,7 @@ int main(int argc, const char* argv[]) { exitCode = app.exec(); } + OculusManager::deinit(); #ifdef Q_OS_WIN ReleaseMutex(mutex); #endif diff --git a/interface/src/ui/AddressBarDialog.cpp b/interface/src/ui/AddressBarDialog.cpp index b414f95240..49158265ba 100644 --- a/interface/src/ui/AddressBarDialog.cpp +++ b/interface/src/ui/AddressBarDialog.cpp @@ -1,148 +1,50 @@ // // AddressBarDialog.cpp -// interface/src/ui // -// Created by Stojce Slavkovski on 9/22/14. -// Copyright 2014 High Fidelity, Inc. +// Created by Bradley Austin Davis on 2015/04/14 +// 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 "AddressBarDialog.h" + #include -#include - -#include "AddressBarDialog.h" +#include "DependencyManager.h" #include "AddressManager.h" -#include "Application.h" -#include "MainWindow.h" -const QString ADDRESSBAR_GO_BUTTON_ICON = "images/address-bar-submit.svg"; -const QString ADDRESSBAR_GO_BUTTON_ACTIVE_ICON = "images/address-bar-submit-active.svg"; +QML_DIALOG_DEF(AddressBarDialog) -AddressBarDialog::AddressBarDialog(QWidget* parent) : - FramelessDialog(parent, 0, FramelessDialog::POSITION_TOP) -{ - setAttribute(Qt::WA_DeleteOnClose, false); - setupUI(); - +AddressBarDialog::AddressBarDialog(QQuickItem *parent) : QQuickItem(parent) { auto addressManager = DependencyManager::get(); - connect(addressManager.data(), &AddressManager::lookupResultIsOffline, this, &AddressBarDialog::displayAddressOfflineMessage); connect(addressManager.data(), &AddressManager::lookupResultIsNotFound, this, &AddressBarDialog::displayAddressNotFoundMessage); + connect(addressManager.data(), &AddressManager::lookupResultsFinished, this, &AddressBarDialog::hide); } -void AddressBarDialog::setupUI() { - - const QString DIALOG_STYLESHEET = "font-family: Helvetica, Arial, sans-serif;"; - const QString ADDRESSBAR_PLACEHOLDER = "Go to: domain, location, @user, /x,y,z"; - const QString ADDRESSBAR_STYLESHEET = "padding: 5px 10px; font-size: 20px;"; - - const int ADDRESSBAR_MIN_WIDTH = 200; - const int ADDRESSBAR_MAX_WIDTH = 615; - const int ADDRESSBAR_HEIGHT = 42; - const int ADDRESSBAR_STRETCH = 60; - - const int BUTTON_SPACER_SIZE = 5; - const int DEFAULT_SPACER_SIZE = 20; - const int ADDRESS_LAYOUT_RIGHT_MARGIN = 10; - - const int GO_BUTTON_SIZE = 42; - const int CLOSE_BUTTON_SIZE = 16; - const QString CLOSE_BUTTON_ICON = "styles/close.svg"; - - const int DIALOG_HEIGHT = 62; - const int DIALOG_INITIAL_WIDTH = 560; - - QSizePolicy sizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); - setSizePolicy(sizePolicy); - setMinimumSize(QSize(DIALOG_INITIAL_WIDTH, DIALOG_HEIGHT)); - setMaximumHeight(DIALOG_HEIGHT); - setStyleSheet(DIALOG_STYLESHEET); - _verticalLayout = new QVBoxLayout(this); - _verticalLayout->setContentsMargins(0, 0, 0, 0); - - _addressLayout = new QHBoxLayout(); - _addressLayout->setContentsMargins(0, 0, ADDRESS_LAYOUT_RIGHT_MARGIN, 0); - - _leftSpacer = new QSpacerItem(DEFAULT_SPACER_SIZE, - DEFAULT_SPACER_SIZE, - QSizePolicy::MinimumExpanding, - QSizePolicy::Minimum); - - _addressLayout->addItem(_leftSpacer); - - _addressLineEdit = new QLineEdit(this); - _addressLineEdit->setAttribute(Qt::WA_MacShowFocusRect, 0); - _addressLineEdit->setPlaceholderText(ADDRESSBAR_PLACEHOLDER); - QSizePolicy sizePolicyLineEdit(QSizePolicy::Preferred, QSizePolicy::Fixed); - sizePolicyLineEdit.setHorizontalStretch(ADDRESSBAR_STRETCH); - _addressLineEdit->setSizePolicy(sizePolicyLineEdit); - _addressLineEdit->setMinimumSize(QSize(ADDRESSBAR_MIN_WIDTH, ADDRESSBAR_HEIGHT)); - _addressLineEdit->setMaximumSize(QSize(ADDRESSBAR_MAX_WIDTH, ADDRESSBAR_HEIGHT)); - _addressLineEdit->setStyleSheet(ADDRESSBAR_STYLESHEET); - _addressLayout->addWidget(_addressLineEdit); - - _buttonSpacer = new QSpacerItem(BUTTON_SPACER_SIZE, BUTTON_SPACER_SIZE, QSizePolicy::Fixed, QSizePolicy::Minimum); - _addressLayout->addItem(_buttonSpacer); - - _goButton = new QPushButton(this); - _goButton->setSizePolicy(sizePolicy); - _goButton->setMinimumSize(QSize(GO_BUTTON_SIZE, GO_BUTTON_SIZE)); - _goButton->setMaximumSize(QSize(GO_BUTTON_SIZE, GO_BUTTON_SIZE)); - _goButton->setIcon(QIcon(PathUtils::resourcesPath() + ADDRESSBAR_GO_BUTTON_ICON)); - _goButton->setIconSize(QSize(GO_BUTTON_SIZE, GO_BUTTON_SIZE)); - _goButton->setDefault(true); - _goButton->setFlat(true); - _addressLayout->addWidget(_goButton); - - _rightSpacer = new QSpacerItem(DEFAULT_SPACER_SIZE, - DEFAULT_SPACER_SIZE, - QSizePolicy::MinimumExpanding, - QSizePolicy::Minimum); - - _addressLayout->addItem(_rightSpacer); - - _closeButton = new QPushButton(this); - _closeButton->setSizePolicy(sizePolicy); - _closeButton->setMinimumSize(QSize(CLOSE_BUTTON_SIZE, CLOSE_BUTTON_SIZE)); - _closeButton->setMaximumSize(QSize(CLOSE_BUTTON_SIZE, CLOSE_BUTTON_SIZE)); - QIcon icon(PathUtils::resourcesPath() + CLOSE_BUTTON_ICON); - _closeButton->setIcon(icon); - _closeButton->setIconSize(QSize(CLOSE_BUTTON_SIZE, CLOSE_BUTTON_SIZE)); - _closeButton->setFlat(true); - _addressLayout->addWidget(_closeButton, 0, Qt::AlignRight); - - _verticalLayout->addLayout(_addressLayout); - - connect(_goButton, &QPushButton::clicked, this, &AddressBarDialog::accept); - connect(_closeButton, &QPushButton::clicked, this, &QDialog::close); +void AddressBarDialog::hide() { + setEnabled(false); + setVisible(false); } -void AddressBarDialog::showEvent(QShowEvent* event) { - _goButton->setIcon(QIcon(PathUtils::resourcesPath() + ADDRESSBAR_GO_BUTTON_ICON)); - _addressLineEdit->setText(QString()); - _addressLineEdit->setFocus(); - FramelessDialog::showEvent(event); -} - -void AddressBarDialog::accept() { - if (!_addressLineEdit->text().isEmpty()) { - _goButton->setIcon(QIcon(PathUtils::resourcesPath() + ADDRESSBAR_GO_BUTTON_ACTIVE_ICON)); - auto addressManager = DependencyManager::get(); - connect(addressManager.data(), &AddressManager::lookupResultsFinished, this, &QDialog::hide); - addressManager->handleLookupString(_addressLineEdit->text()); +void AddressBarDialog::loadAddress(const QString & address) { + qDebug() << "Called LoadAddress with address " << address; + if (!address.isEmpty()) { + DependencyManager::get()->handleLookupString(address); } } +// TODO port to a QML based message box void AddressBarDialog::displayAddressOfflineMessage() { - QMessageBox::information(Application::getInstance()->getWindow(), "Address offline", - "That user or place is currently offline."); + QMessageBox::information(nullptr, "Address offline", + "That user or place is currently offline."); } +// TODO port to a QML based message box void AddressBarDialog::displayAddressNotFoundMessage() { - QMessageBox::information(Application::getInstance()->getWindow(), "Address not found", - "There is no address information for that user or place."); -} \ No newline at end of file + QMessageBox::information(nullptr, "Address not found", + "There is no address information for that user or place."); +} diff --git a/interface/src/ui/AddressBarDialog.h b/interface/src/ui/AddressBarDialog.h index dda807d6e2..00e55ceb10 100644 --- a/interface/src/ui/AddressBarDialog.h +++ b/interface/src/ui/AddressBarDialog.h @@ -1,9 +1,8 @@ // // AddressBarDialog.h -// interface/src/ui // -// Created by Stojce Slavkovski on 9/22/14. -// Copyright 2014 High Fidelity, Inc. +// Created by Bradley Austin Davis on 2015/04/14 +// 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 @@ -12,36 +11,25 @@ #ifndef hifi_AddressBarDialog_h #define hifi_AddressBarDialog_h -#include "FramelessDialog.h" +#pragma once +#include -#include -#include -#include -#include +#include "OffscreenUi.h" -class AddressBarDialog : public FramelessDialog { +class AddressBarDialog : public QQuickItem +{ Q_OBJECT + QML_DIALOG_DECL public: - AddressBarDialog(QWidget* parent); - -private: - void setupUI(); - void showEvent(QShowEvent* event); - - QVBoxLayout *_verticalLayout; - QHBoxLayout *_addressLayout; - QSpacerItem *_leftSpacer; - QSpacerItem *_rightSpacer; - QSpacerItem *_buttonSpacer; - QPushButton *_goButton; - QPushButton *_closeButton; - QLineEdit *_addressLineEdit; - -private slots: - void accept(); + AddressBarDialog(QQuickItem *parent = 0); + +protected: void displayAddressOfflineMessage(); void displayAddressNotFoundMessage(); + void hide(); + + Q_INVOKABLE void loadAddress(const QString & address); }; -#endif // hifi_AddressBarDialog_h +#endif diff --git a/interface/src/ui/ApplicationOverlay.cpp b/interface/src/ui/ApplicationOverlay.cpp index f08df229cc..5583790b13 100644 --- a/interface/src/ui/ApplicationOverlay.cpp +++ b/interface/src/ui/ApplicationOverlay.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include "AudioClient.h" #include "audio/AudioIOStatsRenderer.h" @@ -161,13 +162,27 @@ ApplicationOverlay::ApplicationOverlay() : _domainStatusBorder = geometryCache->allocateID(); _magnifierBorder = geometryCache->allocateID(); + // Once we move UI rendering and screen rendering to different + // threads, we need to use a sync object to deteremine when + // the current UI texture is no longer being read from, and only + // then release it back to the UI for re-use + auto offscreenUi = DependencyManager::get(); + connect(offscreenUi.data(), &OffscreenUi::textureUpdated, this, [&](GLuint textureId) { + auto offscreenUi = DependencyManager::get(); + offscreenUi->lockTexture(textureId); + assert(!glGetError()); + std::swap(_newUiTexture, textureId); + if (textureId) { + offscreenUi->releaseTexture(textureId); + } + }); } ApplicationOverlay::~ApplicationOverlay() { } // Renders the overlays either to a texture or to the screen -void ApplicationOverlay::renderOverlay(bool renderToTexture) { +void ApplicationOverlay::renderOverlay() { PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "ApplicationOverlay::displayOverlay()"); Overlays& overlays = qApp->getOverlays(); auto glCanvas = Application::getInstance()->getGLWidget(); @@ -183,11 +198,9 @@ void ApplicationOverlay::renderOverlay(bool renderToTexture) { glDisable(GL_LIGHTING); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - if (renderToTexture) { - _overlays.buildFramebufferObject(); - _overlays.bind(); - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - } + _overlays.buildFramebufferObject(); + _overlays.bind(); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glPushMatrix(); { const float NEAR_CLIP = -10000; @@ -218,9 +231,25 @@ void ApplicationOverlay::renderOverlay(bool renderToTexture) { glEnable(GL_LIGHTING); glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_CONSTANT_ALPHA, GL_ONE); - if (renderToTexture) { - _overlays.release(); + _overlays.release(); +} + +// A quick and dirty solution for compositing the old overlay +// texture with the new one +template +void with_each_texture(GLuint a, GLuint b, F f) { + glEnable(GL_TEXTURE_2D); + glActiveTexture(GL_TEXTURE0); + if (a) { + glBindTexture(GL_TEXTURE_2D, a); + f(); } + if (b) { + glBindTexture(GL_TEXTURE_2D, b); + f(); + } + glBindTexture(GL_TEXTURE_2D, 0); + glDisable(GL_TEXTURE_2D); } // Draws the FBO texture for the screen @@ -229,30 +258,24 @@ void ApplicationOverlay::displayOverlayTexture() { return; } auto glCanvas = Application::getInstance()->getGLWidget(); - - glEnable(GL_TEXTURE_2D); - glActiveTexture(GL_TEXTURE0); - _overlays.bindTexture(); - glMatrixMode(GL_PROJECTION); glPushMatrix(); { glLoadIdentity(); - glOrtho(0, glCanvas->getDeviceWidth(), glCanvas->getDeviceHeight(), 0, -1.0, 1.0); glDisable(GL_DEPTH_TEST); glDisable(GL_LIGHTING); - glEnable(GL_BLEND); - - glm::vec2 topLeft(0.0f, 0.0f); - glm::vec2 bottomRight(glCanvas->getDeviceWidth(), glCanvas->getDeviceHeight()); - glm::vec2 texCoordTopLeft(0.0f, 1.0f); - glm::vec2 texCoordBottomRight(1.0f, 0.0f); + if (_alpha < 1.0) { + glEnable(GL_BLEND); + } - DependencyManager::get()->renderQuad(topLeft, bottomRight, texCoordTopLeft, texCoordBottomRight, - glm::vec4(1.0f, 1.0f, 1.0f, _alpha)); - + with_each_texture(_overlays.getTexture(), _newUiTexture, [&] { + static const glm::vec2 topLeft(-1, 1); + static const glm::vec2 bottomRight(1, -1); + static const glm::vec2 texCoordTopLeft(0.0f, 1.0f); + static const glm::vec2 texCoordBottomRight(1.0f, 0.0f); + DependencyManager::get()->renderQuad(topLeft, bottomRight, texCoordTopLeft, texCoordBottomRight, + glm::vec4(1.0f, 1.0f, 1.0f, _alpha)); + }); } glPopMatrix(); - - glDisable(GL_TEXTURE_2D); } // Draws the FBO texture for Oculus rift. @@ -260,10 +283,7 @@ void ApplicationOverlay::displayOverlayTextureOculus(Camera& whichCamera) { if (_alpha == 0.0f) { return; } - glEnable(GL_TEXTURE_2D); - glActiveTexture(GL_TEXTURE0); - _overlays.bindTexture(); - + glEnable(GL_BLEND); glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_CONSTANT_ALPHA, GL_ONE); glEnable(GL_DEPTH_TEST); @@ -271,8 +291,8 @@ void ApplicationOverlay::displayOverlayTextureOculus(Camera& whichCamera) { glDisable(GL_LIGHTING); glEnable(GL_ALPHA_TEST); glAlphaFunc(GL_GREATER, 0.01f); - - + + //Update and draw the magnifiers MyAvatar* myAvatar = DependencyManager::get()->getMyAvatar(); const glm::quat& orientation = myAvatar->getOrientation(); @@ -303,8 +323,9 @@ void ApplicationOverlay::displayOverlayTextureOculus(Camera& whichCamera) { //Render magnifier, but dont show border for mouse magnifier glm::vec2 projection = screenToOverlay(glm::vec2(_reticlePosition[MOUSE].x(), _reticlePosition[MOUSE].y())); - - renderMagnifier(projection, _magSizeMult[i], i != MOUSE); + with_each_texture(_overlays.getTexture(), _newUiTexture, [&] { + renderMagnifier(projection, _magSizeMult[i], i != MOUSE); + }); } } @@ -319,12 +340,15 @@ void ApplicationOverlay::displayOverlayTextureOculus(Camera& whichCamera) { _overlays.buildVBO(_textureFov, _textureAspectRatio, 80, 80); } - _overlays.render(); + + with_each_texture(_overlays.getTexture(), _newUiTexture, [&] { + _overlays.render(); + }); + if (!Application::getInstance()->isMouseHidden()) { renderPointersOculus(myAvatar->getDefaultEyePosition()); } glDepthMask(GL_TRUE); - _overlays.releaseTexture(); glDisable(GL_TEXTURE_2D); glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_CONSTANT_ALPHA, GL_ONE); @@ -341,14 +365,10 @@ void ApplicationOverlay::displayOverlayTexture3DTV(Camera& whichCamera, float as MyAvatar* myAvatar = DependencyManager::get()->getMyAvatar(); const glm::vec3& viewMatrixTranslation = qApp->getViewMatrixTranslation(); - glActiveTexture(GL_TEXTURE0); - glEnable(GL_BLEND); glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_CONSTANT_ALPHA, GL_ONE); - _overlays.bindTexture(); glEnable(GL_DEPTH_TEST); glDisable(GL_LIGHTING); - glEnable(GL_TEXTURE_2D); glMatrixMode(GL_MODELVIEW); @@ -382,13 +402,15 @@ void ApplicationOverlay::displayOverlayTexture3DTV(Camera& whichCamera, float as GLfloat y = -halfQuadHeight; glDisable(GL_DEPTH_TEST); - DependencyManager::get()->renderQuad(glm::vec3(x, y + quadHeight, -distance), + with_each_texture(_overlays.getTexture(), _newUiTexture, [&] { + DependencyManager::get()->renderQuad(glm::vec3(x, y + quadHeight, -distance), glm::vec3(x + quadWidth, y + quadHeight, -distance), glm::vec3(x + quadWidth, y, -distance), glm::vec3(x, y, -distance), glm::vec2(0.0f, 1.0f), glm::vec2(1.0f, 1.0f), glm::vec2(1.0f, 0.0f), glm::vec2(0.0f, 0.0f), overlayColor); + }); auto glCanvas = Application::getInstance()->getGLWidget(); if (_crosshairTexture == 0) { @@ -993,14 +1015,6 @@ void ApplicationOverlay::TexturedHemisphere::release() { _framebufferObject->release(); } -void ApplicationOverlay::TexturedHemisphere::bindTexture() { - glBindTexture(GL_TEXTURE_2D, _framebufferObject->texture()); -} - -void ApplicationOverlay::TexturedHemisphere::releaseTexture() { - glBindTexture(GL_TEXTURE_2D, 0); -} - void ApplicationOverlay::TexturedHemisphere::buildVBO(const float fov, const float aspectRatio, const int slices, @@ -1099,14 +1113,14 @@ void ApplicationOverlay::TexturedHemisphere::buildFramebufferObject() { } _framebufferObject = new QOpenGLFramebufferObject(size, QOpenGLFramebufferObject::Depth); - bindTexture(); + glBindTexture(GL_TEXTURE_2D, getTexture()); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); GLfloat borderColor[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor); - releaseTexture(); + glBindTexture(GL_TEXTURE_2D, 0); } //Renders a hemisphere with texture coordinates. @@ -1137,6 +1151,10 @@ void ApplicationOverlay::TexturedHemisphere::render() { glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); } +GLuint ApplicationOverlay::TexturedHemisphere::getTexture() { + return _framebufferObject->texture(); +} + glm::vec2 ApplicationOverlay::directionToSpherical(glm::vec3 direction) const { glm::vec2 result; // Compute yaw diff --git a/interface/src/ui/ApplicationOverlay.h b/interface/src/ui/ApplicationOverlay.h index cc424d0c8f..cc4188e8ef 100644 --- a/interface/src/ui/ApplicationOverlay.h +++ b/interface/src/ui/ApplicationOverlay.h @@ -28,7 +28,7 @@ public: ApplicationOverlay(); ~ApplicationOverlay(); - void renderOverlay(bool renderToTexture = false); + void renderOverlay(); void displayOverlayTexture(); void displayOverlayTextureOculus(Camera& whichCamera); void displayOverlayTexture3DTV(Camera& whichCamera, float aspectRatio, float fov); @@ -75,8 +75,7 @@ private: void bind(); void release(); - void bindTexture(); - void releaseTexture(); + GLuint getTexture(); void buildFramebufferObject(); void buildVBO(const float fov, const float aspectRatio, const int slices, const int stacks); @@ -122,6 +121,9 @@ private: float _trailingAudioLoudness; GLuint _crosshairTexture; + // TODO, move divide up the rendering, displaying and input handling + // facilities of this class + GLuint _newUiTexture{ 0 }; int _reticleQuad; int _magnifierQuad; diff --git a/interface/src/ui/DialogsManager.cpp b/interface/src/ui/DialogsManager.cpp index 701ceb0189..3c54c26379 100644 --- a/interface/src/ui/DialogsManager.cpp +++ b/interface/src/ui/DialogsManager.cpp @@ -9,6 +9,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include "DialogsManager.h" + #include #include @@ -28,14 +30,9 @@ #include "PreferencesDialog.h" #include "ScriptEditorWindow.h" -#include "DialogsManager.h" void DialogsManager::toggleAddressBar() { - maybeCreateDialog(_addressBarDialog); - - if (!_addressBarDialog->isVisible()) { - _addressBarDialog->show(); - } + AddressBarDialog::toggle(); } void DialogsManager::toggleDiskCacheEditor() { @@ -44,13 +41,11 @@ void DialogsManager::toggleDiskCacheEditor() { } void DialogsManager::toggleLoginDialog() { - maybeCreateDialog(_loginDialog); - _loginDialog->toggleQAction(); + LoginDialog::toggleAction(); } void DialogsManager::showLoginDialog() { - maybeCreateDialog(_loginDialog); - _loginDialog->showLoginForCurrentDomain(); + LoginDialog::show(); } void DialogsManager::octreeStatsDetails() { @@ -170,3 +165,4 @@ void DialogsManager::showIRCLink() { _ircInfoBox->raise(); } + diff --git a/interface/src/ui/FramelessDialog.cpp b/interface/src/ui/FramelessDialog.cpp deleted file mode 100644 index bae6217083..0000000000 --- a/interface/src/ui/FramelessDialog.cpp +++ /dev/null @@ -1,166 +0,0 @@ -// -// FramelessDialog.cpp -// interface/src/ui -// -// Created by Stojce Slavkovski on 2/20/14. -// Copyright 2014 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#include -#include - -#include - -#include "Application.h" -#include "FramelessDialog.h" -#include "Menu.h" - -const int RESIZE_HANDLE_WIDTH = 7; - -FramelessDialog::FramelessDialog(QWidget *parent, Qt::WindowFlags flags, Position position) : - QDialog(parent, flags | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint), - _allowResize(true), - _isResizing(false), - _resizeInitialWidth(0), - _selfHidden(false), - _position(position), - _hideOnBlur(true) { - - setAttribute(Qt::WA_DeleteOnClose); - - // handle rezize and move events - parentWidget()->installEventFilter(this); - - // handle minimize, restore and focus events - Application::getInstance()->installEventFilter(this); -} - -bool FramelessDialog::eventFilter(QObject* sender, QEvent* event) { - switch (event->type()) { - case QEvent::Move: - if (sender == parentWidget()) { - resizeAndPosition(false); - } - break; - case QEvent::Resize: - if (sender == parentWidget()) { - resizeAndPosition(false); - } - break; - case QEvent::WindowStateChange: - if (_hideOnBlur && parentWidget()->isMinimized()) { - if (isVisible()) { - _selfHidden = true; - setHidden(true); - } - } else if (_selfHidden) { - _selfHidden = false; - setHidden(false); - } - break; - case QEvent::ApplicationDeactivate: - // hide on minimize and focus lost - if (_hideOnBlur && isVisible()) { - _selfHidden = true; - setHidden(true); - } - break; - case QEvent::ApplicationActivate: - if (_selfHidden) { - _selfHidden = false; - setHidden(false); - } - break; - default: - break; - } - - return false; -} - -void FramelessDialog::setStyleSheetFile(const QString& fileName) { - QFile globalStyleSheet(PathUtils::resourcesPath() + "styles/global.qss"); - QFile styleSheet(PathUtils::resourcesPath() + fileName); - if (styleSheet.open(QIODevice::ReadOnly) && globalStyleSheet.open(QIODevice::ReadOnly) ) { - QDir::setCurrent(PathUtils::resourcesPath()); - setStyleSheet(globalStyleSheet.readAll() + styleSheet.readAll()); - } -} - -void FramelessDialog::showEvent(QShowEvent* event) { - resizeAndPosition(); - QDialog::showEvent(event); -} - -void FramelessDialog::resizeAndPosition(bool resizeParent) { - QRect parentGeometry = Application::getInstance()->getDesirableApplicationGeometry(); - QSize parentSize = parentGeometry.size(); - - // keep full app height or width depending on position - if (_position == POSITION_LEFT || _position == POSITION_RIGHT) { - setFixedHeight(parentSize.height()); - } else { - setFixedWidth(parentSize.width()); - } - - // resize parrent if width is smaller than this dialog - if (resizeParent && parentSize.width() < size().width()) { - parentWidget()->resize(size().width(), parentSize.height()); - } - - if (_position == POSITION_LEFT || _position == POSITION_TOP) { - // move to upper left corner - move(parentGeometry.topLeft()); - } else if (_position == POSITION_RIGHT) { - // move to upper right corner - QPoint pos = parentGeometry.topRight(); - pos.setX(pos.x() - size().width()); - move(pos); - } - repaint(); -} - -void FramelessDialog::mousePressEvent(QMouseEvent* mouseEvent) { - if (_allowResize && mouseEvent->button() == Qt::LeftButton) { - if (_position == POSITION_LEFT || _position == POSITION_RIGHT) { - bool hitLeft = (_position == POSITION_LEFT) && (abs(mouseEvent->pos().x() - size().width()) < RESIZE_HANDLE_WIDTH); - bool hitRight = (_position == POSITION_RIGHT) && (mouseEvent->pos().x() < RESIZE_HANDLE_WIDTH); - if (hitLeft || hitRight) { - _isResizing = true; - _resizeInitialWidth = size().width(); - setCursor(Qt::SizeHorCursor); - } - } else { - bool hitTop = (_position == POSITION_TOP) && (abs(mouseEvent->pos().y() - size().height()) < RESIZE_HANDLE_WIDTH); - if (hitTop) { - _isResizing = true; - _resizeInitialWidth = size().height(); - setCursor(Qt::SizeHorCursor); - } - } - } -} - -void FramelessDialog::mouseReleaseEvent(QMouseEvent* mouseEvent) { - unsetCursor(); - _isResizing = false; -} - -void FramelessDialog::mouseMoveEvent(QMouseEvent* mouseEvent) { - if (_isResizing) { - if (_position == POSITION_LEFT) { - resize(mouseEvent->pos().x(), size().height()); - } else if (_position == POSITION_RIGHT) { - setUpdatesEnabled(false); - resize(_resizeInitialWidth - mouseEvent->pos().x(), size().height()); - resizeAndPosition(); - _resizeInitialWidth = size().width(); - setUpdatesEnabled(true); - } else if (_position == POSITION_TOP) { - resize(size().width(), mouseEvent->pos().y()); - } - } -} diff --git a/interface/src/ui/FramelessDialog.h b/interface/src/ui/FramelessDialog.h deleted file mode 100644 index 32451f746d..0000000000 --- a/interface/src/ui/FramelessDialog.h +++ /dev/null @@ -1,50 +0,0 @@ -// -// FramelessDialog.h -// interface/src/ui -// -// Created by Stojce Slavkovski on 2/20/14. -// Copyright 2014 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - - -#ifndef hifi_FramelessDialog_h -#define hifi_FramelessDialog_h - -#include - -class FramelessDialog : public QDialog { - Q_OBJECT - -public: - enum Position { POSITION_LEFT, POSITION_RIGHT, POSITION_TOP }; - - FramelessDialog(QWidget* parent, Qt::WindowFlags flags = 0, Position position = POSITION_LEFT); - void setStyleSheetFile(const QString& fileName); - void setAllowResize(bool allowResize) { _allowResize = allowResize; } - bool getAllowResize() { return _allowResize; } - void setHideOnBlur(bool hideOnBlur) { _hideOnBlur = hideOnBlur; } - bool getHideOnBlur() { return _hideOnBlur; } - void resizeAndPosition(bool resizeParent = true); - -protected: - virtual void mouseMoveEvent(QMouseEvent* mouseEvent); - virtual void mousePressEvent(QMouseEvent* mouseEvent); - virtual void mouseReleaseEvent(QMouseEvent* mouseEvent); - virtual void showEvent(QShowEvent* event); - - bool eventFilter(QObject* sender, QEvent* event); - -private: - bool _allowResize; - bool _isResizing; - int _resizeInitialWidth; - bool _selfHidden; ///< true when the dialog itself because of a window event (deactivation or minimization) - Position _position; - bool _hideOnBlur; - -}; - -#endif // hifi_FramelessDialog_h diff --git a/interface/src/ui/LoginDialog.cpp b/interface/src/ui/LoginDialog.cpp index b134f7f230..3b164041fa 100644 --- a/interface/src/ui/LoginDialog.cpp +++ b/interface/src/ui/LoginDialog.cpp @@ -1,120 +1,35 @@ // +// // LoginDialog.cpp -// interface/src/ui // -// Created by Ryan Huffman on 4/23/14. -// Copyright 2014 High Fidelity, Inc. +// Created by Bradley Austin Davis on 2015/04/14 +// 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 -#include -#include - -#include -#include - -#include "Application.h" -#include "Menu.h" -#include "AccountManager.h" -#include "ui_loginDialog.h" #include "LoginDialog.h" -#include "UIUtil.h" -const QString CREATE_ACCOUNT_URL = NetworkingConstants::METAVERSE_SERVER_URL.toString() + "/signup"; -const QString FORGOT_PASSWORD_URL = NetworkingConstants::METAVERSE_SERVER_URL.toString() + "/users/password/new"; +#include "DependencyManager.h" +#include "AccountManager.h" +#include "Menu.h" +#include -LoginDialog::LoginDialog(QWidget* parent) : - FramelessDialog(parent, 0, FramelessDialog::POSITION_TOP), - _ui(new Ui::LoginDialog) { - - _ui->setupUi(this); - reset(); - - setAttribute(Qt::WA_DeleteOnClose, false); +QML_DIALOG_DEF(LoginDialog) +LoginDialog::LoginDialog(QQuickItem *parent) : QQuickItem(parent), _rootUrl(NetworkingConstants::METAVERSE_SERVER_URL.toString()) { connect(&AccountManager::getInstance(), &AccountManager::loginComplete, - this, &LoginDialog::handleLoginCompleted); + this, &LoginDialog::handleLoginCompleted); connect(&AccountManager::getInstance(), &AccountManager::loginFailed, - this, &LoginDialog::handleLoginFailed); - connect(_ui->loginButton, &QPushButton::clicked, - this, &LoginDialog::handleLoginClicked); - connect(_ui->closeButton, &QPushButton::clicked, - this, &LoginDialog::close); - - UIUtil::scaleWidgetFontSizes(this); - _ui->accountLabel->setText(_ui->accountLabel->text().arg(CREATE_ACCOUNT_URL, FORGOT_PASSWORD_URL)); - - // Initialize toggle connection - toggleQAction(); -}; - -LoginDialog::~LoginDialog() { - delete _ui; -}; - -void LoginDialog::reset() { - _ui->errorLabel->hide(); - _ui->emailLineEdit->setFocus(); - _ui->logoLabel->setPixmap(QPixmap(PathUtils::resourcesPath() + "images/hifi-logo.svg")); - _ui->loginButton->setIcon(QIcon(PathUtils::resourcesPath() + "images/login.svg")); - _ui->closeButton->setIcon(QIcon(PathUtils::resourcesPath() + "images/close.svg")); - _ui->infoLabel->setVisible(false); - _ui->errorLabel->setVisible(false); - - _ui->emailLineEdit->setText(""); - _ui->passwordLineEdit->setText(""); - _ui->loginArea->setDisabled(false); + this, &LoginDialog::handleLoginFailed); } -void LoginDialog::handleLoginCompleted(const QUrl& authURL) { - reset(); - close(); -}; - -void LoginDialog::handleLoginFailed() { - _ui->infoLabel->setVisible(false); - _ui->errorLabel->setVisible(true); - - _ui->errorLabel->show(); - _ui->loginArea->setDisabled(false); - - // Move focus to password and select the entire line - _ui->passwordLineEdit->setFocus(); - _ui->passwordLineEdit->setSelection(0, _ui->emailLineEdit->maxLength()); -}; - -void LoginDialog::handleLoginClicked() { - // If the email or password inputs are empty, move focus to them, otherwise attempt to login. - if (_ui->emailLineEdit->text().isEmpty()) { - _ui->emailLineEdit->setFocus(); - } else if (_ui->passwordLineEdit->text().isEmpty()) { - _ui->passwordLineEdit->setFocus(); - } else { - _ui->infoLabel->setVisible(true); - _ui->errorLabel->setVisible(false); - - _ui->loginArea->setDisabled(true); - AccountManager::getInstance().requestAccessToken(_ui->emailLineEdit->text(), _ui->passwordLineEdit->text()); - } -}; - -void LoginDialog::moveEvent(QMoveEvent* event) { - // Modal dialogs seemed to get repositioned automatically. Combat this by moving the window if needed. - resizeAndPosition(); -}; - - -void LoginDialog::toggleQAction() { +void LoginDialog::toggleAction() { AccountManager& accountManager = AccountManager::getInstance(); QAction* loginAction = Menu::getInstance()->getActionForOption(MenuOption::Login); Q_CHECK_PTR(loginAction); - disconnect(loginAction, 0, 0, 0); - + if (accountManager.isLoggedIn()) { // change the menu item to logout loginAction->setText("Logout " + accountManager.getAccountInfo().getUsername()); @@ -122,11 +37,40 @@ void LoginDialog::toggleQAction() { } else { // change the menu item to login loginAction->setText("Login"); - connect(loginAction, &QAction::triggered, this, &LoginDialog::showLoginForCurrentDomain); + connect(loginAction, &QAction::triggered, &LoginDialog::show); } } -void LoginDialog::showLoginForCurrentDomain() { - show(); - resizeAndPosition(false); +void LoginDialog::handleLoginCompleted(const QUrl& authURL) { + setEnabled(false); + setVisible(false); +} + +void LoginDialog::handleLoginFailed() { + setStatusText("Invalid username or password.< / font>"); +} + +void LoginDialog::setStatusText(const QString &a) { + if (a != _statusText) { + _statusText = a; + emit statusTextChanged(); + } +} + +QString LoginDialog::statusText() const { + return _statusText; +} + +QString LoginDialog::rootUrl() const { + return _rootUrl; +} + +void LoginDialog::login(const QString & username, const QString & password) { + qDebug() << "Attempting to login " << username; + setStatusText("Authenticating..."); + AccountManager::getInstance().requestAccessToken(username, password); +} + +void LoginDialog::openUrl(const QString & url) { + qDebug() << url; } diff --git a/interface/src/ui/LoginDialog.h b/interface/src/ui/LoginDialog.h index d5384c4625..3c9a98a9a4 100644 --- a/interface/src/ui/LoginDialog.h +++ b/interface/src/ui/LoginDialog.h @@ -1,9 +1,8 @@ // // LoginDialog.h -// interface/src/ui // -// Created by Ryan Huffman on 4/23/14. -// Copyright 2014 High Fidelity, Inc. +// Created by Bradley Austin Davis on 2015/04/14 +// 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 @@ -12,35 +11,41 @@ #ifndef hifi_LoginDialog_h #define hifi_LoginDialog_h -#include -#include "FramelessDialog.h" +#pragma once +#include -namespace Ui { - class LoginDialog; -} +#include "OffscreenUi.h" -class LoginDialog : public FramelessDialog { +class LoginDialog : public QQuickItem +{ Q_OBJECT + QML_DIALOG_DECL + + Q_PROPERTY(QString statusText READ statusText WRITE setStatusText NOTIFY statusTextChanged) + Q_PROPERTY(QString rootUrl READ rootUrl) public: - LoginDialog(QWidget* parent); - ~LoginDialog(); + static void toggleAction(); -public slots: - void toggleQAction(); - void showLoginForCurrentDomain(); - -protected slots: - void reset(); - void handleLoginClicked(); + LoginDialog(QQuickItem *parent = 0); + + void setStatusText(const QString & a); + QString statusText() const; + + QString rootUrl() const; + +signals: + void statusTextChanged(); + +protected: void handleLoginCompleted(const QUrl& authURL); void handleLoginFailed(); -protected: - void moveEvent(QMoveEvent* event); - + Q_INVOKABLE void login(const QString & username, const QString & password); + Q_INVOKABLE void openUrl(const QString & url); private: - Ui::LoginDialog* _ui = nullptr; + QString _statusText; + const QString _rootUrl; }; #endif // hifi_LoginDialog_h diff --git a/libraries/render-utils/CMakeLists.txt b/libraries/render-utils/CMakeLists.txt index caabff44cf..fd4d447b25 100644 --- a/libraries/render-utils/CMakeLists.txt +++ b/libraries/render-utils/CMakeLists.txt @@ -6,7 +6,7 @@ AUTOSCRIBE_SHADER_LIB(gpu model) qt5_add_resources(QT_RESOURCES_FILE "${CMAKE_CURRENT_SOURCE_DIR}/res/fonts/fonts.qrc") # use setup_hifi_library macro to setup our project and link appropriate Qt modules -setup_hifi_library(Widgets OpenGL Network Script) +setup_hifi_library(Widgets OpenGL Network Qml Quick Script) add_dependency_external_projects(glm) find_package(GLM REQUIRED) diff --git a/libraries/render-utils/src/FboCache.cpp b/libraries/render-utils/src/FboCache.cpp new file mode 100644 index 0000000000..fdd29d20d8 --- /dev/null +++ b/libraries/render-utils/src/FboCache.cpp @@ -0,0 +1,98 @@ +// +// OffscreenGlCanvas.cpp +// interface/src/renderer +// +// Created by Bradley Austin Davis on 2014/04/09. +// 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 "FboCache.h" + +#include +#include +#include "ThreadHelpers.h" + +FboCache::FboCache() { + // Why do we even HAVE that lever? +} + +void FboCache::lockTexture(int texture) { + withLock(_lock, [&] { + Q_ASSERT(_fboMap.count(texture)); + if (!_fboLocks.count(texture)) { + Q_ASSERT(_readyFboQueue.front()->texture() == texture); + _readyFboQueue.pop_front(); + _fboLocks[texture] = 1; + } else { + _fboLocks[texture]++; + } + }); +} + +void FboCache::releaseTexture(int texture) { + withLock(_lock, [&] { + Q_ASSERT(_fboMap.count(texture)); + Q_ASSERT(_fboLocks.count(texture)); + int newLockCount = --_fboLocks[texture]; + if (!newLockCount) { + auto fbo = _fboMap[texture].data(); + if (fbo->size() != _size) { + // Move the old FBO to the destruction queue. + // We can't destroy the FBO here because we might + // not be on the right thread or have the context active + _destroyFboQueue.push_back(_fboMap[texture]); + _fboMap.remove(texture); + } else { + _readyFboQueue.push_back(fbo); + } + _fboLocks.remove(texture); + } + }); +} + +QOpenGLFramebufferObject* FboCache::getReadyFbo() { + QOpenGLFramebufferObject* result = nullptr; + withLock(_lock, [&] { + // Delete any FBOs queued for deletion + _destroyFboQueue.clear(); + + if (_readyFboQueue.empty()) { + qDebug() << "Building new offscreen FBO number " << _fboMap.size() + 1; + result = new QOpenGLFramebufferObject(_size, QOpenGLFramebufferObject::CombinedDepthStencil); + _fboMap[result->texture()] = QSharedPointer(result); + _readyFboQueue.push_back(result); + } else { + result = _readyFboQueue.front(); + } + }); + return result; +} + +void FboCache::setSize(const QSize & newSize) { + if (_size == newSize) { + return; + } + _size = newSize; + withLock(_lock, [&] { + // Clear out any fbos with the old id + _readyFboQueue.clear(); + + QSet outdatedFbos; + // FBOs that are locked will be removed as they are unlocked + foreach(int texture, _fboMap.keys()) { + if (!_fboLocks.count(texture)) { + outdatedFbos.insert(texture); + } + } + // Implicitly deletes the FBO via the shared pointer destruction mechanism + foreach(int texture, outdatedFbos) { + _fboMap.remove(texture); + } + }); +} + + diff --git a/libraries/render-utils/src/FboCache.h b/libraries/render-utils/src/FboCache.h new file mode 100644 index 0000000000..bbbb4a943e --- /dev/null +++ b/libraries/render-utils/src/FboCache.h @@ -0,0 +1,50 @@ +// +// OffscreenGlCanvas.h +// interface/src/renderer +// +// Created by Bradley Austin Davis on 2014/04/09. +// 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 +// +#pragma once +#ifndef hifi_FboCache_h +#define hifi_FboCache_h + +#include +#include +#include +#include +#include + +class QOpenGLFramebufferObject; + +class FboCache : public QObject { +public: + FboCache(); + + // setSize() and getReadyFbo() must consitently be called from only a single + // thread. Additionally, it is the caller's responsibility to ensure that + // the appropriate OpenGL context is active when doing so. + + // Important.... textures are sharable resources, but FBOs ARE NOT. + void setSize(const QSize & newSize); + QOpenGLFramebufferObject* getReadyFbo(); + + // These operations are thread safe and require no OpenGL context. They manipulate the + // internal locks and pointers but execute no OpenGL opreations. + void lockTexture(int texture); + void releaseTexture(int texture); + +protected: + QMap> _fboMap; + QMap _fboLocks; + QQueue _readyFboQueue; + QQueue> _destroyFboQueue; + QMutex _lock; + QSize _size; + +}; + +#endif // hifi_FboCache_h diff --git a/libraries/render-utils/src/OffscreenGlCanvas.cpp b/libraries/render-utils/src/OffscreenGlCanvas.cpp new file mode 100644 index 0000000000..694fa68a5a --- /dev/null +++ b/libraries/render-utils/src/OffscreenGlCanvas.cpp @@ -0,0 +1,43 @@ +// +// OffscreenGlCanvas.cpp +// interface/src/renderer +// +// Created by Bradley Austin Davis on 2014/04/09. +// 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 "OffscreenGlCanvas.h" + +OffscreenGlCanvas::OffscreenGlCanvas() { +} + +void OffscreenGlCanvas::create(QOpenGLContext * sharedContext) { + QSurfaceFormat format; + format.setDepthBufferSize(16); + format.setStencilBufferSize(8); + format.setMajorVersion(4); + format.setMinorVersion(1); + format.setProfile(QSurfaceFormat::OpenGLContextProfile::CompatibilityProfile); + + _context.setFormat(format); + if (nullptr != sharedContext) { + _context.setShareContext(sharedContext); + } + _context.create(); + + _offscreenSurface.setFormat(_context.format()); + _offscreenSurface.create(); +} + +bool OffscreenGlCanvas::makeCurrent() { + return _context.makeCurrent(&_offscreenSurface); +} + +void OffscreenGlCanvas::doneCurrent() { + _context.doneCurrent(); +} + diff --git a/libraries/render-utils/src/OffscreenGlCanvas.h b/libraries/render-utils/src/OffscreenGlCanvas.h new file mode 100644 index 0000000000..1b2f15f690 --- /dev/null +++ b/libraries/render-utils/src/OffscreenGlCanvas.h @@ -0,0 +1,31 @@ +// +// OffscreenGlCanvas.h +// interface/src/renderer +// +// Created by Bradley Austin Davis on 2014/04/09. +// 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 +// +#pragma once +#ifndef hifi_OffscreenGlCanvas_h +#define hifi_OffscreenGlCanvas_h + +#include +#include + +class OffscreenGlCanvas : public QObject { +public: + OffscreenGlCanvas(); + void create(QOpenGLContext * sharedContext = nullptr); + bool makeCurrent(); + void doneCurrent(); + +protected: + QOpenGLContext _context; + QOffscreenSurface _offscreenSurface; + +}; + +#endif // hifi_OffscreenGlCanvas_h diff --git a/libraries/render-utils/src/OffscreenUi.cpp b/libraries/render-utils/src/OffscreenUi.cpp new file mode 100644 index 0000000000..cb57d86412 --- /dev/null +++ b/libraries/render-utils/src/OffscreenUi.cpp @@ -0,0 +1,325 @@ +#include "OffscreenUi.h" +#include +#include +#include + +OffscreenUi::OffscreenUi() { +} + +OffscreenUi::~OffscreenUi() { + // Make sure the context is current while doing cleanup. Note that we use the + // offscreen surface here because passing 'this' at this point is not safe: the + // underlying platform window may already be destroyed. To avoid all the trouble, use + // another surface that is valid for sure. + makeCurrent(); + + // Delete the render control first since it will free the scenegraph resources. + // Destroy the QQuickWindow only afterwards. + delete _renderControl; + + delete _qmlComponent; + delete _quickWindow; + delete _qmlEngine; + + doneCurrent(); +} + +void OffscreenUi::create(QOpenGLContext * shareContext) { + OffscreenGlCanvas::create(shareContext); + + makeCurrent(); + + // Create a QQuickWindow that is associated with out render control. Note that this + // window never gets created or shown, meaning that it will never get an underlying + // native (platform) window. + QQuickWindow::setDefaultAlphaBuffer(true); + _quickWindow = new QQuickWindow(_renderControl); + _quickWindow->setColor(QColor(255, 255, 255, 0)); + _quickWindow->setFlags(_quickWindow->flags() | static_cast(Qt::WA_TranslucentBackground)); + // Create a QML engine. + _qmlEngine = new QQmlEngine; + if (!_qmlEngine->incubationController()) + _qmlEngine->setIncubationController(_quickWindow->incubationController()); + + // When Quick says there is a need to render, we will not render immediately. Instead, + // a timer with a small interval is used to get better performance. + _updateTimer.setSingleShot(true); + _updateTimer.setInterval(5); + connect(&_updateTimer, &QTimer::timeout, this, &OffscreenUi::updateQuick); + + // Now hook up the signals. For simplicy we don't differentiate between + // renderRequested (only render is needed, no sync) and sceneChanged (polish and sync + // is needed too). + connect(_renderControl, &QQuickRenderControl::renderRequested, this, &OffscreenUi::requestRender); + connect(_renderControl, &QQuickRenderControl::sceneChanged, this, &OffscreenUi::requestUpdate); + connect(_quickWindow, &QQuickWindow::focusObjectChanged, this, [this](QObject *object){ + OffscreenUi * p = this; + qDebug() << "Focus changed to " << object; + }); + _quickWindow->focusObject(); + + _qmlComponent = new QQmlComponent(_qmlEngine); + + // Initialize the render control and our OpenGL resources. + makeCurrent(); + _renderControl->initialize(&_context); +} + +void OffscreenUi::resize(const QSize & newSize) { + makeCurrent(); + + // Clear out any fbos with the old size + _fboCache.setSize(newSize); + + // Update our members + if (_rootItem) { + _rootItem->setSize(newSize); + } + + if (_quickWindow) { + _quickWindow->setGeometry(QRect(QPoint(), newSize)); + } + + doneCurrent(); +} + +QQmlContext * OffscreenUi::qmlContext() { + if (nullptr == _rootItem) { + return _qmlComponent->creationContext(); + } + return QQmlEngine::contextForObject(_rootItem); +} + +void OffscreenUi::loadQml(const QUrl & qmlSource, std::function f) { + _qmlComponent->loadUrl(qmlSource); + if (_qmlComponent->isLoading()) + connect(_qmlComponent, &QQmlComponent::statusChanged, this, &OffscreenUi::finishQmlLoad); + else + finishQmlLoad(); +} + +void OffscreenUi::requestUpdate() { + _polish = true; + if (!_updateTimer.isActive()) + _updateTimer.start(); +} + +void OffscreenUi::requestRender() { + if (!_updateTimer.isActive()) + _updateTimer.start(); +} + +void OffscreenUi::finishQmlLoad() { + disconnect(_qmlComponent, &QQmlComponent::statusChanged, this, &OffscreenUi::finishQmlLoad); + if (_qmlComponent->isError()) { + QList errorList = _qmlComponent->errors(); + foreach(const QQmlError &error, errorList) { + qWarning() << error.url() << error.line() << error; + } + return; + } + + QObject *rootObject = _qmlComponent->create(); + if (_qmlComponent->isError()) { + QList errorList = _qmlComponent->errors(); + foreach(const QQmlError &error, errorList) + qWarning() << error.url() << error.line() << error; + qFatal("Unable to finish loading QML"); + return; + } + + _rootItem = qobject_cast(rootObject); + if (!_rootItem) { + qWarning("run: Not a QQuickItem"); + delete rootObject; + qFatal("Unable to find root QQuickItem"); + return; + } + + // Make sure we can assign focus to the root item (critical for + // supporting keyboard shortcuts) + _rootItem->setFlag(QQuickItem::ItemIsFocusScope, true); + // The root item is ready. Associate it with the window. + _rootItem->setParentItem(_quickWindow->contentItem()); + _rootItem->setSize(_quickWindow->renderTargetSize()); + qDebug() << "Finished setting up QML provider"; +} + + +void OffscreenUi::updateQuick() { + if (_paused) { + return; + } + if (!makeCurrent()) + return; + + // Polish, synchronize and render the next frame (into our fbo). In this example + // everything happens on the same thread and therefore all three steps are performed + // in succession from here. In a threaded setup the render() call would happen on a + // separate thread. + if (_polish) { + _renderControl->polishItems(); + _renderControl->sync(); + _polish = false; + } + + QOpenGLFramebufferObject* fbo = _fboCache.getReadyFbo(); + + _quickWindow->setRenderTarget(fbo); + fbo->bind(); + + glClearColor(0, 0, 0, 1); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + _renderControl->render(); + + Q_ASSERT(!glGetError()); + + _quickWindow->resetOpenGLState(); + + QOpenGLFramebufferObject::bindDefault(); + // Force completion of all the operations before we emit the texture as being ready for use + glFinish(); + + emit textureUpdated(fbo->texture()); +} + +QPointF OffscreenUi::mapWindowToUi(const QPointF & p, QObject * dest) { + vec2 sourceSize; + if (dynamic_cast(dest)) { + sourceSize = toGlm(((QWidget*)dest)->size()); + } else if (dynamic_cast(dest)) { + sourceSize = toGlm(((QWindow*)dest)->size()); + } + vec2 pos = toGlm(p); + pos /= sourceSize; + pos *= vec2(toGlm(_quickWindow->renderTargetSize())); + return QPointF(pos.x, pos.y); +} + +/////////////////////////////////////////////////////// +// +// Event handling customization +// + +bool OffscreenUi::eventFilter(QObject * dest, QEvent * e) { + // Only intercept events while we're in an active state + if (_paused) { + return false; + } + + // Don't intercept our own events, or we enter an infinite recursion + if (dest == _quickWindow) { + return false; + } + + switch (e->type()) { + case QEvent::Resize: + { + QResizeEvent * re = (QResizeEvent *)e; + QGLWidget * widget = dynamic_cast(dest); + if (widget) { + this->resize(re->size()); + } + return false; + } + + case QEvent::KeyPress: + case QEvent::KeyRelease: + { + e->ignore(); + if (QApplication::sendEvent(_quickWindow, e)) { + return e->isAccepted(); + } + } + break; + + case QEvent::Wheel: + { + QWheelEvent * we = (QWheelEvent*)e; + QWheelEvent mappedEvent(mapWindowToUi(we->pos(), dest), we->delta(), we->buttons(), we->modifiers(), we->orientation()); + QCoreApplication::sendEvent(_quickWindow, &mappedEvent); + return true; + } + break; + + // Fall through + case QEvent::MouseButtonDblClick: + case QEvent::MouseButtonPress: + case QEvent::MouseButtonRelease: + case QEvent::MouseMove: + { + QMouseEvent * me = (QMouseEvent *)e; + QPointF originalPos = me->localPos(); + QPointF transformedPos = _mouseTranslator(originalPos); + QMouseEvent mappedEvent(e->type(), mapWindowToUi(transformedPos, dest), me->screenPos(), me->button(), me->buttons(), me->modifiers()); + QCoreApplication::sendEvent(_quickWindow, &mappedEvent); + return QObject::event(e); + } + + default: break; + } + + return false; +} + +void OffscreenUi::lockTexture(int texture) { + _fboCache.lockTexture(texture); +} + +void OffscreenUi::releaseTexture(int texture) { + _fboCache.releaseTexture(texture); +} + +void OffscreenUi::pause() { + _paused = true; +} + +void OffscreenUi::resume() { + _paused = false; + requestRender(); +} + +bool OffscreenUi::isPaused() const { + return _paused; +} + +void OffscreenUi::setProxyWindow(QWindow * window) { + _renderControl->_renderWindow = window; +} + +void OffscreenUi::show(const QUrl & url, const QString & name) { + QQuickItem * item = _rootItem->findChild(name); + if (nullptr != item) { + item->setEnabled(true); + item->setVisible(true); + } else { + load(url); + } +} + +void OffscreenUi::toggle(const QUrl & url, const QString & name) { + QQuickItem * item = _rootItem->findChild(name); + // First load? + if (nullptr == item) { + load(url); + return; + } + + // Toggle the visibity AND the enabled flag (otherwise invisible + // dialogs can still swallow keyboard input) + bool newFlag = !item->isVisible(); + item->setVisible(newFlag); + item->setEnabled(newFlag); +} + + +void OffscreenUi::load(const QUrl & url) { + QVariant returnedValue; + QVariant msg = url; + QMetaObject::invokeMethod(_rootItem, "loadChild", + Q_RETURN_ARG(QVariant, returnedValue), + Q_ARG(QVariant, msg)); + qDebug() << "QML function returned:" << returnedValue.toString(); +} + diff --git a/libraries/render-utils/src/OffscreenUi.h b/libraries/render-utils/src/OffscreenUi.h new file mode 100644 index 0000000000..2e8206f466 --- /dev/null +++ b/libraries/render-utils/src/OffscreenUi.h @@ -0,0 +1,136 @@ +// +// OffscreenUi.h +// interface/src/entities +// +// Created by Bradley Austin Davis on 2015-04-04 +// 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 +// +#pragma once +#ifndef hifi_OffscreenUi_h +#define hifi_OffscreenUi_h + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "FboCache.h" +#include "OffscreenGlCanvas.h" + +#define QML_DIALOG_DECL \ +private: \ + static const QString NAME; \ + static const QUrl QML; \ +public: \ + static void registerType(); \ + static void show(); \ + static void toggle(); \ +private: + +#define QML_DIALOG_DEF(x) \ + const QUrl x::QML = #x ".qml"; \ + const QString x::NAME = #x; \ + \ + void x::registerType() { \ + qmlRegisterType("Hifi", 1, 0, NAME.toLocal8Bit().constData()); \ + } \ + \ + void x::show() { \ + auto offscreenUi = DependencyManager::get(); \ + offscreenUi->show(QML, NAME); \ + } \ + \ + void x::toggle() { \ + auto offscreenUi = DependencyManager::get(); \ + offscreenUi->toggle(QML, NAME); \ + } + + +class OffscreenUi : public OffscreenGlCanvas, public Dependency { + Q_OBJECT + + class QMyQuickRenderControl : public QQuickRenderControl { + protected: + QWindow * renderWindow(QPoint * offset) Q_DECL_OVERRIDE{ + if (nullptr == _renderWindow) { + return QQuickRenderControl::renderWindow(offset); + } + if (nullptr != offset) { + offset->rx() = offset->ry() = 0; + } + return _renderWindow; + } + + private: + QWindow * _renderWindow{ nullptr }; + friend class OffscreenUi; + }; + +public: + using MouseTranslator = std::function < QPointF(const QPointF &) > ; + OffscreenUi(); + virtual ~OffscreenUi(); + void create(QOpenGLContext * context); + void resize(const QSize & size); + void loadQml(const QUrl & qmlSource, std::function f = [](QQmlContext*) {}); + void load(const QUrl & url); + void show(const QUrl & url, const QString & name); + void toggle(const QUrl & url, const QString & name); + + QQmlContext * qmlContext(); + + void pause(); + void resume(); + bool isPaused() const; + void setProxyWindow(QWindow * window); + QPointF mapWindowToUi(const QPointF & p, QObject * dest); + virtual bool eventFilter(QObject * dest, QEvent * e); + void setMouseTranslator(MouseTranslator mt) { + _mouseTranslator = mt; + } + +protected: + +private slots: + void updateQuick(); + void finishQmlLoad(); + +public slots: + void requestUpdate(); + void requestRender(); + void lockTexture(int texture); + void releaseTexture(int texture); + +signals: + void textureUpdated(GLuint texture); + +private: + QMyQuickRenderControl *_renderControl{ new QMyQuickRenderControl }; + QQuickWindow *_quickWindow{ nullptr }; + QQmlEngine *_qmlEngine{ nullptr }; + QQmlComponent *_qmlComponent{ nullptr }; + QQuickItem * _rootItem{ nullptr }; + QTimer _updateTimer; + FboCache _fboCache; + bool _polish{ true }; + bool _paused{ true }; + MouseTranslator _mouseTranslator{ [](const QPointF & p) { return p; } }; +}; + +#endif \ No newline at end of file diff --git a/libraries/render-utils/src/RenderUtil.h b/libraries/render-utils/src/RenderUtil.h index b2f244733a..74d6c23791 100644 --- a/libraries/render-utils/src/RenderUtil.h +++ b/libraries/render-utils/src/RenderUtil.h @@ -15,4 +15,54 @@ /// Renders a quad from (-1, -1, 0) to (1, 1, 0) with texture coordinates from (sMin, tMin) to (sMax, tMax). void renderFullscreenQuad(float sMin = 0.0f, float sMax = 1.0f, float tMin = 0.0f, float tMax = 1.0f); +template +void withMatrixPush(F f) { + glMatrixMode(matrix); + glPushMatrix(); + f(); + glPopMatrix(); +} + +template +void withProjectionPush(F f) { + withMatrixPush(f); +} + +template +void withProjectionIdentity(F f) { + withProjectionPush([&] { + glLoadIdentity(); + f(); + }); +} + +template +void withProjectionMatrix(GLfloat * matrix, F f) { + withProjectionPush([&] { + glLoadMatrixf(matrix); + f(); + }); +} + +template +void withModelviewPush(F f) { + withMatrixPush(f); +} + +template +void withModelviewIdentity(F f) { + withModelviewPush([&] { + glLoadIdentity(); + f(); + }); +} + +template +void withModelviewMatrix(GLfloat * matrix, F f) { + withModelviewPush([&] { + glLoadMatrixf(matrix); + f(); + }); +} + #endif // hifi_RenderUtil_h diff --git a/libraries/shared/src/GLMHelpers.h b/libraries/shared/src/GLMHelpers.h index 50393b7f5f..e5d22d67dc 100644 --- a/libraries/shared/src/GLMHelpers.h +++ b/libraries/shared/src/GLMHelpers.h @@ -17,6 +17,17 @@ #include #include +// Bring the most commonly used GLM types into the default namespace +using glm::ivec3; +using glm::ivec2; +using glm::uvec2; +using glm::mat3; +using glm::mat4; +using glm::vec2; +using glm::vec3; +using glm::vec4; +using glm::quat; + #include #include #include diff --git a/libraries/shared/src/ThreadHelpers.h b/libraries/shared/src/ThreadHelpers.h new file mode 100644 index 0000000000..862f7bf673 --- /dev/null +++ b/libraries/shared/src/ThreadHelpers.h @@ -0,0 +1,13 @@ +#pragma once +#include + +template +void withLock(L lock, F function) { + throw std::exception(); +} + +template +void withLock(QMutex & lock, F function) { + QMutexLocker locker(&lock); + function(); +} diff --git a/tests/render-utils/src/main.cpp b/tests/render-utils/src/main.cpp index b62ab68c22..304d6f1a07 100644 --- a/tests/render-utils/src/main.cpp +++ b/tests/render-utils/src/main.cpp @@ -10,6 +10,7 @@ #include "TextRenderer.h" #include "MatrixStack.h" +#include "OffscreenUi.h" #include #include @@ -21,9 +22,11 @@ #include #include #include +#include #include #include #include +#include #include #include #include @@ -66,99 +69,143 @@ public: }; // Create a simple OpenGL window that renders text in various ways -class QTestWindow: public QWindow { +class QTestWindow : public QWindow { Q_OBJECT - QOpenGLContext * _context; + + QOpenGLContext * _context{ nullptr }; QSize _size; TextRenderer* _textRenderer[4]; RateCounter fps; + OffscreenUi _offscreenUi; + int testQmlTexture{ 0 }; + //ProgramPtr _planeProgam; + //ShapeWrapperPtr _planeShape; protected: - void resizeEvent(QResizeEvent * ev) override { - QWindow::resizeEvent(ev); - _size = ev->size(); - resizeGl(); - } - void resizeGl() { - glMatrixMode(GL_PROJECTION); - glLoadIdentity(); - glOrtho(0, _size.width(), _size.height(), 0, 1, -1); - glMatrixMode(GL_MODELVIEW); - glViewport(0, 0, _size.width(), _size.height()); + void renderText(); + void renderQml(); + +private: + void resizeWindow(const QSize & size) { + _size = size; + _offscreenUi.resize(_size); } public: - QTestWindow(); - virtual ~QTestWindow() { + QTestWindow() { + setSurfaceType(QSurface::OpenGLSurface); + QSurfaceFormat format; + // Qt Quick may need a depth and stencil buffer. Always make sure these are available. + format.setDepthBufferSize(16); + format.setStencilBufferSize(8); + format.setVersion(4, 5); + format.setProfile(QSurfaceFormat::OpenGLContextProfile::CompatibilityProfile); + format.setOption(QSurfaceFormat::DebugContext); + + setFormat(format); + + _context = new QOpenGLContext; + _context->setFormat(format); + _context->create(); + + show(); + makeCurrent(); + + { + QOpenGLDebugLogger *logger = new QOpenGLDebugLogger(this); + logger->initialize(); // initializes in the current context, i.e. ctx + logger->enableMessages(); + connect(logger, &QOpenGLDebugLogger::messageLogged, this, [&](const QOpenGLDebugMessage & debugMessage) { + qDebug() << debugMessage; + }); + // logger->startLogging(QOpenGLDebugLogger::SynchronousLogging); + } + qDebug() << (const char*)glGetString(GL_VERSION); + +#ifdef WIN32 + glewExperimental = true; + GLenum err = glewInit(); + if (GLEW_OK != err) { + /* Problem: glewInit failed, something is seriously wrong. */ + const GLubyte * errStr = glewGetErrorString(err); + qDebug("Error: %s\n", errStr); + } + qDebug("Status: Using GLEW %s\n", glewGetString(GLEW_VERSION)); + + if (wglewGetExtension("WGL_EXT_swap_control")) { + int swapInterval = wglGetSwapIntervalEXT(); + qDebug("V-Sync is %s\n", (swapInterval > 0 ? "ON" : "OFF")); + } + glGetError(); +#endif + + _textRenderer[0] = TextRenderer::getInstance(SANS_FONT_FAMILY, 12, false); + _textRenderer[1] = TextRenderer::getInstance(SERIF_FONT_FAMILY, 12, false, + TextRenderer::SHADOW_EFFECT); + _textRenderer[2] = TextRenderer::getInstance(MONO_FONT_FAMILY, 48, -1, + false, TextRenderer::OUTLINE_EFFECT); + _textRenderer[3] = TextRenderer::getInstance(INCONSOLATA_FONT_FAMILY, 24); + + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glClearColor(0.2f, 0.2f, 0.2f, 1); + glDisable(GL_DEPTH_TEST); + + _offscreenUi.create(_context); + // FIXME, need to switch to a QWindow for mouse and keyboard input to work + _offscreenUi.setProxyWindow(this); + // "#0e7077" + setFramePosition(QPoint(-1000, 0)); + resize(QSize(800, 600)); + + static const QString f("/Users/bdavis/Git/hifi/interface/resources/qml/Root.qml"); + _offscreenUi.loadQml(QUrl::fromLocalFile(f)); + connect(&_offscreenUi, &OffscreenUi::textureUpdated, this, [&](int textureId) { + _offscreenUi.lockTexture(textureId); + assert(!glGetError()); + GLuint oldTexture = testQmlTexture; + testQmlTexture = textureId; + if (oldTexture) { + _offscreenUi.releaseTexture(oldTexture); + } + }); + installEventFilter(&_offscreenUi); + _offscreenUi.resume(); } + + virtual ~QTestWindow() { + } + + void draw(); void makeCurrent() { _context->makeCurrent(this); } - void draw(); +protected: + + void resizeEvent(QResizeEvent * ev) override { + resizeWindow(ev->size()); + } + + + void keyPressEvent(QKeyEvent *event) { + switch (event->key()) { + case Qt::Key_Slash: + qDebug() << "Foo"; + _offscreenUi.load(QString("Login.qml")); + break; + } + QWindow::keyPressEvent(event); + } + }; #ifndef SERIF_FONT_FAMILY #define SERIF_FONT_FAMILY "Times New Roman" #endif -QTestWindow::QTestWindow() { - setSurfaceType(QSurface::OpenGLSurface); - - QSurfaceFormat format; - // Qt Quick may need a depth and stencil buffer. Always make sure these are available. - format.setDepthBufferSize(16); - format.setStencilBufferSize(8); - format.setVersion(3, 2); - format.setProfile( - QSurfaceFormat::OpenGLContextProfile::CompatibilityProfile); - setFormat(format); - - _context = new QOpenGLContext; - _context->setFormat(format); - _context->create(); - - show(); - makeCurrent(); - qDebug() << (const char*) glGetString(GL_VERSION); - -#ifdef WIN32 - glewExperimental = true; - GLenum err = glewInit(); - if (GLEW_OK != err) { - /* Problem: glewInit failed, something is seriously wrong. */ - const GLubyte * errStr = glewGetErrorString(err); - qDebug("Error: %s\n", errStr); - } - qDebug("Status: Using GLEW %s\n", glewGetString(GLEW_VERSION)); - - if (wglewGetExtension("WGL_EXT_swap_control")) { - int swapInterval = wglGetSwapIntervalEXT(); - qDebug("V-Sync is %s\n", (swapInterval > 0 ? "ON" : "OFF")); - } - glGetError(); -#endif - - setFramePosition(QPoint(100, -900)); - resize(QSize(800, 600)); - _size = QSize(800, 600); - - _textRenderer[0] = TextRenderer::getInstance(SANS_FONT_FAMILY, 12, false); - _textRenderer[1] = TextRenderer::getInstance(SERIF_FONT_FAMILY, 12, false, - TextRenderer::SHADOW_EFFECT); - _textRenderer[2] = TextRenderer::getInstance(MONO_FONT_FAMILY, 48, -1, - false, TextRenderer::OUTLINE_EFFECT); - _textRenderer[3] = TextRenderer::getInstance(INCONSOLATA_FONT_FAMILY, 24); - - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - glClearColor(0.2f, 0.2f, 0.2f, 1); - glDisable(GL_DEPTH_TEST); - resizeGl(); -} - static const wchar_t * EXAMPLE_TEXT = L"Hello"; //static const wchar_t * EXAMPLE_TEXT = L"\xC1y Hello 1.0\ny\xC1 line 2\n\xC1y"; static const glm::uvec2 QUAD_OFFSET(10, 10); @@ -166,14 +213,21 @@ static const glm::uvec2 QUAD_OFFSET(10, 10); static const glm::vec3 COLORS[4] = { { 1.0, 1.0, 1.0 }, { 0.5, 1.0, 0.5 }, { 1.0, 0.5, 0.5 }, { 0.5, 0.5, 1.0 } }; -void QTestWindow::draw() { - makeCurrent(); - glClear(GL_COLOR_BUFFER_BIT); +void QTestWindow::renderText() { + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + glOrtho(0, _size.width(), _size.height(), 0, 1, -1); + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); const glm::uvec2 size = glm::uvec2(_size.width() / 2, _size.height() / 2); - const glm::uvec2 offsets[4] = { { QUAD_OFFSET.x, QUAD_OFFSET.y }, { size.x - + QUAD_OFFSET.x, QUAD_OFFSET.y }, { size.x + QUAD_OFFSET.x, size.y - + QUAD_OFFSET.y }, { QUAD_OFFSET.x, size.y + QUAD_OFFSET.y }, }; + + const glm::uvec2 offsets[4] = { + { QUAD_OFFSET.x, QUAD_OFFSET.y }, + { size.x + QUAD_OFFSET.x, QUAD_OFFSET.y }, + { size.x + QUAD_OFFSET.x, size.y + QUAD_OFFSET.y }, + { QUAD_OFFSET.x, size.y + QUAD_OFFSET.y }, + }; QString str = QString::fromWCharArray(EXAMPLE_TEXT); for (int i = 0; i < 4; ++i) { @@ -200,17 +254,57 @@ void QTestWindow::draw() { glm::vec4(COLORS[i], 1.0f)); } } +} + +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(); +} + +void QTestWindow::draw() { + makeCurrent(); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + glViewport(0, 0, _size.width(), _size.height()); + + renderText(); + //renderQml(); + _context->swapBuffers(this); glFinish(); + fps.increment(); if (fps.elapsed() >= 2.0f) { - qDebug() << "FPS: " << fps.rate(); + //qDebug() << "FPS: " << fps.rate(); fps.reset(); } } int main(int argc, char** argv) { QApplication app(argc, argv); + //QLoggingCategory::setFilterRules("qt.quick.mouse.debug = true"); QTestWindow window; QTimer timer; timer.setInterval(1);