Breaking up offscreen UI work

This commit is contained in:
Brad Davis 2015-04-24 13:21:21 -07:00
parent a4619c8e59
commit 99a6e1f86c
60 changed files with 2847 additions and 540 deletions

View file

@ -128,7 +128,7 @@ target_link_libraries(${TARGET_NAME} ${BULLET_LIBRARIES})
# link required hifi libraries # link required hifi libraries
link_hifi_libraries(shared octree environment gpu model fbx networking entities avatars link_hifi_libraries(shared octree environment gpu model fbx networking entities avatars
audio audio-client animation script-engine physics audio audio-client animation script-engine physics
render-utils entities-renderer) render-utils entities-renderer ui)
add_dependency_external_projects(sdl2) add_dependency_external_projects(sdl2)

Binary file not shown.

View file

@ -1,10 +1,9 @@
import Hifi 1.0 import Hifi 1.0
import QtQuick 2.3 import QtQuick 2.3
import QtQuick.Controls 1.2 import "controls"
import QtQuick.Window 2.2 import "styles"
import QtQuick.Controls.Styles 1.3
CustomDialog { Dialog {
title: "Go to..." title: "Go to..."
objectName: "AddressBarDialog" objectName: "AddressBarDialog"
height: 128 height: 128
@ -36,14 +35,14 @@ CustomDialog {
anchors.margins: parent.margins anchors.margins: parent.margins
anchors.topMargin: parent.topMargin anchors.topMargin: parent.topMargin
CustomBorder { Border {
height: 64 height: 64
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: 0 anchors.leftMargin: 0
anchors.right: goButton.left anchors.right: goButton.left
anchors.rightMargin: 8 anchors.rightMargin: 8
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
CustomTextInput { TextInput {
id: addressLine id: addressLine
anchors.fill: parent anchors.fill: parent
helperText: "domain, location, @user, /x,y,z" helperText: "domain, location, @user, /x,y,z"

View file

@ -1,12 +1,10 @@
import QtQuick 2.3 import QtQuick 2.3
import QtQuick.Controls 1.2 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 import QtWebKit 3.0
import "controls"
CustomDialog { Dialog {
title: "Test Dlg" title: "Browser Window"
id: testDialog id: testDialog
objectName: "Browser" objectName: "Browser"
width: 1280 width: 1280
@ -18,7 +16,6 @@ CustomDialog {
anchors.fill: parent anchors.fill: parent
anchors.margins: parent.margins anchors.margins: parent.margins
anchors.topMargin: parent.topMargin anchors.topMargin: parent.topMargin
ScrollView { ScrollView {
anchors.fill: parent anchors.fill: parent
@ -30,16 +27,4 @@ CustomDialog {
} }
} }
} }
/*
// This is the behavior, and it applies a NumberAnimation to any attempt to set the x property
MouseArea {
anchors.fill: parent
}
*/

View file

@ -1,23 +0,0 @@
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

@ -1,10 +0,0 @@
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,20 @@
import QtQuick 2.4
import QtQuick.Controls 1.3
Action {
property string name
objectName: name + "HifiAction"
text: qsTr(name)
signal triggeredByName(string name);
signal toggledByName(string name);
onTriggered: {
triggeredByName(name);
}
onToggled: {
toggledByName(name, checked);
}
}

View file

@ -0,0 +1,272 @@
import Hifi 1.0 as Hifi
import QtQuick 2.4
import QtQuick.Controls 1.3
import QtQuick.Controls.Styles 1.3
import "controls"
import "styles"
Hifi.HifiMenu {
id: root
anchors.fill: parent
objectName: "HifiMenu"
enabled: false
opacity: 0.0
property int animationDuration: 200
HifiPalette { id: hifiPalette }
z: 10000
onEnabledChanged: {
if (enabled && columns.length == 0) {
pushColumn(rootMenu.items);
}
opacity = enabled ? 1.0 : 0.0
if (enabled) {
forceActiveFocus()
}
}
// The actual animator
Behavior on opacity {
NumberAnimation {
duration: root.animationDuration
easing.type: Easing.InOutBounce
}
}
onOpacityChanged: {
visible = (opacity != 0.0);
}
onVisibleChanged: {
if (!visible) reset();
}
property var menu: Menu {}
property var models: []
property var columns: []
property var itemBuilder: Component {
Text {
SystemPalette { id: sp; colorGroup: SystemPalette.Active }
id: thisText
x: 32
property var source
property var root
property var listViewIndex
property var listView
text: typedText()
height: implicitHeight
width: implicitWidth
color: source.enabled ? "black" : "gray"
onImplicitWidthChanged: {
if (listView) {
listView.minWidth = Math.max(listView.minWidth, implicitWidth + 64);
listView.recalculateSize();
}
}
FontAwesome {
visible: source.type == 1 && source.checkable
x: -32
text: (source.type == 1 && source.checked) ? "\uF05D" : "\uF10C"
}
FontAwesome {
visible: source.type == 2
x: listView.width - 64
text: "\uF0DA"
}
function typedText() {
switch(source.type) {
case 2:
return source.title;
case 1:
return source.text;
case 0:
return "-----"
}
}
MouseArea {
id: mouseArea
acceptedButtons: Qt.LeftButton
anchors.bottom: parent.bottom
anchors.bottomMargin: 0
anchors.top: parent.top
anchors.topMargin: 0
width: listView.width
onClicked: {
listView.currentIndex = listViewIndex
parent.root.selectItem(parent.source);
}
}
}
}
property var menuBuilder: Component {
Border {
SystemPalette { id: sysPalette; colorGroup: SystemPalette.Active }
x: root.models.length == 1 ?
(root.width / 2 - width / 2) :
root.columns[root.models.length - 2].x + 60;
anchors.verticalCenter: parent.verticalCenter
border.color: hifiPalette.hifiBlue
color: sysPalette.window
ListView {
spacing: 6
property int outerMargin: 8
property real minWidth: 0
anchors.fill: parent
anchors.margins: outerMargin
id: listView
height: root.height
currentIndex: -1
onCountChanged: {
recalculateSize()
}
function recalculateSize() {
var newHeight = 0
var newWidth = minWidth;
for (var i = 0; i < children.length; ++i) {
var item = children[i];
newHeight += item.height
}
parent.height = newHeight + outerMargin * 2;
parent.width = newWidth + outerMargin * 2
}
highlight: Rectangle {
width: listView.minWidth; height: 32
color: sysPalette.highlight
y: (listView.currentItem) ? listView.currentItem.y : 0;
x: 32
Behavior on y {
NumberAnimation {
duration: 100
easing.type: Easing.InOutQuint
}
}
}
property int columnIndex: root.models.length - 1
model: root.models[columnIndex]
delegate: Loader {
id: loader
sourceComponent: root.itemBuilder
Binding {
target: loader.item
property: "root"
value: root
when: loader.status == Loader.Ready
}
Binding {
target: loader.item
property: "source"
value: modelData
when: loader.status == Loader.Ready
}
Binding {
target: loader.item
property: "listViewIndex"
value: index
when: loader.status == Loader.Ready
}
Binding {
target: loader.item
property: "listView"
value: listView
when: loader.status == Loader.Ready
}
}
}
}
}
function lastColumn() {
return columns[root.columns.length - 1];
}
function pushColumn(items) {
models.push(items)
if (columns.length) {
var oldColumn = lastColumn();
oldColumn.enabled = false;
oldColumn.opacity = 0.5;
}
var newColumn = menuBuilder.createObject(root);
columns.push(newColumn);
newColumn.forceActiveFocus();
}
function popColumn() {
if (columns.length > 0) {
var curColumn = columns.pop();
console.log(curColumn);
curColumn.visible = false;
curColumn.destroy();
models.pop();
}
if (columns.length == 0) {
enabled = false;
return;
}
curColumn = lastColumn();
curColumn.enabled = true;
curColumn.opacity = 1.0;
curColumn.forceActiveFocus();
}
function selectItem(source) {
switch (source.type) {
case 2:
pushColumn(source.items)
break;
case 1:
source.trigger()
enabled = false
break;
case 0:
break;
}
}
function reset() {
while (columns.length > 0) {
popColumn();
}
}
Keys.onPressed: {
switch (event.key) {
case Qt.Key_Escape:
root.popColumn()
event.accepted = true;
}
}
MouseArea {
anchors.fill: parent
id: mouseArea
acceptedButtons: Qt.LeftButton | Qt.RightButton
onClicked: {
if (mouse.button == Qt.RightButton) {
root.popColumn();
} else {
root.enabled = false;
}
}
}
}

View file

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

View file

@ -1,12 +1,12 @@
import Hifi 1.0 import Hifi 1.0
import QtQuick 2.3 import QtQuick 2.3
import QtQuick.Controls 1.2
import QtQuick.Window 2.2
import QtQuick.Controls.Styles 1.3 import QtQuick.Controls.Styles 1.3
import "hifiConstants.js" as HifiConstants import "controls"
import "styles"
CustomDialog { Dialog {
title: "Login" title: "Login"
HifiPalette { id: hifiPalette }
SystemPalette { id: myPalette; colorGroup: SystemPalette.Active } SystemPalette { id: myPalette; colorGroup: SystemPalette.Active }
objectName: "LoginDialog" objectName: "LoginDialog"
height: 512 height: 512
@ -50,11 +50,11 @@ CustomDialog {
source: "../images/hifi-logo.svg" source: "../images/hifi-logo.svg"
} }
CustomBorder { Border {
width: 304 width: 304
height: 64 height: 64
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
CustomTextInput { TextInput {
id: username id: username
anchors.fill: parent anchors.fill: parent
helperText: "Username or Email" helperText: "Username or Email"
@ -67,11 +67,11 @@ CustomDialog {
} }
} }
CustomBorder { Border {
width: 304 width: 304
height: 64 height: 64
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
CustomTextInput { TextInput {
id: password id: password
anchors.fill: parent anchors.fill: parent
echoMode: TextInput.Password echoMode: TextInput.Password
@ -94,7 +94,7 @@ CustomDialog {
} }
} }
CustomText { Text {
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
textFormat: Text.StyledText textFormat: Text.StyledText
width: parent.width width: parent.width
@ -117,7 +117,7 @@ CustomDialog {
width: 192 width: 192
height: 64 height: 64
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
color: HifiConstants.color color: hifiPalette.hifiBlue
border.width: 0 border.width: 0
radius: 10 radius: 10
@ -142,7 +142,7 @@ CustomDialog {
width: 32 width: 32
source: "../images/login.svg" source: "../images/login.svg"
} }
CustomText { Text {
text: "Login" text: "Login"
color: "white" color: "white"
width: 64 width: 64
@ -152,7 +152,7 @@ CustomDialog {
} }
CustomText { Text {
width: parent.width width: parent.width
height: 24 height: 24
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
@ -160,7 +160,7 @@ CustomDialog {
text:"Create Account" text:"Create Account"
font.pointSize: 12 font.pointSize: 12
font.bold: true font.bold: true
color: HifiConstants.color color: hifiPalette.hifiBlue
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
@ -170,14 +170,14 @@ CustomDialog {
} }
} }
CustomText { Text {
width: parent.width width: parent.width
height: 24 height: 24
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
font.pointSize: 12 font.pointSize: 12
text: "Recover Password" text: "Recover Password"
color: HifiConstants.color color: hifiPalette.hifiBlue
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent

View file

@ -0,0 +1,50 @@
import Hifi 1.0
import QtQuick 2.3
import QtQuick.Controls 1.2
import QtQuick.Controls.Styles 1.3
import QtWebKit 3.0
import "controls"
Dialog {
title: "Test Dlg"
id: testDialog
objectName: "Browser"
width: 720
height: 720
resizable: true
MarketplaceDialog {
id: marketplaceDialog
}
Item {
id: clientArea
// The client area
anchors.fill: parent
anchors.margins: parent.margins
anchors.topMargin: parent.topMargin
ScrollView {
anchors.fill: parent
WebView {
objectName: "WebView"
id: webview
url: "https://metaverse.highfidelity.com/marketplace"
anchors.fill: parent
onNavigationRequested: {
console.log(request.url)
if (!marketplaceDialog.navigationRequested(request.url)) {
console.log("Application absorbed the request")
request.action = WebView.IgnoreRequest;
return;
}
console.log("Application passed on the request")
request.action = WebView.AcceptRequest;
return;
}
}
}
}
}

View file

@ -0,0 +1,359 @@
/*****************************************************************************
**
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** This file is part of the QtQuick.Dialogs module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:BSD$
** You may use this file under the terms of the BSD license as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
** * Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** * Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in
** the documentation and/or other materials provided with the
** distribution.
** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names
** of its contributors may be used to endorse or promote products derived
** from this software without specific prior written permission.
**
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
** $QT_END_LICENSE$
**
*****************************************************************************/
import Hifi 1.0 as Hifi
import QtQuick 2.2
import QtQuick.Controls 1.2
import QtQuick.Dialogs 1.2
import "controls"
Dialog {
id: root
property real spacing: 8
property real outerSpacing: 16
destroyOnCloseButton: true
destroyOnInvisible: true
implicitHeight: content.implicitHeight + outerSpacing * 2 + 48
implicitWidth: Math.min(200, Math.max(mainText.implicitWidth, content.buttonsRowImplicitWidth) + outerSpacing * 2);
onImplicitHeightChanged: root.height = implicitHeight
onImplicitWidthChanged: root.width = implicitWidth
SystemPalette { id: palette }
function calculateImplicitWidth() {
if (buttons.visibleChildren.length < 2)
return;
var calcWidth = 0;
for (var i = 0; i < buttons.visibleChildren.length; ++i) {
calcWidth += Math.max(100, buttons.visibleChildren[i].implicitWidth) + root.spacing
}
content.buttonsRowImplicitWidth = outerSpacing + calcWidth + 48
}
onEnabledChanged: {
if (enabled) {
content.forceActiveFocus();
}
}
Hifi.MessageDialog {
id: content
clip: true
anchors.fill: parent
anchors.topMargin: parent.topMargin + root.outerSpacing
anchors.leftMargin: parent.margins + root.outerSpacing
anchors.rightMargin: parent.margins + root.outerSpacing
anchors.bottomMargin: parent.margins + root.outerSpacing
implicitHeight: contentColumn.implicitHeight + outerSpacing * 2
implicitWidth: Math.max(mainText.implicitWidth, buttonsRowImplicitWidth);
property real buttonsRowImplicitWidth: Screen.pixelDensity * 50
Keys.onPressed: {
console.log("Key press at content")
event.accepted = true
if (event.modifiers === Qt.ControlModifier)
switch (event.key) {
case Qt.Key_A:
console.log("Select All")
detailedText.selectAll()
break
case Qt.Key_C:
console.log("Copy")
detailedText.copy()
break
case Qt.Key_Period:
if (Qt.platform.os === "osx")
reject()
break
} else switch (event.key) {
case Qt.Key_Escape:
case Qt.Key_Back:
console.log("Rejecting")
reject()
break
case Qt.Key_Enter:
case Qt.Key_Return:
console.log("Accepting")
accept()
break
}
}
onImplicitWidthChanged: root.width = implicitWidth
Component.onCompleted: {
root.title = title
}
onTitleChanged: {
root.title = title
}
Column {
id: contentColumn
spacing: root.outerSpacing
anchors {
top: parent.top
left: parent.left
right: parent.right
}
Item {
width: parent.width
height: Math.max(icon.height, mainText.height + informativeText.height + root.spacing)
Image {
id: icon
source: content.standardIconSource
}
Text {
id: mainText
anchors {
left: icon.right
leftMargin: root.spacing
right: parent.right
}
text: content.text
font.pointSize: 14
font.weight: Font.Bold
wrapMode: Text.WordWrap
}
Text {
id: informativeText
anchors {
left: icon.right
right: parent.right
top: mainText.bottom
leftMargin: root.spacing
topMargin: root.spacing
}
text: content.informativeText
font.pointSize: 14
wrapMode: Text.WordWrap
}
}
Flow {
id: buttons
spacing: root.spacing
layoutDirection: Qt.RightToLeft
width: parent.width
Button {
id: okButton
text: qsTr("OK")
onClicked: content.click(StandardButton.Ok)
visible: content.standardButtons & StandardButton.Ok
}
Button {
id: openButton
text: qsTr("Open")
onClicked: content.click(StandardButton.Open)
visible: content.standardButtons & StandardButton.Open
}
Button {
id: saveButton
text: qsTr("Save")
onClicked: content.click(StandardButton.Save)
visible: content.standardButtons & StandardButton.Save
}
Button {
id: saveAllButton
text: qsTr("Save All")
onClicked: content.click(StandardButton.SaveAll)
visible: content.standardButtons & StandardButton.SaveAll
}
Button {
id: retryButton
text: qsTr("Retry")
onClicked: content.click(StandardButton.Retry)
visible: content.standardButtons & StandardButton.Retry
}
Button {
id: ignoreButton
text: qsTr("Ignore")
onClicked: content.click(StandardButton.Ignore)
visible: content.standardButtons & StandardButton.Ignore
}
Button {
id: applyButton
text: qsTr("Apply")
onClicked: content.click(StandardButton.Apply)
visible: content.standardButtons & StandardButton.Apply
}
Button {
id: yesButton
text: qsTr("Yes")
onClicked: content.click(StandardButton.Yes)
visible: content.standardButtons & StandardButton.Yes
}
Button {
id: yesAllButton
text: qsTr("Yes to All")
onClicked: content.click(StandardButton.YesToAll)
visible: content.standardButtons & StandardButton.YesToAll
}
Button {
id: noButton
text: qsTr("No")
onClicked: content.click(StandardButton.No)
visible: content.standardButtons & StandardButton.No
}
Button {
id: noAllButton
text: qsTr("No to All")
onClicked: content.click(StandardButton.NoToAll)
visible: content.standardButtons & StandardButton.NoToAll
}
Button {
id: discardButton
text: qsTr("Discard")
onClicked: content.click(StandardButton.Discard)
visible: content.standardButtons & StandardButton.Discard
}
Button {
id: resetButton
text: qsTr("Reset")
onClicked: content.click(StandardButton.Reset)
visible: content.standardButtons & StandardButton.Reset
}
Button {
id: restoreDefaultsButton
text: qsTr("Restore Defaults")
onClicked: content.click(StandardButton.RestoreDefaults)
visible: content.standardButtons & StandardButton.RestoreDefaults
}
Button {
id: cancelButton
text: qsTr("Cancel")
onClicked: content.click(StandardButton.Cancel)
visible: content.standardButtons & StandardButton.Cancel
}
Button {
id: abortButton
text: qsTr("Abort")
onClicked: content.click(StandardButton.Abort)
visible: content.standardButtons & StandardButton.Abort
}
Button {
id: closeButton
text: qsTr("Close")
onClicked: content.click(StandardButton.Close)
visible: content.standardButtons & StandardButton.Close
}
Button {
id: moreButton
text: qsTr("Show Details...")
onClicked: content.state = (content.state === "" ? "expanded" : "")
visible: content.detailedText.length > 0
}
Button {
id: helpButton
text: qsTr("Help")
onClicked: content.click(StandardButton.Help)
visible: content.standardButtons & StandardButton.Help
}
onVisibleChildrenChanged: root.calculateImplicitWidth()
}
}
Item {
id: details
width: parent.width
implicitHeight: detailedText.implicitHeight + root.spacing
height: 0
clip: true
anchors {
left: parent.left
right: parent.right
top: contentColumn.bottom
topMargin: root.spacing
leftMargin: root.outerSpacing
rightMargin: root.outerSpacing
}
Flickable {
id: flickable
contentHeight: detailedText.height
anchors.fill: parent
anchors.topMargin: root.spacing
anchors.bottomMargin: root.outerSpacing
TextEdit {
id: detailedText
text: content.detailedText
width: details.width
wrapMode: Text.WordWrap
readOnly: true
selectByMouse: true
}
}
}
states: [
State {
name: "expanded"
PropertyChanges {
target: details
height: root.height - contentColumn.height - root.spacing - root.outerSpacing
}
PropertyChanges {
target: content
implicitHeight: contentColumn.implicitHeight + root.spacing * 2 +
detailedText.implicitHeight + root.outerSpacing * 2
}
PropertyChanges {
target: moreButton
text: qsTr("Hide Details")
}
}
]
/*
Rectangle {
}
Component.onCompleted: calculateImplicitWidth()
*/
}
}

View file

@ -1,8 +1,5 @@
import QtQuick 2.3 import QtQuick 2.3
import QtQuick.Controls 1.2 import QtQuick.Controls 1.2
import QtQuick.Window 2.2
import QtQuick.Dialogs 1.2
import QtQuick.Controls.Styles 1.3
Rectangle { Rectangle {
color: "teal" color: "teal"
@ -150,87 +147,4 @@ Rectangle {
Rectangle { height: parent.height; width: 16; color: spd.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

@ -1,9 +1,10 @@
import Hifi 1.0 import Hifi 1.0
import QtQuick 2.3 import QtQuick 2.3
// This is our primary 'window' object to which all dialogs and controls will
// be childed.
Root { Root {
id: root id: root
width: 1280 anchors.fill: parent
height: 720
} }

View file

@ -0,0 +1,9 @@
import QtQuick 2.4
import QtQuick.Controls 1.3
Item {
Menu {
id: root
objectName: "rootMenu"
}
}

View file

@ -1,10 +1,9 @@
import QtQuick 2.3 import QtQuick 2.3
import QtQuick.Controls 1.2 import QtQuick.Controls 1.2
import QtQuick.Window 2.2
import QtQuick.Dialogs 1.2
import QtQuick.Controls.Styles 1.3 import QtQuick.Controls.Styles 1.3
import "controls"
CustomDialog { Dialog {
title: "Test Dialog" title: "Test Dialog"
id: testDialog id: testDialog
objectName: "TestDialog" objectName: "TestDialog"
@ -37,7 +36,7 @@ CustomDialog {
} }
CustomTextEdit { TextEdit {
id: edit id: edit
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: 12 anchors.leftMargin: 12
@ -49,7 +48,7 @@ CustomDialog {
anchors.topMargin: 12 anchors.topMargin: 12
} }
CustomButton { Button {
x: 128 x: 128
y: 192 y: 192
text: "Test" text: "Test"
@ -68,7 +67,7 @@ CustomDialog {
} }
} }
CustomButton { Button {
id: customButton2 id: customButton2
y: 192 y: 192
text: "Move" text: "Move"
@ -92,15 +91,4 @@ CustomDialog {
} }
} }
} }
} }
/*
// This is the behavior, and it applies a NumberAnimation to any attempt to set the x property
MouseArea {
anchors.fill: parent
}
*/

View file

@ -1,12 +1,27 @@
import Hifi 1.0 import Hifi 1.0
import QtQuick 2.3 import QtQuick 2.3
import QtQuick.Controls 1.3
// Import local folder last so that our own control customizations override
// the built in ones
import "controls"
Root { Root {
id: root id: root
width: 1280 anchors.fill: parent
height: 720
CustomButton { onWidthChanged: {
console.log("Root width: " + width)
}
onHeightChanged: {
console.log("Root height: " + height)
}
Component.onCompleted: {
console.log("Completed root")
root.forceActiveFocus()
}
Button {
id: messageBox id: messageBox
anchors.right: createDialog.left anchors.right: createDialog.left
anchors.rightMargin: 24 anchors.rightMargin: 24
@ -20,7 +35,7 @@ Root {
} }
} }
CustomButton { Button {
id: createDialog id: createDialog
anchors.right: parent.right anchors.right: parent.right
anchors.rightMargin: 24 anchors.rightMargin: 24
@ -28,8 +43,12 @@ Root {
anchors.bottomMargin: 24 anchors.bottomMargin: 24
text: "Create" text: "Create"
onClicked: { onClicked: {
root.loadChild("TestDialog.qml"); root.loadChild("MenuTest.qml");
} }
} }
Keys.onPressed: {
console.log(event.key);
}
} }

View file

@ -1,29 +0,0 @@
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,10 @@
import QtQuick 2.3
import QtQuick.Controls 1.3 as Original
import QtQuick.Controls.Styles 1.3
import "."
import "../styles"
Original.Button {
style: ButtonStyle {
}
}

View file

@ -1,40 +1,79 @@
import QtQuick 2.3 import QtQuick 2.3
import QtQuick.Controls 1.2 import QtQuick.Controls 1.2
import QtQuick.Window 2.2 import "."
import QtQuick.Dialogs 1.2 import "../styles"
import QtQuick.Controls.Styles 1.3
import "hifiConstants.js" as HifiConstants
/*
* FIXME Need to create a client property here so that objects can be
* placed in it without having to think about positioning within the outer
* window.
*
* Examine the QML ApplicationWindow.qml source for how it does this
*
*/
Item { Item {
SystemPalette { id: myPalette; colorGroup: SystemPalette.Active } id: root
id: dialog
width: 256 HifiPalette { id: hifiPalette }
height: 256 SystemPalette { id: sysPalette; colorGroup: SystemPalette.Active }
scale: 0.0 x: parent ? parent.width / 2 - width / 2 : 0
enabled: false y: parent ? parent.height / 2 - height / 2 : 0
property int animationDuration: 400 property int animationDuration: 400
property bool destroyOnInvisible: false property bool destroyOnInvisible: false
property bool destroyOnCloseButton: true property bool destroyOnCloseButton: true
property bool resizable: false property bool resizable: false
property int minX: 256 property int minX: 256
property int minY: 256 property int minY: 256
property int topMargin: root.height - clientBorder.height + 8
property int margins: 8
property string title
property int titleSize: titleBorder.height + 12
property string frameColor: hifiPalette.hifiBlue
property string backgroundColor: sysPalette.window
property string headerBackgroundColor: sysPalette.dark
clip: true clip: true
/*
* Support for animating the dialog in and out.
*/
enabled: false
scale: 0.0
// The offscreen UI will enable an object, rather than manipulating it's
// visibility, so that we can do animations in both directions. Because
// visibility and enabled are boolean flags, they cannot be animated. So when
// enabled is change, we modify a property that can be animated, like scale or
// opacity.
onEnabledChanged: { onEnabledChanged: {
scale = enabled ? 1.0 : 0.0 scale = enabled ? 1.0 : 0.0
} }
// The actual animator
Behavior on scale {
NumberAnimation {
duration: root.animationDuration
easing.type: Easing.InOutBounce
}
}
// We remove any load the dialog might have on the QML by toggling it's
// visibility based on the state of the animated property
onScaleChanged: { onScaleChanged: {
visible = (scale != 0.0); visible = (scale != 0.0);
} }
// Some dialogs should be destroyed when they become invisible, so handle that
onVisibleChanged: { onVisibleChanged: {
if (!visible && destroyOnInvisible) { if (!visible && destroyOnInvisible) {
console.log("Destroying closed component");
destroy(); destroy();
} }
} }
// our close function performs the same way as the OffscreenUI class:
// don't do anything but manipulate the enabled flag and let the other
// mechanisms decide if the window should be destoryed after the close
// animation completes
function close() { function close() {
if (destroyOnCloseButton) { if (destroyOnCloseButton) {
destroyOnInvisible = true destroyOnInvisible = true
@ -42,102 +81,14 @@ Item {
enabled = false; enabled = false;
} }
/*
* Resize support
*/
function deltaSize(dx, dy) { function deltaSize(dx, dy) {
width = Math.max(width + dx, minX) width = Math.max(width + dx, minX)
height = Math.max(height + dy, minY) 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 { MouseArea {
id: sizeDrag id: sizeDrag
property int startX property int startX
@ -152,11 +103,91 @@ Item {
startY = mouseY startY = mouseY
} }
onPositionChanged: { onPositionChanged: {
if (pressed && dialog.resizable) { if (pressed && root.resizable) {
dialog.deltaSize((mouseX - startX), (mouseY - startY)) root.deltaSize((mouseX - startX), (mouseY - startY))
startX = mouseX startX = mouseX
startY = mouseY startY = mouseY
} }
} }
} }
/*
* Window decorations, with a title bar and frames
*/
Border {
id: windowBorder
anchors.fill: parent
border.color: root.frameColor
color: root.backgroundColor
Border {
id: titleBorder
height: 48
anchors.right: parent.right
anchors.rightMargin: 0
anchors.left: parent.left
anchors.leftMargin: 0
border.color: root.frameColor
color: root.headerBackgroundColor
Text {
id: titleText
// FIXME move all constant colors to our own palette class HifiPalette
color: "white"
text: root.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: root
minimumX: 0
minimumY: 0
maximumX: root.parent ? root.parent.width - root.width : 0
maximumY: root.parent ? root.parent.height - root.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: {
root.close();
}
}
}
} // header border
Border {
id: clientBorder
border.color: root.frameColor
// FIXME move all constant colors to our own palette class HifiPalette
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
} }

View file

@ -0,0 +1,16 @@
import QtQuick 2.3
import QtQuick.Controls 1.3
import QtQuick.Controls.Styles 1.3
Text {
id: root
FontLoader { id: iconFont; source: "../../fonts/fontawesome-webfont.ttf"; }
property int size: 32
width: size
height: size
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
font.family: iconFont.name
font.pointSize: 18
}

View file

@ -0,0 +1,5 @@
import QtQuick 2.3
import QtQuick.Controls 1.3 as Original
import "../styles"
import "../controls"

View file

@ -0,0 +1,2 @@
These are our own custom controls with the same names as existing controls, but
customized for readability / usability in VR.

View file

@ -1,6 +1,6 @@
import QtQuick 2.3 import QtQuick 2.3 as Original
TextEdit { Original.Text {
font.family: "Helvetica" font.family: "Helvetica"
font.pointSize: 18 font.pointSize: 18
} }

View file

@ -1,6 +1,6 @@
import QtQuick 2.3 import QtQuick 2.3 as Original
Text { Original.TextArea {
font.family: "Helvetica" font.family: "Helvetica"
font.pointSize: 18 font.pointSize: 18
} }

View file

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

View file

@ -3,7 +3,7 @@ import QtQuick.Controls 1.2
TextInput { TextInput {
SystemPalette { id: myPalette; colorGroup: SystemPalette.Active } SystemPalette { id: myPalette; colorGroup: SystemPalette.Active }
property string helperText: "" property string helperText
font.family: "Helvetica" font.family: "Helvetica"
font.pointSize: 18 font.pointSize: 18
width: 256 width: 256
@ -24,7 +24,7 @@ TextInput {
id: helperText id: helperText
anchors.fill: parent anchors.fill: parent
font.pointSize: parent.font.pointSize font.pointSize: parent.font.pointSize
font.family: "Helvetica" font.family: parent.font.family
verticalAlignment: TextInput.AlignVCenter verticalAlignment: TextInput.AlignVCenter
text: parent.helperText text: parent.helperText
color: myPalette.dark color: myPalette.dark

View file

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 253 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 254 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 257 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 224 B

View file

@ -1,6 +1,5 @@
import QtQuick 2.3 import QtQuick 2.3
Rectangle { Rectangle {
SystemPalette { id: myPalette; colorGroup: SystemPalette.Active } SystemPalette { id: myPalette; colorGroup: SystemPalette.Active }
property int margin: 5 property int margin: 5

View file

@ -0,0 +1,24 @@
import QtQuick 2.4 as Original
import QtQuick.Controls.Styles 1.3 as OriginalStyles
import "."
import "../controls"
OriginalStyles.ButtonStyle {
Original.SystemPalette { id: myPalette; colorGroup: Original.SystemPalette.Active }
padding {
top: 8
left: 12
right: 12
bottom: 8
}
background: Border {
anchors.fill: parent
}
label: Text {
renderType: Original.Text.NativeRendering
verticalAlignment: Original.Text.AlignVCenter
horizontalAlignment: Original.Text.AlignHCenter
text: control.text
color: control.enabled ? myPalette.text : myPalette.dark
}
}

View file

@ -0,0 +1,5 @@
import QtQuick 2.4
QtObject {
property string hifiBlue: "#0e7077"
}

View file

@ -0,0 +1,15 @@
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,22 @@
import QtQuick 2.4
import QtQuick.Controls.Styles 1.3
import "../controls"
import "."
ButtonStyle {
HifiPalette { id: hifiPalette }
padding {
top: 2
left: 4
right: 4
bottom: 2
}
background: Item {}
label: Text {
renderType: Text.NativeRendering
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
text: control.text
color: control.enabled ? "yellow" : "brown"
}
}

View file

@ -144,6 +144,23 @@ extern "C" {
} }
#endif #endif
enum CustomEventTypes {
Lambda = QEvent::User + 1
};
class LambdaEvent : public QEvent {
std::function<void()> _fun;
public:
LambdaEvent(const std::function<void()> & fun) :
QEvent(static_cast<QEvent::Type>(Lambda)), _fun(fun) {
}
LambdaEvent(std::function<void()> && fun) :
QEvent(static_cast<QEvent::Type>(Lambda)), _fun(fun) {
}
void call() { _fun(); }
};
using namespace std; using namespace std;
// Starfield information // Starfield information
@ -707,6 +724,13 @@ void Application::initializeGL() {
initDisplay(); initDisplay();
qCDebug(interfaceapp, "Initialized Display."); qCDebug(interfaceapp, "Initialized Display.");
// The UI can't be created until the primary OpenGL
// context is created, because it needs to share
// texture resources
initializeUi();
qCDebug(interfaceapp, "Initialized Offscreen UI.");
_glWidget->makeCurrent();
init(); init();
qCDebug(interfaceapp, "init() complete."); qCDebug(interfaceapp, "init() complete.");
@ -735,17 +759,13 @@ void Application::initializeGL() {
// update before the first render // update before the first render
update(1.0f / _fps); 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); InfoView::showFirstTime(INFO_HELP_PATH);
} }
void Application::initializeUi() { void Application::initializeUi() {
AddressBarDialog::registerType(); AddressBarDialog::registerType();
LoginDialog::registerType(); LoginDialog::registerType();
MessageDialog::registerType();
auto offscreenUi = DependencyManager::get<OffscreenUi>(); auto offscreenUi = DependencyManager::get<OffscreenUi>();
offscreenUi->create(_glWidget->context()->contextHandle()); offscreenUi->create(_glWidget->context()->contextHandle());
@ -753,6 +773,7 @@ void Application::initializeUi() {
offscreenUi->setProxyWindow(_window->windowHandle()); offscreenUi->setProxyWindow(_window->windowHandle());
offscreenUi->setBaseUrl(QUrl::fromLocalFile(PathUtils::resourcesPath() + "/qml/")); offscreenUi->setBaseUrl(QUrl::fromLocalFile(PathUtils::resourcesPath() + "/qml/"));
offscreenUi->load("Root.qml"); offscreenUi->load("Root.qml");
offscreenUi->load("RootMenu.qml");
offscreenUi->setMouseTranslator([this](const QPointF& p){ offscreenUi->setMouseTranslator([this](const QPointF& p){
if (OculusManager::isConnected()) { if (OculusManager::isConnected()) {
glm::vec2 pos = _applicationOverlay.screenToOverlay(toGlm(p)); glm::vec2 pos = _applicationOverlay.screenToOverlay(toGlm(p));
@ -964,6 +985,10 @@ bool Application::importSVOFromURL(const QString& urlString) {
bool Application::event(QEvent* event) { bool Application::event(QEvent* event) {
switch (event->type()) { switch (event->type()) {
case Lambda:
((LambdaEvent*)event)->call();
return true;
case QEvent::MouseMove: case QEvent::MouseMove:
mouseMoveEvent((QMouseEvent*)event); mouseMoveEvent((QMouseEvent*)event);
return true; return true;

View file

@ -21,6 +21,7 @@
#include <QSet> #include <QSet>
#include <QStringList> #include <QStringList>
#include <QUndoStack> #include <QUndoStack>
#include <functional>
#include <AbstractScriptingServicesInterface.h> #include <AbstractScriptingServicesInterface.h>
#include <AbstractViewStateInterface.h> #include <AbstractViewStateInterface.h>
@ -147,6 +148,8 @@ public:
Application(int& argc, char** argv, QElapsedTimer &startup_time); Application(int& argc, char** argv, QElapsedTimer &startup_time);
~Application(); ~Application();
void postLambdaEvent(std::function<void()> f);
void loadScripts(); void loadScripts();
QString getPreviousScriptLocation(); QString getPreviousScriptLocation();
void setPreviousScriptLocation(const QString& previousScriptLocation); void setPreviousScriptLocation(const QString& previousScriptLocation);

View file

@ -323,7 +323,7 @@ void ApplicationOverlay::displayOverlayTextureOculus(Camera& whichCamera) {
//Render magnifier, but dont show border for mouse magnifier //Render magnifier, but dont show border for mouse magnifier
glm::vec2 projection = screenToOverlay(glm::vec2(_reticlePosition[MOUSE].x(), glm::vec2 projection = screenToOverlay(glm::vec2(_reticlePosition[MOUSE].x(),
_reticlePosition[MOUSE].y())); _reticlePosition[MOUSE].y()));
with_each_texture(_overlays.getTexture(), _newUiTexture, [&] { with_each_texture(_overlays.getTexture(), 0, [&] {
renderMagnifier(projection, _magSizeMult[i], i != MOUSE); renderMagnifier(projection, _magSizeMult[i], i != MOUSE);
}); });
} }

View file

@ -1,56 +0,0 @@
//
// 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

@ -14,16 +14,26 @@
#include <QVector> #include <QVector>
#include <QDateTime> #include <QDateTime>
#include <QFileInfo> #include <QFileInfo>
#include <QDir>
#include "PathUtils.h" #include "PathUtils.h"
QString& PathUtils::resourcesPath() { QString& PathUtils::resourcesPath() {
#ifdef DEBUG
static QString staticResourcePath;
if (staticResourcePath.isEmpty()) {
QDir path(__FILE__);
path.cdUp();
staticResourcePath = path.cleanPath(path.absoluteFilePath("../../../interface/resources/")) + "/";
}
#else
#ifdef Q_OS_MAC #ifdef Q_OS_MAC
static QString staticResourcePath = QCoreApplication::applicationDirPath() + "/../Resources/"; static QString staticResourcePath = QCoreApplication::applicationDirPath() + "/../Resources/";
#else #else
static QString staticResourcePath = QCoreApplication::applicationDirPath() + "/resources/"; static QString staticResourcePath = QCoreApplication::applicationDirPath() + "/resources/";
#endif #endif
#endif
return staticResourcePath; return staticResourcePath;
} }

View file

@ -0,0 +1,12 @@
set(TARGET_NAME ui)
# use setup_hifi_library macro to setup our project and link appropriate Qt modules
setup_hifi_library(OpenGL Network Qml Quick Script)
link_hifi_libraries(render-utils shared)
add_dependency_external_projects(glm)
find_package(GLM REQUIRED)
target_include_directories(${TARGET_NAME} PUBLIC ${GLM_INCLUDE_DIRS})

View file

@ -0,0 +1,279 @@
#include "HifiMenu.h"
#include <QtQml>
// FIXME can this be made a class member?
static const QString MENU_SUFFIX{ "__Menu" };
HIFI_QML_DEF_LAMBDA(HifiMenu, [=](QQmlContext* context, QObject* newItem) {
auto offscreenUi = DependencyManager::get<OffscreenUi>();
QObject * rootMenu = offscreenUi->getRootItem()->findChild<QObject*>("rootMenu");
Q_ASSERT(rootMenu);
static_cast<HifiMenu*>(newItem)->setRootMenu(rootMenu);
context->setContextProperty("rootMenu", rootMenu);
});
HifiMenu::HifiMenu(QQuickItem* parent) : QQuickItem(parent), _triggerMapper(this), _toggleMapper(this) {
this->setEnabled(false);
connect(&_triggerMapper, SIGNAL(mapped(QString)), this, SLOT(onTriggeredByName(const QString &)));
connect(&_toggleMapper, SIGNAL(mapped(QString)), this, SLOT(onToggledByName(const QString &)));
}
void HifiMenu::onTriggeredByName(const QString & name) {
qDebug() << name << " triggered";
if (_triggerActions.count(name)) {
_triggerActions[name]();
}
}
void HifiMenu::onToggledByName(const QString & name) {
qDebug() << name << " toggled";
if (_toggleActions.count(name)) {
QObject* menu = findMenuObject(name);
bool checked = menu->property("checked").toBool();
_toggleActions[name](checked);
}
}
void HifiMenu::setToggleAction(const QString & name, std::function<void(bool)> f) {
_toggleActions[name] = f;
}
void HifiMenu::setTriggerAction(const QString & name, std::function<void()> f) {
_triggerActions[name] = f;
}
QObject* addMenu(QObject* parent, const QString & text) {
// FIXME add more checking here to ensure no name conflicts
QVariant returnedValue;
QMetaObject::invokeMethod(parent, "addMenu", Qt::DirectConnection,
Q_RETURN_ARG(QVariant, returnedValue),
Q_ARG(QVariant, text));
QObject* result = returnedValue.value<QObject*>();
if (result) {
result->setObjectName(text + MENU_SUFFIX);
}
return result;
}
class QQuickMenuItem;
QObject* addItem(QObject* parent, const QString& text) {
// FIXME add more checking here to ensure no name conflicts
QQuickMenuItem* returnedValue{ nullptr };
bool invokeResult = QMetaObject::invokeMethod(parent, "addItem", Qt::DirectConnection,
Q_RETURN_ARG(QQuickMenuItem*, returnedValue),
Q_ARG(QString, text));
Q_ASSERT(invokeResult);
QObject* result = reinterpret_cast<QObject*>(returnedValue);
return result;
}
const QObject* HifiMenu::findMenuObject(const QString & menuOption) const {
if (menuOption.isEmpty()) {
return _rootMenu;
}
const QObject* result = _rootMenu->findChild<QObject*>(menuOption + MENU_SUFFIX);
return result;
}
QObject* HifiMenu::findMenuObject(const QString & menuOption) {
if (menuOption.isEmpty()) {
return _rootMenu;
}
QObject* result = _rootMenu->findChild<QObject*>(menuOption + MENU_SUFFIX);
return result;
}
void HifiMenu::addMenu(const QString & parentMenu, const QString & menuOption) {
QObject* parent = findMenuObject(parentMenu);
QObject* result = ::addMenu(parent, menuOption);
Q_ASSERT(result);
result->setObjectName(menuOption + MENU_SUFFIX);
Q_ASSERT(findMenuObject(menuOption));
}
void HifiMenu::removeMenu(const QString& menuName) {
QObject* menu = findMenuObject(menuName);
Q_ASSERT(menu);
Q_ASSERT(menu != _rootMenu);
QMetaObject::invokeMethod(menu->parent(), "removeItem",
Q_ARG(QVariant, QVariant::fromValue(menu)));
}
bool HifiMenu::menuExists(const QString& menuName) const {
return findMenuObject(menuName);
}
void HifiMenu::addSeparator(const QString& parentMenu, const QString& separatorName) {
QObject * parent = findMenuObject(parentMenu);
bool invokeResult = QMetaObject::invokeMethod(parent, "addSeparator", Qt::DirectConnection);
Q_ASSERT(invokeResult);
addItem(parentMenu, separatorName);
enableItem(separatorName, false);
}
void HifiMenu::removeSeparator(const QString& parentMenu, const QString& separatorName) {
}
void HifiMenu::addItem(const QString & parentMenu, const QString & menuOption) {
QObject* parent = findMenuObject(parentMenu);
Q_ASSERT(parent);
QObject* result = ::addItem(parent, menuOption);
Q_ASSERT(result);
result->setObjectName(menuOption + MENU_SUFFIX);
Q_ASSERT(findMenuObject(menuOption));
_triggerMapper.setMapping(result, menuOption);
connect(result, SIGNAL(triggered()), &_triggerMapper, SLOT(map()));
_toggleMapper.setMapping(result, menuOption);
connect(result, SIGNAL(toggled(bool)), &_toggleMapper, SLOT(map()));
}
void HifiMenu::addItem(const QString & parentMenu, const QString & menuOption, std::function<void()> f) {
setTriggerAction(menuOption, f);
addItem(parentMenu, menuOption);
}
void HifiMenu::addItem(const QString & parentMenu, const QString & menuOption, QObject* receiver, const char* slot) {
addItem(parentMenu, menuOption);
connectItem(menuOption, receiver, slot);
}
void HifiMenu::removeItem(const QString& menuOption) {
removeMenu(menuOption);
}
bool HifiMenu::itemExists(const QString& menuName, const QString& menuitem) const {
return findMenuObject(menuName);
}
void HifiMenu::triggerItem(const QString& menuOption) {
QObject* menuItem = findMenuObject(menuOption);
Q_ASSERT(menuItem);
Q_ASSERT(menuItem != _rootMenu);
QMetaObject::invokeMethod(menuItem, "trigger");
}
QHash<QString, QString> warned;
void warn(const QString & menuOption) {
if (!warned.contains(menuOption)) {
warned[menuOption] = menuOption;
qWarning() << "No menu item: " << menuOption;
}
}
bool HifiMenu::isChecked(const QString& menuOption) const {
const QObject* menuItem = findMenuObject(menuOption);
if (!menuItem) {
warn(menuOption);
return false;
}
return menuItem->property("checked").toBool();
}
void HifiMenu::setChecked(const QString& menuOption, bool isChecked) {
QObject* menuItem = findMenuObject(menuOption);
if (!menuItem) {
warn(menuOption);
return;
}
if (menuItem->property("checked").toBool() != isChecked) {
menuItem->setProperty("checked", QVariant::fromValue(isChecked));
Q_ASSERT(menuItem->property("checked").toBool() == isChecked);
}
}
void HifiMenu::setCheckable(const QString& menuOption, bool checkable) {
QObject* menuItem = findMenuObject(menuOption);
if (!menuItem) {
warn(menuOption);
return;
}
menuItem->setProperty("checkable", QVariant::fromValue(checkable));
Q_ASSERT(menuItem->property("checkable").toBool() == checkable);
}
void HifiMenu::setItemText(const QString& menuOption, const QString& text) {
QObject* menuItem = findMenuObject(menuOption);
if (!menuItem) {
warn(menuOption);
return;
}
if (menuItem->property("type").toInt() == 2) {
menuItem->setProperty("title", QVariant::fromValue(text));
} else {
menuItem->setProperty("text", QVariant::fromValue(text));
}
}
void HifiMenu::setRootMenu(QObject* rootMenu) {
_rootMenu = rootMenu;
}
void HifiMenu::enableItem(const QString & menuOption, bool enabled) {
QObject* menuItem = findMenuObject(menuOption);
if (!menuItem) {
warn(menuOption);
return;
}
menuItem->setProperty("enabled", QVariant::fromValue(enabled));
}
void HifiMenu::addCheckableItem(const QString& parentMenu, const QString& menuOption, bool checked) {
addItem(parentMenu, menuOption);
setCheckable(menuOption);
if (checked) {
setChecked(menuOption, checked);
}
}
void HifiMenu::addCheckableItem(const QString& parentMenu, const QString& menuOption, bool checked, std::function<void(bool)> f) {
setToggleAction(menuOption, f);
addCheckableItem(parentMenu, menuOption, checked);
}
void HifiMenu::setItemVisible(const QString& menuOption, bool visible) {
QObject* result = findMenuObject(menuOption);
if (result) {
result->setProperty("visible", visible);
}
}
bool HifiMenu::isItemVisible(const QString& menuOption) {
QObject* result = findMenuObject(menuOption);
if (result) {
return result->property("visible").toBool();
}
return false;
}
void HifiMenu::setItemShortcut(const QString& menuOption, const QString& shortcut) {
QObject* result = findMenuObject(menuOption);
if (result) {
result->setProperty("shortcut", shortcut);
}
}
QString HifiMenu::getItemShortcut(const QString& menuOption) {
QObject* result = findMenuObject(menuOption);
if (result) {
return result->property("shortcut").toString();
}
return QString();
}
void HifiMenu::addCheckableItem(const QString& parentMenu, const QString& menuOption, bool checked, QObject* receiver, const char* slot) {
addCheckableItem(parentMenu, menuOption, checked);
connectItem(menuOption, receiver, slot);
}
void HifiMenu::connectCheckable(const QString& menuOption, QObject* receiver, const char* slot) {
QObject* result = findMenuObject(menuOption);
connect(result, SIGNAL(toggled(bool)), receiver, slot);
}
void HifiMenu::connectItem(const QString& menuOption, QObject* receiver, const char* slot) {
QObject* result = findMenuObject(menuOption);
connect(result, SIGNAL(triggered()), receiver, slot);
}

View file

@ -0,0 +1,83 @@
//
// MenuConstants.h
//
// Created by Bradley Austin Davis on 2015/04/21
// 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_MenuContants_h
#define hifi_MenuConstants_h
#include <QQuickItem>
#include <QHash>
#include <QList>
#include <QSignalMapper>
#include "OffscreenUi.h"
class HifiMenu : public QQuickItem {
Q_OBJECT
HIFI_QML_DECL_LAMBDA
public:
HifiMenu(QQuickItem* parent = nullptr);
void setToggleAction(const QString& name, std::function<void(bool)> f);
void setTriggerAction(const QString& name, std::function<void()> f);
void addMenu(const QString& parentMenu, const QString& menuOption);
void removeMenu(const QString& menuName);
bool menuExists(const QString& menuName) const;
void addSeparator(const QString& menuName, const QString& separatorName);
void removeSeparator(const QString& menuName, const QString& separatorName);
void addItem(const QString& parentMenu, const QString& menuOption);
void addItem(const QString& parentMenu, const QString& menuOption, std::function<void()> f);
void addItem(const QString& parentMenu, const QString& menuOption, QObject* receiver, const char* slot);
void addCheckableItem(const QString& parentMenu, const QString& menuOption, bool checked = false);
void addCheckableItem(const QString& parentMenu, const QString& menuOption, bool checked, std::function<void(bool)> f);
void addCheckableItem(const QString& parentMenu, const QString& menuOption, bool checked, QObject* receiver, const char* slot);
void connectCheckable(const QString& menuOption, QObject* receiver, const char* slot);
void connectItem(const QString& menuOption, QObject* receiver, const char* slot);
void removeItem(const QString& menuitem);
bool itemExists(const QString& menuName, const QString& menuitem) const;
void triggerItem(const QString& menuOption);
void enableItem(const QString& menuOption, bool enabled = true);
bool isChecked(const QString& menuOption) const;
void setChecked(const QString& menuOption, bool checked = true);
void setCheckable(const QString& menuOption, bool checkable = true);
void setExclusiveGroup(const QString& menuOption, const QString& groupName);
void setItemText(const QString& menuOption, const QString& text);
void setItemVisible(const QString& menuOption, bool visible = true);
bool isItemVisible(const QString& menuOption);
void setItemShortcut(const QString& menuOption, const QString& shortcut);
QString getItemShortcut(const QString& menuOption);
void setRootMenu(QObject* rootMenu);
private slots:
void onTriggeredByName(const QString& name);
void onToggledByName(const QString& name);
protected:
QHash<QString, std::function<void()>> _triggerActions;
QHash<QString, std::function<void(bool)>> _toggleActions;
QObject* findMenuObject(const QString& name);
const QObject* findMenuObject(const QString& name) const;
QObject* _rootMenu{ nullptr };
QSignalMapper _triggerMapper;
QSignalMapper _toggleMapper;
};
#endif // hifi_MenuConstants_h

View file

@ -0,0 +1,142 @@
//
//
// MessageDialog.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 "MessageDialog.h"
QML_DIALOG_DEF(MessageDialog)
MessageDialog::MessageDialog(QQuickItem *parent) : OffscreenQmlDialog(parent) {
_buttons = StandardButtons(Ok | Cancel);
}
MessageDialog::~MessageDialog() {
}
QString MessageDialog::text() const {
return _text;
}
QString MessageDialog::informativeText() const {
return _informativeText;
}
QString MessageDialog::detailedText() const {
return _detailedText;
}
MessageDialog::Icon MessageDialog::icon() const {
return _icon;
}
void MessageDialog::setVisible(bool v) {
OffscreenQmlDialog::setVisible(v);
}
void MessageDialog::setText(const QString &arg) {
if (arg != _text) {
_text = arg;
emit textChanged();
}
}
void MessageDialog::setInformativeText(const QString &arg) {
if (arg != _informativeText) {
_informativeText = arg;
emit informativeTextChanged();
}
}
void MessageDialog::setDetailedText(const QString &arg) {
if (arg != _detailedText) {
_detailedText = arg;
emit detailedTextChanged();
}
}
void MessageDialog::setIcon(MessageDialog::Icon icon) {
if (icon != _icon) {
_icon = icon;
emit iconChanged();
}
}
void MessageDialog::setStandardButtons(StandardButtons buttons) {
if (buttons != _buttons) {
_buttons = buttons;
emit standardButtonsChanged();
}
}
void MessageDialog::click(StandardButton button) {
click(static_cast<StandardButton>(button),
static_cast<QPlatformDialogHelper::ButtonRole>(
QPlatformDialogHelper::buttonRole(static_cast<QPlatformDialogHelper::StandardButton>(button))));
}
QUrl MessageDialog::standardIconSource() {
switch (icon()) {
case QMessageDialogOptions::Information:
return QUrl("images/information.png");
break;
case QMessageDialogOptions::Warning:
return QUrl("images/warning.png");
break;
case QMessageDialogOptions::Critical:
return QUrl("images/critical.png");
break;
case QMessageDialogOptions::Question:
return QUrl("images/question.png");
break;
default:
return QUrl();
break;
}
}
MessageDialog::StandardButtons MessageDialog::standardButtons() const {
return _buttons;
}
MessageDialog::StandardButton MessageDialog::clickedButton() const {
return _clickedButton;
}
void MessageDialog::click(StandardButton button, QPlatformDialogHelper::ButtonRole) {
_clickedButton = button;
if (_resultCallback) {
_resultCallback(QMessageBox::StandardButton(_clickedButton));
}
hide();
}
void MessageDialog::accept() {
// enter key is treated like OK
if (_clickedButton == NoButton)
_clickedButton = Ok;
if (_resultCallback) {
_resultCallback(QMessageBox::StandardButton(_clickedButton));
}
OffscreenQmlDialog::accept();
}
void MessageDialog::reject() {
// escape key is treated like cancel
if (_clickedButton == NoButton)
_clickedButton = Cancel;
if (_resultCallback) {
_resultCallback(QMessageBox::StandardButton(_clickedButton));
}
OffscreenQmlDialog::reject();
}
void MessageDialog::setResultCallback(OffscreenUi::ButtonCallback callback) {
_resultCallback = callback;
}

View file

@ -0,0 +1,98 @@
//
// MessageDialog.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_MessageDialog_h
#define hifi_MessageDialog_h
#include "OffscreenQmlDialog.h"
#include <5.4.1/QtGui/qpa/qplatformdialoghelper.h>
class MessageDialog : public OffscreenQmlDialog
{
Q_OBJECT
QML_DIALOG_DECL
private:
Q_ENUMS(Icon)
Q_PROPERTY(QString text READ text WRITE setText NOTIFY textChanged)
Q_PROPERTY(QString informativeText READ informativeText WRITE setInformativeText NOTIFY informativeTextChanged)
Q_PROPERTY(QString detailedText READ detailedText WRITE setDetailedText NOTIFY detailedTextChanged)
Q_PROPERTY(Icon icon READ icon WRITE setIcon NOTIFY iconChanged)
Q_PROPERTY(QUrl standardIconSource READ standardIconSource NOTIFY iconChanged)
Q_PROPERTY(StandardButtons standardButtons READ standardButtons WRITE setStandardButtons NOTIFY standardButtonsChanged)
Q_PROPERTY(StandardButton clickedButton READ clickedButton NOTIFY buttonClicked)
public:
enum Icon {
NoIcon = QMessageDialogOptions::NoIcon,
Information = QMessageDialogOptions::Information,
Warning = QMessageDialogOptions::Warning,
Critical = QMessageDialogOptions::Critical,
Question = QMessageDialogOptions::Question
};
MessageDialog(QQuickItem *parent = 0);
virtual ~MessageDialog();
QString text() const;
QString informativeText() const;
QString detailedText() const;
Icon icon() const;
public slots:
virtual void setVisible(bool v);
void setText(const QString &arg);
void setInformativeText(const QString &arg);
void setDetailedText(const QString &arg);
void setIcon(Icon icon);
void setStandardButtons(StandardButtons buttons);
void setResultCallback(OffscreenUi::ButtonCallback callback);
void click(StandardButton button);
QUrl standardIconSource();
StandardButtons standardButtons() const;
StandardButton clickedButton() const;
signals:
void textChanged();
void informativeTextChanged();
void detailedTextChanged();
void iconChanged();
void standardButtonsChanged();
void buttonClicked();
void discard();
void help();
void yes();
void no();
void apply();
void reset();
protected slots:
virtual void click(StandardButton button, QPlatformDialogHelper::ButtonRole);
virtual void accept();
virtual void reject();
private:
QString _title;
QString _text;
QString _informativeText;
QString _detailedText;
Icon _icon{ Information };
StandardButtons _buttons;
StandardButton _clickedButton{ NoButton };
OffscreenUi::ButtonCallback _resultCallback;
};
#endif // hifi_MessageDialog_h

View file

@ -13,6 +13,30 @@
OffscreenQmlDialog::OffscreenQmlDialog(QQuickItem* parent) OffscreenQmlDialog::OffscreenQmlDialog(QQuickItem* parent)
: QQuickItem(parent) { } : QQuickItem(parent) { }
OffscreenQmlDialog::~OffscreenQmlDialog() {
}
void OffscreenQmlDialog::hide() { void OffscreenQmlDialog::hide() {
static_cast<QQuickItem*>(parent())->setEnabled(false); static_cast<QQuickItem*>(parent())->setEnabled(false);
} }
QString OffscreenQmlDialog::title() const {
return _title;
}
void OffscreenQmlDialog::setTitle(const QString &arg) {
if (arg != _title) {
_title = arg;
emit titleChanged();
}
}
void OffscreenQmlDialog::accept() {
hide();
emit accepted();
}
void OffscreenQmlDialog::reject() {
hide();
emit rejected();
}

View file

@ -0,0 +1,104 @@
//
// 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 <5.4.1/QtGui/qpa/qplatformdialoghelper.h>
#include "OffscreenUi.h"
#define QML_DIALOG_DECL \
private: \
static const QString NAME; \
static const QUrl QML; \
public: \
static void registerType(); \
static void show(std::function<void(QQmlContext*, QObject*)> f = [](QQmlContext*, QObject*) {}); \
static void toggle(std::function<void(QQmlContext*, QObject*)> f = [](QQmlContext*, QObject*) {}); \
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(std::function<void(QQmlContext*, QObject*)> f) { \
auto offscreenUi = DependencyManager::get<OffscreenUi>(); \
offscreenUi->show(QML, NAME, f); \
} \
\
void x::toggle(std::function<void(QQmlContext*, QObject*)> f) { \
auto offscreenUi = DependencyManager::get<OffscreenUi>(); \
offscreenUi->toggle(QML, NAME, f); \
}
class OffscreenQmlDialog : public QQuickItem
{
Q_OBJECT
Q_PROPERTY(QString title READ title WRITE setTitle NOTIFY titleChanged)
Q_ENUMS(StandardButton)
Q_FLAGS(StandardButtons)
public:
OffscreenQmlDialog(QQuickItem* parent = nullptr);
virtual ~OffscreenQmlDialog();
enum StandardButton {
NoButton = QPlatformDialogHelper::NoButton,
Ok = QPlatformDialogHelper::Ok,
Save = QPlatformDialogHelper::Save,
SaveAll = QPlatformDialogHelper::SaveAll,
Open = QPlatformDialogHelper::Open,
Yes = QPlatformDialogHelper::Yes,
YesToAll = QPlatformDialogHelper::YesToAll,
No = QPlatformDialogHelper::No,
NoToAll = QPlatformDialogHelper::NoToAll,
Abort = QPlatformDialogHelper::Abort,
Retry = QPlatformDialogHelper::Retry,
Ignore = QPlatformDialogHelper::Ignore,
Close = QPlatformDialogHelper::Close,
Cancel = QPlatformDialogHelper::Cancel,
Discard = QPlatformDialogHelper::Discard,
Help = QPlatformDialogHelper::Help,
Apply = QPlatformDialogHelper::Apply,
Reset = QPlatformDialogHelper::Reset,
RestoreDefaults = QPlatformDialogHelper::RestoreDefaults,
NButtons
};
Q_DECLARE_FLAGS(StandardButtons, StandardButton)
protected:
void hide();
virtual void accept();
virtual void reject();
public:
QString title() const;
void setTitle(const QString &arg);
signals:
void accepted();
void rejected();
void titleChanged();
private:
QString _title;
};
Q_DECLARE_OPERATORS_FOR_FLAGS(OffscreenQmlDialog::StandardButtons)
#endif

View file

@ -13,6 +13,11 @@
#include <QOpenGLDebugLogger> #include <QOpenGLDebugLogger>
#include <QGLWidget> #include <QGLWidget>
#include <QtQml> #include <QtQml>
#include "MessageDialog.h"
Q_DECLARE_LOGGING_CATEGORY(offscreenFocus)
Q_LOGGING_CATEGORY(offscreenFocus, "hifi.offscreen.focus")
// Time between receiving a request to render the offscreen UI actually triggering // 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 // the render. Could possibly be increased depending on the framerate we expect to
@ -92,10 +97,10 @@ void OffscreenUi::create(QOpenGLContext* shareContext) {
#ifdef DEBUG #ifdef DEBUG
connect(_quickWindow, &QQuickWindow::focusObjectChanged, [this]{ connect(_quickWindow, &QQuickWindow::focusObjectChanged, [this]{
qDebug() << "New focus item " << _quickWindow->focusObject(); qCDebug(offscreenFocus) << "New focus item " << _quickWindow->focusObject();
}); });
connect(_quickWindow, &QQuickWindow::activeFocusItemChanged, [this] { connect(_quickWindow, &QQuickWindow::activeFocusItemChanged, [this] {
qDebug() << "New active focus item " << _quickWindow->activeFocusItem(); qCDebug(offscreenFocus) << "New active focus item " << _quickWindow->activeFocusItem();
}); });
#endif #endif
@ -117,36 +122,45 @@ void OffscreenUi::resize(const QSize& newSize) {
qDebug() << "Offscreen UI resizing to " << newSize.width() << "x" << newSize.height() << " with pixel ratio " << pixelRatio; qDebug() << "Offscreen UI resizing to " << newSize.width() << "x" << newSize.height() << " with pixel ratio " << pixelRatio;
_fboCache.setSize(newSize * pixelRatio); _fboCache.setSize(newSize * pixelRatio);
if (_quickWindow) {
_quickWindow->setGeometry(QRect(QPoint(), newSize));
}
_quickWindow->contentItem()->setSize(newSize);
// Update our members // Update our members
if (_rootItem) { if (_rootItem) {
_rootItem->setSize(newSize); _rootItem->setSize(newSize);
} }
if (_quickWindow) {
_quickWindow->setGeometry(QRect(QPoint(), newSize));
}
doneCurrent(); doneCurrent();
} }
QQmlContext* OffscreenUi::qmlContext() { QQuickItem* OffscreenUi::getRootItem() {
if (nullptr == _rootItem) { return _rootItem;
return _qmlComponent->creationContext();
}
return QQmlEngine::contextForObject(_rootItem);
} }
//QQmlContext* OffscreenUi::qmlContext() {
// if (nullptr == _rootItem) {
// return _qmlComponent->creationContext();
// }
// return QQmlEngine::contextForObject(_rootItem);
//}
void OffscreenUi::setBaseUrl(const QUrl& baseUrl) { void OffscreenUi::setBaseUrl(const QUrl& baseUrl) {
_qmlEngine->setBaseUrl(baseUrl); _qmlEngine->setBaseUrl(baseUrl);
} }
void OffscreenUi::load(const QUrl& qmlSource, std::function<void(QQmlContext*)> f) { void OffscreenUi::load(const QUrl& qmlSource, std::function<void(QQmlContext*, QObject*)> f) {
qDebug() << "Loading QML from URL " << qmlSource; qDebug() << "Loading QML from URL " << qmlSource;
_qmlComponent->loadUrl(qmlSource); _qmlComponent->loadUrl(qmlSource);
if (_qmlComponent->isLoading()) { if (_qmlComponent->isLoading())
connect(_qmlComponent, &QQmlComponent::statusChanged, this, []{}); connect(_qmlComponent, &QQmlComponent::statusChanged, this,
} else { [this, f](QQmlComponent::Status){
finishQmlLoad(); finishQmlLoad(f);
});
else {
finishQmlLoad(f);
} }
} }
@ -163,8 +177,8 @@ void OffscreenUi::requestRender() {
} }
} }
void OffscreenUi::finishQmlLoad() { void OffscreenUi::finishQmlLoad(std::function<void(QQmlContext*, QObject*)> f) {
disconnect(_qmlComponent, &QQmlComponent::statusChanged, this, &OffscreenUi::finishQmlLoad); disconnect(_qmlComponent, &QQmlComponent::statusChanged, this, 0);
if (_qmlComponent->isError()) { if (_qmlComponent->isError()) {
QList<QQmlError> errorList = _qmlComponent->errors(); QList<QQmlError> errorList = _qmlComponent->errors();
foreach(const QQmlError &error, errorList) { foreach(const QQmlError &error, errorList) {
@ -173,7 +187,8 @@ void OffscreenUi::finishQmlLoad() {
return; return;
} }
QObject* newObject = _qmlComponent->create(); QQmlContext * newContext = new QQmlContext(_qmlEngine, qApp);
QObject* newObject = _qmlComponent->beginCreate(newContext);
if (_qmlComponent->isError()) { if (_qmlComponent->isError()) {
QList<QQmlError> errorList = _qmlComponent->errors(); QList<QQmlError> errorList = _qmlComponent->errors();
foreach(const QQmlError &error, errorList) foreach(const QQmlError &error, errorList)
@ -184,9 +199,13 @@ void OffscreenUi::finishQmlLoad() {
return; return;
} }
f(newContext, newObject);
_qmlComponent->completeCreate();
QQuickItem* newItem = qobject_cast<QQuickItem*>(newObject); QQuickItem* newItem = qobject_cast<QQuickItem*>(newObject);
if (!newItem) { if (!newItem) {
qWarning("run: Not a QQuickItem"); qWarning("run: Not a QQuickItem");
return;
delete newObject; delete newObject;
if (!_rootItem) { if (!_rootItem) {
qFatal("Unable to find root QQuickItem"); qFatal("Unable to find root QQuickItem");
@ -197,18 +216,17 @@ void OffscreenUi::finishQmlLoad() {
// Make sure we make items focusable (critical for // Make sure we make items focusable (critical for
// supporting keyboard shortcuts) // supporting keyboard shortcuts)
newItem->setFlag(QQuickItem::ItemIsFocusScope, true); newItem->setFlag(QQuickItem::ItemIsFocusScope, true);
if (!_rootItem) { if (!_rootItem) {
// The root item is ready. Associate it with the window. // The root item is ready. Associate it with the window.
_rootItem = newItem; _rootItem = newItem;
_rootItem->setParentItem(_quickWindow->contentItem()); _rootItem->setParentItem(_quickWindow->contentItem());
_rootItem->setSize(_quickWindow->renderTargetSize()); _rootItem->setSize(_quickWindow->renderTargetSize());
_rootItem->forceActiveFocus();
} else { } else {
// Allow child windows to be destroyed from JS // Allow child windows to be destroyed from JS
QQmlEngine::setObjectOwnership(newItem, QQmlEngine::JavaScriptOwnership); QQmlEngine::setObjectOwnership(newItem, QQmlEngine::JavaScriptOwnership);
newItem->setParent(_rootItem); newItem->setParent(_rootItem);
newItem->setParentItem(_rootItem); newItem->setParentItem(_rootItem);
newItem->setEnabled(true);
} }
} }
@ -390,54 +408,62 @@ void OffscreenUi::setProxyWindow(QWindow* window) {
_renderControl->_renderWindow = window; _renderControl->_renderWindow = window;
} }
void OffscreenUi::show(const QUrl& url, const QString& name) { void OffscreenUi::show(const QUrl& url, const QString& name, std::function<void(QQmlContext*, QObject*)> f) {
QQuickItem* item = _rootItem->findChild<QQuickItem*>(name); QQuickItem* item = _rootItem->findChild<QQuickItem*>(name);
// First load? // First load?
if (!item) { if (!item) {
load(url); load(url, f);
return; item = _rootItem->findChild<QQuickItem*>(name);
} }
item->setEnabled(true); item->setEnabled(true);
} }
void OffscreenUi::toggle(const QUrl& url, const QString& name) { void OffscreenUi::toggle(const QUrl& url, const QString& name, std::function<void(QQmlContext*, QObject*)> f) {
QQuickItem* item = _rootItem->findChild<QQuickItem*>(name); QQuickItem* item = _rootItem->findChild<QQuickItem*>(name);
// First load? // First load?
if (!item) { if (!item) {
load(url); load(url, f);
return; item = _rootItem->findChild<QQuickItem*>(name);
} }
item->setEnabled(!item->isEnabled()); item->setEnabled(!item->isEnabled());
} }
void OffscreenUi::messageBox(const QString& title, const QString& text, void OffscreenUi::messageBox(const QString& title, const QString& text,
ButtonCallback callback,
QMessageBox::Icon icon, QMessageBox::Icon icon,
QMessageBox::StandardButtons buttons, QMessageBox::StandardButtons buttons) {
ButtonCallback f) { MessageDialog::show([=](QQmlContext*ctx, QObject*item) {
MessageDialog * pDialog = item->findChild<MessageDialog*>();
pDialog->setIcon((MessageDialog::Icon)icon);
pDialog->setTitle(title);
pDialog->setText(text);
pDialog->setStandardButtons(MessageDialog::StandardButtons((int)buttons));
pDialog->setResultCallback(callback);
});
} }
void OffscreenUi::information(const QString& title, const QString& text, void OffscreenUi::information(const QString& title, const QString& text,
QMessageBox::StandardButtons buttons, ButtonCallback callback,
ButtonCallback callback) { QMessageBox::StandardButtons buttons) {
callback(QMessageBox::information(nullptr, title, text, buttons)); messageBox(title, text, callback, (QMessageBox::Icon)MessageDialog::Information, buttons);
} }
void OffscreenUi::question(const QString& title, const QString& text, void OffscreenUi::question(const QString& title, const QString& text,
QMessageBox::StandardButtons buttons, ButtonCallback callback,
ButtonCallback callback) { QMessageBox::StandardButtons buttons) {
callback(QMessageBox::question(nullptr, title, text, buttons)); messageBox(title, text, callback, (QMessageBox::Icon)MessageDialog::Question, buttons);
} }
void OffscreenUi::warning(const QString& title, const QString& text, void OffscreenUi::warning(const QString& title, const QString& text,
QMessageBox::StandardButtons buttons, ButtonCallback callback,
ButtonCallback callback) { QMessageBox::StandardButtons buttons) {
callback(QMessageBox::warning(nullptr, title, text, buttons)); messageBox(title, text, callback, (QMessageBox::Icon)MessageDialog::Warning, buttons);
} }
void OffscreenUi::critical(const QString& title, const QString& text, void OffscreenUi::critical(const QString& title, const QString& text,
QMessageBox::StandardButtons buttons, ButtonCallback callback,
ButtonCallback callback) { QMessageBox::StandardButtons buttons) {
callback(QMessageBox::critical(nullptr, title, text, buttons)); messageBox(title, text, callback, (QMessageBox::Icon)MessageDialog::Critical, buttons);
} }

View file

@ -32,6 +32,69 @@
#include "FboCache.h" #include "FboCache.h"
#include <QQuickItem> #include <QQuickItem>
#define HIFI_QML_DECL \
private: \
static const QString NAME; \
static const QUrl QML; \
public: \
static void registerType(); \
static void show(std::function<void(QQmlContext*, QQuickItem *)> f = [](QQmlContext*, QQuickItem*) {}); \
static void toggle(std::function<void(QQmlContext*, QQuickItem *)> f = [](QQmlContext*, QQuickItem*) {}); \
static void load(std::function<void(QQmlContext*, QQuickItem *)> f = [](QQmlContext*, QQuickItem*) {}); \
private:
#define HIFI_QML_DECL_LAMBDA \
protected: \
static const QString NAME; \
static const QUrl QML; \
public: \
static void registerType(); \
static void show(); \
static void toggle(); \
static void load(); \
private:
#define HIFI_QML_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(std::function<void(QQmlContext*, QQuickItem *)> f) { \
auto offscreenUi = DependencyManager::get<OffscreenUi>(); \
offscreenUi->show(QML, NAME, f); \
} \
\
void x::toggle(std::function<void(QQmlContext*, QQuickItem *)> f) { \
auto offscreenUi = DependencyManager::get<OffscreenUi>(); \
offscreenUi->toggle(QML, NAME, f); \
} \
void x::load(std::function<void(QQmlContext*, QQuickItem *)> f) { \
auto offscreenUi = DependencyManager::get<OffscreenUi>(); \
offscreenUi->load(QML, f); \
}
#define HIFI_QML_DEF_LAMBDA(x, f) \
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, f); \
} \
void x::toggle() { \
auto offscreenUi = DependencyManager::get<OffscreenUi>(); \
offscreenUi->toggle(QML, NAME, f); \
} \
void x::load() { \
auto offscreenUi = DependencyManager::get<OffscreenUi>(); \
offscreenUi->load(QML, f); \
}
class OffscreenUi : public OffscreenGlCanvas, public Dependency { class OffscreenUi : public OffscreenGlCanvas, public Dependency {
Q_OBJECT Q_OBJECT
@ -59,16 +122,16 @@ public:
virtual ~OffscreenUi(); virtual ~OffscreenUi();
void create(QOpenGLContext* context); void create(QOpenGLContext* context);
void resize(const QSize& size); void resize(const QSize& size);
void load(const QUrl& qmlSource, std::function<void(QQmlContext*)> f = [](QQmlContext*) {}); void load(const QUrl& qmlSource, std::function<void(QQmlContext*, QObject*)> f = [](QQmlContext*, QObject*) {});
void load(const QString& qmlSourceFile, std::function<void(QQmlContext*)> f = [](QQmlContext*) {}) { void load(const QString& qmlSourceFile, std::function<void(QQmlContext*, QObject*)> f = [](QQmlContext*, QObject*) {}) {
load(QUrl(qmlSourceFile), f); load(QUrl(qmlSourceFile), f);
} }
void show(const QUrl& url, const QString& name); void show(const QUrl& url, const QString& name, std::function<void(QQmlContext*, QObject*)> f = [](QQmlContext*, QObject*) {});
void toggle(const QUrl& url, const QString& name); void toggle(const QUrl& url, const QString& name, std::function<void(QQmlContext*, QObject*)> f = [](QQmlContext*, QObject*) {});
void setBaseUrl(const QUrl& baseUrl); void setBaseUrl(const QUrl& baseUrl);
void addImportPath(const QString& path); void addImportPath(const QString& path);
QQmlContext* qmlContext(); //QQmlContext* getQmlContext();
QQuickItem* getRootItem();
void pause(); void pause();
void resume(); void resume();
bool isPaused() const; bool isPaused() const;
@ -86,31 +149,31 @@ public:
static ButtonCallback NO_OP_CALLBACK; static ButtonCallback NO_OP_CALLBACK;
static void messageBox(const QString& title, const QString& text, static void messageBox(const QString& title, const QString& text,
ButtonCallback f,
QMessageBox::Icon icon, QMessageBox::Icon icon,
QMessageBox::StandardButtons buttons, QMessageBox::StandardButtons buttons);
ButtonCallback f);
static void information(const QString& title, const QString& text, static void information(const QString& title, const QString& text,
QMessageBox::StandardButtons buttons = QMessageBox::Ok, ButtonCallback callback = NO_OP_CALLBACK,
ButtonCallback callback = NO_OP_CALLBACK); QMessageBox::StandardButtons buttons = QMessageBox::Ok);
static void question(const QString& title, const QString& text, static void question(const QString& title, const QString& text,
QMessageBox::StandardButtons buttons = QMessageBox::StandardButtons(QMessageBox::Yes | QMessageBox::No), ButtonCallback callback = NO_OP_CALLBACK,
ButtonCallback callback = [](QMessageBox::StandardButton) {}); QMessageBox::StandardButtons buttons = QMessageBox::StandardButtons(QMessageBox::Yes | QMessageBox::No));
static void warning(const QString& title, const QString& text, static void warning(const QString& title, const QString& text,
QMessageBox::StandardButtons buttons = QMessageBox::Ok, ButtonCallback callback = NO_OP_CALLBACK,
ButtonCallback callback = [](QMessageBox::StandardButton) {}); QMessageBox::StandardButtons buttons = QMessageBox::Ok);
static void critical(const QString& title, const QString& text, static void critical(const QString& title, const QString& text,
QMessageBox::StandardButtons buttons = QMessageBox::Ok, ButtonCallback callback = NO_OP_CALLBACK,
ButtonCallback callback = [](QMessageBox::StandardButton) {}); QMessageBox::StandardButtons buttons = QMessageBox::Ok);
protected: protected:
private slots: private slots:
void updateQuick(); void updateQuick();
void finishQmlLoad(); void finishQmlLoad(std::function<void(QQmlContext*, QObject*)> f);
public slots: public slots:
void requestUpdate(); void requestUpdate();

View file

@ -10,7 +10,6 @@
#include "TextRenderer.h" #include "TextRenderer.h"
#include "MatrixStack.h" #include "MatrixStack.h"
#include "OffscreenUi.h"
#include <QWindow> #include <QWindow>
#include <QFile> #include <QFile>
@ -27,6 +26,7 @@
#include <QOpenGLVertexArrayObject> #include <QOpenGLVertexArrayObject>
#include <QApplication> #include <QApplication>
#include <QOpenGLDebugLogger> #include <QOpenGLDebugLogger>
#include <unordered_map> #include <unordered_map>
#include <memory> #include <memory>
#include <glm/glm.hpp> #include <glm/glm.hpp>
@ -80,6 +80,7 @@ const QString& getQmlDir() {
} }
return dir; return dir;
} }
// Create a simple OpenGL window that renders text in various ways // Create a simple OpenGL window that renders text in various ways
class QTestWindow : public QWindow { class QTestWindow : public QWindow {
Q_OBJECT Q_OBJECT
@ -88,24 +89,17 @@ class QTestWindow : public QWindow {
QSize _size; QSize _size;
TextRenderer* _textRenderer[4]; TextRenderer* _textRenderer[4];
RateCounter fps; RateCounter fps;
int testQmlTexture{ 0 };
//ProgramPtr _planeProgam;
//ShapeWrapperPtr _planeShape;
protected: protected:
void renderText(); void renderText();
void renderQml();
private: private:
void resizeWindow(const QSize& size) { void resizeWindow(const QSize& size) {
_size = size; _size = size;
DependencyManager::get<OffscreenUi>()->resize(_size);
} }
public: public:
QTestWindow() { QTestWindow() {
DependencyManager::set<OffscreenUi>();
setSurfaceType(QSurface::OpenGLSurface); setSurfaceType(QSurface::OpenGLSurface);
QSurfaceFormat format; QSurfaceFormat format;
@ -165,30 +159,10 @@ public:
glClearColor(0.2f, 0.2f, 0.2f, 1); glClearColor(0.2f, 0.2f, 0.2f, 1);
glDisable(GL_DEPTH_TEST); glDisable(GL_DEPTH_TEST);
auto offscreenUi = DependencyManager::get<OffscreenUi>(); makeCurrent();
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)); setFramePosition(QPoint(-1000, 0));
resize(QSize(800, 600)); 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() { virtual ~QTestWindow() {
@ -204,28 +178,6 @@ protected:
void resizeEvent(QResizeEvent* ev) override { void resizeEvent(QResizeEvent* ev) override {
resizeWindow(ev->size()); 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 #ifndef SERIF_FONT_FAMILY
@ -282,39 +234,16 @@ void QTestWindow::renderText() {
} }
} }
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() { void QTestWindow::draw() {
if (!isVisible()) {
return;
}
makeCurrent(); makeCurrent();
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glViewport(0, 0, _size.width() * devicePixelRatio(), _size.height() * devicePixelRatio()); glViewport(0, 0, _size.width() * devicePixelRatio(), _size.height() * devicePixelRatio());
//renderText(); renderText();
renderQml();
_context->swapBuffers(this); _context->swapBuffers(this);
glFinish(); glFinish();
@ -327,10 +256,8 @@ void QTestWindow::draw() {
} }
int main(int argc, char** argv) { int main(int argc, char** argv) {
QApplication app(argc, argv); QGuiApplication app(argc, argv);
//QLoggingCategory::setFilterRules("qt.quick.mouse.debug = true");
QTestWindow window; QTestWindow window;
QTimer timer; QTimer timer;
timer.setInterval(1); timer.setInterval(1);
app.connect(&timer, &QTimer::timeout, &app, [&] { app.connect(&timer, &QTimer::timeout, &app, [&] {

15
tests/ui/CMakeLists.txt Normal file
View file

@ -0,0 +1,15 @@
set(TARGET_NAME ui-tests)
setup_hifi_project(Widgets OpenGL Network Qml Quick Script)
if (WIN32)
add_dependency_external_projects(glew)
find_package(GLEW REQUIRED)
target_include_directories(${TARGET_NAME} PRIVATE ${GLEW_INCLUDE_DIRS})
target_link_libraries(${TARGET_NAME} ${GLEW_LIBRARIES} wsock32.lib opengl32.lib Winmm.lib)
endif()
# link in the shared libraries
link_hifi_libraries(ui render-utils gpu shared)
copy_dlls_beside_windows_executable()

161
tests/ui/main.qml Normal file
View file

@ -0,0 +1,161 @@
/****************************************************************************
**
** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** This file is part of the Qt Quick Controls module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:BSD$
** You may use this file under the terms of the BSD license as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
** * Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** * Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in
** the documentation and/or other materials provided with the
** distribution.
** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names
** of its contributors may be used to endorse or promote products derived
** from this software without specific prior written permission.
**
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
** $QT_END_LICENSE$
**
****************************************************************************/
import QtQuick 2.2
import QtQuick.Layouts 1.1
import QtQuick.Dialogs 1.1
import QtQuick.Controls 1.2
import "qml/UI.js" as UI
import "qml"
//import "/Users/bdavis/Git/hifi/interface/resources/qml"
Item {
anchors.fill: parent
visible: true
//title: "Qt Quick Controls Gallery"
MessageDialog {
id: aboutDialog
icon: StandardIcon.Information
title: "About"
text: "Qt Quick Controls Gallery"
informativeText: "This example demonstrates most of the available Qt Quick Controls."
}
Action {
id: copyAction
text: "&Copy"
shortcut: StandardKey.Copy
iconName: "edit-copy"
enabled: (!!activeFocusItem && !!activeFocusItem["copy"])
onTriggered: activeFocusItem.copy()
}
Action {
id: cutAction
text: "Cu&t"
shortcut: StandardKey.Cut
iconName: "edit-cut"
enabled: (!!activeFocusItem && !!activeFocusItem["cut"])
onTriggered: activeFocusItem.cut()
}
Action {
id: pasteAction
text: "&Paste"
shortcut: StandardKey.Paste
iconName: "edit-paste"
enabled: (!!activeFocusItem && !!activeFocusItem["paste"])
onTriggered: activeFocusItem.paste()
}
// toolBar: ToolBar {
// RowLayout {
// anchors.fill: parent
// anchors.margins: spacing
// Label {
// text: UI.label
// }
// Item { Layout.fillWidth: true }
// CheckBox {
// id: enabler
// text: "Enabled"
// checked: true
// }
// }
// }
// menuBar: MenuBar {
// Menu {
// title: "&File"
// MenuItem {
// text: "E&xit"
// shortcut: StandardKey.Quit
// onTriggered: Qt.quit()
// }
// }
// Menu {
// title: "&Edit"
// visible: tabView.currentIndex == 2
// MenuItem { action: cutAction }
// MenuItem { action: copyAction }
// MenuItem { action: pasteAction }
// }
// Menu {
// title: "&Help"
// MenuItem {
// text: "About..."
// onTriggered: aboutDialog.open()
// }
// }
// }
TabView {
id: tabView
anchors.fill: parent
anchors.margins: UI.margin
tabPosition: UI.tabPosition
Layout.minimumWidth: 360
Layout.minimumHeight: 360
Layout.preferredWidth: 480
Layout.preferredHeight: 640
Tab {
title: "Buttons"
ButtonPage {
enabled: enabler.checked
}
}
Tab {
title: "Progress"
ProgressPage {
enabled: enabler.checked
}
}
Tab {
title: "Input"
InputPage {
enabled: enabler.checked
}
}
}
}

128
tests/ui/qml/ButtonPage.qml Normal file
View file

@ -0,0 +1,128 @@
/****************************************************************************
**
** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** This file is part of the Qt Quick Controls module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:BSD$
** You may use this file under the terms of the BSD license as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
** * Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** * Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in
** the documentation and/or other materials provided with the
** distribution.
** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names
** of its contributors may be used to endorse or promote products derived
** from this software without specific prior written permission.
**
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
** $QT_END_LICENSE$
**
****************************************************************************/
import QtQuick 2.2
import QtQuick.Layouts 1.1
import QtQuick.Controls 1.2
ScrollView {
id: page
horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOff
Item {
id: content
width: Math.max(page.viewport.width, grid.implicitWidth + 2 * grid.rowSpacing)
height: Math.max(page.viewport.height, grid.implicitHeight + 2 * grid.columnSpacing)
GridLayout {
id: grid
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.leftMargin: grid.rowSpacing
anchors.rightMargin: grid.rowSpacing
anchors.topMargin: grid.columnSpacing
columns: page.width < page.height ? 1 : 2
GroupBox {
title: "Button"
Layout.fillWidth: true
Layout.columnSpan: grid.columns
RowLayout {
anchors.fill: parent
Button { text: "OK"; isDefault: true }
Button { text: "Cancel" }
Item { Layout.fillWidth: true }
Button {
text: "Attach"
menu: Menu {
MenuItem { text: "Image" }
MenuItem { text: "Document" }
}
}
}
}
GroupBox {
title: "CheckBox"
Layout.fillWidth: true
ColumnLayout {
anchors.fill: parent
CheckBox { text: "E-mail"; checked: true }
CheckBox { text: "Calendar"; checked: true }
CheckBox { text: "Contacts" }
}
}
GroupBox {
title: "RadioButton"
Layout.fillWidth: true
ColumnLayout {
anchors.fill: parent
ExclusiveGroup { id: radioGroup }
RadioButton { text: "Portrait"; exclusiveGroup: radioGroup }
RadioButton { text: "Landscape"; exclusiveGroup: radioGroup }
RadioButton { text: "Automatic"; exclusiveGroup: radioGroup; checked: true }
}
}
GroupBox {
title: "Switch"
Layout.fillWidth: true
Layout.columnSpan: grid.columns
ColumnLayout {
anchors.fill: parent
RowLayout {
Label { text: "Wi-Fi"; Layout.fillWidth: true }
Switch { checked: true }
}
RowLayout {
Label { text: "Bluetooth"; Layout.fillWidth: true }
Switch { checked: false }
}
}
}
}
}
}

114
tests/ui/qml/InputPage.qml Normal file
View file

@ -0,0 +1,114 @@
/****************************************************************************
**
** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** This file is part of the Qt Quick Controls module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:BSD$
** You may use this file under the terms of the BSD license as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
** * Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** * Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in
** the documentation and/or other materials provided with the
** distribution.
** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names
** of its contributors may be used to endorse or promote products derived
** from this software without specific prior written permission.
**
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
** $QT_END_LICENSE$
**
****************************************************************************/
import QtQuick 2.2
import QtQuick.Layouts 1.1
import QtQuick.Controls 1.2
ScrollView {
id: page
horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOff
Item {
id: content
width: Math.max(page.viewport.width, column.implicitWidth + 2 * column.spacing)
height: Math.max(page.viewport.height, column.implicitHeight + 2 * column.spacing)
ColumnLayout {
id: column
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.margins: column.spacing
GroupBox {
title: "TextField"
Layout.fillWidth: true
ColumnLayout {
anchors.fill: parent
TextField { placeholderText: "..."; Layout.fillWidth: true; z: 1 }
TextField { placeholderText: "Password"; echoMode: TextInput.Password; Layout.fillWidth: true }
}
}
GroupBox {
title: "ComboBox"
Layout.fillWidth: true
ColumnLayout {
anchors.fill: parent
ComboBox {
model: Qt.fontFamilies()
Layout.fillWidth: true
}
ComboBox {
editable: true
model: ListModel {
id: listModel
ListElement { text: "Apple" }
ListElement { text: "Banana" }
ListElement { text: "Coconut" }
ListElement { text: "Orange" }
}
onAccepted: {
if (find(currentText) === -1) {
listModel.append({text: editText})
currentIndex = find(editText)
}
}
Layout.fillWidth: true
}
}
}
GroupBox {
title: "SpinBox"
Layout.fillWidth: true
ColumnLayout {
anchors.fill: parent
SpinBox { value: 99; Layout.fillWidth: true; z: 1 }
SpinBox { decimals: 2; Layout.fillWidth: true }
}
}
}
}
}

View file

@ -0,0 +1,90 @@
/****************************************************************************
**
** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** This file is part of the Qt Quick Controls module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:BSD$
** You may use this file under the terms of the BSD license as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
** * Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** * Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in
** the documentation and/or other materials provided with the
** distribution.
** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names
** of its contributors may be used to endorse or promote products derived
** from this software without specific prior written permission.
**
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
** $QT_END_LICENSE$
**
****************************************************************************/
import QtQuick 2.2
import QtQuick.Layouts 1.1
import QtQuick.Controls 1.2
ScrollView {
id: page
horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOff
Item {
id: content
width: Math.max(page.viewport.width, column.implicitWidth + 2 * column.spacing)
height: Math.max(page.viewport.height, column.implicitHeight + 2 * column.spacing)
ColumnLayout {
id: column
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.margins: column.spacing
GroupBox {
title: "ProgressBar"
Layout.fillWidth: true
ColumnLayout {
anchors.fill: parent
ProgressBar { indeterminate: true; Layout.fillWidth: true }
ProgressBar { value: slider.value; Layout.fillWidth: true }
}
}
GroupBox {
title: "Slider"
Layout.fillWidth: true
ColumnLayout {
anchors.fill: parent
Slider { id: slider; value: 0.5; Layout.fillWidth: true }
}
}
GroupBox {
title: "BusyIndicator"
Layout.fillWidth: true
BusyIndicator { running: true }
}
}
}
}

45
tests/ui/qml/UI.js Normal file
View file

@ -0,0 +1,45 @@
/****************************************************************************
**
** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** This file is part of the Qt Quick Controls module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:BSD$
** You may use this file under the terms of the BSD license as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
** * Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** * Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in
** the documentation and/or other materials provided with the
** distribution.
** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names
** of its contributors may be used to endorse or promote products derived
** from this software without specific prior written permission.
**
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
** $QT_END_LICENSE$
**
****************************************************************************/
.pragma library
var margin = 2
var tabPosition = Qt.TopEdge
var label = ""

336
tests/ui/src/main.cpp Normal file
View file

@ -0,0 +1,336 @@
//
// main.cpp
// tests/render-utils/src
//
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "OffscreenUi.h"
#include <QWindow>
#include <QFile>
#include <QTime>
#include <QImage>
#include <QTimer>
#include <QElapsedTimer>
#include <QOpenGLContext>
#include <QOpenGLBuffer>
#include <QOpenGLShaderProgram>
#include <QResizeEvent>
#include <QLoggingCategory>
#include <QOpenGLTexture>
#include <QOpenGLVertexArrayObject>
#include <QApplication>
#include <QOpenGLDebugLogger>
#include <QOpenGLFunctions>
#include <QQmlContext>
#include <unordered_map>
#include <memory>
#include <glm/glm.hpp>
#include <PathUtils.h>
#include <QDir>
#include "MessageDialog.h"
#include "HifiMenu.h"
class RateCounter {
std::vector<float> times;
QElapsedTimer timer;
public:
RateCounter() {
timer.start();
}
void reset() {
times.clear();
}
unsigned int count() const {
return times.size() - 1;
}
float elapsed() const {
if (times.size() < 1) {
return 0.0f;
}
float elapsed = *times.rbegin() - *times.begin();
return elapsed;
}
void increment() {
times.push_back(timer.elapsed() / 1000.0f);
}
float rate() const {
if (elapsed() == 0.0f) {
return NAN;
}
return (float) count() / elapsed();
}
};
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;
}
const QString & getTestQmlDir() {
static QString dir;
if (dir.isEmpty()) {
QDir path(__FILE__);
path.cdUp();
dir = path.cleanPath(path.absoluteFilePath("../")) + "/";
qDebug() << "Qml Test Path: " << dir;
}
return dir;
}
// Create a simple OpenGL window that renders text in various ways
class QTestWindow : public QWindow, private QOpenGLFunctions {
Q_OBJECT
QOpenGLContext * _context{ nullptr };
QSize _size;
bool _altPressed{ false };
RateCounter fps;
QTimer _timer;
int testQmlTexture{ 0 };
public:
QObject * rootMenu;
QTestWindow() {
_timer.setInterval(1);
connect(&_timer, &QTimer::timeout, [=] {
draw();
});
DependencyManager::set<OffscreenUi>();
setSurfaceType(QSurface::OpenGLSurface);
QSurfaceFormat format;
format.setDepthBufferSize(16);
format.setStencilBufferSize(8);
format.setVersion(4, 1);
format.setProfile(QSurfaceFormat::OpenGLContextProfile::CompatibilityProfile);
format.setOption(QSurfaceFormat::DebugContext);
setFormat(format);
_context = new QOpenGLContext;
_context->setFormat(format);
if (!_context->create()) {
qFatal("Could not create OpenGL context");
}
show();
makeCurrent();
initializeOpenGLFunctions();
{
QOpenGLDebugLogger *logger = new QOpenGLDebugLogger(this);
logger->initialize(); // initializes in the current context, i.e. ctx
logger->enableMessages();
connect(logger, &QOpenGLDebugLogger::messageLogged, this, [&](const QOpenGLDebugMessage & debugMessage) {
qDebug() << debugMessage;
});
// logger->startLogging(QOpenGLDebugLogger::SynchronousLogging);
}
qDebug() << (const char*)this->glGetString(GL_VERSION);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glClearColor(0.2f, 0.2f, 0.2f, 1);
glDisable(GL_DEPTH_TEST);
MessageDialog::registerType();
HifiMenu::registerType();
auto offscreenUi = DependencyManager::get<OffscreenUi>();
offscreenUi->create(_context);
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);
}
});
makeCurrent();
offscreenUi->setProxyWindow(this);
setFramePosition(QPoint(-1000, 0));
resize(QSize(800, 600));
#ifdef QML_CONTROL_GALLERY
offscreenUi->setBaseUrl(QUrl::fromLocalFile(getTestQmlDir()));
offscreenUi->load(QUrl("main.qml"));
#else
offscreenUi->setBaseUrl(QUrl::fromLocalFile(getQmlDir()));
offscreenUi->load(QUrl("TestRoot.qml"));
offscreenUi->load(QUrl("RootMenu.qml"));
HifiMenu::load();
QObject* menuObject = offscreenUi->getRootItem()->findChild<QObject*>("HifiMenu");
HifiMenu* menu = offscreenUi->getRootItem()->findChild<HifiMenu*>();
menu->addMenu("", "File");
menu->addMenuItem("File", "Quit", []{
QApplication::quit();
});
menu->addCheckableMenuItem("File", "Toggle", false, [](bool toggled) {
qDebug() << "Toggle is " << toggled;
});
menu->addMenu("", "Edit");
menu->addMenuItem("Edit", "Undo");
menu->addMenuItem("Edit", "Redo");
menu->addMenuItem("Edit", "Copy");
menu->addMenuItem("Edit", "Cut");
menu->addMenuItem("Edit", "Paste");
menu->addMenu("", "Long Menu Name...");
#endif
installEventFilter(offscreenUi.data());
offscreenUi->resume();
_timer.start();
}
virtual ~QTestWindow() {
DependencyManager::destroy<OffscreenUi>();
}
private:
void draw() {
if (!isVisible()) {
return;
}
makeCurrent();
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glViewport(0, 0, _size.width() * devicePixelRatio(), _size.height() * devicePixelRatio());
renderQml();
_context->swapBuffers(this);
glFinish();
fps.increment();
if (fps.elapsed() >= 2.0f) {
qDebug() << "FPS: " << fps.rate();
fps.reset();
}
}
void makeCurrent() {
_context->makeCurrent(this);
}
void renderQml();
void resizeWindow(const QSize & size) {
_size = size;
DependencyManager::get<OffscreenUi>()->resize(_size);
}
protected:
void resizeEvent(QResizeEvent * ev) override {
resizeWindow(ev->size());
}
void keyPressEvent(QKeyEvent *event) {
_altPressed = Qt::Key_Alt == event->key();
switch (event->key()) {
case Qt::Key_L:
if (event->modifiers() & Qt::CTRL) {
auto offscreenUi = DependencyManager::get<OffscreenUi>();
HifiMenu * menu = offscreenUi->findChild<HifiMenu*>();
menu->addMenuItem("", "Test 3");
menu->addMenuItem("File", "Test 3");
}
break;
case Qt::Key_K:
if (event->modifiers() & Qt::CTRL) {
OffscreenUi::question("Message title", "Message contents", [](QMessageBox::Button b){
qDebug() << b;
});
}
break;
case Qt::Key_J:
if (event->modifiers() & Qt::CTRL) {
auto offscreenUi = DependencyManager::get<OffscreenUi>();
rootMenu = offscreenUi->getRootItem()->findChild<QObject*>("rootMenu");
QMetaObject::invokeMethod(rootMenu, "popup");
}
break;
}
QWindow::keyPressEvent(event);
}
QQmlContext* menuContext{ nullptr };
void keyReleaseEvent(QKeyEvent *event) {
if (_altPressed && Qt::Key_Alt == event->key()) {
HifiMenu::toggle();
}
}
void moveEvent(QMoveEvent *event) {
static qreal oldPixelRatio = 0.0;
if (devicePixelRatio() != oldPixelRatio) {
oldPixelRatio = devicePixelRatio();
resizeWindow(size());
}
QWindow::moveEvent(event);
}
};
void QTestWindow::renderQml() {
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
if (testQmlTexture > 0) {
glEnable(GL_TEXTURE_2D);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, testQmlTexture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
}
glBegin(GL_QUADS);
{
glTexCoord2f(0, 0);
glVertex2f(-1, -1);
glTexCoord2f(0, 1);
glVertex2f(-1, 1);
glTexCoord2f(1, 1);
glVertex2f(1, 1);
glTexCoord2f(1, 0);
glVertex2f(1, -1);
}
glEnd();
}
const char * LOG_FILTER_RULES = R"V0G0N(
*.debug=false
qt.quick.mouse.debug=false
)V0G0N";
int main(int argc, char** argv) {
QGuiApplication app(argc, argv);
// QLoggingCategory::setFilterRules(LOG_FILTER_RULES);
QTestWindow window;
app.exec();
return 0;
}
#include "main.moc"