You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
o3de/Gems/LyShine/Code/Editor/ComponentHelpers.cpp

1195 lines
48 KiB
C++

/*
* 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 "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,
[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