recent projects

This commit is contained in:
Thijs Wenker 2018-12-27 19:52:56 +01:00
parent b6359a6cb4
commit 2f32458f72
11 changed files with 259 additions and 38 deletions

View file

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

View file

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

View file

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

View file

@ -178,4 +178,4 @@ Item {
}
}
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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