diff --git a/interface/resources/qml/hifi/AvatarPackager.qml b/interface/resources/qml/hifi/AvatarPackager.qml deleted file mode 100644 index 2492746627..0000000000 --- a/interface/resources/qml/hifi/AvatarPackager.qml +++ /dev/null @@ -1,276 +0,0 @@ -import QtQuick 2.6 -import QtQuick.Controls 2.2 -import QtQuick.Layouts 1.3 -import QtQml.Models 2.1 -import QtGraphicalEffects 1.0 -import "../controlsUit" 1.0 as HifiControls -import "../stylesUit" 1.0 -import "../windows" as Windows -import "../dialogs" -import "./avatarPackager" 1.0 -import "avatarapp" 1.0 as AvatarApp - -Windows.ScrollingWindow { - id: root - objectName: "AvatarPackager" - width: 480 - height: 706 - title: "Avatar Packager" - resizable: false - opacity: parent.opacity - destroyOnHidden: true - implicitWidth: 384; implicitHeight: 640 - minSize: Qt.vector2d(480, 706) - - HifiConstants { id: hifi } - - Item { - id: windowContent - height: pane.height - width: pane.width - - InfoBox { - id: fileListPopup - - title: "List of Files" - - content: Rectangle { - id: fileList - - color: "#404040" - - anchors.fill: parent - anchors.topMargin: 10 - anchors.bottomMargin: 10 - anchors.leftMargin: 29 - anchors.rightMargin: 29 - - clip: true - - ListView { - anchors.fill: parent - model: AvatarPackagerCore.currentAvatarProject === null ? [] : AvatarPackagerCore.currentAvatarProject.projectFiles - delegate: Rectangle { - width: parent.width - height: fileText.implicitHeight + 8 - color: "#404040" - RalewaySemiBold { - id: fileText - size: 16 - elide: Text.ElideLeft - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - anchors.leftMargin: 16 - anchors.rightMargin: 16 - anchors.topMargin: 4 - width: parent.width - 10 - color: "white" - text: modelData - } - } - } - } - } - - Rectangle { - id: modalOverlay - anchors.fill: parent - z: 20 - color: "#a15d5d5d" - visible: false - - // This mouse area captures the cursor events while the modalOverlay is active - MouseArea { - anchors.fill: parent - propagateComposedEvents: false; - hoverEnabled: true; - } - } - - AvatarApp.MessageBox { - id: popup - anchors.fill: parent - visible: false - closeOnClickOutside: true - } - - Column { - id: avatarPackager - anchors.fill: parent - state: "main" - states: [ - State { - name: AvatarPackagerState.main - PropertyChanges { target: avatarPackagerHeader; title: qsTr("Avatar Packager"); faqEnabled: true; backButtonVisible: false } - PropertyChanges { target: avatarPackagerMain; visible: true } - PropertyChanges { target: avatarPackagerFooter; content: avatarPackagerMain.footer } - }, - State { - name: AvatarPackagerState.createProject - PropertyChanges { target: avatarPackagerHeader; title: qsTr("Create Project") } - PropertyChanges { target: createAvatarProject; visible: true } - PropertyChanges { target: avatarPackagerFooter; content: createAvatarProject.footer } - }, - State { - name: AvatarPackagerState.project - PropertyChanges { target: avatarPackagerHeader; title: AvatarPackagerCore.currentAvatarProject.name; canRename: true } - PropertyChanges { target: avatarProject; visible: true } - PropertyChanges { target: avatarPackagerFooter; content: avatarProject.footer } - }, - State { - name: "project-upload" - PropertyChanges { target: avatarPackagerHeader; title: AvatarPackagerCore.currentAvatarProject.name; backButtonEnabled: false } - PropertyChanges { target: avatarUploader; visible: true } - PropertyChanges { target: avatarPackagerFooter; visible: false } - } - ] - - property alias showModalOverlay: modalOverlay.visible - - function openProject(path) { - let project = AvatarPackagerCore.openAvatarProject(path); - if (project) { - avatarProject.reset(); - avatarPackager.state = "project"; - } - return project; - } - - AvatarPackagerHeader { - id: avatarPackagerHeader - onBackButtonClicked: { - avatarPackager.state = "main" - } - } - - Item { - height: pane.height - avatarPackagerHeader.height - avatarPackagerFooter.height - width: pane.width - - Rectangle { - anchors.fill: parent - color: "#404040" - } - - AvatarProject { - id: avatarProject - colorScheme: root.colorScheme - anchors.fill: parent - } - - AvatarProjectUpload { - id: avatarUploader - anchors.fill: parent - root: avatarProject - } - - CreateAvatarProject { - id: createAvatarProject - colorScheme: root.colorScheme - anchors.fill: parent - } - - Item { - id: avatarPackagerMain - visible: false - anchors.fill: parent - - property var footer: Item { - anchors.fill: parent - anchors.rightMargin: 17 - HifiControls.Button { - id: createProjectButton - anchors.verticalCenter: parent.verticalCenter - anchors.right: openProjectButton.left - anchors.rightMargin: 22 - height: 40 - width: 134 - text: qsTr("New Project") - colorScheme: root.colorScheme - onClicked: { - createAvatarProject.clearInputs(); - avatarPackager.state = AvatarPackagerState.createProject; - } - } - - HifiControls.Button { - id: openProjectButton - anchors.verticalCenter: parent.verticalCenter - anchors.right: parent.right - height: 40 - width: 133 - text: qsTr("Open Project") - color: hifi.buttons.blue - colorScheme: root.colorScheme - onClicked: { - avatarPackager.showModalOverlay = true; - let browser = desktop.fileDialog({ - selectDirectory: false, - dir: fileDialogHelper.pathToUrl(AvatarPackagerCore.AVATAR_PROJECTS_PATH), - filter: "Avatar Project FST Files (*.fst)", - title: "Open Project (.fst)", - }); - - browser.canceled.connect(function() { - avatarPackager.showModalOverlay = false; - }); - - browser.selectedFile.connect(function(fileUrl) { - let fstFilePath = fileDialogHelper.urlToPath(fileUrl); - let currentAvatarProject = avatarPackager.openProject(fstFilePath); - if (currentAvatarProject) { - avatarPackager.showModalOverlay = false; - } - }); - } - } - } - - Flow { - visible: AvatarPackagerCore.recentProjects.length === 0 - anchors { - fill: parent - topMargin: 18 - leftMargin: 16 - rightMargin: 16 - } - RalewayRegular { - size: 20 - color: "white" - text: qsTr("Use a custom avatar to express your identity") - } - RalewayRegular { - size: 20 - color: "white" - text: qsTr("To learn more about using this tool, visit our docs") - } - } - - Column { - visible: AvatarPackagerCore.recentProjects.length > 0 - anchors { - fill: parent - topMargin: 18 - leftMargin: 16 - rightMargin: 16 - } - spacing: 10 - - Repeater { - model: AvatarPackagerCore.recentProjects - AvatarProjectCard { - title: modelData.name - path: modelData.path - onOpen: avatarPackager.openProject(modelData.path) - } - } - } - } - } - AvatarPackagerFooter { - id: avatarPackagerFooter - } - } - } -} diff --git a/interface/resources/qml/hifi/AvatarPackagerWindow.qml b/interface/resources/qml/hifi/AvatarPackagerWindow.qml new file mode 100644 index 0000000000..9d434ef97c --- /dev/null +++ b/interface/resources/qml/hifi/AvatarPackagerWindow.qml @@ -0,0 +1,32 @@ +import QtQuick 2.6 +import QtQuick.Controls 2.2 +import QtQuick.Layouts 1.3 +import QtQml.Models 2.1 +import QtGraphicalEffects 1.0 +import "../controlsUit" 1.0 as HifiControls +import "../stylesUit" 1.0 +import "../windows" as Windows +import "../controls" 1.0 +import "../dialogs" +import "avatarPackager" 1.0 +import "avatarapp" 1.0 as AvatarApp + +Windows.ScrollingWindow { + id: root + objectName: "AvatarPackager" + width: 480 + height: 706 + title: "Avatar Packager" + resizable: false + opacity: parent.opacity + destroyOnHidden: true + implicitWidth: 384; implicitHeight: 640 + minSize: Qt.vector2d(480, 706) + + HifiConstants { id: hifi } + + AvatarPackagerApp { + height: pane.height + width: pane.width + } +} diff --git a/interface/resources/qml/hifi/avatarPackager/AvatarPackagerApp.qml b/interface/resources/qml/hifi/avatarPackager/AvatarPackagerApp.qml new file mode 100644 index 0000000000..2b21b4d938 --- /dev/null +++ b/interface/resources/qml/hifi/avatarPackager/AvatarPackagerApp.qml @@ -0,0 +1,288 @@ +import QtQuick 2.6 +import QtQuick.Controls 2.2 +import QtQuick.Layouts 1.3 +import QtQml.Models 2.1 +import QtGraphicalEffects 1.0 +import "../../controlsUit" 1.0 as HifiControls +import "../../stylesUit" 1.0 +import "../../windows" as Windows +import "../../controls" 1.0 +import "../../dialogs" +import "../avatarapp" 1.0 as AvatarApp + +Item { + HifiConstants { id: hifi } + + property alias desktopObject: avatarPackager.desktopObject + + id: windowContent + // height: pane ? pane.height : parent.width + // width: pane ? pane.width : parent.width + + + MouseArea { + anchors.fill: parent + onClicked: { + unfocusser.forceActiveFocus(); + } + Item { + id: unfocusser + visible: false + } + } + + InfoBox { + id: fileListPopup + + title: "List of Files" + + content: Rectangle { + id: fileList + + color: "#404040" + + anchors.fill: parent + anchors.topMargin: 10 + anchors.bottomMargin: 10 + anchors.leftMargin: 29 + anchors.rightMargin: 29 + + clip: true + + ListView { + anchors.fill: parent + model: AvatarPackagerCore.currentAvatarProject === null ? [] : AvatarPackagerCore.currentAvatarProject.projectFiles + delegate: Rectangle { + width: parent.width + height: fileText.implicitHeight + 8 + color: "#404040" + RalewaySemiBold { + id: fileText + size: 16 + elide: Text.ElideLeft + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.leftMargin: 16 + anchors.rightMargin: 16 + anchors.topMargin: 4 + width: parent.width - 10 + color: "white" + text: modelData + } + } + } + } + } + + Rectangle { + id: modalOverlay + anchors.fill: parent + z: 20 + color: "#a15d5d5d" + visible: false + + // This mouse area captures the cursor events while the modalOverlay is active + MouseArea { + anchors.fill: parent + propagateComposedEvents: false; + hoverEnabled: true; + } + } + + AvatarApp.MessageBox { + id: popup + anchors.fill: parent + visible: false + closeOnClickOutside: true + } + + Column { + id: avatarPackager + anchors.fill: parent + state: "main" + states: [ + State { + name: AvatarPackagerState.main + PropertyChanges { target: avatarPackagerHeader; title: qsTr("Avatar Packager"); docsEnabled: true; backButtonVisible: false } + PropertyChanges { target: avatarPackagerMain; visible: true } + PropertyChanges { target: avatarPackagerFooter; content: avatarPackagerMain.footer } + }, + State { + name: AvatarPackagerState.createProject + PropertyChanges { target: avatarPackagerHeader; title: qsTr("Create Project") } + PropertyChanges { target: createAvatarProject; visible: true } + PropertyChanges { target: avatarPackagerFooter; content: createAvatarProject.footer } + }, + State { + name: AvatarPackagerState.project + PropertyChanges { target: avatarPackagerHeader; title: AvatarPackagerCore.currentAvatarProject.name; canRename: true } + PropertyChanges { target: avatarProject; visible: true } + PropertyChanges { target: avatarPackagerFooter; content: avatarProject.footer } + }, + State { + name: AvatarPackagerState.projectUpload + PropertyChanges { target: avatarPackagerHeader; title: AvatarPackagerCore.currentAvatarProject.name; backButtonEnabled: false } + PropertyChanges { target: avatarUploader; visible: true } + PropertyChanges { target: avatarPackagerFooter; visible: false } + } + ] + + property alias showModalOverlay: modalOverlay.visible + + property var desktopObject: desktop + + function openProject(path) { + let project = AvatarPackagerCore.openAvatarProject(path); + if (project) { + avatarProject.reset(); + avatarPackager.state = AvatarPackagerState.project; + } + return project; + } + + function openDocs() { + Qt.openUrlExternally("https://docs.highfidelity.com/create/avatars/create-avatars#how-to-package-your-avatar"); + } + + AvatarPackagerHeader { + id: avatarPackagerHeader + colorScheme: root.colorScheme + onBackButtonClicked: { + avatarPackager.state = AvatarPackagerState.main; + } + onDocsButtonClicked: { + avatarPackager.openDocs(); + } + } + + Item { + height: windowContent.height - avatarPackagerHeader.height - avatarPackagerFooter.height + width: windowContent.width + + Rectangle { + anchors.fill: parent + color: "#404040" + } + + AvatarProject { + id: avatarProject + colorScheme: root.colorScheme + anchors.fill: parent + } + + AvatarProjectUpload { + id: avatarUploader + anchors.fill: parent + root: avatarProject + } + + CreateAvatarProject { + id: createAvatarProject + colorScheme: root.colorScheme + anchors.fill: parent + } + + Item { + id: avatarPackagerMain + visible: false + anchors.fill: parent + + property var footer: Item { + anchors.fill: parent + anchors.rightMargin: 17 + HifiControls.Button { + id: createProjectButton + anchors.verticalCenter: parent.verticalCenter + anchors.right: openProjectButton.left + anchors.rightMargin: 22 + height: 40 + width: 134 + text: qsTr("New Project") + colorScheme: root.colorScheme + onClicked: { + createAvatarProject.clearInputs(); + avatarPackager.state = AvatarPackagerState.createProject; + } + } + + HifiControls.Button { + id: openProjectButton + anchors.verticalCenter: parent.verticalCenter + anchors.right: parent.right + height: 40 + width: 133 + text: qsTr("Open Project") + color: hifi.buttons.blue + colorScheme: root.colorScheme + onClicked: { + avatarPackager.showModalOverlay = true; + + let browser = avatarPackager.desktopObject.fileDialog({ + selectDirectory: false, + dir: fileDialogHelper.pathToUrl(AvatarPackagerCore.AVATAR_PROJECTS_PATH), + filter: "Avatar Project FST Files (*.fst)", + title: "Open Project (.fst)", + }); + + browser.canceled.connect(function() { + avatarPackager.showModalOverlay = false; + }); + + browser.selectedFile.connect(function(fileUrl) { + let fstFilePath = fileDialogHelper.urlToPath(fileUrl); + let currentAvatarProject = avatarPackager.openProject(fstFilePath); + if (currentAvatarProject) { + avatarPackager.showModalOverlay = false; + } + }); + } + } + } + + Flow { + visible: AvatarPackagerCore.recentProjects.length === 0 + anchors { + fill: parent + topMargin: 18 + leftMargin: 16 + rightMargin: 16 + } + RalewayRegular { + size: 20 + color: "white" + text: qsTr("Use a custom avatar to express your identity") + } + RalewayRegular { + size: 20 + color: "white" + text: qsTr("To learn more about using this tool, visit our docs") + } + } + + Column { + visible: AvatarPackagerCore.recentProjects.length > 0 + anchors { + fill: parent + topMargin: 18 + leftMargin: 16 + rightMargin: 16 + } + spacing: 10 + + Repeater { + model: AvatarPackagerCore.recentProjects + AvatarProjectCard { + title: modelData.name + path: modelData.projectPath + onOpen: avatarPackager.openProject(modelData.path) + } + } + } + } + } + AvatarPackagerFooter { + id: avatarPackagerFooter + } + } +} diff --git a/interface/resources/qml/hifi/avatarPackager/AvatarPackagerHeader.qml b/interface/resources/qml/hifi/avatarPackager/AvatarPackagerHeader.qml index 5b01005edd..7037aa9d92 100644 --- a/interface/resources/qml/hifi/avatarPackager/AvatarPackagerHeader.qml +++ b/interface/resources/qml/hifi/avatarPackager/AvatarPackagerHeader.qml @@ -11,11 +11,18 @@ Rectangle { color: "#252525" property alias title: title.text - property alias faqEnabled: faq.visible + property alias docsEnabled: docs.visible property bool backButtonVisible: true // If false, is not visible and does not take up space property bool backButtonEnabled: true // If false, is not visible but does not affect space - property bool canRename: false; + property bool canRename: false + property int colorScheme + + property color textColor: "white" + property color hoverTextColor: "gray" + property color pressedTextColor: "#6A6A6A" + signal backButtonClicked + signal docsButtonClicked RalewaySemiBold { id: back @@ -27,7 +34,7 @@ Rectangle { anchors.leftMargin: 16 anchors.verticalCenter: back.verticalCenter text: "◀" - color: "white" + color: textColor MouseArea { anchors.fill: parent onClicked: root.backButtonClicked() @@ -37,37 +44,102 @@ Rectangle { states: [ State { name: "hovering" - PropertyChanges { - target: back - color: "gray" - } + PropertyChanges { target: back; color: hoverTextColor } } ] } } - - RalewaySemiBold { - id: title - size: 28 + Item { + id: titleArea anchors.top: parent.top anchors.bottom: parent.bottom anchors.left: root.backButtonVisible ? back.right : parent.left anchors.leftMargin: root.backButtonVisible ? 11 : 21 anchors.verticalCenter: title.verticalCenter - text: qsTr("Avatar Packager") - color: "white" + anchors.right: docs.left + states: [ + State { + name: "renaming" + PropertyChanges { target: title; visible: false } + PropertyChanges { target: titleInputArea; visible: true } + } + ] + + RalewaySemiBold { + id: title + size: 28 + anchors.fill: parent + text: qsTr("Avatar Packager") + color: "white" + + MouseArea { + anchors.fill: parent + onClicked: { + if (!root.canRename || AvatarPackagerCore.currentAvatarProject === null) { + return; + } + + titleArea.state = "renaming"; + titleInput.text = AvatarPackagerCore.currentAvatarProject.name; + titleInput.selectAll(); + titleInput.forceActiveFocus(Qt.MouseFocusReason); + } + } + } + Item { + id: titleInputArea + visible: false + anchors.fill: parent + + HifiControls.TextField { + id: titleInput + anchors.fill: parent + text: "" + colorScheme: root.colorScheme + font.family: "Fira Sans" + font.pixelSize: 28 + z: 200 + onFocusChanged: { + if (titleArea.state === "renaming" && !titleArea.focus) { + //titleArea.state = ""; + accepted(); + } + } + Keys.onPressed: { + if (event.key === Qt.Key_Escape) { + titleArea.state = ""; + } + } + onAccepted: { + if (acceptableInput) { + //AvatarPackagerCore.renameProject(text); + console.warn(text); + AvatarPackagerCore.currentAvatarProject.name = text; + console.warn(AvatarPackagerCore.currentAvatarProject.name); + + } + titleArea.state = ""; + } + } + } } RalewaySemiBold { - id: faq + id: docs visible: false size: 28 anchors.top: parent.top anchors.bottom: parent.bottom anchors.right: parent.right anchors.rightMargin: 16 - anchors.verticalCenter: faq.verticalCenter + anchors.verticalCenter: docs.verticalCenter text: qsTr("Docs") color: "white" + MouseArea { + anchors.fill: parent + onClicked: { + docsButtonClicked(); + } + } } } diff --git a/interface/resources/qml/hifi/avatarPackager/AvatarPackagerState.qml b/interface/resources/qml/hifi/avatarPackager/AvatarPackagerState.qml index f12edf4952..c81173a080 100644 --- a/interface/resources/qml/hifi/avatarPackager/AvatarPackagerState.qml +++ b/interface/resources/qml/hifi/avatarPackager/AvatarPackagerState.qml @@ -6,4 +6,5 @@ Item { readonly property string main: "main" readonly property string project: "project" readonly property string createProject: "createProject" + readonly property string projectUpload: "projectUpload" } diff --git a/interface/resources/qml/hifi/avatarPackager/AvatarProject.qml b/interface/resources/qml/hifi/avatarPackager/AvatarProject.qml index c94c9570ae..a5a2263346 100644 --- a/interface/resources/qml/hifi/avatarPackager/AvatarProject.qml +++ b/interface/resources/qml/hifi/avatarPackager/AvatarProject.qml @@ -178,7 +178,7 @@ Item { } }); root.uploader.send(); - avatarPackager.state = "project-upload"; + avatarPackager.state = AvatarPackagerState.projectUpload; } function showConfirmUploadPopup() { diff --git a/interface/resources/qml/hifi/avatarPackager/AvatarProjectCard.qml b/interface/resources/qml/hifi/avatarPackager/AvatarProjectCard.qml index 736de2019c..5496711d44 100644 --- a/interface/resources/qml/hifi/avatarPackager/AvatarProjectCard.qml +++ b/interface/resources/qml/hifi/avatarPackager/AvatarProjectCard.qml @@ -53,11 +53,14 @@ Item { RalewayBold { id: title + elide: "ElideRight" anchors { top: parent.top topMargin: 13 left: parent.left leftMargin: 16 + right: parent.right + rightMargin: 16 } text: "" size: 16 @@ -69,7 +72,11 @@ Item { top: title.bottom left: parent.left leftMargin: 32 + right: background.right + rightMargin: 16 } + elide: "ElideLeft" + horizontalAlignment: Text.AlignRight text: "<path missing>" size: 20 } diff --git a/interface/resources/qml/hifi/avatarPackager/AvatarProjectUpload.qml b/interface/resources/qml/hifi/avatarPackager/AvatarProjectUpload.qml index 2031392ca2..c1d1a98158 100644 --- a/interface/resources/qml/hifi/avatarPackager/AvatarProjectUpload.qml +++ b/interface/resources/qml/hifi/avatarPackager/AvatarProjectUpload.qml @@ -21,8 +21,8 @@ Item { running: false repeat: false onTriggered: { - if (avatarPackager.state =="project-upload") { - avatarPackager.state = "project" + if (avatarPackager.state === AvatarPackagerState.projectUpload) { + avatarPackager.state = AvatarPackagerState.project; } } } @@ -130,7 +130,7 @@ Item { AvatarUploadStatusItem { id: statusCategories uploader: root.uploader - text: "Retreiving categories" + text: "Retrieving categories" uploaderState: 1 } @@ -189,8 +189,8 @@ Item { colorScheme: root.colorScheme width: 133 height: 40 - onClicked: function() { - avatarPackager.state = "project" + onClicked: { + avatarPackager.state = AvatarPackagerState.project; } } } diff --git a/interface/resources/qml/hifi/avatarPackager/AvatarUploadStatusItem.qml b/interface/resources/qml/hifi/avatarPackager/AvatarUploadStatusItem.qml index ca01e453e9..4749e912c6 100644 --- a/interface/resources/qml/hifi/avatarPackager/AvatarUploadStatusItem.qml +++ b/interface/resources/qml/hifi/avatarPackager/AvatarUploadStatusItem.qml @@ -14,9 +14,9 @@ Item { property int uploaderState; property var uploader; - state: root.uploader.state > uploaderState - ? "success" - : (root.uploader.error !== 0 ? "fail" : (root.uploader.state === uploaderState ? "running" : "")) + state: root.uploader === undefined ? "" : + (root.uploader.state > uploaderState ? "success" + : (root.uploader.error !== 0 ? "fail" : (root.uploader.state === uploaderState ? "running" : ""))) states: [ State { @@ -90,4 +90,4 @@ Item { size: 28 color: "#777777" } -} \ No newline at end of file +} diff --git a/interface/resources/qml/hifi/avatarPackager/CreateAvatarProject.qml b/interface/resources/qml/hifi/avatarPackager/CreateAvatarProject.qml index a5d335feba..d6f530a196 100644 --- a/interface/resources/qml/hifi/avatarPackager/CreateAvatarProject.qml +++ b/interface/resources/qml/hifi/avatarPackager/CreateAvatarProject.qml @@ -119,7 +119,7 @@ Item { ProjectInputControl { id: textureFolder - label: "Specify Texture Folder <i> - optional</i>" + label: "Specify Texture Folder - <i>Optional</i>" colorScheme: root.colorScheme browseEnabled: true browseFolder: true @@ -128,5 +128,4 @@ Item { onTextChanged: checkErrors() } } - } diff --git a/interface/resources/qml/hifi/avatarPackager/ProjectInputControl.qml b/interface/resources/qml/hifi/avatarPackager/ProjectInputControl.qml index 2ac4a37d02..f0a3aac8a7 100644 --- a/interface/resources/qml/hifi/avatarPackager/ProjectInputControl.qml +++ b/interface/resources/qml/hifi/avatarPackager/ProjectInputControl.qml @@ -57,7 +57,7 @@ Column { colorScheme: root.colorScheme onClicked: { avatarPackager.showModalOverlay = true; - let browser = desktop.fileDialog({ + let browser = avatarPackager.desktopObject.fileDialog({ selectDirectory: browseFolder, dir: browseDir, filter: browseFilter, diff --git a/interface/resources/qml/hifi/tablet/AvatarPackager.qml b/interface/resources/qml/hifi/tablet/AvatarPackager.qml new file mode 100644 index 0000000000..c1c234dd73 --- /dev/null +++ b/interface/resources/qml/hifi/tablet/AvatarPackager.qml @@ -0,0 +1,15 @@ +import QtQuick 2.0 +import "../avatarPackager" 1.0 + +Item { + id: root + width: 480 + height: 706 + + AvatarPackagerApp { + width: parent.width + height: parent.height + + desktopObject: tabletRoot + } +} diff --git a/interface/src/avatar/AvatarPackager.cpp b/interface/src/avatar/AvatarPackager.cpp index d8aadeb4e0..eef574a8b5 100644 --- a/interface/src/avatar/AvatarPackager.cpp +++ b/interface/src/avatar/AvatarPackager.cpp @@ -11,6 +11,8 @@ #include "AvatarPackager.h" +#include "Application.h" + #include <QQmlContext> #include <QQmlEngine> #include <QUrl> @@ -19,8 +21,9 @@ #include "ModelSelector.h" #include <avatar/MarketplaceItemUploader.h> -#include <thread> #include <mutex> +#include "scripting/HMDScriptingInterface.h" +#include "ui/TabletScriptingInterface.h" std::once_flag setupQMLTypesFlag; AvatarPackager::AvatarPackager() { @@ -38,31 +41,32 @@ AvatarPackager::AvatarPackager() { } bool AvatarPackager::open() { - static const QUrl url{ "hifi/AvatarPackager.qml" }; + static const QUrl url{ "hifi/AvatarPackagerWindow.qml" }; const auto packageModelDialogCreated = [=](QQmlContext* context, QObject* newObject) { context->setContextProperty("AvatarPackagerCore", this); }; - DependencyManager::get<OffscreenUi>()->show(url, "AvatarPackager", packageModelDialogCreated); + + static const QString SYSTEM_TABLET = "com.highfidelity.interface.tablet.system"; + auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>(); + auto tablet = dynamic_cast<TabletProxy*>(tabletScriptingInterface->getTablet(SYSTEM_TABLET)); + auto hmd = DependencyManager::get<HMDScriptingInterface>(); + + if (tablet->getToolbarMode()) { + DependencyManager::get<OffscreenUi>()->show(url, "AvatarPackager", packageModelDialogCreated); + } else { + static const QUrl url("hifi/tablet/AvatarPackager.qml"); + if (!tablet->isPathLoaded(url)) { + tablet->getTabletSurface()->getSurfaceContext()->setContextProperty("AvatarPackagerCore", this); + tablet->pushOntoStack(url); + } + } return true; } -AvatarProject* AvatarPackager::openAvatarProject(const QString& avatarProjectFSTPath) { - if (_currentAvatarProject) { - _currentAvatarProject->deleteLater(); - } - _currentAvatarProject = AvatarProject::openAvatarProject(avatarProjectFSTPath); - if (_currentAvatarProject) { - addRecentProject(avatarProjectFSTPath, _currentAvatarProject->getProjectName()); - } - qDebug() << "_currentAvatarProject has" << (QQmlEngine::objectOwnership(_currentAvatarProject) == QQmlEngine::CppOwnership ? "CPP" : "JS") << "OWNERSHIP"; - QQmlEngine::setObjectOwnership(_currentAvatarProject, QQmlEngine::CppOwnership); - emit avatarProjectChanged(); - return _currentAvatarProject; -} - -void AvatarPackager::addRecentProject(QString fstPath, QString projectName) { +void AvatarPackager::addCurrentProjectToRecentProjects() { const int MAX_RECENT_PROJECTS = 5; + const QString& fstPath = _currentAvatarProject->getFSTPath(); auto removeProjects = QVector<RecentAvatarProject>(); for (auto project : _recentProjects) { if (project.getProjectFSTPath() == fstPath) { @@ -73,27 +77,61 @@ void AvatarPackager::addRecentProject(QString fstPath, QString projectName) { _recentProjects.removeOne(removeProject); } - RecentAvatarProject newRecentProject = RecentAvatarProject(projectName, fstPath); + const auto newRecentProject = RecentAvatarProject(_currentAvatarProject->getProjectName(), fstPath); _recentProjects.prepend(newRecentProject); while (_recentProjects.size() > MAX_RECENT_PROJECTS) { _recentProjects.pop_back(); } - _recentProjectsSetting.set(recentProjectsToVariantList()); + _recentProjectsSetting.set(recentProjectsToVariantList(false)); emit recentProjectsChanged(); } +QVariantList AvatarPackager::recentProjectsToVariantList(bool includeProjectPaths) { + QVariantList result; + for (const auto& project : _recentProjects) { + QVariantMap projectVariant; + projectVariant.insert("name", project.getProjectName()); + projectVariant.insert("path", project.getProjectFSTPath()); + if (includeProjectPaths) { + projectVariant.insert("projectPath", project.getProjectPath()); + } + result.append(projectVariant); + } + + return result; +} +void AvatarPackager::recentProjectsFromVariantList(QVariantList projectsVariant) { + _recentProjects.clear(); + for (const auto& projectVariant : projectsVariant) { + auto map = projectVariant.toMap(); + _recentProjects.append(RecentAvatarProject(map.value("name").toString(), map.value("path").toString())); + } +} + +AvatarProject* AvatarPackager::openAvatarProject(const QString& avatarProjectFSTPath) { + setAvatarProject(AvatarProject::openAvatarProject(avatarProjectFSTPath)); + return _currentAvatarProject; +} + AvatarProject* AvatarPackager::createAvatarProject(const QString& projectsFolder, const QString& avatarProjectName, const QString& avatarModelPath, const QString& textureFolder) { + setAvatarProject(AvatarProject::createAvatarProject(projectsFolder, avatarProjectName, avatarModelPath, textureFolder)); + return _currentAvatarProject; +} + +void AvatarPackager::setAvatarProject(AvatarProject* avatarProject) { + if (avatarProject == _currentAvatarProject) { + return; + } if (_currentAvatarProject) { _currentAvatarProject->deleteLater(); } - _currentAvatarProject = AvatarProject::createAvatarProject(projectsFolder, avatarProjectName, avatarModelPath, textureFolder); + _currentAvatarProject = avatarProject; if (_currentAvatarProject) { - addRecentProject(_currentAvatarProject->getFSTPath(), _currentAvatarProject->getProjectName()); + addCurrentProjectToRecentProjects(); + connect(_currentAvatarProject, &AvatarProject::nameChanged, this, &AvatarPackager::addCurrentProjectToRecentProjects); + QQmlEngine::setObjectOwnership(_currentAvatarProject, QQmlEngine::CppOwnership); } - qDebug() << "_currentAvatarProject has" << (QQmlEngine::objectOwnership(_currentAvatarProject) == QQmlEngine::CppOwnership ? "CPP" : "JS") << "OWNERSHIP"; - QQmlEngine::setObjectOwnership(_currentAvatarProject, QQmlEngine::CppOwnership); emit avatarProjectChanged(); - return _currentAvatarProject; } diff --git a/interface/src/avatar/AvatarPackager.h b/interface/src/avatar/AvatarPackager.h index 57cbf046a7..343176497f 100644 --- a/interface/src/avatar/AvatarPackager.h +++ b/interface/src/avatar/AvatarPackager.h @@ -35,12 +35,14 @@ public: _projectFSTPath = other._projectFSTPath; } - ~RecentAvatarProject() = default; - QString getProjectName() const { return _projectName; } QString getProjectFSTPath() const { return _projectFSTPath; } + QString getProjectPath() const { + return QFileInfo(_projectFSTPath).absoluteDir().absolutePath(); + } + bool operator==(const RecentAvatarProject& other) const { return _projectName == other._projectName && _projectFSTPath == other._projectFSTPath; } @@ -72,31 +74,18 @@ signals: private: Q_INVOKABLE AvatarProject* getAvatarProject() const { return _currentAvatarProject; }; Q_INVOKABLE QString getAvatarProjectsPath() const { return AvatarProject::getDefaultProjectsPath(); } - Q_INVOKABLE QVariantList getRecentProjects() { return recentProjectsToVariantList(); } + Q_INVOKABLE QVariantList getRecentProjects() { return recentProjectsToVariantList(true); } - void addRecentProject(QString fstPath, QString projectName); + void setAvatarProject(AvatarProject* avatarProject); + + void addCurrentProjectToRecentProjects(); AvatarProject* _currentAvatarProject{ nullptr }; QVector<RecentAvatarProject> _recentProjects; - QVariantList recentProjectsToVariantList() { - QVariantList result; - for (const auto& project : _recentProjects) { - QVariantMap projectVariant; - projectVariant.insert("name", project.getProjectName()); - projectVariant.insert("path", project.getProjectFSTPath()); - result.append(projectVariant); - } - - return result; - } - void recentProjectsFromVariantList(QVariantList projectsVariant) { - _recentProjects.clear(); - for (const auto& projectVariant : projectsVariant) { - auto map = projectVariant.toMap(); - _recentProjects.append(RecentAvatarProject(map.value("name").toString(), map.value("path").toString())); - } - } + QVariantList recentProjectsToVariantList(bool includeProjectPaths); + + void recentProjectsFromVariantList(QVariantList projectsVariant); Setting::Handle<QVariantList> _recentProjectsSetting{ "io.highfidelity.avatarPackager.recentProjects", QVariantList() }; diff --git a/interface/src/avatar/AvatarProject.cpp b/interface/src/avatar/AvatarProject.cpp index 09d60163b6..0c29bcf906 100644 --- a/interface/src/avatar/AvatarProject.cpp +++ b/interface/src/avatar/AvatarProject.cpp @@ -81,18 +81,18 @@ AvatarProject* AvatarProject::createAvatarProject(const QString& projectsFolder, } }; - foreach(const HFMMaterial mat, hfmModel->materials) { - addTextureToList(mat.normalTexture); - addTextureToList(mat.albedoTexture); - addTextureToList(mat.opacityTexture); - addTextureToList(mat.glossTexture); - addTextureToList(mat.roughnessTexture); - addTextureToList(mat.specularTexture); - addTextureToList(mat.metallicTexture); - addTextureToList(mat.emissiveTexture); - addTextureToList(mat.occlusionTexture); - addTextureToList(mat.scatteringTexture); - addTextureToList(mat.lightmapTexture); + foreach(const HFMMaterial material, hfmModel->materials) { + addTextureToList(material.normalTexture); + addTextureToList(material.albedoTexture); + addTextureToList(material.opacityTexture); + addTextureToList(material.glossTexture); + addTextureToList(material.roughnessTexture); + addTextureToList(material.specularTexture); + addTextureToList(material.metallicTexture); + addTextureToList(material.emissiveTexture); + addTextureToList(material.occlusionTexture); + addTextureToList(material.scatteringTexture); + addTextureToList(material.lightmapTexture); } QDir textureDir(textureFolder.isEmpty() ? fbxInfo.absoluteDir() : textureFolder); @@ -152,7 +152,6 @@ AvatarProject::AvatarProject(FST* fst) { _fst->setScriptPaths(getScriptPaths(QDir(_directory.path() + "/scripts"))); _fst->write(); - //_projectFiles = _directory.entryList(); refreshProjectFiles(); _projectPath = fileInfo.absoluteDir().absolutePath(); diff --git a/interface/src/avatar/AvatarProject.h b/interface/src/avatar/AvatarProject.h index e950fd7379..469324004d 100644 --- a/interface/src/avatar/AvatarProject.h +++ b/interface/src/avatar/AvatarProject.h @@ -28,10 +28,10 @@ class AvatarProject : public QObject { Q_PROPERTY(QStringList projectFiles READ getProjectFiles NOTIFY projectFilesChanged) - Q_PROPERTY(QString projectFolderPath READ getProjectPath) - Q_PROPERTY(QString projectFSTPath READ getFSTPath) - Q_PROPERTY(QString projectFBXPath READ getFBXPath) - Q_PROPERTY(QString name READ getProjectName NOTIFY nameChanged) + Q_PROPERTY(QString projectFolderPath READ getProjectPath CONSTANT) + Q_PROPERTY(QString projectFSTPath READ getFSTPath CONSTANT) + Q_PROPERTY(QString projectFBXPath READ getFBXPath CONSTANT) + Q_PROPERTY(QString name READ getProjectName WRITE setProjectName NOTIFY nameChanged) public: Q_INVOKABLE MarketplaceItemUploader* upload(bool updateExisting); @@ -39,6 +39,13 @@ public: Q_INVOKABLE QStringList getProjectFiles() const; Q_INVOKABLE QString getProjectName() const { return _fst->getName(); } + Q_INVOKABLE void setProjectName(const QString& newProjectName) { + if (newProjectName.trimmed().length() > 0) { + _fst->setName(newProjectName); + _fst->write(); + emit nameChanged(); + } + } Q_INVOKABLE QString getProjectPath() const { return _projectPath; } Q_INVOKABLE QString getFSTPath() const { return _fst->getPath(); } Q_INVOKABLE QString getFBXPath() const { return _fst->getModelPath(); } diff --git a/libraries/fbx/src/FST.cpp b/libraries/fbx/src/FST.cpp index 5d3737319f..9510ca5962 100644 --- a/libraries/fbx/src/FST.cpp +++ b/libraries/fbx/src/FST.cpp @@ -15,7 +15,7 @@ #include <QFileInfo> #include <hfm/HFM.h> -FST::FST(const QString& fstPath, QVariantHash data) : _fstPath(fstPath) { +FST::FST(QString fstPath, QVariantHash data) : _fstPath(std::move(fstPath)) { auto setValueFromFSTData = [&data] (const QString& propertyID, auto &targetProperty) mutable { if (data.contains(propertyID)) { @@ -38,7 +38,7 @@ FST::FST(const QString& fstPath, QVariantHash data) : _fstPath(fstPath) { _other = data; } -FST* FST::createFSTFromModel(QString fstPath, QString modelFilePath, const hfm::Model& hfmModel) { +FST* FST::createFSTFromModel(const QString& fstPath, const QString& modelFilePath, const hfm::Model& hfmModel) { QVariantHash mapping; // mixamo files - in the event that a mixamo file was edited by some other tool, it's likely the applicationName will diff --git a/libraries/fbx/src/FST.h b/libraries/fbx/src/FST.h index 6fd654987e..6104130512 100644 --- a/libraries/fbx/src/FST.h +++ b/libraries/fbx/src/FST.h @@ -26,9 +26,9 @@ class FST : public QObject { Q_PROPERTY(QUuid marketplaceID READ getMarketplaceID) Q_PROPERTY(bool hasMarketplaceID READ getHasMarketplaceID NOTIFY marketplaceIDChanged) public: - FST(const QString& fstPath, QVariantHash data); + FST(QString fstPath, QVariantHash data); - static FST* createFSTFromModel(QString fstPath, QString modelFilePath, const hfm::Model& hfmModel); + static FST* createFSTFromModel(const QString& fstPath, const QString& modelFilePath, const hfm::Model& hfmModel); QString absoluteModelPath() const;