mirror of
https://github.com/overte-org/overte.git
synced 2025-04-22 01:44:07 +02:00
recent projects
This commit is contained in:
parent
b6359a6cb4
commit
2f32458f72
11 changed files with 259 additions and 38 deletions
|
@ -33,7 +33,7 @@ Windows.ScrollingWindow {
|
|||
id: modalOverlay
|
||||
anchors.fill: parent
|
||||
z: 20
|
||||
color: "#aa031b33"
|
||||
color: "#a15d5d5d"
|
||||
visible: false
|
||||
|
||||
// This mouse area captures the cursor events while the modalOverlay is active
|
||||
|
@ -70,7 +70,7 @@ Windows.ScrollingWindow {
|
|||
},
|
||||
State {
|
||||
name: AvatarPackagerState.project
|
||||
PropertyChanges { target: avatarPackagerHeader; title: AvatarPackagerCore.currentAvatarProject.name }
|
||||
PropertyChanges { target: avatarPackagerHeader; title: AvatarPackagerCore.currentAvatarProject.name; canRename: true }
|
||||
PropertyChanges { target: avatarProject; visible: true }
|
||||
PropertyChanges { target: avatarPackagerFooter; content: avatarProject.footer }
|
||||
},
|
||||
|
@ -136,6 +136,7 @@ Windows.ScrollingWindow {
|
|||
text: qsTr("New Project")
|
||||
colorScheme: root.colorScheme
|
||||
onClicked: {
|
||||
createAvatarProject.clearInputs();
|
||||
avatarPackager.state = AvatarPackagerState.createProject;
|
||||
}
|
||||
}
|
||||
|
@ -173,7 +174,10 @@ Windows.ScrollingWindow {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Flow {
|
||||
visible: AvatarPackagerCore.recentProjects.length === 0
|
||||
anchors {
|
||||
fill: parent
|
||||
topMargin: 18
|
||||
|
@ -190,6 +194,27 @@ Windows.ScrollingWindow {
|
|||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ Rectangle {
|
|||
property alias title: title.text
|
||||
property alias faqEnabled: faq.visible
|
||||
property alias backButtonEnabled: back.visible
|
||||
property bool canRename: false;
|
||||
signal backButtonClicked
|
||||
|
||||
RalewaySemiBold {
|
||||
|
|
|
@ -0,0 +1,85 @@
|
|||
import QtQuick 2.0
|
||||
|
||||
import "../../controlsUit" 1.0 as HifiControls
|
||||
import "../../stylesUit" 1.0
|
||||
|
||||
|
||||
Item {
|
||||
id: projectCard
|
||||
height: 80
|
||||
width: parent.width
|
||||
|
||||
property alias title: title.text
|
||||
property alias path: path.text
|
||||
|
||||
property color textColor: "#E3E3E3"
|
||||
property color hoverTextColor: "#121212"
|
||||
property color pressedTextColor: "#121212"
|
||||
|
||||
property color backgroundColor: "#121212"
|
||||
property color hoverBackgroundColor: "#E3E3E3"
|
||||
property color pressedBackgroundColor: "#6A6A6A"
|
||||
|
||||
state: mouseArea.pressed ? "pressed" : (mouseArea.containsMouse ? "hover" : "normal")
|
||||
states: [
|
||||
State {
|
||||
name: "normal"
|
||||
PropertyChanges { target: background; color: backgroundColor }
|
||||
PropertyChanges { target: title; color: textColor }
|
||||
PropertyChanges { target: path; color: textColor }
|
||||
},
|
||||
State {
|
||||
name: "hover"
|
||||
PropertyChanges { target: background; color: hoverBackgroundColor }
|
||||
PropertyChanges { target: title; color: hoverTextColor }
|
||||
PropertyChanges { target: path; color: hoverTextColor }
|
||||
},
|
||||
State {
|
||||
name: "pressed"
|
||||
PropertyChanges { target: background; color: pressedBackgroundColor }
|
||||
PropertyChanges { target: title; color: pressedTextColor }
|
||||
PropertyChanges { target: path; color: pressedTextColor }
|
||||
}
|
||||
]
|
||||
|
||||
Rectangle {
|
||||
id: background
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
color: "#121212"
|
||||
radius: 4
|
||||
|
||||
RalewayBold {
|
||||
id: title
|
||||
anchors {
|
||||
top: parent.top
|
||||
topMargin: 13
|
||||
left: parent.left
|
||||
leftMargin: 16
|
||||
}
|
||||
text: "<title missing>"
|
||||
size: 16
|
||||
}
|
||||
|
||||
RalewayRegular {
|
||||
id: path
|
||||
anchors {
|
||||
top: title.bottom
|
||||
left: parent.left
|
||||
leftMargin: 32
|
||||
}
|
||||
text: "<path missing>"
|
||||
size: 20
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
onClicked: {
|
||||
AvatarPackagerCore.openAvatarProject(path.text);
|
||||
avatarPackager.state = "project";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -178,4 +178,4 @@ Item {
|
|||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,9 +22,10 @@ Item {
|
|||
height: 30
|
||||
width: 133
|
||||
text: qsTr("Create")
|
||||
enabled: false
|
||||
onClicked: {
|
||||
if (!AvatarPackagerCore.createAvatarProject(projectLocation.text, name.text, avatarModel.text, textureFolder.text)) {
|
||||
Window.alert('Failed to create project')
|
||||
Window.alert('Failed to create project');
|
||||
return;
|
||||
}
|
||||
avatarPackager.state = AvatarPackagerState.project;
|
||||
|
@ -37,21 +38,39 @@ Item {
|
|||
height: parent.height
|
||||
width: parent.width
|
||||
|
||||
function clearInputs() {
|
||||
name.text = projectLocation.text = avatarModel.text = textureFolder.text = "";
|
||||
}
|
||||
|
||||
property var errorMessages: QtObject {
|
||||
readonly property string fileExists: "A folder with that name already exists at that location. Please choose a different project name or location."
|
||||
function checkErrors() {
|
||||
let newErrorMessageText = "";
|
||||
|
||||
let projectName = name.text;
|
||||
let projectFolder = projectLocation.text;
|
||||
|
||||
let hasProjectNameError = projectName !== "" && projectFolder !== "" && !AvatarPackagerCore.isValidNewProjectName(projectFolder, projectName);
|
||||
|
||||
if (hasProjectNameError) {
|
||||
newErrorMessageText = "A folder with that name already exists at that location. Please choose a different project name or location.";
|
||||
}
|
||||
|
||||
name.error = projectLocation.error = hasProjectNameError;
|
||||
errorMessage.text = newErrorMessageText;
|
||||
createButton.enabled = newErrorMessageText === "" && requiredFieldsFilledIn();
|
||||
}
|
||||
|
||||
function requiredFieldsFilledIn() {
|
||||
return name.text !== "" && projectLocation.text !== "" && avatarModel.text !== "";
|
||||
}
|
||||
|
||||
RalewayRegular {
|
||||
id: errorMessage
|
||||
visible: text !== ""
|
||||
text: ""
|
||||
color: "#EA4C5F";
|
||||
color: "#EA4C5F"
|
||||
wrapMode: Text.WordWrap
|
||||
size: 20
|
||||
anchors {
|
||||
top: createAvatarColumns.bottom
|
||||
bottom: parent.bottom
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
}
|
||||
|
@ -59,6 +78,7 @@ Item {
|
|||
|
||||
Column {
|
||||
id: createAvatarColumns
|
||||
anchors.top: errorMessage.visible ? errorMessage.bottom : parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.margins: 10
|
||||
|
@ -71,6 +91,7 @@ Item {
|
|||
id: name
|
||||
label: "Name"
|
||||
colorScheme: root.colorScheme
|
||||
onTextChanged: checkErrors()
|
||||
}
|
||||
|
||||
ProjectInputControl {
|
||||
|
@ -81,9 +102,7 @@ Item {
|
|||
browseFolder: true
|
||||
browseDir: text !== "" ? fileDialogHelper.pathToUrl(text) : createAvatarColumns.defaultFileBrowserPath
|
||||
browseTitle: "Project Location"
|
||||
onTextChanged: {
|
||||
//TODO: valid folder? Does project with name exist here already?
|
||||
}
|
||||
onTextChanged: checkErrors()
|
||||
}
|
||||
|
||||
ProjectInputControl {
|
||||
|
@ -95,25 +114,18 @@ Item {
|
|||
browseDir: text !== "" ? fileDialogHelper.pathToUrl(text) : createAvatarColumns.defaultFileBrowserPath
|
||||
browseFilter: "Avatar Model File (*.fbx)"
|
||||
browseTitle: "Open Avatar Model (.fbx)"
|
||||
onTextChanged: {
|
||||
if (avatarModel.text !== "") {
|
||||
textureFolder.browseDir = fileDialogHelper.pathToUrl(avatarModel.text.split('/')[0]);
|
||||
}
|
||||
}
|
||||
onTextChanged: checkErrors()
|
||||
}
|
||||
|
||||
ProjectInputControl {
|
||||
id: textureFolder
|
||||
label: "Specify Texture Folder"
|
||||
label: "Specify Texture Folder <i> - optional</i>"
|
||||
colorScheme: root.colorScheme
|
||||
browseEnabled: true
|
||||
browseFolder: true
|
||||
browseDir: text !== "" ? fileDialogHelper.pathToUrl(text) : createAvatarColumns.defaultFileBrowserPath
|
||||
browseTitle: "Texture Folder"
|
||||
onTextChanged: {
|
||||
//TODO: valid folder?
|
||||
|
||||
}
|
||||
onTextChanged: checkErrors()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -22,6 +22,7 @@ Column {
|
|||
property string browseTitle: "Open file"
|
||||
property string browseDir: ""
|
||||
property alias text: input.text
|
||||
property alias error: input.error
|
||||
|
||||
property int colorScheme
|
||||
|
||||
|
|
|
@ -31,6 +31,8 @@ AvatarPackager::AvatarPackager() {
|
|||
qRegisterMetaType<AvatarProject*>();
|
||||
});
|
||||
|
||||
recentProjectsFromVariantList(_recentProjectsSetting.get());
|
||||
|
||||
QDir defaultProjectsDir(AvatarProject::getDefaultProjectsPath());
|
||||
defaultProjectsDir.mkpath(".");
|
||||
}
|
||||
|
@ -50,17 +52,46 @@ AvatarProject* AvatarPackager::openAvatarProject(const QString& avatarProjectFST
|
|||
_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) {
|
||||
const int MAX_RECENT_PROJECTS = 5;
|
||||
auto removeProjects = QVector<RecentAvatarProject>();
|
||||
for (auto project : _recentProjects) {
|
||||
if (project.getProjectFSTPath() == fstPath) {
|
||||
removeProjects.append(project);
|
||||
}
|
||||
}
|
||||
for (const auto removeProject : removeProjects) {
|
||||
_recentProjects.removeOne(removeProject);
|
||||
}
|
||||
|
||||
RecentAvatarProject newRecentProject = RecentAvatarProject(projectName, fstPath);
|
||||
_recentProjects.prepend(newRecentProject);
|
||||
|
||||
while (_recentProjects.size() > MAX_RECENT_PROJECTS) {
|
||||
_recentProjects.pop_back();
|
||||
}
|
||||
|
||||
_recentProjectsSetting.set(recentProjectsToVariantList());
|
||||
emit recentProjectsChanged();
|
||||
}
|
||||
|
||||
AvatarProject* AvatarPackager::createAvatarProject(const QString& projectsFolder, const QString& avatarProjectName, const QString& avatarModelPath, const QString& textureFolder) {
|
||||
if (_currentAvatarProject) {
|
||||
_currentAvatarProject->deleteLater();
|
||||
}
|
||||
_currentAvatarProject = AvatarProject::createAvatarProject(projectsFolder, avatarProjectName, avatarModelPath, textureFolder);
|
||||
if (_currentAvatarProject) {
|
||||
addRecentProject(_currentAvatarProject->getFSTPath(), _currentAvatarProject->getProjectName());
|
||||
}
|
||||
qDebug() << "_currentAvatarProject has" << (QQmlEngine::objectOwnership(_currentAvatarProject) == QQmlEngine::CppOwnership ? "CPP" : "JS") << "OWNERSHIP";
|
||||
QQmlEngine::setObjectOwnership(_currentAvatarProject, QQmlEngine::CppOwnership);
|
||||
emit avatarProjectChanged();
|
||||
|
|
|
@ -19,27 +19,95 @@
|
|||
#include "FileDialogHelper.h"
|
||||
|
||||
#include "avatar/AvatarProject.h"
|
||||
#include "SettingHandle.h"
|
||||
|
||||
class RecentAvatarProject {
|
||||
public:
|
||||
RecentAvatarProject() {
|
||||
|
||||
}
|
||||
|
||||
RecentAvatarProject(QString projectName, QString projectFSTPath) {
|
||||
_projectName = projectName;
|
||||
_projectFSTPath = projectFSTPath;
|
||||
}
|
||||
RecentAvatarProject(const RecentAvatarProject& other) {
|
||||
_projectName = other._projectName;
|
||||
_projectFSTPath = other._projectFSTPath;
|
||||
}
|
||||
|
||||
QString getProjectName() const { return _projectName; }
|
||||
|
||||
QString getProjectFSTPath() const { return _projectFSTPath; }
|
||||
|
||||
bool operator==(const RecentAvatarProject& other) const {
|
||||
return _projectName == other._projectName && _projectFSTPath == other._projectFSTPath;
|
||||
}
|
||||
|
||||
private:
|
||||
QString _projectName;
|
||||
QString _projectFSTPath;
|
||||
|
||||
};
|
||||
|
||||
inline QDebug operator<<(QDebug debug, const RecentAvatarProject& recentAvatarProject) {
|
||||
debug << "[recentAvatarProject:" << recentAvatarProject.getProjectFSTPath() << "]";
|
||||
return debug;
|
||||
}
|
||||
|
||||
Q_DECLARE_METATYPE(RecentAvatarProject);
|
||||
|
||||
Q_DECLARE_METATYPE(QVector<RecentAvatarProject>);
|
||||
|
||||
class AvatarPackager : public QObject, public Dependency {
|
||||
Q_OBJECT
|
||||
SINGLETON_DEPENDENCY
|
||||
Q_PROPERTY(AvatarProject* currentAvatarProject READ getAvatarProject NOTIFY avatarProjectChanged)
|
||||
Q_PROPERTY(QString AVATAR_PROJECTS_PATH READ getAvatarProjectsPath CONSTANT)
|
||||
Q_PROPERTY(QVariantList recentProjects READ getRecentProjects NOTIFY recentProjectsChanged)
|
||||
public:
|
||||
AvatarPackager();
|
||||
bool open();
|
||||
|
||||
Q_INVOKABLE AvatarProject* createAvatarProject(const QString& projectsFolder, const QString& avatarProjectName, const QString& avatarModelPath, const QString& textureFolder);
|
||||
Q_INVOKABLE AvatarProject* openAvatarProject(const QString& avatarProjectFSTPath);
|
||||
Q_INVOKABLE bool isValidNewProjectName(const QString& projectPath, const QString& projectName) { return AvatarProject::isValidNewProjectName(projectPath, projectName); }
|
||||
|
||||
signals:
|
||||
void avatarProjectChanged();
|
||||
void recentProjectsChanged();
|
||||
|
||||
private:
|
||||
Q_INVOKABLE AvatarProject* getAvatarProject() const { return _currentAvatarProject; };
|
||||
Q_INVOKABLE QString getAvatarProjectsPath() const { return AvatarProject::getDefaultProjectsPath(); }
|
||||
Q_INVOKABLE QVariantList getRecentProjects() { return recentProjectsToVariantList(); }
|
||||
|
||||
void addRecentProject(QString fstPath, QString projectName);
|
||||
|
||||
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()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Setting::Handle<QVariantList> _recentProjectsSetting{ "io.highfidelity.avatarPackager.recentProjects", QVariantList() };
|
||||
};
|
||||
|
||||
#endif // hifi_AvatarPackager_h
|
||||
|
|
|
@ -15,13 +15,11 @@
|
|||
|
||||
#include <QFile>
|
||||
#include <QFileInfo>
|
||||
#include <QDebug>
|
||||
#include <QQmlEngine>
|
||||
#include <QTimer>
|
||||
|
||||
#include "FBXSerializer.h"
|
||||
#include <ui/TabletScriptingInterface.h>
|
||||
#include <graphics/TextureMap.h>
|
||||
#include "scripting/HMDScriptingInterface.h"
|
||||
|
||||
AvatarProject* AvatarProject::openAvatarProject(const QString& path) {
|
||||
|
@ -38,7 +36,7 @@ AvatarProject* AvatarProject::openAvatarProject(const QString& path) {
|
|||
}
|
||||
|
||||
AvatarProject* AvatarProject::createAvatarProject(const QString& projectsFolder, const QString& avatarProjectName, const QString& avatarModelPath, const QString& textureFolder) {
|
||||
if (!isValidNewProjectName(avatarProjectName)) {
|
||||
if (!isValidNewProjectName(projectsFolder, avatarProjectName)) {
|
||||
return nullptr;
|
||||
}
|
||||
QDir projectDir(projectsFolder + "/" + avatarProjectName);
|
||||
|
@ -135,8 +133,11 @@ QStringList AvatarProject::getScriptPaths(const QDir& scriptsDir) {
|
|||
return result;
|
||||
}
|
||||
|
||||
bool AvatarProject::isValidNewProjectName(const QString& projectName) {
|
||||
QDir dir(getDefaultProjectsPath() + "/" + projectName);
|
||||
bool AvatarProject::isValidNewProjectName(const QString& projectPath, const QString& projectName) {
|
||||
if (projectPath.trimmed().isEmpty() || projectName.trimmed().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
QDir dir(projectPath + "/" + projectName);
|
||||
return !dir.exists();
|
||||
}
|
||||
|
||||
|
|
|
@ -17,12 +17,9 @@
|
|||
#include "ProjectFile.h"
|
||||
#include "FST.h"
|
||||
|
||||
#include <QDir>
|
||||
#include <QObject>
|
||||
#include <QDir>
|
||||
#include <QFileInfo>
|
||||
#include <QVariantHash>
|
||||
#include <QUuid>
|
||||
#include <QStandardPaths>
|
||||
|
||||
class AvatarProject : public QObject {
|
||||
|
@ -41,6 +38,11 @@ public:
|
|||
Q_INVOKABLE void openInInventory();
|
||||
Q_INVOKABLE QStringList getProjectFiles() const;
|
||||
|
||||
Q_INVOKABLE QString getProjectName() const { return _fst->getName(); }
|
||||
Q_INVOKABLE QString getProjectPath() const { return _projectPath; }
|
||||
Q_INVOKABLE QString getFSTPath() const { return _fst->getPath(); }
|
||||
Q_INVOKABLE QString getFBXPath() const { return _fst->getModelPath(); }
|
||||
|
||||
/**
|
||||
* returns the AvatarProject or a nullptr on failure.
|
||||
*/
|
||||
|
@ -50,7 +52,7 @@ public:
|
|||
const QString& avatarModelPath,
|
||||
const QString& textureFolder);
|
||||
|
||||
static bool isValidNewProjectName(const QString& projectName);
|
||||
static bool isValidNewProjectName(const QString& projectPath, const QString& projectName);
|
||||
|
||||
static QString getDefaultProjectsPath() {
|
||||
return QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation) + "/High Fidelity Projects";
|
||||
|
@ -66,11 +68,6 @@ private:
|
|||
|
||||
~AvatarProject() { _fst->deleteLater(); }
|
||||
|
||||
Q_INVOKABLE QString getProjectName() const { return _fst->getName(); }
|
||||
Q_INVOKABLE QString getProjectPath() const { return _projectPath; }
|
||||
Q_INVOKABLE QString getFSTPath() const { return _fst->getPath(); }
|
||||
Q_INVOKABLE QString getFBXPath() const { return _fst->getModelPath(); }
|
||||
|
||||
FST* getFST() { return _fst; }
|
||||
|
||||
void refreshProjectFiles();
|
||||
|
|
|
@ -15,8 +15,8 @@
|
|||
#include <DependencyManager.h>
|
||||
|
||||
#include <QBuffer>
|
||||
#include <quazip5\quazip.h>
|
||||
#include <quazip5\quazipfile.h>
|
||||
#include <quazip5/quazip.h>
|
||||
#include <quazip5/quazipfile.h>
|
||||
|
||||
#include <qtimer.h>
|
||||
#include <QFile>
|
||||
|
|
Loading…
Reference in a new issue