Merge branch 'development' into scripting/screenshot_toolbutton
Signed-off-by: lsemp3d <58790905+lsemp3d@users.noreply.github.com>
This commit is contained in:
@@ -38,7 +38,7 @@ def Menus_FileMenuOptions_Work():
|
||||
("Save As",),
|
||||
("Save Level Statistics",),
|
||||
("Edit Project Settings",),
|
||||
#("Edit Platform Settings",), Temporarily disabled due to https://github.com/o3de/o3de/issues/6604
|
||||
("Edit Platform Settings",),
|
||||
("New Project",),
|
||||
("Open Project",),
|
||||
("Show Log File",),
|
||||
|
||||
+485
-487
File diff suppressed because it is too large
Load Diff
@@ -209,12 +209,10 @@ namespace ProjectSettingsTool
|
||||
->Attribute(Attributes::FuncValidator, ConvertFunctorToVoid(&Validators::PackageName))
|
||||
->Attribute(Attributes::LinkOptional, true)
|
||||
->Attribute(Attributes::PropertyIdentfier, Identfiers::AndroidPackageName)
|
||||
->Attribute(Attributes::LinkedProperty, Identfiers::IosBundleIdentifer)
|
||||
->DataElement(Handlers::LinkedLineEdit, &AndroidSettings::m_versionName, "Version Name", "Human readable version number. Used to set the \"android: versionName\" tag in the AndroidManifest.xml and ultimately what will be displayed in the App Store.")
|
||||
->Attribute(Attributes::FuncValidator, ConvertFunctorToVoid(&Validators::IOSVersionNumber))
|
||||
->Attribute(Attributes::LinkOptional, true)
|
||||
->Attribute(Attributes::PropertyIdentfier, Identfiers::AndroidVersionName)
|
||||
->Attribute(Attributes::LinkedProperty, Identfiers::IosVersionName)
|
||||
->DataElement(AZ::Edit::UIHandlers::Default, &AndroidSettings::m_versionNumber, "Version Number", "Internal application version number. Used to set the \"android:versionCode\" tag in the AndroidManifest.xml.")
|
||||
->Attribute(AZ::Edit::Attributes::Min, 1)
|
||||
->Attribute(AZ::Edit::Attributes::Max, Validators::maxAndroidVersion)
|
||||
|
||||
@@ -37,19 +37,15 @@ namespace ProjectSettingsTool
|
||||
->DataElement(Handlers::LinkedLineEdit, &BaseSettings::m_projectName, "Project Name", "The name of the project.")
|
||||
->Attribute(Attributes::FuncValidator, ConvertFunctorToVoid(&Validators::FileName))
|
||||
->Attribute(Attributes::PropertyIdentfier, Identfiers::ProjectName)
|
||||
->Attribute(Attributes::LinkedProperty, Identfiers::IosBundleName)
|
||||
->DataElement(Handlers::LinkedLineEdit, &BaseSettings::m_productName, "Product Name", "The project's user facing name.")
|
||||
->Attribute(Attributes::FuncValidator, ConvertFunctorToVoid(&Validators::IsNotEmpty))
|
||||
->Attribute(Attributes::PropertyIdentfier, Identfiers::ProductName)
|
||||
->Attribute(Attributes::LinkedProperty, Identfiers::IosDisplayName)
|
||||
->DataElement(Handlers::LinkedLineEdit, &BaseSettings::m_executableName, "Executable Name", "The project launcher's name.")
|
||||
->Attribute(Attributes::FuncValidator, ConvertFunctorToVoid(&Validators::FileName))
|
||||
->Attribute(Attributes::PropertyIdentfier, Identfiers::ExecutableName)
|
||||
->Attribute(Attributes::LinkedProperty, Identfiers::IosExecutableName)
|
||||
->DataElement(Handlers::QValidatedLineEdit, &BaseSettings::m_projectPath, "Project Path", "The project root folder path .")
|
||||
->Attribute(Attributes::FuncValidator, ConvertFunctorToVoid(&Validators::FileNameOrEmpty))
|
||||
->Attribute(Attributes::PropertyIdentfier, Identfiers::ProductName)
|
||||
->Attribute(Attributes::LinkedProperty, Identfiers::ExecutableName)
|
||||
->DataElement(Handlers::QValidatedLineEdit, &BaseSettings::m_projectOutputFolder, "Output Folder", "The folder the packed project will be exported to.")
|
||||
->DataElement(Handlers::QValidatedLineEdit, &BaseSettings::m_codeFolder, "Code Folder (legacy)", "A legacy setting specifing the folder for this project's code.")
|
||||
;
|
||||
|
||||
@@ -262,27 +262,22 @@ namespace ProjectSettingsTool
|
||||
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
|
||||
->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly)
|
||||
->DataElement(Handlers::LinkedLineEdit, &IosSettings::m_bundleName, "Bundle Name", "The name of the bundle.")
|
||||
->Attribute(Attributes::FuncValidator, ConvertFunctorToVoid(&Validators::FileName))
|
||||
->Attribute(Attributes::FuncValidator, ConvertFunctorToVoid(&Validators::IOSFileName))
|
||||
->Attribute(Attributes::PropertyIdentfier, Identfiers::IosBundleName)
|
||||
->Attribute(Attributes::LinkedProperty, Identfiers::ProjectName)
|
||||
->DataElement(Handlers::LinkedLineEdit, &IosSettings::m_bundleDisplayName, "Display Name", "The user visible name of the bundle.")
|
||||
->Attribute(Attributes::FuncValidator, ConvertFunctorToVoid(&Validators::IsNotEmpty))
|
||||
->Attribute(Attributes::PropertyIdentfier, Identfiers::IosDisplayName)
|
||||
->Attribute(Attributes::LinkedProperty, Identfiers::ProductName)
|
||||
->DataElement(Handlers::LinkedLineEdit, &IosSettings::m_executableName, "Executable Name", "Name of the bundle's executable file.")
|
||||
->Attribute(Attributes::FuncValidator, ConvertFunctorToVoid(&Validators::FileName))
|
||||
->Attribute(Attributes::FuncValidator, ConvertFunctorToVoid(&Validators::IOSFileName))
|
||||
->Attribute(Attributes::PropertyIdentfier, Identfiers::IosExecutableName)
|
||||
->Attribute(Attributes::LinkedProperty, Identfiers::ExecutableName)
|
||||
->DataElement(Handlers::LinkedLineEdit, &IosSettings::m_bundleIdentifier, "Bundle Identifier", "Uniquely identifies the bundle. Should be in reverse-DNS format.")
|
||||
->Attribute(Attributes::FuncValidator, ConvertFunctorToVoid(&Validators::PackageName))
|
||||
->Attribute(Attributes::LinkOptional, true)
|
||||
->Attribute(Attributes::PropertyIdentfier, Identfiers::IosBundleIdentifer)
|
||||
->Attribute(Attributes::LinkedProperty, Identfiers::AndroidPackageName)
|
||||
->DataElement(Handlers::LinkedLineEdit, &IosSettings::m_versionName, "Version Name", "The release version number string for the app. Displayed in the app store.")
|
||||
->Attribute(Attributes::FuncValidator, ConvertFunctorToVoid(&Validators::IOSVersionNumber))
|
||||
->Attribute(Attributes::LinkOptional, true)
|
||||
->Attribute(Attributes::PropertyIdentfier, Identfiers::IosVersionName)
|
||||
->Attribute(Attributes::LinkedProperty, Identfiers::AndroidVersionName)
|
||||
->DataElement(Handlers::QValidatedLineEdit, &IosSettings::m_versionNumber, "Version Number", "The build version number string for the bundle.")
|
||||
->Attribute(Attributes::FuncValidator, ConvertFunctorToVoid(&Validators::IOSVersionNumber))
|
||||
->DataElement(AZ::Edit::UIHandlers::ComboBox, &IosSettings::m_developmentRegion, "Development Region", "The default language and region for the app.")
|
||||
|
||||
@@ -22,7 +22,6 @@ namespace ProjectSettingsTool
|
||||
static const AZ::Crc32 Obfuscated = AZ_CRC("ObfuscatedText");
|
||||
// Used as a tooltip and for distinguising linked properties
|
||||
static const AZ::Crc32 PropertyIdentfier = AZ_CRC("PropertyIdentfier");
|
||||
static const AZ::Crc32 LinkedProperty = AZ_CRC("LinkedProperty");
|
||||
static const AZ::Crc32 DefaultPath = AZ_CRC("DefaultPath");
|
||||
static const AZ::Crc32 DefaultImagePreview = AZ_CRC("DefaultImagePreview");
|
||||
static const AZ::Crc32 ObfuscatedText = AZ_CRC("ObfuscatedText");
|
||||
|
||||
@@ -225,25 +225,6 @@ namespace ProjectSettingsTool
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (attrib == Attributes::LinkedProperty)
|
||||
{
|
||||
AZStd::string linked;
|
||||
if (attrValue->Read<AZStd::string>(linked))
|
||||
{
|
||||
auto result = m_ctrlToIdentAndLink.find(GUI);
|
||||
if (result != m_ctrlToIdentAndLink.end())
|
||||
{
|
||||
result->second.linkedIdentifier = linked;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_ctrlToIdentAndLink.insert(AZStd::pair<PropertyLinkedCtrl*, IdentAndLink>(GUI, IdentAndLink{ "", linked }));
|
||||
m_ctrlInitOrder.push_back(GUI);
|
||||
}
|
||||
|
||||
GUI->SetLinkTooltip(linked.data());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
GUI->ConsumeAttribute(attrib, attrValue, debugName);
|
||||
|
||||
@@ -106,6 +106,11 @@ namespace ProjectSettingsTool
|
||||
return RegularExpressionValidator("[\\w,-]+", name);
|
||||
}
|
||||
|
||||
// Returns true if valid iOS file or directory name
|
||||
RetType IOSFileName(const QString& name)
|
||||
{
|
||||
return RegularExpressionValidator("[\\w,-.]+", name);
|
||||
}
|
||||
RetType FileNameOrEmpty(const QString& name)
|
||||
{
|
||||
if (IsNotEmpty(name).first == QValidator::Acceptable)
|
||||
|
||||
@@ -24,6 +24,8 @@ namespace ProjectSettingsTool
|
||||
|
||||
// Returns true if valid cross platform file or directory name
|
||||
FunctorValidator::ReturnType FileName(const QString& name);
|
||||
// Returns true if valid iOS file or directory name
|
||||
FunctorValidator::ReturnType IOSFileName(const QString& name);
|
||||
// Returns true if valid cross platform file or directory name or empty
|
||||
FunctorValidator::ReturnType FileNameOrEmpty(const QString& name);
|
||||
// Returns true if string isn't empty
|
||||
|
||||
@@ -203,7 +203,7 @@ namespace AZ
|
||||
JsonSerializationResult::Result JsonMapSerializer::LoadElement(void* outputValue, SerializeContext::IDataContainer* container,
|
||||
const SerializeContext::ClassElement* pairElement, SerializeContext::IDataContainer* pairContainer,
|
||||
const SerializeContext::ClassElement* keyElement, const SerializeContext::ClassElement* valueElement,
|
||||
const rapidjson::Value& key, const rapidjson::Value& value, JsonDeserializerContext& context)
|
||||
const rapidjson::Value& key, const rapidjson::Value& value, JsonDeserializerContext& context, bool isMultiMap)
|
||||
{
|
||||
namespace JSR = JsonSerializationResult;
|
||||
|
||||
@@ -231,8 +231,30 @@ namespace AZ
|
||||
return context.Report(keyResult, "Failed to read key for associative container.");
|
||||
}
|
||||
|
||||
void* valueAddress = nullptr;
|
||||
bool keyExists = false;
|
||||
|
||||
// For multimaps, we append values to keys instead updating them.
|
||||
// This is to ensure legacy multimap serialization support.
|
||||
if (!isMultiMap)
|
||||
{
|
||||
auto associativeContainer = container->GetAssociativeContainerInterface();
|
||||
void* existingKeyValuePair = associativeContainer->GetElementByKey(outputValue, keyElement, keyAddress);
|
||||
if (existingKeyValuePair)
|
||||
{
|
||||
valueAddress = pairContainer->GetElementByIndex(existingKeyValuePair, pairElement, 1);
|
||||
expectedSize--;
|
||||
keyExists = true;
|
||||
}
|
||||
}
|
||||
|
||||
// If the key doesn't exist or it's a multimap, we're adding the new element we reserved above.
|
||||
if (!keyExists)
|
||||
{
|
||||
valueAddress = pairContainer->GetElementByIndex(address, pairElement, 1);
|
||||
}
|
||||
|
||||
// Load value
|
||||
void* valueAddress = pairContainer->GetElementByIndex(address, pairElement, 1);
|
||||
AZ_Assert(valueAddress, "Element reserved for associative container, but unable to retrieve address of the value.");
|
||||
ContinuationFlags valueLoadFlags = ContinuationFlags::LoadAsNewInstance;
|
||||
if (valueElement->m_flags & SerializeContext::ClassElement::Flags::FLG_POINTER)
|
||||
@@ -257,7 +279,18 @@ namespace AZ
|
||||
}
|
||||
else
|
||||
{
|
||||
container->StoreElement(outputValue, address);
|
||||
// Even if the key exists, calling StoreElement will not replace the existing key
|
||||
// and will free the temporary address as expected. Checking if the key already
|
||||
// exists and skipping the call to StoreElement if it does, makes the intent more
|
||||
// clear. The end result is the same either way.
|
||||
if (!keyExists)
|
||||
{
|
||||
container->StoreElement(outputValue, address);
|
||||
}
|
||||
else
|
||||
{
|
||||
container->FreeReservedElement(outputValue, address, context.GetSerializeContext());
|
||||
}
|
||||
if (container->Size(outputValue) != expectedSize)
|
||||
{
|
||||
return context.Report(JSR::Tasks::ReadField, JSR::Outcomes::Unavailable,
|
||||
@@ -430,7 +463,7 @@ namespace AZ
|
||||
JsonSerializationResult::Result JsonUnorderedMultiMapSerializer::LoadElement(void* outputValue, SerializeContext::IDataContainer* container,
|
||||
const SerializeContext::ClassElement* pairElement, SerializeContext::IDataContainer* pairContainer,
|
||||
const SerializeContext::ClassElement* keyElement, const SerializeContext::ClassElement* valueElement,
|
||||
const rapidjson::Value& key, const rapidjson::Value& value, JsonDeserializerContext& context)
|
||||
const rapidjson::Value& key, const rapidjson::Value& value, JsonDeserializerContext& context, [[maybe_unused]] bool isMultiMap)
|
||||
{
|
||||
namespace JSR = JsonSerializationResult;
|
||||
|
||||
@@ -440,7 +473,7 @@ namespace AZ
|
||||
for (auto& entry : value.GetArray())
|
||||
{
|
||||
result.Combine(JsonMapSerializer::LoadElement(outputValue, container, pairElement, pairContainer,
|
||||
keyElement, valueElement, key, entry, context));
|
||||
keyElement, valueElement, key, entry, context, true));
|
||||
if (result.GetProcessing() == JSR::Processing::Halted)
|
||||
{
|
||||
return context.Report(result, "Unable to process the key or all values in multi-map.");
|
||||
@@ -451,7 +484,7 @@ namespace AZ
|
||||
else if (IsExplicitDefault(value))
|
||||
{
|
||||
return JsonMapSerializer::LoadElement(outputValue, container, pairElement, pairContainer,
|
||||
keyElement, valueElement, key, value, context);
|
||||
keyElement, valueElement, key, value, context, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -32,7 +32,7 @@ namespace AZ
|
||||
virtual JsonSerializationResult::Result LoadElement(void* outputValue, SerializeContext::IDataContainer* container,
|
||||
const SerializeContext::ClassElement* pairElement, SerializeContext::IDataContainer* pairContainer,
|
||||
const SerializeContext::ClassElement* keyElement, const SerializeContext::ClassElement* valueElement,
|
||||
const rapidjson::Value& key, const rapidjson::Value& value, JsonDeserializerContext& context);
|
||||
const rapidjson::Value& key, const rapidjson::Value& value, JsonDeserializerContext& context, bool isMultiMap = false);
|
||||
|
||||
virtual JsonSerializationResult::Result Store(rapidjson::Value& outputValue, const void* inputValue, const void* defaultValue,
|
||||
const Uuid& valueTypeId, JsonSerializerContext& context, bool sortResult);
|
||||
@@ -62,7 +62,7 @@ namespace AZ
|
||||
JsonSerializationResult::Result LoadElement(void* outputValue, SerializeContext::IDataContainer* container,
|
||||
const SerializeContext::ClassElement* pairElement, SerializeContext::IDataContainer* pairContainer,
|
||||
const SerializeContext::ClassElement* keyElement, const SerializeContext::ClassElement* valueElement,
|
||||
const rapidjson::Value& key, const rapidjson::Value& value, JsonDeserializerContext& context) override;
|
||||
const rapidjson::Value& key, const rapidjson::Value& value, JsonDeserializerContext& context, bool isMultiMap = false) override;
|
||||
|
||||
using JsonMapSerializer::Store;
|
||||
JsonSerializationResult::Result Store(rapidjson::Value& outputValue, const void* inputValue, const void* defaultValue,
|
||||
|
||||
@@ -475,7 +475,7 @@ namespace JsonSerializationTests
|
||||
EXPECT_STRCASEEQ("value_42", worldKey->second.m_value.c_str());
|
||||
}
|
||||
|
||||
TEST_F(JsonMapSerializerTests, Load_DuplicateKey_EntryIgnored)
|
||||
TEST_F(JsonMapSerializerTests, Load_DuplicateKey_EntryUpdated)
|
||||
{
|
||||
using namespace AZ::JsonSerializationResult;
|
||||
|
||||
@@ -489,12 +489,12 @@ namespace JsonSerializationTests
|
||||
StringMap values;
|
||||
ResultCode result = m_unorderedMapSerializer.Load(&values, azrtti_typeid(&values), *m_jsonDocument, *m_jsonDeserializationContext);
|
||||
|
||||
EXPECT_EQ(Processing::PartialAlter, result.GetProcessing());
|
||||
EXPECT_EQ(Outcomes::Unavailable, result.GetOutcome());
|
||||
EXPECT_EQ(Processing::Completed, result.GetProcessing());
|
||||
EXPECT_EQ(Outcomes::Success, result.GetOutcome());
|
||||
|
||||
auto entry = values.find("Hello");
|
||||
ASSERT_NE(values.end(), entry);
|
||||
EXPECT_STRCASEEQ("World", entry->second.c_str());
|
||||
EXPECT_EQ("Other", entry->second);
|
||||
}
|
||||
|
||||
TEST_F(JsonMapSerializerTests, Load_DuplicateMultiKey_LoadEverything)
|
||||
@@ -536,8 +536,8 @@ namespace JsonSerializationTests
|
||||
ResultCode result = m_unorderedMapSerializer.Load(&values,
|
||||
azrtti_typeid(&values), *m_jsonDocument, *m_jsonDeserializationContext);
|
||||
|
||||
EXPECT_EQ(Processing::Altered, result.GetProcessing());
|
||||
EXPECT_EQ(Outcomes::Unavailable, result.GetOutcome());
|
||||
EXPECT_EQ(Processing::Completed, result.GetProcessing());
|
||||
EXPECT_EQ(Outcomes::Success, result.GetOutcome());
|
||||
|
||||
auto entry = values.find("Hello");
|
||||
ASSERT_NE(values.end(), entry);
|
||||
|
||||
@@ -856,18 +856,4 @@ DLL_EXPORT void OutputDebugString(const char* outputString)
|
||||
|
||||
#endif
|
||||
|
||||
// This code does not have a long life span and will be replaced soon
|
||||
#if defined(APPLE) || defined(LINUX) || defined(DEFINE_LEGACY_CRY_FILE_OPERATIONS)
|
||||
|
||||
bool CrySetFileAttributes(const char* lpFileName, uint32 dwFileAttributes)
|
||||
{
|
||||
//TODO: implement
|
||||
printf("CrySetFileAttributes not properly implemented yet\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
#endif //defined(APPLE) || defined(LINUX)
|
||||
|
||||
#endif // AZ_TRAIT_LEGACY_CRYCOMMON_USE_WINDOWS_STUBS
|
||||
|
||||
@@ -336,7 +336,6 @@ void SetFlags(T& dest, U flags, bool b)
|
||||
#include AZ_RESTRICTED_FILE(platform_h)
|
||||
#endif
|
||||
|
||||
bool CrySetFileAttributes(const char* lpFileName, uint32 dwFileAttributes);
|
||||
threadID CryGetCurrentThreadId();
|
||||
|
||||
#ifdef __GNUC__
|
||||
|
||||
@@ -24,7 +24,6 @@
|
||||
#define PLATFORM_IMPL_H_SECTION_TRAITS 1
|
||||
#define PLATFORM_IMPL_H_SECTION_CRYLOWLATENCYSLEEP 2
|
||||
#define PLATFORM_IMPL_H_SECTION_CRYGETFILEATTRIBUTES 3
|
||||
#define PLATFORM_IMPL_H_SECTION_CRYSETFILEATTRIBUTES 4
|
||||
#define PLATFORM_IMPL_H_SECTION_CRY_FILE_ATTRIBUTE_STUBS 5
|
||||
#define PLATFORM_IMPL_H_SECTION_CRY_SYSTEM_FUNCTIONS 6
|
||||
#define PLATFORM_IMPL_H_SECTION_VIRTUAL_ALLOCATORS 7
|
||||
@@ -238,22 +237,6 @@ void InitRootDir(char szExeFileName[], uint nExeSize, char szExeRootName[], uint
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
bool CrySetFileAttributes(const char* lpFileName, uint32 dwFileAttributes)
|
||||
{
|
||||
#if defined(AZ_RESTRICTED_PLATFORM)
|
||||
#define AZ_RESTRICTED_SECTION PLATFORM_IMPL_H_SECTION_CRYSETFILEATTRIBUTES
|
||||
#include AZ_RESTRICTED_FILE(platform_impl_h)
|
||||
#endif
|
||||
#if defined(AZ_RESTRICTED_SECTION_IMPLEMENTED)
|
||||
#undef AZ_RESTRICTED_SECTION_IMPLEMENTED
|
||||
#else
|
||||
AZStd::wstring lpFileNameW;
|
||||
AZStd::to_wstring(lpFileNameW, lpFileName);
|
||||
return SetFileAttributes(lpFileNameW.c_str(), dwFileAttributes) != 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
threadID CryGetCurrentThreadId()
|
||||
{
|
||||
|
||||
@@ -1132,7 +1132,10 @@ bool CXmlNode::saveToFile(const char* fileName)
|
||||
|
||||
bool CXmlNode::saveToFile([[maybe_unused]] const char* fileName, size_t chunkSize, AZ::IO::HandleType fileHandle)
|
||||
{
|
||||
CrySetFileAttributes(fileName, FILE_ATTRIBUTE_NORMAL);
|
||||
if (AZ::IO::SystemFile::Exists(fileName) && !AZ::IO::SystemFile::IsWritable(fileName))
|
||||
{
|
||||
AZ::IO::SystemFile::SetWritable(fileName, true);
|
||||
}
|
||||
|
||||
if (chunkSize < 256 * 1024) // make at least 256k
|
||||
{
|
||||
|
||||
@@ -500,6 +500,10 @@ QProgressBar::chunk {
|
||||
|
||||
/************** Gem Catalog **************/
|
||||
|
||||
#GemCatalogScreen {
|
||||
background-color: #333333;
|
||||
}
|
||||
|
||||
#GemCatalogTitle {
|
||||
font-size: 18px;
|
||||
}
|
||||
@@ -546,9 +550,8 @@ QProgressBar::chunk {
|
||||
min-height:24px;
|
||||
}
|
||||
|
||||
#GemCatalogHeaderLabel {
|
||||
font-size: 12px;
|
||||
color: #FFFFFF;
|
||||
#adjustableHeaderWidget QHeaderView::section {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
#GemCatalogHeaderShowCountLabel {
|
||||
@@ -732,15 +735,6 @@ QProgressBar::chunk {
|
||||
stop: 0 #555555, stop: 1.0 #777777);
|
||||
}
|
||||
|
||||
#gemRepoHeaderTable {
|
||||
background-color: transparent;
|
||||
max-height: 30px;
|
||||
}
|
||||
|
||||
#gemRepoListHeader {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
#gemRepoInspector {
|
||||
background: #444444;
|
||||
}
|
||||
@@ -774,4 +768,4 @@ QProgressBar::chunk {
|
||||
#gemRepoInspectorAddInfoTitleLabel {
|
||||
font-size: 16px;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#include <AdjustableHeaderWidget.h>
|
||||
#include <AzCore/Debug/Trace.h>
|
||||
|
||||
#include <QHeaderView>
|
||||
#include <QTimer>
|
||||
|
||||
namespace O3DE::ProjectManager
|
||||
{
|
||||
AdjustableHeaderWidget::AdjustableHeaderWidget( const QStringList& headerLabels,
|
||||
const QVector<int>& defaultHeaderWidths, int minHeaderWidth,
|
||||
const QVector<QHeaderView::ResizeMode>& resizeModes, QWidget* parent)
|
||||
: QTableWidget(parent)
|
||||
{
|
||||
setObjectName("adjustableHeaderWidget");
|
||||
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum);
|
||||
setFixedHeight(s_headerWidgetHeight);
|
||||
|
||||
m_header = horizontalHeader();
|
||||
m_header->setDefaultAlignment(Qt::AlignLeft);
|
||||
|
||||
setColumnCount(headerLabels.count());
|
||||
setHorizontalHeaderLabels(headerLabels);
|
||||
|
||||
AZ_Assert(defaultHeaderWidths.count() == columnCount(), "Default header widths does not match number of columns");
|
||||
AZ_Assert(resizeModes.count() == columnCount(), "Resize modesdoes not match number of columns");
|
||||
|
||||
for (int column = 0; column < columnCount(); ++column)
|
||||
{
|
||||
m_header->resizeSection(column, defaultHeaderWidths[column]);
|
||||
m_header->setSectionResizeMode(column, resizeModes[column]);
|
||||
}
|
||||
|
||||
m_header->setMinimumSectionSize(minHeaderWidth);
|
||||
m_header->setCascadingSectionResizes(true);
|
||||
|
||||
connect(m_header, &QHeaderView::sectionResized, this, &AdjustableHeaderWidget::OnSectionResized);
|
||||
}
|
||||
|
||||
void AdjustableHeaderWidget::OnSectionResized(int logicalIndex, int oldSize, int newSize)
|
||||
{
|
||||
const int headerCount = columnCount();
|
||||
const int headerWidth = m_header->width();
|
||||
const int totalSectionWidth = m_header->length();
|
||||
|
||||
if (totalSectionWidth > headerWidth && newSize > oldSize)
|
||||
{
|
||||
int xPos = 0;
|
||||
int requiredWidth = 0;
|
||||
|
||||
for (int i = 0; i < headerCount; i++)
|
||||
{
|
||||
if (i < logicalIndex)
|
||||
{
|
||||
xPos += m_header->sectionSize(i);
|
||||
}
|
||||
else if (i == logicalIndex)
|
||||
{
|
||||
xPos += newSize;
|
||||
}
|
||||
else if (i > logicalIndex)
|
||||
{
|
||||
if (m_header->sectionResizeMode(i) == QHeaderView::ResizeMode::Fixed)
|
||||
{
|
||||
requiredWidth += m_header->sectionSize(i);
|
||||
}
|
||||
else
|
||||
{
|
||||
requiredWidth += m_header->minimumSectionSize();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (xPos + requiredWidth > headerWidth)
|
||||
{
|
||||
m_header->resizeSection(logicalIndex, oldSize);
|
||||
}
|
||||
}
|
||||
|
||||
// wait till all columns resized
|
||||
QTimer::singleShot(0, [&]()
|
||||
{
|
||||
// only re-paint when the header and section widths have settled
|
||||
const int headerWidth = m_header->width();
|
||||
const int totalSectionWidth = m_header->length();
|
||||
if (totalSectionWidth == headerWidth)
|
||||
{
|
||||
emit sectionsResized();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
QPair<int, int> AdjustableHeaderWidget::CalcColumnXBounds(int headerIndex) const
|
||||
{
|
||||
// Total the widths of all headers before this one in first and including it in second
|
||||
QPair<int, int> bounds(0, 0);
|
||||
|
||||
for (int curIndex = 0; curIndex <= headerIndex; ++curIndex)
|
||||
{
|
||||
if (curIndex == headerIndex)
|
||||
{
|
||||
bounds.first = bounds.second;
|
||||
}
|
||||
bounds.second += m_header->sectionSize(curIndex);
|
||||
}
|
||||
|
||||
return bounds;
|
||||
}
|
||||
|
||||
|
||||
} // namespace O3DE::ProjectManager
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#if !defined(Q_MOC_RUN)
|
||||
#include <QTableWidget>
|
||||
|
||||
#include <QHeaderView>
|
||||
#include <QStringList>
|
||||
#include <QVector>
|
||||
#include <QPair>
|
||||
#endif
|
||||
|
||||
namespace O3DE::ProjectManager
|
||||
{
|
||||
// Using a QTableWidget for its header
|
||||
// Using a seperate model allows the setup of a header exactly as needed
|
||||
class AdjustableHeaderWidget
|
||||
: public QTableWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit AdjustableHeaderWidget(const QStringList& headerLabels,
|
||||
const QVector<int>& defaultHeaderWidths, int minHeaderWidth,
|
||||
const QVector<QHeaderView::ResizeMode>& resizeModes,
|
||||
QWidget* parent = nullptr);
|
||||
~AdjustableHeaderWidget() = default;
|
||||
|
||||
QPair<int, int> CalcColumnXBounds(int headerIndex) const;
|
||||
|
||||
inline constexpr static int s_headerTextIndent = 7;
|
||||
inline constexpr static int s_headerWidgetHeight = 24;
|
||||
|
||||
QHeaderView* m_header;
|
||||
|
||||
signals:
|
||||
void sectionsResized();
|
||||
|
||||
protected slots:
|
||||
void OnSectionResized(int logicalIndex, int oldSize, int newSize);
|
||||
|
||||
private:
|
||||
inline constexpr static int s_headerIndentSection = 11;
|
||||
};
|
||||
} // namespace O3DE::ProjectManager
|
||||
@@ -17,7 +17,7 @@ namespace O3DE::ProjectManager
|
||||
class ExternalLinkDialog
|
||||
: public QDialog
|
||||
{
|
||||
Q_OBJECT // AUTOMOC
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit ExternalLinkDialog(const QUrl& url, QWidget* parent = nullptr);
|
||||
~ExternalLinkDialog() = default;
|
||||
|
||||
@@ -33,7 +33,7 @@ namespace O3DE::ProjectManager
|
||||
class GemCartWidget
|
||||
: public QScrollArea
|
||||
{
|
||||
Q_OBJECT // AUTOMOC
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
GemCartWidget(GemModel* gemModel, DownloadController* downloadController, QWidget* parent = nullptr);
|
||||
|
||||
@@ -19,8 +19,10 @@
|
||||
#include <GemCatalog/GemDependenciesDialog.h>
|
||||
#include <GemCatalog/GemUpdateDialog.h>
|
||||
#include <GemCatalog/GemUninstallDialog.h>
|
||||
#include <GemCatalog/GemItemDelegate.h>
|
||||
#include <DownloadController.h>
|
||||
#include <ProjectUtils.h>
|
||||
#include <AdjustableHeaderWidget.h>
|
||||
|
||||
#include <QVBoxLayout>
|
||||
#include <QHBoxLayout>
|
||||
@@ -40,6 +42,13 @@ namespace O3DE::ProjectManager
|
||||
GemCatalogScreen::GemCatalogScreen(QWidget* parent)
|
||||
: ScreenWidget(parent)
|
||||
{
|
||||
// The width of either side panel (filters, inspector) in the catalog
|
||||
constexpr int sidePanelWidth = 240;
|
||||
// Querying qApp about styling reports the scroll bar being larger than it is so define it manually
|
||||
constexpr int verticalScrollBarWidth = 8;
|
||||
|
||||
setObjectName("GemCatalogScreen");
|
||||
|
||||
m_gemModel = new GemModel(this);
|
||||
m_proxyModel = new GemSortFilterProxyModel(m_gemModel, this);
|
||||
|
||||
@@ -69,10 +78,8 @@ namespace O3DE::ProjectManager
|
||||
hLayout->setMargin(0);
|
||||
vLayout->addLayout(hLayout);
|
||||
|
||||
m_gemListView = new GemListView(m_proxyModel, m_proxyModel->GetSelectionModel(), this);
|
||||
|
||||
m_rightPanelStack = new QStackedWidget(this);
|
||||
m_rightPanelStack->setFixedWidth(240);
|
||||
m_rightPanelStack->setFixedWidth(sidePanelWidth);
|
||||
|
||||
m_gemInspector = new GemInspector(m_gemModel, this);
|
||||
|
||||
@@ -81,18 +88,45 @@ namespace O3DE::ProjectManager
|
||||
connect(m_gemInspector, &GemInspector::UninstallGem, this, &GemCatalogScreen::UninstallGem);
|
||||
|
||||
QWidget* filterWidget = new QWidget(this);
|
||||
filterWidget->setFixedWidth(240);
|
||||
filterWidget->setFixedWidth(sidePanelWidth);
|
||||
m_filterWidgetLayout = new QVBoxLayout();
|
||||
m_filterWidgetLayout->setMargin(0);
|
||||
m_filterWidgetLayout->setSpacing(0);
|
||||
filterWidget->setLayout(m_filterWidgetLayout);
|
||||
|
||||
GemListHeaderWidget* listHeaderWidget = new GemListHeaderWidget(m_proxyModel);
|
||||
GemListHeaderWidget* catalogHeaderWidget = new GemListHeaderWidget(m_proxyModel);
|
||||
|
||||
constexpr int minHeaderSectionWidth = 100;
|
||||
AdjustableHeaderWidget* listHeaderWidget = new AdjustableHeaderWidget(
|
||||
QStringList{ tr("Gem Name"), tr("Gem Summary"), tr("Status") },
|
||||
QVector<int>{
|
||||
GemItemDelegate::s_defaultSummaryStartX - 30,
|
||||
0, // Section is set to stretch to fit
|
||||
GemItemDelegate::s_buttonWidth + GemItemDelegate::s_itemMargins.left() + GemItemDelegate::s_itemMargins.right() + GemItemDelegate::s_contentMargins.right()
|
||||
},
|
||||
minHeaderSectionWidth,
|
||||
QVector<QHeaderView::ResizeMode>
|
||||
{
|
||||
QHeaderView::ResizeMode::Interactive,
|
||||
QHeaderView::ResizeMode::Stretch,
|
||||
QHeaderView::ResizeMode::Fixed
|
||||
},
|
||||
this);
|
||||
|
||||
m_gemListView = new GemListView(m_proxyModel, m_proxyModel->GetSelectionModel(), listHeaderWidget, this);
|
||||
|
||||
QHBoxLayout* listHeaderLayout = new QHBoxLayout();
|
||||
listHeaderLayout->setMargin(0);
|
||||
listHeaderLayout->setSpacing(0);
|
||||
listHeaderLayout->addSpacing(GemItemDelegate::s_itemMargins.left());
|
||||
listHeaderLayout->addWidget(listHeaderWidget);
|
||||
listHeaderLayout->addSpacing(GemItemDelegate::s_itemMargins.right() + verticalScrollBarWidth);
|
||||
|
||||
QVBoxLayout* middleVLayout = new QVBoxLayout();
|
||||
middleVLayout->setMargin(0);
|
||||
middleVLayout->setSpacing(0);
|
||||
middleVLayout->addWidget(listHeaderWidget);
|
||||
middleVLayout->addWidget(catalogHeaderWidget);
|
||||
middleVLayout->addLayout(listHeaderLayout);
|
||||
middleVLayout->addWidget(m_gemListView);
|
||||
|
||||
hLayout->addWidget(filterWidget);
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace O3DE::ProjectManager
|
||||
class GemDependenciesDialog
|
||||
: public QDialog
|
||||
{
|
||||
Q_OBJECT // AUTOMOC
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit GemDependenciesDialog(GemModel* gemModel, QWidget *parent = nullptr);
|
||||
~GemDependenciesDialog() = default;
|
||||
|
||||
@@ -26,7 +26,7 @@ namespace O3DE::ProjectManager
|
||||
class FilterCategoryWidget
|
||||
: public QWidget
|
||||
{
|
||||
Q_OBJECT // AUTOMOC
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit FilterCategoryWidget(const QString& header,
|
||||
|
||||
@@ -28,7 +28,7 @@ namespace O3DE::ProjectManager
|
||||
class GemInspector
|
||||
: public QScrollArea
|
||||
{
|
||||
Q_OBJECT // AUTOMOC
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit GemInspector(GemModel* model, QWidget* parent = nullptr);
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
#include <GemCatalog/GemItemDelegate.h>
|
||||
#include <GemCatalog/GemModel.h>
|
||||
#include <GemCatalog/GemSortFilterProxyModel.h>
|
||||
#include <AdjustableHeaderWidget.h>
|
||||
|
||||
#include <AzCore/std/smart_ptr/unique_ptr.h>
|
||||
|
||||
#include <QEvent>
|
||||
@@ -22,12 +24,14 @@
|
||||
#include <QAbstractTextDocumentLayout>
|
||||
#include <QDesktopServices>
|
||||
#include <QMovie>
|
||||
#include <QHeaderView>
|
||||
|
||||
namespace O3DE::ProjectManager
|
||||
{
|
||||
GemItemDelegate::GemItemDelegate(QAbstractItemModel* model, QObject* parent)
|
||||
GemItemDelegate::GemItemDelegate(QAbstractItemModel* model, AdjustableHeaderWidget* header, QObject* parent)
|
||||
: QStyledItemDelegate(parent)
|
||||
, m_model(model)
|
||||
, m_headerWidget(header)
|
||||
{
|
||||
AddPlatformIcon(GemInfo::Android, ":/Android.svg");
|
||||
AddPlatformIcon(GemInfo::iOS, ":/iOS.svg");
|
||||
@@ -116,12 +120,15 @@ namespace O3DE::ProjectManager
|
||||
// Gem name
|
||||
QString gemName = GemModel::GetDisplayName(modelIndex);
|
||||
QFont gemNameFont(options.font);
|
||||
const int firstColumnMaxTextWidth = s_summaryStartX - 30;
|
||||
QPair<int, int> nameXBounds = CalcColumnXBounds(HeaderOrder::Name);
|
||||
const int nameStartX = nameXBounds.first;
|
||||
const int firstColumnTextStartX = s_itemMargins.left() + nameStartX + AdjustableHeaderWidget::s_headerTextIndent;
|
||||
const int firstColumnMaxTextWidth = nameXBounds.second - nameStartX - AdjustableHeaderWidget::s_headerTextIndent;
|
||||
gemNameFont.setPixelSize(static_cast<int>(s_gemNameFontSize));
|
||||
gemNameFont.setBold(true);
|
||||
gemName = QFontMetrics(gemNameFont).elidedText(gemName, Qt::TextElideMode::ElideRight, firstColumnMaxTextWidth);
|
||||
QRect gemNameRect = GetTextRect(gemNameFont, gemName, s_gemNameFontSize);
|
||||
gemNameRect.moveTo(contentRect.left(), contentRect.top());
|
||||
gemNameRect.moveTo(firstColumnTextStartX, contentRect.top());
|
||||
painter->setFont(gemNameFont);
|
||||
painter->setPen(m_textColor);
|
||||
gemNameRect = painter->boundingRect(gemNameRect, Qt::TextSingleLine, gemName);
|
||||
@@ -131,7 +138,7 @@ namespace O3DE::ProjectManager
|
||||
QString gemCreator = GemModel::GetCreator(modelIndex);
|
||||
gemCreator = standardFontMetrics.elidedText(gemCreator, Qt::TextElideMode::ElideRight, firstColumnMaxTextWidth);
|
||||
QRect gemCreatorRect = GetTextRect(standardFont, gemCreator, s_fontSize);
|
||||
gemCreatorRect.moveTo(contentRect.left(), contentRect.top() + gemNameRect.height());
|
||||
gemCreatorRect.moveTo(firstColumnTextStartX, contentRect.top() + gemNameRect.height());
|
||||
|
||||
painter->setFont(standardFont);
|
||||
gemCreatorRect = painter->boundingRect(gemCreatorRect, Qt::TextSingleLine, gemCreator);
|
||||
@@ -157,10 +164,13 @@ namespace O3DE::ProjectManager
|
||||
const int featureTagAreaHeight = 30;
|
||||
const int summaryHeight = contentRect.height() - (hasTags * featureTagAreaHeight);
|
||||
|
||||
const int additionalSummarySpacing = s_itemMargins.right() * 3;
|
||||
const QSize summarySize = QSize(contentRect.width() - s_summaryStartX - s_buttonWidth - additionalSummarySpacing,
|
||||
const auto [summaryStartX, summaryEndX] = CalcColumnXBounds(HeaderOrder::Summary);
|
||||
|
||||
const QSize summarySize =
|
||||
QSize(summaryEndX - summaryStartX - AdjustableHeaderWidget::s_headerTextIndent - s_extraSummarySpacing,
|
||||
summaryHeight);
|
||||
return QRect(QPoint(contentRect.left() + s_summaryStartX, contentRect.top()), summarySize);
|
||||
return QRect(
|
||||
QPoint(s_itemMargins.left() + summaryStartX + AdjustableHeaderWidget::s_headerTextIndent, contentRect.top()), summarySize);
|
||||
}
|
||||
|
||||
QSize GemItemDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& modelIndex) const
|
||||
@@ -169,7 +179,7 @@ namespace O3DE::ProjectManager
|
||||
initStyleOption(&options, modelIndex);
|
||||
|
||||
int marginsHorizontal = s_itemMargins.left() + s_itemMargins.right() + s_contentMargins.left() + s_contentMargins.right();
|
||||
return QSize(marginsHorizontal + s_buttonWidth + s_summaryStartX, s_height);
|
||||
return QSize(marginsHorizontal + s_buttonWidth + s_defaultSummaryStartX, s_height);
|
||||
}
|
||||
|
||||
bool GemItemDelegate::editorEvent(QEvent* event, QAbstractItemModel* model, const QStyleOptionViewItem& option, const QModelIndex& modelIndex)
|
||||
@@ -299,9 +309,17 @@ namespace O3DE::ProjectManager
|
||||
return QFontMetrics(font).boundingRect(text);
|
||||
}
|
||||
|
||||
QPair<int, int> GemItemDelegate::CalcColumnXBounds(HeaderOrder header) const
|
||||
{
|
||||
return m_headerWidget->CalcColumnXBounds(static_cast<int>(header));
|
||||
}
|
||||
|
||||
QRect GemItemDelegate::CalcButtonRect(const QRect& contentRect) const
|
||||
{
|
||||
const QPoint topLeft = QPoint(contentRect.right() - s_buttonWidth, contentRect.center().y() - s_buttonHeight / 2);
|
||||
const QPoint topLeft = QPoint(
|
||||
s_itemMargins.left() + CalcColumnXBounds(HeaderOrder::Status).first + AdjustableHeaderWidget::s_headerTextIndent + s_statusIconSize +
|
||||
s_statusButtonSpacing,
|
||||
contentRect.center().y() - s_buttonHeight / 2);
|
||||
const QSize size = QSize(s_buttonWidth, s_buttonHeight);
|
||||
return QRect(topLeft, size);
|
||||
}
|
||||
@@ -331,18 +349,23 @@ namespace O3DE::ProjectManager
|
||||
}
|
||||
}
|
||||
|
||||
void GemItemDelegate::DrawFeatureTags(QPainter* painter, const QRect& contentRect, const QStringList& featureTags, const QFont& standardFont, const QRect& summaryRect) const
|
||||
void GemItemDelegate::DrawFeatureTags(
|
||||
QPainter* painter,
|
||||
const QRect& contentRect,
|
||||
const QStringList& featureTags,
|
||||
const QFont& standardFont,
|
||||
const QRect& summaryRect) const
|
||||
{
|
||||
QFont gemFeatureTagFont(standardFont);
|
||||
gemFeatureTagFont.setPixelSize(s_featureTagFontSize);
|
||||
gemFeatureTagFont.setBold(false);
|
||||
painter->setFont(gemFeatureTagFont);
|
||||
|
||||
int x = s_summaryStartX;
|
||||
int x = CalcColumnXBounds(HeaderOrder::Summary).first + AdjustableHeaderWidget::s_headerTextIndent;
|
||||
for (const QString& featureTag : featureTags)
|
||||
{
|
||||
QRect featureTagRect = GetTextRect(gemFeatureTagFont, featureTag, s_featureTagFontSize);
|
||||
featureTagRect.moveTo(contentRect.left() + x + s_featureTagBorderMarginX,
|
||||
featureTagRect.moveTo(s_itemMargins.left() + x + s_featureTagBorderMarginX,
|
||||
contentRect.top() + 47);
|
||||
featureTagRect = painter->boundingRect(featureTagRect, Qt::TextSingleLine, featureTag);
|
||||
|
||||
|
||||
@@ -19,13 +19,15 @@ QT_FORWARD_DECLARE_CLASS(QEvent)
|
||||
|
||||
namespace O3DE::ProjectManager
|
||||
{
|
||||
QT_FORWARD_DECLARE_CLASS(AdjustableHeaderWidget)
|
||||
|
||||
class GemItemDelegate
|
||||
: public QStyledItemDelegate
|
||||
{
|
||||
Q_OBJECT // AUTOMOC
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit GemItemDelegate(QAbstractItemModel* model, QObject* parent = nullptr);
|
||||
explicit GemItemDelegate(QAbstractItemModel* model, AdjustableHeaderWidget* header, QObject* parent = nullptr);
|
||||
~GemItemDelegate() = default;
|
||||
|
||||
void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& modelIndex) const override;
|
||||
@@ -45,12 +47,13 @@ namespace O3DE::ProjectManager
|
||||
inline constexpr static int s_height = 105; // Gem item total height
|
||||
inline constexpr static qreal s_gemNameFontSize = 13.0;
|
||||
inline constexpr static qreal s_fontSize = 12.0;
|
||||
inline constexpr static int s_summaryStartX = 150;
|
||||
inline constexpr static int s_defaultSummaryStartX = 190;
|
||||
|
||||
// Margin and borders
|
||||
inline constexpr static QMargins s_itemMargins = QMargins(/*left=*/16, /*top=*/8, /*right=*/16, /*bottom=*/8); // Item border distances
|
||||
inline constexpr static QMargins s_contentMargins = QMargins(/*left=*/20, /*top=*/12, /*right=*/20, /*bottom=*/12); // Distances of the elements within an item to the item borders
|
||||
inline constexpr static int s_borderWidth = 4;
|
||||
inline constexpr static int s_extraSummarySpacing = s_itemMargins.right();
|
||||
|
||||
// Button
|
||||
inline constexpr static int s_buttonWidth = 32;
|
||||
@@ -65,6 +68,13 @@ namespace O3DE::ProjectManager
|
||||
inline constexpr static int s_featureTagBorderMarginY = 3;
|
||||
inline constexpr static int s_featureTagSpacing = 7;
|
||||
|
||||
enum class HeaderOrder
|
||||
{
|
||||
Name,
|
||||
Summary,
|
||||
Status
|
||||
};
|
||||
|
||||
signals:
|
||||
void MovieStartedPlaying(const QMovie* playingMovie) const;
|
||||
|
||||
@@ -74,13 +84,20 @@ namespace O3DE::ProjectManager
|
||||
|
||||
void CalcRects(const QStyleOptionViewItem& option, QRect& outFullRect, QRect& outItemRect, QRect& outContentRect) const;
|
||||
QRect GetTextRect(QFont& font, const QString& text, qreal fontSize) const;
|
||||
QPair<int, int> CalcColumnXBounds(HeaderOrder header) const;
|
||||
QRect CalcButtonRect(const QRect& contentRect) const;
|
||||
QRect CalcSummaryRect(const QRect& contentRect, bool hasTags) const;
|
||||
void DrawPlatformIcons(QPainter* painter, const QRect& contentRect, const QModelIndex& modelIndex) const;
|
||||
void DrawButton(QPainter* painter, const QRect& buttonRect, const QModelIndex& modelIndex) const;
|
||||
void DrawFeatureTags(QPainter* painter, const QRect& contentRect, const QStringList& featureTags, const QFont& standardFont, const QRect& summaryRect) const;
|
||||
void DrawFeatureTags(
|
||||
QPainter* painter,
|
||||
const QRect& contentRect,
|
||||
const QStringList& featureTags,
|
||||
const QFont& standardFont,
|
||||
const QRect& summaryRect) const;
|
||||
void DrawText(const QString& text, QPainter* painter, const QRect& rect, const QFont& standardFont) const;
|
||||
void DrawDownloadStatusIcon(QPainter* painter, const QRect& contentRect, const QRect& buttonRect, const QModelIndex& modelIndex) const;
|
||||
void DrawDownloadStatusIcon(
|
||||
QPainter* painter, const QRect& contentRect, const QRect& buttonRect, const QModelIndex& modelIndex) const;
|
||||
|
||||
QAbstractItemModel* m_model = nullptr;
|
||||
|
||||
@@ -100,5 +117,7 @@ namespace O3DE::ProjectManager
|
||||
QPixmap m_downloadSuccessfulPixmap;
|
||||
QPixmap m_downloadFailedPixmap;
|
||||
QMovie* m_downloadingMovie = nullptr;
|
||||
|
||||
AdjustableHeaderWidget* m_headerWidget = nullptr;
|
||||
};
|
||||
} // namespace O3DE::ProjectManager
|
||||
|
||||
@@ -78,37 +78,9 @@ namespace O3DE::ProjectManager
|
||||
// Separating line
|
||||
QFrame* hLine = new QFrame();
|
||||
hLine->setFrameShape(QFrame::HLine);
|
||||
hLine->setStyleSheet("color: #666666;");
|
||||
hLine->setObjectName("horizontalSeparatingLine");
|
||||
vLayout->addWidget(hLine);
|
||||
|
||||
vLayout->addSpacing(GemItemDelegate::s_contentMargins.top());
|
||||
|
||||
// Bottom section
|
||||
QHBoxLayout* columnHeaderLayout = new QHBoxLayout();
|
||||
columnHeaderLayout->setAlignment(Qt::AlignLeft);
|
||||
|
||||
const int gemNameStartX = GemItemDelegate::s_itemMargins.left() + GemItemDelegate::s_contentMargins.left() - 1;
|
||||
columnHeaderLayout->addSpacing(gemNameStartX);
|
||||
|
||||
QLabel* gemNameLabel = new QLabel(tr("Gem Name"));
|
||||
gemNameLabel->setObjectName("GemCatalogHeaderLabel");
|
||||
columnHeaderLayout->addWidget(gemNameLabel);
|
||||
|
||||
columnHeaderLayout->addSpacing(89);
|
||||
|
||||
QLabel* gemSummaryLabel = new QLabel(tr("Gem Summary"));
|
||||
gemSummaryLabel->setObjectName("GemCatalogHeaderLabel");
|
||||
columnHeaderLayout->addWidget(gemSummaryLabel);
|
||||
|
||||
QSpacerItem* horizontalSpacer = new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Minimum);
|
||||
columnHeaderLayout->addSpacerItem(horizontalSpacer);
|
||||
|
||||
QLabel* gemSelectedLabel = new QLabel(tr("Status"));
|
||||
gemSelectedLabel->setObjectName("GemCatalogHeaderLabel");
|
||||
columnHeaderLayout->addWidget(gemSelectedLabel);
|
||||
|
||||
columnHeaderLayout->addSpacing(72);
|
||||
|
||||
vLayout->addLayout(columnHeaderLayout);
|
||||
}
|
||||
} // namespace O3DE::ProjectManager
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace O3DE::ProjectManager
|
||||
class GemListHeaderWidget
|
||||
: public QFrame
|
||||
{
|
||||
Q_OBJECT // AUTOMOC
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit GemListHeaderWidget(GemSortFilterProxyModel* proxyModel, QWidget* parent = nullptr);
|
||||
|
||||
@@ -8,12 +8,15 @@
|
||||
|
||||
#include <GemCatalog/GemListView.h>
|
||||
#include <GemCatalog/GemItemDelegate.h>
|
||||
#include <AdjustableHeaderWidget.h>
|
||||
|
||||
#include <QMovie>
|
||||
#include <QHeaderView>
|
||||
|
||||
namespace O3DE::ProjectManager
|
||||
{
|
||||
GemListView::GemListView(QAbstractItemModel* model, QItemSelectionModel* selectionModel, QWidget* parent)
|
||||
GemListView::GemListView(
|
||||
QAbstractItemModel* model, QItemSelectionModel* selectionModel, AdjustableHeaderWidget* header, QWidget* parent)
|
||||
: QListView(parent)
|
||||
{
|
||||
setObjectName("GemCatalogListView");
|
||||
@@ -21,7 +24,7 @@ namespace O3DE::ProjectManager
|
||||
|
||||
setModel(model);
|
||||
setSelectionModel(selectionModel);
|
||||
GemItemDelegate* itemDelegate = new GemItemDelegate(model, this);
|
||||
GemItemDelegate* itemDelegate = new GemItemDelegate(model, header, this);
|
||||
|
||||
connect(itemDelegate, &GemItemDelegate::MovieStartedPlaying, [=](const QMovie* playingMovie)
|
||||
{
|
||||
@@ -31,6 +34,8 @@ namespace O3DE::ProjectManager
|
||||
this->viewport()->repaint();
|
||||
});
|
||||
});
|
||||
|
||||
connect(header, &AdjustableHeaderWidget::sectionsResized, [=] { update(); });
|
||||
|
||||
setItemDelegate(itemDelegate);
|
||||
}
|
||||
|
||||
@@ -16,13 +16,15 @@
|
||||
|
||||
namespace O3DE::ProjectManager
|
||||
{
|
||||
QT_FORWARD_DECLARE_CLASS(AdjustableHeaderWidget)
|
||||
|
||||
class GemListView
|
||||
: public QListView
|
||||
{
|
||||
Q_OBJECT // AUTOMOC
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit GemListView(QAbstractItemModel* model, QItemSelectionModel* selectionModel, QWidget* parent = nullptr);
|
||||
explicit GemListView(QAbstractItemModel* model, QItemSelectionModel* selectionModel, AdjustableHeaderWidget* header, QWidget* parent = nullptr);
|
||||
~GemListView() = default;
|
||||
};
|
||||
} // namespace O3DE::ProjectManager
|
||||
|
||||
@@ -21,7 +21,7 @@ namespace O3DE::ProjectManager
|
||||
class GemModel
|
||||
: public QStandardItemModel
|
||||
{
|
||||
Q_OBJECT // AUTOMOC
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit GemModel(QObject* parent = nullptr);
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
namespace O3DE::ProjectManager
|
||||
{
|
||||
GemRequirementDelegate::GemRequirementDelegate(QAbstractItemModel* model, QObject* parent)
|
||||
: GemItemDelegate(model, parent)
|
||||
: GemItemDelegate(model, nullptr, parent)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ namespace O3DE::ProjectManager
|
||||
// Gem name
|
||||
QString gemName = GemModel::GetDisplayName(modelIndex);
|
||||
QFont gemNameFont(options.font);
|
||||
const int firstColumnMaxTextWidth = s_summaryStartX - 30;
|
||||
const int firstColumnMaxTextWidth = s_defaultSummaryStartX - 30;
|
||||
gemName = QFontMetrics(gemNameFont).elidedText(gemName, Qt::TextElideMode::ElideRight, firstColumnMaxTextWidth);
|
||||
gemNameFont.setPixelSize(static_cast<int>(s_gemNameFontSize));
|
||||
gemNameFont.setBold(true);
|
||||
@@ -75,8 +75,8 @@ namespace O3DE::ProjectManager
|
||||
|
||||
QRect GemRequirementDelegate::CalcRequirementRect(const QRect& contentRect) const
|
||||
{
|
||||
const QSize requirementSize = QSize(contentRect.width() - s_summaryStartX - s_itemMargins.right(), contentRect.height());
|
||||
return QRect(QPoint(contentRect.left() + s_summaryStartX, contentRect.top()), requirementSize);
|
||||
const QSize requirementSize = QSize(contentRect.width() - s_defaultSummaryStartX - s_itemMargins.right(), contentRect.height());
|
||||
return QRect(QPoint(contentRect.left() + s_defaultSummaryStartX, contentRect.top()), requirementSize);
|
||||
}
|
||||
|
||||
bool GemRequirementDelegate::editorEvent(
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace O3DE::ProjectManager
|
||||
class GemRequirementDelegate
|
||||
: public GemItemDelegate
|
||||
{
|
||||
Q_OBJECT // AUTOMOC
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit GemRequirementDelegate(QAbstractItemModel* model, QObject* parent = nullptr);
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace O3DE::ProjectManager
|
||||
class GemRequirementDialog
|
||||
: public QDialog
|
||||
{
|
||||
Q_OBJECT // AUTOMOC
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit GemRequirementDialog(GemModel* model, QWidget *parent = nullptr);
|
||||
~GemRequirementDialog() = default;
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace O3DE::ProjectManager
|
||||
class GemRequirementFilterProxyModel
|
||||
: public QSortFilterProxyModel
|
||||
{
|
||||
Q_OBJECT // AUTOMOC
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
GemRequirementFilterProxyModel(GemModel* sourceModel, QObject* parent = nullptr);
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace O3DE::ProjectManager
|
||||
class GemRequirementListView
|
||||
: public QListView
|
||||
{
|
||||
Q_OBJECT // AUTOMOC
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit GemRequirementListView(QAbstractItemModel* model, QItemSelectionModel* selectionModel, QWidget* parent = nullptr);
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace O3DE::ProjectManager
|
||||
class GemSortFilterProxyModel
|
||||
: public QSortFilterProxyModel
|
||||
{
|
||||
Q_OBJECT // AUTOMOC
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
enum class GemSelected
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace O3DE::ProjectManager
|
||||
class GemUninstallDialog
|
||||
: public QDialog
|
||||
{
|
||||
Q_OBJECT // AUTOMOC
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit GemUninstallDialog(const QString& gemName, QWidget *parent = nullptr);
|
||||
~GemUninstallDialog() = default;
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace O3DE::ProjectManager
|
||||
class GemUpdateDialog
|
||||
: public QDialog
|
||||
{
|
||||
Q_OBJECT // AUTOMOC
|
||||
Q_OBJECT
|
||||
public :
|
||||
explicit GemUpdateDialog(const QString& gemName, bool updateAvaliable = true, QWidget* parent = nullptr);
|
||||
~GemUpdateDialog() = default;
|
||||
|
||||
@@ -26,7 +26,7 @@ namespace O3DE::ProjectManager
|
||||
{
|
||||
class GemRepoInspector : public QScrollArea
|
||||
{
|
||||
Q_OBJECT // AUTOMOC
|
||||
Q_OBJECT
|
||||
|
||||
public : explicit GemRepoInspector(GemRepoModel* model, QWidget* parent = nullptr);
|
||||
~GemRepoInspector() = default;
|
||||
|
||||
@@ -9,16 +9,19 @@
|
||||
#include <GemRepo/GemRepoItemDelegate.h>
|
||||
#include <GemRepo/GemRepoModel.h>
|
||||
#include <ProjectManagerDefs.h>
|
||||
#include <AdjustableHeaderWidget.h>
|
||||
|
||||
#include <QEvent>
|
||||
#include <QPainter>
|
||||
#include <QMouseEvent>
|
||||
#include <QHeaderView>
|
||||
|
||||
namespace O3DE::ProjectManager
|
||||
{
|
||||
GemRepoItemDelegate::GemRepoItemDelegate(QAbstractItemModel* model, QObject* parent)
|
||||
GemRepoItemDelegate::GemRepoItemDelegate(QAbstractItemModel* model, AdjustableHeaderWidget* header, QObject* parent)
|
||||
: QStyledItemDelegate(parent)
|
||||
, m_model(model)
|
||||
, m_headerWidget(header)
|
||||
{
|
||||
m_refreshIcon = QIcon(":/Refresh.svg").pixmap(s_refreshIconSize, s_refreshIconSize);
|
||||
m_editIcon = QIcon(":/Edit.svg").pixmap(s_iconSize, s_iconSize);
|
||||
@@ -69,44 +72,55 @@ namespace O3DE::ProjectManager
|
||||
painter->restore();
|
||||
}
|
||||
|
||||
int currentHorizontalOffset = CalcColumnXBounds(HeaderOrder::Name).first;
|
||||
|
||||
// Repo name
|
||||
QString repoName = GemRepoModel::GetName(modelIndex);
|
||||
repoName = QFontMetrics(standardFont).elidedText(repoName, Qt::TextElideMode::ElideRight, s_nameMaxWidth);
|
||||
int sectionSize = m_headerWidget->m_header->sectionSize(static_cast<int>(HeaderOrder::Name));
|
||||
repoName = standardFontMetrics.elidedText(repoName, Qt::TextElideMode::ElideRight,
|
||||
sectionSize - AdjustableHeaderWidget::s_headerTextIndent);
|
||||
|
||||
QRect repoNameRect = GetTextRect(standardFont, repoName, s_fontSize);
|
||||
int currentHorizontalOffset = contentRect.left();
|
||||
repoNameRect.moveTo(currentHorizontalOffset, contentRect.center().y() - repoNameRect.height() / 2);
|
||||
repoNameRect.moveTo(currentHorizontalOffset + AdjustableHeaderWidget::s_headerTextIndent,
|
||||
contentRect.center().y() - repoNameRect.height() / 2);
|
||||
repoNameRect = painter->boundingRect(repoNameRect, Qt::TextSingleLine, repoName);
|
||||
|
||||
painter->drawText(repoNameRect, Qt::TextSingleLine, repoName);
|
||||
|
||||
// Rem repo creator
|
||||
currentHorizontalOffset += sectionSize;
|
||||
sectionSize = m_headerWidget->m_header->sectionSize(static_cast<int>(HeaderOrder::Creator));
|
||||
|
||||
QString repoCreator = GemRepoModel::GetCreator(modelIndex);
|
||||
repoCreator = standardFontMetrics.elidedText(repoCreator, Qt::TextElideMode::ElideRight, s_creatorMaxWidth);
|
||||
repoCreator = standardFontMetrics.elidedText(repoCreator, Qt::TextElideMode::ElideRight,
|
||||
sectionSize - AdjustableHeaderWidget::s_headerTextIndent);
|
||||
|
||||
QRect repoCreatorRect = GetTextRect(standardFont, repoCreator, s_fontSize);
|
||||
currentHorizontalOffset += s_nameMaxWidth + s_contentSpacing;
|
||||
repoCreatorRect.moveTo(currentHorizontalOffset, contentRect.center().y() - repoCreatorRect.height() / 2);
|
||||
repoCreatorRect.moveTo(currentHorizontalOffset + AdjustableHeaderWidget::s_headerTextIndent,
|
||||
contentRect.center().y() - repoCreatorRect.height() / 2);
|
||||
repoCreatorRect = painter->boundingRect(repoCreatorRect, Qt::TextSingleLine, repoCreator);
|
||||
|
||||
painter->drawText(repoCreatorRect, Qt::TextSingleLine, repoCreator);
|
||||
|
||||
// Repo update
|
||||
currentHorizontalOffset += sectionSize;
|
||||
sectionSize = m_headerWidget->m_header->sectionSize(static_cast<int>(HeaderOrder::Update));
|
||||
|
||||
QString repoUpdatedDate = GemRepoModel::GetLastUpdated(modelIndex).toString(RepoTimeFormat);
|
||||
repoUpdatedDate = standardFontMetrics.elidedText(repoUpdatedDate, Qt::TextElideMode::ElideRight, s_updatedMaxWidth);
|
||||
repoUpdatedDate = standardFontMetrics.elidedText(
|
||||
repoUpdatedDate, Qt::TextElideMode::ElideRight,
|
||||
sectionSize - GemRepoItemDelegate::s_refreshIconSpacing - GemRepoItemDelegate::s_refreshIconSize - AdjustableHeaderWidget::s_headerTextIndent);
|
||||
|
||||
QRect repoUpdatedDateRect = GetTextRect(standardFont, repoUpdatedDate, s_fontSize);
|
||||
currentHorizontalOffset += s_creatorMaxWidth + s_contentSpacing;
|
||||
repoUpdatedDateRect.moveTo(currentHorizontalOffset, contentRect.center().y() - repoUpdatedDateRect.height() / 2);
|
||||
repoUpdatedDateRect.moveTo(currentHorizontalOffset + AdjustableHeaderWidget::s_headerTextIndent,
|
||||
contentRect.center().y() - repoUpdatedDateRect.height() / 2);
|
||||
repoUpdatedDateRect = painter->boundingRect(repoUpdatedDateRect, Qt::TextSingleLine, repoUpdatedDate);
|
||||
|
||||
painter->drawText(repoUpdatedDateRect, Qt::TextSingleLine, repoUpdatedDate);
|
||||
|
||||
// Draw refresh button
|
||||
painter->drawPixmap(
|
||||
repoUpdatedDateRect.left() + s_updatedMaxWidth + s_refreshIconSpacing,
|
||||
contentRect.center().y() - s_refreshIconSize / 3, // Dividing size by 3 centers much better
|
||||
m_refreshIcon);
|
||||
const QRect refreshButtonRect = CalcRefreshButtonRect(contentRect);
|
||||
painter->drawPixmap(refreshButtonRect.topLeft(), m_refreshIcon);
|
||||
|
||||
if (options.state & QStyle::State_MouseOver)
|
||||
{
|
||||
@@ -121,8 +135,8 @@ namespace O3DE::ProjectManager
|
||||
QStyleOptionViewItem options(option);
|
||||
initStyleOption(&options, modelIndex);
|
||||
|
||||
int marginsHorizontal = s_itemMargins.left() + s_itemMargins.right() + s_contentMargins.left() + s_contentMargins.right();
|
||||
return QSize(marginsHorizontal + s_nameMaxWidth + s_creatorMaxWidth + s_updatedMaxWidth + s_contentSpacing * 3, s_height);
|
||||
const int marginsHorizontal = s_itemMargins.left() + s_itemMargins.right() + s_contentMargins.left() + s_contentMargins.right();
|
||||
return QSize(marginsHorizontal + s_nameDefaultWidth + s_creatorDefaultWidth + s_updatedDefaultWidth, s_height);
|
||||
}
|
||||
|
||||
bool GemRepoItemDelegate::editorEvent(QEvent* event, QAbstractItemModel* model, const QStyleOptionViewItem& option, const QModelIndex& modelIndex)
|
||||
@@ -185,22 +199,31 @@ namespace O3DE::ProjectManager
|
||||
return QFontMetrics(font).boundingRect(text);
|
||||
}
|
||||
|
||||
QPair<int, int> GemRepoItemDelegate::CalcColumnXBounds(HeaderOrder header) const
|
||||
{
|
||||
return m_headerWidget->CalcColumnXBounds(static_cast<int>(header));
|
||||
}
|
||||
|
||||
QRect GemRepoItemDelegate::CalcDeleteButtonRect(const QRect& contentRect) const
|
||||
{
|
||||
const QPoint topLeft = QPoint(contentRect.right() - s_iconSize, contentRect.center().y() - s_iconSize / 2);
|
||||
const int deleteHeaderEndX = CalcColumnXBounds(HeaderOrder::Delete).second;
|
||||
const QPoint topLeft = QPoint(deleteHeaderEndX - s_iconSize - s_contentMargins.right(), contentRect.center().y() - s_iconSize / 2);
|
||||
return QRect(topLeft, QSize(s_iconSize, s_iconSize));
|
||||
}
|
||||
|
||||
QRect GemRepoItemDelegate::CalcRefreshButtonRect(const QRect& contentRect) const
|
||||
{
|
||||
const int topLeftX = contentRect.left() + s_nameMaxWidth + s_creatorMaxWidth + s_updatedMaxWidth + s_contentSpacing * 2 + s_refreshIconSpacing;
|
||||
const QPoint topLeft = QPoint(topLeftX, contentRect.center().y() - s_refreshIconSize / 3);
|
||||
const int headerEndX = CalcColumnXBounds(HeaderOrder::Update).second;
|
||||
const int leftX = headerEndX - s_refreshIconSize - s_refreshIconSpacing;
|
||||
// Dividing size by 3 centers much better
|
||||
const QPoint topLeft = QPoint(leftX, contentRect.center().y() - s_refreshIconSize / 3);
|
||||
return QRect(topLeft, QSize(s_refreshIconSize, s_refreshIconSize));
|
||||
}
|
||||
|
||||
void GemRepoItemDelegate::DrawEditButtons(QPainter* painter, const QRect& contentRect) const
|
||||
{
|
||||
painter->drawPixmap(contentRect.right() - s_iconSize, contentRect.center().y() - s_iconSize / 2, m_deleteIcon);
|
||||
const QRect deleteButtonRect = CalcDeleteButtonRect(contentRect);
|
||||
painter->drawPixmap(deleteButtonRect, m_deleteIcon);
|
||||
}
|
||||
|
||||
} // namespace O3DE::ProjectManager
|
||||
|
||||
@@ -18,13 +18,15 @@ QT_FORWARD_DECLARE_CLASS(QEvent)
|
||||
|
||||
namespace O3DE::ProjectManager
|
||||
{
|
||||
QT_FORWARD_DECLARE_CLASS(AdjustableHeaderWidget)
|
||||
|
||||
class GemRepoItemDelegate
|
||||
: public QStyledItemDelegate
|
||||
{
|
||||
Q_OBJECT // AUTOMOC
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit GemRepoItemDelegate(QAbstractItemModel* model, QObject* parent = nullptr);
|
||||
explicit GemRepoItemDelegate(QAbstractItemModel* model, AdjustableHeaderWidget* header, QObject* parent = nullptr);
|
||||
~GemRepoItemDelegate() = default;
|
||||
|
||||
void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& modelIndex) const override;
|
||||
@@ -42,15 +44,14 @@ namespace O3DE::ProjectManager
|
||||
inline constexpr static qreal s_fontSize = 12.0;
|
||||
|
||||
// Margin and borders
|
||||
inline constexpr static QMargins s_itemMargins = QMargins(/*left=*/0, /*top=*/8, /*right=*/60, /*bottom=*/8); // Item border distances
|
||||
inline constexpr static QMargins s_itemMargins = QMargins(/*left=*/0, /*top=*/8, /*right=*/0, /*bottom=*/8); // Item border distances
|
||||
inline constexpr static QMargins s_contentMargins = QMargins(/*left=*/20, /*top=*/20, /*right=*/20, /*bottom=*/20); // Distances of the elements within an item to the item borders
|
||||
inline constexpr static int s_borderWidth = 4;
|
||||
|
||||
// Content
|
||||
inline constexpr static int s_contentSpacing = 5;
|
||||
inline constexpr static int s_nameMaxWidth = 145;
|
||||
inline constexpr static int s_creatorMaxWidth = 115;
|
||||
inline constexpr static int s_updatedMaxWidth = 125;
|
||||
inline constexpr static int s_nameDefaultWidth = 150;
|
||||
inline constexpr static int s_creatorDefaultWidth = 120;
|
||||
inline constexpr static int s_updatedDefaultWidth = 130;
|
||||
|
||||
// Icon
|
||||
inline constexpr static int s_iconSize = 24;
|
||||
@@ -58,6 +59,14 @@ namespace O3DE::ProjectManager
|
||||
inline constexpr static int s_refreshIconSize = 14;
|
||||
inline constexpr static int s_refreshIconSpacing = 10;
|
||||
|
||||
enum class HeaderOrder
|
||||
{
|
||||
Name,
|
||||
Creator,
|
||||
Update,
|
||||
Delete
|
||||
};
|
||||
|
||||
signals:
|
||||
void RemoveRepo(const QModelIndex& modelIndex);
|
||||
void RefreshRepo(const QModelIndex& modelIndex);
|
||||
@@ -65,13 +74,15 @@ namespace O3DE::ProjectManager
|
||||
protected:
|
||||
void CalcRects(const QStyleOptionViewItem& option, QRect& outFullRect, QRect& outItemRect, QRect& outContentRect) const;
|
||||
QRect GetTextRect(QFont& font, const QString& text, qreal fontSize) const;
|
||||
QRect CalcButtonRect(const QRect& contentRect) const;
|
||||
QPair<int, int> CalcColumnXBounds(HeaderOrder header) const;
|
||||
QRect CalcDeleteButtonRect(const QRect& contentRect) const;
|
||||
QRect CalcRefreshButtonRect(const QRect& contentRect) const;
|
||||
void DrawEditButtons(QPainter* painter, const QRect& contentRect) const;
|
||||
|
||||
QAbstractItemModel* m_model = nullptr;
|
||||
|
||||
AdjustableHeaderWidget* m_headerWidget = nullptr;
|
||||
|
||||
QPixmap m_refreshIcon;
|
||||
QPixmap m_editIcon;
|
||||
QPixmap m_deleteIcon;
|
||||
|
||||
@@ -8,12 +8,15 @@
|
||||
|
||||
#include <GemRepo/GemRepoListView.h>
|
||||
#include <GemRepo/GemRepoItemDelegate.h>
|
||||
#include <AdjustableHeaderWidget.h>
|
||||
|
||||
#include <QShortcut>
|
||||
#include <QHeaderView>
|
||||
|
||||
namespace O3DE::ProjectManager
|
||||
{
|
||||
GemRepoListView::GemRepoListView(QAbstractItemModel* model, QItemSelectionModel* selectionModel, QWidget* parent)
|
||||
GemRepoListView::GemRepoListView(
|
||||
QAbstractItemModel* model, QItemSelectionModel* selectionModel, AdjustableHeaderWidget* header, QWidget* parent)
|
||||
: QListView(parent)
|
||||
{
|
||||
setObjectName("gemRepoListView");
|
||||
@@ -22,9 +25,10 @@ namespace O3DE::ProjectManager
|
||||
setModel(model);
|
||||
setSelectionModel(selectionModel);
|
||||
|
||||
GemRepoItemDelegate* itemDelegate = new GemRepoItemDelegate(model, this);
|
||||
GemRepoItemDelegate* itemDelegate = new GemRepoItemDelegate(model, header, this);
|
||||
connect(itemDelegate, &GemRepoItemDelegate::RemoveRepo, this, &GemRepoListView::RemoveRepo);
|
||||
connect(itemDelegate, &GemRepoItemDelegate::RefreshRepo, this, &GemRepoListView::RefreshRepo);
|
||||
connect(header, &AdjustableHeaderWidget::sectionsResized, [=] { update(); });
|
||||
setItemDelegate(itemDelegate);
|
||||
}
|
||||
} // namespace O3DE::ProjectManager
|
||||
|
||||
@@ -17,13 +17,19 @@ QT_FORWARD_DECLARE_CLASS(QAbstractItemModel)
|
||||
|
||||
namespace O3DE::ProjectManager
|
||||
{
|
||||
QT_FORWARD_DECLARE_CLASS(AdjustableHeaderWidget)
|
||||
|
||||
class GemRepoListView
|
||||
: public QListView
|
||||
{
|
||||
Q_OBJECT // AUTOMOC
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit GemRepoListView(QAbstractItemModel* model, QItemSelectionModel* selectionModel, QWidget* parent = nullptr);
|
||||
explicit GemRepoListView(
|
||||
QAbstractItemModel* model,
|
||||
QItemSelectionModel* selectionModel,
|
||||
AdjustableHeaderWidget* header,
|
||||
QWidget* parent = nullptr);
|
||||
~GemRepoListView() = default;
|
||||
|
||||
signals:
|
||||
|
||||
@@ -21,7 +21,7 @@ namespace O3DE::ProjectManager
|
||||
class GemRepoModel
|
||||
: public QStandardItemModel
|
||||
{
|
||||
Q_OBJECT // AUTOMOC
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit GemRepoModel(QObject* parent = nullptr);
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
#include <PythonBindingsInterface.h>
|
||||
#include <ProjectManagerDefs.h>
|
||||
#include <ProjectUtils.h>
|
||||
#include <AdjustableHeaderWidget.h>
|
||||
#include <ProjectManagerDefs.h>
|
||||
|
||||
#include <QVBoxLayout>
|
||||
#include <QHBoxLayout>
|
||||
@@ -248,6 +250,9 @@ namespace O3DE::ProjectManager
|
||||
|
||||
QFrame* GemRepoScreen::CreateReposContent()
|
||||
{
|
||||
constexpr int inspectorWidth = 240;
|
||||
constexpr int middleLayoutIndent = 60;
|
||||
|
||||
QFrame* contentFrame = new QFrame(this);
|
||||
|
||||
QHBoxLayout* hLayout = new QHBoxLayout();
|
||||
@@ -255,7 +260,7 @@ namespace O3DE::ProjectManager
|
||||
hLayout->setSpacing(0);
|
||||
contentFrame->setLayout(hLayout);
|
||||
|
||||
hLayout->addSpacing(60);
|
||||
hLayout->addSpacing(middleLayoutIndent);
|
||||
|
||||
QVBoxLayout* middleVLayout = new QVBoxLayout();
|
||||
middleVLayout->setMargin(0);
|
||||
@@ -287,37 +292,34 @@ namespace O3DE::ProjectManager
|
||||
|
||||
connect(addRepoButton, &QPushButton::clicked, this, &GemRepoScreen::HandleAddRepoButton);
|
||||
|
||||
topMiddleHLayout->addSpacing(30);
|
||||
|
||||
middleVLayout->addLayout(topMiddleHLayout);
|
||||
|
||||
middleVLayout->addSpacing(30);
|
||||
|
||||
// Create a QTableWidget just for its header
|
||||
// Using a seperate model allows the setup of a header exactly as needed
|
||||
m_gemRepoHeaderTable = new QTableWidget(this);
|
||||
m_gemRepoHeaderTable->setObjectName("gemRepoHeaderTable");
|
||||
m_gemRepoListHeader = m_gemRepoHeaderTable->horizontalHeader();
|
||||
m_gemRepoListHeader->setObjectName("gemRepoListHeader");
|
||||
m_gemRepoListHeader->setDefaultAlignment(Qt::AlignLeft);
|
||||
m_gemRepoListHeader->setSectionResizeMode(QHeaderView::ResizeMode::Fixed);
|
||||
constexpr int minHeaderSectionWidth = 120;
|
||||
|
||||
// Insert columns so the header labels will show up
|
||||
m_gemRepoHeaderTable->insertColumn(0);
|
||||
m_gemRepoHeaderTable->insertColumn(1);
|
||||
m_gemRepoHeaderTable->insertColumn(2);
|
||||
m_gemRepoHeaderTable->setHorizontalHeaderLabels({ tr("Repository Name"), tr("Creator"), tr("Updated") });
|
||||
m_gemRepoHeaderTable = new AdjustableHeaderWidget(
|
||||
QStringList{ tr("Repository Name"), tr("Creator"), tr("Updated"), "" },
|
||||
QVector<int>{
|
||||
GemRepoItemDelegate::s_nameDefaultWidth,
|
||||
GemRepoItemDelegate::s_creatorDefaultWidth,
|
||||
GemRepoItemDelegate::s_updatedDefaultWidth + GemRepoItemDelegate::s_refreshIconSpacing + GemRepoItemDelegate::s_refreshIconSize,
|
||||
// Include invisible header for delete button
|
||||
GemRepoItemDelegate::s_iconSize + GemRepoItemDelegate::s_contentMargins.right()
|
||||
},
|
||||
minHeaderSectionWidth,
|
||||
QVector<QHeaderView::ResizeMode>
|
||||
{
|
||||
QHeaderView::ResizeMode::Interactive,
|
||||
QHeaderView::ResizeMode::Stretch,
|
||||
QHeaderView::ResizeMode::Fixed,
|
||||
QHeaderView::ResizeMode::Fixed
|
||||
},
|
||||
this);
|
||||
|
||||
const int headerExtraMargin = 18;
|
||||
m_gemRepoListHeader->resizeSection(0, GemRepoItemDelegate::s_nameMaxWidth + GemRepoItemDelegate::s_contentSpacing + headerExtraMargin);
|
||||
m_gemRepoListHeader->resizeSection(1, GemRepoItemDelegate::s_creatorMaxWidth + GemRepoItemDelegate::s_contentSpacing);
|
||||
m_gemRepoListHeader->resizeSection(2, GemRepoItemDelegate::s_updatedMaxWidth + GemRepoItemDelegate::s_contentSpacing);
|
||||
|
||||
// Required to set stylesheet in code as it will not be respected if set in qss
|
||||
m_gemRepoHeaderTable->horizontalHeader()->setStyleSheet("QHeaderView::section { background-color:transparent; color:white; font-size:12px; border-style:none; }");
|
||||
middleVLayout->addWidget(m_gemRepoHeaderTable);
|
||||
|
||||
m_gemRepoListView = new GemRepoListView(m_gemRepoModel, m_gemRepoModel->GetSelectionModel(), this);
|
||||
m_gemRepoListView = new GemRepoListView(m_gemRepoModel, m_gemRepoModel->GetSelectionModel(), m_gemRepoHeaderTable, this);
|
||||
middleVLayout->addWidget(m_gemRepoListView);
|
||||
|
||||
connect(m_gemRepoListView, &GemRepoListView::RemoveRepo, this, &GemRepoScreen::HandleRemoveRepoButton);
|
||||
@@ -325,8 +327,10 @@ namespace O3DE::ProjectManager
|
||||
|
||||
hLayout->addLayout(middleVLayout);
|
||||
|
||||
hLayout->addSpacing(middleLayoutIndent);
|
||||
|
||||
m_gemRepoInspector = new GemRepoInspector(m_gemRepoModel, this);
|
||||
m_gemRepoInspector->setFixedWidth(240);
|
||||
m_gemRepoInspector->setFixedWidth(inspectorWidth);
|
||||
hLayout->addWidget(m_gemRepoInspector);
|
||||
|
||||
return contentFrame;
|
||||
|
||||
@@ -24,6 +24,7 @@ namespace O3DE::ProjectManager
|
||||
QT_FORWARD_DECLARE_CLASS(GemRepoInspector)
|
||||
QT_FORWARD_DECLARE_CLASS(GemRepoListView)
|
||||
QT_FORWARD_DECLARE_CLASS(GemRepoModel)
|
||||
QT_FORWARD_DECLARE_CLASS(AdjustableHeaderWidget)
|
||||
|
||||
class GemRepoScreen
|
||||
: public ScreenWidget
|
||||
@@ -59,7 +60,7 @@ namespace O3DE::ProjectManager
|
||||
QFrame* m_noRepoContent;
|
||||
QFrame* m_repoContent;
|
||||
|
||||
QTableWidget* m_gemRepoHeaderTable = nullptr;
|
||||
AdjustableHeaderWidget* m_gemRepoHeaderTable = nullptr;
|
||||
QHeaderView* m_gemRepoListHeader = nullptr;
|
||||
GemRepoListView* m_gemRepoListView = nullptr;
|
||||
GemRepoInspector* m_gemRepoInspector = nullptr;
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace O3DE::ProjectManager
|
||||
class GemsSubWidget
|
||||
: public QWidget
|
||||
{
|
||||
Q_OBJECT // AUTOMOC
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
GemsSubWidget(QWidget* parent = nullptr);
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace O3DE::ProjectManager
|
||||
class LinkLabel
|
||||
: public QLabel
|
||||
{
|
||||
Q_OBJECT // AUTOMOC
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
LinkLabel(const QString& text = {}, const QUrl& url = {}, int fontSize = 10, QWidget* parent = nullptr);
|
||||
|
||||
@@ -31,7 +31,7 @@ namespace O3DE::ProjectManager
|
||||
class LabelButton
|
||||
: public QLabel
|
||||
{
|
||||
Q_OBJECT // AUTOMOC
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit LabelButton(QWidget* parent = nullptr);
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
namespace O3DE::ProjectManager
|
||||
{
|
||||
inline constexpr static int MinWindowWidth = 1200;
|
||||
inline constexpr static int ProjectPreviewImageWidth = 210;
|
||||
inline constexpr static int ProjectPreviewImageHeight = 280;
|
||||
inline constexpr static int ProjectTemplateImageWidth = 92;
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace O3DE::ProjectManager
|
||||
class ScreenHeader
|
||||
: public QFrame
|
||||
{
|
||||
Q_OBJECT // AUTOMOC
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ScreenHeader(QWidget* parent = nullptr);
|
||||
|
||||
@@ -27,7 +27,7 @@ namespace O3DE::ProjectManager
|
||||
class TagWidget
|
||||
: public QLabel
|
||||
{
|
||||
Q_OBJECT // AUTOMOC
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit TagWidget(const Tag& id, QWidget* parent = nullptr);
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace O3DE::ProjectManager
|
||||
class TemplateButton
|
||||
: public QPushButton
|
||||
{
|
||||
Q_OBJECT // AUTOMOC
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit TemplateButton(const QString& imagePath, const QString& labelText, QWidget* parent = nullptr);
|
||||
|
||||
@@ -81,6 +81,8 @@ set(FILES
|
||||
Source/TemplateButtonWidget.cpp
|
||||
Source/ExternalLinkDialog.h
|
||||
Source/ExternalLinkDialog.cpp
|
||||
Source/AdjustableHeaderWidget.h
|
||||
Source/AdjustableHeaderWidget.cpp
|
||||
Source/GemCatalog/GemCatalogHeaderWidget.h
|
||||
Source/GemCatalog/GemCatalogHeaderWidget.cpp
|
||||
Source/GemCatalog/GemCatalogScreen.h
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
#include <Atom/Feature/TransformService/TransformServiceFeatureProcessor.h>
|
||||
#include <Atom/Feature/Mesh/MeshFeatureProcessor.h>
|
||||
#include <Atom/Feature/LookupTable/LookupTableAsset.h>
|
||||
#include <Atom/Feature/ReflectionProbe/ReflectionProbeFeatureProcessor.h>
|
||||
#include <ReflectionProbe/ReflectionProbeFeatureProcessor.h>
|
||||
#include <RayTracing/RayTracingFeatureProcessor.h>
|
||||
|
||||
#include <Atom/Feature/ImageBasedLights/ImageBasedLightFeatureProcessor.h>
|
||||
|
||||
@@ -11,11 +11,11 @@
|
||||
#include <Atom/Feature/RenderCommon.h>
|
||||
#include <Atom/Feature/Mesh/MeshFeatureProcessor.h>
|
||||
#include <Atom/Feature/Mesh/ModelReloaderSystemInterface.h>
|
||||
#include <Atom/Feature/ReflectionProbe/ReflectionProbeFeatureProcessor.h>
|
||||
#include <Atom/RPI.Public/Model/ModelLodUtils.h>
|
||||
#include <Atom/RPI.Public/Scene.h>
|
||||
#include <Atom/RPI.Public/Culling.h>
|
||||
#include <Atom/Utils/StableDynamicArray.h>
|
||||
#include <ReflectionProbe/ReflectionProbeFeatureProcessor.h>
|
||||
|
||||
#include <Atom/RPI.Reflect/Model/ModelAssetCreator.h>
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
*/
|
||||
|
||||
#include <ReflectionProbe/ReflectionProbe.h>
|
||||
#include <Atom/Feature/ReflectionProbe/ReflectionProbeFeatureProcessor.h>
|
||||
#include <ReflectionProbe/ReflectionProbeFeatureProcessor.h>
|
||||
#include <Atom/RHI/RHISystemInterface.h>
|
||||
#include <Atom/RPI.Public/Pass/Pass.h>
|
||||
#include <Atom/RPI.Public/Pass/PassSystemInterface.h>
|
||||
|
||||
+2
-1
@@ -6,12 +6,13 @@
|
||||
*
|
||||
*/
|
||||
|
||||
#include <ReflectionProbe/ReflectionProbeFeatureProcessor.h>
|
||||
|
||||
#include <AzCore/Serialization/SerializeContext.h>
|
||||
#include <Atom/RPI.Public/RPIUtils.h>
|
||||
#include <Atom/RPI.Public/Scene.h>
|
||||
#include <Atom/RPI.Public/View.h>
|
||||
#include <Atom/RPI.Reflect/Asset/AssetUtils.h>
|
||||
#include <Atom/Feature/ReflectionProbe/ReflectionProbeFeatureProcessor.h>
|
||||
#include <Atom/Feature/Mesh/MeshFeatureProcessor.h>
|
||||
#include <Atom/RHI/Factory.h>
|
||||
#include <Atom/RHI/RHISystemInterface.h>
|
||||
|
||||
@@ -31,7 +31,7 @@ set(FILES
|
||||
Include/Atom/Feature/PostProcessing/PostProcessingConstants.h
|
||||
Include/Atom/Feature/PostProcessing/SMAAFeatureProcessorInterface.h
|
||||
Include/Atom/Feature/PostProcess/PostFxLayerCategoriesConstants.h
|
||||
Include/Atom/Feature/ReflectionProbe/ReflectionProbeFeatureProcessor.h
|
||||
Include/Atom/Feature/ReflectionProbe/ReflectionProbeFeatureProcessorInterface.h
|
||||
Include/Atom/Feature/SkyBox/SkyBoxFogBus.h
|
||||
Include/Atom/Feature/SkyBox/SkyboxConstants.h
|
||||
Include/Atom/Feature/SkyBox/SkyBoxLUT.h
|
||||
@@ -272,7 +272,9 @@ set(FILES
|
||||
Source/RayTracing/RayTracingPass.cpp
|
||||
Source/RayTracing/RayTracingPass.h
|
||||
Source/RayTracing/RayTracingPassData.h
|
||||
Source/ReflectionProbe/ReflectionProbeFeatureProcessor.h
|
||||
Source/ReflectionProbe/ReflectionProbeFeatureProcessor.cpp
|
||||
Source/ReflectionProbe/ReflectionProbe.h
|
||||
Source/ReflectionProbe/ReflectionProbe.cpp
|
||||
Source/ReflectionScreenSpace/ReflectionScreenSpaceTracePass.cpp
|
||||
Source/ReflectionScreenSpace/ReflectionScreenSpaceTracePass.h
|
||||
|
||||
@@ -312,8 +312,11 @@ namespace AZ
|
||||
|
||||
const View::UsageFlags viewFlags = worklistData->m_view->GetUsageFlags();
|
||||
const RHI::DrawListMask drawListMask = worklistData->m_view->GetDrawListMask();
|
||||
[[maybe_unused]] uint32_t numDrawPackets = 0;
|
||||
uint32_t numVisibleCullables = 0;
|
||||
#ifdef AZ_CULL_DEBUG_ENABLED
|
||||
// These variable are only used for the gathering of debug information.
|
||||
uint32_t numDrawPackets = 0;
|
||||
uint32_t numVisibleCullables = 0;
|
||||
#endif
|
||||
|
||||
AZ_Assert(worklist.size() > 0, "Received empty worklist in ProcessWorklist");
|
||||
|
||||
@@ -351,8 +354,15 @@ namespace AZ
|
||||
if (TestOcclusionCulling(worklistData, visibleEntry) == MaskedOcclusionCulling::CullingResult::VISIBLE)
|
||||
#endif
|
||||
{
|
||||
numDrawPackets += AddLodDataToView(c->m_cullData.m_boundingSphere.GetCenter(), c->m_lodData, *worklistData->m_view);
|
||||
++numVisibleCullables;
|
||||
// There are ways to write this without [[maybe_unused]], but they are brittle.
|
||||
// For example, using #else could cause a bug where the function's parameter
|
||||
// is changed in #ifdef but not in #else.
|
||||
[[maybe_unused]] const uint32_t drawPacketCount=AddLodDataToView(c->m_cullData.m_boundingSphere.GetCenter(), c->m_lodData, *worklistData->m_view);
|
||||
#ifdef AZ_CULL_DEBUG_ENABLED
|
||||
++numVisibleCullables;
|
||||
numDrawPackets += drawPacketCount;
|
||||
#endif
|
||||
|
||||
c->m_isVisible = true;
|
||||
}
|
||||
}
|
||||
@@ -387,8 +397,15 @@ namespace AZ
|
||||
if (TestOcclusionCulling(worklistData, visibleEntry) == MaskedOcclusionCulling::CullingResult::VISIBLE)
|
||||
#endif
|
||||
{
|
||||
numDrawPackets += AddLodDataToView(c->m_cullData.m_boundingSphere.GetCenter(), c->m_lodData, *worklistData->m_view);
|
||||
++numVisibleCullables;
|
||||
// There are ways to write this without [[maybe_unused]], but they are brittle.
|
||||
// For example, using #else could cause a bug where the function's parameter
|
||||
// is changed in #ifdef but not in #else.
|
||||
[[maybe_unused]] const uint32_t drawPacketCount=AddLodDataToView(c->m_cullData.m_boundingSphere.GetCenter(), c->m_lodData, *worklistData->m_view);
|
||||
#ifdef AZ_CULL_DEBUG_ENABLED
|
||||
++numVisibleCullables;
|
||||
numDrawPackets += drawPacketCount;
|
||||
#endif
|
||||
|
||||
c->m_isVisible = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,6 +123,10 @@ def update_manifest(scene):
|
||||
meshGroup = sceneManifest.add_mesh_group(meshGroupName)
|
||||
meshGroup['id'] = '{' + str(uuid.uuid5(uuid.NAMESPACE_DNS, sourceFilenameOnly + chunkPath)) + '}'
|
||||
sceneManifest.mesh_group_select_node(meshGroup, chunkPath)
|
||||
# un-select all other mesh nodes
|
||||
for otherMeshIndex, otherMeshName in enumerate(meshNameList):
|
||||
if otherMeshName is not chunkName:
|
||||
sceneManifest.mesh_group_unselect_node(meshGroup, otherMeshName.get_path())
|
||||
|
||||
# combine both scene manifests so the OnPrepareForExport will be called
|
||||
originalManifest = json.loads(scene.manifest.ExportToJson())
|
||||
|
||||
@@ -72,8 +72,9 @@ namespace Terrain
|
||||
if (auto serialize = azrtti_cast<AZ::SerializeContext*>(context))
|
||||
{
|
||||
serialize->Class<TerrainPhysicsColliderConfig, AZ::ComponentConfig>()
|
||||
->Version(2)->Field(
|
||||
"Mappings", &TerrainPhysicsColliderConfig::m_surfaceMaterialMappings)
|
||||
->Version(3)
|
||||
->Field("DefaultMaterial", &TerrainPhysicsColliderConfig::m_defaultMaterialSelection)
|
||||
->Field("Mappings", &TerrainPhysicsColliderConfig::m_surfaceMaterialMappings)
|
||||
;
|
||||
|
||||
if (auto edit = serialize->GetEditContext())
|
||||
@@ -84,6 +85,8 @@ namespace Terrain
|
||||
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
|
||||
->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly)
|
||||
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
|
||||
->DataElement(AZ::Edit::UIHandlers::Default, &TerrainPhysicsColliderConfig::m_defaultMaterialSelection,
|
||||
"Default Surface Physics Material", "Select a material to be used by unmapped surfaces by default")
|
||||
->DataElement(
|
||||
AZ::Edit::UIHandlers::Default, &TerrainPhysicsColliderConfig::m_surfaceMaterialMappings,
|
||||
"Surface to Material Mappings", "Maps surfaces to physics materials")
|
||||
@@ -321,7 +324,7 @@ namespace Terrain
|
||||
}
|
||||
|
||||
// If this surface isn't mapped, use the default material.
|
||||
return Physics::MaterialId();
|
||||
return m_configuration.m_defaultMaterialSelection.GetMaterialId();
|
||||
}
|
||||
|
||||
void TerrainPhysicsColliderComponent::GenerateHeightsAndMaterialsInBounds(
|
||||
@@ -419,7 +422,7 @@ namespace Terrain
|
||||
AZStd::vector<Physics::MaterialId> materialList;
|
||||
|
||||
// Ensure the list contains the default material as the first entry.
|
||||
materialList.emplace_back(Physics::MaterialId());
|
||||
materialList.emplace_back(m_configuration.m_defaultMaterialSelection.GetMaterialId());
|
||||
|
||||
for (auto& mapping : m_configuration.m_surfaceMaterialMappings)
|
||||
{
|
||||
|
||||
@@ -48,7 +48,7 @@ namespace Terrain
|
||||
AZ_CLASS_ALLOCATOR(TerrainPhysicsColliderConfig, AZ::SystemAllocator, 0);
|
||||
AZ_RTTI(TerrainPhysicsColliderConfig, "{E9EADB8F-C3A5-4B9C-A62D-2DBC86B4CE59}", AZ::ComponentConfig);
|
||||
static void Reflect(AZ::ReflectContext* context);
|
||||
|
||||
Physics::MaterialSelection m_defaultMaterialSelection;
|
||||
AZStd::vector<TerrainPhysicsSurfaceMaterialMapping> m_surfaceMaterialMappings;
|
||||
};
|
||||
|
||||
|
||||
@@ -46,10 +46,17 @@ protected:
|
||||
appDesc.m_stackRecordLevels = 20;
|
||||
|
||||
m_app.Create(appDesc);
|
||||
|
||||
CreateEntity();
|
||||
|
||||
m_boxComponent = m_entity->CreateComponent<UnitTest::MockAxisAlignedBoxShapeComponent>();
|
||||
m_app.RegisterComponentDescriptor(m_boxComponent->CreateDescriptor());
|
||||
}
|
||||
|
||||
void TearDown() override
|
||||
{
|
||||
m_entity.reset();
|
||||
|
||||
m_app.Destroy();
|
||||
}
|
||||
|
||||
@@ -61,12 +68,9 @@ protected:
|
||||
m_entity->Init();
|
||||
}
|
||||
|
||||
void AddTerrainPhysicsColliderAndShapeComponentToEntity()
|
||||
void AddTerrainPhysicsColliderToEntity(const Terrain::TerrainPhysicsColliderConfig& configuration)
|
||||
{
|
||||
m_boxComponent = m_entity->CreateComponent<UnitTest::MockAxisAlignedBoxShapeComponent>();
|
||||
m_app.RegisterComponentDescriptor(m_boxComponent->CreateDescriptor());
|
||||
|
||||
m_colliderComponent = m_entity->CreateComponent<Terrain::TerrainPhysicsColliderComponent>(Terrain::TerrainPhysicsColliderConfig());
|
||||
m_colliderComponent = m_entity->CreateComponent<Terrain::TerrainPhysicsColliderComponent>(configuration);
|
||||
m_app.RegisterComponentDescriptor(m_colliderComponent->CreateDescriptor());
|
||||
}
|
||||
|
||||
@@ -113,21 +117,17 @@ protected:
|
||||
TEST_F(TerrainPhysicsColliderComponentTest, ActivateEntityActivateSuccess)
|
||||
{
|
||||
// Check that the entity activates with a collider and the required shape attached.
|
||||
CreateEntity();
|
||||
AddTerrainPhysicsColliderAndShapeComponentToEntity();
|
||||
AddTerrainPhysicsColliderToEntity(Terrain::TerrainPhysicsColliderConfig());
|
||||
|
||||
m_entity->Activate();
|
||||
EXPECT_EQ(m_entity->GetState(), AZ::Entity::State::Active);
|
||||
|
||||
m_entity.reset();
|
||||
}
|
||||
|
||||
TEST_F(TerrainPhysicsColliderComponentTest, TerrainPhysicsColliderTransformChangedNotifiesHeightfieldBus)
|
||||
{
|
||||
// Check that the HeightfieldBus is notified when the transform of the entity changes.
|
||||
CreateEntity();
|
||||
|
||||
AddTerrainPhysicsColliderAndShapeComponentToEntity();
|
||||
AddTerrainPhysicsColliderToEntity(Terrain::TerrainPhysicsColliderConfig());
|
||||
|
||||
m_entity->Activate();
|
||||
|
||||
@@ -138,16 +138,12 @@ TEST_F(TerrainPhysicsColliderComponentTest, TerrainPhysicsColliderTransformChang
|
||||
LmbrCentral::ShapeComponentNotificationsBus::Event(
|
||||
m_entity->GetId(), &LmbrCentral::ShapeComponentNotificationsBus::Events::OnShapeChanged,
|
||||
LmbrCentral::ShapeComponentNotifications::ShapeChangeReasons::TransformChanged);
|
||||
|
||||
m_entity.reset();
|
||||
}
|
||||
|
||||
TEST_F(TerrainPhysicsColliderComponentTest, TerrainPhysicsColliderShapeChangedNotifiesHeightfieldBus)
|
||||
{
|
||||
// Check that the Heightfield bus is notified when the shape component changes.
|
||||
CreateEntity();
|
||||
|
||||
AddTerrainPhysicsColliderAndShapeComponentToEntity();
|
||||
AddTerrainPhysicsColliderToEntity(Terrain::TerrainPhysicsColliderConfig());
|
||||
|
||||
m_entity->Activate();
|
||||
|
||||
@@ -157,16 +153,12 @@ TEST_F(TerrainPhysicsColliderComponentTest, TerrainPhysicsColliderShapeChangedNo
|
||||
LmbrCentral::ShapeComponentNotificationsBus::Event(
|
||||
m_entity->GetId(), &LmbrCentral::ShapeComponentNotificationsBus::Events::OnShapeChanged,
|
||||
LmbrCentral::ShapeComponentNotifications::ShapeChangeReasons::ShapeChanged);
|
||||
|
||||
m_entity.reset();
|
||||
}
|
||||
|
||||
TEST_F(TerrainPhysicsColliderComponentTest, TerrainPhysicsColliderReturnsAlignedRowBoundsCorrectly)
|
||||
{
|
||||
// Check that the heightfield grid size is correct when the shape bounds match the grid resolution.
|
||||
CreateEntity();
|
||||
|
||||
AddTerrainPhysicsColliderAndShapeComponentToEntity();
|
||||
AddTerrainPhysicsColliderToEntity(Terrain::TerrainPhysicsColliderConfig());
|
||||
|
||||
m_entity->Activate();
|
||||
|
||||
@@ -188,17 +180,13 @@ TEST_F(TerrainPhysicsColliderComponentTest, TerrainPhysicsColliderReturnsAligned
|
||||
// With the bounds set at 0-1024 and a resolution of 1.0, the heightfield grid should be 1024x1024.
|
||||
EXPECT_EQ(cols, 1024);
|
||||
EXPECT_EQ(rows, 1024);
|
||||
|
||||
m_entity.reset();
|
||||
}
|
||||
|
||||
TEST_F(TerrainPhysicsColliderComponentTest, TerrainPhysicsColliderExpandsMinBoundsCorrectly)
|
||||
{
|
||||
// Check that the heightfield grid is correctly expanded if the minimum value of the bounds needs expanding
|
||||
// to correctly encompass it.
|
||||
CreateEntity();
|
||||
|
||||
AddTerrainPhysicsColliderAndShapeComponentToEntity();
|
||||
AddTerrainPhysicsColliderToEntity(Terrain::TerrainPhysicsColliderConfig());
|
||||
|
||||
m_entity->Activate();
|
||||
|
||||
@@ -221,17 +209,13 @@ TEST_F(TerrainPhysicsColliderComponentTest, TerrainPhysicsColliderExpandsMinBoun
|
||||
// the values returned would be 1023.
|
||||
EXPECT_EQ(cols, 1024);
|
||||
EXPECT_EQ(rows, 1024);
|
||||
|
||||
m_entity.reset();
|
||||
}
|
||||
|
||||
TEST_F(TerrainPhysicsColliderComponentTest, TerrainPhysicsColliderExpandsMaxBoundsCorrectly)
|
||||
{
|
||||
// Check that the heightfield grid is correctly expanded if the maximum value of the bounds needs expanding
|
||||
// to correctly encompass it.
|
||||
CreateEntity();
|
||||
|
||||
AddTerrainPhysicsColliderAndShapeComponentToEntity();
|
||||
AddTerrainPhysicsColliderToEntity(Terrain::TerrainPhysicsColliderConfig());
|
||||
|
||||
m_entity->Activate();
|
||||
|
||||
@@ -254,16 +238,12 @@ TEST_F(TerrainPhysicsColliderComponentTest, TerrainPhysicsColliderExpandsMaxBoun
|
||||
// the values returned would be 1023.
|
||||
EXPECT_EQ(cols, 1024);
|
||||
EXPECT_EQ(rows, 1024);
|
||||
|
||||
m_entity.reset();
|
||||
}
|
||||
|
||||
TEST_F(TerrainPhysicsColliderComponentTest, TerrainPhysicsColliderGetHeightsReturnsHeights)
|
||||
{
|
||||
// Check that the TerrainPhysicsCollider returns a heightfield of the expected size.
|
||||
CreateEntity();
|
||||
|
||||
AddTerrainPhysicsColliderAndShapeComponentToEntity();
|
||||
AddTerrainPhysicsColliderToEntity(Terrain::TerrainPhysicsColliderConfig());
|
||||
|
||||
m_entity->Activate();
|
||||
|
||||
@@ -298,16 +278,12 @@ TEST_F(TerrainPhysicsColliderComponentTest, TerrainPhysicsColliderGetHeightsRetu
|
||||
EXPECT_EQ(cols, 1024);
|
||||
EXPECT_EQ(rows, 1024);
|
||||
EXPECT_EQ(heights.size(), cols * rows);
|
||||
|
||||
m_entity.reset();
|
||||
}
|
||||
|
||||
TEST_F(TerrainPhysicsColliderComponentTest, TerrainPhysicsColliderReturnsRelativeHeightsCorrectly)
|
||||
{
|
||||
// Check that the values stored in the heightfield returned by the TerrainPhysicsCollider are correct.
|
||||
CreateEntity();
|
||||
|
||||
AddTerrainPhysicsColliderAndShapeComponentToEntity();
|
||||
AddTerrainPhysicsColliderToEntity(Terrain::TerrainPhysicsColliderConfig());
|
||||
|
||||
m_entity->Activate();
|
||||
|
||||
@@ -341,18 +317,11 @@ TEST_F(TerrainPhysicsColliderComponentTest, TerrainPhysicsColliderReturnsRelativ
|
||||
|
||||
const float expectedHeightValue = 16384.0f;
|
||||
EXPECT_NEAR(heights[0], expectedHeightValue, 0.01f);
|
||||
|
||||
m_entity->Reset();
|
||||
}
|
||||
|
||||
TEST_F(TerrainPhysicsColliderComponentTest, TerrainPhysicsColliderReturnsMaterials)
|
||||
{
|
||||
// Check that the TerrainPhysicsCollider returns all the assigned materials.
|
||||
CreateEntity();
|
||||
|
||||
m_boxComponent = m_entity->CreateComponent<UnitTest::MockAxisAlignedBoxShapeComponent>();
|
||||
m_app.RegisterComponentDescriptor(m_boxComponent->CreateDescriptor());
|
||||
|
||||
// Create two SurfaceTag/Material mappings and add them to the collider.
|
||||
Terrain::TerrainPhysicsColliderConfig config;
|
||||
|
||||
@@ -372,8 +341,7 @@ TEST_F(TerrainPhysicsColliderComponentTest, TerrainPhysicsColliderReturnsMateria
|
||||
mapping2.m_surfaceTag = tag2;
|
||||
config.m_surfaceMaterialMappings.emplace_back(mapping2);
|
||||
|
||||
m_colliderComponent = m_entity->CreateComponent<Terrain::TerrainPhysicsColliderComponent>(config);
|
||||
m_app.RegisterComponentDescriptor(m_colliderComponent->CreateDescriptor());
|
||||
AddTerrainPhysicsColliderToEntity(config);
|
||||
|
||||
m_entity->Activate();
|
||||
|
||||
@@ -388,20 +356,12 @@ TEST_F(TerrainPhysicsColliderComponentTest, TerrainPhysicsColliderReturnsMateria
|
||||
EXPECT_EQ(materialList[0], defaultMaterial);
|
||||
EXPECT_EQ(materialList[1], mat1);
|
||||
EXPECT_EQ(materialList[2], mat2);
|
||||
|
||||
m_entity.reset();
|
||||
}
|
||||
|
||||
TEST_F(TerrainPhysicsColliderComponentTest, TerrainPhysicsColliderReturnsMaterialsWhenNotMapped)
|
||||
{
|
||||
// Check that the TerrainPhysicsCollider returns a default material when no surfaces are mapped.
|
||||
CreateEntity();
|
||||
|
||||
m_boxComponent = m_entity->CreateComponent<UnitTest::MockAxisAlignedBoxShapeComponent>();
|
||||
m_app.RegisterComponentDescriptor(m_boxComponent->CreateDescriptor());
|
||||
|
||||
m_colliderComponent = m_entity->CreateComponent<Terrain::TerrainPhysicsColliderComponent>();
|
||||
m_app.RegisterComponentDescriptor(m_colliderComponent->CreateDescriptor());
|
||||
AddTerrainPhysicsColliderToEntity(Terrain::TerrainPhysicsColliderConfig());
|
||||
|
||||
m_entity->Activate();
|
||||
|
||||
@@ -414,18 +374,11 @@ TEST_F(TerrainPhysicsColliderComponentTest, TerrainPhysicsColliderReturnsMateria
|
||||
|
||||
Physics::MaterialId defaultMaterial = Physics::MaterialId();
|
||||
EXPECT_EQ(materialList[0], defaultMaterial);
|
||||
|
||||
m_entity.reset();
|
||||
}
|
||||
|
||||
TEST_F(TerrainPhysicsColliderComponentTest, TerrainPhysicsColliderGetHeightsAndMaterialsReturnsCorrectly)
|
||||
{
|
||||
// Check that the TerrainPhysicsCollider returns a heightfield of the expected size.
|
||||
CreateEntity();
|
||||
|
||||
m_boxComponent = m_entity->CreateComponent<UnitTest::MockAxisAlignedBoxShapeComponent>();
|
||||
m_app.RegisterComponentDescriptor(m_boxComponent->CreateDescriptor());
|
||||
|
||||
// Create two SurfaceTag/Material mappings and add them to the collider.
|
||||
Terrain::TerrainPhysicsColliderConfig config;
|
||||
|
||||
@@ -445,8 +398,7 @@ TEST_F(TerrainPhysicsColliderComponentTest, TerrainPhysicsColliderGetHeightsAndM
|
||||
mapping2.m_surfaceTag = tag2;
|
||||
config.m_surfaceMaterialMappings.emplace_back(mapping2);
|
||||
|
||||
m_colliderComponent = m_entity->CreateComponent<Terrain::TerrainPhysicsColliderComponent>(config);
|
||||
m_app.RegisterComponentDescriptor(m_colliderComponent->CreateDescriptor());
|
||||
AddTerrainPhysicsColliderToEntity(config);
|
||||
|
||||
m_entity->Activate();
|
||||
|
||||
@@ -460,15 +412,10 @@ TEST_F(TerrainPhysicsColliderComponentTest, TerrainPhysicsColliderGetHeightsAndM
|
||||
const float mockHeight = 32768.0f;
|
||||
AZ::Vector2 mockHeightResolution = AZ::Vector2(1.0f);
|
||||
|
||||
AzFramework::SurfaceData::SurfaceTagWeight return1;
|
||||
return1.m_surfaceType = tag1;
|
||||
return1.m_weight = 1.0f;
|
||||
AzFramework::SurfaceData::SurfaceTagWeight tagWeight1(tag1, 1.0f);
|
||||
AzFramework::SurfaceData::SurfaceTagWeight tagWeight2(tag2, 1.0f);
|
||||
|
||||
AzFramework::SurfaceData::SurfaceTagWeight return2;
|
||||
return2.m_surfaceType = tag2;
|
||||
return2.m_weight = 1.0f;
|
||||
|
||||
AzFramework::SurfaceData::SurfaceTagWeightList surfaceTags = { return1, return2 };
|
||||
AzFramework::SurfaceData::SurfaceTagWeightList surfaceTags = { tagWeight1, tagWeight2 };
|
||||
|
||||
NiceMock<UnitTest::MockTerrainDataRequests> terrainListener;
|
||||
ON_CALL(terrainListener, GetTerrainHeightQueryResolution).WillByDefault(Return(mockHeightResolution));
|
||||
@@ -499,6 +446,148 @@ TEST_F(TerrainPhysicsColliderComponentTest, TerrainPhysicsColliderGetHeightsAndM
|
||||
// Check an entry from the second half of the list
|
||||
EXPECT_EQ(heightsAndMaterials[256 * 128].m_materialIndex, 2);
|
||||
EXPECT_NEAR(heightsAndMaterials[256 * 128].m_height, expectedHeightValue, 0.01f);
|
||||
|
||||
m_entity.reset();
|
||||
}
|
||||
|
||||
TEST_F(TerrainPhysicsColliderComponentTest, TerrainPhysicsColliderDefaultMaterialAssignedWhenTagHasNoMapping)
|
||||
{
|
||||
// Create two SurfaceTag/Material mappings and add them to the collider.
|
||||
Terrain::TerrainPhysicsColliderConfig config;
|
||||
|
||||
const Physics::MaterialId defaultSurfaceMaterial = Physics::MaterialId::Create();
|
||||
const Physics::MaterialId mat1 = Physics::MaterialId::Create();
|
||||
|
||||
const SurfaceData::SurfaceTag tag1 = SurfaceData::SurfaceTag("tag1");
|
||||
const SurfaceData::SurfaceTag tag2 = SurfaceData::SurfaceTag("tag2");
|
||||
|
||||
Terrain::TerrainPhysicsSurfaceMaterialMapping mapping1;
|
||||
mapping1.m_materialId = mat1;
|
||||
mapping1.m_surfaceTag = tag1;
|
||||
config.m_surfaceMaterialMappings.emplace_back(mapping1);
|
||||
config.m_defaultMaterialSelection.SetMaterialId(defaultSurfaceMaterial);
|
||||
|
||||
// Intentionally don't set the mapping for "tag2". It's expected the default material will substitute.
|
||||
AddTerrainPhysicsColliderToEntity(config);
|
||||
|
||||
m_entity->Activate();
|
||||
|
||||
// Validate material list is generated with the default material
|
||||
{
|
||||
AZStd::vector<Physics::MaterialId> materialList;
|
||||
Physics::HeightfieldProviderRequestsBus::EventResult(
|
||||
materialList, m_entity->GetId(), &Physics::HeightfieldProviderRequestsBus::Events::GetMaterialList);
|
||||
|
||||
// The materialList should be 2 items long: the default material and mat1.
|
||||
EXPECT_EQ(materialList.size(), 2);
|
||||
EXPECT_EQ(materialList[0], defaultSurfaceMaterial);
|
||||
EXPECT_EQ(materialList[1], mat1);
|
||||
}
|
||||
|
||||
const AZ::Vector3 boundsMin = AZ::Vector3(0.0f);
|
||||
const AZ::Vector3 boundsMax = AZ::Vector3(256.0f, 256.0f, 32768.0f);
|
||||
|
||||
NiceMock<UnitTest::MockShapeComponentRequests> boxShape(m_entity->GetId());
|
||||
const AZ::Aabb bounds = AZ::Aabb::CreateFromMinMax(boundsMin, boundsMax);
|
||||
ON_CALL(boxShape, GetEncompassingAabb).WillByDefault(Return(bounds));
|
||||
|
||||
const float mockHeight = 32768.0f;
|
||||
AZ::Vector2 mockHeightResolution = AZ::Vector2(1.0f);
|
||||
|
||||
AzFramework::SurfaceData::SurfaceTagWeight tagWeight1(tag1, 1.0f);
|
||||
AzFramework::SurfaceData::SurfaceTagWeight tagWeight2(tag2, 1.0f);
|
||||
|
||||
AzFramework::SurfaceData::SurfaceTagWeightList surfaceTags = { tagWeight1, tagWeight2 };
|
||||
|
||||
NiceMock<UnitTest::MockTerrainDataRequests> terrainListener;
|
||||
ON_CALL(terrainListener, GetTerrainHeightQueryResolution).WillByDefault(Return(mockHeightResolution));
|
||||
ON_CALL(terrainListener, ProcessSurfacePointsFromRegion).WillByDefault(
|
||||
[this, mockHeight, &surfaceTags](const AZ::Aabb& inRegion, const AZ::Vector2& stepSize,
|
||||
AzFramework::Terrain::SurfacePointRegionFillCallback perPositionCallback,
|
||||
[[maybe_unused]] AzFramework::Terrain::TerrainDataRequests::Sampler sampleFilter)
|
||||
{
|
||||
ProcessRegionLoop(inRegion, stepSize, perPositionCallback, &surfaceTags, mockHeight);
|
||||
}
|
||||
);
|
||||
|
||||
// Validate material indices
|
||||
{
|
||||
AZStd::vector<Physics::HeightMaterialPoint> heightsAndMaterials;
|
||||
Physics::HeightfieldProviderRequestsBus::EventResult(
|
||||
heightsAndMaterials, m_entity->GetId(), &Physics::HeightfieldProviderRequestsBus::Events::GetHeightsAndMaterials);
|
||||
|
||||
// We set the bounds to 256, so check that the correct number of entries are present.
|
||||
EXPECT_EQ(heightsAndMaterials.size(), 256 * 256);
|
||||
|
||||
// Check an entry from the first half of the returned list.
|
||||
EXPECT_EQ(heightsAndMaterials[0].m_materialIndex, 1);
|
||||
|
||||
// Check an entry from the second half of the list.
|
||||
// This should point to the default material (0) since we don't have a mapping for "tag2"
|
||||
EXPECT_EQ(heightsAndMaterials[256 * 128].m_materialIndex, 0);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(TerrainPhysicsColliderComponentTest, TerrainPhysicsColliderDefaultMaterialAssignedWhenNoMappingsExist)
|
||||
{
|
||||
// Create only the default material with no mapping for the tags. It's expected the default material will be assigned to both tags.
|
||||
Terrain::TerrainPhysicsColliderConfig config;
|
||||
const Physics::MaterialId defaultSurfaceMaterial = Physics::MaterialId::Create();
|
||||
config.m_defaultMaterialSelection.SetMaterialId(defaultSurfaceMaterial);
|
||||
AddTerrainPhysicsColliderToEntity(config);
|
||||
|
||||
m_entity->Activate();
|
||||
|
||||
// Validate material list is generated with the default material
|
||||
{
|
||||
AZStd::vector<Physics::MaterialId> materialList;
|
||||
Physics::HeightfieldProviderRequestsBus::EventResult(
|
||||
materialList, m_entity->GetId(), &Physics::HeightfieldProviderRequestsBus::Events::GetMaterialList);
|
||||
|
||||
EXPECT_EQ(materialList.size(), 1);
|
||||
EXPECT_EQ(materialList[0], defaultSurfaceMaterial);
|
||||
}
|
||||
|
||||
const AZ::Vector3 boundsMin = AZ::Vector3(0.0f);
|
||||
const AZ::Vector3 boundsMax = AZ::Vector3(256.0f, 256.0f, 32768.0f);
|
||||
|
||||
NiceMock<UnitTest::MockShapeComponentRequests> boxShape(m_entity->GetId());
|
||||
const AZ::Aabb bounds = AZ::Aabb::CreateFromMinMax(boundsMin, boundsMax);
|
||||
ON_CALL(boxShape, GetEncompassingAabb).WillByDefault(Return(bounds));
|
||||
|
||||
const float mockHeight = 32768.0f;
|
||||
AZ::Vector2 mockHeightResolution = AZ::Vector2(1.0f);
|
||||
|
||||
const SurfaceData::SurfaceTag tag1 = SurfaceData::SurfaceTag("tag1");
|
||||
AzFramework::SurfaceData::SurfaceTagWeight tagWeight1(tag1, 1.0f);
|
||||
|
||||
const SurfaceData::SurfaceTag tag2 = SurfaceData::SurfaceTag("tag2");
|
||||
AzFramework::SurfaceData::SurfaceTagWeight tagWeight2(tag2, 1.0f);
|
||||
|
||||
AzFramework::SurfaceData::SurfaceTagWeightList surfaceTags = { tagWeight1, tagWeight2 };
|
||||
|
||||
NiceMock<UnitTest::MockTerrainDataRequests> terrainListener;
|
||||
ON_CALL(terrainListener, GetTerrainHeightQueryResolution).WillByDefault(Return(mockHeightResolution));
|
||||
ON_CALL(terrainListener, ProcessSurfacePointsFromRegion).WillByDefault(
|
||||
[this, mockHeight, &surfaceTags](const AZ::Aabb& inRegion, const AZ::Vector2& stepSize,
|
||||
AzFramework::Terrain::SurfacePointRegionFillCallback perPositionCallback,
|
||||
[[maybe_unused]] AzFramework::Terrain::TerrainDataRequests::Sampler sampleFilter)
|
||||
{
|
||||
ProcessRegionLoop(inRegion, stepSize, perPositionCallback, &surfaceTags, mockHeight);
|
||||
}
|
||||
);
|
||||
|
||||
// Validate material indices
|
||||
{
|
||||
AZStd::vector<Physics::HeightMaterialPoint> heightsAndMaterials;
|
||||
Physics::HeightfieldProviderRequestsBus::EventResult(
|
||||
heightsAndMaterials, m_entity->GetId(), &Physics::HeightfieldProviderRequestsBus::Events::GetHeightsAndMaterials);
|
||||
|
||||
// We set the bounds to 256, so check that the correct number of entries are present.
|
||||
EXPECT_EQ(heightsAndMaterials.size(), 256 * 256);
|
||||
|
||||
// Check an entry from the first half of the returned list. Should be the default material index 0.
|
||||
EXPECT_EQ(heightsAndMaterials[0].m_materialIndex, 0);
|
||||
|
||||
// Check an entry from the second half of the list. Should be the default material index 0.
|
||||
EXPECT_EQ(heightsAndMaterials[256 * 128].m_materialIndex, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,11 +31,19 @@ IF NOT EXIST %OUTPUT_DIRECTORY% (
|
||||
mkdir %OUTPUT_DIRECTORY%
|
||||
)
|
||||
|
||||
REM Jenkins reports MSB8029 when TMP/TEMP is not defined, define a dummy folder
|
||||
SET TMP=%cd%/temp
|
||||
SET TEMP=%cd%/temp
|
||||
IF NOT EXIST %TMP% (
|
||||
mkdir %TMP%
|
||||
REM Jenkins does not defined TMP
|
||||
IF "%TMP%"=="" (
|
||||
IF "%WORKSPACE%"=="" (
|
||||
SET TMP=%APPDATA%\Local\Temp
|
||||
SET TEMP=%APPDATA%\Local\Temp
|
||||
) ELSE (
|
||||
SET TMP=%WORKSPACE%\Temp
|
||||
SET TEMP=%WORKSPACE%\Temp
|
||||
REM This folder may not be created in the workspace
|
||||
IF NOT EXIST "!TMP!" (
|
||||
MKDIR "!TMP!"
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
REM Optionally sign the APK if we are generating an APK
|
||||
|
||||
Reference in New Issue
Block a user