Files
UnrealEngine/Engine/Source/Editor/UnrealEd/Private/Tools/EdModeInteractiveToolsContext.cpp
2025-05-18 13:04:45 +08:00

1411 lines
46 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "Tools/EdModeInteractiveToolsContext.h"
#include "BaseGizmos/GizmoViewContext.h"
#include "ContextObjectStore.h"
#include "Editor.h"
#include "EditorViewportClient.h"
#include "EditorModeManager.h"
#include "Framework/Application/SlateApplication.h"
#include "LevelEditor.h"
#include "LevelEditorViewport.h"
#include "IAssetViewport.h"
#include "Math/Rotator.h"
#include "Misc/AssertionMacros.h"
#include "SLevelViewport.h"
#include "Modules/ModuleManager.h"
#include "ShowFlags.h" // for EngineShowFlags
#include "Engine/Engine.h"
#include "Engine/Selection.h"
#include "Engine/World.h"
#include "Misc/ITransaction.h"
#include "ScopedTransaction.h"
#include "Materials/Material.h"
#include "Engine/StaticMesh.h"
#include "Components/StaticMeshComponent.h"
#include "ToolContextInterfaces.h"
#include "InteractiveToolObjects.h"
#include "EditorInteractiveGizmoManager.h"
#include "BaseBehaviors/ClickDragBehavior.h"
#include "EditorModeManager.h"
#include "EdMode.h"
#include "EditorDragTools.h"
#include "BaseGizmos/GizmoRenderingUtil.h"
#include "UnrealClient.h"
#include "EditorDragTools/DragToolsBehaviourSource.h"
#include "EditorDragTools/EditorDragToolBehaviorTarget.h"
#include "UObject/ObjectSaveContext.h"
//#define ENABLE_DEBUG_PRINTING
DEFINE_LOG_CATEGORY_STATIC(LogInteractiveToolsContext, Log, All);
class FEdModeToolsContextQueriesImpl : public IToolsContextQueriesAPI
{
public:
UEditorInteractiveToolsContext* ToolsContext;
FEditorModeTools* EditorModeManager;
FViewCameraState CachedViewState;
FEditorViewportClient* CachedViewportClient;
FEdModeToolsContextQueriesImpl(UEditorInteractiveToolsContext* Context, FEditorModeTools* InEditorModeManager)
{
ToolsContext = Context;
EditorModeManager = InEditorModeManager;
}
void CacheCurrentViewState(FEditorViewportClient* ViewportClient)
{
CachedViewportClient = ViewportClient;
FViewportCameraTransform ViewTransform = ViewportClient->GetViewTransform();
CachedViewState.bIsOrthographic = ViewportClient->IsOrtho();
CachedViewState.Position = ViewTransform.GetLocation();
CachedViewState.HorizontalFOVDegrees = ViewportClient->ViewFOV;
CachedViewState.AspectRatio = ViewportClient->AspectRatio;
// ViewTransform rotation is only initialized for perspective!
if (CachedViewState.bIsOrthographic == false)
{
// if using Orbit camera, the rotation in the ViewTransform is not the current camera rotation, it
// is set to a different rotation based on the Orbit. So we have to convert back to camera rotation.
FRotator ViewRotation = (ViewportClient->bUsingOrbitCamera) ?
ViewTransform.ComputeOrbitMatrix().InverseFast().Rotator() : ViewTransform.GetRotation();
CachedViewState.Orientation = ViewRotation.Quaternion();
}
else
{
// These rotations are based on hardcoded values in EditorViewportClient.cpp, see switches in FEditorViewportClient::CalcSceneView and FEditorViewportClient::Draw
switch (ViewportClient->ViewportType)
{
case LVT_OrthoXY:
CachedViewState.Orientation = FQuat(FRotator(-90.0f, -90.0f, 0.0f));
break;
case LVT_OrthoNegativeXY:
CachedViewState.Orientation = FQuat(FRotator(90.0f, 90.0f, 0.0f));
break;
case LVT_OrthoXZ:
CachedViewState.Orientation = FQuat(FRotator(0.0f, -90.0f, 0.0f));
break;
case LVT_OrthoNegativeXZ:
CachedViewState.Orientation = FQuat(FRotator(0.0f, 90.0f, 0.0f));
break;
case LVT_OrthoYZ:
CachedViewState.Orientation = FQuat(FRotator(0.0f, 0.0f, 0.0f));
break;
case LVT_OrthoNegativeYZ:
CachedViewState.Orientation = FQuat(FRotator(0.0f, 180.0f, 0.0f));
break;
default:
CachedViewState.Orientation = FQuat::Identity;
}
CachedViewState.OrthoWorldCoordinateWidth = ViewportClient->GetOrthoUnitsPerPixel(ViewportClient->Viewport) * static_cast<float>(ViewportClient->Viewport->GetSizeXY().X);
}
CachedViewState.bIsVR = false;
}
virtual UWorld* GetCurrentEditingWorld() const override
{
return EditorModeManager->GetWorld();
}
virtual void GetCurrentSelectionState(FToolBuilderState& StateOut) const override
{
StateOut.ToolManager = ToolsContext->ToolManager;
StateOut.TargetManager = ToolsContext->TargetManager;
StateOut.GizmoManager = ToolsContext->GizmoManager;
StateOut.World = EditorModeManager->GetWorld();
EditorModeManager->GetSelectedActors()->GetSelectedObjects(StateOut.SelectedActors);
EditorModeManager->GetSelectedComponents()->GetSelectedObjects(StateOut.SelectedComponents);
PRAGMA_DISABLE_DEPRECATION_WARNINGS
StateOut.TypedElementSelectionSet = EditorModeManager->GetEditorSelectionSet();
PRAGMA_ENABLE_DEPRECATION_WARNINGS
}
virtual void GetCurrentViewState(FViewCameraState& StateOut) const override
{
StateOut = CachedViewState;
}
virtual EToolContextCoordinateSystem GetCurrentCoordinateSystem() const override
{
ECoordSystem CoordSys = EditorModeManager->GetCoordSystem();
return (CoordSys == COORD_World) ? EToolContextCoordinateSystem::World : EToolContextCoordinateSystem::Local;
}
virtual EToolContextTransformGizmoMode GetCurrentTransformGizmoMode() const override
{
if (ToolsContext->GetForceCombinedGizmoModeEnabled() == false)
{
UE::Widget::EWidgetMode WidgetMode = EditorModeManager->GetWidgetMode();
switch (WidgetMode)
{
case UE::Widget::EWidgetMode::WM_None:
return EToolContextTransformGizmoMode::NoGizmo;
case UE::Widget::EWidgetMode::WM_Translate:
return EToolContextTransformGizmoMode::Translation;
case UE::Widget::EWidgetMode::WM_Rotate:
return EToolContextTransformGizmoMode::Rotation;
case UE::Widget::EWidgetMode::WM_Scale:
return EToolContextTransformGizmoMode::Scale;
}
}
return EToolContextTransformGizmoMode::Combined;
}
virtual FToolContextSnappingConfiguration GetCurrentSnappingSettings() const override
{
FToolContextSnappingConfiguration Config;
Config.bEnablePositionGridSnapping = (GetDefault<ULevelEditorViewportSettings>()->GridEnabled != 0);
float EditorGridSize = GEditor->GetGridSize();
Config.PositionGridDimensions = FVector(EditorGridSize, EditorGridSize, EditorGridSize);
Config.bEnableRotationGridSnapping = (GetDefault<ULevelEditorViewportSettings>()->RotGridEnabled != 0);
Config.RotationGridAngles = GEditor->GetRotGridSize();
Config.bEnableScaleGridSnapping = (GetDefault<ULevelEditorViewportSettings>()->SnapScaleEnabled != 0);
Config.ScaleGridSize = GEditor->GetScaleGridSize();
Config.bEnableAbsoluteWorldSnapping = ToolsContext->GetAbsoluteWorldSnappingEnabled();
return Config;
}
virtual UMaterialInterface* GetStandardMaterial(EStandardToolContextMaterials MaterialType) const
{
if (MaterialType == EStandardToolContextMaterials::VertexColorMaterial)
{
return ToolsContext->StandardVertexColorMaterial;
}
check(false);
return nullptr;
}
virtual FViewport* GetHoveredViewport() const override
{
if (FEditorViewportClient* HoveredClient = EditorModeManager->GetHoveredViewportClient())
{
return HoveredClient->Viewport;
}
return nullptr;
}
virtual FViewport* GetFocusedViewport() const override
{
if (FEditorViewportClient* FocusedClient = EditorModeManager->GetFocusedViewportClient())
{
return FocusedClient->Viewport;
}
return nullptr;
}
};
class FEdModeToolsContextTransactionImpl : public IToolsContextTransactionsAPI
{
public:
FEdModeToolsContextTransactionImpl(UEditorInteractiveToolsContext* Context, FEditorModeTools* InEditorModeManager)
: ToolsContext(Context)
, EditorModeManager(InEditorModeManager)
{
check(EditorModeManager);
check(ToolsContext);
}
virtual void DisplayMessage(const FText& Message, EToolMessageLevel Level) override
{
if (Level == EToolMessageLevel::UserNotification || Level == EToolMessageLevel::UserMessage)
{
UE_LOG(LogInteractiveToolsContext, Display, TEXT("%s"), *Message.ToString());
ToolsContext->PostToolNotificationMessage(Message);
}
else if (Level == EToolMessageLevel::UserWarning || Level == EToolMessageLevel::UserError)
{
UE_LOG(LogInteractiveToolsContext, Warning, TEXT("%s"), *Message.ToString());
ToolsContext->PostToolWarningMessage(Message);
}
else
{
UE_LOG(LogInteractiveToolsContext, Warning, TEXT("%s"), *Message.ToString());
}
}
virtual void PostInvalidation() override
{
ToolsContext->PostInvalidation();
}
virtual void BeginUndoTransaction(const FText& Description) override
{
GEditor->BeginTransaction(Description);
}
virtual void EndUndoTransaction() override
{
GEditor->EndTransaction();
}
virtual void AppendChange(UObject* TargetObject, TUniquePtr<FToolCommandChange> Change, const FText& Description) override
{
FScopedTransaction Transaction(Description);
//if (ensure(GUndo != nullptr)) // ideally we would ensure here, but currently this can be hit on world teardown, resolution TBD
if (GUndo != nullptr)
{
GUndo->StoreUndo(TargetObject, MoveTemp(Change));
}
}
virtual bool RequestSelectionChange(const FSelectedObjectsChangeList& SelectionChange) override
{
checkf(SelectionChange.Components.Num() == 0, TEXT("FEdModeToolsContextTransactionImpl::RequestSelectionChange - Component selection not supported yet"));
if (SelectionChange.ModificationType == ESelectedObjectsModificationType::Clear)
{
GEditor->SelectNone(true, true, false);
return true;
}
if (SelectionChange.ModificationType == ESelectedObjectsModificationType::Replace )
{
GEditor->SelectNone(false, true, false);
}
bool bAdd = (SelectionChange.ModificationType != ESelectedObjectsModificationType::Remove);
int NumActors = SelectionChange.Actors.Num();
for (int k = 0; k < NumActors; ++k)
{
// Calling GEditor->NoteSelectionChange(true) will not send out change notifications on the TypedElementSelectionSet.
// The selection change will work but any Editor stuff listening for changes will not be notified.
// This may be a bug, needs further investigation.
// In the meantime, just send out the notification on the last SelectActor() call
bool bNotify = (k == NumActors-1);
GEditor->SelectActor(SelectionChange.Actors[k], bAdd, bNotify, true, false);
}
GEditor->NoteSelectionChange(true);
return true;
}
protected:
UEditorInteractiveToolsContext* ToolsContext;
FEditorModeTools* EditorModeManager;
};
UEditorInteractiveToolsContext::UEditorInteractiveToolsContext()
{
QueriesAPI = nullptr;
TransactionAPI = nullptr;
}
void UEditorInteractiveToolsContext::Initialize(IToolsContextQueriesAPI* QueriesAPIIn, IToolsContextTransactionsAPI* TransactionsAPIIn)
{
UInteractiveToolsContext::Initialize(QueriesAPIIn, TransactionsAPIIn);
InvalidationTimestamp = 0;
// This gets set up in UInteractiveToolsContext::Initialize;
GizmoViewContext = ToolManager->GetContextObjectStore()->FindContext<UGizmoViewContext>();
UToolsContextCursorAPI* ToolsContextCursorAPI = ToolManager->GetContextObjectStore()->FindContext<UToolsContextCursorAPI>();
if (!ToolsContextCursorAPI)
{
ToolsContextCursorAPI = NewObject< UToolsContextCursorAPI >();
ToolManager->GetContextObjectStore()->AddContextObject(ToolsContextCursorAPI);
}
}
void UEditorInteractiveToolsContext::Shutdown()
{
bIsActive = false;
// auto-accept any in-progress tools
DeactivateAllActiveTools(EToolShutdownType::Accept);
UInteractiveToolsContext::Shutdown();
}
void UEditorInteractiveToolsContext::InitializeContextWithEditorModeManager(FEditorModeTools* InEditorModeManager, UInputRouter* UseInputRouter)
{
check(InEditorModeManager);
EditorModeManager = InEditorModeManager;
this->TransactionAPI = new FEdModeToolsContextTransactionImpl(this, InEditorModeManager);
this->QueriesAPI = new FEdModeToolsContextQueriesImpl(this, InEditorModeManager);
SetCreateGizmoManagerFunc([this](const FContextInitInfo& ContextInfo)
{
UEditorInteractiveGizmoManager* NewGizmoManager = NewObject<UEditorInteractiveGizmoManager>(ContextInfo.ToolsContext);
NewGizmoManager->InitializeWithEditorModeManager(ContextInfo.QueriesAPI, ContextInfo.TransactionsAPI, ContextInfo.InputRouter, EditorModeManager);
NewGizmoManager->RegisterDefaultGizmos();
return NewGizmoManager;
});
if (UseInputRouter != nullptr)
{
SetCreateInputRouterFunc([this, UseInputRouter](const FContextInitInfo& ContextInfo)
{
return UseInputRouter;
});
SetShutdownInputRouterFunc([this](UInputRouter*) {});
}
Initialize(QueriesAPI, TransactionAPI);
if (UseInputRouter != nullptr)
{
// enable auto invalidation in Editor, because invalidating for all hover and capture events is unpleasant
this->InputRouter->bAutoInvalidateOnHover = true;
this->InputRouter->bAutoInvalidateOnCapture = true;
}
// set up standard materials
StandardVertexColorMaterial = GEngine->VertexColorMaterial;
}
void UEditorInteractiveToolsContext::ShutdownContext()
{
Shutdown();
OnToolNotificationMessage.Clear();
OnToolWarningMessage.Clear();
if (QueriesAPI != nullptr)
{
delete QueriesAPI;
QueriesAPI = nullptr;
}
if (TransactionAPI != nullptr)
{
delete TransactionAPI;
TransactionAPI = nullptr;
}
}
void UEditorInteractiveToolsContext::TerminateActiveToolsOnPIEStart()
{
if (bDeactivateOnPIEStart)
{
DeactivateAllActiveTools(EToolShutdownType::Accept);
}
}
void UEditorInteractiveToolsContext::TerminateActiveToolsOnSaveWorld()
{
if (bDeactivateOnSaveWorld)
{
DeactivateAllActiveTools(EToolShutdownType::Accept);
}
}
void UEditorInteractiveToolsContext::TerminateActiveToolsOnWorldTearDown()
{
DeactivateAllActiveTools(EToolShutdownType::Cancel);
}
void UEditorInteractiveToolsContext::TerminateActiveToolsOnLevelChange()
{
DeactivateAllActiveTools(EToolShutdownType::Cancel);
}
void UEditorInteractiveToolsContext::PostInvalidation()
{
InvalidationTimestamp++;
}
UWorld* UEditorInteractiveToolsContext::GetWorld() const
{
if (bIsActive && EditorModeManager)
{
return EditorModeManager->GetWorld();
}
return nullptr;
}
void UEditorInteractiveToolsContext::Tick(FEditorViewportClient* ViewportClient, float DeltaTime)
{
// invalidate this viewport if it's timestamp is not current
const int32* FoundTimestamp = InvalidationMap.Find(ViewportClient);
if (ViewportClient && FoundTimestamp == nullptr)
{
ViewportClient->Invalidate(false, false);
InvalidationMap.Add(ViewportClient, InvalidationTimestamp);
}
if (ViewportClient && FoundTimestamp != nullptr && *FoundTimestamp < InvalidationTimestamp)
{
ViewportClient->Invalidate(false, false);
InvalidationMap[ViewportClient] = InvalidationTimestamp;
}
if ( PendingToolShutdownType )
{
UInteractiveToolsContext::EndTool(EToolSide::Mouse, *PendingToolShutdownType);
PendingToolShutdownType.Reset();
}
if ( PendingToolToStart )
{
if (UInteractiveToolsContext::StartTool(EToolSide::Mouse, *PendingToolToStart))
{
SetEditorStateForTool();
}
PendingToolToStart.Reset();
}
// This Tick() is called for every ViewportClient, however we only want to Tick the ToolManager and GizmoManager
// once, for the 'Active'/Focused Viewport, so early-out here
if (ViewportClient != EditorModeManager->GetFocusedViewportClient())
{
return;
}
// Cache current camera state from this Viewport in the ContextQueries, which we will use for things like snapping/etc that
// is computed by the Tool and Gizmo Tick()s
// (This is not necessarily correct for Hover, because we might be Hovering over a different Viewport than the Active one...)
if (ViewportClient)
{
((FEdModeToolsContextQueriesImpl*)this->QueriesAPI)->CacheCurrentViewState(ViewportClient);
}
// tick our stuff
ToolManager->Tick(DeltaTime);
GizmoManager->Tick(DeltaTime);
OnTick.Broadcast(DeltaTime);
}
class FEdModeTempRenderContext : public IToolsContextRenderAPI
{
public:
FPrimitiveDrawInterface* PDI;
const FSceneView* SceneView;
FViewCameraState ViewCameraState;
EViewInteractionState ViewInteractionState;
FEdModeTempRenderContext(const FSceneView* View, FViewport* Viewport, FEditorViewportClient* ViewportClient, FPrimitiveDrawInterface* DrawInterface, EViewInteractionState ViewInteractionState)
:PDI(DrawInterface), SceneView(View), ViewInteractionState(ViewInteractionState)
{
CacheCurrentViewState(Viewport, ViewportClient);
}
virtual FPrimitiveDrawInterface* GetPrimitiveDrawInterface() override
{
return PDI;
}
virtual const FSceneView* GetSceneView() override
{
return SceneView;
}
virtual FViewCameraState GetCameraState() override
{
return ViewCameraState;
}
virtual EViewInteractionState GetViewInteractionState() override
{
return ViewInteractionState;
}
void CacheCurrentViewState(FViewport* Viewport, FEditorViewportClient* ViewportClient)
{
if (!ViewportClient)
{
return;
}
FViewportCameraTransform ViewTransform = ViewportClient->GetViewTransform();
ViewCameraState.bIsOrthographic = ViewportClient->IsOrtho();
ViewCameraState.Position = ViewTransform.GetLocation();
ViewCameraState.HorizontalFOVDegrees = ViewportClient->ViewFOV;
ViewCameraState.AspectRatio = ViewportClient->AspectRatio;
// ViewTransform rotation is only initialized for perspective!
if (ViewCameraState.bIsOrthographic == false)
{
// if using Orbit camera, the rotation in the ViewTransform is not the current camera rotation, it
// is set to a different rotation based on the Orbit. So we have to convert back to camera rotation.
FRotator ViewRotation = (ViewportClient->bUsingOrbitCamera) ?
ViewTransform.ComputeOrbitMatrix().InverseFast().Rotator() : ViewTransform.GetRotation();
ViewCameraState.Orientation = ViewRotation.Quaternion();
}
else
{
// These rotations are based on hardcoded values in EditorViewportClient.cpp, see switches in FEditorViewportClient::CalcSceneView and FEditorViewportClient::Draw
switch (ViewportClient->ViewportType)
{
case LVT_OrthoXY:
ViewCameraState.Orientation = FQuat(FRotator(-90.0f, -90.0f, 0.0f));
break;
case LVT_OrthoNegativeXY:
ViewCameraState.Orientation = FQuat(FRotator(90.0f, 90.0f, 0.0f));
break;
case LVT_OrthoXZ:
ViewCameraState.Orientation = FQuat(FRotator(0.0f, -90.0f, 0.0f));
break;
case LVT_OrthoNegativeXZ:
ViewCameraState.Orientation = FQuat(FRotator(0.0f, 90.0f, 0.0f));
break;
case LVT_OrthoYZ:
ViewCameraState.Orientation = FQuat(FRotator(0.0f, 0.0f, 0.0f));
break;
case LVT_OrthoNegativeYZ:
ViewCameraState.Orientation = FQuat(FRotator(0.0f, 180.0f, 0.0f));
break;
default:
ViewCameraState.Orientation = FQuat::Identity;
}
ViewCameraState.OrthoWorldCoordinateWidth = ViewportClient->GetOrthoUnitsPerPixel(ViewportClient->Viewport) * static_cast<float>(ViewportClient->Viewport->GetSizeXY().X);
}
ViewCameraState.bIsVR = false;
}
};
void UEditorInteractiveToolsContext::Render(const FSceneView* View, FViewport* Viewport, FPrimitiveDrawInterface* PDI)
{
// skip HitProxy rendering passes if desired
if (PDI->IsHitTesting() && bEnableRenderingDuringHitProxyPass == false)
{
return;
}
// THIS IS NOT SAFE!! However it appears that (1) it is only possible to get certain info from the EditorViewportClient,
// but (2) there is no way to know if a FViewportClient is an FEditorViewportClient. Currently this ::Render() function
// is only intended to be called by FEdMode/UEdMode::Render(), and their ::Render() calls are only called by the
// FEditorViewportClient, which passes it's own Viewport down. So, this cast should be valid (for now)
FEditorViewportClient* ViewportClient = static_cast<FEditorViewportClient*>(Viewport->GetClient());
// Update the currently-hovered scene view information, which GizmoArrowComponent and friends will
// use to recalculate their size/visibility/etc.
if (GizmoViewContext && ViewportClient == EditorModeManager->GetHoveredViewportClient())
{
GizmoViewContext->ResetFromSceneView(*View);
}
// Render Tool and Gizmos
const FEditorViewportClient* Focused = EditorModeManager->GetFocusedViewportClient();
const FEditorViewportClient* Hovered = EditorModeManager->GetHoveredViewportClient();
EViewInteractionState InteractionState = EViewInteractionState::None;
if (ViewportClient == Focused )
{
InteractionState |= EViewInteractionState::Focused;
}
if (ViewportClient == Hovered )
{
InteractionState |= EViewInteractionState::Hovered;
}
FEdModeTempRenderContext RenderContext(View, Viewport, ViewportClient, PDI, InteractionState);
ToolManager->Render(&RenderContext);
GizmoManager->Render(&RenderContext);
OnRender.Broadcast(&RenderContext);
}
void UEditorInteractiveToolsContext::DrawHUD(FViewportClient* ViewportClient,FViewport* Viewport,const FSceneView* View, FCanvas* Canvas)
{
FEditorViewportClient* EditorViewportClient = static_cast<FEditorViewportClient*>(Viewport->GetClient());
const FViewportClient* Focused = EditorModeManager->GetFocusedViewportClient();
const FViewportClient* Hovered = EditorModeManager->GetHoveredViewportClient();
EViewInteractionState InteractionState = EViewInteractionState::None;
if (ViewportClient == Focused )
{
InteractionState |= EViewInteractionState::Focused;
}
if (ViewportClient == Hovered )
{
InteractionState |= EViewInteractionState::Hovered;
}
FEdModeTempRenderContext RenderContext(View, Viewport, EditorViewportClient, nullptr /*PDI*/, InteractionState);
ToolManager->DrawHUD(Canvas, &RenderContext);
GizmoManager->DrawHUD(Canvas, &RenderContext);
OnDrawHUD.Broadcast(Canvas, &RenderContext);
}
bool UEditorInteractiveToolsContext::ProcessEditDelete()
{
if (ToolManager->HasAnyActiveTool() == false)
{
return false;
}
bool bSkipDelete = false;
USelection* SelectedActors = GEditor->GetSelectedActors();
for (int i = 0; i < SelectedActors->Num() && bSkipDelete == false; ++i)
{
UObject* SelectedActor = SelectedActors->GetSelectedObject(i);
// If any of the selected actors are AInternalToolFrameworkActor, we do not want to allow them to be deleted,
// as generally this will cause problems for the Tool.
if ( Cast<AInternalToolFrameworkActor>(SelectedActor) != nullptr)
{
bSkipDelete = true;
}
// If any Components of selected Actors implement UToolFrameworkComponent, we disable delete (for now).
// (Currently Sculpt and a few other Modeling Tools attach their preview mesh components to the selected Actor)
AActor* Actor = Cast<AActor>(SelectedActor);
if (Actor != nullptr)
{
const TSet<UActorComponent*>& Components = Actor->GetComponents();
for (const UActorComponent* Component : Components)
{
if ( Component->Implements<UToolFrameworkComponent>() )
{
bSkipDelete = true;
}
}
}
}
return bSkipDelete;
}
FRay UEditorInteractiveToolsContext::GetRayFromMousePos(FEditorViewportClient* ViewportClient, FViewport* Viewport, int MouseX, int MouseY)
{
if (!ViewportClient)
{
return FRay();
}
FSceneViewFamilyContext ViewFamily(FSceneViewFamily::ConstructionValues(
ViewportClient->Viewport,
ViewportClient->GetScene(),
ViewportClient->EngineShowFlags).SetRealtimeUpdate(ViewportClient->IsRealtime())); // why SetRealtimeUpdate here??
// this View is deleted by the FSceneViewFamilyContext destructor
FSceneView* View = ViewportClient->CalcSceneView(&ViewFamily);
FViewportCursorLocation MouseViewportRay(View, ViewportClient, MouseX, MouseY);
FVector RayOrigin = MouseViewportRay.GetOrigin();
FVector RayDirection = MouseViewportRay.GetDirection();
// in Ortho views, the RayOrigin appears to be completely arbitrary, in some views it is on the view plane,
// others it moves back/forth with the OrthoZoom. Translate by a large amount here in hopes of getting
// ray origin "outside" the scene (which is a disaster for numerical precision !! ... )
if (ViewportClient->IsOrtho())
{
RayOrigin -= UE_FLOAT_HUGE_DISTANCE * RayDirection;
}
return FRay(RayOrigin, RayDirection, true);
}
bool UEditorInteractiveToolsContext::CanStartTool(const FString ToolTypeIdentifier) const
{
return UInteractiveToolsContext::CanStartTool(EToolSide::Mouse, ToolTypeIdentifier);
}
bool UEditorInteractiveToolsContext::HasActiveTool() const
{
return UInteractiveToolsContext::HasActiveTool(EToolSide::Mouse);
}
FString UEditorInteractiveToolsContext::GetActiveToolName() const
{
return UInteractiveToolsContext::GetActiveToolName(EToolSide::Mouse);
}
bool UEditorInteractiveToolsContext::ActiveToolHasAccept() const
{
return UInteractiveToolsContext::ActiveToolHasAccept(EToolSide::Mouse);
}
bool UEditorInteractiveToolsContext::CanAcceptActiveTool() const
{
return UInteractiveToolsContext::CanAcceptActiveTool(EToolSide::Mouse);
}
bool UEditorInteractiveToolsContext::CanCancelActiveTool() const
{
return UInteractiveToolsContext::CanCancelActiveTool(EToolSide::Mouse);
}
bool UEditorInteractiveToolsContext::CanCompleteActiveTool() const
{
return UInteractiveToolsContext::CanCompleteActiveTool(EToolSide::Mouse);
}
void UEditorInteractiveToolsContext::StartTool(const FString ToolTypeIdentifier)
{
FString LocalIdentifier(ToolTypeIdentifier);
PendingToolToStart = LocalIdentifier;
PostInvalidation();
}
void UEditorInteractiveToolsContext::EndTool(EToolShutdownType ShutdownType)
{
PendingToolShutdownType = ShutdownType;
PostInvalidation();
}
void UEditorInteractiveToolsContext::Activate()
{
bIsActive = true;
}
void UEditorInteractiveToolsContext::Deactivate()
{
bIsActive = false;
}
void UEditorInteractiveToolsContext::DeactivateActiveTool(EToolSide WhichSide, EToolShutdownType ShutdownType)
{
UInteractiveToolsContext::DeactivateActiveTool(WhichSide, ShutdownType);
RestoreEditorState();
}
void UEditorInteractiveToolsContext::DeactivateAllActiveTools(EToolShutdownType ShutdownType)
{
UInteractiveToolsContext::DeactivateAllActiveTools(ShutdownType);
RestoreEditorState();
}
void UEditorInteractiveToolsContext::SetEditorStateForTool()
{
FLevelEditorModule& LevelEditorModule = FModuleManager::GetModuleChecked<FLevelEditorModule>("LevelEditor");
TSharedPtr<ILevelEditor> LevelEditor = LevelEditorModule.GetFirstLevelEditor();
if (LevelEditor.IsValid())
{
TArray<TSharedPtr<SLevelViewport>> Viewports = LevelEditor->GetViewports();
for (const TSharedPtr<SLevelViewport>& ViewportWindow : Viewports)
{
if (ViewportWindow.IsValid())
{
FEditorViewportClient& Viewport = ViewportWindow->GetAssetViewportClient();
Viewport.EnableOverrideEngineShowFlags([](FEngineShowFlags& Flags)
{
Flags.SetTemporalAA(false);
Flags.SetMotionBlur(false);
// disable this as depending on fixed exposure settings the entire scene may turn black
//Flags.SetEyeAdaptation(false);
});
}
}
}
}
void UEditorInteractiveToolsContext::RestoreEditorState()
{
FLevelEditorModule& LevelEditorModule = FModuleManager::GetModuleChecked<FLevelEditorModule>("LevelEditor");
TSharedPtr<ILevelEditor> LevelEditor = LevelEditorModule.GetFirstLevelEditor();
if (LevelEditor.IsValid())
{
TArray<TSharedPtr<SLevelViewport>> Viewports = LevelEditor->GetViewports();
for (const TSharedPtr<SLevelViewport>& ViewportWindow : Viewports)
{
if (ViewportWindow.IsValid())
{
FEditorViewportClient& ViewportClient = ViewportWindow->GetAssetViewportClient();
ViewportClient.DisableOverrideEngineShowFlags();
// Rebuild the cached hit proxy. The tool may have disabled some viewport items while active (e.g. transform gizmo) and
// so the hit proxy cache may be out of date. In the past this has led to, for example, the gizmo being visible but not
// clickable when returning from a tool (UE-116888).
// TODO: Figure out why the hit proxy is not being rebuilt elsewhere
ViewportClient.RequestInvalidateHitProxy(ViewportClient.Viewport);
}
}
}
}
void UEditorInteractiveToolsContext::OnToolEnded(UInteractiveToolManager* InToolManager, UInteractiveTool* InEndedTool)
{
RestoreEditorState();
}
void UEditorInteractiveToolsContext::OnToolPostBuild(UInteractiveToolManager* InToolManager, EToolSide InSide, UInteractiveTool* InBuiltTool, UInteractiveToolBuilder* InToolBuilder, const FToolBuilderState& ToolState)
{
// todo: Add any shared tool targets for the mode toolkit
}
void UEditorInteractiveToolsContext::SetEnableRenderingDuringHitProxyPass(bool bEnabled)
{
bEnableRenderingDuringHitProxyPass = bEnabled;
}
void UEditorInteractiveToolsContext::SetForceCombinedGizmoMode(bool bEnabled)
{
bForceCombinedGizmoMode = bEnabled;
}
void UEditorInteractiveToolsContext::SetAbsoluteWorldSnappingEnabled(bool bEnabled)
{
bEnableAbsoluteWorldSnapping = bEnabled;
}
void UEditorInteractiveToolsContext::SetDeactivateToolsOnPIEStart(bool bDeactivateTools)
{
bDeactivateOnPIEStart = bDeactivateTools;
}
void UEditorInteractiveToolsContext::SetDeactivateToolsOnSaveWorld(bool bDeactivateTools)
{
bDeactivateOnSaveWorld = bDeactivateTools;
}
void UModeManagerInteractiveToolsContext::Tick(FEditorViewportClient* ViewportClient, float DeltaTime)
{
UEditorInteractiveToolsContext::Tick(ViewportClient, DeltaTime);
for (const TObjectPtr<UEdModeInteractiveToolsContext>& EdModeContext : EdModeToolsContexts)
{
EdModeContext->Tick(ViewportClient, DeltaTime);
}
}
void UModeManagerInteractiveToolsContext::Render(const FSceneView* View, FViewport* Viewport, FPrimitiveDrawInterface* PDI)
{
UEditorInteractiveToolsContext::Render(View, Viewport, PDI);
for (const TObjectPtr<UEdModeInteractiveToolsContext>& EdModeContext : EdModeToolsContexts)
{
EdModeContext->Render(View, Viewport, PDI);
}
}
void UModeManagerInteractiveToolsContext::DrawHUD(FViewportClient* ViewportClient, FViewport* Viewport, const FSceneView* View, FCanvas* Canvas)
{
UEditorInteractiveToolsContext::DrawHUD(ViewportClient, Viewport, View, Canvas);
for (const TObjectPtr<UEdModeInteractiveToolsContext>& EdModeContext : EdModeToolsContexts)
{
EdModeContext->DrawHUD(ViewportClient, Viewport, View, Canvas);
}
if (DragToolsBehaviorSource)
{
DragToolsBehaviorSource->RenderTools(View, Canvas);
}
}
bool UModeManagerInteractiveToolsContext::ProcessEditDelete()
{
bool bHandled = UEditorInteractiveToolsContext::ProcessEditDelete();
for (const TObjectPtr<UEdModeInteractiveToolsContext>& EdModeContext : EdModeToolsContexts)
{
bHandled |= EdModeContext->ProcessEditDelete();
}
return bHandled;
}
void UModeManagerInteractiveToolsContext::DeactivateAllActiveTools(EToolShutdownType ShutdownType)
{
for (UEdModeInteractiveToolsContext* EdModeContext : EdModeToolsContexts)
{
EdModeContext->DeactivateAllActiveTools(ShutdownType);
}
Super::DeactivateAllActiveTools(ShutdownType);
}
void UModeManagerInteractiveToolsContext::UpdateStateWithoutRoutingInputKey(FEditorViewportClient* ViewportClient, FViewport* Viewport, FKey Key, EInputEvent Event)
{
// Currently, the only internal state we keep is the state of various mouse keys being down. Note
// that we don't want to save bPressed or bReleased for them as those are one-time events issued
// from InputKey, and shouldn't show up on mouse moves or on other keys being pressed/released.
if ((Event == IE_Pressed || Event == IE_Released)
&& Key.IsMouseButton())
{
if (Key.IsMouseButton())
{
if (Key == EKeys::LeftMouseButton)
{
CurrentMouseState.Mouse.Left.bDown = (Event == IE_Pressed);
}
else if (Key == EKeys::MiddleMouseButton)
{
CurrentMouseState.Mouse.Middle.bDown = (Event == IE_Pressed);
}
else if (Key == EKeys::RightMouseButton)
{
CurrentMouseState.Mouse.Right.bDown = (Event == IE_Pressed);
}
}
}
}
bool UModeManagerInteractiveToolsContext::InputKey(FEditorViewportClient* ViewportClient, FViewport* Viewport, FKey Key, EInputEvent Event)
{
#ifdef ENABLE_DEBUG_PRINTING
if (Event == IE_Pressed) { UE_LOG(LogInteractiveToolsContext, Warning, TEXT("PRESSED EVENT")); }
else if (Event == IE_Released) { UE_LOG(LogInteractiveToolsContext, Warning, TEXT("RELEASED EVENT")); }
else if (Event == IE_Repeat) { UE_LOG(LogInteractiveToolsContext, Warning, TEXT("REPEAT EVENT")); }
else if (Event == IE_Axis) { UE_LOG(LogInteractiveToolsContext, Warning, TEXT("AXIS EVENT")); }
else if (Event == IE_DoubleClick) { UE_LOG(LogInteractiveToolsContext, Warning, TEXT("DOUBLECLICK EVENT")); }
#endif
// Update the current state, then route result
UpdateStateWithoutRoutingInputKey(ViewportClient, Viewport, Key, Event);
if (Event == IE_Pressed || Event == IE_Released || Event == IE_DoubleClick)
{
if (Key.IsMouseButton())
{
bool bIsLeftMouse = (Key == EKeys::LeftMouseButton);
bool bIsMiddleMouse = (Key == EKeys::MiddleMouseButton);
bool bIsRightMouse = (Key == EKeys::RightMouseButton);
if (bIsLeftMouse || bIsMiddleMouse || bIsRightMouse)
{
// Currently, we don't capture mouse clicks that start with Alt being down because we want
// Alt camera manipulation to have priority over tools. So, we let those inputs pass on up
// to wherever they get handled.
// Someday these kinds of prioritizations will be handled by having camera manipulation be
// in a common input router so that behavior priorities can determine the ordering.
if (BypassAltModifier())
{
if (ViewportClient && ViewportClient->IsAltPressed() && InputRouter->HasActiveMouseCapture() == false)
{
return false;
}
}
FInputDeviceState InputState = CurrentMouseState;
InputState.InputDevice = EInputDevices::Mouse;
FModifierKeysState ModifierKeys = FSlateApplication::Get().GetModifierKeys();
InputState.SetModifierKeyStates(
ModifierKeys.IsShiftDown(), ModifierKeys.IsAltDown(),
ModifierKeys.IsControlDown(), ModifierKeys.IsCommandDown());
// DoubleClick replaces a Press event. Include DoubleClick in Pressed/Down events so
// that it can still be processed by single click behaviors. For explicit double click
// handling, clients can listen for the DoubleClick event with a higher capture priority.
const bool bIsPressed = (Event == IE_Pressed || Event == IE_DoubleClick);
const bool bIsReleased = (Event == IE_Released);
const bool bIsDoubleClick = (Event == IE_DoubleClick);
if (bIsLeftMouse)
{
InputState.Mouse.Left.SetStates(bIsPressed, bIsPressed, bIsReleased, bIsDoubleClick);
}
else if (bIsMiddleMouse)
{
InputState.Mouse.Middle.SetStates(bIsPressed, bIsPressed, bIsReleased, bIsDoubleClick);
}
else
{
InputState.Mouse.Right.SetStates(bIsPressed, bIsPressed, bIsReleased, bIsDoubleClick);
}
if (InputRouter->PostInputEvent(InputState))
{
return true;
}
}
else if (Key == EKeys::MouseScrollUp || Key == EKeys::MouseScrollDown)
{
// Note that we get two events for each scroll- an IE_Pressed, and IE_Released.
// We pass both of these in, though only the first one will have WheelDelta set.
// If a behavior captures the mouse wheel interaction, the second event will give
// it the opportunity to immediately release capture. Not passing in the second
// event would probably be ok too- capture would get released on next hover event.
FInputDeviceState InputState = CurrentMouseState;
InputState.InputDevice = EInputDevices::Mouse;
FModifierKeysState ModifierKeys = FSlateApplication::Get().GetModifierKeys();
InputState.SetModifierKeyStates(
ModifierKeys.IsShiftDown(), ModifierKeys.IsAltDown(),
ModifierKeys.IsControlDown(), ModifierKeys.IsCommandDown());
InputState.Mouse.WheelDelta = (Event != IE_Pressed) ? 0.f
: (Key == EKeys::MouseScrollUp) ? 1.f
: -1.f;
return InputRouter->PostInputEvent(InputState);
}
}
else if (Key.IsGamepadKey())
{
// not supported yet
}
else if (Key.IsTouch())
{
// not supported yet
}
else if (Key.IsAnalog())
{
// not supported yet
}
else // is this definitely a keyboard key?
{
FInputDeviceState InputState;
InputState.InputDevice = EInputDevices::Keyboard;
FModifierKeysState ModifierKeys = FSlateApplication::Get().GetModifierKeys();
InputState.SetModifierKeyStates(
ModifierKeys.IsShiftDown(), ModifierKeys.IsAltDown(),
ModifierKeys.IsControlDown(), ModifierKeys.IsCommandDown());
InputState.Keyboard.ActiveKey.Button = Key;
bool bPressed = (Event == IE_Pressed);
InputState.Keyboard.ActiveKey.SetStates(bPressed, bPressed, !bPressed);
return InputRouter->PostInputEvent(InputState);
}
}
return false;
}
bool UModeManagerInteractiveToolsContext::MouseEnter(FEditorViewportClient* ViewportClient, FViewport* Viewport, int32 x, int32 y)
{
#ifdef ENABLE_DEBUG_PRINTING
UE_LOG(LogInteractiveToolsContext, Warning, TEXT("MOUSE ENTER"));
#endif
CurrentMouseState.Mouse.Position2D = FVector2D(x, y);
CurrentMouseState.Mouse.WorldRay = GetRayFromMousePos(ViewportClient, Viewport, x, y);
return false;
}
bool UModeManagerInteractiveToolsContext::MouseMove(FEditorViewportClient* ViewportClient, FViewport* Viewport, int32 x, int32 y)
{
#ifdef ENABLE_DEBUG_PRINTING
UE_LOG(LogInteractiveToolsContext, Warning, TEXT("HOVER %p"), ViewportClient);
#endif
CurrentMouseState.Mouse.Position2D = FVector2D(x, y);
CurrentMouseState.Mouse.WorldRay = GetRayFromMousePos(ViewportClient, Viewport, x, y);
FInputDeviceState InputState = CurrentMouseState;
InputState.InputDevice = EInputDevices::Mouse;
FModifierKeysState ModifierKeys = FSlateApplication::Get().GetModifierKeys();
InputState.SetModifierKeyStates(
ModifierKeys.IsShiftDown(), ModifierKeys.IsAltDown(),
ModifierKeys.IsControlDown(), ModifierKeys.IsCommandDown());
if (InputRouter->HasActiveMouseCapture())
{
// TODO: This should no longer be necessary: test and remove.
// This state occurs if InputBehavior did not release capture on mouse release.
// UMultiClickSequenceInputBehavior does this, eg for multi-click draw-polygon sequences.
// It's not ideal though and maybe would be better done via multiple captures + hover...?
InputRouter->PostInputEvent(InputState);
}
else
{
InputRouter->PostHoverInputEvent(InputState);
}
return false;
}
bool UModeManagerInteractiveToolsContext::LostFocus(FEditorViewportClient* InViewportClient, FViewport* Viewport) const
{
InputRouter->ForceTerminateAll();
return false;
}
bool UModeManagerInteractiveToolsContext::MouseLeave(FEditorViewportClient* ViewportClient, FViewport* Viewport)
{
#ifdef ENABLE_DEBUG_PRINTING
UE_LOG(LogInteractiveToolsContext, Warning, TEXT("MOUSE LEAVE"));
#endif
return false;
}
bool UModeManagerInteractiveToolsContext::StartTracking(FEditorViewportClient* InViewportClient, FViewport* InViewport)
{
bIsTrackingMouse = InputRouter->HasActiveMouseCapture();
return bIsTrackingMouse;
}
bool UModeManagerInteractiveToolsContext::CapturedMouseMove(FEditorViewportClient* InViewportClient, FViewport* InViewport, int32 InMouseX, int32 InMouseY)
{
FVector2D OldPosition = CurrentMouseState.Mouse.Position2D;
CurrentMouseState.Mouse.Position2D = FVector2D(InMouseX, InMouseY);
CurrentMouseState.Mouse.WorldRay = GetRayFromMousePos(InViewportClient, InViewport, InMouseX, InMouseY);
if (InputRouter->HasActiveMouseCapture())
{
#ifdef ENABLE_DEBUG_PRINTING
UE_LOG(LogInteractiveToolsContext, Warning, TEXT("CAPTURED MOUSE MOVE"));
#endif
FInputDeviceState InputState = CurrentMouseState;
InputState.InputDevice = EInputDevices::Mouse;
FModifierKeysState ModifierKeys = FSlateApplication::Get().GetModifierKeys();
InputState.SetModifierKeyStates(
ModifierKeys.IsShiftDown(), ModifierKeys.IsAltDown(),
ModifierKeys.IsControlDown(), ModifierKeys.IsCommandDown());
InputState.Mouse.Delta2D = CurrentMouseState.Mouse.Position2D - OldPosition;
InputRouter->PostInputEvent(InputState);
return true;
}
return false;
}
bool UModeManagerInteractiveToolsContext::EndTracking(FEditorViewportClient* InViewportClient, FViewport* InViewport)
{
if (bIsTrackingMouse)
{
// If the input router captured the mouse input, we need to invalidate the viewport client here, since the mouse delta tracker's end tracking will not be called.
constexpr bool bForceChildViewportRedraw = true;
constexpr bool bInvalidateHitProxies = true;
if (InViewportClient)
{
InViewportClient->Invalidate(bForceChildViewportRedraw, bInvalidateHitProxies);
}
bIsTrackingMouse = false;
return true;
}
return false;
}
bool UModeManagerInteractiveToolsContext::GetCursor(EMouseCursor::Type& OutCursor) const
{
UToolsContextCursorAPI* ToolsContextCursorAPI = ToolManager->GetContextObjectStore()->FindContext<UToolsContextCursorAPI>();
if (ToolsContextCursorAPI && ToolsContextCursorAPI->IsCursorOverridden())
{
OutCursor = ToolsContextCursorAPI->GetCurrentCursorOverride();
return true;
}
if (bIsTrackingMouse)
{
OutCursor = EMouseCursor::SlashedCircle;
return true;
}
return false;
}
FRay UModeManagerInteractiveToolsContext::GetLastWorldRay() const
{
return CurrentMouseState.Mouse.WorldRay;
}
void UModeManagerInteractiveToolsContext::Initialize(IToolsContextQueriesAPI* QueriesAPIIn, IToolsContextTransactionsAPI* TransactionsAPIIn)
{
Super::Initialize(QueriesAPIIn, TransactionsAPIIn);
BeginPIEDelegateHandle = FEditorDelegates::BeginPIE.AddLambda([this](bool bSimulating)
{
TerminateActiveToolsOnPIEStart();
});
PreSaveWorldDelegateHandle = FEditorDelegates::PreSaveWorldWithContext.AddLambda([this](UWorld* World, FObjectPreSaveContext ObjectSaveContext)
{
TerminateActiveToolsOnSaveWorld();
});
FLevelEditorModule& LevelEditor = FModuleManager::LoadModuleChecked<FLevelEditorModule>("LevelEditor");
WorldTearDownDelegateHandle = LevelEditor.OnMapChanged().AddLambda([this](UWorld* World, EMapChangeType ChangeType)
{
if (ChangeType == EMapChangeType::TearDownWorld)
{
TerminateActiveToolsOnWorldTearDown();
}
});
// Tools frequently spawn temporary objects in the level for visualization, gizmos, etc, so having a level be
// removed from the world puts the tools at risk of letting those objects be garbage collected and cause crashes
FWorldDelegates::PreLevelRemovedFromWorld.AddWeakLambda(this, [this](ULevel*, UWorld*) {
TerminateActiveToolsOnLevelChange();
});
ToolManager->OnToolEnded.AddUObject(this, &UModeManagerInteractiveToolsContext::OnToolEnded);
ToolManager->OnToolPostBuild.AddUObject(this, &UModeManagerInteractiveToolsContext::OnToolPostBuild);
// if viewport clients change we will discard our overrides as we aren't sure what happened
ViewportClientListChangedHandle = GEditor->OnViewportClientListChanged().AddLambda([this]()
{
RestoreEditorState();
});
GetParentEditorModeManager()->OnEditorModeIDChanged().AddUObject(this, &UModeManagerInteractiveToolsContext::OnEditorModeChanged);
// Delegates used to handle SelectionMode.EnableITFTools CVAR changes
UE::Editor::DragTools::OnEditorDragToolsActivated().AddUObject(this, &UModeManagerInteractiveToolsContext::RegisterDragTools);
UE::Editor::DragTools::OnEditorDragToolsDeactivated().AddUObject(this, &UModeManagerInteractiveToolsContext::UnregisterDragTools);
}
void UModeManagerInteractiveToolsContext::Shutdown()
{
UnregisterDragTools();
GetParentEditorModeManager()->OnEditorModeIDChanged().RemoveAll(this);
UE::Editor::DragTools::OnEditorDragToolsActivated().RemoveAll(this);
UE::Editor::DragTools::OnEditorDragToolsDeactivated().RemoveAll(this);
if (ToolManager)
{
ToolManager->OnToolPostBuild.RemoveAll(this);
ToolManager->OnToolEnded.RemoveAll(this);
}
if (FLevelEditorModule* LevelEditor = FModuleManager::GetModulePtr<FLevelEditorModule>("LevelEditor"))
{
LevelEditor->OnMapChanged().Remove(WorldTearDownDelegateHandle);
FEditorDelegates::BeginPIE.Remove(BeginPIEDelegateHandle);
FEditorDelegates::PreSaveWorldWithContext.Remove(PreSaveWorldDelegateHandle);
GEditor->OnViewportClientListChanged().Remove(ViewportClientListChangedHandle);
}
FWorldDelegates::PreLevelRemovedFromWorld.RemoveAll(this);
Super::Shutdown();
}
UEdModeInteractiveToolsContext* UModeManagerInteractiveToolsContext::CreateNewChildEdModeToolsContext()
{
UEdModeInteractiveToolsContext* NewModeToolsContext =
NewObject<UEdModeInteractiveToolsContext>(GetTransientPackage(), UEdModeInteractiveToolsContext::StaticClass(), NAME_None, RF_Transient);
NewModeToolsContext->InitializeContextFromModeManagerContext(this);
return NewModeToolsContext;
}
bool UModeManagerInteractiveToolsContext::OnChildEdModeActivated(UEdModeInteractiveToolsContext* ChildToolsContext)
{
if (!ensureMsgf(EdModeToolsContexts.Find(ChildToolsContext), TEXT("Child ToolsContext was already found!")))
{
return false;
}
EdModeToolsContexts.Add(ChildToolsContext);
ChildToolsContext->Activate();
return true;
}
bool UModeManagerInteractiveToolsContext::OnChildEdModeDeactivated(UEdModeInteractiveToolsContext* ChildToolsContext)
{
for (int32 k = 0; k < EdModeToolsContexts.Num(); ++k)
{
if (EdModeToolsContexts[k] == ChildToolsContext)
{
ChildToolsContext->DeactivateAllActiveTools(EToolShutdownType::Cancel);
ChildToolsContext->Deactivate();
EdModeToolsContexts.RemoveAt(k);
return true;
}
}
ensureMsgf(false, TEXT("Child ToolsContext was not found! It may have already been removed"));
return false;
}
void UModeManagerInteractiveToolsContext::SetBypassAltModifier(bool bInBypassAltModifier)
{
bBypassAltModifier = bInBypassAltModifier;
}
void UModeManagerInteractiveToolsContext::RegisterDragTools()
{
FEditorModeTools* EditorModeTools = GetParentEditorModeManager();
if (!EditorModeTools)
{
return;
}
// Ignore uninitialized Mode Tools missing a Viewport Client
if (!EditorModeTools->GetFocusedViewportClient())
{
return;
}
// This Mode Manager does not require Drag Tools
if (!UsesDragTools())
{
return;
}
// Not initialized yet
if (!DragToolsBehaviorSource)
{
// Alt modifier input required by Drag Tools, but currently skipped by ITF in Tool Context.
// Let's make sure Alt keys are processed
SetBypassAltModifier(false);
DragToolsBehaviorSource = NewObject<UDragToolsBehaviorSource>();
DragToolsBehaviorSource->Initialize(this);
DragToolsBehaviorSource->RegisterSource();
}
}
void UModeManagerInteractiveToolsContext::UnregisterDragTools()
{
if (DragToolsBehaviorSource)
{
// Revert to previous state, with Alt Modifier not being processed
SetBypassAltModifier(true);
DragToolsBehaviorSource->DeregisterSource();
DragToolsBehaviorSource = nullptr;
}
}
void UModeManagerInteractiveToolsContext::OnEditorModeChanged(const FEditorModeID& InModeID, bool bInIsEnteringMode)
{
if (!UE::Editor::DragTools::UseEditorDragTools())
{
return;
}
if (bInIsEnteringMode)
{
RegisterDragTools();
}
else
{
UnregisterDragTools();
}
}
void UEdModeInteractiveToolsContext::InitializeContextFromModeManagerContext(UModeManagerInteractiveToolsContext* ModeManagerToolsContext)
{
check(ModeManagerToolsContext != nullptr);
check(ModeManagerToolsContext->InputRouter != nullptr);
FEditorModeTools* ModeManager = ModeManagerToolsContext->GetParentEditorModeManager();
check(ModeManager != nullptr);
ParentModeManagerToolsContext = ModeManagerToolsContext;
SetCreateContextStoreFunc([this, ModeManagerToolsContext](const FContextInitInfo& ContextInfo)
{
UContextObjectStore* NewContextStore = NewObject<UContextObjectStore>(ModeManagerToolsContext->ContextObjectStore);
return NewContextStore;
});
InitializeContextWithEditorModeManager(ModeManager, ModeManagerToolsContext->InputRouter);
}
FRay UEdModeInteractiveToolsContext::GetLastWorldRay() const
{
return ParentModeManagerToolsContext->GetLastWorldRay();
}