Merge branch 'development' of https://github.com/aws-lumberyard-dev/o3de into mnaumov/2372

This commit is contained in:
mnaumov
2021-08-16 15:23:34 -07:00
878 changed files with 10002 additions and 25721 deletions
@@ -600,8 +600,8 @@ namespace AzToolsFramework
* Open 3D Engine Internal use only.
*
* Run a specific redo command separate from the undo/redo system.
* In many cases before a modifcation on an entity takes place, it is first packaged into
* undo/redo commands. Running the modification's redo command separete from the undo/redo
* In many cases before a modification on an entity takes place, it is first packaged into
* undo/redo commands. Running the modification's redo command separate from the undo/redo
* system simulates its execution, and avoids some code duplication.
*/
virtual void RunRedoSeparately(UndoSystem::URSequencePoint* redoCommand) = 0;
@@ -841,9 +841,6 @@ namespace AzToolsFramework
*/
virtual AZStd::string GetComponentIconPath(const AZ::Uuid& /*componentType*/, AZ::Crc32 /*componentIconAttrib*/, AZ::Component* /*component*/) { return AZStd::string(); }
/// Resource Selector hook, returns a path for a resource.
virtual AZStd::string SelectResource(const AZStd::string& /*resourceType*/, const AZStd::string& /*previousValue*/) { return AZStd::string(); }
/**
* Calculate the navigation 2D radius in units of an agent given its Navigation Type Name
* @param angentTypeName the name that identifies the agent navigation type
@@ -8,7 +8,7 @@
#pragma once
#include <AzCore/RTTI/TypeInfo.h>
#include <AzCore/RTTI/TypeInfoSimple.h>
#include <QRect>
#include <QKeySequence>
@@ -1781,5 +1781,4 @@ namespace AzToolsFramework
{
appType.m_maskValue = AZ::ApplicationTypeQuery::Masks::Tool;
};
} // namespace AzToolsFramework
@@ -11,7 +11,7 @@
#include <AzCore/Asset/AssetManagerBus.h>
#include <AzCore/IO/FileIO.h>
#include <AzCore/Outcome/Outcome.h>
#include <AzCore/RTTI/TypeInfo.h>
#include <AzCore/RTTI/TypeInfoSimple.h>
#include <AzCore/std/containers/map.h>
#include <AzCore/std/containers/set.h>
#include <AzCore/std/containers/vector.h>
@@ -46,7 +46,6 @@ namespace AzToolsFramework
bool InstanceToTemplatePropagator::GenerateDomForEntity(PrefabDom& generatedEntityDom, const AZ::Entity& entity)
{
//grab the owning instance so we can use the entityIdMapper in settings
InstanceOptionalReference owningInstance = m_instanceEntityMapperInterface->FindOwningInstance(entity.GetId());
if (!owningInstance)
@@ -54,19 +53,8 @@ namespace AzToolsFramework
AZ_Error("Prefab", false, "Entity does not belong to an instance");
return false;
}
InstanceEntityIdMapper entityIdMapper;
entityIdMapper.SetStoringInstance(owningInstance->get());
//create settings so that the serialized entity dom undergoes mapping from entity id to entity alias
AZ::JsonSerializerSettings settings;
settings.m_metadata.Add(static_cast<AZ::JsonEntityIdSerializer::JsonEntityIdMapper*>(&entityIdMapper));
//generate PrefabDom using Json serialization system
AZ::JsonSerializationResult::ResultCode result = AZ::JsonSerialization::Store(
generatedEntityDom, generatedEntityDom.GetAllocator(), entity, settings);
return result.GetOutcome() == AZ::JsonSerializationResult::Outcomes::Success;
return PrefabDomUtils::StoreEntityInPrefabDomFormat(entity, owningInstance->get(), generatedEntityDom);
}
bool InstanceToTemplatePropagator::GenerateDomForInstance(PrefabDom& generatedInstanceDom, const Prefab::Instance& instance)
@@ -185,8 +173,10 @@ namespace AzToolsFramework
else
{
AZ_Error(
"Prefab", result.GetOutcome() != AZ::JsonSerializationResult::Outcomes::PartialSkip,
"Some of the patches are not successfully applied.");
"Prefab",
(result.GetOutcome() != AZ::JsonSerializationResult::Outcomes::Skipped) &&
(result.GetOutcome() != AZ::JsonSerializationResult::Outcomes::PartialSkip),
"Some of the patches were not successfully applied.");
m_prefabSystemComponentInterface->SetTemplateDirtyFlag(templateId, true);
m_prefabSystemComponentInterface->PropagateTemplateChanges(templateId, instanceToExclude);
return true;
@@ -196,7 +196,8 @@ namespace AzToolsFramework
m_sourceTemplateId, m_targetTemplateId);
return false;
}
if (applyPatchResult.GetOutcome() == AZ::JsonSerializationResult::Outcomes::PartialSkip)
if (applyPatchResult.GetOutcome() == AZ::JsonSerializationResult::Outcomes::PartialSkip ||
applyPatchResult.GetOutcome() == AZ::JsonSerializationResult::Outcomes::Skipped)
{
AZ_Error(
"Prefab", false,
@@ -26,6 +26,30 @@ namespace AzToolsFramework
{
namespace PrefabDomUtils
{
namespace Internal
{
AZ::JsonSerializationResult::ResultCode JsonIssueReporter(AZStd::string& scratchBuffer,
AZStd::string_view message, AZ::JsonSerializationResult::ResultCode result, AZStd::string_view path)
{
namespace JSR = AZ::JsonSerializationResult;
if (result.GetProcessing() == JSR::Processing::Halted)
{
scratchBuffer.append(message.begin(), message.end());
scratchBuffer.append("\n Reason: ");
result.AppendToString(scratchBuffer, path);
scratchBuffer.append(".");
AZ_Warning("Prefab Serialization", false, "%s", scratchBuffer.c_str());
scratchBuffer.clear();
return JSR::ResultCode(result.GetTask(), JSR::Outcomes::Skipped);
}
return result;
}
}
PrefabDomValueReference FindPrefabDomValue(PrefabDomValue& parentValue, const char* valueName)
{
PrefabDomValue::MemberIterator valueIterator = parentValue.FindMember(valueName);
@@ -48,7 +72,7 @@ namespace AzToolsFramework
return valueIterator->value;
}
bool StoreInstanceInPrefabDom(const Instance& instance, PrefabDom& prefabDom, StoreInstanceFlags flags)
bool StoreInstanceInPrefabDom(const Instance& instance, PrefabDom& prefabDom, StoreFlags flags)
{
InstanceEntityIdMapper entityIdMapper;
entityIdMapper.SetStoringInstance(instance);
@@ -59,17 +83,27 @@ namespace AzToolsFramework
settings.m_metadata.Add(static_cast<AZ::JsonEntityIdSerializer::JsonEntityIdMapper*>(&entityIdMapper));
settings.m_metadata.Add(&entityIdMapper);
if ((flags & StoreInstanceFlags::StripDefaultValues) != StoreInstanceFlags::StripDefaultValues)
if ((flags & StoreFlags::StripDefaultValues) != StoreFlags::StripDefaultValues)
{
settings.m_keepDefaults = true;
}
if ((flags & StoreInstanceFlags::StoreLinkIds) != StoreInstanceFlags::None)
if ((flags & StoreFlags::StoreLinkIds) != StoreFlags::None)
{
LinkIdMetadata linkIdMetadata;
settings.m_metadata.Add(&linkIdMetadata);
}
AZStd::string scratchBuffer;
auto issueReportingCallback = [&scratchBuffer]
(AZStd::string_view message, AZ::JsonSerializationResult::ResultCode result,
AZStd::string_view path) -> AZ::JsonSerializationResult::ResultCode
{
return Internal::JsonIssueReporter(scratchBuffer, message, result, path);
};
settings.m_reporting = AZStd::move(issueReportingCallback);
AZ::JsonSerializationResult::ResultCode result =
AZ::JsonSerialization::Store(prefabDom, prefabDom.GetAllocator(), instance, settings);
@@ -86,7 +120,38 @@ namespace AzToolsFramework
return true;
}
bool LoadInstanceFromPrefabDom(Instance& instance, const PrefabDom& prefabDom, LoadInstanceFlags flags)
bool StoreEntityInPrefabDomFormat(const AZ::Entity& entity, Instance& owningInstance, PrefabDom& prefabDom, StoreFlags flags)
{
InstanceEntityIdMapper entityIdMapper;
entityIdMapper.SetStoringInstance(owningInstance);
//create settings so that the serialized entity dom undergoes mapping from entity id to entity alias
AZ::JsonSerializerSettings settings;
settings.m_metadata.Add(static_cast<AZ::JsonEntityIdSerializer::JsonEntityIdMapper*>(&entityIdMapper));
if ((flags & StoreFlags::StripDefaultValues) != StoreFlags::StripDefaultValues)
{
settings.m_keepDefaults = true;
}
AZStd::string scratchBuffer;
auto issueReportingCallback = [&scratchBuffer]
(AZStd::string_view message, AZ::JsonSerializationResult::ResultCode result,
AZStd::string_view path) -> AZ::JsonSerializationResult::ResultCode
{
return Internal::JsonIssueReporter(scratchBuffer, message, result, path);
};
settings.m_reporting = AZStd::move(issueReportingCallback);
//generate PrefabDom using Json serialization system
AZ::JsonSerializationResult::ResultCode result = AZ::JsonSerialization::Store(
prefabDom, prefabDom.GetAllocator(), entity, settings);
return result.GetOutcome() == AZ::JsonSerializationResult::Outcomes::Success;
}
bool LoadInstanceFromPrefabDom(Instance& instance, const PrefabDom& prefabDom, LoadFlags flags)
{
// When entities are rebuilt they are first destroyed. As a result any assets they were exclusively holding on to will
// be released and reloaded once the entities are built up again. By suspending asset release temporarily the asset reload
@@ -95,7 +160,7 @@ namespace AzToolsFramework
InstanceEntityIdMapper entityIdMapper;
entityIdMapper.SetLoadingInstance(instance);
if ((flags & LoadInstanceFlags::AssignRandomEntityId) == LoadInstanceFlags::AssignRandomEntityId)
if ((flags & LoadFlags::AssignRandomEntityId) == LoadFlags::AssignRandomEntityId)
{
entityIdMapper.SetEntityIdGenerationApproach(InstanceEntityIdMapper::EntityIdGenerationApproach::Random);
}
@@ -125,7 +190,7 @@ namespace AzToolsFramework
}
bool LoadInstanceFromPrefabDom(
Instance& instance, const PrefabDom& prefabDom, AZStd::vector<AZ::Data::Asset<AZ::Data::AssetData>>& referencedAssets, LoadInstanceFlags flags)
Instance& instance, const PrefabDom& prefabDom, AZStd::vector<AZ::Data::Asset<AZ::Data::AssetData>>& referencedAssets, LoadFlags flags)
{
// When entities are rebuilt they are first destroyed. As a result any assets they were exclusively holding on to will
// be released and reloaded once the entities are built up again. By suspending asset release temporarily the asset reload
@@ -134,7 +199,7 @@ namespace AzToolsFramework
InstanceEntityIdMapper entityIdMapper;
entityIdMapper.SetLoadingInstance(instance);
if ((flags & LoadInstanceFlags::AssignRandomEntityId) == LoadInstanceFlags::AssignRandomEntityId)
if ((flags & LoadFlags::AssignRandomEntityId) == LoadFlags::AssignRandomEntityId)
{
entityIdMapper.SetEntityIdGenerationApproach(InstanceEntityIdMapper::EntityIdGenerationApproach::Random);
}
@@ -167,7 +232,7 @@ namespace AzToolsFramework
}
bool LoadInstanceFromPrefabDom(
Instance& instance, Instance::EntityList& newlyAddedEntities, const PrefabDom& prefabDom, LoadInstanceFlags flags)
Instance& instance, Instance::EntityList& newlyAddedEntities, const PrefabDom& prefabDom, LoadFlags flags)
{
// When entities are rebuilt they are first destroyed. As a result any assets they were exclusively holding on to will
// be released and reloaded once the entities are built up again. By suspending asset release temporarily the asset reload
@@ -176,7 +241,7 @@ namespace AzToolsFramework
InstanceEntityIdMapper entityIdMapper;
entityIdMapper.SetLoadingInstance(instance);
if ((flags & LoadInstanceFlags::AssignRandomEntityId) == LoadInstanceFlags::AssignRandomEntityId)
if ((flags & LoadFlags::AssignRandomEntityId) == LoadFlags::AssignRandomEntityId)
{
entityIdMapper.SetEntityIdGenerationApproach(InstanceEntityIdMapper::EntityIdGenerationApproach::Random);
}
@@ -246,15 +311,12 @@ namespace AzToolsFramework
AZ::JsonSerializationResult::ResultCode ApplyPatches(
PrefabDomValue& prefabDomToApplyPatchesOn, PrefabDom::AllocatorType& allocator, const PrefabDomValue& patches)
{
auto issueReportingCallback = [](AZStd::string_view, AZ::JsonSerializationResult::ResultCode result,
AZStd::string_view) -> AZ::JsonSerializationResult::ResultCode
AZStd::string scratchBuffer;
auto issueReportingCallback = [&scratchBuffer]
(AZStd::string_view message, AZ::JsonSerializationResult::ResultCode result,
AZStd::string_view path) -> AZ::JsonSerializationResult::ResultCode
{
using namespace AZ::JsonSerializationResult;
if (result.GetProcessing() == Processing::Halted)
{
return ResultCode(result.GetTask(), Outcomes::PartialSkip);
}
return result;
return Internal::JsonIssueReporter(scratchBuffer, message, result, path);
};
AZ::JsonApplyPatchSettings applyPatchSettings;
@@ -38,7 +38,7 @@ namespace AzToolsFramework
PrefabDomValueReference FindPrefabDomValue(PrefabDomValue& parentValue, const char* valueName);
PrefabDomValueConstReference FindPrefabDomValue(const PrefabDomValue& parentValue, const char* valueName);
enum class StoreInstanceFlags : uint8_t
enum class StoreFlags : uint8_t
{
//! No flags used during the call to LoadInstanceFromPrefabDom.
None = 0,
@@ -51,7 +51,7 @@ namespace AzToolsFramework
//! linkIds to instance dom so any nested prefabs will have linkIds correctly set.
StoreLinkIds = 1 << 1
};
AZ_DEFINE_ENUM_BITWISE_OPERATORS(StoreInstanceFlags);
AZ_DEFINE_ENUM_BITWISE_OPERATORS(StoreFlags);
/**
* Stores a valid Prefab Instance within a Prefab Dom. Useful for generating Templates
@@ -60,9 +60,21 @@ namespace AzToolsFramework
* @param flags Controls behavior such as whether to store default values
* @return bool on whether the operation succeeded
*/
bool StoreInstanceInPrefabDom(const Instance& instance, PrefabDom& prefabDom, StoreInstanceFlags flags = StoreInstanceFlags::None);
bool StoreInstanceInPrefabDom(const Instance& instance, PrefabDom& prefabDom, StoreFlags flags = StoreFlags::None);
enum class LoadInstanceFlags : uint8_t
/**
* Stores a valid entity in Prefab Dom format.
* @param entity The entity to store
* @param owningInstance The instance owning the passed in entity.
* Used for contextualizing the entity's place in a Prefab hierarchy.
* @param prefabDom The prefabDom that will be used to store the entity data
* @param flags controls behavior such as whether to store default values
* @return bool on whether the operation succeeded
*/
bool StoreEntityInPrefabDomFormat(const AZ::Entity& entity, Instance& owningInstance, PrefabDom& prefabDom,
StoreFlags flags = StoreFlags::None);
enum class LoadFlags : uint8_t
{
//! No flags used during the call to LoadInstanceFromPrefabDom.
None = 0,
@@ -70,7 +82,7 @@ namespace AzToolsFramework
//! unique, e.g. when they are duplicates of live entities, this flag will assign them a random new id.
AssignRandomEntityId = 1 << 0
};
AZ_DEFINE_ENUM_BITWISE_OPERATORS(LoadInstanceFlags);
AZ_DEFINE_ENUM_BITWISE_OPERATORS(LoadFlags);
/**
* Loads a valid Prefab Instance from a Prefab Dom. Useful for generating Instances.
@@ -80,7 +92,7 @@ namespace AzToolsFramework
* @return bool on whether the operation succeeded.
*/
bool LoadInstanceFromPrefabDom(
Instance& instance, const PrefabDom& prefabDom, LoadInstanceFlags flags = LoadInstanceFlags::None);
Instance& instance, const PrefabDom& prefabDom, LoadFlags flags = LoadFlags::None);
/**
* Loads a valid Prefab Instance from a Prefab Dom. Useful for generating Instances.
@@ -92,7 +104,7 @@ namespace AzToolsFramework
*/
bool LoadInstanceFromPrefabDom(
Instance& instance, const PrefabDom& prefabDom, AZStd::vector<AZ::Data::Asset<AZ::Data::AssetData>>& referencedAssets,
LoadInstanceFlags flags = LoadInstanceFlags::None);
LoadFlags flags = LoadFlags::None);
/**
* Loads a valid Prefab Instance from a Prefab Dom. Useful for generating Instances.
@@ -105,7 +117,7 @@ namespace AzToolsFramework
*/
bool LoadInstanceFromPrefabDom(
Instance& instance, Instance::EntityList& newlyAddedEntities, const PrefabDom& prefabDom,
LoadInstanceFlags flags = LoadInstanceFlags::None);
LoadFlags flags = LoadFlags::None);
inline PrefabDomPath GetPrefabDomInstancePath(const char* instanceName)
{
@@ -300,7 +300,7 @@ namespace AzToolsFramework
}
PrefabDom storedPrefabDom(&loadedTemplateDom->get().GetAllocator());
if (!PrefabDomUtils::StoreInstanceInPrefabDom(loadedPrefabInstance, storedPrefabDom, PrefabDomUtils::StoreInstanceFlags::StoreLinkIds))
if (!PrefabDomUtils::StoreInstanceInPrefabDom(loadedPrefabInstance, storedPrefabDom, PrefabDomUtils::StoreFlags::StoreLinkIds))
{
return false;
}
@@ -328,7 +328,7 @@ namespace AzToolsFramework
PrefabDom storedPrefabDom(&savingTemplateDom->get().GetAllocator());
if (!PrefabDomUtils::StoreInstanceInPrefabDom(savingPrefabInstance, storedPrefabDom,
PrefabDomUtils::StoreInstanceFlags::StripDefaultValues))
PrefabDomUtils::StoreFlags::StripDefaultValues))
{
return false;
}
@@ -61,7 +61,7 @@ namespace AzToolsFramework
m_prefabUndoCache.Destroy();
}
PrefabOperationResult PrefabPublicHandler::CreatePrefab(const AZStd::vector<AZ::EntityId>& entityIds, AZ::IO::PathView absolutePath)
PrefabOperationResult PrefabPublicHandler::CreatePrefabInMemory(const AZStd::vector<AZ::EntityId>& entityIds, AZ::IO::PathView filePath)
{
EntityList inputEntityList, topLevelEntities;
AZ::EntityId commonRootEntityId;
@@ -73,8 +73,6 @@ namespace AzToolsFramework
return findCommonRootOutcome;
}
AZ_Assert(absolutePath.IsAbsolute(), "CreatePrefab requires an absolute path for saving the initial prefab file.");
InstanceOptionalReference instanceToCreate;
{
// Initialize Undo Batch object
@@ -125,7 +123,7 @@ namespace AzToolsFramework
PrefabDom linkPatchesCopy;
linkPatchesCopy.CopyFrom(linkPatches->get(), linkPatchesCopy.GetAllocator());
nestedInstanceLinkPatchesMap.emplace(nestedInstance, AZStd::move(linkPatchesCopy));
RemoveLink(outInstance, commonRootEntityOwningInstance->get().GetTemplateId(), undoBatch.GetUndoBatch());
instancePtrs.emplace_back(AZStd::move(outInstance));
@@ -139,18 +137,20 @@ namespace AzToolsFramework
if (!prefabEditorEntityOwnershipInterface)
{
return AZ::Failure(AZStd::string("Could not create a new prefab out of the entities provided - internal error "
"(PrefabEditorEntityOwnershipInterface unavailable)."));
"(PrefabEditorEntityOwnershipInterface unavailable)."));
}
// Create the Prefab
AZ_Assert(filePath.IsAbsolute(), "CreatePrefabInMemory requires an absolute file path.");
instanceToCreate = prefabEditorEntityOwnershipInterface->CreatePrefab(
entities, AZStd::move(instancePtrs), m_prefabLoaderInterface->GenerateRelativePath(absolutePath),
entities, AZStd::move(instancePtrs), m_prefabLoaderInterface->GenerateRelativePath(filePath),
commonRootEntityOwningInstance);
if (!instanceToCreate)
{
return AZ::Failure(AZStd::string("Could not create a new prefab out of the entities provided - internal error "
"(A null instance is returned)."));
"(A null instance is returned)."));
}
AZ::EntityId containerEntityId = instanceToCreate->get().GetContainerEntityId();
@@ -218,7 +218,7 @@ namespace AzToolsFramework
linkUpdate.Redo();
}
});
// Create a link between the templates of the newly created instance and the instance it's being parented under.
CreateLink(
instanceToCreate->get(), commonRootEntityOwningInstance->get().GetTemplateId(), undoBatch.GetUndoBatch(),
@@ -255,18 +255,35 @@ namespace AzToolsFramework
// Select Container Entity
{
auto selectionUndo = aznew SelectionCommand({containerEntityId}, "Select Prefab Container Entity");
auto selectionUndo = aznew SelectionCommand({ containerEntityId }, "Select Prefab Container Entity");
selectionUndo->SetParent(undoBatch.GetUndoBatch());
ToolsApplicationRequestBus::Broadcast(&ToolsApplicationRequestBus::Events::RunRedoSeparately, selectionUndo);
}
}
// Save Template to file
m_prefabLoaderInterface->SaveTemplateToFile(instanceToCreate->get().GetTemplateId(), absolutePath);
return AZ::Success();
}
PrefabOperationResult PrefabPublicHandler::CreatePrefabInDisk(const AZStd::vector<AZ::EntityId>& entityIds, AZ::IO::PathView filePath)
{
auto result = CreatePrefabInMemory(entityIds, filePath);
if (result.IsSuccess())
{
// Save Template to file
auto relativePath = m_prefabLoaderInterface->GenerateRelativePath(filePath);
Prefab::TemplateId templateId = m_prefabSystemComponentInterface->GetTemplateIdFromFilePath(relativePath);
if (!m_prefabLoaderInterface->SaveTemplateToFile(templateId, filePath))
{
AZStd::string_view filePathString(filePath);
return AZ::Failure(AZStd::string::format(
"Could not save the newly created prefab to file path %.*s - internal error ",
AZ_STRING_ARG(filePathString)));
}
}
return result;
}
PrefabDom PrefabPublicHandler::ApplyContainerTransformAndGeneratePatch(AZ::EntityId containerEntityId, AZ::EntityId parentEntityId, const EntityList& childEntities)
{
AZ::Entity* containerEntity = GetEntityById(containerEntityId);
@@ -301,7 +318,7 @@ namespace AzToolsFramework
return AZStd::move(patch);
}
PrefabOperationResult PrefabPublicHandler::InstantiatePrefab(
InstantiatePrefabResult PrefabPublicHandler::InstantiatePrefab(
AZStd::string_view filePath, AZ::EntityId parent, const AZ::Vector3& position)
{
auto prefabEditorEntityOwnershipInterface = AZ::Interface<PrefabEditorEntityOwnershipInterface>::Get();
@@ -347,6 +364,7 @@ namespace AzToolsFramework
relativePath.Native().c_str(), instanceToParentUnder->get().GetTemplateSourcePath().Native().c_str()));
}
AZ::EntityId containerEntityId;
{
// Initialize Undo Batch object
ScopedUndoBatch undoBatch("Instantiate Prefab");
@@ -367,7 +385,7 @@ namespace AzToolsFramework
instanceToParentUnder->get(), "Update prefab instance", instanceToParentUnderDomBeforeCreate, undoBatch.GetUndoBatch());
// Create Link with correct container patches
AZ::EntityId containerEntityId = instanceToCreate->get().GetContainerEntityId();
containerEntityId = instanceToCreate->get().GetContainerEntityId();
AZ::Entity* containerEntity = GetEntityById(containerEntityId);
AZ_Assert(containerEntity, "Invalid container entity detected in InstantiatePrefab.");
@@ -394,7 +412,7 @@ namespace AzToolsFramework
&AzToolsFramework::ToolsApplicationRequestBus::Events::ClearDirtyEntities);
}
return AZ::Success();
return AZ::Success(containerEntityId);
}
PrefabOperationResult PrefabPublicHandler::FindCommonRootOwningInstance(
@@ -1727,6 +1745,7 @@ namespace AzToolsFramework
AliasPath absoluteInstancePath = commonOwningInstance.GetAbsoluteInstanceAliasPath();
absoluteInstancePath.Append(newInstanceAlias);
absoluteInstancePath.Append(PrefabDomUtils::ContainerEntityName);
AZ::EntityId newEntityId = InstanceEntityIdMapper::GenerateEntityIdForAliasPath(absoluteInstancePath);
duplicatedEntityIds.push_back(newEntityId);
@@ -42,8 +42,11 @@ namespace AzToolsFramework
void UnregisterPrefabPublicHandlerInterface();
// PrefabPublicInterface...
PrefabOperationResult CreatePrefab(const AZStd::vector<AZ::EntityId>& entityIds, AZ::IO::PathView absolutePath) override;
PrefabOperationResult InstantiatePrefab(AZStd::string_view filePath, AZ::EntityId parent, const AZ::Vector3& position) override;
PrefabOperationResult CreatePrefabInDisk(
const AZStd::vector<AZ::EntityId>& entityIds, AZ::IO::PathView filePath) override;
PrefabOperationResult CreatePrefabInMemory(
const AZStd::vector<AZ::EntityId>& entityIds, AZ::IO::PathView filePath) override;
InstantiatePrefabResult InstantiatePrefab(AZStd::string_view filePath, AZ::EntityId parent, const AZ::Vector3& position) override;
PrefabOperationResult SavePrefab(AZ::IO::Path filePath) override;
PrefabEntityResult CreateEntity(AZ::EntityId parentId, const AZ::Vector3& position) override;
@@ -25,6 +25,7 @@ namespace AzToolsFramework
namespace Prefab
{
typedef AZ::Outcome<void, AZStd::string> PrefabOperationResult;
typedef AZ::Outcome<AZ::EntityId, AZStd::string> InstantiatePrefabResult;
typedef AZ::Outcome<bool, AZStd::string> PrefabRequestResult;
typedef AZ::Outcome<AZ::EntityId, AZStd::string> PrefabEntityResult;
@@ -39,22 +40,34 @@ namespace AzToolsFramework
AZ_RTTI(PrefabPublicInterface, "{931AAE9D-C775-4818-9070-A2DA69489CBE}");
/**
* Create a prefab out of the entities provided, at the path provided.
* Create a prefab out of the entities provided, at the path provided, and save it in disk immediately.
* Automatically detects descendants of entities, and discerns between entities and child instances.
* @param entityIds The entities that should form the new prefab (along with their descendants).
* @param filePath The absolute path for the new prefab file.
* @return An outcome object; on failure, it comes with an error message detailing the cause of the error.
*/
virtual PrefabOperationResult CreatePrefab(const AZStd::vector<AZ::EntityId>& entityIds, AZ::IO::PathView absolutePath) = 0;
virtual PrefabOperationResult CreatePrefabInDisk(
const AZStd::vector<AZ::EntityId>& entityIds, AZ::IO::PathView filePath) = 0;
/**
* Create a prefab out of the entities provided, at the path provided, and keep it in memory.
* Automatically detects descendants of entities, and discerns between entities and child instances.
* @param entityIds The entities that should form the new prefab (along with their descendants).
* @param filePath The absolute path for the new prefab file.
* @return An outcome object; on failure, it comes with an error message detailing the cause of the error.
*/
virtual PrefabOperationResult CreatePrefabInMemory(
const AZStd::vector<AZ::EntityId>& entityIds, AZ::IO::PathView filePath) = 0;
/**
* Instantiate a prefab from a prefab file.
* @param filePath The path to the prefab file to instantiate.
* @param parent The entity the prefab should be a child of in the transform hierarchy.
* @param position The position in world space the prefab should be instantiated in.
* @return An outcome object; on failure, it comes with an error message detailing the cause of the error.
* @return An outcome object with an entityId of the new prefab's container entity;
* on failure, it comes with an error message detailing the cause of the error.
*/
virtual PrefabOperationResult InstantiatePrefab(AZStd::string_view filePath, AZ::EntityId parent, const AZ::Vector3& position) = 0;
virtual InstantiatePrefabResult InstantiatePrefab(AZStd::string_view filePath, AZ::EntityId parent, const AZ::Vector3& position) = 0;
/**
* Saves changes to prefab to disk.
@@ -0,0 +1,56 @@
/*
* 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
#include <AzCore/Component/EntityId.h>
#include <AzCore/EBus/EBus.h>
#include <AzCore/Math/Vector3.h>
#include <AzCore/std/containers/vector.h>
#include <AzCore/std/string/string_view.h>
namespace AzToolsFramework
{
namespace Prefab
{
/**
* The primary purpose of this bus is to facilitate writing automated tests for prefabs.
* It calls PrefabPublicInterface internally to talk to the prefab system.
* If you would like to integrate prefabs into your system, please call PrefabPublicInterface
* directly for better performance.
*/
class PrefabPublicRequests
: public AZ::EBusTraits
{
public:
using Bus = AZ::EBus<PrefabPublicRequests>;
//////////////////////////////////////////////////////////////////////////
// EBusTraits overrides
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
//////////////////////////////////////////////////////////////////////////
virtual ~PrefabPublicRequests() = default;
/**
* Create a prefab out of the entities provided, at the path provided, and keep it in memory.
* Automatically detects descendants of entities, and discerns between entities and child instances.
*/
virtual bool CreatePrefabInMemory(
const AZStd::vector<AZ::EntityId>& entityIds, AZStd::string_view filePath) = 0;
/**
* Instantiate a prefab from a prefab file.
*/
virtual AZ::EntityId InstantiatePrefab(AZStd::string_view filePath, AZ::EntityId parent, const AZ::Vector3& position) = 0;
};
using PrefabPublicRequestBus = AZ::EBus<PrefabPublicRequests>;
} // namespace Prefab
} // namespace AzToolsFramework
@@ -0,0 +1,79 @@
/*
* 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 <AzToolsFramework/Prefab/PrefabPublicRequestHandler.h>
#include <AzToolsFramework/Prefab/PrefabPublicInterface.h>
namespace AzToolsFramework
{
namespace Prefab
{
void PrefabPublicRequestHandler::Reflect(AZ::ReflectContext* context)
{
AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context);
if (behaviorContext)
{
behaviorContext->EBus<PrefabPublicRequestBus>("PrefabPublicRequestBus")
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation)
->Attribute(AZ::Script::Attributes::Category, "Prefab")
->Attribute(AZ::Script::Attributes::Module, "prefab")
->Event("CreatePrefabInMemory", &PrefabPublicRequests::CreatePrefabInMemory)
->Event("InstantiatePrefab", &PrefabPublicRequests::InstantiatePrefab)
;
}
}
void PrefabPublicRequestHandler::Connect()
{
m_prefabPublicInterface = AZ::Interface<Prefab::PrefabPublicInterface>::Get();
AZ_Assert(m_prefabPublicInterface, "PrefabPublicRequestHandler - Could not retrieve instance of PrefabPublicInterface");
PrefabPublicRequestBus::Handler::BusConnect();
}
void PrefabPublicRequestHandler::Disconnect()
{
PrefabPublicRequestBus::Handler::BusDisconnect();
m_prefabPublicInterface = nullptr;
}
bool PrefabPublicRequestHandler::CreatePrefabInMemory(const AZStd::vector<AZ::EntityId>& entityIds, AZStd::string_view filePath)
{
auto createPrefabOutcome = m_prefabPublicInterface->CreatePrefabInMemory(entityIds, filePath);
if (!createPrefabOutcome.IsSuccess())
{
AZ_Error("CreatePrefabInMemory", false,
"Failed to create Prefab on file path '%.*s'. Error message: %s.",
AZ_STRING_ARG(filePath),
createPrefabOutcome.GetError().c_str());
return false;
}
return true;
}
AZ::EntityId PrefabPublicRequestHandler::InstantiatePrefab(AZStd::string_view filePath, AZ::EntityId parent, const AZ::Vector3& position)
{
auto instantiatePrefabOutcome = m_prefabPublicInterface->InstantiatePrefab(filePath, parent, position);
if (!instantiatePrefabOutcome.IsSuccess())
{
AZ_Error("InstantiatePrefab", false,
"Failed to instantiate Prefab on file path '%.*s'. Error message: %s.",
AZ_STRING_ARG(filePath),
instantiatePrefabOutcome.GetError().c_str());
return AZ::EntityId();
}
return instantiatePrefabOutcome.GetValue();
}
} // namespace Prefab
} // namespace AzToolsFramework
@@ -0,0 +1,41 @@
/*
* 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
#include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/RTTI/RTTI.h>
#include <AzToolsFramework/Prefab/PrefabPublicRequestBus.h>
namespace AzToolsFramework
{
namespace Prefab
{
class PrefabPublicInterface;
class PrefabPublicRequestHandler final
: public PrefabPublicRequestBus::Handler
{
public:
AZ_CLASS_ALLOCATOR(PrefabPublicRequestHandler, AZ::SystemAllocator, 0);
AZ_RTTI(PrefabPublicRequestHandler, "{83FBDDF9-10BE-4373-B1DC-44B47EE4805C}");
static void Reflect(AZ::ReflectContext* context);
void Connect();
void Disconnect();
bool CreatePrefabInMemory(const AZStd::vector<AZ::EntityId>& entityIds, AZStd::string_view filePath) override;
AZ::EntityId InstantiatePrefab(AZStd::string_view filePath, AZ::EntityId parent, const AZ::Vector3& position) override;
private:
PrefabPublicInterface* m_prefabPublicInterface = nullptr;
};
} // namespace Prefab
} // namespace AzToolsFramework
@@ -35,12 +35,14 @@ namespace AzToolsFramework
m_instanceUpdateExecutor.RegisterInstanceUpdateExecutorInterface();
m_instanceToTemplatePropagator.RegisterInstanceToTemplateInterface();
m_prefabPublicHandler.RegisterPrefabPublicHandlerInterface();
m_prefabPublicRequestHandler.Connect();
AZ::SystemTickBus::Handler::BusConnect();
}
void PrefabSystemComponent::Deactivate()
{
AZ::SystemTickBus::Handler::BusDisconnect();
m_prefabPublicRequestHandler.Disconnect();
m_prefabPublicHandler.UnregisterPrefabPublicHandlerInterface();
m_instanceToTemplatePropagator.UnregisterInstanceToTemplateInterface();
m_instanceUpdateExecutor.UnregisterInstanceUpdateExecutorInterface();
@@ -54,6 +56,7 @@ namespace AzToolsFramework
AzToolsFramework::Prefab::PrefabConversionUtils::PrefabConversionPipeline::Reflect(context);
AzToolsFramework::Prefab::PrefabConversionUtils::PrefabCatchmentProcessor::Reflect(context);
AzToolsFramework::Prefab::PrefabConversionUtils::EditorInfoRemover::Reflect(context);
PrefabPublicRequestHandler::Reflect(context);
AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context);
if (serialize)
@@ -62,7 +65,6 @@ namespace AzToolsFramework
}
AZ::JsonRegistrationContext* jsonRegistration = azrtti_cast<AZ::JsonRegistrationContext*>(context);
if (jsonRegistration)
{
jsonRegistration->Serializer<JsonInstanceSerializer>()->HandlesType<Instance>();
@@ -24,6 +24,7 @@
#include <AzToolsFramework/Prefab/PrefabIdTypes.h>
#include <AzToolsFramework/Prefab/PrefabLoader.h>
#include <AzToolsFramework/Prefab/PrefabPublicHandler.h>
#include <AzToolsFramework/Prefab/PrefabPublicRequestHandler.h>
#include <AzToolsFramework/Prefab/PrefabSystemComponentInterface.h>
#include <AzToolsFramework/Prefab/Template/Template.h>
@@ -369,6 +370,9 @@ namespace AzToolsFramework
// Used for updating Templates when Instances are modified
InstanceToTemplatePropagator m_instanceToTemplatePropagator;
// Handler of the public Prefab requests
PrefabPublicRequestHandler m_prefabPublicRequestHandler;
};
} // namespace Prefab
} // namespace AzToolsFramework
@@ -266,8 +266,8 @@ namespace AzToolsFramework
AZ_Error(
"Prefab",
result.GetOutcome() == AZ::JsonSerializationResult::Outcomes::PartialSkip ||
result.GetOutcome() == AZ::JsonSerializationResult::Outcomes::Success,
(result.GetOutcome() != AZ::JsonSerializationResult::Outcomes::Skipped) &&
(result.GetOutcome() != AZ::JsonSerializationResult::Outcomes::PartialSkip),
"Some of the patches are not successfully applied.");
//remove the link id placed into the instance
@@ -513,7 +513,7 @@ exportComponent, prefabProcessorContext);
// convert Prefab DOM into Prefab Instance.
AZStd::unique_ptr<Instance> instance(aznew Instance());
if (!Prefab::PrefabDomUtils::LoadInstanceFromPrefabDom(*instance, prefab,
Prefab::PrefabDomUtils::LoadInstanceFlags::AssignRandomEntityId))
Prefab::PrefabDomUtils::LoadFlags::AssignRandomEntityId))
{
PrefabDomValueReference sourceReference = PrefabDomUtils::FindPrefabDomValue(prefab, PrefabDomUtils::SourceName);
@@ -30,7 +30,7 @@ namespace AzToolsFramework::Prefab::SpawnableUtils
{
Instance instance;
if (Prefab::PrefabDomUtils::LoadInstanceFromPrefabDom(instance, prefabDom, referencedAssets,
Prefab::PrefabDomUtils::LoadInstanceFlags::AssignRandomEntityId)) // Always assign random entity ids because the spawnable is
Prefab::PrefabDomUtils::LoadFlags::AssignRandomEntityId)) // Always assign random entity ids because the spawnable is
// going to be used to create clones of the entities.
{
AzFramework::Spawnable::EntityList& entities = spawnable.GetEntities();
@@ -6,7 +6,6 @@
*
*/
// Description : For listing available script commands with their descriptions
#include "ScriptHelpDialog.h"
@@ -23,6 +22,7 @@
// AzToolsFramework
#include <AzToolsFramework/API/EditorPythonConsoleBus.h> // for EditorPythonConsoleInterface
#include <AzToolsFramework/API/EditorWindowRequestBus.h>
AZ_PUSH_DISABLE_DLL_EXPORT_MEMBER_WARNING
#include <AzToolsFramework/PythonTerminal/ui_ScriptHelpDialog.h>
@@ -313,6 +313,45 @@ namespace AzToolsFramework
connect(ui->tableView, &ScriptTableView::doubleClicked, this, &CScriptHelpDialog::OnDoubleClick);
}
CScriptHelpDialog* CScriptHelpDialog::GetInstance()
{
static CScriptHelpDialog* pInstance = nullptr;
if (!pInstance)
{
QMainWindow* mainWindow = GetMainWindowOfCurrentApplication();
if (!mainWindow)
{
AZ_Assert(false, "Failed to find MainWindow.");
return nullptr;
}
QWidget* parentWidget = mainWindow->window()
? mainWindow->window()
: mainWindow; // MainWindow might have a WindowDecorationWrapper parent. Makes a difference on macOS.
pInstance = new CScriptHelpDialog(parentWidget);
}
return pInstance;
}
QMainWindow* CScriptHelpDialog::GetMainWindowOfCurrentApplication()
{
QWidget* mainWindowWidget = nullptr;
EditorWindowRequestBus::BroadcastResult(mainWindowWidget, &EditorWindowRequests::GetAppMainWindow);
if (QMainWindow* mainWindow = qobject_cast<QMainWindow*>(mainWindowWidget))
{
return mainWindow;
}
for (QWidget* topLevelWidget : qApp->topLevelWidgets())
{
if (QMainWindow* mainWindow = qobject_cast<QMainWindow*>(topLevelWidget))
{
return mainWindow;
}
}
return nullptr;
}
void CScriptHelpDialog::OnDoubleClick(const QModelIndex& index)
{
if (!index.isValid())
@@ -132,43 +132,13 @@ namespace AzToolsFramework
{
Q_OBJECT
public:
static CScriptHelpDialog* GetInstance()
{
static CScriptHelpDialog* pInstance = nullptr;
if (!pInstance)
{
QMainWindow* mainWindow = GetMainWindowOfCurrentApplication();
if (!mainWindow)
{
AZ_Assert(false, "Failed to find MainWindow.");
return nullptr;
}
QWidget* parentWidget = mainWindow->window() ? mainWindow->window() : mainWindow; // MainWindow might have a WindowDecorationWrapper parent. Makes a difference on macOS.
pInstance = new CScriptHelpDialog(parentWidget);
}
return pInstance;
}
static CScriptHelpDialog* GetInstance();
private Q_SLOTS:
void OnDoubleClick(const QModelIndex&);
private:
static QMainWindow* GetMainWindowOfCurrentApplication()
{
QMainWindow* mainWindow = nullptr;
for (QWidget* w : qApp->topLevelWidgets())
{
mainWindow = qobject_cast<QMainWindow*>(w);
if (mainWindow)
{
return mainWindow;
}
}
return nullptr;
}
explicit CScriptHelpDialog(QWidget* parent = nullptr);
static QMainWindow* GetMainWindowOfCurrentApplication();
QScopedPointer<Ui::ScriptDialog> ui;
};
} // namespace AzToolsFramework
@@ -27,7 +27,7 @@
namespace AzToolsFramework
{
EntityOutlinerTreeView::EntityOutlinerTreeView(QWidget* pParent)
: QTreeView(pParent)
: AzQtComponents::StyledTreeView(pParent)
, m_queuedMouseEvent(nullptr)
, m_draggingUnselectedItem(false)
{
@@ -144,16 +144,12 @@ namespace AzToolsFramework
if (!selectionModel()->isSelected(index))
{
startCustomDrag({ index }, supportedActions);
StartCustomDrag({ index }, supportedActions);
return;
}
}
if (!selectionModel()->selectedIndexes().empty())
{
startCustomDrag(selectionModel()->selectedIndexes(), supportedActions);
return;
}
StyledTreeView::startDrag(supportedActions);
}
void EntityOutlinerTreeView::dragMoveEvent(QDragMoveEvent* event)
@@ -243,14 +239,14 @@ namespace AzToolsFramework
QTreeView::mousePressEvent(&mousePressedEvent);
}
void EntityOutlinerTreeView::startCustomDrag(const QModelIndexList& indexList, Qt::DropActions supportedActions)
void EntityOutlinerTreeView::StartCustomDrag(const QModelIndexList& indexList, Qt::DropActions supportedActions)
{
m_draggingUnselectedItem = true;
//sort by container entity depth and order in hierarchy for proper drag image and drop order
QModelIndexList indexListSorted = indexList;
AZStd::unordered_map<AZ::EntityId, AZStd::list<AZ::u64>> locations;
for (auto index : indexListSorted)
for (const auto& index : indexListSorted)
{
AZ::EntityId entityId(index.data(EntityOutlinerListModel::EntityIdRole).value<AZ::u64>());
AzToolsFramework::GetEntityLocationInHierarchy(entityId, locations[entityId]);
@@ -263,76 +259,8 @@ namespace AzToolsFramework
return AZStd::lexicographical_compare(locationsE1.begin(), locationsE1.end(), locationsE2.begin(), locationsE2.end());
});
//get the data for the unselected item(s)
QMimeData* mimeData = model()->mimeData(indexListSorted);
if (mimeData)
{
//initiate drag/drop for the item
QDrag* drag = new QDrag(this);
drag->setPixmap(QPixmap::fromImage(createDragImage(indexListSorted)));
drag->setMimeData(mimeData);
Qt::DropAction defDropAction = Qt::IgnoreAction;
if (defaultDropAction() != Qt::IgnoreAction && (supportedActions & defaultDropAction()))
{
defDropAction = defaultDropAction();
}
else if (supportedActions & Qt::CopyAction && dragDropMode() != QAbstractItemView::InternalMove)
{
defDropAction = Qt::CopyAction;
}
drag->exec(supportedActions, defDropAction);
}
StyledTreeView::StartCustomDrag(indexListSorted, supportedActions);
}
QImage EntityOutlinerTreeView::createDragImage(const QModelIndexList& indexList)
{
//generate a drag image of the item icon and text, normally done internally, and inaccessible
QRect rect(0, 0, 0, 0);
for (auto index : indexList)
{
if (index.column() != 0)
{
continue;
}
QRect itemRect = visualRect(index);
rect.setHeight(rect.height() + itemRect.height());
rect.setWidth(AZStd::GetMax(rect.width(), itemRect.width()));
}
QImage dragImage(rect.size(), QImage::Format_ARGB32_Premultiplied);
QPainter dragPainter(&dragImage);
dragPainter.setCompositionMode(QPainter::CompositionMode_Source);
dragPainter.fillRect(dragImage.rect(), Qt::transparent);
dragPainter.setCompositionMode(QPainter::CompositionMode_SourceOver);
dragPainter.setOpacity(0.35f);
dragPainter.fillRect(rect, QColor("#222222"));
dragPainter.setOpacity(1.0f);
int imageY = 0;
for (auto index : indexList)
{
if (index.column() != 0)
{
continue;
}
QRect itemRect = visualRect(index);
dragPainter.drawPixmap(QPoint(0, imageY),
model()->data(index, Qt::DecorationRole).value<QIcon>().pixmap(QSize(16, 16)));
dragPainter.setPen(
model()->data(index, Qt::ForegroundRole).value<QBrush>().color());
dragPainter.setFont(
font());
dragPainter.drawText(QRect(20, imageY, rect.width() - 20, rect.height()),
model()->data(index, Qt::DisplayRole).value<QString>());
imageY += itemRect.height();
}
dragPainter.end();
return dragImage;
}
}
#include <UI/Outliner/moc_EntityOutlinerTreeView.cpp>
@@ -14,7 +14,8 @@
#include <QBasicTimer>
#include <QEvent>
#include <QTreeView>
#include <AzQtComponents/Components/Widgets/TreeView.h>
#endif
#pragma once
@@ -33,7 +34,7 @@ namespace AzToolsFramework
//! allow for dragging and dropping of entities from the outliner into the property editor
//! of other entities. If the selection updates instantly, this would never be possible.
class EntityOutlinerTreeView
: public QTreeView
: public AzQtComponents::StyledTreeView
{
Q_OBJECT;
public:
@@ -68,9 +69,7 @@ namespace AzToolsFramework
void processQueuedMousePressedEvent(QMouseEvent* event);
void startCustomDrag(const QModelIndexList& indexList, Qt::DropActions supportedActions);
QImage createDragImage(const QModelIndexList& indexList);
void StartCustomDrag(const QModelIndexList& indexList, Qt::DropActions supportedActions) override;
void PaintBranchBackground(QPainter* painter, const QRect& rect, const QModelIndex& index) const;
@@ -256,11 +256,11 @@ namespace AzToolsFramework
void PrefabIntegrationManager::HandleSourceFileType(AZStd::string_view sourceFilePath, AZ::EntityId parentId, AZ::Vector3 position) const
{
auto createPrefabOutcome = s_prefabPublicInterface->InstantiatePrefab(sourceFilePath, parentId, position);
auto instantiatePrefabOutcome = s_prefabPublicInterface->InstantiatePrefab(sourceFilePath, parentId, position);
if (!createPrefabOutcome.IsSuccess())
if (!instantiatePrefabOutcome.IsSuccess())
{
WarnUserOfError("Prefab Instantiation Error", createPrefabOutcome.GetError());
WarnUserOfError("Prefab Instantiation Error", instantiatePrefabOutcome.GetError());
}
}
@@ -348,7 +348,7 @@ namespace AzToolsFramework
}
}
auto createPrefabOutcome = s_prefabPublicInterface->CreatePrefab(selectedEntities, prefabFilePath.data());
auto createPrefabOutcome = s_prefabPublicInterface->CreatePrefabInDisk(selectedEntities, prefabFilePath.data());
if (!createPrefabOutcome.IsSuccess())
{
@@ -19,6 +19,7 @@
#include <AzCore/Component/Component.h>
#include <AzCore/Component/ComponentBus.h>
#include <AzCore/Component/EntityBus.h>
#include <AzCore/Component/TickBus.h>
#include <AzCore/Asset/AssetCommon.h>
#include <AzToolsFramework/UI/PropertyEditor/PropertyEditorAPI.h>
#include <AzToolsFramework/Undo/UndoSystem.h>
@@ -110,6 +111,7 @@ namespace AzToolsFramework
, public EditorInspectorComponentNotificationBus::MultiHandler
, private AzToolsFramework::ComponentModeFramework::EditorComponentModeNotificationBus::Handler
, public AZ::EntitySystemBus::Handler
, public AZ::TickBus::Handler
, private EditorWindowUIRequestBus::Handler
{
Q_OBJECT;
@@ -117,6 +119,23 @@ namespace AzToolsFramework
AZ_CLASS_ALLOCATOR(EntityPropertyEditor, AZ::SystemAllocator, 0)
enum class ReorderState
{
Inactive, // No row widget reordering operation is in progress.
DraggingComponent, // User is dragging a component editor.
DraggingRowWidget, // User is dragging a row widget around.
UsingMenu, // User has the context menu open and may hover over a move up/down operation.
MenuOperationInProgress, // User has selected a move/up down menu item.
WaitForRedraw, // Wait for rebuild of RPE.
HighlightMovedRow // User has moved a row, highlight the new position.
};
enum class DropArea
{
Above,
Below
};
EntityPropertyEditor(QWidget* pParent = NULL, Qt::WindowFlags flags = Qt::WindowFlags(), bool isLevelEntityEditor = false);
virtual ~EntityPropertyEditor();
@@ -151,6 +170,16 @@ namespace AzToolsFramework
bool IsLockedToSpecificEntities() const { return !m_overrideSelectedEntityIds.empty(); }
static bool AreComponentsCopyable(const AZ::Entity::ComponentArrayType& components, const ComponentFilter& filter);
ReorderState GetReorderState() const;
ComponentEditor* GetEditorForCurrentReorderRowWidget() const;
PropertyRowWidget* GetReorderRowWidget() const;
PropertyRowWidget* GetReorderDropTarget() const;
DropArea GetReorderDropArea() const;
QPixmap GetReorderRowWidgetImage() const;
float GetMoveIndicatorAlpha() const;
PropertyRowWidget* GetRowToHighlight();
Q_SIGNALS:
void SelectedEntityNameChanged(const AZ::EntityId& entityId, const AZStd::string& name);
@@ -211,6 +240,9 @@ namespace AzToolsFramework
void GetSelectedEntities(EntityIdList& selectedEntityIds) override;
void SetNewComponentId(AZ::ComponentId componentId) override;
// TickBus
void OnTick(float deltaTime, AZ::ScriptTimePoint time) override;
// EditorWindowRequestBus overrides
void SetEditorUiEnabled(bool enable) override;
@@ -253,6 +285,10 @@ namespace AzToolsFramework
void ContextMenuActionPullFieldData(AZ::Component* parentComponent, InstanceDataNode* fieldNode);
void ContextMenuActionSetDataFlag(InstanceDataNode* node, AZ::DataPatch::Flag flag, bool additive);
void GenerateRowWidgetIndexMapToChildIndex(PropertyRowWidget* parent, int destIndex);
void ContextMenuActionMoveItemUp(ComponentEditor* componentEditor, PropertyRowWidget* rowWidget);
void ContextMenuActionMoveItemDown(ComponentEditor* componentEditor, PropertyRowWidget* rowWidget);
/// Given an InstanceDataNode, calculate a DataPatch address relative to the entity.
/// @return true if successful.
bool GetEntityDataPatchAddress(const InstanceDataNode* componentFieldNode, AZ::DataPatch::AddressType& dataPatchAddressOut, AZ::EntityId* entityIdOut = nullptr) const;
@@ -341,8 +377,6 @@ namespace AzToolsFramework
QAction* m_actionToMoveComponentsBottom = nullptr;
QAction* m_resetToSliceAction = nullptr;
bool m_isShowingContextMenu = false;
void CreateActions();
void UpdateActions();
@@ -390,6 +424,10 @@ namespace AzToolsFramework
void ResetToSlice();
bool DoesOwnFocus() const;
AZ::u32 GetHeightOfRowAndVisibleChildren(const PropertyRowWidget* row) const;
QRect GetWidgetAndVisibleChildrenGlobalRect(const PropertyRowWidget* widget) const;
PropertyRowWidget* GetRowWidgetAtSameLevelAfter(PropertyRowWidget* widget) const;
PropertyRowWidget* GetRowWidgetAtSameLevelBefore(PropertyRowWidget* widget) const;
QRect GetWidgetGlobalRect(const QWidget* widget) const;
bool DoesIntersectWidget(const QRect& globalRect, const QWidget* widget) const;
bool DoesIntersectSelectedComponentEditor(const QRect& globalRect) const;
@@ -445,6 +483,8 @@ namespace AzToolsFramework
bool HandleSelectionEvents(QObject* object, QEvent* event);
bool m_selectionEventAccepted;
bool HandleMenuEvent(QObject* object, QEvent* event);
// drag and drop events
QRect GetInflatedRectFromPoint(const QPoint& point, int radius) const;
bool GetComponentsAtDropEventPosition(QDropEvent* event, AZ::Entity::ComponentArrayType& targetComponents);
@@ -458,8 +498,12 @@ namespace AzToolsFramework
ComponentEditor* GetReorderDropTarget(const QRect& globalRect) const;
bool ResetDrag(QMouseEvent* event);
bool FindAllowedRowWidgetReorderDropTarget(const QPoint& globalPos);
bool UpdateRowWidgetDrag(const QPoint& localPos, Qt::MouseButtons mouseButtons, const QMimeData* mimeData);
PropertyRowWidget* FindPropertyRowWidgetAt(QPoint globalPos);
bool UpdateDrag(const QPoint& localPos, Qt::MouseButtons mouseButtons, const QMimeData* mimeData);
bool StartDrag(QMouseEvent* event);
void EndRowWidgetReorder();
bool HandleDrop(QDropEvent* event);
bool HandleDropForComponentTypes(QDropEvent* event);
bool HandleDropForComponentAssets(QDropEvent* event);
@@ -468,6 +512,8 @@ namespace AzToolsFramework
bool CanDropForComponentTypes(const QMimeData* mimeData) const;
bool CanDropForComponentAssets(const QMimeData* mimeData) const;
bool CanDropForAssetBrowserEntries(const QMimeData* mimeData) const;
void SetRowWidgetHighlighted(PropertyRowWidget* rowWidget);
AZStd::vector<AZ::s32> ExtractComponentEditorIndicesFromMimeData(const QMimeData* mimeData) const;
ComponentEditorVector GetComponentEditorsFromIndices(const AZStd::vector<AZ::s32>& indices) const;
ComponentEditor* GetComponentEditorsFromIndex(const AZ::s32 index) const;
@@ -559,6 +605,8 @@ namespace AzToolsFramework
QIcon m_emptyIcon;
QIcon m_clearIcon;
QIcon m_dragIcon;
QCursor m_dragCursor;
QStandardItem* m_comboItems[StatusItems];
EntityIdSet m_overrideSelectedEntityIds;
@@ -566,6 +614,19 @@ namespace AzToolsFramework
Prefab::PrefabPublicInterface* m_prefabPublicInterface = nullptr;
bool m_prefabsAreEnabled = false;
// Reordering row widgets within the RPE.
static constexpr float MoveFadeSeconds = 0.5f;
ReorderState m_currentReorderState = ReorderState::Inactive;
ComponentEditor* m_reorderRowWidgetEditor = nullptr;
InstanceDataNode* m_nodeToMove = nullptr;
PropertyRowWidget* m_reorderRowWidget = nullptr;
PropertyRowWidget* m_reorderDropTarget = nullptr;
DropArea m_reorderDropArea = DropArea::Above;
QPixmap m_reorderRowImage;
float m_moveFadeSecondsRemaining;
AZStd::vector<int> m_indexMapOfMovedRow;
// When m_initiatingPropertyChangeNotification is set to true, it means this EntityPropertyEditor is
// broadcasting a change to all listeners about a property change for a given entity. This is needed
// so that we don't update the values twice for this inspector
@@ -573,6 +634,9 @@ namespace AzToolsFramework
void ConnectToEntityBuses(const AZ::EntityId& entityId);
void DisconnectFromEntityBuses(const AZ::EntityId& entityId);
void BeginMoveRowWidgetFade();
void HighlightMovedRowWidget();
//! Stores a component id to be focused on next time the UI updates.
AZStd::optional<AZ::ComponentId> m_newComponentId;
@@ -594,6 +658,8 @@ namespace AzToolsFramework
bool SelectedEntitiesAreFromSameSourceSliceEntity() const;
void DragStopped();
AZ::Entity* GetSelectedEntityById(AZ::EntityId& entityId) const;
};
@@ -7,8 +7,8 @@
*/
#include "PropertyAudioCtrl.h"
#include "PropertyQTConstants.h"
#include <UI/PropertyEditor/PropertyAudioCtrl.h>
#include <UI/PropertyEditor/PropertyQTConstants.h>
#include <QtWidgets/QLabel>
#include <QtWidgets/QLineEdit>
@@ -34,7 +34,7 @@ namespace AzToolsFramework
: QWidget(parent)
, m_browseEdit(nullptr)
, m_mainLayout(nullptr)
, m_propertyType(AudioPropertyType::Invalid)
, m_propertyType(AudioPropertyType::NumTypes)
{
// create the gui
m_mainLayout = new QHBoxLayout();
@@ -96,7 +96,7 @@ namespace AzToolsFramework
return;
}
if (type != AudioPropertyType::Invalid)
if (type != AudioPropertyType::NumTypes)
{
m_propertyType = type;
}
@@ -136,10 +136,11 @@ namespace AzToolsFramework
void AudioControlSelectorWidget::OnOpenAudioControlSelector()
{
AZStd::string resourceResult;
AZStd::string resourceType(GetResourceSelectorNameFromType(m_propertyType));
AZStd::string currentValue(m_controlName.toStdString().c_str());
EditorRequests::Bus::BroadcastResult(resourceResult, &EditorRequests::Bus::Events::SelectResource, resourceType, currentValue);
AZStd::string resourceResult;
AudioControlSelectorRequestBus::EventResult(
resourceResult, m_propertyType,
&AudioControlSelectorRequestBus::Events::SelectResource, currentValue);
SetControlName(QString(resourceResult.c_str()));
}
@@ -167,12 +168,12 @@ namespace AzToolsFramework
{
case AudioPropertyType::Trigger:
return { "AudioTrigger" };
case AudioPropertyType::Rtpc:
return { "AudioRTPC" };
case AudioPropertyType::Switch:
return { "AudioSwitch" };
case AudioPropertyType::SwitchState:
return { "AudioSwitchState" };
case AudioPropertyType::Rtpc:
return { "AudioRTPC" };
case AudioPropertyType::Environment:
return { "AudioEnvironment" };
case AudioPropertyType::Preload:
@@ -29,6 +29,27 @@ class QMimeData;
namespace AzToolsFramework
{
//=============================================================================
// Audio Control Selector Request Bus
// For connecting UI proper
//=============================================================================
class AudioControlSelectorRequests
: public AZ::EBusTraits
{
public:
// EBusTraits
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ById;
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
using BusIdType = AudioPropertyType;
virtual AZStd::string SelectResource(AZStd::string_view previousValue)
{
return previousValue;
}
};
using AudioControlSelectorRequestBus = AZ::EBus<AudioControlSelectorRequests>;
//=============================================================================
// Audio Control Selector Widget
//=============================================================================
@@ -18,15 +18,15 @@
namespace AzToolsFramework
{
//=========================================================================
enum class AudioPropertyType
enum class AudioPropertyType : AZ::u32
{
Invalid = 0,
Trigger,
Trigger = 0,
Rtpc,
Switch,
SwitchState,
Rtpc,
Environment,
Preload,
NumTypes,
};
//=========================================================================
@@ -40,7 +40,7 @@ namespace AzToolsFramework
virtual ~CReflectedVarAudioControl() = default;
AZStd::string m_controlName;
AudioPropertyType m_propertyType = AudioPropertyType::Invalid;
AudioPropertyType m_propertyType = AudioPropertyType::NumTypes;
static void Reflect(AZ::ReflectContext* context)
{
@@ -70,7 +70,7 @@ namespace AzToolsFramework
m_leftAreaContainer = new QWidget(this);
m_middleAreaContainer = new QWidget(this);
const int minimumControlWidth = 192;
const int minimumControlWidth = 142;
m_middleAreaContainer->setMinimumWidth(minimumControlWidth);
m_mainLayout->addWidget(m_leftAreaContainer, LabelColumnStretch, Qt::AlignLeft);
m_mainLayout->addWidget(m_middleAreaContainer, ValueColumnStretch);
@@ -368,10 +368,15 @@ namespace AzToolsFramework
delete m_containerAddButton;
}
this->unsetCursor();
if ((m_parentRow) && (m_parentRow->IsContainerEditable()))
{
if (!m_elementRemoveButton)
{
QIcon icon = QIcon(QStringLiteral(":/Cursors/Grab_release.svg"));
this->setCursor(QCursor(icon.pixmap(16), 5, 2));
static QIcon s_iconRemove(QStringLiteral(":/stylesheet/img/UI20/delete-16.svg"));
m_elementRemoveButton = new QToolButton(this);
m_elementRemoveButton->setAutoRaise(true);
@@ -570,7 +575,12 @@ namespace AzToolsFramework
AZ_Assert(m_selectionEnabled, "Property is not selectable");
m_isSelected = selected;
m_nameLabel->setProperty("selected", selected);
}
}
bool PropertyRowWidget::GetSelected()
{
return m_isSelected;
}
void PropertyRowWidget::SetSelectionEnabled(bool selectionEnabled)
{
@@ -1395,6 +1405,21 @@ namespace AzToolsFramework
return !m_childrenRows.empty();
}
AZ::u32 PropertyRowWidget::GetChildRowCount() const
{
return static_cast<AZ::u32>(m_childrenRows.size());
}
PropertyRowWidget* PropertyRowWidget::GetChildRowByIndex(AZ::u32 index) const
{
if (index >= m_childrenRows.size())
{
return nullptr;
}
return m_childrenRows[index];
}
bool PropertyRowWidget::ShouldPreValidatePropertyChange() const
{
return (m_changeValidators.size() > 0);
@@ -1722,6 +1747,162 @@ namespace AzToolsFramework
return m_parentRow->CanChildrenBeReordered();
}
int PropertyRowWidget::GetIndexInParent() const
{
if (!GetParentRow())
{
return -1;
}
for (AZ::u32 index = 0; index < GetParentRow()->GetChildRowCount(); index++)
{
if (GetParentRow()->GetChildrenRows()[index] == this)
{
return index;
}
}
return -1;
}
bool PropertyRowWidget::CanMoveUp() const
{
if (!CanBeReordered())
{
return false;
}
return this != m_parentRow->GetChildRowByIndex(0);
}
bool PropertyRowWidget::CanMoveDown() const
{
if (!CanBeReordered())
{
return false;
}
AZ::u32 numChildrenOfParent = m_parentRow->GetChildRowCount();
return this != m_parentRow->GetChildRowByIndex(numChildrenOfParent - 1);
}
int PropertyRowWidget::GetContainingEditorFrameWidth()
{
QWidget* parent = parentWidget();
// Find the first ancestor that can be cast to a QFrame, this will be the RPE.
while (!qobject_cast<QFrame*>(parent))
{
parent = parent->parentWidget();
}
if (!parent)
{
return 0;
}
// The parent of the RPE is the size we want.
parent = parent->parentWidget();
return parent->rect().width();
}
int PropertyRowWidget::GetHeightOfRowAndVisibleChildren()
{
int height = rect().height();
if (!GetChildRowCount() || !IsExpanded())
{
return height;
}
for (auto childRow : GetChildrenRows())
{
height += childRow->GetHeightOfRowAndVisibleChildren();
}
return height;
}
int PropertyRowWidget::DrawDragImageAndVisibleChildrenInto(QPainter& painter, int xpos, int ypos)
{
// Render our image into the given painter.
int ystart = ypos;
render(&painter, QPoint(xpos, ypos));
if (!GetChildRowCount() || !IsExpanded())
{
return rect().height();
}
ypos += rect().height();
// Recursively draw any children.
for (auto childRow : GetChildrenRows())
{
ypos += childRow->DrawDragImageAndVisibleChildrenInto(painter, xpos, ypos);
}
return ypos - ystart;
}
QPixmap PropertyRowWidget::createDragImage(
const QColor backgroundColor, const QColor borderColor, const float alpha, DragImageType imageType)
{
// Make the drag box as wide as the containing editor minus a gap each side for the border.
static constexpr int ParentEditorBorderSize = 2;
int width = GetContainingEditorFrameWidth() - ParentEditorBorderSize * 2;
int height = 0;
if (imageType == DragImageType::IncludeVisibleChildren)
{
height = GetHeightOfRowAndVisibleChildren();
}
else
{
height = rect().height();
}
const auto dpr = devicePixelRatioF();
QPixmap dragImage(width * dpr, height * dpr);
dragImage.setDevicePixelRatio(dpr);
dragImage.fill(Qt::transparent);
QRect imageRect = QRect(0, 0, width, height);
QPainter dragPainter(&dragImage);
dragPainter.setCompositionMode(QPainter::CompositionMode_Source);
dragPainter.fillRect(imageRect, Qt::transparent);
dragPainter.setCompositionMode(QPainter::CompositionMode_SourceOver);
dragPainter.setOpacity(alpha);
dragPainter.fillRect(imageRect, backgroundColor);
dragPainter.setOpacity(1.0f);
int marginWidth = (imageRect.width() - rect().width()) / 2 + ParentEditorBorderSize - 1;
if (imageType == DragImageType::IncludeVisibleChildren)
{
DrawDragImageAndVisibleChildrenInto(dragPainter, marginWidth, 0);
}
else
{
render(&dragPainter, QPoint(marginWidth, 0));
}
QPen pen;
pen.setColor(QColor(borderColor));
pen.setWidth(1);
dragPainter.setPen(pen);
dragPainter.drawRect(0, 0, imageRect.width() - 1, imageRect.height() - 1);
dragPainter.end();
return dragImage;
}
}
#include "UI/PropertyEditor/moc_PropertyRowWidget.cpp"
@@ -45,6 +45,13 @@ namespace AzToolsFramework
Q_PROPERTY(bool appendDefaultLabelToName READ GetAppendDefaultLabelToName WRITE AppendDefaultLabelToName)
public:
AZ_CLASS_ALLOCATOR(PropertyRowWidget, AZ::SystemAllocator, 0)
enum class DragImageType
{
SingleRow,
IncludeVisibleChildren
};
PropertyRowWidget(QWidget* pParent);
virtual ~PropertyRowWidget();
@@ -86,6 +93,9 @@ namespace AzToolsFramework
bool GetAppendDefaultLabelToName();
void AppendDefaultLabelToName(bool doAppend);
AZ::u32 GetChildRowCount() const;
PropertyRowWidget* GetChildRowByIndex(AZ::u32 index) const;
AZStd::vector<PropertyRowWidget*>& GetChildrenRows() { return m_childrenRows; }
bool HasChildRows() const;
@@ -124,6 +134,7 @@ namespace AzToolsFramework
void SetSelectionEnabled(bool selectionEnabled);
void SetSelected(bool selected);
bool GetSelected();
bool eventFilter(QObject *watched, QEvent *event) override;
void paintEvent(QPaintEvent*) override;
@@ -152,9 +163,18 @@ namespace AzToolsFramework
bool CanChildrenBeReordered() const;
bool CanBeReordered() const;
int GetIndexInParent() const;
bool CanMoveUp() const;
bool CanMoveDown() const;
int GetContainingEditorFrameWidth();
QPixmap createDragImage(const QColor backgroundColor, const QColor borderColor, const float alpha, DragImageType imageType);
protected:
int CalculateLabelWidth() const;
int GetHeightOfRowAndVisibleChildren();
int DrawDragImageAndVisibleChildrenInto(QPainter& painter, int xpos, int ypos);
bool IsHidden(InstanceDataNode* node) const;
struct ChangeNotification;
@@ -216,6 +236,7 @@ namespace AzToolsFramework
bool m_isMultiSizeContainer = false;
bool m_isFixedSizeOrSmartPtrContainer = false;
bool m_custom = false;
bool m_canChildrenBeReordered = false;
bool m_isSelected = false;
bool m_selectionEnabled = false;
@@ -19,6 +19,7 @@
#include <QtWidgets/QVBoxLayout>
#include <QtWidgets/QScrollArea>
#include <QtWidgets/QApplication>
#include <QPainter>
AZ_PUSH_DISABLE_WARNING(4251, "-Wunknown-warning-option") // 'QTextFormat::d': class 'QSharedDataPointer<QTextFormatPrivate>' needs to have dll-interface to be used by clients of class 'QTextFormat'
#include <QtWidgets/QInputDialog>
AZ_POP_DISABLE_WARNING
@@ -1343,7 +1344,7 @@ namespace AzToolsFramework
// calculate the index/offset of the instance data node in the container
// (useful for notifying which element in a vector was modified/removed)
static size_t CalculateElementIndexInContainer(
static int CalculateElementIndexInContainer(
InstanceDataNode* node, void* parentInstanceNode,
AZ::SerializeContext::IDataContainer* container, AZStd::vector<void*>& nodeInstancesOut)
{
@@ -1358,7 +1359,7 @@ namespace AzToolsFramework
}
}
size_t elementIndex = 0;
int elementIndex = 0;
void* elementPtr = nodeInstancesOut.empty() ? nullptr : nodeInstancesOut.front();
// find the index of the element we are about to remove
@@ -1429,7 +1430,7 @@ namespace AzToolsFramework
// if the element being modified exists in a container, calculate
// the index to be passed through to PropertyNotify
const auto calculateElementIndex = [](InstanceDataNode* node) -> size_t {
const auto calculateElementIndex = [](InstanceDataNode* node) -> int {
if (InstanceDataNode* parent = node->GetParent())
{
if (AZ::SerializeContext::IDataContainer* container = parent->GetClassMetadata()->m_container)
@@ -1656,6 +1657,221 @@ namespace AzToolsFramework
AzToolsFramework::Refresh_EntireTree);
}
InstanceDataNode* ReflectedPropertyEditor::FindContainerNodeForNode(InstanceDataNode* node) const
{
// Locate the owning container. There may be a level of indirection due to wrappers, such as DynamicSerializableField.
InstanceDataNode* pContainerNode = node->GetParent();
if (!pContainerNode)
{
return nullptr;
}
while (pContainerNode && !pContainerNode->GetClassMetadata()->m_container)
{
pContainerNode = pContainerNode->GetParent();
node = node->GetParent();
}
// Check for pContainerNode again, can happen if a node is deleted during operation.
if (!pContainerNode)
{
return nullptr;
}
if (IsParentAssociativeContainer(pContainerNode) && IsPairContainer(pContainerNode))
{
// Go up one more level to the associative container, we'll remove the pair from that container
pContainerNode = pContainerNode->GetParent();
node = node->GetParent();
}
AZ_Assert(
pContainerNode, "Failed to locate parent container for element \"%s\" of type %s.",
node->GetElementMetadata() ? node->GetElementMetadata()->m_name : node->GetClassMetadata()->m_name,
node->GetClassMetadata()->m_typeId.ToString<AZStd::string>().c_str());
return pContainerNode;
}
InstanceDataNode* ReflectedPropertyEditor::GetNodeAtIndex(int index)
{
if (index >= m_impl->m_widgetsInDisplayOrder.size())
{
return nullptr;
}
return GetNodeFromWidget(m_impl->m_widgetsInDisplayOrder[index]);
}
QSet<PropertyRowWidget*> ReflectedPropertyEditor::GetTopLevelWidgets()
{
return m_impl->getTopLevelWidgets();
}
void ReflectedPropertyEditor::ChangeNodeIndex(InstanceDataNode* containerNode, InstanceDataNode* node, int fromIndex, int toIndex)
{
auto container = containerNode->GetElementMetadata()
? containerNode->GetElementMetadata()->m_genericClassInfo->GetClassData()->m_container
: nullptr;
if (fromIndex == toIndex)
{
return;
}
if (!container || container->GetAssociativeContainerInterface())
{
return;
}
AZ::Uuid typeId = node->GetClassMetadata()->m_typeId;
if (m_impl->m_ptrNotify)
{
m_impl->m_ptrNotify->BeforePropertyModified(containerNode);
}
const AZ::SerializeContext::ClassElement* containerClassElement = container->GetElement(container->GetDefaultElementNameCrc());
AZ::SerializeContext* serializeContext = nullptr;
AZ::ComponentApplicationBus::BroadcastResult(serializeContext, &AZ::ComponentApplicationBus::Events::GetSerializeContext);
// Backup the item we're moving.
void* srcElement = nullptr;
void* destElement = nullptr;
int destIndex = -1;
int srcIndex = fromIndex;
srcElement = container->GetElementByIndex(containerNode->GetInstance(0), containerClassElement, srcIndex);
void* tmpBuffer = serializeContext->CloneObject(srcElement, typeId);
// Shuffle all intervening items up (or down).
int indexOffset = (toIndex < fromIndex) ? -1 : 1;
while (destIndex != toIndex - indexOffset)
{
destIndex = srcIndex;
srcIndex += indexOffset;
destElement = srcElement;
srcElement = container->GetElementByIndex(containerNode->GetInstance(0), containerClassElement, srcIndex);
serializeContext->CloneObjectInplace(destElement, srcElement, typeId);
}
// Now replace the final element with the one backed up previously.
destElement = srcElement;
serializeContext->CloneObjectInplace(destElement, tmpBuffer, typeId);
if (m_impl->m_ptrNotify)
{
m_impl->m_ptrNotify->AfterPropertyModified(containerNode);
m_impl->m_ptrNotify->SealUndoStack();
}
// Need to refresh any pinned inspectors as well to keep the container state in sync
QueueInvalidation(Refresh_Values);
AzToolsFramework::ToolsApplicationEvents::Bus::Broadcast(
&AzToolsFramework::ToolsApplicationEvents::InvalidatePropertyDisplay, AzToolsFramework::Refresh_Values);
}
void ReflectedPropertyEditor::MoveNodeToIndex(InstanceDataNode* node, int index)
{
InstanceDataNode* pContainerNode = FindContainerNodeForNode(node);
if (!pContainerNode)
{
return;
}
AZ::SerializeContext::IDataContainer* container = pContainerNode->GetClassMetadata()->m_container;
AZStd::vector<void*> nodeInstancesOut;
const int elementIndex = CalculateElementIndexInContainer(node, pContainerNode->GetInstance(0), container, nodeInstancesOut);
ChangeNodeIndex(pContainerNode, node, elementIndex, index);
}
void ReflectedPropertyEditor::MoveNodeBefore(InstanceDataNode* nodeToMove, InstanceDataNode* nodeToMoveBefore)
{
InstanceDataNode* pContainerNode = FindContainerNodeForNode(nodeToMove);
InstanceDataNode* pContainerNodeTarget = FindContainerNodeForNode(nodeToMoveBefore);
if (nodeToMove == nodeToMoveBefore)
{
return;
}
// Can only move nodes within the same parent.
if (pContainerNode != pContainerNodeTarget)
{
return;
}
AZ::SerializeContext::IDataContainer* container = pContainerNode->GetClassMetadata()->m_container;
AZStd::vector<void*> nodeInstancesOut;
int elementIndex = CalculateElementIndexInContainer(nodeToMove, pContainerNode->GetInstance(0), container, nodeInstancesOut);
nodeInstancesOut.clear();
int elementIndexTarget =
CalculateElementIndexInContainer(nodeToMoveBefore, pContainerNode->GetInstance(0), container, nodeInstancesOut);
if (elementIndex < elementIndexTarget)
{
elementIndexTarget -= 1;
}
ChangeNodeIndex(pContainerNode, nodeToMove, elementIndex, elementIndexTarget);
}
void ReflectedPropertyEditor::MoveNodeAfter(InstanceDataNode* nodeToMove, InstanceDataNode* nodeToMoveBefore)
{
InstanceDataNode* pContainerNode = FindContainerNodeForNode(nodeToMove);
InstanceDataNode* pContainerNodeTarget = FindContainerNodeForNode(nodeToMoveBefore);
if (nodeToMove == nodeToMoveBefore)
{
return;
}
// Can only move nodes within the same parent.
if (pContainerNode != pContainerNodeTarget)
{
return;
}
AZ::SerializeContext::IDataContainer* container = pContainerNode->GetClassMetadata()->m_container;
AZStd::vector<void*> nodeInstancesOut;
int elementIndex = CalculateElementIndexInContainer(nodeToMove, pContainerNode->GetInstance(0), container, nodeInstancesOut);
nodeInstancesOut.clear();
int elementIndexTarget =
CalculateElementIndexInContainer(nodeToMoveBefore, pContainerNode->GetInstance(0), container, nodeInstancesOut);
if (elementIndex > elementIndexTarget)
{
elementIndexTarget += 1;
}
ChangeNodeIndex(pContainerNode, nodeToMove, elementIndex, elementIndexTarget);
}
int ReflectedPropertyEditor::GetNodeIndexInContainer(InstanceDataNode* node)
{
InstanceDataNode* pContainerNode = FindContainerNodeForNode(node);
AZ::SerializeContext::IDataContainer* container = pContainerNode->GetClassMetadata()->m_container;
AZStd::vector<void*> nodeInstancesOut;
int elementIndex = CalculateElementIndexInContainer(node, pContainerNode->GetInstance(0), container, nodeInstancesOut);
return elementIndex;
}
void ReflectedPropertyEditor::OnPropertyRowRequestContainerRemoveItem(PropertyRowWidget* widget, InstanceDataNode* node)
{
// Locate the owning container. There may be a level of indirection due to wrappers, such as DynamicSerializableField.
@@ -1690,7 +1906,7 @@ namespace AzToolsFramework
// the index of the element being removed
AZStd::vector<void*> nodeInstancesOut;
const size_t elementIndex = CalculateElementIndexInContainer(
const int elementIndex = CalculateElementIndexInContainer(
node, pContainerNode->GetInstance(0), container, nodeInstancesOut);
// pass the context as the last parameter to actually delete the related data.
@@ -155,9 +155,19 @@ namespace AzToolsFramework
using VisibilityCallback = AZStd::function<void(InstanceDataNode* node, NodeDisplayVisibility& visibility, bool& checkChildVisibility)>;
void SetVisibilityCallback(VisibilityCallback callback);
void MoveNodeToIndex(InstanceDataNode* node, int index);
void MoveNodeBefore(InstanceDataNode* nodeToMove, InstanceDataNode* nodeToMoveBefore);
void MoveNodeAfter(InstanceDataNode* nodeToMove, InstanceDataNode* nodeToMoveBefore);
int GetNodeIndexInContainer(InstanceDataNode* node);
InstanceDataNode* GetNodeAtIndex(int index);
QSet<PropertyRowWidget*> GetTopLevelWidgets();
signals:
void OnExpansionContractionDone();
private:
InstanceDataNode* FindContainerNodeForNode(InstanceDataNode* node) const;
void ChangeNodeIndex(InstanceDataNode* containerNode, InstanceDataNode* node, int oldIndex, int newIndex);
class Impl;
std::unique_ptr<Impl> m_impl;
@@ -19,6 +19,7 @@
namespace AZ
{
class ReflectContext;
class SerializeContext;
}
namespace AzToolsFramework
@@ -657,6 +657,9 @@ set(FILES
Prefab/PrefabPublicHandler.cpp
Prefab/PrefabPublicInterface.h
Prefab/PrefabPublicNotificationBus.h
Prefab/PrefabPublicRequestBus.h
Prefab/PrefabPublicRequestHandler.h
Prefab/PrefabPublicRequestHandler.cpp
Prefab/PrefabUndo.h
Prefab/PrefabUndo.cpp
Prefab/PrefabUndoCache.cpp
@@ -85,7 +85,7 @@ namespace Benchmark
void BM_Prefab::CreateEntities(const unsigned int entityCount, AZStd::vector<AZ::Entity*>& entities)
{
for (int entityIndex = 0; entityIndex < entityCount; ++entityIndex)
for (unsigned int entityIndex = 0; entityIndex < entityCount; ++entityIndex)
{
AZStd::string entityName = "TestEntity";
entityName = entityName + AZStd::to_string(entityIndex);
@@ -101,7 +101,7 @@ namespace Benchmark
void BM_Prefab::CreateFakePaths(const unsigned int pathCount)
{
//setup fake paths
for (int number = 0; number < pathCount; ++number)
for (unsigned int number = 0; number < pathCount; ++number)
{
AZStd::string path = m_pathString;
m_paths.push_back(path + AZStd::to_string(number) + "_" + AZStd::to_string(pathCount));
@@ -32,7 +32,7 @@ namespace Benchmark
state.ResumeTiming();
for (int instanceCounter = 0; instanceCounter < numInstances; ++instanceCounter)
for (unsigned int instanceCounter = 0; instanceCounter < numInstances; ++instanceCounter)
{
newInstances.push_back(m_prefabSystemComponent->CreatePrefab(
{ entities[instanceCounter] },
@@ -110,7 +110,7 @@ namespace Benchmark
AZStd::vector<AZStd::unique_ptr<Instance>> testInstances;
testInstances.resize(numInstancesToAdd);
for (int instanceCounter = 0; instanceCounter < numInstancesToAdd; ++instanceCounter)
for (unsigned int instanceCounter = 0; instanceCounter < numInstancesToAdd; ++instanceCounter)
{
testInstances[instanceCounter] = (m_prefabSystemComponent->CreatePrefab(
{ entities[instanceCounter] }
@@ -161,7 +161,7 @@ namespace Benchmark
state.ResumeTiming();
for (int instanceCounter = 0; instanceCounter < numInstances; ++instanceCounter)
for (unsigned int instanceCounter = 0; instanceCounter < numInstances; ++instanceCounter)
{
nestedInstanceRoot = m_prefabSystemComponent->CreatePrefab(
{},
@@ -33,7 +33,7 @@ namespace Benchmark
state.ResumeTiming();
for (int instanceCounter = 0; instanceCounter < numInstances; ++instanceCounter)
for (unsigned int instanceCounter = 0; instanceCounter < numInstances; ++instanceCounter)
{
newInstances[instanceCounter] = m_prefabSystemComponent->InstantiatePrefab(templateToInstantiateId);
}
@@ -29,7 +29,7 @@ namespace Benchmark
state.ResumeTiming();
for (int templateCounter = 0; templateCounter < numTemplates; ++templateCounter)
for (unsigned int templateCounter = 0; templateCounter < numTemplates; ++templateCounter)
{
m_prefabLoaderInterface->LoadTemplateFromFile(m_paths[templateCounter]);
}
@@ -33,7 +33,7 @@ namespace Benchmark
AZStd::vector<AZStd::unique_ptr<AzFramework::Spawnable>> spawnables;
spawnables.reserve(numSpawnables);
for (int spwanableCounter = 0; spwanableCounter < numSpawnables; ++spwanableCounter)
for (unsigned int spwanableCounter = 0; spwanableCounter < numSpawnables; ++spwanableCounter)
{
AZStd::unique_ptr<AzFramework::Spawnable> spawnable = AZStd::make_unique<AzFramework::Spawnable>();
AzToolsFramework::Prefab::SpawnableUtils::CreateSpawnable(*spawnable, prefabDom);
@@ -8,6 +8,7 @@
#include <Prefab/PrefabTestDomUtils.h>
#include <Prefab/PrefabTestFixture.h>
#include <Prefab/PrefabTestComponent.h>
#include <AzCore/Component/TransformBus.h>
#include <AzCore/Component/ComponentApplicationBus.h>
@@ -18,6 +19,98 @@ namespace UnitTest
{
using PrefabInstanceToTemplateTests = PrefabTestFixture;
TEST_F(PrefabInstanceToTemplateTests, GenerateEntityDom_InvalidType_InvalidTypeSkipped)
{
const char* newEntityName = "New Entity";
AZ::Entity* newEntity = CreateEntity(newEntityName, false);
ASSERT_TRUE(newEntity);
// Add a component with a member that is missing reflection info
// and a member that is properly reflected
PrefabTestComponentWithUnReflectedTypeMember* newComponent =
newEntity->CreateComponent<PrefabTestComponentWithUnReflectedTypeMember>();
ASSERT_TRUE(newComponent);
AZStd::unique_ptr<Instance> prefabInstance = m_prefabSystemComponent->CreatePrefab({ newEntity }, {}, "test/path");
ASSERT_TRUE(prefabInstance);
PrefabDom entityDom;
m_instanceToTemplateInterface->GenerateDomForEntity(entityDom, *newEntity);
auto componentListDom = entityDom.FindMember("Components");
// Confirm that there is only one component in the entityDom
ASSERT_NE(componentListDom, entityDom.MemberEnd());
ASSERT_TRUE(componentListDom->value.IsObject());
ASSERT_EQ(componentListDom->value.MemberCount(), 1);
auto testComponentDom = componentListDom->value.MemberBegin();
ASSERT_TRUE(testComponentDom->value.IsObject());
// Confirm that the componentDom does not contained the invalid UnReflectedType
// We want to skip over it and produce a best effort entityDom
auto unReflectedTypeDom = testComponentDom->value.FindMember("UnReflectedType");
ASSERT_EQ(unReflectedTypeDom, testComponentDom->value.MemberEnd());
// Confirm the presence of the valid ReflectedType
auto reflectedTypeDom = testComponentDom->value.FindMember("ReflectedType");
ASSERT_NE(reflectedTypeDom, testComponentDom->value.MemberEnd());
// Confirm the reflected type has the correct type and value
ASSERT_TRUE(reflectedTypeDom->value.IsInt());
EXPECT_EQ(reflectedTypeDom->value.GetInt(), newComponent->m_reflectedType);
}
TEST_F(PrefabInstanceToTemplateTests, GenerateInstanceDom_InvalidType_InvalidTypeSkipped)
{
const char* newEntityName = "New Entity";
AZ::Entity* newEntity = CreateEntity(newEntityName, false);
ASSERT_TRUE(newEntity);
// Add a component with a member that is missing reflection info
// and a member that is properly reflected
PrefabTestComponentWithUnReflectedTypeMember* newComponent =
newEntity->CreateComponent<PrefabTestComponentWithUnReflectedTypeMember>();
ASSERT_TRUE(newComponent);
AZStd::unique_ptr<Instance> prefabInstance = m_prefabSystemComponent->CreatePrefab({ newEntity }, {}, "test/path");
ASSERT_TRUE(prefabInstance);
PrefabDom instanceDom;
m_instanceToTemplateInterface->GenerateDomForInstance(instanceDom, *prefabInstance);
// Acquire the entity out of the instanceDom
auto entitiesDom = instanceDom.FindMember(PrefabDomUtils::EntitiesName);
ASSERT_NE(entitiesDom, instanceDom.MemberEnd());
ASSERT_EQ(entitiesDom->value.MemberCount(), 1);
auto entityDom = entitiesDom->value.MemberBegin();
auto componentListDom = entityDom->value.FindMember("Components");
// Confirm that there is only one component in the entityDom
ASSERT_NE(componentListDom, entityDom->value.MemberEnd());
ASSERT_TRUE(componentListDom->value.IsObject());
ASSERT_EQ(componentListDom->value.MemberCount(), 1);
auto testComponentDom = componentListDom->value.MemberBegin();
ASSERT_TRUE(testComponentDom->value.IsObject());
// Confirm that the componentDom does not contained the invalid UnReflectedType
// We want to skip over it and produce a best effort entityDom
auto unReflectedTypeDom = testComponentDom->value.FindMember("UnReflectedType");
ASSERT_EQ(unReflectedTypeDom, testComponentDom->value.MemberEnd());
// Confirm the presence of the valid ReflectedType
auto reflectedTypeDom = testComponentDom->value.FindMember("ReflectedType");
ASSERT_NE(reflectedTypeDom, testComponentDom->value.MemberEnd());
// Confirm the reflected type has the correct type and value
ASSERT_TRUE(reflectedTypeDom->value.IsInt());
EXPECT_EQ(reflectedTypeDom->value.GetInt(), newComponent->m_reflectedType);
}
TEST_F(PrefabInstanceToTemplateTests, PrefabUpdateTemplate_UpdateEntityOnInstance)
{
//create template with single entity
@@ -28,4 +28,18 @@ namespace UnitTest
: m_boolProperty(boolProperty)
{
}
void PrefabTestComponentWithUnReflectedTypeMember::Reflect(AZ::ReflectContext* reflection)
{
AZ::SerializeContext* serializeContext = AZ::RttiCast<AZ::SerializeContext*>(reflection);
// We reflect our member but not its type this will result in missing reflection data
// when we try to store or load this field
if (serializeContext)
{
serializeContext->Class<PrefabTestComponentWithUnReflectedTypeMember, AzToolsFramework::Components::EditorComponentBase>()
->Field("UnReflectedType", &PrefabTestComponentWithUnReflectedTypeMember::m_unReflectedType)
->Field("ReflectedType", &PrefabTestComponentWithUnReflectedTypeMember::m_reflectedType);
}
}
}
@@ -27,4 +27,22 @@ namespace UnitTest
int m_intProperty = 0;
AZ::EntityId m_entityIdProperty;
};
class UnReflectedType
{
public:
AZ_TYPE_INFO(UnReflectedType, "{FB65262C-CE9A-45CA-99EB-4DDCB19B32DB}");
int m_unReflectedInt = 42;
};
class PrefabTestComponentWithUnReflectedTypeMember
: public AzToolsFramework::Components::EditorComponentBase
{
public:
AZ_EDITOR_COMPONENT(PrefabTestComponentWithUnReflectedTypeMember, "{726281E1-8E47-46AB-8018-D3F4BA823D74}");
static void Reflect(AZ::ReflectContext* reflection);
UnReflectedType m_unReflectedType;
int m_reflectedType = 52;
};
}
@@ -49,6 +49,7 @@ namespace UnitTest
EXPECT_TRUE(m_instanceToTemplateInterface);
GetApplication()->RegisterComponentDescriptor(PrefabTestComponent::CreateDescriptor());
GetApplication()->RegisterComponentDescriptor(PrefabTestComponentWithUnReflectedTypeMember::CreateDescriptor());
}
AZStd::unique_ptr<ToolsTestApplication> PrefabTestFixture::CreateTestApplication()
@@ -136,7 +136,10 @@ namespace UnitTest
PrefabDomValueReference wheelEntityComponentBoolPropertyValue =
PrefabDomUtils::FindPrefabDomValue(wheelEntityComponentValue->get(), PrefabTestDomUtils::BoolPropertyName);
ASSERT_FALSE(wheelEntityComponentBoolPropertyValue.has_value());
ASSERT_TRUE(wheelEntityComponentBoolPropertyValue.has_value());
ASSERT_TRUE(wheelEntityComponentBoolPropertyValue->get().IsBool());
ASSERT_FALSE(wheelEntityComponentBoolPropertyValue->get().GetBool());
// Validate that the axles under the car have the same DOM as the axle template.
PrefabTestDomUtils::ValidatePrefabDomInstances(axleInstanceAliasesUnderCar, carTemplateDom, axleTemplateDom);