// 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(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 UInteractiveTool::GetToolProperties(bool bEnabledOnly) const { if (bEnabledOnly == false) { return ToolPropertyObjects; } TArray Properties; for (UObject* Object : ToolPropertyObjects) { UInteractiveToolPropertySet* Prop = Cast(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(GetClass()) ) { #if WITH_EDITOR if (!Prop->HasMetaData(TEXT("TransientToolProperty"))) #endif { void* DestValue = Prop->ContainerPtrToValuePtr(this); void* SrcValue = Prop->ContainerPtrToValuePtr(PropertyCache); if ( bSaving ) { Swap(SrcValue, DestValue); } Prop->CopySingleValue(DestValue, SrcValue); } } } TObjectPtr UInteractiveToolPropertySet::GetDynamicPropertyCache(const FString& CacheIdentifier, bool& bWasCreatedOut) { bWasCreatedOut = false; UInteractiveToolPropertySet* CDO = GetMutableDefault(GetClass()); TObjectPtr* Found = CDO->CachedPropertiesMap.Find(CacheIdentifier); if (Found == nullptr) { TObjectPtr NewPropCache = NewObject((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 NewPropCache = NewObject((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(ToRawPtr(Object)); if ( Propset != nullptr ) { if ( Propset->IsPropertySetEnabled() ) { Propset->CheckAndUpdateWatched(); } else { Propset->SilentUpdateWatched(); } } } OnTick(DeltaTime); } UInteractiveToolManager* UInteractiveTool::GetToolManager() const { UInteractiveToolManager* ToolManager = Cast(GetOuter()); check(ToolManager != nullptr); return ToolManager; } #undef LOCTEXT_NAMESPACE