Merge branch 'main' into ftue_auto_register

main
scottr 5 years ago
commit c41283c3ec

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

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

@ -25,7 +25,6 @@ ly_add_target(
OUTPUT_NAME o3de
NAMESPACE AZ
AUTOMOC
AUTOUIC
AUTORCC
FILES_CMAKE
project_manager_files.cmake

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

@ -15,7 +15,6 @@
#include <PythonBindingsInterface.h>
#include <NewProjectSettingsScreen.h>
#include <ScreenHeaderWidget.h>
#include <GemCatalog/GemCatalogScreen.h>
#include <QDialogButtonBox>
#include <QHBoxLayout>
@ -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<ScreenWidget*>(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);
}
}

@ -14,6 +14,7 @@
#if !defined(Q_MOC_RUN)
#include <ScreenWidget.h>
#include <ProjectInfo.h>
#include <GemCatalog/GemCatalogScreen.h>
#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

@ -15,13 +15,12 @@
#include <GemCatalog/GemCatalogHeaderWidget.h>
#include <GemCatalog/GemListHeaderWidget.h>
#include <GemCatalog/GemSortFilterProxyModel.h>
#include <GemCatalog/GemFilterWidget.h>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QPushButton>
#include <QTimer>
//#define USE_TESTGEMDATA
#include <PythonBindingsInterface.h>
#include <QMessageBox>
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);
m_gemInspector->setFixedWidth(240);
// Start: Temporary gem test data
#ifdef USE_TESTGEMDATA
QVector<GemInfo> 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
QWidget* filterWidget = new QWidget(this);
filterWidget->setFixedWidth(240);
m_filterWidgetLayout = new QVBoxLayout();
m_filterWidgetLayout->setMargin(0);
m_filterWidgetLayout->setSpacing(0);
filterWidget->setLayout(m_filterWidgetLayout);
GemFilterWidget* filterWidget = new GemFilterWidget(proxyModel);
filterWidget->setFixedWidth(250);
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<QVector<GemInfo>, 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<GemInfo> allGemInfos = allGemInfosResult.GetValue();
for (const GemInfo& gemInfo : allGemInfos)
{
m_gemModel->AddGem(gemInfo);
}
proxyModel->InvalidateFilter();
}
QVector<GemInfo> GemCatalogScreen::GenerateTestData()
{
QVector<GemInfo> 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;
// Gather enabled gems for the given project.
auto enabledGemNamesResult = PythonBindingsInterface::Get()->GetEnabledGemNames(projectPath);
if (enabledGemNamesResult.IsSuccess())
{
const QVector<AZStd::string> 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()));
}
}
void GemCatalogScreen::EnableDisableGemsForProject(const QString& projectPath)
{
IPythonBindings* pythonBindings = PythonBindingsInterface::Get();
QVector<QModelIndex> toBeAdded = m_gemModel->GatherGemsToBeAdded();
QVector<QModelIndex> toBeRemoved = m_gemModel->GatherGemsToBeRemoved();
for (const QModelIndex& modelIndex : toBeAdded)
{
const QString gemPath = GemModel::GetPath(modelIndex);
const AZ::Outcome<void, AZStd::string> 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<void, AZStd::string> 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()

@ -14,9 +14,11 @@
#if !defined(Q_MOC_RUN)
#include <ScreenWidget.h>
#include <GemCatalog/GemFilterWidget.h>
#include <GemCatalog/GemListView.h>
#include <GemCatalog/GemInspector.h>
#include <GemCatalog/GemModel.h>
#include <GemCatalog/GemSortFilterProxyModel.h>
#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<GemInfo> 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

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

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

@ -34,33 +34,13 @@ namespace O3DE::ProjectManager
constexpr const char* k_pathProperty = "Path";
NewProjectSettingsScreen::NewProjectSettingsScreen(QWidget* parent)
: ScreenWidget(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);
: ProjectSettingsScreen(parent)
{
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);
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
@ -87,7 +67,7 @@ namespace O3DE::ProjectManager
auto templatesResult = PythonBindingsInterface::Get()->GetProjectTemplates();
if (templatesResult.IsSuccess() && !templatesResult.GetValue().isEmpty())
{
for (auto projectTemplate : templatesResult.GetValue())
for (const ProjectTemplateInfo& projectTemplate : templatesResult.GetValue())
{
QRadioButton* radioButton = new QRadioButton(projectTemplate.m_name, this);
radioButton->setProperty(k_pathProperty, projectTemplate.m_path);
@ -100,17 +80,11 @@ namespace O3DE::ProjectManager
}
}
projectTemplateWidget->setLayout(containerLayout);
vLayout->addWidget(projectTemplateWidget);
}
projectSettingsFrame->setLayout(vLayout);
hLayout->addWidget(projectSettingsFrame);
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

@ -12,41 +12,28 @@
#pragma once
#if !defined(Q_MOC_RUN)
#include <ScreenWidget.h>
#include <ProjectInfo.h>
#include <ProjectSettingsScreen.h>
#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;
};

@ -22,8 +22,6 @@
#include <QMenu>
#include <QSpacerItem>
//#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)

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

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

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

@ -11,45 +11,131 @@
*/
#include <ProjectSettingsScreen.h>
#include <FormBrowseEditWidget.h>
#include <FormLineEditWidget.h>
#include <PathValidator.h>
#include <PythonBindingsInterface.h>
#include <Source/ui_ProjectSettingsScreen.h>
#include <QFileDialog>
#include <QFrame>
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QLabel>
#include <QLineEdit>
#include <QStandardPaths>
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()
{
QString defaultPath = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation);
AZ::Outcome<EngineInfo> engineInfoResult = PythonBindingsInterface::Get()->GetEngineInfo();
if (engineInfoResult.IsSuccess())
{
// Impl pending next PR
return ProjectInfo();
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()
{
bool projectNameIsValid = true;
if (m_projectName->lineEdit()->text().isEmpty())
{
// Impl pending next PR
return true;
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()
{
bool projectPathIsValid = true;
if (m_projectPath->lineEdit()->text().isEmpty())
{
projectPathIsValid = false;
m_projectPath->setErrorLabelText(tr("Please provide a valid location."));
}
else
{
emit ChangeScreenRequest(ProjectManagerScreen::GemCatalog);
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

@ -12,17 +12,18 @@
#pragma once
#if !defined(Q_MOC_RUN)
#include <ScreenWidget.h>
#include <ProjectInfo.h>
#include <ScreenWidget.h>
#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<Ui::ProjectSettingsClass> m_ui;
QHBoxLayout* m_horizontalLayout;
QVBoxLayout* m_verticalLayout;
FormLineEditWidget* m_projectName;
FormBrowseEditWidget* m_projectPath;
};
} // namespace O3DE::ProjectManager

@ -1,113 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ProjectSettingsClass</class>
<widget class="QWidget" name="ProjectSettingsClass">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>782</width>
<height>579</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QPushButton" name="projectSettingsButton">
<property name="text">
<string>Project Settings</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="gemsButton">
<property name="text">
<string>Gems</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>761</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Project Name</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="lineEdit"/>
</item>
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>Project Location</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="lineEdit_2"/>
</item>
<item>
<widget class="QLabel" name="label_3">
<property name="text">
<string>Project Image Location</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="lineEdit_3"/>
</item>
<item>
<widget class="QLabel" name="label_4">
<property name="text">
<string>Project Background Image Location</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="lineEdit_4"/>
</item>
</layout>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

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

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

@ -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<void()> executionCallback)
AZ::Outcome<void, AZStd::string> PythonBindings::ExecuteWithLockErrorHandling(AZStd::function<void()> executionCallback)
{
AZStd::lock_guard<decltype(m_lock)> 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<AZStd::string>(e.what());
}
return AZ::Success();
}
bool PythonBindings::ExecuteWithLock(AZStd::function<void()> executionCallback)
{
return ExecuteWithLockErrorHandling(executionCallback).IsSuccess();
}
AZ::Outcome<EngineInfo> PythonBindings::GetEngineInfo()
@ -452,7 +459,7 @@ namespace O3DE::ProjectManager
return result;
}
AZ::Outcome<GemInfo> PythonBindings::GetGem(const QString& path)
AZ::Outcome<GemInfo> PythonBindings::GetGemInfo(const QString& path)
{
GemInfo gemInfo = GemInfoFromPath(pybind11::str(path.toStdString()));
if (gemInfo.IsValid())
@ -465,32 +472,79 @@ namespace O3DE::ProjectManager
}
}
AZ::Outcome<QVector<GemInfo>> PythonBindings::GetGems()
AZ::Outcome<QVector<GemInfo>, AZStd::string> PythonBindings::GetEngineGemInfos()
{
QVector<GemInfo> gems;
bool result = ExecuteWithLock([&] {
// external gems
for (auto path : m_manifest.attr("get_gems")())
auto result = ExecuteWithLockErrorHandling([&]
{
for (auto path : m_manifest.attr("get_engine_gems")())
{
gems.push_back(GemInfoFromPath(path));
}
});
if (!result.IsSuccess())
{
return AZ::Failure<AZStd::string>(result.GetError().c_str());
}
// gems from the engine
for (auto path : m_manifest.attr("get_engine_gems")())
std::sort(gems.begin(), gems.end());
return AZ::Success(AZStd::move(gems));
}
AZ::Outcome<QVector<GemInfo>, AZStd::string> PythonBindings::GetAllGemInfos(const QString& projectPath)
{
QVector<GemInfo> gems;
auto result = ExecuteWithLockErrorHandling([&]
{
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<AZStd::string>(result.GetError().c_str());
}
if (!result)
std::sort(gems.begin(), gems.end());
return AZ::Success(AZStd::move(gems));
}
AZ::Outcome<QVector<AZStd::string>, AZStd::string> PythonBindings::GetEnabledGemNames(const QString& projectPath)
{
return AZ::Failure();
// 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<AZStd::string>(result.GetError().c_str());
}
else
// Retrieve the actual list of names from the cmake file.
QVector<AZStd::string> gemNames;
result = ExecuteWithLockErrorHandling([&]
{
return AZ::Success(AZStd::move(gems));
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::Failure<AZStd::string>(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<void, AZStd::string> PythonBindings::AddGemToProject(const QString& gemPath, const QString& projectPath)
{
return ExecuteWithLockErrorHandling([&]
{
bool result = ExecuteWithLock([&] {
pybind11::str pyGemPath = gemPath.toStdString();
pybind11::str pyProjectPath = projectPath.toStdString();
m_enableGemProject.attr("enable_gem_in_project")(
pybind11::none(), // gem_name
pybind11::none(), // gem name not needed as path is provided
pyGemPath,
pybind11::none(), // project_name
pybind11::none(), // project name not needed as path is provided
pyProjectPath
);
});
return result;
}
bool PythonBindings::RemoveGemFromProject(const QString& gemPath, const QString& projectPath)
AZ::Outcome<void, AZStd::string> PythonBindings::RemoveGemFromProject(const QString& gemPath, const QString& projectPath)
{
return ExecuteWithLockErrorHandling([&]
{
bool result = ExecuteWithLock([&] {
pybind11::str pyGemPath = gemPath.toStdString();
pybind11::str pyProjectPath = projectPath.toStdString();
m_disableGemProject.attr("disable_gem_in_project")(
pybind11::none(), // gem_name
pybind11::none(), // gem name not needed as path is provided
pyGemPath,
pybind11::none(), // project_name
pybind11::none(), // project name not needed as path is provided
pyProjectPath
);
});
return result;
}
bool PythonBindings::UpdateProject([[maybe_unused]] const ProjectInfo& projectInfo)

@ -39,8 +39,10 @@ namespace O3DE::ProjectManager
bool SetEngineInfo(const EngineInfo& engineInfo) override;
// Gem
AZ::Outcome<GemInfo> GetGem(const QString& path) override;
AZ::Outcome<QVector<GemInfo>> GetGems() override;
AZ::Outcome<GemInfo> GetGemInfo(const QString& path) override;
AZ::Outcome<QVector<GemInfo>, AZStd::string> GetEngineGemInfos() override;
AZ::Outcome<QVector<GemInfo>, AZStd::string> GetAllGemInfos(const QString& projectPath) override;
AZ::Outcome<QVector<AZStd::string>, AZStd::string> GetEnabledGemNames(const QString& projectPath) override;
// Project
AZ::Outcome<ProjectInfo> 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<void, AZStd::string> AddGemToProject(const QString& gemPath, const QString& projectPath) override;
AZ::Outcome<void, AZStd::string> RemoveGemFromProject(const QString& gemPath, const QString& projectPath) override;
// ProjectTemplate
AZ::Outcome<QVector<ProjectTemplateInfo>> GetProjectTemplates() override;
@ -58,6 +60,7 @@ namespace O3DE::ProjectManager
private:
AZ_DISABLE_COPY_MOVE(PythonBindings);
AZ::Outcome<void, AZStd::string> ExecuteWithLockErrorHandling(AZStd::function<void()> executionCallback);
bool ExecuteWithLock(AZStd::function<void()> 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;

@ -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<GemInfo> GetGem(const QString& path) = 0;
virtual AZ::Outcome<GemInfo> 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<QVector<GemInfo>> GetGems() = 0;
virtual AZ::Outcome<QVector<GemInfo>, AZStd::string> GetAllGemInfos(const QString& projectPath) = 0;
/**
* Get engine gem infos.
* @return A list of all registered gem infos.
*/
virtual AZ::Outcome<QVector<GemInfo>, 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<QVector<AZStd::string>, 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<void, AZStd::string> 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<void, AZStd::string> RemoveGemFromProject(const QString& gemPath, const QString& projectPath) = 0;
// Project Templates

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

@ -16,7 +16,7 @@
#include <NewProjectSettingsScreen.h>
#include <GemCatalog/GemCatalogScreen.h>
#include <ProjectsScreen.h>
#include <ProjectSettingsScreen.h>
#include <UpdateProjectSettingsScreen.h>
#include <EngineSettingsScreen.h>
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);

@ -13,6 +13,7 @@
#include <ScreensCtrl.h>
#include <ScreenFactory.h>
#include <ScreenWidget.h>
#include <UpdateProjectCtrl.h>
#include <QTabWidget>
#include <QVBoxLayout>

@ -10,15 +10,20 @@
*
*/
#include <UpdateProjectCtrl.h>
#include <ScreensCtrl.h>
#include <GemCatalog/GemCatalogScreen.h>
#include <PythonBindingsInterface.h>
#include <ProjectSettingsScreen.h>
#include <ScreenHeaderWidget.h>
#include <ScreensCtrl.h>
#include <UpdateProjectCtrl.h>
#include <UpdateProjectSettingsScreen.h>
#include <ProjectUtils.h>
#include <QDialogButtonBox>
#include <QVBoxLayout>
#include <QPushButton>
#include <QMessageBox>
#include <QPushButton>
#include <QStackedWidget>
#include <QTabWidget>
#include <QVBoxLayout>
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<ScreensCtrl*>(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 (!m_updateSettingsScreen->Validate())
{
QMessageBox::critical(this, tr("Invalid project settings"), tr("Invalid project settings"));
return;
}
if (screenEnum == ProjectManagerScreen::ProjectSettings)
{
auto projectScreen = reinterpret_cast<ProjectSettingsScreen*>(currentScreen);
if (projectScreen)
ProjectInfo newProjectSettings = m_updateSettingsScreen->GetProjectInfo();
// Update project if settings changed
if (m_projectInfo != newProjectSettings)
{
if (!projectScreen->Validate())
bool result = PythonBindingsInterface::Get()->UpdateProject(newProjectSettings);
if (!result)
{
QMessageBox::critical(this, tr("Invalid project settings"), tr("Invalid project settings"));
QMessageBox::critical(this, tr("Project update failed"), tr("Failed to update project."));
return;
}
}
m_projectInfo = projectScreen->GetProjectInfo();
// 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;
}
}
if (screenOrderIter != m_screensOrder.end())
{
m_screensCtrl->ChangeToScreen(*screenOrderIter);
UpdateNextButtonText();
m_projectInfo = newProjectSettings;
}
else
{
auto result = PythonBindingsInterface::Get()->UpdateProject(m_projectInfo);
if (result)
{
emit ChangeScreenRequest(ProjectManagerScreen::Projects);
}
else
if (m_stack->currentIndex() == ScreenOrder::Gems && m_gemCatalogScreen)
{
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()
{
if (m_stack->currentIndex() == ScreenOrder::Gems)
{
QString nextButtonText = tr("Continue");
if (m_screensCtrl->GetCurrentScreen()->GetScreenEnum() == ProjectManagerScreen::GemCatalog)
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

@ -12,40 +12,57 @@
#pragma once
#if !defined(Q_MOC_RUN)
#include "ProjectInfo.h"
#include <ProjectInfo.h>
#include <ScreenWidget.h>
#include <ScreensCtrl.h>
#include <QPushButton>
#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
};
ScreenHeader* m_header = nullptr;
QStackedWidget* m_stack = nullptr;
UpdateProjectSettingsScreen* m_updateSettingsScreen = nullptr;
GemCatalogScreen* m_gemCatalogScreen = nullptr;
ScreensCtrl* m_screensCtrl;
QPushButton* m_backButton;
QPushButton* m_nextButton;
QPushButton* m_backButton = nullptr;
QPushButton* m_nextButton = nullptr;
QVector<ProjectManagerScreen> m_screensOrder;
ProjectInfo m_projectInfo;
ProjectManagerScreen m_screenEnum;
};
} // namespace O3DE::ProjectManager

@ -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 <UpdateProjectSettingsScreen.h>
#include <FormBrowseEditWidget.h>
#include <FormLineEditWidget.h>
#include <QLineEdit>
#include <QDir>
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

@ -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 <ProjectSettingsScreen.h>
#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

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

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

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

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

@ -11,15 +11,15 @@
#pragma once
#include <AWSCoreModule.h>
#include <AzCore/Module/Module.h>
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();

@ -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<AWSCoreEditorSystemComponent>());
return requiredComponents;
return AZ::ComponentTypeList{
azrtti_typeid<AWSCoreEditorSystemComponent>()
};
}
}

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

@ -11,7 +11,5 @@
set(FILES
Include/Private/AWSCoreEditorModule.h
Include/Private/AWSCoreModule.h
Source/AWSCoreEditorModule.cpp
Source/AWSCoreModule.cpp
)

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

@ -12,7 +12,9 @@
#include <Atom/Feature/Material/MaterialAssignment.h>
#include <AzCore/RTTI/BehaviorContext.h>
#include <AzCore/Serialization/Json/RegistrationContext.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <Material/MaterialAssignmentSerializer.h>
namespace AZ
{
@ -22,6 +24,11 @@ namespace AZ
{
MaterialAssignmentId::Reflect(context);
if (auto jsonContext = azrtti_cast<JsonRegistrationContext*>(context))
{
jsonContext->Serializer<JsonMaterialAssignmentSerializer>()->HandlesType<MaterialAssignment>();
}
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->RegisterGenericType<MaterialAssignmentMap>();

@ -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 <Material/MaterialAssignmentSerializer.h>
#include <Atom/Feature/Material/MaterialAssignment.h>
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<AZ::Render::MaterialAssignment>() == outputValueTypeId,
"Unable to deserialize MaterialAssignment from json because the provided type is %s.",
outputValueTypeId.ToString<AZStd::string>().c_str());
AZ::Render::MaterialAssignment* materialAssignment = reinterpret_cast<AZ::Render::MaterialAssignment*>(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_typeid<decltype(materialAssignment->m_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<bool>(propertyValue, inputPropertyPair.value, context, result) ||
LoadAny<AZ::u8>(propertyValue, inputPropertyPair.value, context, result) ||
LoadAny<AZ::u16>(propertyValue, inputPropertyPair.value, context, result) ||
LoadAny<AZ::u32>(propertyValue, inputPropertyPair.value, context, result) ||
LoadAny<AZ::u64>(propertyValue, inputPropertyPair.value, context, result) ||
LoadAny<AZ::s8>(propertyValue, inputPropertyPair.value, context, result) ||
LoadAny<AZ::s16>(propertyValue, inputPropertyPair.value, context, result) ||
LoadAny<AZ::s32>(propertyValue, inputPropertyPair.value, context, result) ||
LoadAny<AZ::s64>(propertyValue, inputPropertyPair.value, context, result) ||
LoadAny<float>(propertyValue, inputPropertyPair.value, context, result) ||
LoadAny<double>(propertyValue, inputPropertyPair.value, context, result) ||
LoadAny<AZ::Vector2>(propertyValue, inputPropertyPair.value, context, result) ||
LoadAny<AZ::Vector3>(propertyValue, inputPropertyPair.value, context, result) ||
LoadAny<AZ::Vector4>(propertyValue, inputPropertyPair.value, context, result) ||
LoadAny<AZ::Color>(propertyValue, inputPropertyPair.value, context, result) ||
LoadAny<AZStd::string>(propertyValue, inputPropertyPair.value, context, result) ||
LoadAny<AZ::Data::AssetId>(propertyValue, inputPropertyPair.value, context, result) ||
LoadAny<AZ::Data::Asset<AZ::RPI::ImageAsset>>(propertyValue, inputPropertyPair.value, context, result) ||
LoadAny<AZ::Data::Asset<AZ::RPI::StreamingImageAsset>>(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<AZ::Render::MaterialAssignment>() == valueTypeId,
"Unable to Serialize MaterialAssignment because the provided type is %s.", valueTypeId.ToString<AZStd::string>().c_str());
const AZ::Render::MaterialAssignment* materialAssignment = reinterpret_cast<const AZ::Render::MaterialAssignment*>(inputValue);
AZ_Assert(materialAssignment, "Input value for JsonMaterialAssignmentSerializer can't be null.");
const AZ::Render::MaterialAssignment* defaultMaterialAssignmentInstance =
reinterpret_cast<const AZ::Render::MaterialAssignment*>(defaultValue);
outputValue.SetObject();
JSR::ResultCode result(JSR::Tasks::WriteValue);
{
AZ::ScopedContextPath subPathMaterialAsset(context, "m_materialAsset");
const AZ::Data::Asset<RPI::MaterialAsset>* materialAsset = &materialAssignment->m_materialAsset;
const AZ::Data::Asset<RPI::MaterialAsset>* defaultmaterialAsset =
defaultMaterialAssignmentInstance ? &defaultMaterialAssignmentInstance->m_materialAsset : nullptr;
result.Combine(ContinueStoringToJsonObjectField(
outputValue, "MaterialAsset", materialAsset, defaultmaterialAsset,
azrtti_typeid<decltype(materialAssignment->m_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<bool>(propertyValue, outputPropertyValue, context, result) ||
StoreAny<AZ::u8>(propertyValue, outputPropertyValue, context, result) ||
StoreAny<AZ::u16>(propertyValue, outputPropertyValue, context, result) ||
StoreAny<AZ::u32>(propertyValue, outputPropertyValue, context, result) ||
StoreAny<AZ::u64>(propertyValue, outputPropertyValue, context, result) ||
StoreAny<AZ::s8>(propertyValue, outputPropertyValue, context, result) ||
StoreAny<AZ::s16>(propertyValue, outputPropertyValue, context, result) ||
StoreAny<AZ::s32>(propertyValue, outputPropertyValue, context, result) ||
StoreAny<AZ::s64>(propertyValue, outputPropertyValue, context, result) ||
StoreAny<float>(propertyValue, outputPropertyValue, context, result) ||
StoreAny<double>(propertyValue, outputPropertyValue, context, result) ||
StoreAny<AZ::Vector2>(propertyValue, outputPropertyValue, context, result) ||
StoreAny<AZ::Vector3>(propertyValue, outputPropertyValue, context, result) ||
StoreAny<AZ::Vector4>(propertyValue, outputPropertyValue, context, result) ||
StoreAny<AZ::Color>(propertyValue, outputPropertyValue, context, result) ||
StoreAny<AZStd::string>(propertyValue, outputPropertyValue, context, result) ||
StoreAny<AZ::Data::AssetId>(propertyValue, outputPropertyValue, context, result) ||
StoreAny<AZ::Data::Asset<AZ::RPI::ImageAsset>>(propertyValue, outputPropertyValue, context, result) ||
StoreAny<AZ::Data::Asset<AZ::RPI::StreamingImageAsset>>(
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<typename T>
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<T>();
AZ::Uuid typeId = AZ::Uuid::CreateNull();
result.Combine(LoadTypeId(typeId, inputPropertyValue, context, &baseTypeId));
if (typeId == azrtti_typeid<T>())
{
T value = {};
result.Combine(ContinueLoadingFromJsonObjectField(&value, azrtti_typeid<T>(), inputPropertyValue, "Value", context));
propertyValue = value;
return true;
}
}
return false;
}
template<typename T>
bool JsonMaterialAssignmentSerializer::StoreAny(
const AZStd::any& propertyValue, rapidjson::Value& outputPropertyValue, AZ::JsonSerializerContext& context,
AZ::JsonSerializationResult::ResultCode& result)
{
if (propertyValue.is<T>())
{
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<T>(), context));
outputPropertyValue.AddMember("$type", typeValue, context.GetJsonAllocator());
T value = AZStd::any_cast<T>(propertyValue);
result.Combine(
ContinueStoringToJsonObjectField(outputPropertyValue, "Value", &value, nullptr, azrtti_typeid<T>(), context));
return true;
}
return false;
}
} // namespace Render
} // namespace AZ

@ -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 <AzCore/Memory/Memory.h>
#include <AzCore/Name/Name.h>
#include <AzCore/Serialization/Json/BaseJsonSerializer.h>
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<typename T>
bool LoadAny(
AZStd::any& propertyValue, const rapidjson::Value& inputPropertyValue, AZ::JsonDeserializerContext& context,
AZ::JsonSerializationResult::ResultCode& result);
template<typename T>
bool StoreAny(
const AZStd::any& propertyValue, rapidjson::Value& outputPropertyValue, AZ::JsonSerializerContext& context,
AZ::JsonSerializationResult::ResultCode& result);
};
} // namespace Render
} // namespace AZ

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

@ -78,11 +78,9 @@ namespace AZ
if (AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<EditorMaterialComponentSlot>()
->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())

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

Loading…
Cancel
Save