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