// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #include "EdModeInteractiveToolsContext.h" #include "Editor.h" #include "EditorViewportClient.h" #include "EditorModeManager.h" #include "LevelEditorViewport.h" // for GCurrentLevelEditingViewportClient #include "Engine/Selection.h" #include "Misc/ITransaction.h" #include "ScopedTransaction.h" #include "Materials/Material.h" #include "Engine/StaticMesh.h" #include "Components/StaticMeshComponent.h" #include "EditorToolAssetAPI.h" #include "EditorComponentSourceFactory.h" //#include "PhysicsEngine/BodySetup.h" //#include "Interfaces/Interface_CollisionDataProvider.h" //#define ENABLE_DEBUG_PRINTING class FEdModeToolsContextQueriesImpl : public IToolsContextQueriesAPI { public: UEdModeInteractiveToolsContext* ToolsContext; FEdMode* EditorMode; FViewCameraState CachedViewState; FEdModeToolsContextQueriesImpl(UEdModeInteractiveToolsContext* Context, FEdMode* EditorModeIn) { ToolsContext = Context; EditorMode = EditorModeIn; } void CacheCurrentViewState(FEditorViewportClient* ViewportClient) { FViewportCameraTransform ViewTransform = ViewportClient->GetViewTransform(); CachedViewState.Position = ViewTransform.GetLocation(); CachedViewState.Orientation = ViewTransform.GetRotation().Quaternion(); CachedViewState.bIsOrthographic = ViewportClient->IsOrtho(); CachedViewState.bIsVR = false; } virtual void GetCurrentSelectionState(FToolBuilderState& StateOut) const override { StateOut.ToolManager = ToolsContext->ToolManager; StateOut.GizmoManager = ToolsContext->GizmoManager; StateOut.World = EditorMode->GetWorld(); StateOut.SelectedActors = EditorMode->GetModeManager()->GetSelectedActors(); StateOut.SelectedComponents = EditorMode->GetModeManager()->GetSelectedComponents(); StateOut.SourceBuilder = ToolsContext->GetComponentSourceFactory(); } virtual void GetCurrentViewState(FViewCameraState& StateOut) const override { StateOut = CachedViewState; } virtual bool ExecuteSceneSnapQuery(const FSceneSnapQueryRequest& Request, TArray& Results) const override { if (Request.RequestType != ESceneSnapQueryType::Position) { return false; // not supported yet } int FoundResultCount = 0; // // Run a snap query by casting ray into the world. // If a hit is found, we look up what triangle was hit, and then test its vertices and edges // // cast ray into world FVector RayStart = CachedViewState.Position; FVector RayDirection = Request.Position - RayStart; RayDirection.Normalize(); FVector RayEnd = RayStart + 9999999 * RayDirection; FCollisionObjectQueryParams ObjectQueryParams(FCollisionObjectQueryParams::AllObjects); FCollisionQueryParams QueryParams = FCollisionQueryParams::DefaultQueryParam; QueryParams.bTraceComplex = true; QueryParams.bReturnFaceIndex = true; FHitResult HitResult; bool bHitWorld = EditorMode->GetWorld()->LineTraceSingleByObjectType(HitResult, RayStart, RayEnd, ObjectQueryParams, QueryParams); if (bHitWorld && HitResult.FaceIndex >= 0) { float VisualAngle = OpeningAngleDeg(Request.Position, HitResult.ImpactPoint, RayStart); //UE_LOG(LogTemp, Warning, TEXT("[HIT] visualangle %f faceindex %d"), VisualAngle, HitResult.FaceIndex); if (VisualAngle < Request.VisualAngleThresholdDegrees) { UPrimitiveComponent* Component = HitResult.Component.Get(); if (Cast(Component) != nullptr) { // HitResult.FaceIndex is apparently an index into the TriMeshCollisionData, not sure how // to directly access it. Calling GetPhysicsTriMeshData is expensive! //UBodySetup* BodySetup = Cast(Component)->GetBodySetup(); //UObject* CDPObj = BodySetup->GetOuter(); //IInterface_CollisionDataProvider* CDP = Cast(CDPObj); //FTriMeshCollisionData TriMesh; //CDP->GetPhysicsTriMeshData(&TriMesh, true); //FTriIndices Triangle = TriMesh.Indices[HitResult.FaceIndex]; //FVector Positions[3] = { TriMesh.Vertices[Triangle.v0], TriMesh.Vertices[Triangle.v1], TriMesh.Vertices[Triangle.v2] }; // physics collision data is created from StaticMesh RenderData // so use HitResult.FaceIndex to extract triangle from the LOD0 mesh // (note: this may be incorrect if there are multiple sections...in that case I think we have to // first find section whose accumulated index range would contain .FaceIndexX) UStaticMesh* StaticMesh = Cast(Component)->GetStaticMesh(); FStaticMeshLODResources& LOD = StaticMesh->RenderData->LODResources[0]; FIndexArrayView Indices = LOD.IndexBuffer.GetArrayView(); int32 TriIdx = 3 * HitResult.FaceIndex; FVector Positions[3]; Positions[0] = LOD.VertexBuffers.PositionVertexBuffer.VertexPosition(Indices[TriIdx]); Positions[1] = LOD.VertexBuffers.PositionVertexBuffer.VertexPosition(Indices[TriIdx+1]); Positions[2] = LOD.VertexBuffers.PositionVertexBuffer.VertexPosition(Indices[TriIdx+2]); // transform to world space FTransform ComponentTransform = Component->GetComponentTransform(); Positions[0] = ComponentTransform.TransformPosition(Positions[0]); Positions[1] = ComponentTransform.TransformPosition(Positions[1]); Positions[2] = ComponentTransform.TransformPosition(Positions[2]); FSceneSnapQueryResult SnapResult; SnapResult.TriVertices[0] = Positions[0]; SnapResult.TriVertices[1] = Positions[1]; SnapResult.TriVertices[2] = Positions[2]; // try snapping to vertices float SmallestAngle = Request.VisualAngleThresholdDegrees; if ( (Request.TargetTypes & ESceneSnapQueryTargetType::MeshVertex) != ESceneSnapQueryTargetType::None) { for (int j = 0; j < 3; ++j) { VisualAngle = OpeningAngleDeg(Request.Position, Positions[j], RayStart); if (VisualAngle < SmallestAngle) { SmallestAngle = VisualAngle; SnapResult.Position = Positions[j]; SnapResult.TargetType = ESceneSnapQueryTargetType::MeshVertex; SnapResult.TriSnapIndex = j; } } } // try snapping to nearest points on edges if ( ((Request.TargetTypes & ESceneSnapQueryTargetType::MeshEdge) != ESceneSnapQueryTargetType::None) && (SnapResult.TargetType != ESceneSnapQueryTargetType::MeshVertex) ) { for (int j = 0; j < 3; ++j) { FVector EdgeNearestPt = NearestSegmentPt(Positions[j], Positions[(j+1)%3], Request.Position); VisualAngle = OpeningAngleDeg(Request.Position, EdgeNearestPt, RayStart); if (VisualAngle < SmallestAngle ) { SmallestAngle = VisualAngle; SnapResult.Position = EdgeNearestPt; SnapResult.TargetType = ESceneSnapQueryTargetType::MeshEdge; SnapResult.TriSnapIndex = j; } } } // if we found a valid snap, return it if (SmallestAngle < Request.VisualAngleThresholdDegrees) { SnapResult.TargetActor = HitResult.Actor.Get(); SnapResult.TargetComponent = HitResult.Component.Get(); Results.Add(SnapResult); FoundResultCount++; } } } } return (FoundResultCount > 0); } //@ todo this are mirrored from GeometryProcessing, which is still experimental...replace w/ direct calls once GP component is standardized static float OpeningAngleDeg(FVector A, FVector B, const FVector& P) { A -= P; A.Normalize(); B -= P; B.Normalize(); float Dot = FMath::Clamp(FVector::DotProduct(A,B), -1.0f, 1.0f); return acos(Dot) * (180.0f / 3.141592653589f); } static FVector NearestSegmentPt(FVector A, FVector B, const FVector& P) { FVector Direction = (B - A); float Length = Direction.Size(); Direction /= Length; float t = FVector::DotProduct( (P - A), Direction); if (t >= Length) { return B; } if (t <= 0) { return A; } return A + t * Direction; } virtual UMaterialInterface* GetStandardMaterial(EStandardToolContextMaterials MaterialType) const { if (MaterialType == EStandardToolContextMaterials::VertexColorMaterial) { return ToolsContext->StandardVertexColorMaterial; } check(false); return nullptr; } }; class FEdModeToolsContextTransactionImpl : public IToolsContextTransactionsAPI { public: UEdModeInteractiveToolsContext* ToolsContext; FEdMode* EditorMode; FEdModeToolsContextTransactionImpl(UEdModeInteractiveToolsContext* Context, FEdMode* EditorModeIn) { ToolsContext = Context; EditorMode = EditorModeIn; } virtual void PostMessage(const TCHAR* Message, EToolMessageLevel Level) override { UE_LOG(LogTemp, Warning, TEXT("[ToolsContext] %s"), Message); } 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 Change, const FText& Description) override { FScopedTransaction Transaction(Description); check(GUndo != nullptr); GUndo->StoreUndo(TargetObject, MoveTemp(Change)); // end transaction } virtual bool RequestSelectionChange(const FSelectedOjectsChangeList& 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) { GEditor->SelectActor(SelectionChange.Actors[k], bAdd, false, true, false); } GEditor->NoteSelectionChange(true); return true; } }; UEdModeInteractiveToolsContext::UEdModeInteractiveToolsContext() { EditorMode = nullptr; QueriesAPI = nullptr; TransactionAPI = nullptr; AssetAPI = nullptr; SourceFactory = nullptr; } void UEdModeInteractiveToolsContext::Initialize(IToolsContextQueriesAPI* QueriesAPIIn, IToolsContextTransactionsAPI* TransactionsAPIIn) { UInteractiveToolsContext::Initialize(QueriesAPIIn, TransactionsAPIIn); BeginPIEDelegateHandle = FEditorDelegates::BeginPIE.AddLambda([this](bool bSimulating) { TerminateActiveToolsOnPIEStart(); }); PreSaveWorldDelegateHandle = FEditorDelegates::PreSaveWorld.AddLambda([this](uint32 SaveFlags, UWorld* World) { TerminateActiveToolsOnSaveWorld(); }); bInvalidationPending = false; } void UEdModeInteractiveToolsContext::Shutdown() { FEditorDelegates::BeginPIE.Remove(BeginPIEDelegateHandle); FEditorDelegates::PreSaveWorld.Remove(PreSaveWorldDelegateHandle); UInteractiveToolsContext::Shutdown(); } void UEdModeInteractiveToolsContext::InitializeContextFromEdMode(FEdMode* EditorModeIn) { this->EditorMode = EditorModeIn; this->TransactionAPI = new FEdModeToolsContextTransactionImpl(this, EditorModeIn); this->QueriesAPI = new FEdModeToolsContextQueriesImpl(this, EditorModeIn); this->AssetAPI = new FEditorToolAssetAPI(); this->SourceFactory = new FEditorComponentSourceFactory(); Initialize(QueriesAPI, TransactionAPI); // 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 = LoadObject(nullptr, TEXT("/Game/Materials/VertexColor")); } void UEdModeInteractiveToolsContext::ShutdownContext() { Shutdown(); if (QueriesAPI != nullptr) { delete QueriesAPI; QueriesAPI = nullptr; } if (TransactionAPI != nullptr) { delete TransactionAPI; TransactionAPI = nullptr; } if (AssetAPI != nullptr) { delete AssetAPI; AssetAPI = nullptr; } if (SourceFactory != nullptr) { delete SourceFactory; SourceFactory = nullptr; } this->EditorMode = nullptr; } void UEdModeInteractiveToolsContext::TerminateActiveToolsOnPIEStart() { if (ToolManager->HasActiveTool(EToolSide::Left)) { EToolShutdownType ShutdownType = ToolManager->CanAcceptActiveTool(EToolSide::Left) ? EToolShutdownType::Accept : EToolShutdownType::Cancel; ToolManager->DeactivateTool(EToolSide::Left, ShutdownType); } if (ToolManager->HasActiveTool(EToolSide::Right)) { EToolShutdownType ShutdownType = ToolManager->CanAcceptActiveTool(EToolSide::Right) ? EToolShutdownType::Accept : EToolShutdownType::Cancel; ToolManager->DeactivateTool(EToolSide::Right, ShutdownType); } } void UEdModeInteractiveToolsContext::TerminateActiveToolsOnSaveWorld() { if (ToolManager->HasActiveTool(EToolSide::Left)) { EToolShutdownType ShutdownType = ToolManager->CanAcceptActiveTool(EToolSide::Left) ? EToolShutdownType::Accept : EToolShutdownType::Cancel; ToolManager->DeactivateTool(EToolSide::Left, ShutdownType); } if (ToolManager->HasActiveTool(EToolSide::Right)) { EToolShutdownType ShutdownType = ToolManager->CanAcceptActiveTool(EToolSide::Right) ? EToolShutdownType::Accept : EToolShutdownType::Cancel; ToolManager->DeactivateTool(EToolSide::Right, ShutdownType); } } void UEdModeInteractiveToolsContext::PostInvalidation() { bInvalidationPending = true; } void UEdModeInteractiveToolsContext::Tick(FEditorViewportClient* ViewportClient, float DeltaTime) { ToolManager->Tick(DeltaTime); GizmoManager->Tick(DeltaTime); if (bInvalidationPending) { ViewportClient->Invalidate(); bInvalidationPending = false; } // save this view // Check against GCurrentLevelEditingViewportClient is temporary and should be removed in future. // Current issue is that this ::Tick() is called *per viewport*, so once for each view in a 4-up view. if (ViewportClient == GCurrentLevelEditingViewportClient) { ((FEdModeToolsContextQueriesImpl*)this->QueriesAPI)->CacheCurrentViewState(ViewportClient); } } class TempRenderContext : public IToolsContextRenderAPI { public: FPrimitiveDrawInterface* PDI; virtual FPrimitiveDrawInterface* GetPrimitiveDrawInterface() override { return PDI; } }; void UEdModeInteractiveToolsContext::Render(const FSceneView* View, FViewport* Viewport, FPrimitiveDrawInterface* PDI) { TempRenderContext RenderContext; RenderContext.PDI = PDI; ToolManager->Render(&RenderContext); GizmoManager->Render(&RenderContext); } bool UEdModeInteractiveToolsContext::InputKey(FEditorViewportClient* ViewportClient, FViewport* Viewport, FKey Key, EInputEvent Event) { #ifdef ENABLE_DEBUG_PRINTING if (Event == IE_Pressed) { UE_LOG(LogTemp, Warning, TEXT("PRESSED EVENT")); } else if (Event == IE_Released) { UE_LOG(LogTemp, Warning, TEXT("RELEASED EVENT")); } else if (Event == IE_Repeat) { UE_LOG(LogTemp, Warning, TEXT("REPEAT EVENT")); } else if (Event == IE_Axis) { UE_LOG(LogTemp, Warning, TEXT("AXIS EVENT")); } else if (Event == IE_DoubleClick) { UE_LOG(LogTemp, Warning, TEXT("DOUBLECLICK EVENT")); } #endif bool bHandled = false; // escape key cancels current tool if (Key == EKeys::Escape && Event == IE_Released ) { if (ToolManager->HasAnyActiveTool()) { if (ToolManager->HasActiveTool(EToolSide::Mouse)) { ToolManager->DeactivateTool(EToolSide::Mouse, EToolShutdownType::Cancel); } return true; } } // enter key accepts current tool, or ends tool if it does not have accept state if (Key == EKeys::Enter && Event == IE_Released && ToolManager->HasAnyActiveTool()) { if (ToolManager->HasActiveTool(EToolSide::Mouse)) { if (ToolManager->GetActiveTool(EToolSide::Mouse)->HasAccept()) { if (ToolManager->CanAcceptActiveTool(EToolSide::Mouse)) { ToolManager->DeactivateTool(EToolSide::Mouse, EToolShutdownType::Accept); return true; } } else { ToolManager->DeactivateTool(EToolSide::Mouse, EToolShutdownType::Completed); return true; } } } // if alt is down we do not process mouse event if (ViewportClient->IsAltPressed()) { return false; } if (Event == IE_Pressed || Event == IE_Released) { if (Key.IsMouseButton()) { bool bIsLeftMouse = (Key == EKeys::LeftMouseButton); bool bIsMiddleMouse = (Key == EKeys::MiddleMouseButton); bool bIsRightMouse = (Key == EKeys::RightMouseButton); if (bIsLeftMouse || bIsMiddleMouse || bIsRightMouse) { // early-out here if we are going to do camera manipulation if (ViewportClient->IsAltPressed()) { return bHandled; } FInputDeviceState InputState = CurrentMouseState; InputState.InputDevice = EInputDevices::Mouse; InputState.SetModifierKeyStates( ViewportClient->IsShiftPressed(), ViewportClient->IsAltPressed(), ViewportClient->IsCtrlPressed(), ViewportClient->IsCmdPressed()); if (bIsLeftMouse) { InputState.Mouse.Left.SetStates( (Event == IE_Pressed), (Event == IE_Pressed), (Event == IE_Released)); CurrentMouseState.Mouse.Left.bDown = (Event == IE_Pressed); } else if (bIsMiddleMouse) { InputState.Mouse.Middle.SetStates( (Event == IE_Pressed), (Event == IE_Pressed), (Event == IE_Released)); CurrentMouseState.Mouse.Middle.bDown = (Event == IE_Pressed); } else { InputState.Mouse.Right.SetStates( (Event == IE_Pressed), (Event == IE_Pressed), (Event == IE_Released)); CurrentMouseState.Mouse.Right.bDown = (Event == IE_Pressed); } InputRouter->PostInputEvent(InputState); if (InputRouter->HasActiveMouseCapture()) { // what is this about? MeshPaintMode has it... ViewportClient->bLockFlightCamera = true; bHandled = true; // indicate that we handled this event, // which will disable camera movement/etc ? } else { //ViewportClient->bLockFlightCamera = false; } } } else if (Key.IsGamepadKey()) { // not supported yet } else if (Key.IsTouch()) { // not supported yet } else if (Key.IsFloatAxis() || Key.IsVectorAxis()) { // not supported yet } else // is this definitely a keyboard key? { FInputDeviceState InputState; InputState.InputDevice = EInputDevices::Keyboard; InputState.SetModifierKeyStates( ViewportClient->IsShiftPressed(), ViewportClient->IsAltPressed(), ViewportClient->IsCtrlPressed(), ViewportClient->IsCmdPressed()); InputState.Keyboard.ActiveKey.Button = Key; bool bPressed = (Event == IE_Pressed); InputState.Keyboard.ActiveKey.SetStates(bPressed, bPressed, !bPressed); InputRouter->PostInputEvent(InputState); } } return bHandled; } bool UEdModeInteractiveToolsContext::MouseEnter(FEditorViewportClient* ViewportClient, FViewport* Viewport, int32 x, int32 y) { #ifdef ENABLE_DEBUG_PRINTING UE_LOG(LogTemp, Warning, TEXT("MOUSE ENTER")); #endif CurrentMouseState.Mouse.Position2D = FVector2D(x, y); CurrentMouseState.Mouse.WorldRay = GetRayFromMousePos(ViewportClient, Viewport, x, y); return false; } bool UEdModeInteractiveToolsContext::MouseMove(FEditorViewportClient* ViewportClient, FViewport* Viewport, int32 x, int32 y) { #ifdef ENABLE_DEBUG_PRINTING //UE_LOG(LogTemp, Warning, TEXT("MOUSE MOVE")); #endif CurrentMouseState.Mouse.Position2D = FVector2D(x, y); CurrentMouseState.Mouse.WorldRay = GetRayFromMousePos(ViewportClient, Viewport, x, y); FInputDeviceState InputState = CurrentMouseState; InputState.InputDevice = EInputDevices::Mouse; InputState.SetModifierKeyStates( ViewportClient->IsShiftPressed(), ViewportClient->IsAltPressed(), ViewportClient->IsCtrlPressed(), ViewportClient->IsCmdPressed()); if (InputRouter->HasActiveMouseCapture()) { // 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 UEdModeInteractiveToolsContext::MouseLeave(FEditorViewportClient* ViewportClient, FViewport* Viewport) { #ifdef ENABLE_DEBUG_PRINTING UE_LOG(LogTemp, Warning, TEXT("MOUSE LEAVE")); #endif return false; } bool UEdModeInteractiveToolsContext::StartTracking(FEditorViewportClient* InViewportClient, FViewport* InViewport) { return false; } bool UEdModeInteractiveToolsContext::CapturedMouseMove(FEditorViewportClient* InViewportClient, FViewport* InViewport, int32 InMouseX, int32 InMouseY) { #ifdef ENABLE_DEBUG_PRINTING //UE_LOG(LogTemp, Warning, TEXT("CAPTURED MOUSE MOVE")); #endif // if alt is down we will not allow client to see this event if (InViewportClient->IsAltPressed()) { return false; } FVector2D OldPosition = CurrentMouseState.Mouse.Position2D; CurrentMouseState.Mouse.Position2D = FVector2D(InMouseX, InMouseY); CurrentMouseState.Mouse.WorldRay = GetRayFromMousePos(InViewportClient, InViewport, InMouseX, InMouseY); if (InputRouter->HasActiveMouseCapture()) { FInputDeviceState InputState = CurrentMouseState; InputState.InputDevice = EInputDevices::Mouse; InputState.SetModifierKeyStates( InViewportClient->IsShiftPressed(), InViewportClient->IsAltPressed(), InViewportClient->IsCtrlPressed(), InViewportClient->IsCmdPressed()); InputState.Mouse.Delta2D = CurrentMouseState.Mouse.Position2D - OldPosition; InputRouter->PostInputEvent(InputState); return true; } return false; } bool UEdModeInteractiveToolsContext::EndTracking(FEditorViewportClient* InViewportClient, FViewport* InViewport) { #ifdef ENABLE_DEBUG_PRINTING UE_LOG(LogTemp, Warning, TEXT("END TRACKING")); #endif return true; } FRay UEdModeInteractiveToolsContext::GetRayFromMousePos(FEditorViewportClient* ViewportClient, FViewport* Viewport, int MouseX, int MouseY) { FSceneViewFamilyContext ViewFamily(FSceneViewFamily::ConstructionValues( ViewportClient->Viewport, ViewportClient->GetScene(), ViewportClient->EngineShowFlags) .SetRealtimeUpdate(ViewportClient->IsRealtime())); FSceneView* View = ViewportClient->CalcSceneView(&ViewFamily); FViewportCursorLocation MouseViewportRay(View, (FEditorViewportClient*)Viewport->GetClient(), MouseX, MouseY); return FRay(MouseViewportRay.GetOrigin(), MouseViewportRay.GetDirection(), true); } bool UEdModeInteractiveToolsContext::CanStartTool(const FString& ToolTypeIdentifier) const { return (ToolManager->HasActiveTool(EToolSide::Mouse) == false) && (ToolManager->CanActivateTool(EToolSide::Mouse, ToolTypeIdentifier) == true); } bool UEdModeInteractiveToolsContext::ActiveToolHasAccept() const { return ToolManager->HasActiveTool(EToolSide::Mouse) && ToolManager->GetActiveTool(EToolSide::Mouse)->HasAccept(); } bool UEdModeInteractiveToolsContext::CanAcceptActiveTool() const { return ToolManager->CanAcceptActiveTool(EToolSide::Mouse); } bool UEdModeInteractiveToolsContext::CanCancelActiveTool() const { return ToolManager->CanCancelActiveTool(EToolSide::Mouse); } bool UEdModeInteractiveToolsContext::CanCompleteActiveTool() const { return ToolManager->HasActiveTool(EToolSide::Mouse) && CanCancelActiveTool() == false; } void UEdModeInteractiveToolsContext::StartTool(const FString& ToolTypeIdentifier) { if (ToolManager->SelectActiveToolType(EToolSide::Mouse, ToolTypeIdentifier) == false) { UE_LOG(LogTemp, Warning, TEXT("ToolManager: Unknown Tool Type %s"), *ToolTypeIdentifier); } else { ToolManager->ActivateTool(EToolSide::Mouse); } } void UEdModeInteractiveToolsContext::EndTool(EToolShutdownType ShutdownType) { ToolManager->DeactivateTool(EToolSide::Mouse, ShutdownType); }