diff --git a/Code/Tools/ProjectManager/Resources/Add.svg b/Code/Tools/ProjectManager/Resources/Add.svg index d2b9b2e0a6..4fa30932fb 100644 --- a/Code/Tools/ProjectManager/Resources/Add.svg +++ b/Code/Tools/ProjectManager/Resources/Add.svg @@ -1,4 +1,3 @@ - diff --git a/Code/Tools/ProjectManager/Resources/ProjectManager.qrc b/Code/Tools/ProjectManager/Resources/ProjectManager.qrc new file mode 100644 index 0000000000..1ffd7cf3e7 --- /dev/null +++ b/Code/Tools/ProjectManager/Resources/ProjectManager.qrc @@ -0,0 +1,16 @@ + + + ProjectManager.qss + + + Add.svg + Select_Folder.svg + o3de_editor.ico + Windows.svg + Android.svg + iOS.svg + Linux.svg + macOS.svg + Backgrounds/FirstTimeBackgroundImage.jpg + + diff --git a/Code/Tools/ProjectManager/Resources/ProjectManager.qss b/Code/Tools/ProjectManager/Resources/ProjectManager.qss index e69de29bb2..16ef48ee7c 100644 --- a/Code/Tools/ProjectManager/Resources/ProjectManager.qss +++ b/Code/Tools/ProjectManager/Resources/ProjectManager.qss @@ -0,0 +1,73 @@ +/************** General (MainWindow) **************/ +QMainWindow { + background-color: #333333; +} + + +QPushButton:focus { + outline: none; + border:1px solid #1e70eb; +} + +/************** General (Forms) **************/ + +#formLineEditWidget, +#formBrowseEditWidget { + max-width: 780px; +} + +#formFrame { + max-width: 720px; + background-color: #444444; + border:1px solid #dddddd; + border-radius: 4px; + padding: 0px 10px 2px 6px; + margin-top:10px; + margin-left:30px; +} + +#formFrame[Focus="true"] { + border:1px solid #1e70eb; +} + +#formFrame[Valid="false"] { + border:1px solid red; +} + +#formFrame QLabel { + font-size: 13px; + color: #cccccc; +} + +#formFrame QPushButton { + background-color: transparent; + background:transparent url(:/Select_Folder.svg) no-repeat center; + qproperty-flat: true; +} + +#formFrame QPushButton:focus { + border:none; +} + +#formFrame QLineEdit { + background-color: rgba(0,0,0,0); + font-size: 18px; + color: #ffffff; + border:0; + line-height: 30px; + height: 1em; + padding-top: -4px; +} + +#formErrorLabel { + color: #ec3030; + font-size: 14px; + margin-left: 40px; +} + +#formTitleLabel { + font-size:21px; + color:#ffffff; + margin: 10px 0 10px 30px; +} + diff --git a/Code/Tools/ProjectManager/Resources/Select_Folder.svg b/Code/Tools/ProjectManager/Resources/Select_Folder.svg index 72dcd3385e..df20a06e76 100644 --- a/Code/Tools/ProjectManager/Resources/Select_Folder.svg +++ b/Code/Tools/ProjectManager/Resources/Select_Folder.svg @@ -1,4 +1,3 @@ - diff --git a/Code/Tools/ProjectManager/Source/EngineInfo.cpp b/Code/Tools/ProjectManager/Source/EngineInfo.cpp index 8043a498ff..934d3af9d8 100644 --- a/Code/Tools/ProjectManager/Source/EngineInfo.cpp +++ b/Code/Tools/ProjectManager/Source/EngineInfo.cpp @@ -14,8 +14,16 @@ namespace O3DE::ProjectManager { - EngineInfo::EngineInfo(const QString& path) + EngineInfo::EngineInfo(const QString& path, const QString& name, const QString& version, const QString& thirdPartyPath) : m_path(path) + , m_name(name) + , m_version(version) + , m_thirdPartyPath(thirdPartyPath) { } + + bool EngineInfo::IsValid() const + { + return !m_path.isEmpty(); + } } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/EngineInfo.h b/Code/Tools/ProjectManager/Source/EngineInfo.h index ada6e73a15..262c42e56b 100644 --- a/Code/Tools/ProjectManager/Source/EngineInfo.h +++ b/Code/Tools/ProjectManager/Source/EngineInfo.h @@ -22,8 +22,20 @@ namespace O3DE::ProjectManager { public: EngineInfo() = default; - EngineInfo(const QString& path); + EngineInfo(const QString& path, const QString& name, const QString& version, const QString& thirdPartyPath); + // from engine.json + QString m_version; + QString m_name; + QString m_thirdPartyPath; + + // from o3de_manifest.json QString m_path; + QString m_defaultProjectsFolder; + QString m_defaultGemsFolder; + QString m_defaultTemplatesFolder; + QString m_defaultRestrictedFolder; + + bool IsValid() const; }; } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/EngineSettingsScreen.cpp b/Code/Tools/ProjectManager/Source/EngineSettingsScreen.cpp index 1adab41c0e..f51996bd65 100644 --- a/Code/Tools/ProjectManager/Source/EngineSettingsScreen.cpp +++ b/Code/Tools/ProjectManager/Source/EngineSettingsScreen.cpp @@ -11,20 +11,99 @@ */ #include - -#include +#include +#include +#include +#include +#include +#include +#include +#include namespace O3DE::ProjectManager { EngineSettingsScreen::EngineSettingsScreen(QWidget* parent) : ScreenWidget(parent) - , m_ui(new Ui::EngineSettingsClass()) { - m_ui->setupUi(this); + auto* layout = new QVBoxLayout(this); + layout->setAlignment(Qt::AlignTop); + + setObjectName("engineSettingsScreen"); + + EngineInfo engineInfo; + + AZ::Outcome engineInfoResult = PythonBindingsInterface::Get()->GetEngineInfo(); + if (engineInfoResult.IsSuccess()) + { + engineInfo = engineInfoResult.GetValue(); + } + + QLabel* formTitleLabel = new QLabel(tr("O3DE Settings"), this); + formTitleLabel->setObjectName("formTitleLabel"); + layout->addWidget(formTitleLabel); + + m_engineVersion = new FormLineEditWidget(tr("Engine Version"), engineInfo.m_version, this); + m_engineVersion->lineEdit()->setReadOnly(true); + layout->addWidget(m_engineVersion); + + m_thirdParty = new FormBrowseEditWidget(tr("3rd Party Software Folder"), engineInfo.m_thirdPartyPath, this); + m_thirdParty->lineEdit()->setValidator(new PathValidator(PathValidator::PathMode::ExistingFolder, this)); + m_thirdParty->lineEdit()->setReadOnly(true); + m_thirdParty->setErrorLabelText(tr("Please provide a valid path to a folder that exists")); + connect(m_thirdParty->lineEdit(), &QLineEdit::textChanged, this, &EngineSettingsScreen::OnTextChanged); + layout->addWidget(m_thirdParty); + + m_defaultProjects = new FormBrowseEditWidget(tr("Default Projects Folder"), engineInfo.m_defaultProjectsFolder, this); + m_defaultProjects->lineEdit()->setValidator(new PathValidator(PathValidator::PathMode::ExistingFolder, this)); + m_defaultProjects->lineEdit()->setReadOnly(true); + m_defaultProjects->setErrorLabelText(tr("Please provide a valid path to a folder that exists")); + connect(m_defaultProjects->lineEdit(), &QLineEdit::textChanged, this, &EngineSettingsScreen::OnTextChanged); + layout->addWidget(m_defaultProjects); + + m_defaultGems = new FormBrowseEditWidget(tr("Default Gems Folder"), engineInfo.m_defaultGemsFolder, this); + m_defaultGems->lineEdit()->setValidator(new PathValidator(PathValidator::PathMode::ExistingFolder, this)); + m_defaultGems->lineEdit()->setReadOnly(true); + m_defaultGems->setErrorLabelText(tr("Please provide a valid path to a folder that exists")); + connect(m_defaultGems->lineEdit(), &QLineEdit::textChanged, this, &EngineSettingsScreen::OnTextChanged); + layout->addWidget(m_defaultGems); + + m_defaultProjectTemplates = new FormBrowseEditWidget(tr("Default Project Templates Folder"), engineInfo.m_defaultTemplatesFolder, this); + m_defaultProjectTemplates->lineEdit()->setValidator(new PathValidator(PathValidator::PathMode::ExistingFolder, this)); + m_defaultProjectTemplates->lineEdit()->setReadOnly(true); + m_defaultProjectTemplates->setErrorLabelText(tr("Please provide a valid path to a folder that exists")); + connect(m_defaultProjectTemplates->lineEdit(), &QLineEdit::textChanged, this, &EngineSettingsScreen::OnTextChanged); + layout->addWidget(m_defaultProjectTemplates); + + setLayout(layout); } ProjectManagerScreen EngineSettingsScreen::GetScreenEnum() { return ProjectManagerScreen::EngineSettings; } + + void EngineSettingsScreen::OnTextChanged() + { + // save engine settings + auto engineInfoResult = PythonBindingsInterface::Get()->GetEngineInfo(); + if (engineInfoResult.IsSuccess()) + { + EngineInfo engineInfo; + engineInfo = engineInfoResult.GetValue(); + engineInfo.m_thirdPartyPath = m_thirdParty->lineEdit()->text(); + engineInfo.m_defaultProjectsFolder = m_defaultProjects->lineEdit()->text(); + engineInfo.m_defaultGemsFolder = m_defaultGems->lineEdit()->text(); + engineInfo.m_defaultTemplatesFolder = m_defaultProjectTemplates->lineEdit()->text(); + + bool result = PythonBindingsInterface::Get()->SetEngineInfo(engineInfo); + if (!result) + { + QMessageBox::critical(this, tr("Engine Settings"), tr("Failed to save engine settings.")); + } + } + else + { + QMessageBox::critical(this, tr("Engine Settings"), tr("Failed to get engine settings.")); + } + } } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/EngineSettingsScreen.h b/Code/Tools/ProjectManager/Source/EngineSettingsScreen.h index 4baa3fb28c..0e91ec2d3b 100644 --- a/Code/Tools/ProjectManager/Source/EngineSettingsScreen.h +++ b/Code/Tools/ProjectManager/Source/EngineSettingsScreen.h @@ -15,13 +15,11 @@ #include #endif -namespace Ui -{ - class EngineSettingsClass; -} - namespace O3DE::ProjectManager { + QT_FORWARD_DECLARE_CLASS(FormLineEditWidget) + QT_FORWARD_DECLARE_CLASS(FormBrowseEditWidget) + class EngineSettingsScreen : public ScreenWidget { @@ -30,8 +28,15 @@ namespace O3DE::ProjectManager ~EngineSettingsScreen() = default; ProjectManagerScreen GetScreenEnum() override; + protected slots: + void OnTextChanged(); + private: - QScopedPointer m_ui; + FormLineEditWidget* m_engineVersion; + FormBrowseEditWidget* m_thirdParty; + FormBrowseEditWidget* m_defaultProjects; + FormBrowseEditWidget* m_defaultGems; + FormBrowseEditWidget* m_defaultProjectTemplates; }; } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/EngineSettingsScreen.ui b/Code/Tools/ProjectManager/Source/EngineSettingsScreen.ui deleted file mode 100644 index c8fda8bfd7..0000000000 --- a/Code/Tools/ProjectManager/Source/EngineSettingsScreen.ui +++ /dev/null @@ -1,82 +0,0 @@ - - - EngineSettingsClass - - - - 0 - 0 - 839 - 597 - - - - Form - - - - - - O3DE Settings - - - - - - - Engine Version - - - - - - - v1.01 - - - - - - - 3rd Party Software Folder - - - - - - - - - - Restricted Folder - - - - - - - - - - Default Gems Folder - - - - - - - - - - Default Project Templates Folder - - - - - - - - - - - diff --git a/Code/Tools/ProjectManager/Source/FirstTimeUseScreen.cpp b/Code/Tools/ProjectManager/Source/FirstTimeUseScreen.cpp index 2c96078d43..a1be7e8ac9 100644 --- a/Code/Tools/ProjectManager/Source/FirstTimeUseScreen.cpp +++ b/Code/Tools/ProjectManager/Source/FirstTimeUseScreen.cpp @@ -49,11 +49,11 @@ namespace O3DE::ProjectManager QHBoxLayout* buttonLayout = new QHBoxLayout(); buttonLayout->setSpacing(s_buttonSpacing); - m_createProjectButton = CreateLargeBoxButton(QIcon(":/Resources/Add.svg"), tr("Create Project"), this); + m_createProjectButton = CreateLargeBoxButton(QIcon(":/Add.svg"), tr("Create Project"), this); m_createProjectButton->setIconSize(QSize(s_iconSize, s_iconSize)); buttonLayout->addWidget(m_createProjectButton); - m_addProjectButton = CreateLargeBoxButton(QIcon(":/Resources/Select_Folder.svg"), tr("Add a Project"), this); + m_addProjectButton = CreateLargeBoxButton(QIcon(":/Select_Folder.svg"), tr("Add a Project"), this); m_addProjectButton->setIconSize(QSize(s_iconSize, s_iconSize)); buttonLayout->addWidget(m_addProjectButton); @@ -66,7 +66,7 @@ namespace O3DE::ProjectManager vLayout->addItem(verticalSpacer); // Using border-image allows for scaling options background-image does not support - setStyleSheet("O3DE--ProjectManager--ScreenWidget { border-image: url(:/Resources/Backgrounds/FirstTimeBackgroundImage.jpg) repeat repeat; }"); + setStyleSheet("O3DE--ProjectManager--ScreenWidget { border-image: url(:/Backgrounds/FirstTimeBackgroundImage.jpg) repeat repeat; }"); connect(m_createProjectButton, &QPushButton::pressed, this, &FirstTimeUseScreen::HandleNewProjectButton); connect(m_addProjectButton, &QPushButton::pressed, this, &FirstTimeUseScreen::HandleAddProjectButton); diff --git a/Code/Tools/ProjectManager/Source/FormBrowseEditWidget.cpp b/Code/Tools/ProjectManager/Source/FormBrowseEditWidget.cpp new file mode 100644 index 0000000000..c30d6a7b30 --- /dev/null +++ b/Code/Tools/ProjectManager/Source/FormBrowseEditWidget.cpp @@ -0,0 +1,49 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace O3DE::ProjectManager +{ + FormBrowseEditWidget::FormBrowseEditWidget(const QString& labelText, const QString& valueText, QWidget* parent) + : FormLineEditWidget(labelText, valueText, parent) + { + setObjectName("formBrowseEditWidget"); + + QPushButton* browseButton = new QPushButton(this); + connect(browseButton, &QPushButton::pressed, this, &FormBrowseEditWidget::HandleBrowseButton); + m_frameLayout->addWidget(browseButton); + } + + void FormBrowseEditWidget::HandleBrowseButton() + { + QString defaultPath = m_lineEdit->text(); + if (defaultPath.isEmpty()) + { + defaultPath = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); + } + + QString directory = QDir::toNativeSeparators(QFileDialog::getExistingDirectory(this, tr("Browse"), defaultPath)); + if (!directory.isEmpty()) + { + m_lineEdit->setText(directory); + } + + } +} // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/FormBrowseEditWidget.h b/Code/Tools/ProjectManager/Source/FormBrowseEditWidget.h new file mode 100644 index 0000000000..887fc29dd9 --- /dev/null +++ b/Code/Tools/ProjectManager/Source/FormBrowseEditWidget.h @@ -0,0 +1,33 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#pragma once + +#if !defined(Q_MOC_RUN) +#include +#endif + +namespace O3DE::ProjectManager +{ + class FormBrowseEditWidget + : public FormLineEditWidget + { + Q_OBJECT + + public: + explicit FormBrowseEditWidget(const QString& labelText, const QString& valueText = "", QWidget* parent = nullptr); + ~FormBrowseEditWidget() = default; + + private slots: + void HandleBrowseButton(); + }; +} // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/FormLineEditWidget.cpp b/Code/Tools/ProjectManager/Source/FormLineEditWidget.cpp new file mode 100644 index 0000000000..7ef7e3c7d8 --- /dev/null +++ b/Code/Tools/ProjectManager/Source/FormLineEditWidget.cpp @@ -0,0 +1,123 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace O3DE::ProjectManager +{ + FormLineEditWidget::FormLineEditWidget(const QString& labelText, const QString& valueText, QWidget* parent) + : QWidget(parent) + { + setObjectName("formLineEditWidget"); + + QVBoxLayout* mainLayout = new QVBoxLayout(); + mainLayout->setAlignment(Qt::AlignTop); + { + m_frame = new QFrame(this); + m_frame->setObjectName("formFrame"); + + // use a horizontal box layout so buttons can be added to the right of the field + m_frameLayout = new QHBoxLayout(); + { + QVBoxLayout* fieldLayout = new QVBoxLayout(); + + QLabel* label = new QLabel(labelText, this); + fieldLayout->addWidget(label); + + m_lineEdit = new AzQtComponents::StyledLineEdit(this); + m_lineEdit->setFlavor(AzQtComponents::StyledLineEdit::Question); + AzQtComponents::LineEdit::setErrorIconEnabled(m_lineEdit, false); + m_lineEdit->setText(valueText); + + connect(m_lineEdit, &AzQtComponents::StyledLineEdit::flavorChanged, this, &FormLineEditWidget::flavorChanged); + connect(m_lineEdit, &AzQtComponents::StyledLineEdit::onFocus, this, &FormLineEditWidget::onFocus); + connect(m_lineEdit, &AzQtComponents::StyledLineEdit::onFocusOut, this, &FormLineEditWidget::onFocusOut); + + m_lineEdit->setFrame(false); + fieldLayout->addWidget(m_lineEdit); + + m_frameLayout->addLayout(fieldLayout); + + QWidget* emptyWidget = new QWidget(this); + m_frameLayout->addWidget(emptyWidget); + } + + m_frame->setLayout(m_frameLayout); + + mainLayout->addWidget(m_frame); + + m_errorLabel = new QLabel(this); + m_errorLabel->setObjectName("formErrorLabel"); + m_errorLabel->setVisible(false); + mainLayout->addWidget(m_errorLabel); + } + + setLayout(mainLayout); + } + + void FormLineEditWidget::setErrorLabelText(const QString& labelText) + { + m_errorLabel->setText(labelText); + } + + QLineEdit* FormLineEditWidget::lineEdit() const + { + return m_lineEdit; + } + + void FormLineEditWidget::flavorChanged() + { + if (m_lineEdit->flavor() == AzQtComponents::StyledLineEdit::Flavor::Invalid) + { + m_frame->setProperty("Valid", false); + m_errorLabel->setVisible(true); + } + else + { + m_frame->setProperty("Valid", true); + m_errorLabel->setVisible(false); + } + refreshStyle(); + } + + void FormLineEditWidget::onFocus() + { + m_frame->setProperty("Focus", true); + refreshStyle(); + } + + void FormLineEditWidget::onFocusOut() + { + m_frame->setProperty("Focus", false); + refreshStyle(); + } + + void FormLineEditWidget::refreshStyle() + { + // we must unpolish/polish every child after changing a property + // or else they won't use the correct stylesheet selector + for (auto child : findChildren()) + { + child->style()->unpolish(child); + child->style()->polish(child); + } + } +} // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/FormLineEditWidget.h b/Code/Tools/ProjectManager/Source/FormLineEditWidget.h new file mode 100644 index 0000000000..3094442cbd --- /dev/null +++ b/Code/Tools/ProjectManager/Source/FormLineEditWidget.h @@ -0,0 +1,60 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#pragma once + +#if !defined(Q_MOC_RUN) +#include +#endif + +QT_FORWARD_DECLARE_CLASS(QLineEdit) +QT_FORWARD_DECLARE_CLASS(QLabel) +QT_FORWARD_DECLARE_CLASS(QFrame) +QT_FORWARD_DECLARE_CLASS(QHBoxLayout) + +namespace AzQtComponents +{ + class StyledLineEdit; +} + +namespace O3DE::ProjectManager +{ + class FormLineEditWidget + : public QWidget + { + Q_OBJECT + + public: + explicit FormLineEditWidget(const QString& labelText, const QString& valueText = "", QWidget* parent = nullptr); + ~FormLineEditWidget() = default; + + //! Set the error message for to display when invalid. + void setErrorLabelText(const QString& labelText); + + //! Returns a pointer to the underlying LineEdit. + QLineEdit* lineEdit() const; + + protected: + QLabel* m_errorLabel = nullptr; + QFrame* m_frame = nullptr; + QHBoxLayout* m_frameLayout = nullptr; + AzQtComponents::StyledLineEdit* m_lineEdit = nullptr; + + private slots: + void flavorChanged(); + void onFocus(); + void onFocusOut(); + + private: + void refreshStyle(); + }; +} // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemItemDelegate.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemItemDelegate.cpp index 434a4aeef2..9a45600f70 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemItemDelegate.cpp +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemItemDelegate.cpp @@ -22,11 +22,11 @@ namespace O3DE::ProjectManager : QStyledItemDelegate(parent) , m_gemModel(gemModel) { - AddPlatformIcon(GemInfo::Android, ":/Resources/Android.svg"); - AddPlatformIcon(GemInfo::iOS, ":/Resources/iOS.svg"); - AddPlatformIcon(GemInfo::Linux, ":/Resources/Linux.svg"); - AddPlatformIcon(GemInfo::macOS, ":/Resources/macOS.svg"); - AddPlatformIcon(GemInfo::Windows, ":/Resources/Windows.svg"); + AddPlatformIcon(GemInfo::Android, ":/Android.svg"); + AddPlatformIcon(GemInfo::iOS, ":/iOS.svg"); + AddPlatformIcon(GemInfo::Linux, ":/Linux.svg"); + AddPlatformIcon(GemInfo::macOS, ":/macOS.svg"); + AddPlatformIcon(GemInfo::Windows, ":/Windows.svg"); } void GemItemDelegate::AddPlatformIcon(GemInfo::Platform platform, const QString& iconPath) diff --git a/Code/Tools/ProjectManager/Source/PathValidator.cpp b/Code/Tools/ProjectManager/Source/PathValidator.cpp new file mode 100644 index 0000000000..8b74284b6c --- /dev/null +++ b/Code/Tools/ProjectManager/Source/PathValidator.cpp @@ -0,0 +1,65 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#include "PathValidator.h" + +#include +#include +#include + +namespace O3DE::ProjectManager +{ + PathValidator::PathValidator(PathMode pathMode, QWidget* parent) + : QValidator(parent) + , m_pathMode(pathMode) + { + } + + void PathValidator::setAllowEmpty(bool allowEmpty) + { + m_allowEmpty = allowEmpty; + } + + void PathValidator::setPathMode(PathMode pathMode) + { + m_pathMode = pathMode; + } + + QValidator::State PathValidator::validate(QString &text, int &) const + { + if(text.isEmpty()) + { + return m_allowEmpty ? QValidator::Acceptable : QValidator::Intermediate; + } + + QFileInfo pathInfo(text); + if(!pathInfo.dir().exists()) + { + return QValidator::Intermediate; + } + + switch(m_pathMode) + { + case PathMode::AnyFile://acceptable, as long as it's not an directoy + return pathInfo.isDir() ? QValidator::Intermediate : QValidator::Acceptable; + case PathMode::ExistingFile://must be an existing file + return pathInfo.exists() && pathInfo.isFile() ? QValidator::Acceptable : QValidator::Intermediate; + case PathMode::ExistingFolder://must be an existing folder + return pathInfo.exists() && pathInfo.isDir() ? QValidator::Acceptable : QValidator::Intermediate; + default: + Q_UNREACHABLE(); + } + + return QValidator::Invalid; + } + +} // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/PathValidator.h b/Code/Tools/ProjectManager/Source/PathValidator.h new file mode 100644 index 0000000000..aeb35571b9 --- /dev/null +++ b/Code/Tools/ProjectManager/Source/PathValidator.h @@ -0,0 +1,45 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#pragma once + +#if !defined(Q_MOC_RUN) +#include +#endif + +QT_FORWARD_DECLARE_CLASS(QWidget) + +namespace O3DE::ProjectManager +{ + class PathValidator + : public QValidator + { + public: + enum class PathMode { + ExistingFile, //!< A single, existings file. Useful for "Open file" + ExistingFolder, //!< A single, existing directory. Useful for "Open Folder" + AnyFile //!< A single, valid file, doesn't have to exist but the directory must. Useful for "Save File" + }; + + explicit PathValidator(PathMode pathMode, QWidget* parent = nullptr); + ~PathValidator() = default; + + void setAllowEmpty(bool allowEmpty); + void setPathMode(PathMode pathMode); + + QValidator::State validate(QString &text, int &) const override; + + private: + PathMode m_pathMode = PathMode::AnyFile; + bool m_allowEmpty = false; + }; +} // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/ProjectManagerWindow.cpp b/Code/Tools/ProjectManager/Source/ProjectManagerWindow.cpp index 6b9d268564..121add657f 100644 --- a/Code/Tools/ProjectManager/Source/ProjectManagerWindow.cpp +++ b/Code/Tools/ProjectManager/Source/ProjectManagerWindow.cpp @@ -44,10 +44,10 @@ namespace O3DE::ProjectManager QDir rootDir = QString::fromUtf8(engineRootPath.Native().data(), aznumeric_cast(engineRootPath.Native().size())); const auto pathOnDisk = rootDir.absoluteFilePath("Code/Tools/ProjectManager/Resources"); - const auto qrcPath = QStringLiteral(":/ProjectManagerWindow"); - AzQtComponents::StyleManager::addSearchPaths("projectmanagerwindow", pathOnDisk, qrcPath, engineRootPath); + const auto qrcPath = QStringLiteral(":/ProjectManager/style"); + AzQtComponents::StyleManager::addSearchPaths("style", pathOnDisk, qrcPath, engineRootPath); - AzQtComponents::StyleManager::setStyleSheet(this, QStringLiteral("projectlauncherwindow:ProjectManagerWindow.qss")); + AzQtComponents::StyleManager::setStyleSheet(this, QStringLiteral("style:ProjectManager.qss")); QVector screenEnums = { diff --git a/Code/Tools/ProjectManager/Source/ProjectManagerWindow.ui b/Code/Tools/ProjectManager/Source/ProjectManagerWindow.ui index a71ed3aabf..4e33511bff 100644 --- a/Code/Tools/ProjectManager/Source/ProjectManagerWindow.ui +++ b/Code/Tools/ProjectManager/Source/ProjectManagerWindow.ui @@ -41,8 +41,8 @@ Icon - - :/Resources/o3de_editor.ico:/Resources/o3de_editor.ico + + :/o3de_editor.ico:/o3de_editor.ico @@ -61,7 +61,7 @@ - + diff --git a/Code/Tools/ProjectManager/Source/ProjectSettingsCtrl.cpp b/Code/Tools/ProjectManager/Source/ProjectSettingsCtrl.cpp index fd1013c871..95dcec3e18 100644 --- a/Code/Tools/ProjectManager/Source/ProjectSettingsCtrl.cpp +++ b/Code/Tools/ProjectManager/Source/ProjectSettingsCtrl.cpp @@ -106,6 +106,7 @@ namespace O3DE::ProjectManager auto result = PythonBindingsInterface::Get()->CreateProject(m_projectTemplatePath, m_projectInfo); if (result.IsSuccess()) { + // adding gems is not implemented yet because we don't know what targets to add or how to add them emit ChangeScreenRequest(ProjectManagerScreen::ProjectsHome); } else diff --git a/Code/Tools/ProjectManager/Source/ProjectsHomeScreen.ui b/Code/Tools/ProjectManager/Source/ProjectsHomeScreen.ui index ea3e34d84b..2ba93ccf90 100644 --- a/Code/Tools/ProjectManager/Source/ProjectsHomeScreen.ui +++ b/Code/Tools/ProjectManager/Source/ProjectsHomeScreen.ui @@ -48,8 +48,8 @@ - - :/Resources/Add.svg:/Resources/Add.svg + + :/Add.svg:/Add.svg @@ -65,8 +65,8 @@ - - :/Resources/Select_Folder.svg:/Resources/Select_Folder.svg + + :/Select_Folder.svg:/Select_Folder.svg @@ -131,7 +131,7 @@ - + diff --git a/Code/Tools/ProjectManager/Source/PythonBindings.cpp b/Code/Tools/ProjectManager/Source/PythonBindings.cpp index e4642c95e0..9a5e82dafb 100644 --- a/Code/Tools/ProjectManager/Source/PythonBindings.cpp +++ b/Code/Tools/ProjectManager/Source/PythonBindings.cpp @@ -328,12 +328,91 @@ namespace O3DE::ProjectManager AZ::Outcome PythonBindings::GetEngineInfo() { + EngineInfo engineInfo; + bool result = ExecuteWithLock([&] { + pybind11::str enginePath = m_registration.attr("get_this_engine_path")(); + + auto o3deData = m_registration.attr("load_o3de_manifest")(); + if (pybind11::isinstance(o3deData)) + { + engineInfo.m_path = Py_To_String(enginePath); + engineInfo.m_defaultGemsFolder = Py_To_String(o3deData["default_gems_folder"]); + engineInfo.m_defaultProjectsFolder = Py_To_String(o3deData["default_projects_folder"]); + engineInfo.m_defaultRestrictedFolder = Py_To_String(o3deData["default_restricted_folder"]); + engineInfo.m_defaultTemplatesFolder = Py_To_String(o3deData["default_templates_folder"]); + engineInfo.m_thirdPartyPath = Py_To_String_Optional(o3deData,"third_party_path",""); + } + + auto engineData = m_registration.attr("get_engine_data")(pybind11::none(), enginePath); + if (pybind11::isinstance(engineData)) + { + try + { + engineInfo.m_version = Py_To_String_Optional(engineData,"O3DEVersion","0.0.0.0"); + engineInfo.m_name = Py_To_String_Optional(engineData,"engine_name","O3DE"); + } + catch ([[maybe_unused]] const std::exception& e) + { + AZ_Warning("PythonBindings", false, "Failed to get EngineInfo from %s", Py_To_String(enginePath)); + } + } + }); + + if (!result || !engineInfo.IsValid()) + { + return AZ::Failure(); + } + else + { + return AZ::Success(AZStd::move(engineInfo)); + } + return AZ::Failure(); } - bool PythonBindings::SetEngineInfo([[maybe_unused]] const EngineInfo& engineInfo) + bool PythonBindings::SetEngineInfo(const EngineInfo& engineInfo) { - return false; + bool result = ExecuteWithLock([&] { + pybind11::str enginePath = engineInfo.m_path.toStdString(); + pybind11::str defaultProjectsFolder = engineInfo.m_defaultProjectsFolder.toStdString(); + pybind11::str defaultGemsFolder = engineInfo.m_defaultGemsFolder.toStdString(); + pybind11::str defaultTemplatesFolder = engineInfo.m_defaultTemplatesFolder.toStdString(); + + auto registrationResult = m_registration.attr("register")( + enginePath, // engine_path + pybind11::none(), // project_path + pybind11::none(), // gem_path + pybind11::none(), // template_path + pybind11::none(), // restricted_path + pybind11::none(), // repo_uri + pybind11::none(), // default_engines_folder + defaultProjectsFolder, + defaultGemsFolder, + defaultTemplatesFolder + ); + + if (registrationResult.cast() != 0) + { + result = false; + } + + auto manifest = m_registration.attr("load_o3de_manifest")(); + if (pybind11::isinstance(manifest)) + { + try + { + manifest["third_party_path"] = engineInfo.m_thirdPartyPath.toStdString(); + m_registration.attr("save_o3de_manifest")(manifest); + } + catch ([[maybe_unused]] const std::exception& e) + { + AZ_Warning("PythonBindings", false, "Failed to set third party path."); + } + } + + }); + + return result; } AZ::Outcome PythonBindings::GetGem(const QString& path) diff --git a/Code/Tools/ProjectManager/project_manager.qrc b/Code/Tools/ProjectManager/project_manager.qrc deleted file mode 100644 index f36633142f..0000000000 --- a/Code/Tools/ProjectManager/project_manager.qrc +++ /dev/null @@ -1,16 +0,0 @@ - - - Resources/ProjectManager.qss - Resources/Add.svg - Resources/Select_Folder.svg - Resources/o3de_editor.ico - Resources/Windows.svg - Resources/Android.svg - Resources/iOS.svg - Resources/Linux.svg - Resources/macOS.svg - Resources/ArrowDownLine.svg - Resources/ArrowUpLine.svg - Resources/Backgrounds/FirstTimeBackgroundImage.jpg - - diff --git a/Code/Tools/ProjectManager/project_manager_files.cmake b/Code/Tools/ProjectManager/project_manager_files.cmake index 3594d1e079..858fb972aa 100644 --- a/Code/Tools/ProjectManager/project_manager_files.cmake +++ b/Code/Tools/ProjectManager/project_manager_files.cmake @@ -10,7 +10,8 @@ # set(FILES - project_manager.qrc + Resources/ProjectManager.qrc + Resources/ProjectManager.qss Source/main.cpp Source/ScreenDefs.h Source/ScreenFactory.h @@ -22,6 +23,12 @@ set(FILES Source/EngineInfo.cpp Source/FirstTimeUseScreen.h Source/FirstTimeUseScreen.cpp + Source/FormLineEditWidget.h + Source/FormLineEditWidget.cpp + Source/FormBrowseEditWidget.h + Source/FormBrowseEditWidget.cpp + Source/PathValidator.h + Source/PathValidator.cpp Source/ProjectManagerWindow.h Source/ProjectManagerWindow.cpp Source/ProjectTemplateInfo.h @@ -44,7 +51,6 @@ set(FILES Source/ProjectSettingsScreen.ui Source/EngineSettingsScreen.h Source/EngineSettingsScreen.cpp - Source/EngineSettingsScreen.ui Source/LinkWidget.h Source/LinkWidget.cpp Source/TagWidget.h diff --git a/cmake/Tools/registration.py b/cmake/Tools/registration.py index 184d2cdb31..b13419414b 100755 --- a/cmake/Tools/registration.py +++ b/cmake/Tools/registration.py @@ -2118,27 +2118,35 @@ def find_engine_data(json_data: dict, return None -def get_engine_data(engine_name: str = None, - engine_path: str or pathlib.Path = None, ) -> dict or None: +def _validate_engine_name_and_path(engine_name: str = None, + engine_path: str or pathlib.Path = None) -> pathlib.Path or None: if not engine_name and not engine_path: logger.error('Must specify either a Engine name or Engine Path.') - return 1 + return None if engine_name and not engine_path: engine_path = get_registered(engine_name=engine_name) if not engine_path: logger.error(f'Engine Path {engine_path} has not been registered.') - return 1 + return None engine_path = pathlib.Path(engine_path).resolve() engine_json = engine_path / 'engine.json' if not engine_json.is_file(): logger.error(f'Engine json {engine_json} is not present.') - return 1 + return None if not valid_o3de_engine_json(engine_json): logger.error(f'Engine json {engine_json} is not valid.') - return 1 + return None + + return engine_json + +def get_engine_data(engine_name: str = None, + engine_path: str or pathlib.Path = None ) -> dict or None: + engine_json = _validate_engine_name_and_path(engine_name, engine_path) + if not engine_json: + return None with engine_json.open('r') as f: try: @@ -2150,6 +2158,26 @@ def get_engine_data(engine_name: str = None, return None +def set_engine_data(engine_name: str = None, + engine_path: str or pathlib.Path = None, + engine_data: dict = None ) -> int: + if not engine_data: + logger.error('Must provide engine data.') + return 1 + + engine_json = _validate_engine_name_and_path(engine_name, engine_path) + if not engine_json: + return 1 + + with engine_json.open('w') as f: + try: + json.dump(engine_data, f, indent=4) + except Exception as e: + logger.warn(f'Failed to load or write {engine_json}: {str(e)}') + return 1 + + return 0 + def get_project_data(project_name: str = None, project_path: str or pathlib.Path = None, ) -> dict or None: @@ -2184,27 +2212,36 @@ def get_project_data(project_name: str = None, return None -def get_gem_data(gem_name: str = None, - gem_path: str or pathlib.Path = None, ) -> dict or None: +def _validate_gem_name_and_path(gem_name: str = None, + gem_path: str or pathlib.Path = None) -> pathlib.Path or None: if not gem_name and not gem_path: logger.error('Must specify either a Gem name or Gem Path.') - return 1 + return None if gem_name and not gem_path: gem_path = get_registered(gem_name=gem_name) if not gem_path: logger.error(f'Gem Path {gem_path} has not been registered.') - return 1 + return None gem_path = pathlib.Path(gem_path).resolve() gem_json = gem_path / 'gem.json' if not gem_json.is_file(): logger.error(f'Gem json {gem_json} is not present.') - return 1 + return None if not valid_o3de_gem_json(gem_json): logger.error(f'Gem json {gem_json} is not valid.') - return 1 + return None + + return gem_json + + +def get_gem_data(gem_name: str = None, + gem_path: str or pathlib.Path = None) -> dict or None: + gem_json = _validate_gem_name_and_path(gem_name, gem_path) + if not gem_json: + return None with gem_json.open('r') as f: try: @@ -2217,6 +2254,27 @@ def get_gem_data(gem_name: str = None, return None +def set_gem_data(gem_name: str = None, + gem_path: str or pathlib.Path = None, + gem_data: dict = None) -> int: + if not gem_data: + logger.error('Must provide Gem data.') + return 1 + + gem_json = _validate_gem_name_and_path(gem_name, gem_path) + if not gem_json: + return 1 + + with gem_json.open('w') as f: + try: + json.dump(gem_data, f, indent=4) + except Exception as e: + logger.warn(f'Failed to load and write {gem_json}: {str(e)}') + return 1 + + return 0 + + def get_template_data(template_name: str = None, template_path: str or pathlib.Path = None, ) -> dict or None: if not template_name and not template_path: