Merge pull request #4644 from jherico/offscreen

Working on new-UI implementations of address bar and login
This commit is contained in:
Brad Hefta-Gaub 2015-04-23 07:17:45 -07:00
commit 82d3302d1b
49 changed files with 2513 additions and 731 deletions

View file

@ -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)

View file

@ -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)

View file

@ -0,0 +1,76 @@
import Hifi 1.0
import QtQuick 2.3
import QtQuick.Controls 1.2
import QtQuick.Window 2.2
import QtQuick.Controls.Styles 1.3
CustomDialog {
title: "Go to..."
objectName: "AddressBarDialog"
height: 128
width: 512
destroyOnCloseButton: false
onVisibleChanged: {
if (!visible) {
reset();
}
}
onEnabledChanged: {
if (enabled) {
addressLine.forceActiveFocus();
}
}
function reset() {
addressLine.text = ""
goButton.source = "../images/address-bar-submit.svg"
}
AddressBarDialog {
id: addressBarDialog
// The client area
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)
}
}
}
}
}

View file

@ -0,0 +1,45 @@
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 QtWebKit 3.0
CustomDialog {
title: "Test Dlg"
id: testDialog
objectName: "Browser"
width: 1280
height: 720
Item {
id: clientArea
// The client area
anchors.fill: parent
anchors.margins: parent.margins
anchors.topMargin: parent.topMargin
ScrollView {
anchors.fill: parent
WebView {
id: webview
url: "http://slashdot.org"
anchors.fill: parent
}
}
}
}
/*
// This is the behavior, and it applies a NumberAnimation to any attempt to set the x property
MouseArea {
anchors.fill: parent
}
*/

View file

@ -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
}

View file

@ -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
}
}
}

View file

@ -0,0 +1,162 @@
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
scale: 0.0
enabled: false
property int animationDuration: 400
property bool destroyOnInvisible: false
property bool destroyOnCloseButton: true
property bool resizable: false
property int minX: 256
property int minY: 256
clip: true
onEnabledChanged: {
scale = enabled ? 1.0 : 0.0
}
onScaleChanged: {
visible = (scale != 0.0);
}
onVisibleChanged: {
if (!visible && destroyOnInvisible) {
console.log("Destroying closed component");
destroy();
}
}
function close() {
if (destroyOnCloseButton) {
destroyOnInvisible = true
}
enabled = false;
}
function deltaSize(dx, dy) {
width = Math.max(width + dx, minX)
height = Math.max(height + dy, minY)
}
Behavior on scale {
NumberAnimation {
//This specifies how long the animation takes
duration: dialog.animationDuration
//This selects an easing curve to interpolate with, the default is Easing.Linear
easing.type: Easing.InOutBounce
}
}
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
anchors.right: closeButton.left
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.top: parent.top
anchors.rightMargin: 4
drag {
target: dialog
minimumX: 0
minimumY: 0
maximumX: dialog.parent ? dialog.parent.width - dialog.width : 0
maximumY: dialog.parent ? dialog.parent.height - dialog.height : 0
}
}
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.close();
}
}
}
} // 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
clip: true
} // client border
} // window border
MouseArea {
id: sizeDrag
property int startX
property int startY
anchors.right: parent.right
anchors.bottom: parent.bottom
width: 16
height: 16
hoverEnabled: true
onPressed: {
startX = mouseX
startY = mouseY
}
onPositionChanged: {
if (pressed && dialog.resizable) {
dialog.deltaSize((mouseX - startX), (mouseY - startY))
startX = mouseX
startY = mouseY
}
}
}
}

View file

@ -0,0 +1,7 @@
import QtQuick 2.3
Text {
font.family: "Helvetica"
font.pointSize: 18
}

View file

@ -0,0 +1,10 @@
import QtQuick 2.3
import QtQuick.Controls 1.2
TextArea {
font.family: "Helvetica"
font.pointSize: 18
backgroundVisible: false
readOnly: true
}

View file

@ -0,0 +1,7 @@
import QtQuick 2.3
TextEdit {
font.family: "Helvetica"
font.pointSize: 18
}

View file

@ -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
}
}

View file

@ -0,0 +1,8 @@
import QtQuick 1.0
Image {
id: icon
width: 64
height: 64
source: "file.svg"
}

View file

@ -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"
}
}
}

View file

@ -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
CustomDialog {
title: "Login"
SystemPalette { id: myPalette; colorGroup: SystemPalette.Active }
objectName: "LoginDialog"
height: 512
width: 384
onVisibleChanged: {
if (!visible) {
reset()
}
}
onEnabledChanged: {
if (enabled) {
username.forceActiveFocus();
}
}
function reset() {
username.text = ""
password.text = ""
loginDialog.statusText = ""
}
LoginDialog {
id: loginDialog
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")
}
}
}
}
}
}

View file

@ -0,0 +1,236 @@
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
Rectangle {
color: "teal"
height: 512
width: 192
SystemPalette { id: sp; colorGroup: SystemPalette.Active }
SystemPalette { id: spi; colorGroup: SystemPalette.Inactive }
SystemPalette { id: spd; colorGroup: SystemPalette.Disabled }
Column {
anchors.margins: 8
anchors.fill: parent
spacing: 8
Row {
width: parent.width
height: 16
Text { height: parent.height; width: 128; text: "base" }
Rectangle { height: parent.height; width: 16; color: sp.base }
Rectangle { height: parent.height; width: 16; color: spi.base }
Rectangle { height: parent.height; width: 16; color: spd.base }
}
Row {
width: parent.width
height: 16
Text { height: parent.height; width: 128; text: "alternateBase" }
Rectangle { height: parent.height; width: 16; color: sp.alternateBase }
Rectangle { height: parent.height; width: 16; color: spi.alternateBase }
Rectangle { height: parent.height; width: 16; color: spd.alternateBase }
}
Item {
height: 16
width:parent.width
}
Row {
width: parent.width
height: 16
Text { height: parent.height; width: 128; text: "dark" }
Rectangle { height: parent.height; width: 16; color: sp.dark }
Rectangle { height: parent.height; width: 16; color: spi.dark }
Rectangle { height: parent.height; width: 16; color: spd.dark }
}
Row {
width: parent.width
height: 16
Text { height: parent.height; width: 128; text: "mid" }
Rectangle { height: parent.height; width: 16; color: sp.mid }
Rectangle { height: parent.height; width: 16; color: spi.mid }
Rectangle { height: parent.height; width: 16; color: spd.mid }
}
Row {
width: parent.width
height: 16
Text { height: parent.height; width: 128; text: "mid light" }
Rectangle { height: parent.height; width: 16; color: sp.midlight }
Rectangle { height: parent.height; width: 16; color: spi.midlight }
Rectangle { height: parent.height; width: 16; color: spd.midlight }
}
Row {
width: parent.width
height: 16
Text { height: parent.height; width: 128; text: "light" }
Rectangle { height: parent.height; width: 16; color: sp.light}
Rectangle { height: parent.height; width: 16; color: spi.light}
Rectangle { height: parent.height; width: 16; color: spd.light}
}
Row {
width: parent.width
height: 16
Text { height: parent.height; width: 128; text: "shadow" }
Rectangle { height: parent.height; width: 16; color: sp.shadow}
Rectangle { height: parent.height; width: 16; color: spi.shadow}
Rectangle { height: parent.height; width: 16; color: spd.shadow}
}
Item {
height: 16
width:parent.width
}
Row {
width: parent.width
height: 16
Text { height: parent.height; width: 128; text: "text" }
Rectangle { height: parent.height; width: 16; color: sp.text }
Rectangle { height: parent.height; width: 16; color: spi.text }
Rectangle { height: parent.height; width: 16; color: spd.text }
}
Item {
height: 16
width:parent.width
}
Row {
width: parent.width
height: 16
Text { height: parent.height; width: 128; text: "window" }
Rectangle { height: parent.height; width: 16; color: sp.window }
Rectangle { height: parent.height; width: 16; color: spi.window }
Rectangle { height: parent.height; width: 16; color: spd.window }
}
Row {
width: parent.width
height: 16
Text { height: parent.height; width: 128; text: "window text" }
Rectangle { height: parent.height; width: 16; color: sp.windowText }
Rectangle { height: parent.height; width: 16; color: spi.windowText }
Rectangle { height: parent.height; width: 16; color: spd.windowText }
}
Item {
height: 16
width:parent.width
}
Row {
width: parent.width
height: 16
Text { height: parent.height; width: 128; text: "button" }
Rectangle { height: parent.height; width: 16; color: sp.button }
Rectangle { height: parent.height; width: 16; color: spi.button }
Rectangle { height: parent.height; width: 16; color: spd.button }
}
Row {
width: parent.width
height: 16
Text { height: parent.height; width: 128; text: "buttonText" }
Rectangle { height: parent.height; width: 16; color: sp.buttonText }
Rectangle { height: parent.height; width: 16; color: spi.buttonText }
Rectangle { height: parent.height; width: 16; color: spd.buttonText }
}
Item {
height: 16
width:parent.width
}
Row {
width: parent.width
height: 16
Text { height: parent.height; width: 128; text: "highlight" }
Rectangle { height: parent.height; width: 16; color: sp.highlight }
Rectangle { height: parent.height; width: 16; color: spi.highlight }
Rectangle { height: parent.height; width: 16; color: spd.highlight }
}
Row {
width: parent.width
height: 16
Text { height: parent.height; width: 128; text: "highlighted text" }
Rectangle { height: parent.height; width: 16; color: sp.highlightedText}
Rectangle { height: parent.height; width: 16; color: spi.highlightedText}
Rectangle { height: parent.height; width: 16; color: spd.highlightedText}
}
}
/*
CustomDialog {
title: "Test Dlg"
anchors.fill: parent
Rectangle {
property int d: 100
id: square
objectName: "testRect"
width: d
height: d
anchors.centerIn: parent
color: "red"
NumberAnimation on rotation { from: 0; to: 360; duration: 2000; loops: Animation.Infinite; }
}
CustomTextEdit {
anchors.left: parent.left
anchors.leftMargin: 12
anchors.right: parent.right
anchors.rightMargin: 12
clip: true
text: "test edit"
anchors.top: parent.top
anchors.topMargin: parent.titleSize + 12
}
CustomButton {
x: 128
y: 192
anchors.bottom: parent.bottom
anchors.bottomMargin: 12
anchors.right: parent.right
anchors.rightMargin: 12
onClicked: {
console.log("Click");
if (square.visible) {
square.visible = false
} else {
square.visible = true
}
}
}
CustomButton {
id: customButton2
y: 192
text: "Close"
anchors.left: parent.left
anchors.leftMargin: 12
anchors.bottom: parent.bottom
anchors.bottomMargin: 12
onClicked: {
onClicked: testDialog.x == 0 ? testDialog.x = 200 : testDialog.x = 0
}
}
Keys.onPressed: {
console.log("Key " + event.key);
switch (event.key) {
case Qt.Key_Q:
if (Qt.ControlModifier == event.modifiers) {
event.accepted = true;
break;
}
}
}
}
*/
}
/*
// This is the behavior, and it applies a NumberAnimation to any attempt to set the x property
MouseArea {
anchors.fill: parent
}
*/

View file

@ -0,0 +1,9 @@
import Hifi 1.0
import QtQuick 2.3
Root {
id: root
width: 1280
height: 720
}

View file

@ -0,0 +1,106 @@
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
CustomDialog {
title: "Test Dialog"
id: testDialog
objectName: "TestDialog"
width: 512
height: 512
animationDuration: 200
onEnabledChanged: {
if (enabled) {
edit.forceActiveFocus();
}
}
Item {
id: clientArea
// The client area
anchors.fill: parent
anchors.margins: parent.margins
anchors.topMargin: parent.topMargin
Rectangle {
property int d: 100
id: square
objectName: "testRect"
width: d
height: d
anchors.centerIn: parent
color: "red"
NumberAnimation on rotation { from: 0; to: 360; duration: 2000; loops: Animation.Infinite; }
}
CustomTextEdit {
id: edit
anchors.left: parent.left
anchors.leftMargin: 12
anchors.right: parent.right
anchors.rightMargin: 12
clip: true
text: "test edit"
anchors.top: parent.top
anchors.topMargin: 12
}
CustomButton {
x: 128
y: 192
text: "Test"
anchors.bottom: parent.bottom
anchors.bottomMargin: 12
anchors.right: parent.right
anchors.rightMargin: 12
onClicked: {
console.log("Click");
if (square.visible) {
square.visible = false
} else {
square.visible = true
}
}
}
CustomButton {
id: customButton2
y: 192
text: "Move"
anchors.left: parent.left
anchors.leftMargin: 12
anchors.bottom: parent.bottom
anchors.bottomMargin: 12
onClicked: {
onClicked: testDialog.x == 0 ? testDialog.x = 200 : testDialog.x = 0
}
}
Keys.onPressed: {
console.log("Key " + event.key);
switch (event.key) {
case Qt.Key_Q:
if (Qt.ControlModifier == event.modifiers) {
event.accepted = true;
break;
}
}
}
}
}
/*
// This is the behavior, and it applies a NumberAnimation to any attempt to set the x property
MouseArea {
anchors.fill: parent
}
*/

View file

@ -0,0 +1,35 @@
import Hifi 1.0
import QtQuick 2.3
Root {
id: root
width: 1280
height: 720
CustomButton {
id: messageBox
anchors.right: createDialog.left
anchors.rightMargin: 24
anchors.bottom: parent.bottom
anchors.bottomMargin: 24
text: "Message"
onClicked: {
console.log("Foo")
root.information("a")
console.log("Bar")
}
}
CustomButton {
id: createDialog
anchors.right: parent.right
anchors.rightMargin: 24
anchors.bottom: parent.bottom
anchors.bottomMargin: 24
text: "Create"
onClicked: {
root.loadChild("TestDialog.qml");
}
}
}

View file

@ -0,0 +1,29 @@
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.enabled = true
}
} else if (component.status == Component.Error) {
// Error Handling
console.log("Error loading component:", component.errorString());
} else {
console.log("Unknown component status: " + component.status);
}
}

View file

@ -0,0 +1,4 @@
var color = "#0e7077"
var Colors = {
hifiBlue: "#0e7077"
}

View file

@ -135,6 +135,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)
@ -207,8 +208,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"));
}
}
@ -257,6 +262,7 @@ bool setupEssentials(int& argc, char** argv) {
#endif
auto discoverabilityManager = DependencyManager::set<DiscoverabilityManager>();
auto sceneScriptingInterface = DependencyManager::set<SceneScriptingInterface>();
auto offscreenUi = DependencyManager::set<OffscreenUi>();
return true;
}
@ -313,8 +319,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");
@ -559,8 +567,12 @@ 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<OffscreenUi>().data());
}
void Application::aboutToQuit() {
emit beforeAboutToQuit();
@ -634,6 +646,7 @@ Application::~Application() {
// stop the glWidget frame timer so it doesn't call paintGL
_glWidget->stopFrameTimer();
DependencyManager::destroy<OffscreenUi>();
DependencyManager::destroy<AvatarManager>();
DependencyManager::destroy<AnimationCache>();
DependencyManager::destroy<TextureCache>();
@ -721,10 +734,45 @@ 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>();
offscreenUi->create(_glWidget->context()->contextHandle());
offscreenUi->resize(_glWidget->size());
offscreenUi->setProxyWindow(_window->windowHandle());
offscreenUi->setBaseUrl(QUrl::fromLocalFile(PathUtils::resourcesPath() + "/qml/"));
offscreenUi->load("Root.qml");
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();
connect(_window, &MainWindow::windowGeometryChanged, [this](const QRect & r){
static qreal oldDevicePixelRatio = 0;
qreal devicePixelRatio = _glWidget->devicePixelRatio();
if (devicePixelRatio != oldDevicePixelRatio) {
oldDevicePixelRatio = devicePixelRatio;
qDebug() << "Device pixel ratio changed, triggering GL resize";
resizeGL(_glWidget->width(),
_glWidget->height());
}
});
}
void Application::paintGL() {
PROFILE_RANGE(__FUNCTION__);
PerformanceTimer perfTimer("paintGL");
@ -819,7 +867,7 @@ void Application::paintGL() {
{
PerformanceTimer perfTimer("renderOverlay");
_applicationOverlay.renderOverlay(true);
_applicationOverlay.renderOverlay();
_applicationOverlay.displayOverlayTexture();
}
}
@ -864,6 +912,9 @@ void Application::resizeGL(int width, int height) {
updateProjectionMatrix();
glLoadIdentity();
auto offscreenUi = DependencyManager::get<OffscreenUi>();
offscreenUi->resize(_glWidget->size());
// 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;
@ -912,6 +963,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<QTouchEvent*>(event));
event->accept();
return true;
case QEvent::TouchEnd:
touchEndEvent(static_cast<QTouchEvent*>(event));
return true;
case QEvent::TouchUpdate:
touchUpdateEvent(static_cast<QTouchEvent*>(event));
return true;
case QEvent::Wheel:
wheelEvent(static_cast<QWheelEvent*>(event));
return true;
case QEvent::Drop:
dropEvent(static_cast<QDropEvent*>(event));
return true;
default:
break;
}
// handle custom URL
if (event->type() == QEvent::FileOpen) {
@ -932,7 +1021,7 @@ bool Application::event(QEvent* event) {
if (HFActionEvent::types().contains(event->type())) {
_controllerScriptingInterface.handleMetaEvent(static_cast<HFMetaEvent*>(event));
}
return QApplication::event(event);
}
@ -964,14 +1053,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:
@ -1031,11 +1123,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;
@ -1495,6 +1582,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();

View file

@ -30,6 +30,7 @@
#include <NetworkPacket.h>
#include <NodeList.h>
#include <OctreeQuery.h>
#include <OffscreenUi.h>
#include <PacketHeaders.h>
#include <PhysicsEngine.h>
#include <ScriptEngine.h>
@ -151,6 +152,7 @@ public:
void setPreviousScriptLocation(const QString& previousScriptLocation);
void clearScriptsBeforeRunning();
void initializeGL();
void initializeUi();
void paintGL();
void resizeGL(int width, int height);
@ -169,7 +171,8 @@ public:
void touchUpdateEvent(QTouchEvent* event);
void wheelEvent(QWheelEvent* event);
void dropEvent(QDropEvent *event);
void dropEvent(QDropEvent* event);
void dragEnterEvent(QDragEnterEvent* event);
bool event(QEvent* event);
bool eventFilter(QObject* object, QEvent* event);

View file

@ -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<QTouchEvent*>(event));
event->accept();
return true;
case QEvent::TouchEnd:
Application::getInstance()->touchEndEvent(static_cast<QTouchEvent*>(event));
return true;
case QEvent::TouchUpdate:
Application::getInstance()->touchUpdateEvent(static_cast<QTouchEvent*>(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

View file

@ -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();

View file

@ -96,7 +96,7 @@ Menu::Menu() {
addActionToQMenuAndActionHash(fileMenu,
MenuOption::AddressBar,
Qt::Key_Enter,
Qt::CTRL | Qt::Key_L,
dialogsManager.data(),
SLOT(toggleAddressBar()));
auto addressManager = DependencyManager::get<AddressManager>();
@ -150,7 +150,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()));
@ -193,7 +194,7 @@ Menu::Menu() {
addActionToQMenuAndActionHash(toolsMenu,
MenuOption::ResetSensors,
Qt::Key_Apostrophe,
0, // QML Qt::Key_Apostrophe,
qApp,
SLOT(resetSensors()));
@ -206,17 +207,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()));
@ -245,11 +246,15 @@ Menu::Menu() {
qApp,
SLOT(setFullscreen(bool)));
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,
#ifdef Q_OS_MAC
@ -282,8 +287,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,
@ -293,7 +302,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);
@ -345,13 +356,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<GlowEffect>().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");

View file

@ -525,7 +525,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)) {

View file

@ -101,7 +101,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<GlowEffect>()->prepare();
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

View file

@ -1,148 +1,47 @@
//
// 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 <QMessageBox>
#include <PathUtils.h>
#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) : OffscreenQmlDialog(parent) {
auto addressManager = DependencyManager::get<AddressManager>();
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() {
((QQuickItem*)parent())->setEnabled(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<AddressManager>();
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<AddressManager>()->handleLookupString(address);
}
}
void AddressBarDialog::displayAddressOfflineMessage() {
QMessageBox::information(Application::getInstance()->getWindow(), "Address offline",
"That user or place is currently offline.");
OffscreenUi::information("Address offline",
"That user or place is currently offline.");
}
void AddressBarDialog::displayAddressNotFoundMessage() {
QMessageBox::information(Application::getInstance()->getWindow(), "Address not found",
"There is no address information for that user or place.");
}
OffscreenUi::information("Address not found",
"There is no address information for that user or place.");
}

View file

@ -1,47 +1,33 @@
//
// 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
//
#pragma once
#ifndef hifi_AddressBarDialog_h
#define hifi_AddressBarDialog_h
#include "FramelessDialog.h"
#include <OffscreenQmlDialog.h>
#include <QLineEdit>
#include <QHBoxLayout>
#include <QSpacerItem>
#include <QVBoxLayout>
class AddressBarDialog : public FramelessDialog {
class AddressBarDialog : public OffscreenQmlDialog
{
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 = nullptr);
protected:
void displayAddressOfflineMessage();
void displayAddressNotFoundMessage();
void hide();
Q_INVOKABLE void loadAddress(const QString& address);
};
#endif // hifi_AddressBarDialog_h
#endif

View file

@ -17,6 +17,7 @@
#include <GLMHelpers.h>
#include <PathUtils.h>
#include <PerfStat.h>
#include <OffscreenUi.h>
#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<OffscreenUi>();
connect(offscreenUi.data(), &OffscreenUi::textureUpdated, this, [&](GLuint textureId) {
auto offscreenUi = DependencyManager::get<OffscreenUi>();
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 <typename F>
void with_each_texture(GLuint firstPassTexture, GLuint secondPassTexture, F f) {
glEnable(GL_TEXTURE_2D);
glActiveTexture(GL_TEXTURE0);
if (firstPassTexture) {
glBindTexture(GL_TEXTURE_2D, firstPassTexture);
f();
}
if (secondPassTexture) {
glBindTexture(GL_TEXTURE_2D, secondPassTexture);
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<GeometryCache>()->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<GeometryCache>()->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<AvatarManager>()->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<AvatarManager>()->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<GeometryCache>()->renderQuad(glm::vec3(x, y + quadHeight, -distance),
with_each_texture(_overlays.getTexture(), _newUiTexture, [&] {
DependencyManager::get<GeometryCache>()->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

View file

@ -23,12 +23,13 @@ const float MAGNIFY_MULT = 2.0f;
const float DEFAULT_OCULUS_UI_ANGULAR_SIZE = 72.0f;
// Handles the drawing of the overlays to the screen
class ApplicationOverlay {
class ApplicationOverlay : public QObject {
Q_OBJECT
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 +76,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 +122,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;

View file

@ -9,6 +9,8 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "DialogsManager.h"
#include <QMessageBox>
#include <AccountManager.h>
@ -29,14 +31,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() {
@ -45,13 +42,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() {
@ -180,3 +175,4 @@ void DialogsManager::showIRCLink() {
_ircInfoBox->raise();
}

View file

@ -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 <QScreen>
#include <QWindow>
#include <PathUtils.h>
#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());
}
}
}

View file

@ -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 <QDialog>
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

View file

@ -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 <QWidget>
#include <QPushButton>
#include <QPixmap>
#include <NetworkingConstants.h>
#include <PathUtils.h>
#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 <NetworkingConstants.h>
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) : OffscreenQmlDialog(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,39 @@ 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&) {
hide();
}
void LoginDialog::handleLoginFailed() {
setStatusText("<font color = \"#267077\">Invalid username or password.< / font>");
}
void LoginDialog::setStatusText(const QString& statusText) {
if (statusText != _statusText) {
_statusText = statusText;
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;
}

View file

@ -1,46 +1,49 @@
//
// 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
//
#pragma once
#ifndef hifi_LoginDialog_h
#define hifi_LoginDialog_h
#include <QObject>
#include "FramelessDialog.h"
#include <OffscreenQmlDialog.h>
namespace Ui {
class LoginDialog;
}
class LoginDialog : public FramelessDialog {
class LoginDialog : public OffscreenQmlDialog
{
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 = nullptr);
void setStatusText(const QString& statusText);
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

View file

@ -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)

View file

@ -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 <QOpenGLFramebufferObject>
#include <QDebug>
#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<QOpenGLFramebufferObject>(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<int> 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);
}
});
}

View file

@ -0,0 +1,51 @@
//
// 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 <QOpenGLContext>
#include <QOffscreenSurface>
#include <QQueue>
#include <QMap>
#include <QSharedPointer>
#include <QMutex>
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<int, QSharedPointer<QOpenGLFramebufferObject>> _fboMap;
QMap<int, int> _fboLocks;
QQueue<QOpenGLFramebufferObject*> _readyFboQueue;
QQueue<QSharedPointer<QOpenGLFramebufferObject>> _destroyFboQueue;
QMutex _lock;
QSize _size;
};
#endif // hifi_FboCache_h

View file

@ -0,0 +1,45 @@
//
// 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) {
if (nullptr != sharedContext) {
sharedContext->doneCurrent();
_context.setFormat(sharedContext->format());
_context.setShareContext(sharedContext);
} else {
QSurfaceFormat format;
format.setDepthBufferSize(16);
format.setStencilBufferSize(8);
format.setMajorVersion(4);
format.setMinorVersion(1);
format.setProfile(QSurfaceFormat::OpenGLContextProfile::CompatibilityProfile);
_context.setFormat(format);
}
_context.create();
_offscreenSurface.setFormat(_context.format());
_offscreenSurface.create();
}
bool OffscreenGlCanvas::makeCurrent() {
return _context.makeCurrent(&_offscreenSurface);
}
void OffscreenGlCanvas::doneCurrent() {
_context.doneCurrent();
}

View file

@ -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 <QOpenGLContext>
#include <QOffscreenSurface>
class OffscreenGlCanvas : public QObject {
public:
OffscreenGlCanvas();
void create(QOpenGLContext* sharedContext = nullptr);
bool makeCurrent();
void doneCurrent();
protected:
QOpenGLContext _context;
QOffscreenSurface _offscreenSurface;
};
#endif // hifi_OffscreenGlCanvas_h

View file

@ -0,0 +1,18 @@
//
// OffscreenQmlDialog.cpp
//
// 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 "OffscreenQmlDialog.h"
OffscreenQmlDialog::OffscreenQmlDialog(QQuickItem* parent)
: QQuickItem(parent) { }
void OffscreenQmlDialog::hide() {
static_cast<QQuickItem*>(parent())->setEnabled(false);
}

View file

@ -0,0 +1,56 @@
//
// OffscreenQmlDialog.h
//
// 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
//
#pragma once
#ifndef hifi_OffscreenQmlDialog_h
#define hifi_OffscreenQmlDialog_h
#include <QQuickItem>
#include "OffscreenUi.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 = QUrl(#x ".qml"); \
const QString x::NAME = #x; \
\
void x::registerType() { \
qmlRegisterType<x>("Hifi", 1, 0, NAME.toLocal8Bit().constData()); \
} \
\
void x::show() { \
auto offscreenUi = DependencyManager::get<OffscreenUi>(); \
offscreenUi->show(QML, NAME); \
} \
\
void x::toggle() { \
auto offscreenUi = DependencyManager::get<OffscreenUi>(); \
offscreenUi->toggle(QML, NAME); \
}
class OffscreenQmlDialog : public QQuickItem
{
Q_OBJECT
public:
OffscreenQmlDialog(QQuickItem* parent = nullptr);
protected:
void hide();
};
#endif

View file

@ -0,0 +1,406 @@
//
// OffscreenUi.cpp
// interface/src/render-utils
//
// 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
//
#include "OffscreenUi.h"
#include <QOpenGLFramebufferObject>
#include <QOpenGLDebugLogger>
#include <QGLWidget>
#include <QtQml>
// Time between receiving a request to render the offscreen UI actually triggering
// the render. Could possibly be increased depending on the framerate we expect to
// achieve.
static const int SMALL_INTERVAL = 5;
class OffscreenUiRoot : public QQuickItem {
Q_OBJECT
public:
OffscreenUiRoot(QQuickItem* parent = 0);
Q_INVOKABLE void information(const QString& title, const QString& text);
Q_INVOKABLE void loadChild(const QUrl& url) {
DependencyManager::get<OffscreenUi>()->load(url);
}
};
OffscreenUiRoot::OffscreenUiRoot(QQuickItem* parent) : QQuickItem(parent) {
}
void OffscreenUiRoot::information(const QString& title, const QString& text) {
OffscreenUi::information(title, text);
}
OffscreenUi::OffscreenUi() {
::qmlRegisterType<OffscreenUiRoot>("Hifi", 1, 0, "Root");
}
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::WindowFlags>(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(SMALL_INTERVAL);
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);
_quickWindow->focusObject();
_qmlComponent = new QQmlComponent(_qmlEngine);
// Initialize the render control and our OpenGL resources.
makeCurrent();
_renderControl->initialize(&_context);
}
void OffscreenUi::addImportPath(const QString& path) {
_qmlEngine->addImportPath(path);
}
void OffscreenUi::resize(const QSize& newSize) {
makeCurrent();
// Clear out any fbos with the old size
qreal pixelRatio = _renderControl->_renderWindow ? _renderControl->_renderWindow->devicePixelRatio() : 1.0;
qDebug() << "Offscreen UI resizing to " << newSize.width() << "x" << newSize.height() << " with pixel ratio " << pixelRatio;
_fboCache.setSize(newSize * pixelRatio);
// 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::setBaseUrl(const QUrl& baseUrl) {
_qmlEngine->setBaseUrl(baseUrl);
}
void OffscreenUi::load(const QUrl& qmlSource, std::function<void(QQmlContext*)> f) {
qDebug() << "Loading QML from URL " << qmlSource;
_qmlComponent->loadUrl(qmlSource);
if (_qmlComponent->isLoading()) {
connect(_qmlComponent, &QQmlComponent::statusChanged, this, []{});
} 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<QQmlError> errorList = _qmlComponent->errors();
foreach(const QQmlError &error, errorList) {
qWarning() << error.url() << error.line() << error;
}
return;
}
QObject* newObject = _qmlComponent->create();
if (_qmlComponent->isError()) {
QList<QQmlError> errorList = _qmlComponent->errors();
foreach(const QQmlError &error, errorList)
qWarning() << error.url() << error.line() << error;
if (!_rootItem) {
qFatal("Unable to finish loading QML root");
}
return;
}
QQuickItem* newItem = qobject_cast<QQuickItem*>(newObject);
if (!newItem) {
qWarning("run: Not a QQuickItem");
delete newObject;
if (!_rootItem) {
qFatal("Unable to find root QQuickItem");
}
return;
}
// Make sure we make items focusable (critical for
// supporting keyboard shortcuts)
newItem->setFlag(QQuickItem::ItemIsFocusScope, true);
if (!_rootItem) {
// The root item is ready. Associate it with the window.
_rootItem = newItem;
_rootItem->setParentItem(_quickWindow->contentItem());
_rootItem->setSize(_quickWindow->renderTargetSize());
} else {
// Allow child windows to be destroyed from JS
QQmlEngine::setObjectOwnership(newItem, QQmlEngine::JavaScriptOwnership);
newItem->setParent(_rootItem);
newItem->setParentItem(_rootItem);
newItem->setEnabled(true);
}
}
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& sourcePosition, QObject* sourceObject) {
vec2 sourceSize;
if (dynamic_cast<QWidget*>(sourceObject)) {
sourceSize = toGlm(((QWidget*)sourceObject)->size());
} else if (dynamic_cast<QWindow*>(sourceObject)) {
sourceSize = toGlm(((QWindow*)sourceObject)->size());
}
vec2 offscreenPosition = toGlm(sourcePosition);
offscreenPosition /= sourceSize;
offscreenPosition *= vec2(toGlm(_quickWindow->size()));
return QPointF(offscreenPosition.x, offscreenPosition.y);
}
///////////////////////////////////////////////////////
//
// Event handling customization
//
bool OffscreenUi::eventFilter(QObject* originalDestination, QEvent* event) {
// 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 (originalDestination == _quickWindow) {
return false;
}
switch (event->type()) {
case QEvent::Resize: {
QResizeEvent* resizeEvent = static_cast<QResizeEvent*>(event);
QGLWidget* widget = dynamic_cast<QGLWidget*>(originalDestination);
if (widget) {
this->resize(resizeEvent->size());
}
return false;
}
case QEvent::KeyPress:
case QEvent::KeyRelease: {
event->ignore();
if (QCoreApplication::sendEvent(_quickWindow, event)) {
return event->isAccepted();
}
break;
}
case QEvent::Wheel: {
QWheelEvent* wheelEvent = static_cast<QWheelEvent*>(event);
QWheelEvent mappedEvent(
mapWindowToUi(wheelEvent->pos(), originalDestination),
wheelEvent->delta(), wheelEvent->buttons(),
wheelEvent->modifiers(), wheelEvent->orientation());
QCoreApplication::sendEvent(_quickWindow, &mappedEvent);
return true;
}
// Fall through
case QEvent::MouseButtonDblClick:
case QEvent::MouseButtonPress:
case QEvent::MouseButtonRelease:
case QEvent::MouseMove: {
QMouseEvent* mouseEvent = static_cast<QMouseEvent*>(event);
QPointF originalPos = mouseEvent->localPos();
QPointF transformedPos = _mouseTranslator(originalPos);
QMouseEvent mappedEvent(mouseEvent->type(),
mapWindowToUi(transformedPos, originalDestination),
mouseEvent->screenPos(), mouseEvent->button(),
mouseEvent->buttons(), mouseEvent->modifiers());
QCoreApplication::sendEvent(_quickWindow, &mappedEvent);
return QObject::event(event);
}
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<QQuickItem*>(name);
// First load?
if (!item) {
load(url);
return;
}
item->setEnabled(true);
}
void OffscreenUi::toggle(const QUrl& url, const QString& name) {
QQuickItem* item = _rootItem->findChild<QQuickItem*>(name);
// First load?
if (!item) {
load(url);
return;
}
item->setEnabled(!item->isEnabled());
}
void OffscreenUi::messageBox(const QString& title, const QString& text,
QMessageBox::Icon icon,
QMessageBox::StandardButtons buttons,
ButtonCallback f) {
}
void OffscreenUi::information(const QString& title, const QString& text,
QMessageBox::StandardButtons buttons,
ButtonCallback callback) {
callback(QMessageBox::information(nullptr, title, text, buttons));
}
void OffscreenUi::question(const QString& title, const QString& text,
QMessageBox::StandardButtons buttons,
ButtonCallback callback) {
callback(QMessageBox::question(nullptr, title, text, buttons));
}
void OffscreenUi::warning(const QString& title, const QString& text,
QMessageBox::StandardButtons buttons,
ButtonCallback callback) {
callback(QMessageBox::warning(nullptr, title, text, buttons));
}
void OffscreenUi::critical(const QString& title, const QString& text,
QMessageBox::StandardButtons buttons,
ButtonCallback callback) {
callback(QMessageBox::critical(nullptr, title, text, buttons));
}
OffscreenUi::ButtonCallback OffscreenUi::NO_OP_CALLBACK = [](QMessageBox::StandardButton) {};
#include "OffscreenUi.moc"

View file

@ -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 <QQmlEngine>
#include <QQmlComponent>
#include <QQuickItem>
#include <QQuickWindow>
#include <QQuickRenderControl>
#include <QQuickImageProvider>
#include <QTimer>
#include <QMessageBox>
#include <atomic>
#include <functional>
#include <GLMHelpers.h>
#include <ThreadHelpers.h>
#include <DependencyManager.h>
#include "OffscreenGlCanvas.h"
#include "FboCache.h"
#include <QQuickItem>
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 load(const QUrl& qmlSource, std::function<void(QQmlContext*)> f = [](QQmlContext*) {});
void load(const QString& qmlSourceFile, std::function<void(QQmlContext*)> f = [](QQmlContext*) {}) {
load(QUrl(qmlSourceFile), f);
}
void show(const QUrl& url, const QString& name);
void toggle(const QUrl& url, const QString& name);
void setBaseUrl(const QUrl& baseUrl);
void addImportPath(const QString& path);
QQmlContext* qmlContext();
void pause();
void resume();
bool isPaused() const;
void setProxyWindow(QWindow* window);
QPointF mapWindowToUi(const QPointF& sourcePosition, QObject* sourceObject);
virtual bool eventFilter(QObject* originalDestination, QEvent* event);
void setMouseTranslator(MouseTranslator mouseTranslator) {
_mouseTranslator = mouseTranslator;
}
// Messagebox replacement functions
using ButtonCallback = std::function<void(QMessageBox::StandardButton)>;
static ButtonCallback NO_OP_CALLBACK;
static void messageBox(const QString& title, const QString& text,
QMessageBox::Icon icon,
QMessageBox::StandardButtons buttons,
ButtonCallback f);
static void information(const QString& title, const QString& text,
QMessageBox::StandardButtons buttons = QMessageBox::Ok,
ButtonCallback callback = NO_OP_CALLBACK);
static void question(const QString& title, const QString& text,
QMessageBox::StandardButtons buttons = QMessageBox::StandardButtons(QMessageBox::Yes | QMessageBox::No),
ButtonCallback callback = [](QMessageBox::StandardButton) {});
static void warning(const QString& title, const QString& text,
QMessageBox::StandardButtons buttons = QMessageBox::Ok,
ButtonCallback callback = [](QMessageBox::StandardButton) {});
static void critical(const QString& title, const QString& text,
QMessageBox::StandardButtons buttons = QMessageBox::Ok,
ButtonCallback callback = [](QMessageBox::StandardButton) {});
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

View file

@ -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 <typename F, GLenum matrix>
void withMatrixPush(F f) {
glMatrixMode(matrix);
glPushMatrix();
f();
glPopMatrix();
}
template <typename F>
void withProjectionPush(F f) {
withMatrixPush<GL_PROJECTION>(f);
}
template <typename F>
void withProjectionIdentity(F f) {
withProjectionPush([&] {
glLoadIdentity();
f();
});
}
template <typename F>
void withProjectionMatrix(GLfloat* matrix, F f) {
withProjectionPush([&] {
glLoadMatrixf(matrix);
f();
});
}
template <typename F>
void withModelviewPush(F f) {
withMatrixPush<GL_MODELVIEW>(f);
}
template <typename F>
void withModelviewIdentity(F f) {
withModelviewPush([&] {
glLoadIdentity();
f();
});
}
template <typename F>
void withModelviewMatrix(GLfloat* matrix, F f) {
withModelviewPush([&] {
glLoadMatrixf(matrix);
f();
});
}
#endif // hifi_RenderUtil_h

View file

@ -17,6 +17,17 @@
#include <glm/glm.hpp>
#include <glm/gtc/quaternion.hpp>
// 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 <QtCore/QByteArray>
#include <QtGui/QMatrix4x4>
#include <QtGui/QColor>

View file

@ -0,0 +1,29 @@
//
// ThreadHelpers.h
//
// 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_ThreadHelpers_h
#define hifi_ThreadHelpers_h
#include <exception>
#include <QMutex>
#include <QMutexLocker>
template <typename L, typename F>
void withLock(L lock, F function) {
throw std::exception();
}
template <typename F>
void withLock(QMutex& lock, F function) {
QMutexLocker locker(&lock);
function();
}
#endif

View file

@ -10,6 +10,7 @@
#include "TextRenderer.h"
#include "MatrixStack.h"
#include "OffscreenUi.h"
#include <QWindow>
#include <QFile>
@ -21,13 +22,16 @@
#include <QOpenGLBuffer>
#include <QOpenGLShaderProgram>
#include <QResizeEvent>
#include <QLoggingCategory>
#include <QOpenGLTexture>
#include <QOpenGLVertexArrayObject>
#include <QApplication>
#include <QOpenGLDebugLogger>
#include <unordered_map>
#include <memory>
#include <glm/glm.hpp>
#include <PathUtils.h>
#include <QDir>
class RateCounter {
std::vector<float> times;
@ -65,115 +69,191 @@ public:
}
};
const QString& getQmlDir() {
static QString dir;
if (dir.isEmpty()) {
QDir path(__FILE__);
path.cdUp();
dir = path.cleanPath(path.absoluteFilePath("../../../interface/resources/qml/")) + "/";
qDebug() << "Qml Path: " << dir;
}
return dir;
}
// 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;
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;
DependencyManager::get<OffscreenUi>()->resize(_size);
}
public:
QTestWindow();
virtual ~QTestWindow() {
QTestWindow() {
DependencyManager::set<OffscreenUi>();
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);
auto offscreenUi = DependencyManager::get<OffscreenUi>();
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));
offscreenUi->setBaseUrl(QUrl::fromLocalFile(getQmlDir()));
offscreenUi->load(QUrl("TestRoot.qml"));
offscreenUi->addImportPath(getQmlDir());
offscreenUi->addImportPath(".");
connect(offscreenUi.data(), &OffscreenUi::textureUpdated, this, [this, offscreenUi](int textureId) {
offscreenUi->lockTexture(textureId);
assert(!glGetError());
GLuint oldTexture = testQmlTexture;
testQmlTexture = textureId;
if (oldTexture) {
offscreenUi->releaseTexture(oldTexture);
}
});
installEventFilter(offscreenUi.data());
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_L:
if (event->modifiers() & Qt::CTRL) {
DependencyManager::get<OffscreenUi>()->toggle(QString("TestDialog.qml"), "TestDialog");
}
break;
}
QWindow::keyPressEvent(event);
}
void moveEvent(QMoveEvent* event) {
static qreal oldPixelRatio = 0.0;
if (devicePixelRatio() != oldPixelRatio) {
oldPixelRatio = devicePixelRatio();
resizeWindow(size());
}
QWindow::moveEvent(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 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);
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,8 +280,45 @@ 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() * devicePixelRatio(), _size.height() * devicePixelRatio());
//renderText();
renderQml();
_context->swapBuffers(this);
glFinish();
fps.increment();
if (fps.elapsed() >= 2.0f) {
qDebug() << "FPS: " << fps.rate();
@ -211,7 +328,9 @@ void QTestWindow::draw() {
int main(int argc, char** argv) {
QApplication app(argc, argv);
//QLoggingCategory::setFilterRules("qt.quick.mouse.debug = true");
QTestWindow window;
QTimer timer;
timer.setInterval(1);
app.connect(&timer, &QTimer::timeout, &app, [&] {