Files
o3de/Gems/LyShine/Code/Editor/ComponentHelpers.cpp
T
lumberyard-employee-dm 3dec5d3b71 LYN-2537 engine assets (#254)
* LYN-2537 Moved the Engine and Editor folder to be within the EngineAssets folder

* Fixed Documentation in bootstrap.cfg to correct the path to the user project specific registry file

* Adding a newline to the output of AssetCatalog 'Registering asset..., but type is not set' message

* Updating the AssetProcessorPlatformConfig.setreg Scan Folder to detect
the @ENGINEROOT@/EngineAssets/Engine path for engine runtime assets and
@ENGINEROOT@/EngineAssets/Editor path for engine tool assets

* Updating references to Icons and other assets to account for moving the
Engine and Editor folder under a single EngineAssets folder

* Moving the Engine Settings Registry folder from Engine/Registry -> Registry

* Removed the LY_PROJECT_CMAKE_PATH define as it is not portable to other locations. It is hard coded to the project location that was used for the CMake configuration. Furthermore it paths with backslashes within it are treated as escape characters and not a path separator

* Updated the LyTestTools asset_processor.py script to copy the exclude.filetag from the EngineAssets/Engine directory now

* Fixed Atom Shader Preprocessing when running using an External Project

* Updated the TSGenerateAction.cpp to fix the build error with using a renamed variable

* Updated the Install_Common.cmake ly_setup_others function to install the
EngineAssets directory and the each of the Gem's Assets directory while
maintaining the relative directory structure to the Engine Root
Also updated the install step to install the Registry folder at the
engine root

* Fixed the copying of the Registry folder to be in the install root, instead of under a second 'Registry' folder

* Moving the AssetProcessorPlatformConfig.setreg file over to the Registry folder

* Updated the LyTestTools and C++ code to point that the new location of
the AssetProcessorPlatformConfig.setreg file inside of the Registry
folder

* Renamed Test AssetProcessor*Config.ini files to have the .setreg extension

* Converted the AssetProcessor test setreg files from ini format to json
format using the SerializeContextTools convert-ini command

* Updated the AssetProcessor CMakeLists.txt to copy over the test setreg files to the build folder

* Updated the assetprocessor test file list to point at the renamed AsssetProcessor*Config setreg filenames

* Removed the Output Prefix code from the AssetProcessor. The complexity that it brought to the AP code is not needed, as users can replicate the behavior by just moving there assets underneath a another folder, underneath the scan folder

* Adding back support to read the AssetProcessorPlatformConfig.setreg file from the asset root. This is only needed for C++ UnitTests as they run in an environment where the accessing the Engine Settings Registry is not available

* Updating the Install_common.cmake logic to copy any "Assets" folder to
the install layout.
The Script has also been updated to copy over the "Assets" folder in the
Engine Root to the install layout instead of an "EngineAssets" folder

* Updating References to EngineAssets source asset folder in code to be the Assets source folder

* Moved the Engine Source Asset folder of 'EngineAssets' to a new folder name of 'Assets'. This is inline with the naming scheme we use for Gem asset folders

* Adding the EngineFinder.cmake to the AutomatedTesting project to allow it to work in a project centric manner

* Updating the LyTestTools copy_assets_to_project function to be able to copy assets with folders to the temporary project root
Fixed an issue in LyTestTools where the temporary log directory could have shutil.rmtree being called twice on it leading to an exception which fails an automated test

Updated the asset_procesor_gui_tests_2 AddScanFolder test to not use the
output prefix, but instead place the source asset root into a
subdirectory

* Correct the AssetProcessorPlatformConfig Scan Folders for the EngineAssets directory to point at the Assets directory

* Updated the asset procesor batch dependency test scan folder to point at the 'Assets' folder instead of 'EngineAssets'
2021-04-28 21:38:43 -05:00

1201 lines
48 KiB
C++

/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include "UiCanvasEditor_precompiled.h"
#include "EditorCommon.h"
#include "UiEditorInternalBus.h"
#include <AzCore/Component/ComponentBus.h>
#include <AzToolsFramework/Application/ToolsApplication.h>
#include <AzToolsFramework/API/EntityCompositionRequestBus.h>
#include <AzToolsFramework/ToolsComponents/GenericComponentWrapper.h>
#include <AzToolsFramework/API/EditorAssetSystemAPI.h>
#include <LyShine/Bus/UiSystemBus.h>
#include <AzToolsFramework/Entity/EditorEntityHelpers.h>
#include <AzToolsFramework/ToolsComponents/ComponentMimeData.h>
#include <LyShine/Bus/UiLayoutManagerBus.h>
#include <QMimeData>
// Internal helper functions
namespace Internal
{
AZStd::string GetComponentIconPath(const AZ::SerializeContext::ClassData& componentClassData)
{
AZStd::string iconPath = "Icons/Components/Component_Placeholder.svg";
auto editorElementData = componentClassData.m_editData->FindElementData(AZ::Edit::ClassElements::EditorData);
if (auto iconAttribute = editorElementData->FindAttribute(AZ::Edit::Attributes::Icon))
{
if (auto iconAttributeData = azdynamic_cast<const AZ::Edit::AttributeData<const char*>*>(iconAttribute))
{
AZStd::string iconAttributeValue = iconAttributeData->Get(nullptr);
if (!iconAttributeValue.empty())
{
iconPath = AZStd::move(iconAttributeValue);
}
}
}
// use absolute path if possible
bool result = false;
AZ::Data::AssetInfo info;
AZStd::string watchFolder;
AzToolsFramework::AssetSystemRequestBus::BroadcastResult(result, &AzToolsFramework::AssetSystemRequestBus::Events::GetSourceInfoBySourcePath, iconPath.c_str(), info, watchFolder);
if (result)
{
iconPath = AZStd::string::format("%s/%s", watchFolder.c_str(), info.m_relativePath.c_str());
}
return iconPath;
}
AZ::SerializeContext* GetSerializeContext()
{
AZ::SerializeContext* serializeContext = nullptr;
AZ::ComponentApplicationBus::BroadcastResult(serializeContext, &AZ::ComponentApplicationRequests::GetSerializeContext);
AZ_Assert(serializeContext, "We should have a valid context!");
return serializeContext;
}
const char* GetFriendlyComponentName(const AZ::SerializeContext::ClassData& componentClassData)
{
return componentClassData.m_editData
? componentClassData.m_editData->m_name
: componentClassData.m_name;
}
AZStd::string GetFriendlyComponentNameFromType(const AZ::TypeId& componentType)
{
const AZ::SerializeContext::ClassData* componentClassData = GetSerializeContext()->FindClassData(componentType);
AZStd::string componentName(componentClassData ? GetFriendlyComponentName(*componentClassData) : "<unknown>");
return componentName;
}
AzToolsFramework::Components::EditorComponentBase* GetEditorComponent(AZ::Component* component)
{
auto editorComponentBaseComponent = azrtti_cast<AzToolsFramework::Components::EditorComponentBase*>(component);
return editorComponentBaseComponent;
}
bool AppearsInUIComponentMenu(const AZ::SerializeContext::ClassData& componentClassData, bool forCanvasEntity)
{
return AzToolsFramework::AppearsInAddComponentMenu(componentClassData, forCanvasEntity ? AZ_CRC("CanvasUI", 0xe1e05605) : AZ_CRC("UI", 0x27ff46b0));
}
bool IsAddableByUser(const AZ::SerializeContext::ClassData& componentClassData)
{
if (componentClassData.m_editData)
{
if (auto editorDataElement = componentClassData.m_editData->FindElementData(AZ::Edit::ClassElements::EditorData))
{
if (auto addableAttribute = editorDataElement->FindAttribute(AZ::Edit::Attributes::AddableByUser))
{
// skip this component (return early) if user is not allowed to add it directly
if (auto addableData = azdynamic_cast<AZ::Edit::AttributeData<bool>*>(addableAttribute))
{
if (!addableData->Get(nullptr))
{
return false;
}
}
}
}
}
return true;
}
bool IsAddableByUserAndCompatibleWithEntityType(const AZ::SerializeContext::ClassData& componentClassData, bool forCanvasEntity)
{
return IsAddableByUser(componentClassData) && AppearsInUIComponentMenu(componentClassData, forCanvasEntity);
}
bool IsComponentServiceCompatibleWithOtherServices(const AZ::TypeId& componentType,
const AZStd::vector<AZ::TypeId>& otherComponentTypes)
{
// Get the componentDescriptor from the componentClassData
AZ::ComponentDescriptor* componentDescriptor = nullptr;
EBUS_EVENT_ID_RESULT(componentDescriptor, componentType, AZ::ComponentDescriptorBus, GetDescriptor);
if (!componentDescriptor)
{
AZStd::string message = AZStd::string::format("ComponentDescriptor not found for component %s.", GetFriendlyComponentNameFromType(componentType).c_str());
AZ_Error("UI Editor", false, message.c_str());
return false;
}
// Get the incompatible, provided and required services from the descriptor
AZ::ComponentDescriptor::DependencyArrayType incompatibleServices;
componentDescriptor->GetIncompatibleServices(incompatibleServices, nullptr);
AZ::ComponentDescriptor::DependencyArrayType providedServices;
componentDescriptor->GetProvidedServices(providedServices, nullptr);
AZ::ComponentDescriptor::DependencyArrayType requiredServices;
componentDescriptor->GetRequiredServices(requiredServices, nullptr);
// Check if component is compatible with other components
AZ::ComponentDescriptor::DependencyArrayType services;
for (const AZ::TypeId& otherComponentType : otherComponentTypes)
{
AZ::ComponentDescriptor* otherComponentDescriptor = nullptr;
EBUS_EVENT_ID_RESULT(otherComponentDescriptor, otherComponentType, AZ::ComponentDescriptorBus, GetDescriptor);
if (!otherComponentDescriptor)
{
AZStd::string message = AZStd::string::format("ComponentDescriptor not found for component %s.", GetFriendlyComponentNameFromType(otherComponentType).c_str());
AZ_Error("UI Editor", false, message.c_str());
return false;
}
services.clear();
otherComponentDescriptor->GetProvidedServices(services, nullptr);
// Check that none of the services currently provided by the entity are incompatible services
// for the new component.
// Also check that all of the required services of the new component are provided by the
// existing components
for (const AZ::ComponentServiceType& service : services)
{
for (const AZ::ComponentServiceType& incompatibleService : incompatibleServices)
{
if (service == incompatibleService)
{
return false;
}
}
for (auto iter = requiredServices.begin(); iter != requiredServices.end(); ++iter)
{
const AZ::ComponentServiceType& requiredService = *iter;
if (service == requiredService)
{
// this required service has been matched - remove from list
requiredServices.erase(iter);
break;
}
}
}
services.clear();
otherComponentDescriptor->GetIncompatibleServices(services, nullptr);
// Check that none of the services provided by the component are incompatible with any
// of the services currently provided by the entity
for (const AZ::ComponentServiceType& service : services)
{
for (const AZ::ComponentServiceType& providedService : providedServices)
{
if (service == providedService)
{
return false;
}
}
}
}
if (requiredServices.size() > 0)
{
// some required services were not provided by the components on the entity
return false;
}
return true;
}
bool AreComponentServicesCompatibleWithEntity(const AZStd::vector<AZ::TypeId>& componentTypes,
const AZ::EntityId& entityId,
ComponentHelpers::EntityComponentPair* firstIncompatibleComponentType = nullptr)
{
AZ::Entity* entity = AzToolsFramework::GetEntityById(entityId);
if (!entity)
{
AZ_Error("UI Editor", false, "Can't find entity by Id.");
return false;
}
// Make a list of the entity's existing component types
AZStd::vector<AZ::TypeId> entityComponentTypes;
entityComponentTypes.reserve(entity->GetComponents().size());
for (const AZ::Component* component : entity->GetComponents())
{
const AZ::Uuid& componentTypeId = AzToolsFramework::GetUnderlyingComponentType(*component);
entityComponentTypes.push_back(componentTypeId);
}
if (componentTypes.size() == 1)
{
if (!IsComponentServiceCompatibleWithOtherServices(componentTypes[0], entityComponentTypes))
{
if (firstIncompatibleComponentType)
{
*firstIncompatibleComponentType = AZStd::make_pair(entityId, componentTypes[0]);
}
return false;
}
}
else
{
for (int i = 0; i < componentTypes.size(); i++)
{
// Add all but this component type
AZStd::vector<AZ::TypeId> otherComponentTypes = entityComponentTypes;
if (i > 0)
{
otherComponentTypes.insert(otherComponentTypes.end(), componentTypes.begin(), componentTypes.begin() + i);
}
if (i < componentTypes.size() - 1)
{
otherComponentTypes.insert(otherComponentTypes.end(), componentTypes.begin() + (i + 1), componentTypes.end());
}
if (!IsComponentServiceCompatibleWithOtherServices(componentTypes[i], otherComponentTypes))
{
if (firstIncompatibleComponentType)
{
*firstIncompatibleComponentType = AZStd::make_pair(entityId, componentTypes[i]);
}
return false;
}
}
}
return true;
}
bool IsComponentServiceCompatibleWithEntity(const AZ::TypeId& componentType, const AZ::EntityId& entityId)
{
AZStd::vector<AZ::TypeId> componentTypes;
componentTypes.push_back(componentType);
return AreComponentServicesCompatibleWithEntity(componentTypes, entityId);
}
bool CanAddComponentsToEntities(const AzToolsFramework::ClassDataList& classDataForComponentsToAdd,
const AzToolsFramework::EntityIdList& entities,
bool isCanvasEntity,
ComponentHelpers::EntityComponentPair* firstIncompatibleComponentType = nullptr)
{
if (classDataForComponentsToAdd.empty() || entities.empty())
{
return false;
}
for (auto componentClassData : classDataForComponentsToAdd)
{
if (!IsAddableByUserAndCompatibleWithEntityType(*componentClassData, isCanvasEntity))
{
return false;
}
}
// Make a list of component types from component class data
AZStd::vector<AZ::TypeId> componentTypes;
componentTypes.reserve(classDataForComponentsToAdd.size());
for (auto componentClassData : classDataForComponentsToAdd)
{
componentTypes.push_back(componentClassData->m_typeId);
}
for (const AZ::EntityId& entityId : entities)
{
if (!AreComponentServicesCompatibleWithEntity(componentTypes, entityId, firstIncompatibleComponentType))
{
return false;
}
}
return true;
}
bool CanComponentServicesBeRemovedFromEntity(const AZ::Entity::ComponentArrayType& componentsToRemove, const AZ::EntityId& entityId)
{
AZ::Entity* entity = AzToolsFramework::GetEntityById(entityId);
if (!entity)
{
AZ_Error("UI Editor", false, "Can't find entity by Id.");
return false;
}
// Go through all the components on the entity (except the ones to remove) and collect all the required services
// and all the provided services
AZ::ComponentDescriptor::DependencyArrayType allRequiredServices;
AZ::ComponentDescriptor::DependencyArrayType allProvidedServices;
AZ::ComponentDescriptor::DependencyArrayType services;
for (const AZ::Component* component : entity->GetComponents())
{
if (AZStd::find(componentsToRemove.begin(), componentsToRemove.end(), component) != componentsToRemove.end())
{
continue;
}
const AZ::Uuid& componentTypeId = AzToolsFramework::GetUnderlyingComponentType(*component);
AZ::ComponentDescriptor* componentDescriptor = nullptr;
EBUS_EVENT_ID_RESULT(componentDescriptor, componentTypeId, AZ::ComponentDescriptorBus, GetDescriptor);
if (!componentDescriptor)
{
AZStd::string message = AZStd::string::format("ComponentDescriptor not found for component %s.", GetFriendlyComponentNameFromType(componentTypeId).c_str());
AZ_Error("UI Editor", false, message.c_str());
return false;
}
services.clear();
componentDescriptor->GetRequiredServices(services, nullptr);
for (auto requiredService : services)
{
allRequiredServices.push_back(requiredService);
}
services.clear();
componentDescriptor->GetProvidedServices(services, nullptr);
for (auto providedService : services)
{
allProvidedServices.push_back(providedService);
}
}
// remove all the satisfied services from the required services list
for (auto providedService : allProvidedServices)
{
for (auto iter = allRequiredServices.begin(); iter != allRequiredServices.end(); ++iter)
{
const AZ::ComponentServiceType& requiredService = *iter;
if (providedService == requiredService)
{
// this required service has been matched - remove from list
allRequiredServices.erase(iter);
break;
}
}
}
// if there is anything left in required services then check if the component we are removing
// provides any of those services, if so we cannot remove it
if (allRequiredServices.size() > 0)
{
for (const AZ::Component* componentToRemove : componentsToRemove)
{
const AZ::Uuid& componentToRemoveTypeId = AzToolsFramework::GetUnderlyingComponentType(*componentToRemove);
AZ::ComponentDescriptor* componentDescriptor = nullptr;
EBUS_EVENT_ID_RESULT(componentDescriptor, componentToRemoveTypeId, AZ::ComponentDescriptorBus, GetDescriptor);
if (!componentDescriptor)
{
AZStd::string message = AZStd::string::format("ComponentDescriptor not found for component %s.", GetFriendlyComponentNameFromType(componentToRemoveTypeId).c_str());
AZ_Error("UI Editor", false, message.c_str());
return false;
}
// Get the services provided by the component to be deleted
AZ::ComponentDescriptor::DependencyArrayType providedServices;
componentDescriptor->GetProvidedServices(providedServices, nullptr);
for (auto requiredService : allRequiredServices)
{
// Check that none of the services currently still by the entity are the any of the ones we want to remove
for (auto providedService : providedServices)
{
if (requiredService == providedService)
{
// this required service is being provided by the component we want to remove
return false;
}
}
}
}
}
return true;
}
bool CanComponentServicesBeRemoved(const AZ::Entity::ComponentArrayType& componentsToRemove)
{
// Group components by entityId
AZStd::unordered_map<AZ::EntityId, AZ::Entity::ComponentArrayType> m_componentsByEntityId;
for (auto component : componentsToRemove)
{
m_componentsByEntityId[component->GetEntityId()].push_back(component);
}
for (auto componentsByEntityIdItr : m_componentsByEntityId)
{
const AZ::EntityId& entityId = componentsByEntityIdItr.first;
const AZ::Entity::ComponentArrayType& componentsToRemoveFromEntity = componentsByEntityIdItr.second;
if (!CanComponentServicesBeRemovedFromEntity(componentsToRemoveFromEntity, entityId))
{
return false;
}
}
return true;
}
bool AreComponentsAddableByUser(const AZ::Entity::ComponentArrayType& components)
{
// Get the serialize context.
AZ::SerializeContext* serializeContext = GetSerializeContext();
for (auto component : components)
{
const AZ::Uuid& componentToAddTypeId = AzToolsFramework::GetUnderlyingComponentType(*component);
const AZ::SerializeContext::ClassData* componentClassData = serializeContext->FindClassData(componentToAddTypeId);
if (!componentClassData)
{
AZ_Error("UI Editor", false, "Can't find class data for class Id %s", componentToAddTypeId.ToString<AZStd::string>().c_str());
return false;
}
if (!IsAddableByUser(*componentClassData))
{
return false;
}
}
return true;
}
bool CanComponentsBeRemoved(const AZ::Entity::ComponentArrayType& componentsToRemove)
{
if (!AreComponentsAddableByUser(componentsToRemove))
{
return false;
}
return CanComponentServicesBeRemoved(componentsToRemove);
}
bool CanPasteComponentsToEntities(const AzToolsFramework::EntityIdList& entities, bool isCanvasEntity)
{
const QMimeData* mimeData = AzToolsFramework::ComponentMimeData::GetComponentMimeDataFromClipboard();
// Check that there are components on the clipboard and that they can all be pasted onto the entities
bool canPasteAll = true;
if (entities.empty() || !mimeData)
{
canPasteAll = false;
}
else
{
// Create class data from mime data
AzToolsFramework::ComponentTypeMimeData::ClassDataContainer classDataForComponentsToAdd;
AzToolsFramework::ComponentTypeMimeData::Get(mimeData, classDataForComponentsToAdd);
canPasteAll = CanAddComponentsToEntities(classDataForComponentsToAdd, entities, isCanvasEntity);
}
return canPasteAll;
}
AzToolsFramework::EntityIdList GetSelectedEntities(bool* isCanvasSelectedOut = nullptr)
{
AzToolsFramework::EntityIdList selectedEntities;
EBUS_EVENT_RESULT(selectedEntities, UiEditorInternalRequestBus, GetSelectedEntityIds);
if (isCanvasSelectedOut)
{
*isCanvasSelectedOut = false;
}
if (selectedEntities.empty())
{
AZ::EntityId canvasEntityId;
EBUS_EVENT_RESULT(canvasEntityId, UiEditorInternalRequestBus, GetActiveCanvasEntityId);
selectedEntities.push_back(canvasEntityId);
if (isCanvasSelectedOut)
{
*isCanvasSelectedOut = true;
}
}
return selectedEntities;
}
AZ::Entity::ComponentArrayType GetCopyableComponents(const AZ::Entity::ComponentArrayType& componentsToCopy)
{
AzToolsFramework::EntityIdList selectedEntities = GetSelectedEntities();
AZ::EntityId firstSelectedEntity = selectedEntities.front();
// Copyable components are the components that belong to the first selected entity
AZ::Entity::ComponentArrayType copyableComponents;
copyableComponents.reserve(componentsToCopy.size());
for (auto component : componentsToCopy)
{
const AZ::EntityId entityId = component ? component->GetEntityId() : AZ::EntityId();
if (entityId == firstSelectedEntity)
{
copyableComponents.push_back(component);
}
}
return copyableComponents;
}
void HandleSelectedEntitiesPropertiesChanged()
{
EBUS_EVENT(UiEditorInternalNotificationBus, OnSelectedEntitiesPropertyChanged);
}
void RemoveComponents(const AZ::Entity::ComponentArrayType& componentsToRemove)
{
// Group components by entityId
AZStd::unordered_map<AZ::EntityId, AZ::Entity::ComponentArrayType> m_componentsByEntityId;
for (auto component : componentsToRemove)
{
m_componentsByEntityId[component->GetEntityId()].push_back(component);
}
// Since the undo commands use the selected entities, make sure that the components being removed belong to selected entities
AzToolsFramework::EntityIdList selectedEntities = GetSelectedEntities();
bool foundUnselectedEntities = false;
for (auto componentsByEntityIdItr : m_componentsByEntityId)
{
const AZ::EntityId& entityId = componentsByEntityIdItr.first;
if (!entityId.IsValid() || AZStd::find(selectedEntities.begin(), selectedEntities.end(), entityId) == selectedEntities.end())
{
foundUnselectedEntities = true;
break;
}
}
if (foundUnselectedEntities)
{
AZ_Error("UI Editor", false, "Attempting to remove components from an unselected element.");
return;
}
EBUS_EVENT(UiEditorInternalNotificationBus, OnBeginUndoableEntitiesChange);
for (auto componentsByEntityIdItr : m_componentsByEntityId)
{
const AZ::EntityId& entityId = componentsByEntityIdItr.first;
const AZ::Entity::ComponentArrayType& componentsToRemoveFromEntity = componentsByEntityIdItr.second;
AZ::Entity* entity = AzToolsFramework::GetEntityById(entityId);
if (!entity)
{
AZ_Error("UI Editor", false, "Can't find entity by Id.");
continue;
}
// We must deactivate the entity to remove components
bool reactivate = false;
if (entity->GetState() == AZ::Entity::State::Active)
{
reactivate = true;
entity->Deactivate();
}
// Remove all the components requested
for (auto componentToRemove : componentsToRemoveFromEntity)
{
// See if the component is on the entity
const auto& entityComponents = entity->GetComponents();
if (AZStd::find(entityComponents.begin(), entityComponents.end(), componentToRemove) != entityComponents.end())
{
entity->RemoveComponent(componentToRemove);
delete componentToRemove;
}
}
// Reactivate if we were previously active
if (reactivate)
{
entity->Activate();
}
}
EBUS_EVENT(UiEditorInternalNotificationBus, OnEndUndoableEntitiesChange, componentsToRemove.size() > 1 ? "delete components" : "delete component");
HandleSelectedEntitiesPropertiesChanged();
}
void CopyComponents(const AZ::Entity::ComponentArrayType& copyableComponents)
{
// Create the mime data object
AZStd::unique_ptr<QMimeData> mimeData = AzToolsFramework::ComponentMimeData::Create(copyableComponents);
// Put it on the clipboard
AzToolsFramework::ComponentMimeData::PutComponentMimeDataOnClipboard(AZStd::move(mimeData));
}
void AddComponentsWithAssetToEntities(const ComponentAssetHelpers::ComponentAssetPairs& componentAssetPairs, const AzToolsFramework::EntityIdList& entities)
{
for (const AZ::EntityId& entityId : entities)
{
ComponentHelpers::AddComponentsWithAssetToEntity(componentAssetPairs, entityId);
}
}
bool GetClassDataForComponentsFromType(const AZStd::vector<AZ::TypeId>& componentTypes,
AzToolsFramework::ClassDataList& classDataForComponentsToAdd)
{
// Make a list of component class data from the component types
classDataForComponentsToAdd.reserve(componentTypes.size());
AZ::SerializeContext* serializeContext = GetSerializeContext();
for (const AZ::TypeId& componentType : componentTypes)
{
const AZ::SerializeContext::ClassData* componentClassData = serializeContext->FindClassData(componentType);
if (!componentClassData)
{
AZ_Error("UI Editor", false, "Can't find class data for class Id %s", componentType.ToString<AZStd::string>().c_str());
return false;
}
classDataForComponentsToAdd.push_back(componentClassData);
}
return true;
}
} // namespace
namespace ComponentHelpers
{
QList<QAction*> CreateAddComponentActions(HierarchyWidget* hierarchy,
QTreeWidgetItemRawPtrQList& selectedItems,
QWidget* parent)
{
HierarchyItemRawPtrList items = SelectionHelpers::GetSelectedHierarchyItems(hierarchy,
selectedItems);
AzToolsFramework::EntityIdList entitiesSelected;
bool isCanvasSelected = false;
if (selectedItems.empty())
{
isCanvasSelected = true;
entitiesSelected.push_back(hierarchy->GetEditorWindow()->GetCanvas());
}
else
{
for (auto item : items)
{
entitiesSelected.push_back(item->GetEntityId());
}
}
using ComponentsList = AZStd::vector < const AZ::SerializeContext::ClassData* >;
ComponentsList componentsList;
// Get the serialize context.
AZ::SerializeContext* serializeContext = Internal::GetSerializeContext();
// Gather all components that match our filter and group by category.
serializeContext->EnumerateDerived<AZ::Component>(
[&componentsList, isCanvasSelected](const AZ::SerializeContext::ClassData* componentClassData, const AZ::Uuid& knownType) -> bool
{
(void)knownType;
if (Internal::AppearsInUIComponentMenu(*componentClassData, isCanvasSelected))
{
if (!Internal::IsAddableByUser(*componentClassData))
{
return true;
}
componentsList.push_back(componentClassData);
}
return true;
}
);
// Create a component list that is in the same order that the components were registered in
const AZStd::vector<AZ::Uuid>* componentOrderList;
ComponentsList orderedComponentsList;
EBUS_EVENT_RESULT(componentOrderList, UiSystemBus, GetComponentTypesForMenuOrdering);
for (auto& componentType : *componentOrderList)
{
auto iter = AZStd::find_if(componentsList.begin(), componentsList.end(),
[componentType](const AZ::SerializeContext::ClassData* componentClassData) -> bool
{
return (componentClassData->m_typeId == componentType);
}
);
if (iter != componentsList.end())
{
orderedComponentsList.push_back(*iter);
componentsList.erase(iter);
}
}
// add any remaining component desciptiors to the end of the ordered list
// (just to catch any component types that were not registered for ordering)
for (auto componentClass : componentsList)
{
orderedComponentsList.push_back(componentClass);
}
QList<QAction*> result;
{
// Add an action for each factory.
for (auto componentClass : orderedComponentsList)
{
const char* typeName = componentClass->m_editData->m_name;
AZStd::string iconPath = Internal::GetComponentIconPath(*componentClass);
QString iconUrl = QString(iconPath.c_str());
bool isEnabled = false;
if (items.empty())
{
AZ::EntityId canvasEntityId = hierarchy->GetEditorWindow()->GetCanvas();
isEnabled = Internal::IsComponentServiceCompatibleWithEntity(componentClass->m_typeId, canvasEntityId);
}
else
{
for (auto i : items)
{
AZ::EntityId entityId = i->GetEntityId();
if (Internal::IsComponentServiceCompatibleWithEntity(componentClass->m_typeId, entityId))
{
isEnabled = true;
// Stop iterating thru the items
// and go to the next factory.
break;
}
}
}
QAction* action = new QAction(QIcon(iconUrl), typeName, parent);
action->setEnabled(isEnabled);
QObject::connect(action,
&QAction::triggered,
hierarchy,
[serializeContext, hierarchy, componentClass, items]([[maybe_unused]] bool checked)
{
EBUS_EVENT(UiEditorInternalNotificationBus, OnBeginUndoableEntitiesChange);
AzToolsFramework::EntityIdList entitiesSelected;
if (items.empty())
{
entitiesSelected.push_back(hierarchy->GetEditorWindow()->GetCanvas());
}
else
{
for (auto item : items)
{
entitiesSelected.push_back(item->GetEntityId());
}
}
for (auto entityId : entitiesSelected)
{
if (Internal::IsComponentServiceCompatibleWithEntity(componentClass->m_typeId, entityId))
{
AZ::Entity* entity = AzToolsFramework::GetEntityById(entityId);
if (!entity)
{
AZ_Error("UI Editor", false, "Can't find entity by Id.");
continue;
}
entity->Deactivate();
AZ::Component* component;
EBUS_EVENT_ID_RESULT(component, componentClass->m_typeId, AZ::ComponentDescriptorBus, CreateComponent);
entity->AddComponent(component);
entity->Activate();
}
}
EBUS_EVENT(UiEditorInternalNotificationBus, OnEndUndoableEntitiesChange, "add component");
Internal::HandleSelectedEntitiesPropertiesChanged();
});
result.push_back(action);
}
}
return result;
}
QAction* CreateRemoveComponentsAction(QWidget* parent)
{
QAction* action = new QAction("Delete component", parent);
action->setShortcut(QKeySequence::Delete);
action->setShortcutContext(Qt::WidgetWithChildrenShortcut);
QObject::connect(action,
&QAction::triggered,
[]()
{
AZ::Entity::ComponentArrayType componentsToRemove;
EBUS_EVENT_RESULT(componentsToRemove, UiEditorInternalRequestBus, GetSelectedComponents);
Internal::RemoveComponents(componentsToRemove);
Internal::HandleSelectedEntitiesPropertiesChanged();
});
return action;
}
void UpdateRemoveComponentsAction(QAction* action)
{
AZ::Entity::ComponentArrayType componentsToRemove;
EBUS_EVENT_RESULT(componentsToRemove, UiEditorInternalRequestBus, GetSelectedComponents);
action->setText(componentsToRemove.size() > 1 ? "Delete components" : "Delete component");
// Check if we can remove every component from every element
bool canRemove = true;
if (componentsToRemove.empty() || !Internal::CanComponentsBeRemoved(componentsToRemove))
{
canRemove = false;
}
// Disable the action if not every element can remove the component
action->setEnabled(canRemove);
}
QAction* CreateCutComponentsAction(QWidget* parent)
{
QAction* action = new QAction("Cut component", parent);
action->setShortcut(QKeySequence::Cut);
action->setShortcutContext(Qt::WidgetWithChildrenShortcut);
QObject::connect(action,
&QAction::triggered,
[]()
{
AZ::Entity::ComponentArrayType componentsToCut;
EBUS_EVENT_RESULT(componentsToCut, UiEditorInternalRequestBus, GetSelectedComponents);
AZ::Entity::ComponentArrayType copyableComponents = Internal::GetCopyableComponents(componentsToCut);
// Copy components
Internal::CopyComponents(copyableComponents);
// Delete components
Internal::RemoveComponents(componentsToCut);
});
return action;
}
void UpdateCutComponentsAction(QAction* action)
{
AZ::Entity::ComponentArrayType componentsToCut;
EBUS_EVENT_RESULT(componentsToCut, UiEditorInternalRequestBus, GetSelectedComponents);
AZ::Entity::ComponentArrayType copyableComponents = Internal::GetCopyableComponents(componentsToCut);
action->setText(componentsToCut.size() > 1 ? "Cut components" : "Cut component");
// Check that all components can be deleted and that all copyable components can be pasted
bool canCut = true;
if (copyableComponents.empty()
|| componentsToCut.empty()
|| !Internal::AreComponentsAddableByUser(copyableComponents)
|| !Internal::CanComponentsBeRemoved(componentsToCut))
{
canCut = false;
}
// Disable the action if not every component can be deleted or every copyable components pasted
action->setEnabled(canCut);
}
QAction* CreateCopyComponentsAction(QWidget* parent)
{
QAction* action = new QAction("Copy component", parent);
action->setShortcut(QKeySequence::Copy);
action->setShortcutContext(Qt::WidgetWithChildrenShortcut);
QObject::connect(action,
&QAction::triggered,
[]()
{
AZ::Entity::ComponentArrayType componentsToCopy;
EBUS_EVENT_RESULT(componentsToCopy, UiEditorInternalRequestBus, GetSelectedComponents);
// Get the components of the first selected elements to copy onto the clipboard
AZ::Entity::ComponentArrayType copyableComponents = Internal::GetCopyableComponents(componentsToCopy);
Internal::CopyComponents(copyableComponents);
});
return action;
}
void UpdateCopyComponentsAction(QAction* action)
{
AZ::Entity::ComponentArrayType componentsToCopy;
EBUS_EVENT_RESULT(componentsToCopy, UiEditorInternalRequestBus, GetSelectedComponents);
// Get the components of the first selected elements to copy onto the clipboard
AZ::Entity::ComponentArrayType copyableComponents = Internal::GetCopyableComponents(componentsToCopy);
action->setText(copyableComponents.size() > 1 ? "Copy components" : "Copy component");
// Check that all copyable components can be added by the user
bool canCopy = true;
if (copyableComponents.empty() || !Internal::AreComponentsAddableByUser(copyableComponents))
{
canCopy = false;
}
// Disable the action if not all copyable components can be added to all elements
action->setEnabled(canCopy);
}
QAction* CreatePasteComponentsAction(QWidget* parent)
{
QAction* action = new QAction("Paste component", parent);
action->setShortcut(QKeySequence::Paste);
action->setShortcutContext(Qt::WidgetWithChildrenShortcut);
QObject::connect(action,
&QAction::triggered,
[]()
{
bool isCanvasSelected = false;
AzToolsFramework::EntityIdList selectedEntities = Internal::GetSelectedEntities(&isCanvasSelected);
if (Internal::CanPasteComponentsToEntities(selectedEntities, isCanvasSelected))
{
// Create components from mime data
const QMimeData* mimeData = AzToolsFramework::ComponentMimeData::GetComponentMimeDataFromClipboard();
AzToolsFramework::ComponentMimeData::ComponentDataContainer componentsToAdd;
AzToolsFramework::ComponentMimeData::GetComponentDataFromMimeData(mimeData, componentsToAdd);
// Create class data from mime data
AzToolsFramework::ComponentTypeMimeData::ClassDataContainer classDataForComponentsToAdd;
AzToolsFramework::ComponentTypeMimeData::Get(mimeData, classDataForComponentsToAdd);
AZ_Error("UI Editor", componentsToAdd.size() == classDataForComponentsToAdd.size(), "Component mime data's components list size is different from class data list size.");
if (componentsToAdd.size() == classDataForComponentsToAdd.size())
{
EBUS_EVENT(UiEditorInternalNotificationBus, OnBeginUndoableEntitiesChange);
// Paste to all selected entities
for (const AZ::EntityId& entityId : selectedEntities)
{
AZ::Entity* entity = AzToolsFramework::GetEntityById(entityId);
// De-serialize from mime data each time the component is pasted, otherwise the same component pointer could be added to multiple entities.
AzToolsFramework::ComponentMimeData::GetComponentDataFromMimeData(mimeData, componentsToAdd);
if (!entity)
{
AZ_Error("UI Editor", false, "Can't find entity by Id.");
continue;
}
// We must deactivate the entity to add components
bool reactivate = false;
if (entity->GetState() == AZ::Entity::State::Active)
{
reactivate = true;
entity->Deactivate();
}
// Add components
for (int componentIndex = 0; componentIndex < componentsToAdd.size(); ++componentIndex)
{
AZ::Component* component = componentsToAdd[componentIndex];
if (!component)
{
AZ_Error("UI Editor", false, "Null component provided by mime data.");
continue;
}
// Add the component
entity->AddComponent(component);
}
// Reactivate if we were previously active
if (reactivate)
{
entity->Activate();
}
}
EBUS_EVENT(UiEditorInternalNotificationBus, OnEndUndoableEntitiesChange, "paste component");
Internal::HandleSelectedEntitiesPropertiesChanged();
}
}
});
return action;
}
void UpdatePasteComponentsAction(QAction* action)
{
const QMimeData* mimeData = AzToolsFramework::ComponentMimeData::GetComponentMimeDataFromClipboard();
AzToolsFramework::ComponentTypeMimeData::ClassDataContainer classDataForComponentsToAdd;
AzToolsFramework::ComponentTypeMimeData::Get(mimeData, classDataForComponentsToAdd);
action->setText(classDataForComponentsToAdd.size() > 1 ? "Paste components" : "Paste component");
bool isCanvasSelected = false;
AzToolsFramework::EntityIdList selectedEntities = Internal::GetSelectedEntities(&isCanvasSelected);
// Check that there are components on the clipboard and that they can all be pasted onto the selected elements
bool canPasteAll = Internal::CanPasteComponentsToEntities(selectedEntities, isCanvasSelected);
// Disable the action if not every component can be pasted onto every element
action->setEnabled(canPasteAll);
}
bool CanAddComponentsToSelectedEntities(const AZStd::vector<AZ::TypeId>& componentTypes, EntityComponentPair* firstIncompatibleComponentType)
{
bool isCanvasSelected = false;
AzToolsFramework::EntityIdList selectedEntities = Internal::GetSelectedEntities(&isCanvasSelected);
if (selectedEntities.empty())
{
return false;
}
// Make a list of component class data for all the components to add
AzToolsFramework::ClassDataList classDataForComponentsToAdd;
if (!Internal::GetClassDataForComponentsFromType(componentTypes, classDataForComponentsToAdd))
{
return false;
}
return Internal::CanAddComponentsToEntities(classDataForComponentsToAdd, selectedEntities, isCanvasSelected, firstIncompatibleComponentType);
}
void AddComponentsWithAssetToSelectedEntities(const ComponentAssetHelpers::ComponentAssetPairs& componentAssetPairs)
{
EBUS_EVENT(UiEditorInternalNotificationBus, OnBeginUndoableEntitiesChange);
AzToolsFramework::EntityIdList selectedEntities = Internal::GetSelectedEntities();
Internal::AddComponentsWithAssetToEntities(componentAssetPairs, selectedEntities);
EBUS_EVENT(UiEditorInternalNotificationBus, OnEndUndoableEntitiesChange, "add component");
Internal::HandleSelectedEntitiesPropertiesChanged();
}
bool CanAddComponentsToEntity(const AZStd::vector<AZ::TypeId>& componentTypes,
const AZ::EntityId& entityId,
EntityComponentPair* firstIncompatibleComponentType)
{
// Make a list of component class data for all the components to add
AzToolsFramework::ClassDataList classDataForComponentsToAdd;
if (!Internal::GetClassDataForComponentsFromType(componentTypes, classDataForComponentsToAdd))
{
return false;
}
AzToolsFramework::EntityIdList entities;
entities.push_back(entityId);
bool isCanvasEntity = (UiCanvasBus::FindFirstHandler(entityId));
return Internal::CanAddComponentsToEntities(classDataForComponentsToAdd, entities, isCanvasEntity, firstIncompatibleComponentType);
}
void AddComponentsWithAssetToEntity(const ComponentAssetHelpers::ComponentAssetPairs& componentAssetPairs, const AZ::EntityId& entityId)
{
if (!entityId.IsValid())
{
AZ_Error("UI Editor", false, "Attempting to add components to an invalid entityId.");
return;
}
if (componentAssetPairs.empty())
{
AZ_Error("UI Editor", false, "Attempting to add an empty list of components to and entity.");
return;
}
AZ::Entity* entity = AzToolsFramework::GetEntityById(entityId);
if (!entity)
{
AZ_Error("UI Editor", false, "Can't find entity by Id.");
return;
}
// We must deactivate the entity to add components
bool reactivate = false;
if (entity->GetState() == AZ::Entity::State::Active)
{
reactivate = true;
entity->Deactivate();
}
// Add all components and remember the assets that will be assigned to them after the element is reactivated
AZStd::vector<AZStd::pair<AZ::Component*, AZ::Data::AssetId>> newComponentAssetPairs;
for (const ComponentAssetHelpers::ComponentAssetPair& pair : componentAssetPairs)
{
const AZ::TypeId& componentType = pair.first;
const AZ::Data::AssetId& assetId = pair.second;
AZ::Component* component;
EBUS_EVENT_ID_RESULT(component, componentType, AZ::ComponentDescriptorBus, CreateComponent);
entity->AddComponent(component);
newComponentAssetPairs.push_back(AZStd::make_pair(component, assetId));
}
// Reactivate if we were previously active
if (reactivate)
{
entity->Activate();
}
// Assign assets to components after the entity has been reactivated
for (AZStd::pair<AZ::Component*, AZ::Data::AssetId>& pair : newComponentAssetPairs)
{
AZ::Component* component = pair.first;
const AZ::Data::AssetId& assetId = pair.second;
auto editorComponent = Internal::GetEditorComponent(component);
if (editorComponent)
{
editorComponent->SetPrimaryAsset(assetId);
}
}
}
AZStd::vector<ComponentTypeData> GetAllComponentTypesThatCanAppearInAddComponentMenu()
{
using ComponentsList = AZStd::vector<ComponentTypeData>;
ComponentsList componentsList;
// Get the serialize context.
AZ::SerializeContext* serializeContext = Internal::GetSerializeContext();
const AZStd::list<AZ::ComponentDescriptor*>* lyShineComponentDescriptors;
EBUS_EVENT_RESULT(lyShineComponentDescriptors, UiSystemBus, GetLyShineComponentDescriptors);
// Gather all components that match our filter and group by category.
serializeContext->EnumerateDerived<AZ::Component>(
[ &componentsList, lyShineComponentDescriptors ](const AZ::SerializeContext::ClassData* classData, const AZ::Uuid& knownType) -> bool
{
(void)knownType;
if (Internal::AppearsInUIComponentMenu(*classData, false))
{
if (!Internal::IsAddableByUser(*classData))
{
// skip this component (return early) if user is not allowed to add it directly
return true;
}
bool isLyShineComponent = false;
for (auto descriptor : * lyShineComponentDescriptors)
{
if (descriptor->GetUuid() == classData->m_typeId)
{
isLyShineComponent = true;
break;
}
}
ComponentTypeData data;
data.classData = classData;
data.isLyShineComponent = isLyShineComponent;
componentsList.push_back(data);
}
return true;
}
);
return componentsList;
}
} // namespace ComponentHelpers