// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "CoreMinimal.h" #include "Templates/SubclassOf.h" #include "Components/ActorComponent.h" #include "Engine/EngineTypes.h" #include "GameFramework/Actor.h" #include "UObject/UObjectHash.h" class UToolMenu; class UMaterialInterface; class FComponentEditorUtils { public: /** Is the instance component is editable */ static UNREALED_API bool CanEditComponentInstance(const UActorComponent* ActorComp, const UActorComponent* ParentSceneComp, bool bAllowUserContructionScript); /** * Test if the native component is editable. If it is, return a valid pointer to it's FProperty * Otherwise, return nullptr. A native component is editable if it is marked as EditAnywhere * via meta data tags or is within an editable property container */ static UNREALED_API FProperty* GetPropertyForEditableNativeComponent(const UActorComponent* NativeComponent); /** Test whether or not the given string is a valid variable name string for the given component instance */ static UNREALED_API bool IsValidVariableNameString(const UActorComponent* InComponent, const FString& InString); /** * Test whether or not the given string is already the name string of a component on the the owner * Optionally excludes an existing component from the check (ex. a component currently being renamed) * @return True if the InString is an available name for a component of ComponentOwner */ static UNREALED_API bool IsComponentNameAvailable(const FString& InString, UObject* ComponentOwner, const UActorComponent* ComponentToIgnore = nullptr); /** Generate a valid variable name string for the given component instance */ static UNREALED_API FString GenerateValidVariableName(TSubclassOf InComponentClass, AActor* ComponentOwner); /** Generate a valid variable name string for the given component instance based on the name of the asset referenced by the component */ static UNREALED_API FString GenerateValidVariableNameFromAsset(UObject* Asset, AActor* ComponentOwner); /** * Checks whether it is valid to copy the given component * @param ComponentToCopy The component to check * @return Whether the given component can be copied */ static UNREALED_API bool CanCopyComponent(const UActorComponent* ComponentToCopy); /** * Checks whether it is valid to copy the indicated components * @param ComponentsToCopy The list of components to check * @return Whether the indicated components can be copied */ static UNREALED_API bool CanCopyComponents(const TArray& ComponentsToCopy); /** * Copies the selected components to the clipboard * @param ComponentsToCopy The list of components to copy * @param DestinationData Buffer to fill with the copied data, or null to use the clipboard */ static UNREALED_API void CopyComponents(const TArray& ComponentsToCopy, FString* DestinationData = nullptr); /** * Determines whether the current contents of the clipboard contain paste-able component information * @param RootComponent The root component of the actor being pasted on * @param bOverrideCanAttach Optional override declaring that components can be attached and a check is not needed * @param SourceData Component data to paste, or null to use the clipboard * @return Whether components can be pasted */ static UNREALED_API bool CanPasteComponents(const USceneComponent* RootComponent, bool bOverrideCanAttach = false, bool bPasteAsArchetypes = false, const FString* SourceData = nullptr); /** * Attempts to paste components from the clipboard as siblings of the target component * @param OutPastedComponents List of all the components that were pasted * @param TargetActor The actor to attach the pasted components to * @param TargetComponent The component the paste is targeting (will attempt to paste components as siblings). If null, will attach pasted components to the root. * @param SourceData Component data to paste, or null to use the clipboard */ static UNREALED_API void PasteComponents(TArray& OutPastedComponents, AActor* TargetActor, USceneComponent* TargetComponent = nullptr, const FString* SourceData = nullptr); /** * Gets the copied components from the clipboard without attempting to paste/apply them in any way * @param OutParentMap Contains the child->parent name relationships of the copied components * @param OutNewObjectMap Contains the name->instance object mapping of the copied components */ static UNREALED_API void GetComponentsFromClipboard(TMap& OutParentMap, TMap& OutNewObjectMap, bool bGetComponentsAsArchetypes); /** * Determines whether the indicated component can be deleted * @param ComponentToDelete The component to determine can be deleted * @return Whether the indicated component can be deleted */ static UNREALED_API bool CanDeleteComponent(const UActorComponent* ComponentToDelete); /** * Determines whether the indicated components can be deleted * @param ComponentsToDelete The list of components to determine can be deleted * @return Whether the indicated components can be deleted */ static UNREALED_API bool CanDeleteComponents(const TArray& ComponentsToDelete); /** * Deletes the indicated components and identifies the component that should be selected following the operation. * Note: Does not take care of the actual selection of a new component. It only identifies which component should be selected. * * @param ComponentsToDelete The list of components to delete * @param OutComponentToSelect The component that should be selected after the deletion * @return The number of components that were actually deleted */ static UNREALED_API int32 DeleteComponents(const TArray& ComponentsToDelete, UActorComponent*& OutComponentToSelect); /** * Duplicates a component instance and takes care of attachment and registration. * @param TemplateComponent The component to duplicate * @return The created clone of the provided TemplateComponent. nullptr if the duplication failed. */ static UNREALED_API UActorComponent* DuplicateComponent(UActorComponent* TemplateComponent); /** * Ensures that the selection override delegate is properly bound for the supplied component * This includes any attached editor-only primitive components (such as billboard visualizers) * * @param SceneComponent The component to set the selection override for * @param bBind Whether the override should be bound */ static UNREALED_API void BindComponentSelectionOverride(USceneComponent* SceneComponent, bool bBind); /** * Attempts to apply a material to a component at the specified slot. * * @param SceneComponent The component to which we should attempt to apply the material * @param MaterialToApply The material to apply to the component * @param OptionalMaterialSlot The material slot on the component to which the material should be applied. -1 to apply to all slots on the component. * * @return True if the material was successfully applied to the component. */ static UNREALED_API bool AttemptApplyMaterialToComponent( USceneComponent* SceneComponent, UMaterialInterface* MaterialToApply, int32 OptionalMaterialSlot = -1 ); /** Potentially transforms the delta to be applied to a component into the appropriate space */ static UNREALED_API void AdjustComponentDelta(const USceneComponent* Component, FVector& Drag, FRotator& Rotation); // Given a template and a property, propagates a default value change to all instances (only if applicable) template static void PropagateDefaultValueChange(class USceneComponent* InSceneComponentTemplate, const class FProperty* InProperty, const T& OldDefaultValue, const T& NewDefaultValue, TSet& UpdatedInstances, int32 PropertyOffset = INDEX_NONE) { TArray ArchetypeInstances; if(InSceneComponentTemplate->HasAnyFlags(RF_ArchetypeObject)) { InSceneComponentTemplate->GetArchetypeInstances(ArchetypeInstances); for(int32 InstanceIndex = 0; InstanceIndex < ArchetypeInstances.Num(); ++InstanceIndex) { USceneComponent* InstancedSceneComponent = static_cast(ArchetypeInstances[InstanceIndex]); if(InstancedSceneComponent != nullptr && !UpdatedInstances.Contains(InstancedSceneComponent) && ApplyDefaultValueChange(InstancedSceneComponent, InProperty, OldDefaultValue, NewDefaultValue, PropertyOffset)) { UpdatedInstances.Add(InstancedSceneComponent); } } } else if(UObject* Outer = InSceneComponentTemplate->GetOuter()) { Outer->GetArchetypeInstances(ArchetypeInstances); for(int32 InstanceIndex = 0; InstanceIndex < ArchetypeInstances.Num(); ++InstanceIndex) { USceneComponent* InstancedSceneComponent = static_cast(FindObjectWithOuter(ArchetypeInstances[InstanceIndex], InSceneComponentTemplate->GetClass(), InSceneComponentTemplate->GetFName())); if(InstancedSceneComponent != nullptr && !UpdatedInstances.Contains(InstancedSceneComponent) && ApplyDefaultValueChange(InstancedSceneComponent, InProperty, OldDefaultValue, NewDefaultValue, PropertyOffset)) { UpdatedInstances.Add(InstancedSceneComponent); } } } } // Given an instance of a template and a property, set a default value change to the instance (only if applicable) template static bool ApplyDefaultValueChange(class USceneComponent* InSceneComponent, const class FProperty* InProperty, const T& OldDefaultValue, const T& NewDefaultValue, int32 PropertyOffset) { check(InProperty != nullptr); check(InSceneComponent != nullptr); ensureMsgf(CastField(InProperty) == nullptr, TEXT("ApplyDefaultValueChange cannot be safely called on a bool property with a non-bool value, becuase of bitfields")); T* CurrentValue = PropertyOffset == INDEX_NONE ? InProperty->ContainerPtrToValuePtr(InSceneComponent) : (T*)((uint8*)InSceneComponent + PropertyOffset); check(CurrentValue); return ApplyDefaultValueChange(InSceneComponent, *CurrentValue, OldDefaultValue, NewDefaultValue); } // Bool specialization so it can properly handle bitfields static bool ApplyDefaultValueChange(class USceneComponent* InSceneComponent, const class FProperty* InProperty, const bool& OldDefaultValue, const bool& NewDefaultValue, int32 PropertyOffset) { check(InProperty != nullptr); check(InSceneComponent != nullptr); // Only bool properties can have bool values const FBoolProperty* BoolProperty = CastField(InProperty); check(BoolProperty); uint8* CurrentValue = PropertyOffset == INDEX_NONE ? InProperty->ContainerPtrToValuePtr(InSceneComponent) : ((uint8*)InSceneComponent + PropertyOffset); check(CurrentValue); bool CurrentBool = BoolProperty->GetPropertyValue(CurrentValue); if (ApplyDefaultValueChange(InSceneComponent, CurrentBool, OldDefaultValue, NewDefaultValue, false)) { BoolProperty->SetPropertyValue(CurrentValue, CurrentBool); InSceneComponent->ReregisterComponent(); return true; } return false; } // Given an instance of a template and a current value, propagates a default value change to the instance (only if applicable) template static bool ApplyDefaultValueChange(class USceneComponent* InSceneComponent, T& CurrentValue, const T& OldDefaultValue, const T& NewDefaultValue, bool bReregisterComponent = true) { check(InSceneComponent != nullptr); // Propagate the change only if the current instanced value matches the previous default value (otherwise this could overwrite any per-instance override) if(NewDefaultValue != OldDefaultValue && CurrentValue == OldDefaultValue) { // Ensure that this instance will be included in any undo/redo operations, and record it into the transaction buffer. // Note: We don't do this for components that originate from script, because they will be re-instanced from the template after an undo, so there is no need to record them. if (!InSceneComponent->IsCreatedByConstructionScript()) { InSceneComponent->SetFlags(RF_Transactional); InSceneComponent->Modify(); } // We must also modify the owner, because we'll need script components to be reconstructed as part of an undo operation. AActor* Owner = InSceneComponent->GetOwner(); if(Owner != nullptr) { Owner->Modify(); } // Modify the value CurrentValue = NewDefaultValue; if (bReregisterComponent && InSceneComponent->IsRegistered()) { // Re-register the component with the scene so that transforms are updated for display if (InSceneComponent->AllowReregistration()) { InSceneComponent->ReregisterComponent(); } else { InSceneComponent->UpdateComponentToWorld(); } } return true; } return false; } // Try to find the correct variable name for a given native component template or instance (which can have a mismatch) static UNREALED_API FName FindVariableNameGivenComponentInstance(const UActorComponent* ComponentInstance); /** * Populates the given menu with basic options for operations on components in the world. * @param Menu Used to register the menu options * @param SelectedComponents The selected components to create menu options for */ static UNREALED_API void FillComponentContextMenuOptions(UToolMenu* Menu, const TArray& SelectedComponents); /** * Tries to find a match for ComponentInstance in the ComponentList. First by name and then if multiple Components have a matching name try to match the SceneComponent hierarchy to find the best match. * @param ComponentInstance Component we are trying to match in the ComponentList * @param ComponentList List containing possible matches * @return Valid Component pointer if match was found. nullptr otherwise. */ static UNREALED_API UActorComponent* FindMatchingComponent(const UActorComponent* ComponentInstance, const TInlineComponentArray& ComponentList); /** * Make a FComponentReference from a component pointer. * @param ExpectedComponentOwner The expected component owner. Should be the same as OwningActor from FComponentReference::GetComponent(). * @param Component The component we would like to initialize the FComponentReference with. */ static UNREALED_API FComponentReference MakeComponentReference(const AActor* ExpectedComponentOwner, const UActorComponent* Component); private: static UNREALED_API USceneComponent* FindClosestParentInList(UActorComponent* ChildComponent, const TArray& ComponentList); static UNREALED_API void OnGoToComponentAssetInBrowser(UObject* Asset); static UNREALED_API void OnOpenComponentCodeFile(const FString CodeFileName); static UNREALED_API void OnEditBlueprintComponent(UObject* Blueprint); };