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_hifi_libraries(shared octree environment gpu model fbx networking entities avatars
audio audio-client animation script-engine physics
render-utils entities-renderer)
render-utils entities-renderer ui)
add_dependency_external_projects(sdl2)

Binary file not shown.

View file

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

View file

@ -1,12 +1,10 @@
import QtQuick 2.3
import QtQuick.Controls 1.2
import QtQuick.Window 2.2
import QtQuick.Dialogs 1.2
import QtQuick.Controls.Styles 1.3
import QtWebKit 3.0
import "controls"
CustomDialog {
title: "Test Dlg"
Dialog {
title: "Browser Window"
id: testDialog
objectName: "Browser"
width: 1280
@ -18,7 +16,6 @@ CustomDialog {
anchors.fill: parent
anchors.margins: parent.margins
anchors.topMargin: parent.topMargin
ScrollView {
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 QtQuick 2.3
import QtQuick.Controls 1.2
import QtQuick.Window 2.2
import QtQuick.Controls.Styles 1.3
import "hifiConstants.js" as HifiConstants
import "controls"
import "styles"
CustomDialog {
Dialog {
title: "Login"
HifiPalette { id: hifiPalette }
SystemPalette { id: myPalette; colorGroup: SystemPalette.Active }
objectName: "LoginDialog"
height: 512
@ -50,11 +50,11 @@ CustomDialog {
source: "../images/hifi-logo.svg"
}
CustomBorder {
Border {
width: 304
height: 64
anchors.horizontalCenter: parent.horizontalCenter
CustomTextInput {
TextInput {
id: username
anchors.fill: parent
helperText: "Username or Email"
@ -67,11 +67,11 @@ CustomDialog {
}
}
CustomBorder {
Border {
width: 304
height: 64
anchors.horizontalCenter: parent.horizontalCenter
CustomTextInput {
TextInput {
id: password
anchors.fill: parent
echoMode: TextInput.Password
@ -94,7 +94,7 @@ CustomDialog {
}
}
CustomText {
Text {
anchors.horizontalCenter: parent.horizontalCenter
textFormat: Text.StyledText
width: parent.width
@ -117,7 +117,7 @@ CustomDialog {
width: 192
height: 64
anchors.horizontalCenter: parent.horizontalCenter
color: HifiConstants.color
color: hifiPalette.hifiBlue
border.width: 0
radius: 10
@ -142,7 +142,7 @@ CustomDialog {
width: 32
source: "../images/login.svg"
}
CustomText {
Text {
text: "Login"
color: "white"
width: 64
@ -152,7 +152,7 @@ CustomDialog {
}
CustomText {
Text {
width: parent.width
height: 24
horizontalAlignment: Text.AlignHCenter
@ -160,7 +160,7 @@ CustomDialog {
text:"Create Account"
font.pointSize: 12
font.bold: true
color: HifiConstants.color
color: hifiPalette.hifiBlue
MouseArea {
anchors.fill: parent
@ -170,14 +170,14 @@ CustomDialog {
}
}
CustomText {
Text {
width: parent.width
height: 24
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
font.pointSize: 12
text: "Recover Password"
color: HifiConstants.color
color: hifiPalette.hifiBlue
MouseArea {
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.Controls 1.2
import QtQuick.Window 2.2
import QtQuick.Dialogs 1.2
import QtQuick.Controls.Styles 1.3
Rectangle {
color: "teal"
@ -150,87 +147,4 @@ Rectangle {
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 QtQuick 2.3
// This is our primary 'window' object to which all dialogs and controls will
// be childed.
Root {
id: root
width: 1280
height: 720
anchors.fill: parent
}

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.Controls 1.2
import QtQuick.Window 2.2
import QtQuick.Dialogs 1.2
import QtQuick.Controls.Styles 1.3
import "controls"
CustomDialog {
Dialog {
title: "Test Dialog"
id: testDialog
objectName: "TestDialog"
@ -37,7 +36,7 @@ CustomDialog {
}
CustomTextEdit {
TextEdit {
id: edit
anchors.left: parent.left
anchors.leftMargin: 12
@ -49,7 +48,7 @@ CustomDialog {
anchors.topMargin: 12
}
CustomButton {
Button {
x: 128
y: 192
text: "Test"
@ -68,7 +67,7 @@ CustomDialog {
}
}
CustomButton {
Button {
id: customButton2
y: 192
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 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 {
id: root
width: 1280
height: 720
anchors.fill: parent
CustomButton {
onWidthChanged: {
console.log("Root width: " + width)
}
onHeightChanged: {
console.log("Root height: " + height)
}
Component.onCompleted: {
console.log("Completed root")
root.forceActiveFocus()
}
Button {
id: messageBox
anchors.right: createDialog.left
anchors.rightMargin: 24
@ -20,7 +35,7 @@ Root {
}
}
CustomButton {
Button {
id: createDialog
anchors.right: parent.right
anchors.rightMargin: 24
@ -28,8 +43,12 @@ Root {
anchors.bottomMargin: 24
text: "Create"
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.Controls 1.2
import QtQuick.Window 2.2
import QtQuick.Dialogs 1.2
import QtQuick.Controls.Styles 1.3
import "hifiConstants.js" as HifiConstants
import "."
import "../styles"
/*
* 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 {
SystemPalette { id: myPalette; colorGroup: SystemPalette.Active }
id: dialog
width: 256
height: 256
scale: 0.0
enabled: false
id: root
HifiPalette { id: hifiPalette }
SystemPalette { id: sysPalette; colorGroup: SystemPalette.Active }
x: parent ? parent.width / 2 - width / 2 : 0
y: parent ? parent.height / 2 - height / 2 : 0
property int animationDuration: 400
property bool destroyOnInvisible: false
property bool destroyOnCloseButton: true
property bool resizable: false
property int minX: 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
/*
* 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: {
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: {
visible = (scale != 0.0);
}
// Some dialogs should be destroyed when they become invisible, so handle that
onVisibleChanged: {
if (!visible && destroyOnInvisible) {
console.log("Destroying closed component");
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() {
if (destroyOnCloseButton) {
destroyOnInvisible = true
@ -42,102 +81,14 @@ Item {
enabled = false;
}
/*
* Resize support
*/
function deltaSize(dx, dy) {
width = Math.max(width + dx, minX)
height = Math.max(height + dy, minY)
}
Behavior on scale {
NumberAnimation {
//This specifies how long the animation takes
duration: dialog.animationDuration
//This selects an easing curve to interpolate with, the default is Easing.Linear
easing.type: Easing.InOutBounce
}
}
property int topMargin: dialog.height - clientBorder.height + 8
property int margins: 8
property string title
property int titleSize: titleBorder.height + 12
property string frameColor: HifiConstants.color
property string backgroundColor: myPalette.window
property string headerBackgroundColor: myPalette.dark
CustomBorder {
id: windowBorder
anchors.fill: parent
border.color: dialog.frameColor
color: dialog.backgroundColor
CustomBorder {
id: titleBorder
height: 48
anchors.right: parent.right
anchors.rightMargin: 0
anchors.left: parent.left
anchors.leftMargin: 0
border.color: dialog.frameColor
color: dialog.headerBackgroundColor
CustomText {
id: titleText
color: "white"
text: dialog.title
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
anchors.fill: parent
}
MouseArea {
id: titleDrag
anchors.right: closeButton.left
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.top: parent.top
anchors.rightMargin: 4
drag {
target: dialog
minimumX: 0
minimumY: 0
maximumX: dialog.parent ? dialog.parent.width - dialog.width : 0
maximumY: dialog.parent ? dialog.parent.height - dialog.height : 0
}
}
Image {
id: closeButton
x: 360
height: 16
anchors.verticalCenter: parent.verticalCenter
width: 16
anchors.right: parent.right
anchors.rightMargin: 12
source: "../styles/close.svg"
MouseArea {
anchors.fill: parent
onClicked: {
dialog.close();
}
}
}
} // header border
CustomBorder {
id: clientBorder
border.color: dialog.frameColor
color: "#00000000"
anchors.bottom: parent.bottom
anchors.bottomMargin: 0
anchors.top: titleBorder.bottom
anchors.topMargin: -titleBorder.border.width
anchors.right: parent.right
anchors.rightMargin: 0
anchors.left: parent.left
anchors.leftMargin: 0
clip: true
} // client border
} // window border
MouseArea {
id: sizeDrag
property int startX
@ -152,11 +103,91 @@ Item {
startY = mouseY
}
onPositionChanged: {
if (pressed && dialog.resizable) {
dialog.deltaSize((mouseX - startX), (mouseY - startY))
if (pressed && root.resizable) {
root.deltaSize((mouseX - startX), (mouseY - startY))
startX = mouseX
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.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.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 {
SystemPalette { id: myPalette; colorGroup: SystemPalette.Active }
property string helperText: ""
property string helperText
font.family: "Helvetica"
font.pointSize: 18
width: 256
@ -24,7 +24,7 @@ TextInput {
id: helperText
anchors.fill: parent
font.pointSize: parent.font.pointSize
font.family: "Helvetica"
font.family: parent.font.family
verticalAlignment: TextInput.AlignVCenter
text: parent.helperText
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
Rectangle {
SystemPalette { id: myPalette; colorGroup: SystemPalette.Active }
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
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;
// Starfield information
@ -707,6 +724,13 @@ void Application::initializeGL() {
initDisplay();
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();
qCDebug(interfaceapp, "init() complete.");
@ -735,17 +759,13 @@ void Application::initializeGL() {
// update before the first render
update(1.0f / _fps);
// The UI can't be created until the primary OpenGL
// context is created, because it needs to share
// texture resources
initializeUi();
InfoView::showFirstTime(INFO_HELP_PATH);
}
void Application::initializeUi() {
AddressBarDialog::registerType();
LoginDialog::registerType();
MessageDialog::registerType();
auto offscreenUi = DependencyManager::get<OffscreenUi>();
offscreenUi->create(_glWidget->context()->contextHandle());
@ -753,6 +773,7 @@ void Application::initializeUi() {
offscreenUi->setProxyWindow(_window->windowHandle());
offscreenUi->setBaseUrl(QUrl::fromLocalFile(PathUtils::resourcesPath() + "/qml/"));
offscreenUi->load("Root.qml");
offscreenUi->load("RootMenu.qml");
offscreenUi->setMouseTranslator([this](const QPointF& p){
if (OculusManager::isConnected()) {
glm::vec2 pos = _applicationOverlay.screenToOverlay(toGlm(p));
@ -964,6 +985,10 @@ bool Application::importSVOFromURL(const QString& urlString) {
bool Application::event(QEvent* event) {
switch (event->type()) {
case Lambda:
((LambdaEvent*)event)->call();
return true;
case QEvent::MouseMove:
mouseMoveEvent((QMouseEvent*)event);
return true;

View file

@ -21,6 +21,7 @@
#include <QSet>
#include <QStringList>
#include <QUndoStack>
#include <functional>
#include <AbstractScriptingServicesInterface.h>
#include <AbstractViewStateInterface.h>
@ -147,6 +148,8 @@ public:
Application(int& argc, char** argv, QElapsedTimer &startup_time);
~Application();
void postLambdaEvent(std::function<void()> f);
void loadScripts();
QString getPreviousScriptLocation();
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
glm::vec2 projection = screenToOverlay(glm::vec2(_reticlePosition[MOUSE].x(),
_reticlePosition[MOUSE].y()));
with_each_texture(_overlays.getTexture(), _newUiTexture, [&] {
with_each_texture(_overlays.getTexture(), 0, [&] {
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 <QDateTime>
#include <QFileInfo>
#include <QDir>
#include "PathUtils.h"
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
static QString staticResourcePath = QCoreApplication::applicationDirPath() + "/../Resources/";
#else
static QString staticResourcePath = QCoreApplication::applicationDirPath() + "/resources/";
#endif
#endif
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)
: QQuickItem(parent) { }
OffscreenQmlDialog::~OffscreenQmlDialog() {
}
void OffscreenQmlDialog::hide() {
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 <QGLWidget>
#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
// the render. Could possibly be increased depending on the framerate we expect to
@ -92,10 +97,10 @@ void OffscreenUi::create(QOpenGLContext* shareContext) {
#ifdef DEBUG
connect(_quickWindow, &QQuickWindow::focusObjectChanged, [this]{
qDebug() << "New focus item " << _quickWindow->focusObject();
qCDebug(offscreenFocus) << "New focus item " << _quickWindow->focusObject();
});
connect(_quickWindow, &QQuickWindow::activeFocusItemChanged, [this] {
qDebug() << "New active focus item " << _quickWindow->activeFocusItem();
qCDebug(offscreenFocus) << "New active focus item " << _quickWindow->activeFocusItem();
});
#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;
_fboCache.setSize(newSize * pixelRatio);
if (_quickWindow) {
_quickWindow->setGeometry(QRect(QPoint(), newSize));
}
_quickWindow->contentItem()->setSize(newSize);
// Update our members
if (_rootItem) {
_rootItem->setSize(newSize);
}
if (_quickWindow) {
_quickWindow->setGeometry(QRect(QPoint(), newSize));
}
doneCurrent();
}
QQmlContext* OffscreenUi::qmlContext() {
if (nullptr == _rootItem) {
return _qmlComponent->creationContext();
}
return QQmlEngine::contextForObject(_rootItem);
QQuickItem* OffscreenUi::getRootItem() {
return _rootItem;
}
//QQmlContext* OffscreenUi::qmlContext() {
// if (nullptr == _rootItem) {
// return _qmlComponent->creationContext();
// }
// return QQmlEngine::contextForObject(_rootItem);
//}
void OffscreenUi::setBaseUrl(const QUrl& baseUrl) {
_qmlEngine->setBaseUrl(baseUrl);
}
void OffscreenUi::load(const QUrl& qmlSource, std::function<void(QQmlContext*)> f) {
void OffscreenUi::load(const QUrl& qmlSource, std::function<void(QQmlContext*, QObject*)> f) {
qDebug() << "Loading QML from URL " << qmlSource;
_qmlComponent->loadUrl(qmlSource);
if (_qmlComponent->isLoading()) {
connect(_qmlComponent, &QQmlComponent::statusChanged, this, []{});
} else {
finishQmlLoad();
if (_qmlComponent->isLoading())
connect(_qmlComponent, &QQmlComponent::statusChanged, this,
[this, f](QQmlComponent::Status){
finishQmlLoad(f);
});
else {
finishQmlLoad(f);
}
}
@ -163,8 +177,8 @@ void OffscreenUi::requestRender() {
}
}
void OffscreenUi::finishQmlLoad() {
disconnect(_qmlComponent, &QQmlComponent::statusChanged, this, &OffscreenUi::finishQmlLoad);
void OffscreenUi::finishQmlLoad(std::function<void(QQmlContext*, QObject*)> f) {
disconnect(_qmlComponent, &QQmlComponent::statusChanged, this, 0);
if (_qmlComponent->isError()) {
QList<QQmlError> errorList = _qmlComponent->errors();
foreach(const QQmlError &error, errorList) {
@ -173,7 +187,8 @@ void OffscreenUi::finishQmlLoad() {
return;
}
QObject* newObject = _qmlComponent->create();
QQmlContext * newContext = new QQmlContext(_qmlEngine, qApp);
QObject* newObject = _qmlComponent->beginCreate(newContext);
if (_qmlComponent->isError()) {
QList<QQmlError> errorList = _qmlComponent->errors();
foreach(const QQmlError &error, errorList)
@ -184,9 +199,13 @@ void OffscreenUi::finishQmlLoad() {
return;
}
f(newContext, newObject);
_qmlComponent->completeCreate();
QQuickItem* newItem = qobject_cast<QQuickItem*>(newObject);
if (!newItem) {
qWarning("run: Not a QQuickItem");
return;
delete newObject;
if (!_rootItem) {
qFatal("Unable to find root QQuickItem");
@ -197,18 +216,17 @@ void OffscreenUi::finishQmlLoad() {
// Make sure we make items focusable (critical for
// supporting keyboard shortcuts)
newItem->setFlag(QQuickItem::ItemIsFocusScope, true);
if (!_rootItem) {
// The root item is ready. Associate it with the window.
_rootItem = newItem;
_rootItem->setParentItem(_quickWindow->contentItem());
_rootItem->setSize(_quickWindow->renderTargetSize());
_rootItem->forceActiveFocus();
} else {
// Allow child windows to be destroyed from JS
QQmlEngine::setObjectOwnership(newItem, QQmlEngine::JavaScriptOwnership);
newItem->setParent(_rootItem);
newItem->setParentItem(_rootItem);
newItem->setEnabled(true);
}
}
@ -390,54 +408,62 @@ void OffscreenUi::setProxyWindow(QWindow* 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);
// First load?
if (!item) {
load(url);
return;
load(url, f);
item = _rootItem->findChild<QQuickItem*>(name);
}
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);
// First load?
if (!item) {
load(url);
return;
load(url, f);
item = _rootItem->findChild<QQuickItem*>(name);
}
item->setEnabled(!item->isEnabled());
}
void OffscreenUi::messageBox(const QString& title, const QString& text,
ButtonCallback callback,
QMessageBox::Icon icon,
QMessageBox::StandardButtons buttons,
ButtonCallback f) {
QMessageBox::StandardButtons buttons) {
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,
QMessageBox::StandardButtons buttons,
ButtonCallback callback) {
callback(QMessageBox::information(nullptr, title, text, buttons));
ButtonCallback callback,
QMessageBox::StandardButtons buttons) {
messageBox(title, text, callback, (QMessageBox::Icon)MessageDialog::Information, buttons);
}
void OffscreenUi::question(const QString& title, const QString& text,
QMessageBox::StandardButtons buttons,
ButtonCallback callback) {
callback(QMessageBox::question(nullptr, title, text, buttons));
ButtonCallback callback,
QMessageBox::StandardButtons buttons) {
messageBox(title, text, callback, (QMessageBox::Icon)MessageDialog::Question, buttons);
}
void OffscreenUi::warning(const QString& title, const QString& text,
QMessageBox::StandardButtons buttons,
ButtonCallback callback) {
callback(QMessageBox::warning(nullptr, title, text, buttons));
ButtonCallback callback,
QMessageBox::StandardButtons buttons) {
messageBox(title, text, callback, (QMessageBox::Icon)MessageDialog::Warning, buttons);
}
void OffscreenUi::critical(const QString& title, const QString& text,
QMessageBox::StandardButtons buttons,
ButtonCallback callback) {
callback(QMessageBox::critical(nullptr, title, text, buttons));
ButtonCallback callback,
QMessageBox::StandardButtons buttons) {
messageBox(title, text, callback, (QMessageBox::Icon)MessageDialog::Critical, buttons);
}

View file

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

View file

@ -10,7 +10,6 @@
#include "TextRenderer.h"
#include "MatrixStack.h"
#include "OffscreenUi.h"
#include <QWindow>
#include <QFile>
@ -27,6 +26,7 @@
#include <QOpenGLVertexArrayObject>
#include <QApplication>
#include <QOpenGLDebugLogger>
#include <unordered_map>
#include <memory>
#include <glm/glm.hpp>
@ -80,6 +80,7 @@ const QString& getQmlDir() {
}
return dir;
}
// Create a simple OpenGL window that renders text in various ways
class QTestWindow : public QWindow {
Q_OBJECT
@ -88,24 +89,17 @@ class QTestWindow : public QWindow {
QSize _size;
TextRenderer* _textRenderer[4];
RateCounter fps;
int testQmlTexture{ 0 };
//ProgramPtr _planeProgam;
//ShapeWrapperPtr _planeShape;
protected:
void renderText();
void renderQml();
private:
void resizeWindow(const QSize& size) {
_size = size;
DependencyManager::get<OffscreenUi>()->resize(_size);
}
public:
QTestWindow() {
DependencyManager::set<OffscreenUi>();
setSurfaceType(QSurface::OpenGLSurface);
QSurfaceFormat format;
@ -165,30 +159,10 @@ public:
glClearColor(0.2f, 0.2f, 0.2f, 1);
glDisable(GL_DEPTH_TEST);
auto offscreenUi = DependencyManager::get<OffscreenUi>();
offscreenUi->create(_context);
// FIXME, need to switch to a QWindow for mouse and keyboard input to work
offscreenUi->setProxyWindow(this);
// "#0e7077"
makeCurrent();
setFramePosition(QPoint(-1000, 0));
resize(QSize(800, 600));
offscreenUi->setBaseUrl(QUrl::fromLocalFile(getQmlDir()));
offscreenUi->load(QUrl("TestRoot.qml"));
offscreenUi->addImportPath(getQmlDir());
offscreenUi->addImportPath(".");
connect(offscreenUi.data(), &OffscreenUi::textureUpdated, this, [this, offscreenUi](int textureId) {
offscreenUi->lockTexture(textureId);
assert(!glGetError());
GLuint oldTexture = testQmlTexture;
testQmlTexture = textureId;
if (oldTexture) {
offscreenUi->releaseTexture(oldTexture);
}
});
installEventFilter(offscreenUi.data());
offscreenUi->resume();
}
virtual ~QTestWindow() {
@ -204,28 +178,6 @@ protected:
void resizeEvent(QResizeEvent* ev) override {
resizeWindow(ev->size());
}
void keyPressEvent(QKeyEvent* event) {
switch (event->key()) {
case Qt::Key_L:
if (event->modifiers() & Qt::CTRL) {
DependencyManager::get<OffscreenUi>()->toggle(QString("TestDialog.qml"), "TestDialog");
}
break;
}
QWindow::keyPressEvent(event);
}
void moveEvent(QMoveEvent* event) {
static qreal oldPixelRatio = 0.0;
if (devicePixelRatio() != oldPixelRatio) {
oldPixelRatio = devicePixelRatio();
resizeWindow(size());
}
QWindow::moveEvent(event);
}
};
#ifndef SERIF_FONT_FAMILY
@ -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() {
if (!isVisible()) {
return;
}
makeCurrent();
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glViewport(0, 0, _size.width() * devicePixelRatio(), _size.height() * devicePixelRatio());
//renderText();
renderQml();
renderText();
_context->swapBuffers(this);
glFinish();
@ -327,10 +256,8 @@ void QTestWindow::draw() {
}
int main(int argc, char** argv) {
QApplication app(argc, argv);
//QLoggingCategory::setFilterRules("qt.quick.mouse.debug = true");
QGuiApplication app(argc, argv);
QTestWindow window;
QTimer timer;
timer.setInterval(1);
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"