diff --git a/Code/Sandbox/Editor/CMakeLists.txt b/Code/Sandbox/Editor/CMakeLists.txt index 7be9947e99..01b58e3f77 100644 --- a/Code/Sandbox/Editor/CMakeLists.txt +++ b/Code/Sandbox/Editor/CMakeLists.txt @@ -177,6 +177,11 @@ ly_add_target( Legacy::EditorLib ProjectManager ) +set_property(SOURCE + CryEdit.cpp + APPEND PROPERTY + COMPILE_DEFINITIONS LY_CMAKE_TARGET="Editor" +) ly_add_translations( TARGETS Editor PREFIX Translations @@ -186,15 +191,8 @@ ly_add_translations( ) ly_add_dependencies(Editor AssetProcessor) -if(TARGET Editor) - set_property(SOURCE - CryEdit.cpp - APPEND PROPERTY - COMPILE_DEFINITIONS LY_CMAKE_TARGET="Editor" - ) -else() - message(FATAL_ERROR "Cannot set LY_CMAKE_TARGET define to Editor as the target doesn't exist anymore." - " Perhaps it has been renamed") +if(LY_FIRST_PROJECT_PATH) + set_property(TARGET Editor APPEND PROPERTY VS_DEBUGGER_COMMAND_ARGUMENTS "--project-path=\"${LY_FIRST_PROJECT_PATH}\"") endif() ################################################################################ diff --git a/Code/Tools/AssetProcessor/CMakeLists.txt b/Code/Tools/AssetProcessor/CMakeLists.txt index 5d4980eed4..6c12ab6024 100644 --- a/Code/Tools/AssetProcessor/CMakeLists.txt +++ b/Code/Tools/AssetProcessor/CMakeLists.txt @@ -125,6 +125,10 @@ ly_add_target( AZ::AssetProcessorBatch.Static ) +if(LY_FIRST_PROJECT_PATH) + set_property(TARGET AssetProcessor AssetProcessorBatch APPEND PROPERTY VS_DEBUGGER_COMMAND_ARGUMENTS "--project-path=\"${LY_FIRST_PROJECT_PATH}\"") +endif() + # Adds the AssetProcessorBatch target as a C preprocessor define so that it can be used as a Settings Registry # specialization in order to look up the generated .setreg which contains the dependencies # specified for the target. diff --git a/Code/Tools/ProjectManager/CMakeLists.txt b/Code/Tools/ProjectManager/CMakeLists.txt index a655600325..aeb7be9793 100644 --- a/Code/Tools/ProjectManager/CMakeLists.txt +++ b/Code/Tools/ProjectManager/CMakeLists.txt @@ -25,7 +25,6 @@ ly_add_target( OUTPUT_NAME o3de NAMESPACE AZ AUTOMOC - AUTOUIC AUTORCC FILES_CMAKE project_manager_files.cmake diff --git a/Code/Tools/ProjectManager/Resources/ProjectManager.qss b/Code/Tools/ProjectManager/Resources/ProjectManager.qss index a85b911c15..224574f522 100644 --- a/Code/Tools/ProjectManager/Resources/ProjectManager.qss +++ b/Code/Tools/ProjectManager/Resources/ProjectManager.qss @@ -232,6 +232,18 @@ QTabBar::tab:pressed margin-left:30px; } +#projectSettingsTab::tab-bar { + left: 60px; +} + +#projectSettingsTabBar::tab { + height:50px; +} + +#projectSettingsTopFrame { + background-color:#1E252F; +} + /************** Projects **************/ #firstTimeContent > #titleLabel { font-size:60px; diff --git a/Code/Tools/ProjectManager/Source/CreateProjectCtrl.cpp b/Code/Tools/ProjectManager/Source/CreateProjectCtrl.cpp index 60e351cdb4..85e34aeced 100644 --- a/Code/Tools/ProjectManager/Source/CreateProjectCtrl.cpp +++ b/Code/Tools/ProjectManager/Source/CreateProjectCtrl.cpp @@ -15,7 +15,6 @@ #include #include #include -#include #include #include @@ -42,9 +41,10 @@ namespace O3DE::ProjectManager m_stack = new QStackedWidget(this); m_stack->setObjectName("body"); - m_stack->setSizePolicy(QSizePolicy(QSizePolicy::Preferred,QSizePolicy::Expanding)); + m_stack->setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding)); m_stack->addWidget(new NewProjectSettingsScreen()); - m_stack->addWidget(new GemCatalogScreen()); + m_gemCatalog = new GemCatalogScreen(); + m_stack->addWidget(m_gemCatalog); vLayout->addWidget(m_stack); QDialogButtonBox* backNextButtons = new QDialogButtonBox(); @@ -88,6 +88,7 @@ namespace O3DE::ProjectManager emit GotoPreviousScreenRequest(); } } + void CreateProjectCtrl::HandleNextButton() { ScreenWidget* currentScreen = reinterpret_cast(m_stack->currentWidget()); @@ -106,6 +107,9 @@ namespace O3DE::ProjectManager m_projectInfo = newProjectScreen->GetProjectInfo(); m_projectTemplatePath = newProjectScreen->GetProjectTemplatePath(); + + // The next page is the gem catalog. Gather the available gems that will be shown in the gem catalog. + m_gemCatalog->ReinitForProject(m_projectInfo.m_path, /*isNewProject=*/true); } } @@ -129,6 +133,9 @@ namespace O3DE::ProjectManager { QMessageBox::critical(this, tr("Project creation failed"), tr("Failed to create project.")); } + + // Enable/disable gems for the newly created project. + m_gemCatalog->EnableDisableGemsForProject(m_projectInfo.m_path); } } diff --git a/Code/Tools/ProjectManager/Source/CreateProjectCtrl.h b/Code/Tools/ProjectManager/Source/CreateProjectCtrl.h index 355ba3941d..89d18a9ebc 100644 --- a/Code/Tools/ProjectManager/Source/CreateProjectCtrl.h +++ b/Code/Tools/ProjectManager/Source/CreateProjectCtrl.h @@ -14,6 +14,7 @@ #if !defined(Q_MOC_RUN) #include #include +#include #endif QT_FORWARD_DECLARE_CLASS(QStackedWidget) @@ -48,6 +49,8 @@ namespace O3DE::ProjectManager QString m_projectTemplatePath; ProjectInfo m_projectInfo; + + GemCatalogScreen* m_gemCatalog = nullptr; }; } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp index 2d243e7f8b..aa36c1b0ab 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp @@ -15,13 +15,12 @@ #include #include #include -#include #include #include #include #include - -//#define USE_TESTGEMDATA +#include +#include namespace O3DE::ProjectManager { @@ -29,47 +28,32 @@ namespace O3DE::ProjectManager : ScreenWidget(parent) { m_gemModel = new GemModel(this); - GemSortFilterProxyModel* proxyModel = new GemSortFilterProxyModel(m_gemModel, this); + m_proxModel = new GemSortFilterProxyModel(m_gemModel, this); QVBoxLayout* vLayout = new QVBoxLayout(); vLayout->setMargin(0); vLayout->setSpacing(0); setLayout(vLayout); - GemCatalogHeaderWidget* headerWidget = new GemCatalogHeaderWidget(proxyModel); + GemCatalogHeaderWidget* headerWidget = new GemCatalogHeaderWidget(m_proxModel); vLayout->addWidget(headerWidget); QHBoxLayout* hLayout = new QHBoxLayout(); hLayout->setMargin(0); vLayout->addLayout(hLayout); - m_gemListView = new GemListView(proxyModel, proxyModel->GetSelectionModel(), this); + m_gemListView = new GemListView(m_proxModel, m_proxModel->GetSelectionModel(), this); m_gemInspector = new GemInspector(m_gemModel, this); - m_gemInspector->setFixedWidth(320); - - // Start: Temporary gem test data -#ifdef USE_TESTGEMDATA - QVector testGemData = GenerateTestData(); - for (const GemInfo& gemInfo : testGemData) - { - m_gemModel->AddGem(gemInfo); - } -#else - // End: Temporary gem test data - auto result = PythonBindingsInterface::Get()->GetGems(); - if (result.IsSuccess()) - { - for (auto gemInfo : result.GetValue()) - { - m_gemModel->AddGem(gemInfo); - } - } -#endif + m_gemInspector->setFixedWidth(240); - GemFilterWidget* filterWidget = new GemFilterWidget(proxyModel); - filterWidget->setFixedWidth(250); + QWidget* filterWidget = new QWidget(this); + filterWidget->setFixedWidth(240); + m_filterWidgetLayout = new QVBoxLayout(); + m_filterWidgetLayout->setMargin(0); + m_filterWidgetLayout->setSpacing(0); + filterWidget->setLayout(m_filterWidgetLayout); - GemListHeaderWidget* listHeaderWidget = new GemListHeaderWidget(proxyModel); + GemListHeaderWidget* listHeaderWidget = new GemListHeaderWidget(m_proxModel); QVBoxLayout* middleVLayout = new QVBoxLayout(); middleVLayout->setMargin(0); @@ -80,98 +64,111 @@ namespace O3DE::ProjectManager hLayout->addWidget(filterWidget); hLayout->addLayout(middleVLayout); hLayout->addWidget(m_gemInspector); + } + + void GemCatalogScreen::ReinitForProject(const QString& projectPath, bool isNewProject) + { + m_gemModel->clear(); + FillModel(projectPath, isNewProject); + + if (m_filterWidget) + { + m_filterWidget->hide(); + m_filterWidget->deleteLater(); + } + + m_filterWidget = new GemFilterWidget(m_proxModel); + m_filterWidgetLayout->addWidget(m_filterWidget); + + m_proxModel->InvalidateFilter(); + + // Select the first entry after everything got correctly sized + QTimer::singleShot(200, [=]{ + QModelIndex firstModelIndex = m_gemListView->model()->index(0,0); + m_gemListView->selectionModel()->select(firstModelIndex, QItemSelectionModel::ClearAndSelect); + }); + } + + void GemCatalogScreen::FillModel(const QString& projectPath, bool isNewProject) + { + AZ::Outcome, AZStd::string> allGemInfosResult; + if (isNewProject) + { + allGemInfosResult = PythonBindingsInterface::Get()->GetEngineGemInfos(); + } + else + { + allGemInfosResult = PythonBindingsInterface::Get()->GetAllGemInfos(projectPath); + } + + if (allGemInfosResult.IsSuccess()) + { + // Add all available gems to the model. + const QVector allGemInfos = allGemInfosResult.GetValue(); + for (const GemInfo& gemInfo : allGemInfos) + { + m_gemModel->AddGem(gemInfo); + } - proxyModel->InvalidateFilter(); + // Gather enabled gems for the given project. + auto enabledGemNamesResult = PythonBindingsInterface::Get()->GetEnabledGemNames(projectPath); + if (enabledGemNamesResult.IsSuccess()) + { + const QVector enabledGemNames = enabledGemNamesResult.GetValue(); + for (const AZStd::string& enabledGemName : enabledGemNames) + { + const QModelIndex modelIndex = m_gemModel->FindIndexByNameString(enabledGemName.c_str()); + if (modelIndex.isValid()) + { + GemModel::SetWasPreviouslyAdded(*m_gemModel, modelIndex, true); + GemModel::SetIsAdded(*m_gemModel, modelIndex, true); + } + else + { + AZ_Warning("ProjectManager::GemCatalog", false, + "Cannot find entry for gem with name '%s'. The CMake target name probably does not match the specified name in the gem.json.", + enabledGemName.c_str()); + } + } + } + else + { + QMessageBox::critical(nullptr, tr("Operation failed"), QString("Cannot retrieve enabled gems for project %1.\n\nError:\n%2").arg(projectPath, enabledGemNamesResult.GetError().c_str())); + } + } + else + { + QMessageBox::critical(nullptr, tr("Operation failed"), QString("Cannot retrieve gems for %1.\n\nError:\n%2").arg(projectPath, allGemInfosResult.GetError().c_str())); + } } - QVector GemCatalogScreen::GenerateTestData() + void GemCatalogScreen::EnableDisableGemsForProject(const QString& projectPath) { - QVector result; - - GemInfo gem("EMotion FX", - "O3DE Foundation", - "EMFX is a real-time character animation system. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", - (GemInfo::Android | GemInfo::iOS | GemInfo::macOS | GemInfo::Windows | GemInfo::Linux), - true); - gem.m_directoryLink = "C:/"; - gem.m_documentationLink = "http://www.amazon.com"; - gem.m_dependingGemUuids = QStringList({"EMotionFX", "Atom"}); - gem.m_conflictingGemUuids = QStringList({"Vegetation", "Camera", "ScriptCanvas", "CloudCanvas", "Networking"}); - gem.m_types = (GemInfo::Code | GemInfo::Asset); - gem.m_version = "v1.01"; - gem.m_lastUpdatedDate = "24th April 2021"; - gem.m_binarySizeInKB = 40; - gem.m_features = QStringList({"Animation", "Assets", "Physics"}); - gem.m_gemOrigin = GemInfo::O3DEFoundation; - result.push_back(gem); - - gem.m_name = "Atom"; - gem.m_creator = "O3DE Seattle"; - gem.m_summary = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."; - gem.m_platforms = (GemInfo::Android | GemInfo::Windows | GemInfo::Linux | GemInfo::macOS); - gem.m_isAdded = true; - gem.m_directoryLink = "C:/"; - gem.m_documentationLink = "https://aws.amazon.com/gametech/"; - gem.m_dependingGemUuids = QStringList({"EMotionFX", "Core", "AudioSystem", "Camera", "Particles"}); - gem.m_conflictingGemUuids = QStringList({"CloudCanvas", "NovaNet"}); - gem.m_version = "v2.31"; - gem.m_lastUpdatedDate = "24th November 2020"; - gem.m_features = QStringList({"Assets", "Rendering", "UI", "VR", "Debug", "Environment"}); - gem.m_binarySizeInKB = 2087; - result.push_back(gem); - - gem.m_name = "Physics"; - gem.m_creator = "O3DE London"; - gem.m_summary = "Lorem ipsum dolor sit amet, consectetur adipiscing elit."; - gem.m_platforms = (GemInfo::Android | GemInfo::Linux | GemInfo::macOS); - gem.m_isAdded = true; - gem.m_directoryLink = "C:/"; - gem.m_documentationLink = "https://aws.amazon.com/gametech/"; - gem.m_dependingGemUuids = QStringList({"GraphCanvas", "ExpressionEvaluation", "UI Lib", "Multiplayer", "GameStateSamples"}); - gem.m_conflictingGemUuids = QStringList({"Cloud Canvas", "EMotion FX", "Streaming", "MessagePopup", "Cloth", "Graph Canvas", "Twitch Integration"}); - gem.m_version = "v1.5.102145"; - gem.m_lastUpdatedDate = "1st January 2021"; - gem.m_binarySizeInKB = 2000000; - gem.m_features = QStringList({"Physics", "Gameplay", "Debug", "Assets"}); - result.push_back(gem); - - result.push_back(O3DE::ProjectManager::GemInfo("Certificate Manager", - "O3DE Irvine", - "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", - GemInfo::Windows, - false)); - - result.push_back(O3DE::ProjectManager::GemInfo("Cloud Gem Framework", - "O3DE Seattle", - "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", - GemInfo::iOS | GemInfo::Linux, - false)); - - result.push_back(O3DE::ProjectManager::GemInfo("Cloud Gem Core", - "O3DE Foundation", - "Lorem ipsum dolor sit amet, consectetur adipiscing elit.", - GemInfo::Android | GemInfo::Windows | GemInfo::Linux, - true)); - - result.push_back(O3DE::ProjectManager::GemInfo("Gestures", - "O3DE Foundation", - "Lorem ipsum dolor sit amet, consectetur adipiscing elit.", - GemInfo::Android | GemInfo::Windows | GemInfo::Linux, - false)); - - result.push_back(O3DE::ProjectManager::GemInfo("Effects System", - "O3DE Foundation", - "Lorem ipsum dolor sit amet, consectetur adipiscing elit.", - GemInfo::Android | GemInfo::Windows | GemInfo::Linux, - true)); - - result.push_back(O3DE::ProjectManager::GemInfo("Microphone", - "O3DE Foundation", - "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus euismod ligula vitae dui dictum, a sodales dolor luctus. Sed id elit dapibus, finibus neque sed, efficitur mi. Nam facilisis ligula at eleifend pellentesque. Praesent non ex consectetur, blandit tellus in, venenatis lacus. Duis nec neque in urna ullamcorper euismod id eu leo. Nam efficitur dolor sed odio vehicula venenatis. Suspendisse nec est non velit commodo cursus in sit amet dui. Ut bibendum nisl et libero hendrerit dapibus. Vestibulum ultrices ullamcorper urna, placerat porttitor est lobortis in. Interdum et malesuada fames ac ante ipsum primis in faucibus. Integer a magna ac tellus sollicitudin porttitor. Phasellus lobortis viverra justo id bibendum. Etiam ac pharetra risus. Nulla vitae justo nibh. Nulla viverra leo et molestie interdum. Duis sit amet bibendum nulla, sit amet vehicula augue.", - GemInfo::Android | GemInfo::Windows | GemInfo::Linux, - false)); - - return result; + IPythonBindings* pythonBindings = PythonBindingsInterface::Get(); + QVector toBeAdded = m_gemModel->GatherGemsToBeAdded(); + QVector toBeRemoved = m_gemModel->GatherGemsToBeRemoved(); + + for (const QModelIndex& modelIndex : toBeAdded) + { + const QString gemPath = GemModel::GetPath(modelIndex); + const AZ::Outcome result = pythonBindings->AddGemToProject(gemPath, projectPath); + if (!result.IsSuccess()) + { + QMessageBox::critical(nullptr, "Operation failed", + QString("Cannot add gem %1 to project.\n\nError:\n%2").arg(GemModel::GetName(modelIndex), result.GetError().c_str())); + } + } + + for (const QModelIndex& modelIndex : toBeRemoved) + { + const QString gemPath = GemModel::GetPath(modelIndex); + const AZ::Outcome result = pythonBindings->RemoveGemFromProject(gemPath, projectPath); + if (!result.IsSuccess()) + { + QMessageBox::critical(nullptr, "Operation failed", + QString("Cannot remove gem %1 from project.\n\nError:\n%2").arg(GemModel::GetName(modelIndex), result.GetError().c_str())); + } + } } ProjectManagerScreen GemCatalogScreen::GetScreenEnum() diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.h b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.h index 44e0727c7e..0847d9b74e 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.h +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.h @@ -14,9 +14,11 @@ #if !defined(Q_MOC_RUN) #include +#include #include #include #include +#include #endif namespace O3DE::ProjectManager @@ -29,11 +31,17 @@ namespace O3DE::ProjectManager ~GemCatalogScreen() = default; ProjectManagerScreen GetScreenEnum() override; + void ReinitForProject(const QString& projectPath, bool isNewProject); + void EnableDisableGemsForProject(const QString& projectPath); + private: - QVector GenerateTestData(); + void FillModel(const QString& projectPath, bool isNewProject); GemListView* m_gemListView = nullptr; GemInspector* m_gemInspector = nullptr; GemModel* m_gemModel = nullptr; + GemSortFilterProxyModel* m_proxModel = nullptr; + QVBoxLayout* m_filterWidgetLayout = nullptr; + GemFilterWidget* m_filterWidget = nullptr; }; } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemInfo.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemInfo.cpp index bc44928868..c96d31c3c2 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemInfo.cpp +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemInfo.cpp @@ -79,4 +79,9 @@ namespace O3DE::ProjectManager { return (m_platforms & platform); } + + bool GemInfo::operator<(const GemInfo& gemInfo) const + { + return (m_displayName < gemInfo.m_displayName); + } } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemInfo.h b/Code/Tools/ProjectManager/Source/GemCatalog/GemInfo.h index 1032ca5eaf..722783ece1 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemInfo.h +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemInfo.h @@ -61,6 +61,8 @@ namespace O3DE::ProjectManager bool IsValid() const; + bool operator<(const GemInfo& gemInfo) const; + QString m_path; QString m_name = "Unknown Gem Name"; QString m_displayName = "Unknown Gem Name"; diff --git a/Code/Tools/ProjectManager/Source/NewProjectSettingsScreen.cpp b/Code/Tools/ProjectManager/Source/NewProjectSettingsScreen.cpp index 53400b3193..c8dc8451ae 100644 --- a/Code/Tools/ProjectManager/Source/NewProjectSettingsScreen.cpp +++ b/Code/Tools/ProjectManager/Source/NewProjectSettingsScreen.cpp @@ -34,83 +34,57 @@ namespace O3DE::ProjectManager constexpr const char* k_pathProperty = "Path"; NewProjectSettingsScreen::NewProjectSettingsScreen(QWidget* parent) - : ScreenWidget(parent) + : ProjectSettingsScreen(parent) { - QHBoxLayout* hLayout = new QHBoxLayout(this); - hLayout->setAlignment(Qt::AlignLeft); - hLayout->setContentsMargins(0,0,0,0); - - // if we don't provide a parent for this box layout the stylesheet doesn't take - // if we don't set this in a frame (just use a sub-layout) all the content will align incorrectly horizontally - QFrame* projectSettingsFrame = new QFrame(this); - projectSettingsFrame->setObjectName("projectSettings"); - QVBoxLayout* vLayout = new QVBoxLayout(this); - - // you cannot remove content margins in qss - vLayout->setContentsMargins(0,0,0,0); - vLayout->setAlignment(Qt::AlignTop); + const QString defaultName{ "NewProject" }; + const QString defaultPath = QDir::toNativeSeparators(GetDefaultProjectPath() + "/" + defaultName); + + m_projectName->lineEdit()->setText(defaultName); + m_projectPath->lineEdit()->setText(defaultPath); + + // if we don't use a QFrame we cannot "contain" the widgets inside and move them around + // as a group + QFrame* projectTemplateWidget = new QFrame(this); + projectTemplateWidget->setObjectName("projectTemplate"); + QVBoxLayout* containerLayout = new QVBoxLayout(); + containerLayout->setAlignment(Qt::AlignTop); { - const QString defaultName{ "NewProject" }; - const QString defaultPath = QDir::toNativeSeparators(GetDefaultProjectPath() + "/" + defaultName); - - m_projectName = new FormLineEditWidget(tr("Project name"), defaultName, this); - connect(m_projectName->lineEdit(), &QLineEdit::textChanged, this, &NewProjectSettingsScreen::ValidateProjectPath); - vLayout->addWidget(m_projectName); - - m_projectPath = new FormBrowseEditWidget(tr("Project Location"), defaultPath, this); - m_projectPath->lineEdit()->setReadOnly(true); - connect(m_projectPath->lineEdit(), &QLineEdit::textChanged, this, &NewProjectSettingsScreen::ValidateProjectPath); - vLayout->addWidget(m_projectPath); - - // if we don't use a QFrame we cannot "contain" the widgets inside and move them around - // as a group - QFrame* projectTemplateWidget = new QFrame(this); - projectTemplateWidget->setObjectName("projectTemplate"); - QVBoxLayout* containerLayout = new QVBoxLayout(); - containerLayout->setAlignment(Qt::AlignTop); + QLabel* projectTemplateLabel = new QLabel(tr("Select a Project Template")); + projectTemplateLabel->setObjectName("projectTemplateLabel"); + containerLayout->addWidget(projectTemplateLabel); + + QLabel* projectTemplateDetailsLabel = new QLabel(tr("Project templates are pre-configured with relevant Gems that provide " + "additional functionality and content to the project.")); + projectTemplateDetailsLabel->setWordWrap(true); + projectTemplateDetailsLabel->setObjectName("projectTemplateDetailsLabel"); + containerLayout->addWidget(projectTemplateDetailsLabel); + + QHBoxLayout* templateLayout = new QHBoxLayout(this); + containerLayout->addItem(templateLayout); + + m_projectTemplateButtonGroup = new QButtonGroup(this); + m_projectTemplateButtonGroup->setObjectName("templateButtonGroup"); + auto templatesResult = PythonBindingsInterface::Get()->GetProjectTemplates(); + if (templatesResult.IsSuccess() && !templatesResult.GetValue().isEmpty()) { - QLabel* projectTemplateLabel = new QLabel(tr("Select a Project Template")); - projectTemplateLabel->setObjectName("projectTemplateLabel"); - containerLayout->addWidget(projectTemplateLabel); - - QLabel* projectTemplateDetailsLabel = new QLabel(tr("Project templates are pre-configured with relevant Gems that provide " - "additional functionality and content to the project.")); - projectTemplateDetailsLabel->setWordWrap(true); - projectTemplateDetailsLabel->setObjectName("projectTemplateDetailsLabel"); - containerLayout->addWidget(projectTemplateDetailsLabel); - - QHBoxLayout* templateLayout = new QHBoxLayout(this); - containerLayout->addItem(templateLayout); - - m_projectTemplateButtonGroup = new QButtonGroup(this); - m_projectTemplateButtonGroup->setObjectName("templateButtonGroup"); - auto templatesResult = PythonBindingsInterface::Get()->GetProjectTemplates(); - if (templatesResult.IsSuccess() && !templatesResult.GetValue().isEmpty()) + for (const ProjectTemplateInfo& projectTemplate : templatesResult.GetValue()) { - for (auto projectTemplate : templatesResult.GetValue()) - { - QRadioButton* radioButton = new QRadioButton(projectTemplate.m_name, this); - radioButton->setProperty(k_pathProperty, projectTemplate.m_path); - m_projectTemplateButtonGroup->addButton(radioButton); + QRadioButton* radioButton = new QRadioButton(projectTemplate.m_name, this); + radioButton->setProperty(k_pathProperty, projectTemplate.m_path); + m_projectTemplateButtonGroup->addButton(radioButton); - containerLayout->addWidget(radioButton); - } - - m_projectTemplateButtonGroup->buttons().first()->setChecked(true); + containerLayout->addWidget(radioButton); } + + m_projectTemplateButtonGroup->buttons().first()->setChecked(true); } - projectTemplateWidget->setLayout(containerLayout); - vLayout->addWidget(projectTemplateWidget); } - projectSettingsFrame->setLayout(vLayout); - - hLayout->addWidget(projectSettingsFrame); + projectTemplateWidget->setLayout(containerLayout); + m_verticalLayout->addWidget(projectTemplateWidget); QWidget* projectTemplateDetails = new QWidget(this); projectTemplateDetails->setObjectName("projectTemplateDetails"); - hLayout->addWidget(projectTemplateDetails); - - this->setLayout(hLayout); + m_horizontalLayout->addWidget(projectTemplateDetails); } QString NewProjectSettingsScreen::GetDefaultProjectPath() @@ -133,69 +107,13 @@ namespace O3DE::ProjectManager return ProjectManagerScreen::NewProjectSettings; } - void NewProjectSettingsScreen::ValidateProjectPath() - { - Validate(); - } - void NewProjectSettingsScreen::NotifyCurrentScreen() { Validate(); } - ProjectInfo NewProjectSettingsScreen::GetProjectInfo() - { - ProjectInfo projectInfo; - projectInfo.m_projectName = m_projectName->lineEdit()->text(); - projectInfo.m_path = m_projectPath->lineEdit()->text(); - return projectInfo; - } - QString NewProjectSettingsScreen::GetProjectTemplatePath() { return m_projectTemplateButtonGroup->checkedButton()->property(k_pathProperty).toString(); } - - bool NewProjectSettingsScreen::Validate() - { - bool projectPathIsValid = true; - if (m_projectPath->lineEdit()->text().isEmpty()) - { - projectPathIsValid = false; - m_projectPath->setErrorLabelText(tr("Please provide a valid location.")); - } - else - { - QDir path(m_projectPath->lineEdit()->text()); - if (path.exists() && !path.isEmpty()) - { - projectPathIsValid = false; - m_projectPath->setErrorLabelText(tr("This folder exists and isn't empty. Please choose a different location.")); - } - } - - bool projectNameIsValid = true; - if (m_projectName->lineEdit()->text().isEmpty()) - { - projectNameIsValid = false; - m_projectName->setErrorLabelText(tr("Please provide a project name.")); - } - else - { - // this validation should roughly match the utils.validate_identifier which the cli - // uses to validate project names - QRegExp validProjectNameRegex("[A-Za-z][A-Za-z0-9_-]{0,63}"); - const bool result = validProjectNameRegex.exactMatch(m_projectName->lineEdit()->text()); - if (!result) - { - projectNameIsValid = false; - m_projectName->setErrorLabelText(tr("Project names must start with a letter and consist of up to 64 letter, number, '_' or '-' characters")); - } - - } - - m_projectName->setErrorLabelVisible(!projectNameIsValid); - m_projectPath->setErrorLabelVisible(!projectPathIsValid); - return projectNameIsValid && projectPathIsValid; - } } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/NewProjectSettingsScreen.h b/Code/Tools/ProjectManager/Source/NewProjectSettingsScreen.h index 0560f8728d..6a4b6ec57d 100644 --- a/Code/Tools/ProjectManager/Source/NewProjectSettingsScreen.h +++ b/Code/Tools/ProjectManager/Source/NewProjectSettingsScreen.h @@ -12,41 +12,28 @@ #pragma once #if !defined(Q_MOC_RUN) -#include -#include +#include #endif QT_FORWARD_DECLARE_CLASS(QButtonGroup) namespace O3DE::ProjectManager { - QT_FORWARD_DECLARE_CLASS(FormLineEditWidget) - QT_FORWARD_DECLARE_CLASS(FormBrowseEditWidget) - class NewProjectSettingsScreen - : public ScreenWidget + : public ProjectSettingsScreen { public: explicit NewProjectSettingsScreen(QWidget* parent = nullptr); ~NewProjectSettingsScreen() = default; ProjectManagerScreen GetScreenEnum() override; - ProjectInfo GetProjectInfo(); QString GetProjectTemplatePath(); - bool Validate(); - void NotifyCurrentScreen() override; - protected slots: - void HandleBrowseButton(); - void ValidateProjectPath(); - private: QString GetDefaultProjectPath(); - FormLineEditWidget* m_projectName; - FormBrowseEditWidget* m_projectPath; QButtonGroup* m_projectTemplateButtonGroup; }; diff --git a/Code/Tools/ProjectManager/Source/ProjectButtonWidget.cpp b/Code/Tools/ProjectManager/Source/ProjectButtonWidget.cpp index 72ffa686c1..b1dbd984fb 100644 --- a/Code/Tools/ProjectManager/Source/ProjectButtonWidget.cpp +++ b/Code/Tools/ProjectManager/Source/ProjectButtonWidget.cpp @@ -22,8 +22,6 @@ #include #include -//#define SHOW_ALL_PROJECT_ACTIONS - namespace O3DE::ProjectManager { inline constexpr static int s_projectImageWidth = 210; @@ -96,10 +94,6 @@ namespace O3DE::ProjectManager m_removeProjectAction = newProjectMenu->addAction(tr("Remove from O3DE")); m_deleteProjectAction = newProjectMenu->addAction(tr("Delete this Project")); -#ifdef SHOW_ALL_PROJECT_ACTIONS - m_editProjectGemsAction = newProjectMenu->addAction(tr("Cutomize Gems...")); -#endif - QFrame* footer = new QFrame(this); QHBoxLayout* hLayout = new QHBoxLayout(); hLayout->setContentsMargins(0, 0, 0, 0); @@ -121,10 +115,6 @@ namespace O3DE::ProjectManager connect(m_copyProjectAction, &QAction::triggered, [this]() { emit CopyProject(m_projectInfo.m_path); }); connect(m_removeProjectAction, &QAction::triggered, [this]() { emit RemoveProject(m_projectInfo.m_path); }); connect(m_deleteProjectAction, &QAction::triggered, [this]() { emit DeleteProject(m_projectInfo.m_path); }); - -#ifdef SHOW_ALL_PROJECT_ACTIONS - connect(m_editProjectGemsAction, &QAction::triggered, [this]() { emit EditProjectGems(m_projectInfo.m_path); }); -#endif } void ProjectButton::SetButtonEnabled(bool enabled) diff --git a/Code/Tools/ProjectManager/Source/ProjectButtonWidget.h b/Code/Tools/ProjectManager/Source/ProjectButtonWidget.h index e82b56b3fa..3ac69b7603 100644 --- a/Code/Tools/ProjectManager/Source/ProjectButtonWidget.h +++ b/Code/Tools/ProjectManager/Source/ProjectButtonWidget.h @@ -62,7 +62,6 @@ namespace O3DE::ProjectManager signals: void OpenProject(const QString& projectName); void EditProject(const QString& projectName); - void EditProjectGems(const QString& projectName); void CopyProject(const QString& projectName); void RemoveProject(const QString& projectName); void DeleteProject(const QString& projectName); @@ -73,7 +72,6 @@ namespace O3DE::ProjectManager ProjectInfo m_projectInfo; LabelButton* m_projectImageLabel; QAction* m_editProjectAction; - QAction* m_editProjectGemsAction; QAction* m_copyProjectAction; QAction* m_removeProjectAction; QAction* m_deleteProjectAction; diff --git a/Code/Tools/ProjectManager/Source/ProjectInfo.cpp b/Code/Tools/ProjectManager/Source/ProjectInfo.cpp index b0b740fad9..f0dc05cc62 100644 --- a/Code/Tools/ProjectManager/Source/ProjectInfo.cpp +++ b/Code/Tools/ProjectManager/Source/ProjectInfo.cpp @@ -25,6 +25,19 @@ namespace O3DE::ProjectManager { } + bool ProjectInfo::operator==(const ProjectInfo& rhs) + { + return m_path == rhs.m_path + && m_projectName == rhs.m_projectName + && m_imagePath == rhs.m_imagePath + && m_backgroundImagePath == rhs.m_backgroundImagePath; + } + + bool ProjectInfo::operator!=(const ProjectInfo& rhs) + { + return !operator==(rhs); + } + bool ProjectInfo::IsValid() const { return !m_path.isEmpty() && !m_projectName.isEmpty(); diff --git a/Code/Tools/ProjectManager/Source/ProjectInfo.h b/Code/Tools/ProjectManager/Source/ProjectInfo.h index 92a7459d78..71fa12b344 100644 --- a/Code/Tools/ProjectManager/Source/ProjectInfo.h +++ b/Code/Tools/ProjectManager/Source/ProjectInfo.h @@ -25,6 +25,8 @@ namespace O3DE::ProjectManager ProjectInfo() = default; ProjectInfo(const QString& path, const QString& projectName, const QString& displayName, const QString& imagePath, const QString& backgroundImagePath, bool isNew); + bool operator==(const ProjectInfo& rhs); + bool operator!=(const ProjectInfo& rhs); bool IsValid() const; diff --git a/Code/Tools/ProjectManager/Source/ProjectSettingsScreen.cpp b/Code/Tools/ProjectManager/Source/ProjectSettingsScreen.cpp index 76aa1d2897..26711753d4 100644 --- a/Code/Tools/ProjectManager/Source/ProjectSettingsScreen.cpp +++ b/Code/Tools/ProjectManager/Source/ProjectSettingsScreen.cpp @@ -11,45 +11,131 @@ */ #include +#include +#include +#include +#include -#include +#include +#include +#include +#include +#include +#include +#include namespace O3DE::ProjectManager { ProjectSettingsScreen::ProjectSettingsScreen(QWidget* parent) : ScreenWidget(parent) - , m_ui(new Ui::ProjectSettingsClass()) { - m_ui->setupUi(this); + m_horizontalLayout = new QHBoxLayout(this); + m_horizontalLayout->setAlignment(Qt::AlignLeft); + m_horizontalLayout->setContentsMargins(0, 0, 0, 0); - connect(m_ui->gemsButton, &QPushButton::pressed, this, &ProjectSettingsScreen::HandleGemsButton); + // if we don't provide a parent for this box layout the stylesheet doesn't take + // if we don't set this in a frame (just use a sub-layout) all the content will align incorrectly horizontally + QFrame* projectSettingsFrame = new QFrame(this); + projectSettingsFrame->setObjectName("projectSettings"); + m_verticalLayout = new QVBoxLayout(this); + + // you cannot remove content margins in qss + m_verticalLayout->setContentsMargins(0, 0, 0, 0); + m_verticalLayout->setAlignment(Qt::AlignTop); + + m_projectName = new FormLineEditWidget(tr("Project name"), "", this); + connect(m_projectName->lineEdit(), &QLineEdit::textChanged, this, &ProjectSettingsScreen::ValidateProjectName); + m_verticalLayout->addWidget(m_projectName); + + m_projectPath = new FormBrowseEditWidget(tr("Project Location"), "", this); + m_projectPath->lineEdit()->setReadOnly(true); + connect(m_projectPath->lineEdit(), &QLineEdit::textChanged, this, &ProjectSettingsScreen::Validate); + m_verticalLayout->addWidget(m_projectPath); + + projectSettingsFrame->setLayout(m_verticalLayout); + + m_horizontalLayout->addWidget(projectSettingsFrame); + + setLayout(m_horizontalLayout); } ProjectManagerScreen ProjectSettingsScreen::GetScreenEnum() { - return ProjectManagerScreen::ProjectSettings; + return ProjectManagerScreen::Invalid; } - ProjectInfo ProjectSettingsScreen::GetProjectInfo() + QString ProjectSettingsScreen::GetDefaultProjectPath() { - // Impl pending next PR - return ProjectInfo(); + QString defaultPath = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); + AZ::Outcome engineInfoResult = PythonBindingsInterface::Get()->GetEngineInfo(); + if (engineInfoResult.IsSuccess()) + { + QDir path(QDir::toNativeSeparators(engineInfoResult.GetValue().m_defaultProjectsFolder)); + if (path.exists()) + { + defaultPath = path.absolutePath(); + } + } + return defaultPath; } - void ProjectSettingsScreen::SetProjectInfo() + ProjectInfo ProjectSettingsScreen::GetProjectInfo() { - // Impl pending next PR + ProjectInfo projectInfo; + projectInfo.m_projectName = m_projectName->lineEdit()->text(); + projectInfo.m_path = m_projectPath->lineEdit()->text(); + return projectInfo; } - bool ProjectSettingsScreen::Validate() + bool ProjectSettingsScreen::ValidateProjectName() { - // Impl pending next PR - return true; - } + bool projectNameIsValid = true; + if (m_projectName->lineEdit()->text().isEmpty()) + { + projectNameIsValid = false; + m_projectName->setErrorLabelText(tr("Please provide a project name.")); + } + else + { + // this validation should roughly match the utils.validate_identifier which the cli + // uses to validate project names + QRegExp validProjectNameRegex("[A-Za-z][A-Za-z0-9_-]{0,63}"); + const bool result = validProjectNameRegex.exactMatch(m_projectName->lineEdit()->text()); + if (!result) + { + projectNameIsValid = false; + m_projectName->setErrorLabelText( + tr("Project names must start with a letter and consist of up to 64 letter, number, '_' or '-' characters")); + } + } - void ProjectSettingsScreen::HandleGemsButton() + m_projectName->setErrorLabelVisible(!projectNameIsValid); + return projectNameIsValid; + } + bool ProjectSettingsScreen::ValidateProjectPath() { - emit ChangeScreenRequest(ProjectManagerScreen::GemCatalog); + bool projectPathIsValid = true; + if (m_projectPath->lineEdit()->text().isEmpty()) + { + projectPathIsValid = false; + m_projectPath->setErrorLabelText(tr("Please provide a valid location.")); + } + else + { + QDir path(m_projectPath->lineEdit()->text()); + if (path.exists() && !path.isEmpty()) + { + projectPathIsValid = false; + m_projectPath->setErrorLabelText(tr("This folder exists and isn't empty. Please choose a different location.")); + } + } + + m_projectPath->setErrorLabelVisible(!projectPathIsValid); + return projectPathIsValid; } + bool ProjectSettingsScreen::Validate() + { + return ValidateProjectName() && ValidateProjectPath(); + } } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/ProjectSettingsScreen.h b/Code/Tools/ProjectManager/Source/ProjectSettingsScreen.h index a4cafcd93a..0d75bbbc64 100644 --- a/Code/Tools/ProjectManager/Source/ProjectSettingsScreen.h +++ b/Code/Tools/ProjectManager/Source/ProjectSettingsScreen.h @@ -12,17 +12,18 @@ #pragma once #if !defined(Q_MOC_RUN) -#include #include +#include #endif -namespace Ui -{ - class ProjectSettingsClass; -} +QT_FORWARD_DECLARE_CLASS(QHBoxLayout) +QT_FORWARD_DECLARE_CLASS(QVBoxLayout) namespace O3DE::ProjectManager { + QT_FORWARD_DECLARE_CLASS(FormLineEditWidget) + QT_FORWARD_DECLARE_CLASS(FormBrowseEditWidget) + class ProjectSettingsScreen : public ScreenWidget { @@ -32,15 +33,20 @@ namespace O3DE::ProjectManager ProjectManagerScreen GetScreenEnum() override; ProjectInfo GetProjectInfo(); - void SetProjectInfo(); bool Validate(); protected slots: - void HandleGemsButton(); + virtual bool ValidateProjectName(); + virtual bool ValidateProjectPath(); + + protected: + QString GetDefaultProjectPath(); - private: - QScopedPointer m_ui; + QHBoxLayout* m_horizontalLayout; + QVBoxLayout* m_verticalLayout; + FormLineEditWidget* m_projectName; + FormBrowseEditWidget* m_projectPath; }; } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/ProjectSettingsScreen.ui b/Code/Tools/ProjectManager/Source/ProjectSettingsScreen.ui deleted file mode 100644 index 934238a257..0000000000 --- a/Code/Tools/ProjectManager/Source/ProjectSettingsScreen.ui +++ /dev/null @@ -1,113 +0,0 @@ - - - ProjectSettingsClass - - - - 0 - 0 - 782 - 579 - - - - Form - - - - - - - - Project Settings - - - - - - - Gems - - - - - - - Qt::Horizontal - - - - 761 - 20 - - - - - - - - - - - - - - Project Name - - - - - - - - - - Project Location - - - - - - - - - - Project Image Location - - - - - - - - - - Project Background Image Location - - - - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - - - diff --git a/Code/Tools/ProjectManager/Source/ProjectsScreen.cpp b/Code/Tools/ProjectManager/Source/ProjectsScreen.cpp index 7b9e3ecb9d..425aa8514d 100644 --- a/Code/Tools/ProjectManager/Source/ProjectsScreen.cpp +++ b/Code/Tools/ProjectManager/Source/ProjectsScreen.cpp @@ -182,10 +182,6 @@ namespace O3DE::ProjectManager connect(projectButton, &ProjectButton::CopyProject, this, &ProjectsScreen::HandleCopyProject); connect(projectButton, &ProjectButton::RemoveProject, this, &ProjectsScreen::HandleRemoveProject); connect(projectButton, &ProjectButton::DeleteProject, this, &ProjectsScreen::HandleDeleteProject); - -#ifdef SHOW_ALL_PROJECT_ACTIONS - connect(projectButton, &ProjectButton::EditProjectGems, this, &ProjectsScreen::HandleEditProjectGems); -#endif } layout->addWidget(projectsScrollArea); @@ -293,14 +289,8 @@ namespace O3DE::ProjectManager void ProjectsScreen::HandleEditProject(const QString& projectPath) { emit NotifyCurrentProject(projectPath); - emit ResetScreenRequest(ProjectManagerScreen::UpdateProject); emit ChangeScreenRequest(ProjectManagerScreen::UpdateProject); } - void ProjectsScreen::HandleEditProjectGems(const QString& projectPath) - { - emit NotifyCurrentProject(projectPath); - emit ChangeScreenRequest(ProjectManagerScreen::GemCatalog); - } void ProjectsScreen::HandleCopyProject(const QString& projectPath) { // Open file dialog and choose location for copied project then register copy with O3DE diff --git a/Code/Tools/ProjectManager/Source/ProjectsScreen.h b/Code/Tools/ProjectManager/Source/ProjectsScreen.h index d88ba8398d..e02b34525b 100644 --- a/Code/Tools/ProjectManager/Source/ProjectsScreen.h +++ b/Code/Tools/ProjectManager/Source/ProjectsScreen.h @@ -41,7 +41,6 @@ namespace O3DE::ProjectManager void HandleAddProjectButton(); void HandleOpenProject(const QString& projectPath); void HandleEditProject(const QString& projectPath); - void HandleEditProjectGems(const QString& projectPath); void HandleCopyProject(const QString& projectPath); void HandleRemoveProject(const QString& projectPath); void HandleDeleteProject(const QString& projectPath); diff --git a/Code/Tools/ProjectManager/Source/PythonBindings.cpp b/Code/Tools/ProjectManager/Source/PythonBindings.cpp index 2859a8fc9d..7062d886da 100644 --- a/Code/Tools/ProjectManager/Source/PythonBindings.cpp +++ b/Code/Tools/ProjectManager/Source/PythonBindings.cpp @@ -283,6 +283,7 @@ namespace O3DE::ProjectManager AZ_Warning("ProjectManagerWindow", result != -1, "Append to sys path failed"); // import required modules + m_cmake = pybind11::module::import("o3de.cmake"); m_register = pybind11::module::import("o3de.register"); m_manifest = pybind11::module::import("o3de.manifest"); m_engineTemplate = pybind11::module::import("o3de.engine_template"); @@ -344,7 +345,7 @@ namespace O3DE::ProjectManager return finalResult; } - bool PythonBindings::ExecuteWithLock(AZStd::function executionCallback) + AZ::Outcome PythonBindings::ExecuteWithLockErrorHandling(AZStd::function executionCallback) { AZStd::lock_guard lock(m_lock); pybind11::gil_scoped_release release; @@ -353,13 +354,19 @@ namespace O3DE::ProjectManager try { executionCallback(); - return true; } catch ([[maybe_unused]] const std::exception& e) { AZ_Warning("PythonBindings", false, "Python exception %s", e.what()); - return false; + return AZ::Failure(e.what()); } + + return AZ::Success(); + } + + bool PythonBindings::ExecuteWithLock(AZStd::function executionCallback) + { + return ExecuteWithLockErrorHandling(executionCallback).IsSuccess(); } AZ::Outcome PythonBindings::GetEngineInfo() @@ -452,7 +459,7 @@ namespace O3DE::ProjectManager return result; } - AZ::Outcome PythonBindings::GetGem(const QString& path) + AZ::Outcome PythonBindings::GetGemInfo(const QString& path) { GemInfo gemInfo = GemInfoFromPath(pybind11::str(path.toStdString())); if (gemInfo.IsValid()) @@ -465,32 +472,79 @@ namespace O3DE::ProjectManager } } - AZ::Outcome> PythonBindings::GetGems() + AZ::Outcome, AZStd::string> PythonBindings::GetEngineGemInfos() { QVector gems; - bool result = ExecuteWithLock([&] { - // external gems - for (auto path : m_manifest.attr("get_gems")()) + auto result = ExecuteWithLockErrorHandling([&] { - gems.push_back(GemInfoFromPath(path)); - } + for (auto path : m_manifest.attr("get_engine_gems")()) + { + gems.push_back(GemInfoFromPath(path)); + } + }); + if (!result.IsSuccess()) + { + return AZ::Failure(result.GetError().c_str()); + } + + std::sort(gems.begin(), gems.end()); + return AZ::Success(AZStd::move(gems)); + } - // gems from the engine - for (auto path : m_manifest.attr("get_engine_gems")()) + AZ::Outcome, AZStd::string> PythonBindings::GetAllGemInfos(const QString& projectPath) + { + QVector gems; + + auto result = ExecuteWithLockErrorHandling([&] { - gems.push_back(GemInfoFromPath(path)); - } - }); + pybind11::str pyProjectPath = projectPath.toStdString(); + for (auto path : m_manifest.attr("get_all_gems")(pyProjectPath)) + { + gems.push_back(GemInfoFromPath(path)); + } + }); + if (!result.IsSuccess()) + { + return AZ::Failure(result.GetError().c_str()); + } - if (!result) + std::sort(gems.begin(), gems.end()); + return AZ::Success(AZStd::move(gems)); + } + + AZ::Outcome, AZStd::string> PythonBindings::GetEnabledGemNames(const QString& projectPath) + { + // Retrieve the path to the cmake file that lists the enabled gems. + pybind11::str enabledGemsFilename; + auto result = ExecuteWithLockErrorHandling([&] + { + const pybind11::str pyProjectPath = projectPath.toStdString(); + enabledGemsFilename = m_cmake.attr("get_enabled_gem_cmake_file")( + pybind11::none(), // project_name + pyProjectPath); // project_path + }); + if (!result.IsSuccess()) { - return AZ::Failure(); + return AZ::Failure(result.GetError().c_str()); } - else + + // Retrieve the actual list of names from the cmake file. + QVector gemNames; + result = ExecuteWithLockErrorHandling([&] + { + const auto pyGemNames = m_cmake.attr("get_enabled_gems")(enabledGemsFilename); + for (auto gemName : pyGemNames) + { + gemNames.push_back(Py_To_String(gemName)); + } + }); + if (!result.IsSuccess()) { - return AZ::Success(AZStd::move(gems)); + return AZ::Failure(result.GetError().c_str()); } + + return AZ::Success(AZStd::move(gemNames)); } bool PythonBindings::AddProject(const QString& path) @@ -670,38 +724,36 @@ namespace O3DE::ProjectManager } } - bool PythonBindings::AddGemToProject(const QString& gemPath, const QString& projectPath) + AZ::Outcome PythonBindings::AddGemToProject(const QString& gemPath, const QString& projectPath) { - bool result = ExecuteWithLock([&] { - pybind11::str pyGemPath = gemPath.toStdString(); - pybind11::str pyProjectPath = projectPath.toStdString(); - - m_enableGemProject.attr("enable_gem_in_project")( - pybind11::none(), // gem_name - pyGemPath, - pybind11::none(), // project_name - pyProjectPath - ); - }); - - return result; + return ExecuteWithLockErrorHandling([&] + { + pybind11::str pyGemPath = gemPath.toStdString(); + pybind11::str pyProjectPath = projectPath.toStdString(); + + m_enableGemProject.attr("enable_gem_in_project")( + pybind11::none(), // gem name not needed as path is provided + pyGemPath, + pybind11::none(), // project name not needed as path is provided + pyProjectPath + ); + }); } - bool PythonBindings::RemoveGemFromProject(const QString& gemPath, const QString& projectPath) + AZ::Outcome PythonBindings::RemoveGemFromProject(const QString& gemPath, const QString& projectPath) { - bool result = ExecuteWithLock([&] { - pybind11::str pyGemPath = gemPath.toStdString(); - pybind11::str pyProjectPath = projectPath.toStdString(); - - m_disableGemProject.attr("disable_gem_in_project")( - pybind11::none(), // gem_name - pyGemPath, - pybind11::none(), // project_name - pyProjectPath - ); - }); - - return result; + return ExecuteWithLockErrorHandling([&] + { + pybind11::str pyGemPath = gemPath.toStdString(); + pybind11::str pyProjectPath = projectPath.toStdString(); + + m_disableGemProject.attr("disable_gem_in_project")( + pybind11::none(), // gem name not needed as path is provided + pyGemPath, + pybind11::none(), // project name not needed as path is provided + pyProjectPath + ); + }); } bool PythonBindings::UpdateProject([[maybe_unused]] const ProjectInfo& projectInfo) diff --git a/Code/Tools/ProjectManager/Source/PythonBindings.h b/Code/Tools/ProjectManager/Source/PythonBindings.h index e751327733..278aa2d5d7 100644 --- a/Code/Tools/ProjectManager/Source/PythonBindings.h +++ b/Code/Tools/ProjectManager/Source/PythonBindings.h @@ -39,8 +39,10 @@ namespace O3DE::ProjectManager bool SetEngineInfo(const EngineInfo& engineInfo) override; // Gem - AZ::Outcome GetGem(const QString& path) override; - AZ::Outcome> GetGems() override; + AZ::Outcome GetGemInfo(const QString& path) override; + AZ::Outcome, AZStd::string> GetEngineGemInfos() override; + AZ::Outcome, AZStd::string> GetAllGemInfos(const QString& projectPath) override; + AZ::Outcome, AZStd::string> GetEnabledGemNames(const QString& projectPath) override; // Project AZ::Outcome CreateProject(const QString& projectTemplatePath, const ProjectInfo& projectInfo) override; @@ -49,8 +51,8 @@ namespace O3DE::ProjectManager bool AddProject(const QString& path) override; bool RemoveProject(const QString& path) override; bool UpdateProject(const ProjectInfo& projectInfo) override; - bool AddGemToProject(const QString& gemPath, const QString& projectPath) override; - bool RemoveGemFromProject(const QString& gemPath, const QString& projectPath) override; + AZ::Outcome AddGemToProject(const QString& gemPath, const QString& projectPath) override; + AZ::Outcome RemoveGemFromProject(const QString& gemPath, const QString& projectPath) override; // ProjectTemplate AZ::Outcome> GetProjectTemplates() override; @@ -58,6 +60,7 @@ namespace O3DE::ProjectManager private: AZ_DISABLE_COPY_MOVE(PythonBindings); + AZ::Outcome ExecuteWithLockErrorHandling(AZStd::function executionCallback); bool ExecuteWithLock(AZStd::function executionCallback); GemInfo GemInfoFromPath(pybind11::handle path); ProjectInfo ProjectInfoFromPath(pybind11::handle path); @@ -70,6 +73,7 @@ namespace O3DE::ProjectManager AZ::IO::FixedMaxPath m_enginePath; pybind11::handle m_engineTemplate; AZStd::recursive_mutex m_lock; + pybind11::handle m_cmake; pybind11::handle m_register; pybind11::handle m_manifest; pybind11::handle m_enableGemProject; diff --git a/Code/Tools/ProjectManager/Source/PythonBindingsInterface.h b/Code/Tools/ProjectManager/Source/PythonBindingsInterface.h index a58eea0fe6..09d9187dbd 100644 --- a/Code/Tools/ProjectManager/Source/PythonBindingsInterface.h +++ b/Code/Tools/ProjectManager/Source/PythonBindingsInterface.h @@ -57,13 +57,27 @@ namespace O3DE::ProjectManager * @param path the absolute path to the Gem * @return an outcome with GemInfo on success */ - virtual AZ::Outcome GetGem(const QString& path) = 0; + virtual AZ::Outcome GetGemInfo(const QString& path) = 0; /** - * Get info about all known Gems - * @return an outcome with GemInfos on success + * Get all available gem infos. This concatenates gems registered by the engine and the project. + * @param path The absolute path to the project. + * @return A list of gem infos. */ - virtual AZ::Outcome> GetGems() = 0; + virtual AZ::Outcome, AZStd::string> GetAllGemInfos(const QString& projectPath) = 0; + + /** + * Get engine gem infos. + * @return A list of all registered gem infos. + */ + virtual AZ::Outcome, AZStd::string> GetEngineGemInfos() = 0; + + /** + * Get a list of all enabled gem names for a given project. + * @param[in] projectPath Absolute file path to the project. + * @return A list of gem names of all the enabled gems for a given project or a error message on failure. + */ + virtual AZ::Outcome, AZStd::string> GetEnabledGemNames(const QString& projectPath) = 0; // Projects @@ -114,17 +128,17 @@ namespace O3DE::ProjectManager * Add a gem to a project * @param gemPath the absolute path to the gem * @param projectPath the absolute path to the project - * @return true on success, false on failure + * @return An outcome with the success flag as well as an error message in case of a failure. */ - virtual bool AddGemToProject(const QString& gemPath, const QString& projectPath) = 0; + virtual AZ::Outcome AddGemToProject(const QString& gemPath, const QString& projectPath) = 0; /** * Remove gem to a project * @param gemPath the absolute path to the gem * @param projectPath the absolute path to the project - * @return true on success, false on failure + * @return An outcome with the success flag as well as an error message in case of a failure. */ - virtual bool RemoveGemFromProject(const QString& gemPath, const QString& projectPath) = 0; + virtual AZ::Outcome RemoveGemFromProject(const QString& gemPath, const QString& projectPath) = 0; // Project Templates diff --git a/Code/Tools/ProjectManager/Source/ScreenDefs.h b/Code/Tools/ProjectManager/Source/ScreenDefs.h index 43ed303461..198f1b5d03 100644 --- a/Code/Tools/ProjectManager/Source/ScreenDefs.h +++ b/Code/Tools/ProjectManager/Source/ScreenDefs.h @@ -26,7 +26,7 @@ namespace O3DE::ProjectManager GemCatalog, Projects, UpdateProject, - ProjectSettings, + UpdateProjectSettings, EngineSettings }; @@ -37,7 +37,7 @@ namespace O3DE::ProjectManager { "GemCatalog", ProjectManagerScreen::GemCatalog}, { "Projects", ProjectManagerScreen::Projects}, { "UpdateProject", ProjectManagerScreen::UpdateProject}, - { "ProjectSettings", ProjectManagerScreen::ProjectSettings}, + { "UpdateProjectSettings", ProjectManagerScreen::UpdateProjectSettings}, { "EngineSettings", ProjectManagerScreen::EngineSettings} }; diff --git a/Code/Tools/ProjectManager/Source/ScreenFactory.cpp b/Code/Tools/ProjectManager/Source/ScreenFactory.cpp index b2b4376e14..a85f44080d 100644 --- a/Code/Tools/ProjectManager/Source/ScreenFactory.cpp +++ b/Code/Tools/ProjectManager/Source/ScreenFactory.cpp @@ -16,7 +16,7 @@ #include #include #include -#include +#include #include namespace O3DE::ProjectManager @@ -42,8 +42,8 @@ namespace O3DE::ProjectManager case (ProjectManagerScreen::UpdateProject): newScreen = new UpdateProjectCtrl(parent); break; - case (ProjectManagerScreen::ProjectSettings): - newScreen = new ProjectSettingsScreen(parent); + case (ProjectManagerScreen::UpdateProjectSettings): + newScreen = new UpdateProjectSettingsScreen(parent); break; case (ProjectManagerScreen::EngineSettings): newScreen = new EngineSettingsScreen(parent); diff --git a/Code/Tools/ProjectManager/Source/ScreensCtrl.cpp b/Code/Tools/ProjectManager/Source/ScreensCtrl.cpp index 6206d4cee9..52fcbf354a 100644 --- a/Code/Tools/ProjectManager/Source/ScreensCtrl.cpp +++ b/Code/Tools/ProjectManager/Source/ScreensCtrl.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include diff --git a/Code/Tools/ProjectManager/Source/UpdateProjectCtrl.cpp b/Code/Tools/ProjectManager/Source/UpdateProjectCtrl.cpp index b3180966ce..a383a0f93b 100644 --- a/Code/Tools/ProjectManager/Source/UpdateProjectCtrl.cpp +++ b/Code/Tools/ProjectManager/Source/UpdateProjectCtrl.cpp @@ -10,15 +10,20 @@ * */ -#include -#include +#include #include -#include +#include +#include +#include +#include +#include #include -#include -#include #include +#include +#include +#include +#include namespace O3DE::ProjectManager { @@ -26,31 +31,57 @@ namespace O3DE::ProjectManager : ScreenWidget(parent) { QVBoxLayout* vLayout = new QVBoxLayout(); - setLayout(vLayout); + vLayout->setContentsMargins(0, 0, 0, 0); + + m_header = new ScreenHeader(this); + m_header->setTitle(tr("")); + m_header->setSubTitle(tr("Edit Project Settings:")); + connect(m_header->backButton(), &QPushButton::clicked, this, &UpdateProjectCtrl::HandleBackButton); + vLayout->addWidget(m_header); + + m_updateSettingsScreen = new UpdateProjectSettingsScreen(); + m_gemCatalogScreen = new GemCatalogScreen(); + + m_stack = new QStackedWidget(this); + m_stack->setObjectName("body"); + m_stack->setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding)); + vLayout->addWidget(m_stack); + + QFrame* topBarFrameWidget = new QFrame(this); + topBarFrameWidget->setObjectName("projectSettingsTopFrame"); + QHBoxLayout* topBarHLayout = new QHBoxLayout(); + topBarHLayout->setContentsMargins(0, 0, 0, 0); + topBarFrameWidget->setLayout(topBarHLayout); + + QTabWidget* tabWidget = new QTabWidget(); + tabWidget->setObjectName("projectSettingsTab"); + tabWidget->tabBar()->setObjectName("projectSettingsTabBar"); + tabWidget->addTab(m_updateSettingsScreen, tr("General")); + + QPushButton* gemsButton = new QPushButton(tr("Add More Gems"), this); + topBarHLayout->addWidget(gemsButton); + tabWidget->setCornerWidget(gemsButton); - m_screensCtrl = new ScreensCtrl(); - vLayout->addWidget(m_screensCtrl); + topBarHLayout->addWidget(tabWidget); + + m_stack->addWidget(topBarFrameWidget); + m_stack->addWidget(m_gemCatalogScreen); QDialogButtonBox* backNextButtons = new QDialogButtonBox(); + backNextButtons->setObjectName("footer"); vLayout->addWidget(backNextButtons); m_backButton = backNextButtons->addButton(tr("Back"), QDialogButtonBox::RejectRole); + m_backButton->setProperty("secondary", true); m_nextButton = backNextButtons->addButton(tr("Next"), QDialogButtonBox::ApplyRole); - connect(m_backButton, &QPushButton::pressed, this, &UpdateProjectCtrl::HandleBackButton); - connect(m_nextButton, &QPushButton::pressed, this, &UpdateProjectCtrl::HandleNextButton); + connect(gemsButton, &QPushButton::clicked, this, &UpdateProjectCtrl::HandleGemsButton); + connect(m_backButton, &QPushButton::clicked, this, &UpdateProjectCtrl::HandleBackButton); + connect(m_nextButton, &QPushButton::clicked, this, &UpdateProjectCtrl::HandleNextButton); connect(reinterpret_cast(parent), &ScreensCtrl::NotifyCurrentProject, this, &UpdateProjectCtrl::UpdateCurrentProject); - m_screensOrder = - { - ProjectManagerScreen::ProjectSettings, - ProjectManagerScreen::GemCatalog - }; - m_screensCtrl->BuildScreens(m_screensOrder); - m_screensCtrl->ForceChangeToScreen(ProjectManagerScreen::ProjectSettings, false); - - UpdateNextButtonText(); - + Update(); + setLayout(vLayout); } ProjectManagerScreen UpdateProjectCtrl::GetScreenEnum() @@ -58,63 +89,80 @@ namespace O3DE::ProjectManager return ProjectManagerScreen::UpdateProject; } + void UpdateProjectCtrl::NotifyCurrentScreen() + { + m_stack->setCurrentIndex(ScreenOrder::Settings); + Update(); + } + + void UpdateProjectCtrl::HandleGemsButton() + { + // The next page is the gem catalog. Gather the available gems that will be shown in the gem catalog. + m_gemCatalogScreen->ReinitForProject(m_projectInfo.m_path, /*isNewProject=*/false); + + m_stack->setCurrentWidget(m_gemCatalogScreen); + Update(); + } + void UpdateProjectCtrl::HandleBackButton() { - if (!m_screensCtrl->GotoPreviousScreen()) + if (m_stack->currentIndex() > 0) { - emit GotoPreviousScreenRequest(); + m_stack->setCurrentIndex(m_stack->currentIndex() - 1); + Update(); } else { - UpdateNextButtonText(); + emit GotoPreviousScreenRequest(); } } + void UpdateProjectCtrl::HandleNextButton() { - ScreenWidget* currentScreen = m_screensCtrl->GetCurrentScreen(); - ProjectManagerScreen screenEnum = currentScreen->GetScreenEnum(); - auto screenOrderIter = m_screensOrder.begin(); - for (; screenOrderIter != m_screensOrder.end(); ++screenOrderIter) + if (m_stack->currentIndex() == ScreenOrder::Settings) { - if (*screenOrderIter == screenEnum) + if (m_updateSettingsScreen) { - ++screenOrderIter; - break; - } - } - - if (screenEnum == ProjectManagerScreen::ProjectSettings) - { - auto projectScreen = reinterpret_cast(currentScreen); - if (projectScreen) - { - if (!projectScreen->Validate()) + if (!m_updateSettingsScreen->Validate()) { QMessageBox::critical(this, tr("Invalid project settings"), tr("Invalid project settings")); return; } - m_projectInfo = projectScreen->GetProjectInfo(); + ProjectInfo newProjectSettings = m_updateSettingsScreen->GetProjectInfo(); + + // Update project if settings changed + if (m_projectInfo != newProjectSettings) + { + bool result = PythonBindingsInterface::Get()->UpdateProject(newProjectSettings); + if (!result) + { + QMessageBox::critical(this, tr("Project update failed"), tr("Failed to update project.")); + return; + } + } + + // Check if project path has changed and move it + if (newProjectSettings.m_path != m_projectInfo.m_path) + { + if (!ProjectUtils::MoveProject(m_projectInfo.m_path, newProjectSettings.m_path)) + { + QMessageBox::critical(this, tr("Project move failed"), tr("Failed to move project.")); + return; + } + } + + m_projectInfo = newProjectSettings; } } - if (screenOrderIter != m_screensOrder.end()) - { - m_screensCtrl->ChangeToScreen(*screenOrderIter); - UpdateNextButtonText(); - } - else + if (m_stack->currentIndex() == ScreenOrder::Gems && m_gemCatalogScreen) { - auto result = PythonBindingsInterface::Get()->UpdateProject(m_projectInfo); - if (result) - { - emit ChangeScreenRequest(ProjectManagerScreen::Projects); - } - else - { - QMessageBox::critical(this, tr("Project update failed"), tr("Failed to update project.")); - } + // Enable or disable the gems that got adjusted in the gem catalog and apply them to the given project. + m_gemCatalogScreen->EnableDisableGemsForProject(m_projectInfo.m_path); } + + emit ChangeScreenRequest(ProjectManagerScreen::Projects); } void UpdateProjectCtrl::UpdateCurrentProject(const QString& projectPath) @@ -124,16 +172,28 @@ namespace O3DE::ProjectManager { m_projectInfo = projectResult.GetValue(); } + + Update(); + UpdateSettingsScreen(); } - void UpdateProjectCtrl::UpdateNextButtonText() + void UpdateProjectCtrl::Update() { - QString nextButtonText = tr("Continue"); - if (m_screensCtrl->GetCurrentScreen()->GetScreenEnum() == ProjectManagerScreen::GemCatalog) + if (m_stack->currentIndex() == ScreenOrder::Gems) + { + m_header->setSubTitle(QString(tr("Add More Gems to \"%1\"")).arg(m_projectInfo.m_projectName)); + m_nextButton->setText(tr("Confirm")); + } + else { - nextButtonText = tr("Update Project"); + m_header->setSubTitle(QString(tr("Edit Project Settings: \"%1\"")).arg(m_projectInfo.m_projectName)); + m_nextButton->setText(tr("Save")); } - m_nextButton->setText(nextButtonText); + } + + void UpdateProjectCtrl::UpdateSettingsScreen() + { + m_updateSettingsScreen->SetProjectInfo(m_projectInfo); } } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/UpdateProjectCtrl.h b/Code/Tools/ProjectManager/Source/UpdateProjectCtrl.h index ee871e7bb2..231bfb8f19 100644 --- a/Code/Tools/ProjectManager/Source/UpdateProjectCtrl.h +++ b/Code/Tools/ProjectManager/Source/UpdateProjectCtrl.h @@ -12,40 +12,57 @@ #pragma once #if !defined(Q_MOC_RUN) -#include "ProjectInfo.h" +#include #include -#include -#include #endif +QT_FORWARD_DECLARE_CLASS(QStackedWidget) +QT_FORWARD_DECLARE_CLASS(QTabWidget) +QT_FORWARD_DECLARE_CLASS(QPushButton) +QT_FORWARD_DECLARE_CLASS(QFrame) namespace O3DE::ProjectManager { - class UpdateProjectCtrl - : public ScreenWidget + QT_FORWARD_DECLARE_CLASS(ScreenHeader) + QT_FORWARD_DECLARE_CLASS(UpdateProjectSettingsScreen) + QT_FORWARD_DECLARE_CLASS(GemCatalogScreen) + + class UpdateProjectCtrl : public ScreenWidget { public: explicit UpdateProjectCtrl(QWidget* parent = nullptr); ~UpdateProjectCtrl() = default; ProjectManagerScreen GetScreenEnum() override; + protected: + void NotifyCurrentScreen() override; protected slots: void HandleBackButton(); void HandleNextButton(); + void HandleGemsButton(); void UpdateCurrentProject(const QString& projectPath); private: - void UpdateNextButtonText(); + void Update(); + void UpdateSettingsScreen(); + + enum ScreenOrder + { + Settings, + Gems + }; - ScreensCtrl* m_screensCtrl; - QPushButton* m_backButton; - QPushButton* m_nextButton; + ScreenHeader* m_header = nullptr; + QStackedWidget* m_stack = nullptr; + UpdateProjectSettingsScreen* m_updateSettingsScreen = nullptr; + GemCatalogScreen* m_gemCatalogScreen = nullptr; + + QPushButton* m_backButton = nullptr; + QPushButton* m_nextButton = nullptr; QVector m_screensOrder; ProjectInfo m_projectInfo; - - ProjectManagerScreen m_screenEnum; }; } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/UpdateProjectSettingsScreen.cpp b/Code/Tools/ProjectManager/Source/UpdateProjectSettingsScreen.cpp new file mode 100644 index 0000000000..c29be3c7fd --- /dev/null +++ b/Code/Tools/ProjectManager/Source/UpdateProjectSettingsScreen.cpp @@ -0,0 +1,51 @@ +/* + * 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 + +namespace O3DE::ProjectManager +{ + UpdateProjectSettingsScreen::UpdateProjectSettingsScreen(QWidget* parent) + : ProjectSettingsScreen(parent) + { + } + + ProjectManagerScreen UpdateProjectSettingsScreen::GetScreenEnum() + { + return ProjectManagerScreen::UpdateProjectSettings; + } + + void UpdateProjectSettingsScreen::SetProjectInfo(const ProjectInfo& projectInfo) + { + m_projectName->lineEdit()->setText(projectInfo.m_projectName); + m_projectPath->lineEdit()->setText(projectInfo.m_path); + } + + bool UpdateProjectSettingsScreen::ValidateProjectPath() + { + bool projectPathIsValid = true; + if (m_projectPath->lineEdit()->text().isEmpty()) + { + projectPathIsValid = false; + m_projectPath->setErrorLabelText(tr("Please provide a valid location.")); + } + + m_projectPath->setErrorLabelVisible(!projectPathIsValid); + return projectPathIsValid; + } + +} // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/UpdateProjectSettingsScreen.h b/Code/Tools/ProjectManager/Source/UpdateProjectSettingsScreen.h new file mode 100644 index 0000000000..95bbceb9c6 --- /dev/null +++ b/Code/Tools/ProjectManager/Source/UpdateProjectSettingsScreen.h @@ -0,0 +1,34 @@ +/* + * 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 UpdateProjectSettingsScreen + : public ProjectSettingsScreen + { + public: + explicit UpdateProjectSettingsScreen(QWidget* parent = nullptr); + ~UpdateProjectSettingsScreen() = default; + ProjectManagerScreen GetScreenEnum() override; + + void SetProjectInfo(const ProjectInfo& projectInfo); + + protected: + bool ValidateProjectPath() override; + }; + +} // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/project_manager_files.cmake b/Code/Tools/ProjectManager/project_manager_files.cmake index feaea4c172..a7a36f26ab 100644 --- a/Code/Tools/ProjectManager/project_manager_files.cmake +++ b/Code/Tools/ProjectManager/project_manager_files.cmake @@ -38,6 +38,8 @@ set(FILES Source/ProjectInfo.cpp Source/ProjectUtils.h Source/ProjectUtils.cpp + Source/UpdateProjectSettingsScreen.h + Source/UpdateProjectSettingsScreen.cpp Source/NewProjectSettingsScreen.h Source/NewProjectSettingsScreen.cpp Source/CreateProjectCtrl.h @@ -48,7 +50,6 @@ set(FILES Source/ProjectsScreen.cpp Source/ProjectSettingsScreen.h Source/ProjectSettingsScreen.cpp - Source/ProjectSettingsScreen.ui Source/EngineSettingsScreen.h Source/EngineSettingsScreen.cpp Source/ProjectButtonWidget.h diff --git a/Code/Tools/SceneAPI/SDKWrapper/AssImpSceneWrapper.cpp b/Code/Tools/SceneAPI/SDKWrapper/AssImpSceneWrapper.cpp index 791af4bf68..336cc1b172 100644 --- a/Code/Tools/SceneAPI/SDKWrapper/AssImpSceneWrapper.cpp +++ b/Code/Tools/SceneAPI/SDKWrapper/AssImpSceneWrapper.cpp @@ -42,7 +42,7 @@ namespace AZ } #if AZ_TRAIT_COMPILER_SUPPORT_CSIGNAL - void signal_handler(int signal) + void signal_handler([[maybe_unused]] int signal) { AZ_TracePrintf( SceneAPI::Utilities::ErrorWindow, diff --git a/Gems/AWSClientAuth/Code/CMakeLists.txt b/Gems/AWSClientAuth/Code/CMakeLists.txt index 40f6e0fe36..3f85b6453e 100644 --- a/Gems/AWSClientAuth/Code/CMakeLists.txt +++ b/Gems/AWSClientAuth/Code/CMakeLists.txt @@ -56,9 +56,13 @@ ly_add_target( Gem::HttpRequestor ) -# servers and clients use the above module. +# Load the "Gem::AWSClientAuth" module in all types of applications. ly_create_alias(NAME AWSClientAuth.Servers NAMESPACE Gem TARGETS Gem::AWSClientAuth) ly_create_alias(NAME AWSClientAuth.Clients NAMESPACE Gem TARGETS Gem::AWSClientAuth) +if (PAL_TRAIT_BUILD_HOST_TOOLS) + ly_create_alias(NAME AWSClientAuth.Tools NAMESPACE Gem TARGETS Gem::AWSClientAuth) + ly_create_alias(NAME AWSClientAuth.Builders NAMESPACE Gem TARGETS Gem::AWSClientAuth) +endif() ################################################################################ # Tests diff --git a/Gems/AWSCore/Code/CMakeLists.txt b/Gems/AWSCore/Code/CMakeLists.txt index 46046c0791..7edb22124f 100644 --- a/Gems/AWSCore/Code/CMakeLists.txt +++ b/Gems/AWSCore/Code/CMakeLists.txt @@ -79,14 +79,12 @@ if (PAL_TRAIT_BUILD_HOST_TOOLS) INCLUDE_DIRECTORIES PRIVATE Include/Private - COMPILE_DEFINITIONS - PRIVATE - AWSCORE_EDITOR BUILD_DEPENDENCIES PRIVATE AZ::AzCore - Gem::AWSCore.Static Gem::AWSCore.Editor.Static + RUNTIME_DEPENDENCIES + Gem::AWSCore ) ly_add_target( diff --git a/Gems/AWSCore/Code/Include/Private/AWSCoreEditorModule.h b/Gems/AWSCore/Code/Include/Private/AWSCoreEditorModule.h index 91a1af00d9..45a2c1f9f7 100644 --- a/Gems/AWSCore/Code/Include/Private/AWSCoreEditorModule.h +++ b/Gems/AWSCore/Code/Include/Private/AWSCoreEditorModule.h @@ -11,15 +11,15 @@ #pragma once -#include +#include namespace AWSCore { class AWSCoreEditorModule - : public AWSCoreModule + :public AZ::Module { public: - AZ_RTTI(AWSCoreEditorModule, "{C1C9B898-848B-4C2F-A7AA-69642D12BCB5}", AWSCoreModule); + AZ_RTTI(AWSCoreEditorModule, "{C1C9B898-848B-4C2F-A7AA-69642D12BCB5}", AZ::Module); AZ_CLASS_ALLOCATOR(AWSCoreEditorModule, AZ::SystemAllocator, 0); AWSCoreEditorModule(); diff --git a/Gems/AWSCore/Code/Source/AWSCoreEditorModule.cpp b/Gems/AWSCore/Code/Source/AWSCoreEditorModule.cpp index d8df1695c2..69e45bfd68 100644 --- a/Gems/AWSCore/Code/Source/AWSCoreEditorModule.cpp +++ b/Gems/AWSCore/Code/Source/AWSCoreEditorModule.cpp @@ -15,7 +15,6 @@ namespace AWSCore { AWSCoreEditorModule::AWSCoreEditorModule() - : AWSCoreModule() { // Push results of [MyComponent]::CreateDescriptor() into m_descriptors here. m_descriptors.insert(m_descriptors.end(), { @@ -28,10 +27,9 @@ namespace AWSCore */ AZ::ComponentTypeList AWSCoreEditorModule::GetRequiredSystemComponents() const { - AZ::ComponentTypeList requiredComponents = AWSCoreModule::GetRequiredSystemComponents(); - requiredComponents.push_back(azrtti_typeid()); - - return requiredComponents; + return AZ::ComponentTypeList{ + azrtti_typeid() + }; } } diff --git a/Gems/AWSCore/Code/Source/AWSCoreModule.cpp b/Gems/AWSCore/Code/Source/AWSCoreModule.cpp index 19a3ad4383..a80ca62b89 100644 --- a/Gems/AWSCore/Code/Source/AWSCoreModule.cpp +++ b/Gems/AWSCore/Code/Source/AWSCoreModule.cpp @@ -40,9 +40,7 @@ namespace AWSCore } -#if !defined(AWSCORE_EDITOR) // DO NOT MODIFY THIS LINE UNLESS YOU RENAME THE GEM // The first parameter should be GemName_GemIdLower // The second should be the fully qualified name of the class above AZ_DECLARE_MODULE_CLASS(Gem_AWSCore, AWSCore::AWSCoreModule) -#endif diff --git a/Gems/AWSCore/Code/awscore_editor_shared_files.cmake b/Gems/AWSCore/Code/awscore_editor_shared_files.cmake index 61ff9c3bf2..42cebe8dc4 100644 --- a/Gems/AWSCore/Code/awscore_editor_shared_files.cmake +++ b/Gems/AWSCore/Code/awscore_editor_shared_files.cmake @@ -11,7 +11,5 @@ set(FILES Include/Private/AWSCoreEditorModule.h - Include/Private/AWSCoreModule.h Source/AWSCoreEditorModule.cpp - Source/AWSCoreModule.cpp ) diff --git a/Gems/AWSMetrics/Code/CMakeLists.txt b/Gems/AWSMetrics/Code/CMakeLists.txt index aa790371d2..3e7118cb58 100644 --- a/Gems/AWSMetrics/Code/CMakeLists.txt +++ b/Gems/AWSMetrics/Code/CMakeLists.txt @@ -46,9 +46,13 @@ ly_add_target( Gem::AWSCore ) -# Servers and Clients use the above metrics module +# Load the "Gem::AWSMetrics" module in all types of applications. ly_create_alias(NAME AWSMetrics.Servers NAMESPACE Gem TARGETS Gem::AWSMetrics) ly_create_alias(NAME AWSMetrics.Clients NAMESPACE Gem TARGETS Gem::AWSMetrics) +if (PAL_TRAIT_BUILD_HOST_TOOLS) + ly_create_alias(NAME AWSMetrics.Tools NAMESPACE Gem TARGETS Gem::AWSMetrics) + ly_create_alias(NAME AWSMetrics.Builders NAMESPACE Gem TARGETS Gem::AWSMetrics) +endif() ################################################################################ # Tests diff --git a/Gems/Atom/Feature/Common/Code/Source/Material/MaterialAssignment.cpp b/Gems/Atom/Feature/Common/Code/Source/Material/MaterialAssignment.cpp index ffb5469aef..029a81ec49 100644 --- a/Gems/Atom/Feature/Common/Code/Source/Material/MaterialAssignment.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/Material/MaterialAssignment.cpp @@ -12,7 +12,9 @@ #include #include +#include #include +#include namespace AZ { @@ -22,6 +24,11 @@ namespace AZ { MaterialAssignmentId::Reflect(context); + if (auto jsonContext = azrtti_cast(context)) + { + jsonContext->Serializer()->HandlesType(); + } + if (auto serializeContext = azrtti_cast(context)) { serializeContext->RegisterGenericType(); diff --git a/Gems/Atom/Feature/Common/Code/Source/Material/MaterialAssignmentSerializer.cpp b/Gems/Atom/Feature/Common/Code/Source/Material/MaterialAssignmentSerializer.cpp new file mode 100644 index 0000000000..a895c04b95 --- /dev/null +++ b/Gems/Atom/Feature/Common/Code/Source/Material/MaterialAssignmentSerializer.cpp @@ -0,0 +1,214 @@ +/* + * 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 + +namespace AZ +{ + namespace Render + { + AZ_CLASS_ALLOCATOR_IMPL(JsonMaterialAssignmentSerializer, AZ::SystemAllocator, 0); + + JsonSerializationResult::Result JsonMaterialAssignmentSerializer::Load( + void* outputValue, [[maybe_unused]] const Uuid& outputValueTypeId, const rapidjson::Value& inputValue, + JsonDeserializerContext& context) + { + namespace JSR = JsonSerializationResult; + + AZ_Assert( + azrtti_typeid() == outputValueTypeId, + "Unable to deserialize MaterialAssignment from json because the provided type is %s.", + outputValueTypeId.ToString().c_str()); + + AZ::Render::MaterialAssignment* materialAssignment = reinterpret_cast(outputValue); + AZ_Assert(materialAssignment, "Output value for JsonMaterialAssignmentSerializer can't be null."); + + JSR::ResultCode result(JSR::Tasks::ReadField); + { + result.Combine(ContinueLoadingFromJsonObjectField( + &materialAssignment->m_materialAsset, azrtti_typeidm_materialAsset)>(), inputValue, + "MaterialAsset", context)); + } + + if (inputValue.HasMember("PropertyOverrides") && inputValue["PropertyOverrides"].IsObject()) + { + // Attempt to load material property override values for a subset of types + for (const auto& inputPropertyPair : inputValue["PropertyOverrides"].GetObject()) + { + const AZ::Name propertyName(inputPropertyPair.name.GetString()); + if (!propertyName.IsEmpty()) + { + AZStd::any propertyValue; + if (LoadAny(propertyValue, inputPropertyPair.value, context, result) || + LoadAny(propertyValue, inputPropertyPair.value, context, result) || + LoadAny(propertyValue, inputPropertyPair.value, context, result) || + LoadAny(propertyValue, inputPropertyPair.value, context, result) || + LoadAny(propertyValue, inputPropertyPair.value, context, result) || + LoadAny(propertyValue, inputPropertyPair.value, context, result) || + LoadAny(propertyValue, inputPropertyPair.value, context, result) || + LoadAny(propertyValue, inputPropertyPair.value, context, result) || + LoadAny(propertyValue, inputPropertyPair.value, context, result) || + LoadAny(propertyValue, inputPropertyPair.value, context, result) || + LoadAny(propertyValue, inputPropertyPair.value, context, result) || + LoadAny(propertyValue, inputPropertyPair.value, context, result) || + LoadAny(propertyValue, inputPropertyPair.value, context, result) || + LoadAny(propertyValue, inputPropertyPair.value, context, result) || + LoadAny(propertyValue, inputPropertyPair.value, context, result) || + LoadAny(propertyValue, inputPropertyPair.value, context, result) || + LoadAny(propertyValue, inputPropertyPair.value, context, result) || + LoadAny>(propertyValue, inputPropertyPair.value, context, result) || + LoadAny>(propertyValue, inputPropertyPair.value, context, result)) + { + materialAssignment->m_propertyOverrides[propertyName] = propertyValue; + } + } + } + } + + return context.Report( + result, + result.GetProcessing() != JSR::Processing::Halted ? "Succesfully loaded MaterialAssignment information." + : "Failed to load MaterialAssignment information."); + } + + JsonSerializationResult::Result JsonMaterialAssignmentSerializer::Store( + rapidjson::Value& outputValue, const void* inputValue, const void* defaultValue, [[maybe_unused]] const Uuid& valueTypeId, + JsonSerializerContext& context) + { + namespace JSR = AZ::JsonSerializationResult; + + AZ_Assert( + azrtti_typeid() == valueTypeId, + "Unable to Serialize MaterialAssignment because the provided type is %s.", valueTypeId.ToString().c_str()); + + const AZ::Render::MaterialAssignment* materialAssignment = reinterpret_cast(inputValue); + AZ_Assert(materialAssignment, "Input value for JsonMaterialAssignmentSerializer can't be null."); + const AZ::Render::MaterialAssignment* defaultMaterialAssignmentInstance = + reinterpret_cast(defaultValue); + + outputValue.SetObject(); + + JSR::ResultCode result(JSR::Tasks::WriteValue); + { + AZ::ScopedContextPath subPathMaterialAsset(context, "m_materialAsset"); + const AZ::Data::Asset* materialAsset = &materialAssignment->m_materialAsset; + const AZ::Data::Asset* defaultmaterialAsset = + defaultMaterialAssignmentInstance ? &defaultMaterialAssignmentInstance->m_materialAsset : nullptr; + + result.Combine(ContinueStoringToJsonObjectField( + outputValue, "MaterialAsset", materialAsset, defaultmaterialAsset, + azrtti_typeidm_materialAsset)>(), context)); + } + + { + AZ::ScopedContextPath subPathPropertyOverrides(context, "m_propertyOverrides"); + if (!materialAssignment->m_propertyOverrides.empty()) + { + rapidjson::Value outputPropertyValueContainer; + outputPropertyValueContainer.SetObject(); + + // Attempt to extract and store material property override values for a subset of types + for (const auto& propertyPair : materialAssignment->m_propertyOverrides) + { + const AZ::Name& propertyName = propertyPair.first; + const AZStd::any& propertyValue = propertyPair.second; + if (!propertyName.IsEmpty() && !propertyValue.empty()) + { + rapidjson::Value outputPropertyValue; + if (StoreAny(propertyValue, outputPropertyValue, context, result) || + StoreAny(propertyValue, outputPropertyValue, context, result) || + StoreAny(propertyValue, outputPropertyValue, context, result) || + StoreAny(propertyValue, outputPropertyValue, context, result) || + StoreAny(propertyValue, outputPropertyValue, context, result) || + StoreAny(propertyValue, outputPropertyValue, context, result) || + StoreAny(propertyValue, outputPropertyValue, context, result) || + StoreAny(propertyValue, outputPropertyValue, context, result) || + StoreAny(propertyValue, outputPropertyValue, context, result) || + StoreAny(propertyValue, outputPropertyValue, context, result) || + StoreAny(propertyValue, outputPropertyValue, context, result) || + StoreAny(propertyValue, outputPropertyValue, context, result) || + StoreAny(propertyValue, outputPropertyValue, context, result) || + StoreAny(propertyValue, outputPropertyValue, context, result) || + StoreAny(propertyValue, outputPropertyValue, context, result) || + StoreAny(propertyValue, outputPropertyValue, context, result) || + StoreAny(propertyValue, outputPropertyValue, context, result) || + StoreAny>(propertyValue, outputPropertyValue, context, result) || + StoreAny>( + propertyValue, outputPropertyValue, context, result)) + { + outputPropertyValueContainer.AddMember( + rapidjson::Value::StringRefType(propertyName.GetCStr()), outputPropertyValue, + context.GetJsonAllocator()); + } + } + } + + if (outputPropertyValueContainer.MemberCount() > 0) + { + outputValue.AddMember("PropertyOverrides", outputPropertyValueContainer, context.GetJsonAllocator()); + } + } + } + + return context.Report( + result, + result.GetProcessing() != JSR::Processing::Halted ? "Successfully stored MaterialAssignment information." + : "Failed to store MaterialAssignment information."); + } + + template + bool JsonMaterialAssignmentSerializer::LoadAny( + AZStd::any& propertyValue, const rapidjson::Value& inputPropertyValue, AZ::JsonDeserializerContext& context, + AZ::JsonSerializationResult::ResultCode& result) + { + if (inputPropertyValue.IsObject() && inputPropertyValue.HasMember("Value") && inputPropertyValue.HasMember("$type")) + { + // Requiring explicit type info to differentiate be=tween colors versus vectors and numeric types + const AZ::Uuid baseTypeId = azrtti_typeid(); + AZ::Uuid typeId = AZ::Uuid::CreateNull(); + result.Combine(LoadTypeId(typeId, inputPropertyValue, context, &baseTypeId)); + + if (typeId == azrtti_typeid()) + { + T value = {}; + result.Combine(ContinueLoadingFromJsonObjectField(&value, azrtti_typeid(), inputPropertyValue, "Value", context)); + propertyValue = value; + return true; + } + } + return false; + } + + template + bool JsonMaterialAssignmentSerializer::StoreAny( + const AZStd::any& propertyValue, rapidjson::Value& outputPropertyValue, AZ::JsonSerializerContext& context, + AZ::JsonSerializationResult::ResultCode& result) + { + if (propertyValue.is()) + { + outputPropertyValue.SetObject(); + + // Storing explicit type info to differentiate be=tween colors versus vectors and numeric types + rapidjson::Value typeValue; + result.Combine(StoreTypeId(typeValue, azrtti_typeid(), context)); + outputPropertyValue.AddMember("$type", typeValue, context.GetJsonAllocator()); + + T value = AZStd::any_cast(propertyValue); + result.Combine( + ContinueStoringToJsonObjectField(outputPropertyValue, "Value", &value, nullptr, azrtti_typeid(), context)); + return true; + } + return false; + } + } // namespace Render +} // namespace AZ diff --git a/Gems/Atom/Feature/Common/Code/Source/Material/MaterialAssignmentSerializer.h b/Gems/Atom/Feature/Common/Code/Source/Material/MaterialAssignmentSerializer.h new file mode 100644 index 0000000000..14db019668 --- /dev/null +++ b/Gems/Atom/Feature/Common/Code/Source/Material/MaterialAssignmentSerializer.h @@ -0,0 +1,50 @@ +/* + * 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 + +#include +#include +#include + +namespace AZ +{ + namespace Render + { + // Custom JSON serializer for material assignment objects containing AZStd::any property overrides, + // which aren't supported by the system + class JsonMaterialAssignmentSerializer : public BaseJsonSerializer + { + public: + AZ_RTTI(JsonMaterialAssignmentSerializer, "{3D33653E-4582-483F-91F5-BBCC347C3DF0}", BaseJsonSerializer); + AZ_CLASS_ALLOCATOR_DECL; + + JsonSerializationResult::Result Load( + void* outputValue, const Uuid& outputValueTypeId, const rapidjson::Value& inputValue, + JsonDeserializerContext& context) override; + + JsonSerializationResult::Result Store( + rapidjson::Value& outputValue, const void* inputValue, const void* defaultValue, const Uuid& valueTypeId, + JsonSerializerContext& context) override; + + private: + template + bool LoadAny( + AZStd::any& propertyValue, const rapidjson::Value& inputPropertyValue, AZ::JsonDeserializerContext& context, + AZ::JsonSerializationResult::ResultCode& result); + template + bool StoreAny( + const AZStd::any& propertyValue, rapidjson::Value& outputPropertyValue, AZ::JsonSerializerContext& context, + AZ::JsonSerializationResult::ResultCode& result); + }; + } // namespace Render +} // namespace AZ diff --git a/Gems/Atom/Feature/Common/Code/atom_feature_common_staticlibrary_files.cmake b/Gems/Atom/Feature/Common/Code/atom_feature_common_staticlibrary_files.cmake index 553f307409..285659ab82 100644 --- a/Gems/Atom/Feature/Common/Code/atom_feature_common_staticlibrary_files.cmake +++ b/Gems/Atom/Feature/Common/Code/atom_feature_common_staticlibrary_files.cmake @@ -16,6 +16,8 @@ set(FILES Include/Atom/Feature/Utils/ModelPreset.h Source/Material/MaterialAssignment.cpp Source/Material/MaterialAssignmentId.cpp + Source/Material/MaterialAssignmentSerializer.cpp + Source/Material/MaterialAssignmentSerializer.h Source/Utils/LightingPreset.cpp Source/Utils/ModelPreset.cpp ) diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentSlot.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentSlot.cpp index 7ebf25454d..bdc82acda6 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentSlot.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentSlot.cpp @@ -78,11 +78,9 @@ namespace AZ if (AZ::SerializeContext* serializeContext = azrtti_cast(context)) { serializeContext->Class() - ->Version(4, &EditorMaterialComponentSlot::ConvertVersion) + ->Version(5, &EditorMaterialComponentSlot::ConvertVersion) ->Field("id", &EditorMaterialComponentSlot::m_id) ->Field("materialAsset", &EditorMaterialComponentSlot::m_materialAsset) - ->Field("propertyOverrides", &EditorMaterialComponentSlot::m_propertyOverrides) - ->Field("matModUvOverrides", &EditorMaterialComponentSlot::m_matModUvOverrides) ; if (AZ::EditContext* editContext = serializeContext->GetEditContext()) diff --git a/cmake/Projects.cmake b/cmake/Projects.cmake index 297dad4ddf..adaf7ee15f 100644 --- a/cmake/Projects.cmake +++ b/cmake/Projects.cmake @@ -167,6 +167,9 @@ endfunction() # Add the projects here so the above function is found foreach(project ${LY_PROJECTS}) file(REAL_PATH ${project} full_directory_path BASE_DIRECTORY ${CMAKE_SOURCE_DIR}) + if(NOT LY_FIRST_PROJECT) + ly_set(LY_FIRST_PROJECT_PATH ${full_directory_path}) + endif() string(SHA256 full_directory_hash ${full_directory_path}) # Truncate the full_directory_hash down to 8 characters to avoid hitting the Windows 260 character path limit