// Copyright Epic Games, Inc. All Rights Reserved. #include "InteractiveToolManager.h" #include "Engine/Engine.h" #include "InteractiveToolsContext.h" #include "InteractiveToolQueryInterfaces.h" #include "ContextObjectStore.h" #include UE_INLINE_GENERATED_CPP_BY_NAME(InteractiveToolManager) #define LOCTEXT_NAMESPACE "UInteractiveToolManager" UInteractiveToolManager::UInteractiveToolManager() { QueriesAPI = nullptr; TransactionsAPI = nullptr; InputRouter = nullptr; ActiveLeftBuilder = nullptr; ActiveLeftTool = nullptr; ActiveRightBuilder = nullptr; ActiveRightTool = nullptr; ActiveToolChangeTrackingMode = EToolChangeTrackingMode::UndoToExit; } void UInteractiveToolManager::Initialize(IToolsContextQueriesAPI* queriesAPI, IToolsContextTransactionsAPI* transactionsAPI, UInputRouter* InputRouterIn) { this->QueriesAPI = queriesAPI; this->TransactionsAPI = transactionsAPI; this->InputRouter = InputRouterIn; bIsActive = true; } void UInteractiveToolManager::Shutdown() { this->QueriesAPI = nullptr; if (ActiveLeftTool != nullptr) { DeactivateTool(EToolSide::Left, EToolShutdownType::Cancel); } if (ActiveRightTool != nullptr) { DeactivateTool(EToolSide::Right, EToolShutdownType::Cancel); } this->TransactionsAPI = nullptr; bIsActive = false; } void UInteractiveToolManager::DoPostBuild(EToolSide Side, UInteractiveTool* InBuiltTool, UInteractiveToolBuilder* InToolBuilder, const FToolBuilderState& InBuilderState) { InToolBuilder->PostBuildTool(InBuiltTool, InBuilderState); OnToolPostBuild.Broadcast(this, Side, InBuiltTool, InToolBuilder, InBuilderState); } void UInteractiveToolManager::DoPostSetup(EToolSide Side, UInteractiveTool* InInteractiveTool, UInteractiveToolBuilder* InToolBuilder, const FToolBuilderState& InBuilderState) { InToolBuilder->PostSetupTool(InInteractiveTool, InBuilderState); OnToolPostSetup.Broadcast(this, Side, InInteractiveTool); } void UInteractiveToolManager::RegisterToolType(const FString& Identifier, UInteractiveToolBuilder* Builder) { if (ensure(ToolBuilders.Contains(Identifier) == false)) { ToolBuilders.Add(Identifier, Builder); } } void UInteractiveToolManager::UnregisterToolType(const FString& Identifier) { if (ActiveLeftToolName == Identifier) { DeactivateTool(EToolSide::Left, EToolShutdownType::Cancel); } if (ActiveRightToolName == Identifier) { DeactivateTool(EToolSide::Right, EToolShutdownType::Cancel); } ToolBuilders.Remove(Identifier); } bool UInteractiveToolManager::SelectActiveToolType(EToolSide Side, const FString& Identifier) { if (ToolBuilders.Contains(Identifier)) { UInteractiveToolBuilder* Builder = ToolBuilders[Identifier]; if (Side == EToolSide::Right) { ActiveRightBuilder = Builder; ActiveRightBuilderName = Identifier; } else { ActiveLeftBuilder = Builder; ActiveLeftBuilderName = Identifier; } return true; } return false; } const FString& UInteractiveToolManager::GetActiveToolType(EToolSide Side) const { return Side == EToolSide::Right ? ActiveRightBuilderName : ActiveLeftBuilderName; } bool UInteractiveToolManager::CanActivateTool(EToolSide Side, FString Identifier) { if (ensure(Side == EToolSide::Left) == false) // TODO: support right-side tool { return false; } if (ToolBuilders.Contains(Identifier)) { FToolBuilderState InputState; QueriesAPI->GetCurrentSelectionState(InputState); UInteractiveToolBuilder* Builder = ToolBuilders[Identifier]; return Builder->CanBuildTool(InputState); } return false; } bool UInteractiveToolManager::ActivateTool(EToolSide Side) { if (ensure(Side == EToolSide::Left) == false) // TODO: support right-side tool { return false; } // wrap tool change in a transaction so that deactivate and activate are grouped bool bInTransaction = false; if (ActiveToolChangeTrackingMode == EToolChangeTrackingMode::FullUndoRedo) { BeginUndoTransaction(LOCTEXT("ToolChange", "Change Tool")); bInTransaction = true; } if (ActiveLeftTool != nullptr) { // Decide how to shut down the currently active tool based on ToolSwitchMode EToolShutdownType ShutdownType = EToolShutdownType::Completed; switch (ToolSwitchMode) { case EToolManagerToolSwitchMode::AcceptIfAble: case EToolManagerToolSwitchMode::CustomizableAcceptIfAble: ShutdownType = CanAcceptActiveTool(EToolSide::Left) ? EToolShutdownType::Accept : CanCancelActiveTool(EToolSide::Left) ? EToolShutdownType::Cancel : EToolShutdownType::Completed; break; case EToolManagerToolSwitchMode::CancelIfAble: case EToolManagerToolSwitchMode::CustomizableCancelIfAble: ShutdownType = CanCancelActiveTool(EToolSide::Left) ? EToolShutdownType::Cancel : EToolShutdownType::Completed; break; default: ensure(false); } // If the tool switch mode is set to be customizable, see if the tool wants to customize it if (ToolSwitchMode == EToolManagerToolSwitchMode::CustomizableAcceptIfAble || ToolSwitchMode == EToolManagerToolSwitchMode::CustomizableCancelIfAble) { if (IInteractiveToolShutdownQueryAPI* ShutdownQueryAPI = Cast(ActiveLeftTool)) { ShutdownType = ShutdownQueryAPI->GetPreferredShutdownType( IInteractiveToolShutdownQueryAPI::EShutdownReason::SwitchTool, ShutdownType); } } DeactivateTool(EToolSide::Left, ShutdownType); } if (ActiveLeftBuilder == nullptr || ActivateToolInternal(Side) == false) { if (bInTransaction) { EndUndoTransaction(); } return false; } if (ActiveToolChangeTrackingMode == EToolChangeTrackingMode::FullUndoRedo) { if (ensure(TransactionsAPI)) { TransactionsAPI->AppendChange(this, MakeUnique(Side, ActiveLeftToolName), LOCTEXT("ActivateToolChange", "Activate Tool")); } } else if (ActiveToolChangeTrackingMode == EToolChangeTrackingMode::UndoToExit) { EmitObjectChange(this, MakeUnique(), LOCTEXT("ActivateToolChange", "Activate Tool")); } if (bInTransaction) { EndUndoTransaction(); } return true; } bool UInteractiveToolManager::ActivateToolInternal(EToolSide Side) { // construct input state we will pass to tools FToolBuilderState InputState; QueriesAPI->GetCurrentSelectionState(InputState); if (ActiveLeftBuilder->CanBuildTool(InputState) == false) { TransactionsAPI->DisplayMessage(LOCTEXT("ActivateToolCanBuildFailMessage", "UInteractiveToolManager::ActivateTool: CanBuildTool returned false."), EToolMessageLevel::Internal); return false; } ActiveLeftTool = ActiveLeftBuilder->BuildTool(InputState); if (ActiveLeftTool == nullptr) { return false; } ActiveLeftToolName = ActiveLeftBuilderName; DoPostBuild(Side, ActiveLeftTool, ActiveLeftBuilder, InputState); bInToolSetup = true; ActiveLeftTool->Setup(); bInToolSetup = false; if (bToolRequestedTerminationDuringSetup) { bToolRequestedTerminationDuringSetup = false; ActiveLeftTool->Shutdown(EToolShutdownType::Cancel); // need to give Tool a chance to clean up... ActiveLeftTool = nullptr; ActiveLeftToolName.Empty(); return false; } DoPostSetup(Side, ActiveLeftTool, ActiveLeftBuilder, InputState); // register new active input behaviors InputRouter->RegisterSource(ActiveLeftTool); PostInvalidation(); OnToolStarted.Broadcast(this, ActiveLeftTool); return true; } void UInteractiveToolManager::DeactivateTool(EToolSide Side, EToolShutdownType ShutdownType) { if (ensure(Side == EToolSide::Left) == false) // TODO: support right-side tool { return; } if (bInToolShutdown) { return; } if (ActiveLeftTool != nullptr) { if (ActiveToolChangeTrackingMode == EToolChangeTrackingMode::FullUndoRedo) { if (ensure(TransactionsAPI)) { TransactionsAPI->AppendChange(this, MakeUnique(Side, ActiveLeftToolName, ShutdownType), LOCTEXT("DeactivateToolChange", "Deactivate Tool")); } } DeactivateToolInternal(Side, ShutdownType); } } void UInteractiveToolManager::DeactivateToolInternal(EToolSide Side, EToolShutdownType ShutdownType) { if (Side == EToolSide::Left) { if (!ensure(ActiveLeftTool)) { return; } bInToolShutdown = true; InputRouter->ForceTerminateSource(ActiveLeftTool); ActiveLeftTool->Shutdown(ShutdownType); InputRouter->DeregisterSource(ActiveLeftTool); UInteractiveTool* DoneTool = ActiveLeftTool; ActiveLeftTool = nullptr; ActiveLeftToolName.Empty(); PostInvalidation(); OnToolEndedWithStatus.Broadcast(this, DoneTool, ShutdownType); OnToolEnded.Broadcast(this, DoneTool); bInToolShutdown = false; } } bool UInteractiveToolManager::HasActiveTool(EToolSide Side) const { return (Side == EToolSide::Left) ? (ActiveLeftTool != nullptr) : (ActiveRightTool != nullptr); } bool UInteractiveToolManager::HasAnyActiveTool() const { return ActiveLeftTool != nullptr || ActiveRightTool != nullptr; } UInteractiveTool* UInteractiveToolManager::GetActiveTool(EToolSide Side) { return (Side == EToolSide::Left) ? ActiveLeftTool : ActiveRightTool; } UInteractiveToolBuilder* UInteractiveToolManager::GetActiveToolBuilder(EToolSide Side) { return (Side == EToolSide::Left) ? ActiveLeftBuilder : ActiveRightBuilder; } FString UInteractiveToolManager::GetActiveToolName(EToolSide Side) { if (GetActiveTool(Side) == nullptr) { return FString(); } return (Side == EToolSide::Left) ? ActiveLeftToolName : ActiveRightToolName; } bool UInteractiveToolManager::CanAcceptActiveTool(EToolSide Side) { if (ActiveLeftTool != nullptr) { return ActiveLeftTool->HasAccept() && ActiveLeftTool->CanAccept(); } return false; } bool UInteractiveToolManager::CanCancelActiveTool(EToolSide Side) { if (ActiveLeftTool != nullptr) { return ActiveLeftTool->HasCancel(); } return false; } void UInteractiveToolManager::ConfigureChangeTrackingMode(EToolChangeTrackingMode ChangeMode) { ActiveToolChangeTrackingMode = ChangeMode; } void UInteractiveToolManager::Tick(float DeltaTime) { if (ActiveLeftTool != nullptr) { ActiveLeftTool->Tick(DeltaTime); } if (ActiveRightTool!= nullptr) { ActiveRightTool->Tick(DeltaTime); } } void UInteractiveToolManager::Render(IToolsContextRenderAPI* RenderAPI) { if (ActiveLeftTool != nullptr) { ActiveLeftTool->Render(RenderAPI); } if (ActiveRightTool != nullptr) { ActiveRightTool->Render(RenderAPI); } } void UInteractiveToolManager::DrawHUD(FCanvas* Canvas, IToolsContextRenderAPI* RenderAPI) { if (ActiveLeftTool != nullptr) { ActiveLeftTool->DrawHUD(Canvas, RenderAPI); } if (ActiveRightTool != nullptr) { ActiveRightTool->DrawHUD(Canvas, RenderAPI); } } UInteractiveGizmoManager* UInteractiveToolManager::GetPairedGizmoManager() { return Cast(GetOuter())->GizmoManager; } UContextObjectStore* UInteractiveToolManager::GetContextObjectStore() const { return Cast(GetOuter())->ContextObjectStore; } void UInteractiveToolManager::DisplayMessage(const FText& Message, EToolMessageLevel Level) { TransactionsAPI->DisplayMessage(Message, Level); } void UInteractiveToolManager::PostInvalidation() { TransactionsAPI->PostInvalidation(); } void UInteractiveToolManager::BeginUndoTransaction(const FText& Description) { TransactionsAPI->BeginUndoTransaction(Description); } void UInteractiveToolManager::EndUndoTransaction() { TransactionsAPI->EndUndoTransaction(); } void UInteractiveToolManager::EmitObjectChange(UObject* TargetObject, TUniquePtr Change, const FText& Description) { // Currently we do not support undo/redo of changes emitted /during/ a Tool after the Tool exits, // because we do not support "undo back into a Tool". So once a Tool exits, any Changes it emitted are considered invalid. // We do not assume we can modify the external history/transaction system (because in the Editor we cannot) // so all we can do is mark those changes as "expired" and hope that the external transaction system respects that. // The Tools themselves will not be around to know whether they have exited, so we wrap // the changes they emit in a FToolChangeWrapperChange which will allow the ToolManager // to decide whether the change is still valid/applicable. // Note: if you do not want this wrapping behavior for particular changes, you can use the TransactionsAPI directly // via GetContextTransactionsAPI() // If you hit this ensure from a Gizmo, you may have accidentally registered the ToolManager as the Gizmo's TransactionProvider, // this should only be done if the Gizmo is embedded inside a Tool if (ensure(HasActiveTool(EToolSide::Left))) { TUniquePtr Wrapper = MakeUnique(); Wrapper->ToolManager = this; Wrapper->ActiveTool = GetActiveTool(EToolSide::Left); Wrapper->ToolChange = MoveTemp(Change); TransactionsAPI->AppendChange(TargetObject, MoveTemp(Wrapper), Description); } } bool UInteractiveToolManager::RequestSelectionChange(const FSelectedObjectsChangeList& SelectionChange) { return TransactionsAPI->RequestSelectionChange(SelectionChange); } bool UInteractiveToolManager::PostActiveToolShutdownRequest(UInteractiveTool* Tool, EToolShutdownType ShutdownType, bool bShowUnexpectedShutdownMessage, const FText& UnexpectedShutdownMessage) { if (bShowUnexpectedShutdownMessage) { if (OnToolUnexpectedShutdownMessage.IsBound() == false ) { if ( UnexpectedShutdownMessage.IsEmpty() ) { FString ToolName = (Tool != nullptr) ? Tool->GetToolInfo().ToolDisplayName.ToString() : FString(TEXT("(Null Tool)")); if ( bInToolSetup ) { UE_LOG(LogTemp, Error, TEXT("[InteractiveToolManager] Tool %s Could not be Initialized"), *ToolName); } else { UE_LOG(LogTemp, Error, TEXT("[InteractiveToolManager] Tool %s was Shut Down Automatically, no message provided"), *ToolName); } } else { UE_LOG(LogTemp, Error, TEXT("[InteractiveToolManager] %s"), *UnexpectedShutdownMessage.ToString()); } } else { OnToolUnexpectedShutdownMessage.Broadcast(this, Tool, UnexpectedShutdownMessage, bInToolSetup); } } // if Tool calls this function from it's Setup() function, then we need to do some special-case handling because // the code below can't be run yet if (bInToolSetup) { bToolRequestedTerminationDuringSetup = true; return true; } bool bIsActiveTool = (Tool != nullptr) && (ActiveLeftTool == Tool || ActiveRightTool == Tool); if (!bIsActiveTool) { return false; } bool bHandled = false; if (OnToolShutdownRequest.IsBound()) { bHandled = OnToolShutdownRequest.Execute(this, Tool, ShutdownType); } if (!bHandled) { EToolSide WhichSide = (ActiveLeftTool == Tool) ? EToolSide::Left : EToolSide::Right; DeactivateTool(WhichSide, ShutdownType); } return true; } void FBeginToolChange::Apply(UObject* Object) { // do nothing on apply, we do not want to re-enter the tool } void FBeginToolChange::Revert(UObject* Object) { // On revert, if a tool is active, we cancel it. // Note that this should only happen once, because any further tool activations // would be pushing their own FBeginToolChange UInteractiveToolManager* ToolManager = CastChecked(Object); if (ToolManager->HasAnyActiveTool()) { ToolManager->DeactivateToolInternal(EToolSide::Left, EToolShutdownType::Cancel); } } bool FBeginToolChange::HasExpired( UObject* Object ) const { UInteractiveToolManager* ToolManager = CastChecked(Object); return (ToolManager == nullptr) || (ToolManager->IsActive() == false) || (ToolManager->HasAnyActiveTool() == false); } FString FBeginToolChange::ToString() const { return FString(TEXT("Begin Tool")); } void FActivateToolChange::Apply(UObject* Object) { UInteractiveToolManager* ToolManager = CastChecked(Object); if (ToolManager) { if (bIsDeactivate) { ToolManager->DeactivateToolInternal(Side, ShutdownType); } else { ToolManager->SelectActiveToolType(Side, ToolType); ToolManager->ActivateToolInternal(Side); } } } void FActivateToolChange::Revert(UObject* Object) { UInteractiveToolManager* ToolManager = CastChecked(Object); if (ToolManager) { if (bIsDeactivate) { ToolManager->SelectActiveToolType(Side, ToolType); ToolManager->ActivateToolInternal(Side); } else { ToolManager->DeactivateToolInternal(Side, ShutdownType); } } } bool FActivateToolChange::HasExpired(UObject* Object) const { UInteractiveToolManager* ToolManager = CastChecked(Object); return (ToolManager == nullptr) || (ToolManager->IsActive() == false); } FString FActivateToolChange::ToString() const { return FString(TEXT("Change Tool")); } void FToolChangeWrapperChange::Apply(UObject* Object) { if (ToolChange.IsValid()) { ToolChange->Apply(Object); } } void FToolChangeWrapperChange::Revert(UObject* Object) { if (ToolChange.IsValid()) { ToolChange->Revert(Object); } } bool FToolChangeWrapperChange::HasExpired(UObject* Object) const { if (ToolChange.IsValid() && ToolManager.IsValid() && ActiveTool.IsValid()) { if (ToolChange->HasExpired(Object)) { return true; } if (ToolManager->GetActiveTool(EToolSide::Left) == ActiveTool.Get()) { return false; } } return true; } FString FToolChangeWrapperChange::ToString() const { if (ToolChange.IsValid()) { return ToolChange->ToString(); } return FString(); } #undef LOCTEXT_NAMESPACE