Files
UnrealEngine/Engine/Source/Runtime/InteractiveToolsFramework/Private/InteractiveTool.cpp
2025-05-18 13:04:45 +08:00

339 lines
8.5 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "InteractiveTool.h"
#if WITH_EDITOR
#include "EditorToolDelegates.h"
#endif
#include "InteractiveToolManager.h"
#include "UObject/Class.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(InteractiveTool)
#define LOCTEXT_NAMESPACE "UInteractiveTool"
#if WITH_EDITOR
void UInteractiveToolPropertySet::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
{
Super::PostEditChangeProperty(PropertyChangedEvent);
OnModified.Broadcast(this, PropertyChangedEvent.Property);
}
#endif
UInteractiveTool::UInteractiveTool()
{
// tools need to be transactional or undo/redo won't work on their uproperties
SetFlags(RF_Transactional);
// tools don't get saved but this isn't necessary because they are created in the transient package...
//SetFlags(RF_Transient);
InputBehaviors = NewObject<UInputBehaviorSet>(this, TEXT("InputBehaviors"));
// initialize ToolInfo
#if WITH_EDITORONLY_DATA
DefaultToolInfo.ToolDisplayName = GetClass()->GetDisplayNameText();
#else
DefaultToolInfo.ToolDisplayName = FText(LOCTEXT("DefaultInteractiveToolName", "DefaultToolName"));
#endif
}
void UInteractiveTool::Setup()
{
#if WITH_EDITOR
FEditorToolDelegates::OnEditorToolStarted.Broadcast(GetClass()->GetName());
#endif
}
void UInteractiveTool::Shutdown(EToolShutdownType ShutdownType)
{
InputBehaviors->RemoveAll();
ToolPropertyObjects.Reset();
}
void UInteractiveTool::Render(IToolsContextRenderAPI* RenderAPI)
{
}
void UInteractiveTool::DrawHUD(FCanvas* Canvas, IToolsContextRenderAPI* RenderAPI)
{
}
void UInteractiveTool::AddInputBehavior(UInputBehavior* Behavior, void* Source)
{
InputBehaviors->Add(Behavior, Source);
}
void UInteractiveTool::RemoveInputBehaviorsBySource(void* Source)
{
InputBehaviors->RemoveBySource(Source);
}
const UInputBehaviorSet* UInteractiveTool::GetInputBehaviors() const
{
return InputBehaviors;
}
void UInteractiveTool::AddToolPropertySource(UObject* PropertyObject)
{
check(ToolPropertyObjects.Contains(PropertyObject) == false);
ToolPropertyObjects.Add(PropertyObject);
OnPropertySetsModified.Broadcast();
}
void UInteractiveTool::AddToolPropertySource(UInteractiveToolPropertySet* PropertySet)
{
check(ToolPropertyObjects.Contains(PropertySet) == false);
ToolPropertyObjects.Add(PropertySet);
// @todo do we need to create a lambda every time for this?
PropertySet->GetOnModified().AddLambda([this](UObject* PropertySetArg, FProperty* PropertyArg)
{
OnPropertyModified(PropertySetArg, PropertyArg);
});
OnPropertySetsModified.Broadcast();
}
bool UInteractiveTool::RemoveToolPropertySource(UInteractiveToolPropertySet* PropertySet)
{
int32 NumRemoved = ToolPropertyObjects.Remove(PropertySet);
if (NumRemoved == 0)
{
return false;
}
PropertySet->GetOnModified().Clear();
OnPropertySetsModified.Broadcast();
return true;
}
bool UInteractiveTool::ReplaceToolPropertySource(UInteractiveToolPropertySet* CurPropertySet, UInteractiveToolPropertySet* ReplaceWith, bool bSetToEnabled)
{
int32 Index = ToolPropertyObjects.Find(CurPropertySet);
if (Index == INDEX_NONE)
{
return false;
}
CurPropertySet->GetOnModified().Clear();
ReplaceWith->GetOnModified().AddLambda([this](UObject* PropertySetArg, FProperty* PropertyArg)
{
OnPropertyModified(PropertySetArg, PropertyArg);
});
ToolPropertyObjects[Index] = ReplaceWith;
if (bSetToEnabled)
{
ReplaceWith->bIsPropertySetEnabled = true;
}
OnPropertySetsModified.Broadcast();
return true;
}
bool UInteractiveTool::SetToolPropertySourceEnabled(UInteractiveToolPropertySet* PropertySet, bool bEnabled)
{
int32 Index = ToolPropertyObjects.Find(PropertySet);
if (Index == INDEX_NONE)
{
return false;
}
if (PropertySet->bIsPropertySetEnabled != bEnabled)
{
PropertySet->bIsPropertySetEnabled = bEnabled;
OnPropertySetsModified.Broadcast();
}
return true;
}
void UInteractiveTool::NotifyOfPropertyChangeByTool(UInteractiveToolPropertySet* PropertySet) const
{
OnPropertyModifiedDirectlyByTool.Broadcast(PropertySet);
}
TArray<UObject*> UInteractiveTool::GetToolProperties(bool bEnabledOnly) const
{
if (bEnabledOnly == false)
{
return ToolPropertyObjects;
}
TArray<UObject*> Properties;
for (UObject* Object : ToolPropertyObjects)
{
UInteractiveToolPropertySet* Prop = Cast<UInteractiveToolPropertySet>(Object);
if (Prop == nullptr || Prop->IsPropertySetEnabled())
{
Properties.Add(Object);
}
}
return Properties;
}
void UInteractiveToolPropertySet::SaveProperties(UInteractiveTool* SaveFromTool, const FString& CacheIdentifier)
{
SaveRestoreProperties(SaveFromTool, CacheIdentifier, true);
}
void UInteractiveToolPropertySet::RestoreProperties(UInteractiveTool* RestoreToTool, const FString& CacheIdentifier)
{
SaveRestoreProperties(RestoreToTool, CacheIdentifier, false);
}
void UInteractiveToolPropertySet::SaveRestoreProperties(UInteractiveTool* RestoreToTool, const FString& CacheIdentifier, bool bSaving)
{
bool bWasCreated = false;
UInteractiveToolPropertySet* PropertyCache = GetDynamicPropertyCache(CacheIdentifier, bWasCreated);
if (bWasCreated && !bSaving)
{
// if this is the first time we have seen this property set, then we don't have any values to Restore
return;
}
if (PropertyCache == nullptr)
{
// if for whatever reason a valid PropertyCache could not be returned, just abort
return;
}
for ( FProperty* Prop : TFieldRange<FProperty>(GetClass()) )
{
#if WITH_EDITOR
if (!Prop->HasMetaData(TEXT("TransientToolProperty")))
#endif
{
void* DestValue = Prop->ContainerPtrToValuePtr<void>(this);
void* SrcValue = Prop->ContainerPtrToValuePtr<void>(PropertyCache);
if ( bSaving )
{
Swap(SrcValue, DestValue);
}
Prop->CopySingleValue(DestValue, SrcValue);
}
}
}
TObjectPtr<UInteractiveToolPropertySet> UInteractiveToolPropertySet::GetDynamicPropertyCache(const FString& CacheIdentifier, bool& bWasCreatedOut)
{
bWasCreatedOut = false;
UInteractiveToolPropertySet* CDO = GetMutableDefault<UInteractiveToolPropertySet>(GetClass());
TObjectPtr<UInteractiveToolPropertySet>* Found = CDO->CachedPropertiesMap.Find(CacheIdentifier);
if (Found == nullptr)
{
TObjectPtr<UInteractiveToolPropertySet> NewPropCache = NewObject<UInteractiveToolPropertySet>((UObject*)GetTransientPackage(), GetClass());
ensure(NewPropCache != nullptr);
CDO->CachedPropertiesMap.Add(CacheIdentifier, NewPropCache);
bWasCreatedOut = true;
return NewPropCache;
}
if ( ensure(*Found != nullptr) == false )
{
// this case seems to occur sometimes for Blueprintable Tools, uncertain why, but perhaps this ensure will help to find it
TObjectPtr<UInteractiveToolPropertySet> NewPropCache = NewObject<UInteractiveToolPropertySet>((UObject*)GetTransientPackage(), GetClass());
ensure(NewPropCache != nullptr);
CDO->CachedPropertiesMap[CacheIdentifier] = NewPropCache;
bWasCreatedOut = true;
return NewPropCache;
}
return *Found;
}
void UInteractiveTool::RegisterActions(FInteractiveToolActionSet& ActionSet)
{
}
FInteractiveToolActionSet* UInteractiveTool::GetActionSet()
{
if (ToolActionSet == nullptr)
{
ToolActionSet = new FInteractiveToolActionSet();
RegisterActions(*ToolActionSet);
}
return ToolActionSet;
}
void UInteractiveTool::ExecuteAction(int32 ActionID)
{
GetActionSet()->ExecuteAction(ActionID);
}
bool UInteractiveTool::HasCancel() const
{
return false;
}
bool UInteractiveTool::HasAccept() const
{
return false;
}
bool UInteractiveTool::CanAccept() const
{
return false;
}
void UInteractiveTool::UpdateAcceptWarnings(EAcceptWarning Warning)
{
switch (Warning)
{
case EAcceptWarning::NoWarning:
if (bLastShowedAcceptWarning)
{
// clear warning
GetToolManager()->DisplayMessage(FText(), EToolMessageLevel::UserWarning);
}
break;
case EAcceptWarning::EmptyForbidden:
GetToolManager()->DisplayMessage(LOCTEXT("CannotCreateEmptyMesh", "WARNING: Tool doesn't allow creation of an empty mesh."),
EToolMessageLevel::UserWarning);
break;
default:
check(false);
}
bLastShowedAcceptWarning = Warning != EAcceptWarning::NoWarning;
}
void UInteractiveTool::Tick(float DeltaTime)
{
for (auto& Object : ToolPropertyObjects)
{
auto* Propset = Cast<UInteractiveToolPropertySet>(ToRawPtr(Object));
if ( Propset != nullptr )
{
if ( Propset->IsPropertySetEnabled() )
{
Propset->CheckAndUpdateWatched();
}
else
{
Propset->SilentUpdateWatched();
}
}
}
OnTick(DeltaTime);
}
UInteractiveToolManager* UInteractiveTool::GetToolManager() const
{
UInteractiveToolManager* ToolManager = Cast<UInteractiveToolManager>(GetOuter());
check(ToolManager != nullptr);
return ToolManager;
}
#undef LOCTEXT_NAMESPACE