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

2141 lines
60 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "EditorModeManager.h"
#include "Components/StaticMeshComponent.h"
#include "Engine/Selection.h"
#include "Misc/MessageDialog.h"
#include "Editor/EditorPerProjectUserSettings.h"
#include "Misc/ConfigCacheIni.h"
#include "GameFramework/WorldSettings.h"
#include "LevelEditorViewport.h"
#include "EditorModeRegistry.h"
#include "EditorModes.h"
#include "EditorSupportDelegates.h"
#include "EdMode.h"
#include "Rendering/PositionVertexBuffer.h"
#include "SceneView.h"
#include "StaticMeshResources.h"
#include "Toolkits/IToolkitHost.h"
#include "Framework/Application/SlateApplication.h"
#include "Framework/Notifications/NotificationManager.h"
#include "Widgets/Notifications/SNotificationList.h"
#include "Editor/EditorEngine.h"
#include "Editor/UnrealEdEngine.h"
#include "Framework/Commands/UICommandList.h"
#include "Toolkits/BaseToolkit.h"
#include "Subsystems/AssetEditorSubsystem.h"
#include "Subsystems/BrushEditingSubsystem.h"
#include "Tools/UEdMode.h"
#include "InputRouter.h"
#include "Tools/EdModeInteractiveToolsContext.h"
#include "Tools/LegacyEdModeInterfaces.h"
#include "CanvasTypes.h"
#include "CanvasItem.h"
#include "Engine/StaticMeshActor.h"
#include "Engine/Texture2D.h"
#include "EngineUtils.h"
#include "Tools/AssetEditorContextObject.h"
#include "ContextObjectStore.h"
#include "EditorInteractiveGizmoManager.h"
#include "GizmoEdModeInterface.h"
#include "UObject/GCObjectScopeGuard.h"
#include "Settings/LevelEditorViewportSettings.h"
#include "Subsystems/EditorElementSubsystem.h"
#include "Elements/Interfaces/TypedElementWorldInterface.h"
#include "TextureResource.h"
#include "EditorGizmos/EditorGizmoStateTarget.h"
#include "EditorGizmos/EditorTransformGizmoUtil.h"
#include "EditorGizmos/GizmoRotationUtil.h"
#include "Toolkits/ToolkitManager.h"
/*------------------------------------------------------------------------------
FEditorModeTools.
The master class that handles tracking of the current mode.
------------------------------------------------------------------------------*/
TAutoConsoleVariable<int32> FEditorModeTools::CVarEnableITFCursorOverrideSupport(
TEXT("Editor.EnableITFCursorOverrideSupport"),
0,
TEXT("Enable support for the EditorModeManager to query the InteractiveToolsContext for tool customized cursor overrides."));
FEditorModeTools::FEditorModeTools()
: PivotShown(false)
, Snapping(false)
, SnappedActor(false)
, CachedLocation(ForceInitToZero)
, PivotLocation(ForceInitToZero)
, SnappedLocation(ForceInitToZero)
, GridBase(ForceInitToZero)
, TranslateRotateXAxisAngle(0.0f)
, TranslateRotate2DAngle(0.0f)
, DefaultModeIDs()
, WidgetMode(UE::Widget::WM_None)
, OverrideWidgetMode(UE::Widget::WM_None)
, bShowWidget(true)
, bHideViewportUI(false)
, bSelectionHasSceneComponent(false)
, WidgetScale(1.0f)
, CoordSystem(COORD_World)
, bIsTracking(false)
{
DefaultModeIDs.Add( FBuiltinEditorModes::EM_Default );
InteractiveToolsContext = NewObject<UModeManagerInteractiveToolsContext>(GetTransientPackage(), UModeManagerInteractiveToolsContext::StaticClass(), NAME_None, RF_Transient);
InteractiveToolsContext->InitializeContextWithEditorModeManager(this);
InteractiveToolsContext->Activate();
// Load the last used settings
LoadConfig();
// Register our callback for actor selection changes
USelection::SelectNoneEvent.AddRaw(this, &FEditorModeTools::OnEditorSelectNone);
USelection::SelectionChangedEvent.AddRaw(this, &FEditorModeTools::OnEditorSelectionChanged);
USelection::SelectObjectEvent.AddRaw(this, &FEditorModeTools::OnEditorSelectionChanged);
if( GEditor )
{
// Register our callback for undo/redo
GEditor->RegisterForUndo(this);
// This binding ensures the mode is destroyed if the type is unregistered outside of normal shutdown process
GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->OnEditorModeUnregistered().AddRaw(this, &FEditorModeTools::OnModeUnregistered);
}
UE::EditorTransformGizmoUtil::RegisterTransformGizmoContextObject(this);
FWorldDelegates::OnWorldCleanup.AddRaw(this, &FEditorModeTools::OnWorldCleanup);
}
FEditorModeTools::~FEditorModeTools()
{
SetDefaultMode(FBuiltinEditorModes::EM_Default);
DeactivateAllModes();
RemoveAllDelegateHandlers();
ExitAllModesPendingDeactivate();
RecycledScriptableModes.Empty();
// We may be destroyed after the UObject system has already shutdown,
// which would mean that this instances will be garbage
if (UObjectInitialized())
{
UE::EditorTransformGizmoUtil::UnregisterTransformGizmoContextObject(this);
InteractiveToolsContext->Deactivate();
InteractiveToolsContext->ShutdownContext();
InteractiveToolsContext = nullptr;
WeakGizmoStateTarget = nullptr;
WeakGizmoContext = nullptr;
}
}
void FEditorModeTools::LoadConfig(void)
{
GConfig->GetBool(TEXT("FEditorModeTools"),TEXT("ShowWidget"),bShowWidget,
GEditorPerProjectIni);
static constexpr bool bGetRawValue = true;
int32 CoordSystemAsInt = (int32)GetCoordSystem(bGetRawValue);
GConfig->GetInt(TEXT("FEditorModeTools"),TEXT("CoordSystem"), CoordSystemAsInt,
GEditorPerProjectIni);
const ECoordSystem CoordinateSystem = static_cast<ECoordSystem>(CoordSystemAsInt);
if (CoordinateSystem == COORD_Parent || CoordinateSystem == COORD_Explicit)
{
// parent mode is only supported with new trs gizmos for now
const bool bUseNewGizmo = UEditorInteractiveGizmoManager::UsesNewTRSGizmos();
if (!bUseNewGizmo)
{
CoordSystemAsInt = static_cast<int32>(COORD_Local);
}
}
SetCoordSystem((ECoordSystem)CoordSystemAsInt);
LoadWidgetSettings();
}
void FEditorModeTools::SaveConfig(void)
{
GConfig->SetBool(TEXT("FEditorModeTools"), TEXT("ShowWidget"), bShowWidget, GEditorPerProjectIni);
static constexpr bool bGetRawValue = true;
GConfig->SetInt(TEXT("FEditorModeTools"), TEXT("CoordSystem"), (int32)GetCoordSystem(bGetRawValue), GEditorPerProjectIni);
SaveWidgetSettings();
}
TSharedPtr<class IToolkitHost> FEditorModeTools::GetToolkitHost() const
{
TSharedPtr<class IToolkitHost> Result = ToolkitHost.Pin();
check(ToolkitHost.IsValid());
return Result;
}
bool FEditorModeTools::HasToolkitHost() const
{
return ToolkitHost.Pin().IsValid();
}
void FEditorModeTools::SetToolkitHost(TSharedRef<class IToolkitHost> InHost)
{
checkf(!ToolkitHost.IsValid(), TEXT("SetToolkitHost can only be called once"));
ToolkitHost = InHost;
if (HasToolkitHost())
{
UAssetEditorContextObject* AssetEditorContextObject = NewObject<UAssetEditorContextObject>(InteractiveToolsContext->ToolManager);
AssetEditorContextObject->SetToolkitHost(GetToolkitHost().Get());
InteractiveToolsContext->ContextObjectStore->AddContextObject(AssetEditorContextObject);
}
}
USelection* FEditorModeTools::GetSelectedActors() const
{
return GEditor->GetSelectedActors();
}
USelection* FEditorModeTools::GetSelectedObjects() const
{
return GEditor->GetSelectedObjects();
}
USelection* FEditorModeTools::GetSelectedComponents() const
{
return GEditor->GetSelectedComponents();
}
UTypedElementSelectionSet* FEditorModeTools::GetEditorSelectionSet() const
{
if (USelection* SelectedActorsSet = GetSelectedActors())
{
return SelectedActorsSet->GetElementSelectionSet();
}
return nullptr;
}
void FEditorModeTools::StoreSelection(FName SelectionStoreKey, bool bClearSelection)
{
if (UTypedElementSelectionSet* SelectionSet = GetEditorSelectionSet())
{
StoredSelectionSets.Emplace(SelectionStoreKey, SelectionSet->GetCurrentSelectionState());
if (bClearSelection)
{
SelectionSet->ClearSelection(FTypedElementSelectionOptions().SetAllowHidden(true));
}
}
}
void FEditorModeTools::RestoreSelection(FName SelectionStoreKey)
{
if (UTypedElementSelectionSet* SelectionSet = GetEditorSelectionSet())
{
if (FTypedElementSelectionSetState* StoredState = StoredSelectionSets.Find(SelectionStoreKey))
{
SelectionSet->RestoreSelectionState(*StoredState);
}
}
}
UWorld* FEditorModeTools::GetWorld() const
{
// When in 'Simulate' mode, the editor mode tools will actually interact with the PIE world
if( GEditor->bIsSimulatingInEditor && GEditor->GetPIEWorldContext() )
{
return GEditor->GetPIEWorldContext()->World();
}
else
{
return GEditor->GetEditorWorldContext().World();
}
}
FEditorViewportClient* FEditorModeTools::GetHoveredViewportClient() const
{
// Note: as per the comment in MouseLeave, this currently acts as LastHoveredViewportClient.
return HoveredViewportClient;
}
FEditorViewportClient* FEditorModeTools::GetFocusedViewportClient() const
{
// Note: as per the comment in LostFocus, this actually currently acts as LastFocusedViewportClient.
return FocusedViewportClient;
}
bool FEditorModeTools::SelectionHasSceneComponent() const
{
return bSelectionHasSceneComponent;
}
void FEditorModeTools::SetSelectionHasSceneComponent(bool bHasSceneComponent)
{
bSelectionHasSceneComponent = bHasSceneComponent;
}
bool FEditorModeTools::IsEditingDisallowed(AActor* InActor) const
{
for (const UEdMode* Mode : ActiveScriptableModes)
{
if (Mode->IsEditingDisallowed(InActor))
{
return true;
}
}
return false;
}
bool FEditorModeTools::IsSelectionAllowed(AActor* InActor, const bool bInSelected) const
{
bool bSelectionAllowed = (ActiveScriptableModes.Num() == 0);
for (const UEdMode* Mode : ActiveScriptableModes)
{
// Exclusive ability for a mode to disable selection
if (Mode->IsSelectionDisallowed(InActor, bInSelected))
{
return false;
}
bSelectionAllowed |= Mode->IsSelectionAllowed(InActor, bInSelected);
}
return bSelectionAllowed;
}
bool FEditorModeTools::IsSelectionHandled(AActor* InActor, const bool bInSelected) const
{
bool bSelectionHandled = false;
ForEachEdMode([&bSelectionHandled, bInSelected, InActor](UEdMode* Mode)
{
bSelectionHandled |= Mode->Select(InActor, bInSelected);
return true;
});
return bSelectionHandled;
}
bool FEditorModeTools::ProcessEditDuplicate()
{
bool bHandled = false;
ForEachEdMode([&bHandled](UEdMode* Mode)
{
bHandled |= Mode->ProcessEditDuplicate();
return true;
});
return bHandled;
}
bool FEditorModeTools::ProcessEditDelete()
{
bool bHandled = InteractiveToolsContext->ProcessEditDelete();
ForEachEdMode([&bHandled](UEdMode* Mode)
{
bHandled |= Mode->ProcessEditDelete();
return true;
});
return bHandled;
}
bool FEditorModeTools::ProcessEditCut()
{
bool bHandled = false;
ForEachEdMode([&bHandled](UEdMode* Mode)
{
bHandled = Mode->ProcessEditCut();
return !bHandled;
});
return bHandled;
}
bool FEditorModeTools::ProcessEditCopy()
{
bool bHandled = false;
ForEachEdMode([&bHandled](UEdMode* Mode)
{
bHandled = Mode->ProcessEditCopy();
return !bHandled;
});
return bHandled;
}
bool FEditorModeTools::ProcessEditPaste()
{
bool bHandled = false;
ForEachEdMode([&bHandled](UEdMode* Mode)
{
bHandled = Mode->ProcessEditPaste();
return !bHandled;
});
return bHandled;
}
EEditAction::Type FEditorModeTools::GetActionEditDuplicate()
{
EEditAction::Type ReturnedAction = EEditAction::Skip;
ForEachEdMode([&ReturnedAction](UEdMode* Mode)
{
const EEditAction::Type EditAction = Mode->GetActionEditDuplicate();
if (EditAction == EEditAction::Process || EditAction == EEditAction::Halt)
{
ReturnedAction = EditAction;
return false;
}
return true;
});
return ReturnedAction;
}
EEditAction::Type FEditorModeTools::GetActionEditDelete()
{
EEditAction::Type ReturnedAction = EEditAction::Skip;
ForEachEdMode([&ReturnedAction](UEdMode* Mode)
{
const EEditAction::Type EditAction = Mode->GetActionEditDelete();
if (EditAction == EEditAction::Process || EditAction == EEditAction::Halt)
{
ReturnedAction = EditAction;
return false;
}
return true;
});
return ReturnedAction;
}
EEditAction::Type FEditorModeTools::GetActionEditCut()
{
EEditAction::Type ReturnedAction = EEditAction::Skip;
ForEachEdMode([&ReturnedAction](UEdMode* Mode)
{
const EEditAction::Type EditAction = Mode->GetActionEditCut();
if (EditAction == EEditAction::Process || EditAction == EEditAction::Halt)
{
ReturnedAction = EditAction;
return false;
}
return true;
});
return ReturnedAction;
}
EEditAction::Type FEditorModeTools::GetActionEditCopy()
{
EEditAction::Type ReturnedAction = EEditAction::Skip;
ForEachEdMode([&ReturnedAction](UEdMode* Mode)
{
const EEditAction::Type EditAction = Mode->GetActionEditCopy();
if (EditAction == EEditAction::Process || EditAction == EEditAction::Halt)
{
ReturnedAction = EditAction;
return false;
}
return true;
});
return ReturnedAction;
}
EEditAction::Type FEditorModeTools::GetActionEditPaste()
{
EEditAction::Type ReturnedAction = EEditAction::Skip;
ForEachEdMode([&ReturnedAction](UEdMode* Mode)
{
const EEditAction::Type EditAction = Mode->GetActionEditPaste();
if (EditAction == EEditAction::Process || EditAction == EEditAction::Halt)
{
ReturnedAction = EditAction;
return false;
}
return true;
});
return ReturnedAction;
}
void FEditorModeTools::DeactivateOtherVisibleModes(FEditorModeID InMode)
{
ForEachEdMode([this, InMode](UEdMode* Mode)
{
if (Mode->GetID() != InMode && Mode->GetModeInfo().IsVisible())
{
DeactivateMode(Mode->GetID());
}
return true;
});
}
bool FEditorModeTools::IsSnapRotationEnabled() const
{
bool bRetVal = false;
ForEachEdMode([&bRetVal](UEdMode* Mode)
{
bRetVal = Mode->IsSnapRotationEnabled();
return !bRetVal;
});
return bRetVal;
}
bool FEditorModeTools::SnapRotatorToGridOverride(FRotator& InRotation) const
{
bool bRetVal = false;
ForEachEdMode([&bRetVal, &InRotation](UEdMode* Mode)
{
bRetVal = Mode->SnapRotatorToGridOverride(InRotation);
return !bRetVal;
});
return bRetVal;
}
void FEditorModeTools::ActorsDuplicatedNotify(TArray<AActor*>& InPreDuplicateSelection, TArray<AActor*>& InPostDuplicateSelection, const bool bOffsetLocations)
{
ForEachEdMode([&InPreDuplicateSelection, &InPostDuplicateSelection, bOffsetLocations](UEdMode* Mode)
{
// Tell the tools about the duplication
Mode->ActorsDuplicatedNotify(InPreDuplicateSelection, InPostDuplicateSelection, bOffsetLocations);
return true;
});
}
void FEditorModeTools::ActorMoveNotify()
{
ForEachEdMode([](UEdMode* Mode)
{
// Also notify the current editing modes if they are interested.
Mode->ActorMoveNotify();
return true;
});
}
void FEditorModeTools::ActorSelectionChangeNotify()
{
ForEachEdMode([](UEdMode* Mode)
{
Mode->ActorSelectionChangeNotify();
return true;
});
}
void FEditorModeTools::ElementSelectionChangeNotify()
{
ForEachEdMode([](UEdMode* Mode)
{
Mode->ElementSelectionChangeNotify();
return true;
});
}
void FEditorModeTools::ActorPropChangeNotify()
{
ForEachEdMode([](UEdMode* Mode)
{
Mode->ActorPropChangeNotify();
return true;
});
}
void FEditorModeTools::UpdateInternalData()
{
ForEachEdMode([](UEdMode* Mode)
{
Mode->UpdateInternalData();
return true;
});
}
bool FEditorModeTools::IsOnlyVisibleActiveMode(FEditorModeID InMode) const
{
// Only return true if this is the *only* active mode
bool bFoundAnotherVisibleMode = false;
ForEachEdMode([&bFoundAnotherVisibleMode, InMode](UEdMode* Mode)
{
bFoundAnotherVisibleMode = (Mode->GetModeInfo().IsVisible() && Mode->GetID() != InMode);
return !bFoundAnotherVisibleMode;
});
return !bFoundAnotherVisibleMode;
}
bool FEditorModeTools::IsOnlyActiveMode(FEditorModeID InMode) const
{
return ActiveScriptableModes.Num() == 1 && ActiveScriptableModes[0]->GetID() == InMode;
}
void FEditorModeTools::OnEditorSelectionChanged(UObject* NewSelection)
{
if (NewSelection == GetSelectedActors())
{
// when actors are selected check if there is at least one component selected and cache that off
// Editor modes use this primarily to determine of transform gizmos should be drawn.
// Performing this check each frame with lots of actors is expensive so only do this when selection changes
bSelectionHasSceneComponent = false;
for(FSelectionIterator It(*GetSelectedActors()); It; ++It)
{
AActor* Actor = Cast<AActor>(*It);
if(Actor != nullptr && Actor->FindComponentByClass<USceneComponent>() != nullptr)
{
bSelectionHasSceneComponent = true;
break;
}
}
}
else
{
// If selecting an actor, move the pivot location.
AActor* Actor = Cast<AActor>(NewSelection);
if(Actor != nullptr)
{
if(Actor->IsSelected())
{
SetPivotLocation(Actor->GetActorLocation(), false);
// If this actor wasn't part of the original selection set during pie/sie, clear it now
if(GEditor->ActorsThatWereSelected.Num() > 0)
{
AActor* EditorActor = EditorUtilities::GetEditorWorldCounterpartActor(Actor);
if(!EditorActor || !GEditor->ActorsThatWereSelected.Contains(EditorActor))
{
GEditor->ActorsThatWereSelected.Empty();
}
}
}
else if(GEditor->ActorsThatWereSelected.Num() > 0)
{
// Clear the selection set
GEditor->ActorsThatWereSelected.Empty();
}
}
}
for(const auto& Pair : FEditorModeRegistry::Get().GetFactoryMap())
{
Pair.Value->OnSelectionChanged(*this, NewSelection);
}
}
void FEditorModeTools::OnEditorSelectNone()
{
GEditor->SelectNone( false, true );
GEditor->ActorsThatWereSelected.Empty();
}
void FEditorModeTools::DrawBrackets(FEditorViewportClient* ViewportClient, FViewport* Viewport, const FSceneView* View, FCanvas* Canvas)
{
if (!ViewportClient->IsPerspective() || !GetDefault<ULevelEditorViewportSettings>()->bHighlightWithBrackets)
{
return;
}
if (UTypedElementSelectionSet* CurrentSelection = GetEditorSelectionSet())
{
CurrentSelection->ForEachSelectedObject<AActor>([Canvas, View, Viewport, ViewportClient](AActor* Actor)
{
const FLinearColor SelectedActorBoxColor(0.6f, 0.6f, 1.0f);
const bool bDrawBracket = Actor->IsA<AStaticMeshActor>();
ViewportClient->DrawActorScreenSpaceBoundingBox(Canvas, View, Viewport, Actor, SelectedActorBoxColor, bDrawBracket);
return true;
});
}
}
void FEditorModeTools::ForEachEdMode(TFunctionRef<bool(UEdMode*)> InCalllback) const
{
// Copy Array in case callback deactivates a mode
auto ActiveModes = ActiveScriptableModes;
for (UEdMode* Mode : ActiveModes)
{
if (Mode)
{
if (!InCalllback(Mode))
{
break;
}
}
}
}
bool FEditorModeTools::TestAllModes(TFunctionRef<bool(UEdMode*)> InCalllback, bool bExpected) const
{
for (UEdMode* Mode : ActiveScriptableModes)
{
if (Mode)
{
if (InCalllback(Mode) != bExpected)
{
return false;
}
}
}
return true;
}
void FEditorModeTools::ExitAllModesPendingDeactivate()
{
bIsExitingModesDuringTick = true;
// Make a copy so we can modify the pending deactivate modes map during ExitMode
TMap<FEditorModeID, UEdMode*> PendingDeactivateModesCopy(ObjectPtrDecay(PendingDeactivateModes));
for (auto& Pair : PendingDeactivateModesCopy)
{
ExitMode(Pair.Value);
}
bIsExitingModesDuringTick = false;
check(PendingDeactivateModes.Num() == 0);
}
void FEditorModeTools::SetPivotLocation( const FVector& Location, const bool bIncGridBase )
{
CachedLocation = PivotLocation = SnappedLocation = Location;
if ( bIncGridBase )
{
GridBase = Location;
}
}
ECoordSystem FEditorModeTools::GetCoordSystem(bool bGetRawValue) const
{
if (!bGetRawValue && (GetWidgetMode() == UE::Widget::WM_Scale))
{
return COORD_Local;
}
return CoordSystem;
}
void FEditorModeTools::SetCoordSystem(ECoordSystem NewCoordSystem)
{
CoordSystem = NewCoordSystem;
BroadcastCoordSystemChanged(NewCoordSystem);
}
void FEditorModeTools::SetDefaultMode( const FEditorModeID DefaultModeID )
{
DefaultModeIDs.Reset();
DefaultModeIDs.Add( DefaultModeID );
}
void FEditorModeTools::AddDefaultMode( const FEditorModeID DefaultModeID )
{
DefaultModeIDs.AddUnique( DefaultModeID );
}
void FEditorModeTools::RemoveDefaultMode( const FEditorModeID DefaultModeID )
{
DefaultModeIDs.RemoveSingle( DefaultModeID );
}
void FEditorModeTools::ActivateDefaultMode()
{
// NOTE: Activating EM_Default will cause ALL default editor modes to be activated (handled specially in ActivateMode())
ActivateMode( FBuiltinEditorModes::EM_Default );
}
void FEditorModeTools::ExitMode(UEdMode* InMode)
{
if (InMode)
{
InMode->Exit();
const FEditorModeID EditorModeID = InMode->GetID();
PendingDeactivateModes.Remove(EditorModeID);
RecycledScriptableModes.Add(EditorModeID, InMode);
}
}
void FEditorModeTools::OnModeUnregistered(FEditorModeID ModeID)
{
DestroyMode(ModeID);
}
void FEditorModeTools::OnWorldCleanup(UWorld* InWorld, bool bSessionEnded, bool bCleanupResources)
{
UWorld* World = GetWorld();
if (InWorld == World)
{
ExitAllModesPendingDeactivate();
}
}
void FEditorModeTools::RemoveAllDelegateHandlers()
{
if (GEditor)
{
GEditor->UnregisterForUndo(this);
if (UAssetEditorSubsystem* AssetEditorSubsystem = GEditor->GetEditorSubsystem<UAssetEditorSubsystem>())
{
AssetEditorSubsystem->OnEditorModeUnregistered().RemoveAll(this);
}
}
FWorldDelegates::OnWorldCleanup.RemoveAll(this);
// For now, check that UObjects are even valid, because the level editor has a global static mode tools
if (UObjectInitialized())
{
USelection::SelectionChangedEvent.RemoveAll(this);
USelection::SelectNoneEvent.RemoveAll(this);
USelection::SelectObjectEvent.RemoveAll(this);
}
OnEditorModeIDChanged().Clear();
OnWidgetModeChanged().Clear();
OnCoordSystemChanged().Clear();
}
void FEditorModeTools::DeactivateModeAtIndex(int32 Index)
{
UEdMode* Mode = ActiveScriptableModes[Index];
const FEditorModeID ModeID = Mode->GetID();
PendingDeactivateModes.Emplace(ModeID, Mode);
ActiveScriptableModes.RemoveAt(Index);
if (const TSharedPtr<FModeToolkit> Toolkit = Mode->GetToolkit().Pin())
{
FToolkitManager::Get().CloseToolkit(Toolkit.ToSharedRef());
}
constexpr bool bIsEnteringMode = false;
BroadcastEditorModeIDChanged(ModeID, bIsEnteringMode);
}
void FEditorModeTools::DeactivateMode( FEditorModeID InID )
{
// Find the mode from the ID and exit it.
for (int32 Index = ActiveScriptableModes.Num() - 1; Index >= 0; --Index)
{
UEdMode* Mode = ActiveScriptableModes[Index];
if (Mode->GetID() == InID)
{
DeactivateModeAtIndex(Index);
break;
}
}
}
void FEditorModeTools::DeactivateAllModes()
{
TArray<FEditorModeID> EditorModesToRemove;
for (int32 Index = ActiveScriptableModes.Num() - 1; Index >= 0; --Index)
{
UEdMode* Mode = ActiveScriptableModes[Index];
EditorModesToRemove.Add(Mode->GetID());
}
for (FEditorModeID EditorModeToRemove : EditorModesToRemove)
{
DeactivateMode(EditorModeToRemove);
}
}
void FEditorModeTools::DestroyMode( FEditorModeID InID )
{
// Since deactivating the last active mode will cause the default modes to be activated, make sure this mode is removed from defaults.
RemoveDefaultMode( InID );
// Add back the default default mode if we just removed the last valid default.
if ( DefaultModeIDs.Num() == 0 )
{
AddDefaultMode( FBuiltinEditorModes::EM_Default );
}
// Find the mode from the ID and exit it.
DeactivateMode(InID);
if (UEdMode* DeactivatedMode = PendingDeactivateModes.FindRef(InID))
{
ExitMode(DeactivatedMode);
}
RecycledScriptableModes.Remove(InID);
}
bool FEditorModeTools::ShouldShowModeToolbox() const
{
for (const UEdMode* Mode : ActiveScriptableModes)
{
if (Mode->GetModeInfo().IsVisible() && Mode->UsesToolkits())
{
return true;
}
}
return false;
}
void FEditorModeTools::ActivateMode(FEditorModeID InID, bool bToggle)
{
static bool bReentrant = false;
if( !bReentrant )
{
if (InID == FBuiltinEditorModes::EM_Default)
{
bReentrant = true;
for( const FEditorModeID& ModeID : DefaultModeIDs )
{
ActivateMode( ModeID );
}
for( const FEditorModeID& ModeID : DefaultModeIDs )
{
check( IsModeActive( ModeID ) );
}
bReentrant = false;
return;
}
}
// Check to see if the mode is already active
if (IsModeActive(InID))
{
// The mode is already active toggle it off if we should toggle off already active modes.
if (bToggle)
{
DeactivateMode(InID);
}
// Nothing more to do
return;
}
// Recycle a mode or factory a new one
UEdMode* ScriptableMode = RecycledScriptableModes.FindRef(InID);
bool bNeedsEnter = true;
if (!ScriptableMode)
{
ScriptableMode = PendingDeactivateModes.FindRef(InID);
if (ScriptableMode)
{
// If we are actively exiting modes, don't re-activate the mode
if (bIsExitingModesDuringTick)
{
return;
}
bNeedsEnter = false;
}
}
if (!ScriptableMode)
{
ScriptableMode = GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->CreateEditorModeWithToolsOwner(InID, *this);
}
if (!ScriptableMode)
{
UE_LOG(LogEditorModes, Log, TEXT("FEditorModeTools::ActivateMode : Couldn't find mode '%s'."), *InID.ToString());
// Just return and leave the mode list unmodified
return;
}
{
// Make sure ScriptableMode doesn't get GCed while Deactivating modes
FGCObjectScopeGuard ScriptModeGuard(ScriptableMode);
// Remove anything that isn't compatible with this mode
TArray<FEditorModeID> EditorModesToRemove;
const bool bIsVisibleMode = ScriptableMode->GetModeInfo().IsVisible();
for (int32 ModeIndex = ActiveScriptableModes.Num() - 1; ModeIndex >= 0; ModeIndex--)
{
UEdMode* Mode = ActiveScriptableModes[ModeIndex];
const bool bModesAreCompatible = ScriptableMode->IsCompatibleWith(Mode->GetID()) || Mode->IsCompatibleWith(ScriptableMode->GetID());
if (!bModesAreCompatible || (bIsVisibleMode && Mode->GetModeInfo().IsVisible()))
{
EditorModesToRemove.Add(Mode->GetID());
}
}
for (FEditorModeID EditorModeToRemove : EditorModesToRemove)
{
DeactivateMode(EditorModeToRemove);
}
}
ActiveScriptableModes.Add(ScriptableMode);
// Enter the new mode
if (bNeedsEnter)
{
ScriptableMode->Enter();
}
const bool bIsEnteringMode = true;
BroadcastEditorModeIDChanged(InID, bIsEnteringMode);
PendingDeactivateModes.Remove(InID);
RecycledScriptableModes.Remove(InID);
// Update the editor UI
FEditorSupportDelegates::UpdateUI.Broadcast();
}
bool FEditorModeTools::EnsureNotInMode(FEditorModeID ModeID, const FText& ErrorMsg, bool bNotifyUser) const
{
// We're in a 'safe' mode if we're not in the specified mode.
const bool bInASafeMode = !IsModeActive(ModeID);
if( !bInASafeMode && !ErrorMsg.IsEmpty() )
{
// Do we want to display this as a notification or a dialog to the user
if ( bNotifyUser )
{
FNotificationInfo Info( ErrorMsg );
FSlateNotificationManager::Get().AddNotification( Info );
}
else
{
FMessageDialog::Open( EAppMsgType::Ok, ErrorMsg );
}
}
return bInASafeMode;
}
UEdMode* FEditorModeTools::GetActiveScriptableMode(FEditorModeID InID) const
{
if (auto* FoundMode = ActiveScriptableModes.FindByPredicate([InID](UEdMode* Mode) { return (Mode->GetID() == InID); }))
{
return *FoundMode;
}
return nullptr;
}
UTexture2D* FEditorModeTools::GetVertexTexture() const
{
return GEngine->DefaultBSPVertexTexture;
}
FMatrix FEditorModeTools::GetCustomDrawingCoordinateSystem() const
{
FMatrix Matrix = FMatrix::Identity;
switch (GetCoordSystem())
{
case COORD_World:
{
if (UEditorTransformGizmoContextObject* GizmoContextObject = WeakGizmoContext.Get())
{
GizmoContextObject->RotationContext = FRotationContext();
}
}
break;
case COORD_Local:
Matrix = GetLocalCoordinateSystem();
break;
case COORD_Parent:
Matrix = GetParentSpaceCoordinateSystem();
break;
case COORD_Explicit:
Matrix = GetExplicitCoordinateSystem();
break;
default:
break;
}
return Matrix;
}
FMatrix FEditorModeTools::GetCustomInputCoordinateSystem() const
{
return GetCustomDrawingCoordinateSystem();
}
FMatrix FEditorModeTools::GetLocalCoordinateSystem() const
{
return GetCustomCoordinateSystem([](const TTypedElement<ITypedElementWorldInterface>& InElement, FTransform& OutTransform)
{
InElement.GetWorldTransform(OutTransform);
});
}
FMatrix FEditorModeTools::GetParentSpaceCoordinateSystem() const
{
return GetCustomCoordinateSystem([](const TTypedElement<ITypedElementWorldInterface>& InElement, FTransform& OutTransform)
{
if (InElement.GetWorldTransform(OutTransform))
{
FTransform RelativeTransform;
if (InElement.GetRelativeTransform(RelativeTransform))
{
const FTransform ParentWorld = RelativeTransform.Inverse() * OutTransform;
OutTransform.SetRotation(ParentWorld.GetRotation());
}
return true;
}
return false;
});
}
FMatrix FEditorModeTools::GetExplicitCoordinateSystem() const
{
return GetCustomCoordinateSystem([this](const TTypedElement<ITypedElementWorldInterface>& InElement, FTransform& OutTransform)
{
if (UEditorTransformGizmoContextObject* GizmoContext = GetGizmoContext())
{
FRotationContext& RotationContext = GizmoContext->RotationContext;
RotationContext = FRotationContext();
RotationContext.bUseExplicitRotator = GetWidgetMode() == UE::Widget::EWidgetMode::WM_Rotate;
if (UE::GizmoRotationUtil::GetRelativeTransform(InElement, OutTransform, RotationContext))
{
return true;
}
}
return false;
});
}
FMatrix FEditorModeTools::GetCustomCoordinateSystem(TUniqueFunction<void(const TTypedElement<ITypedElementWorldInterface>&, FTransform&)>&& InGetTransformFunc) const
{
FMatrix Matrix = FMatrix::Identity;
// Let the current mode have a shot at setting the local coordinate system.
bool CustomCoordinateSystemProvided = false;
ForEachEdMode<ILegacyEdModeWidgetInterface>([&Matrix, &CustomCoordinateSystemProvided](ILegacyEdModeWidgetInterface* LegacyMode)
{
CustomCoordinateSystemProvided = LegacyMode->GetCustomDrawingCoordinateSystem(Matrix, nullptr);
return !CustomCoordinateSystemProvided;
});
// If there isn't an active mode overriding the local coordinate system, create it by looking at the current selection.
if (!CustomCoordinateSystemProvided)
{
if (UEditorTransformGizmoContextObject* GizmoContextObject = WeakGizmoContext.Get())
{
GizmoContextObject->RotationContext = FRotationContext();
}
TTypedElement<ITypedElementWorldInterface> LastSelected;
if ((this == &GLevelEditorModeTools()) && GCurrentLevelEditingViewportClient)
{
// Use the cache from the viewport when available
LastSelected = GCurrentLevelEditingViewportClient->GetElementsToManipulate()->GetBottomElement<ITypedElementWorldInterface>();
}
else
{
LastSelected = UEditorElementSubsystem::GetLastSelectedEditorManipulableElement(UEditorElementSubsystem::GetEditorNormalizedSelectionSet(*GetEditorSelectionSet()));
}
if (LastSelected)
{
FTransform CustomToWorldTransform;
InGetTransformFunc(LastSelected, CustomToWorldTransform);
Matrix = FQuatRotationMatrix(CustomToWorldTransform.GetRotation());
}
}
if (!Matrix.Equals(FMatrix::Identity))
{
Matrix.RemoveScaling();
}
return Matrix;
}
/** Gets the widget axis to be drawn */
EAxisList::Type FEditorModeTools::GetWidgetAxisToDraw( UE::Widget::EWidgetMode InWidgetMode ) const
{
EAxisList::Type OutAxis = EAxisList::All;
for( int Index = ActiveScriptableModes.Num() - 1; Index >= 0 ; Index-- )
{
ILegacyEdModeWidgetInterface* Mode = Cast<ILegacyEdModeWidgetInterface>(ActiveScriptableModes[Index]);
if ( Mode && Mode->ShouldDrawWidget() )
{
OutAxis = Mode->GetWidgetAxisToDraw( InWidgetMode );
break;
}
}
return OutAxis;
}
/** Mouse tracking interface. Passes tracking messages to all active modes */
bool FEditorModeTools::StartTracking(FEditorViewportClient* InViewportClient, FViewport* InViewport)
{
bIsTracking = true;
CachedLocation = PivotLocation; // Cache the pivot location
bool bTrackingHandled = InteractiveToolsContext->StartTracking(InViewportClient, InViewport);
// no need to go further if bHasOngoingTransform is true
if (!bHasOngoingTransform)
{
ForEachEdMode<ILegacyEdModeViewportInterface>([&bTrackingHandled, InViewportClient, InViewport](ILegacyEdModeViewportInterface* ViewportInterface)
{
bTrackingHandled |= ViewportInterface->StartTracking(InViewportClient, InViewport);
return true;
});
}
return bTrackingHandled;
}
/** Mouse tracking interface. Passes tracking messages to all active modes */
bool FEditorModeTools::EndTracking(FEditorViewportClient* InViewportClient, FViewport* InViewport)
{
bIsTracking = false;
bool bTrackingHandled = InteractiveToolsContext->EndTracking(InViewportClient, InViewport);
// no need to go further if bHasOngoingTransform is true
if (!bHasOngoingTransform)
{
ForEachEdMode<ILegacyEdModeViewportInterface>([&bTrackingHandled, InViewportClient, InViewport](ILegacyEdModeViewportInterface* ViewportInterface)
{
bTrackingHandled |= ViewportInterface->EndTracking(InViewportClient, InViewport);
return true;
});
}
CachedLocation = PivotLocation; // Clear the pivot location
bHasOngoingTransform = false;
return bTrackingHandled;
}
bool FEditorModeTools::AllowsViewportDragTool() const
{
if (bHasOngoingTransform)
{
return false;
}
bool bCanUseDragTool = false;
ForEachEdMode<const ILegacyEdModeViewportInterface>([&bCanUseDragTool](const ILegacyEdModeViewportInterface* LegacyMode)
{
bCanUseDragTool |= LegacyMode->AllowsViewportDragTool();
return true;
});
return bCanUseDragTool;
}
/** Notifies all active modes that a map change has occured */
void FEditorModeTools::MapChangeNotify()
{
ForEachEdMode([](UEdMode* Mode)
{
Mode->MapChangeNotify();
return true;
});
}
/** Notifies all active modes to empty their selections */
void FEditorModeTools::SelectNone()
{
ForEachEdMode([](UEdMode* Mode)
{
Mode->SelectNone();
return true;
});
}
/** Notifies all active modes of box selection attempts */
bool FEditorModeTools::BoxSelect( FBox& InBox, bool InSelect )
{
bool bHandled = false;
ForEachEdMode<ILegacyEdModeSelectInterface>([&bHandled, &InBox, InSelect](ILegacyEdModeSelectInterface* LegacyMode)
{
bHandled |= LegacyMode->BoxSelect(InBox, InSelect);
return true;
});
return bHandled;
}
/** Notifies all active modes of frustum selection attempts */
bool FEditorModeTools::FrustumSelect( const FConvexVolume& InFrustum, FEditorViewportClient* InViewportClient, bool InSelect )
{
bool bHandled = false;
ForEachEdMode<ILegacyEdModeSelectInterface>([&bHandled, InFrustum, InViewportClient, InSelect](ILegacyEdModeSelectInterface* LegacyMode)
{
bHandled |= LegacyMode->FrustumSelect(InFrustum, InViewportClient, InSelect);
return true;
});
return bHandled;
}
/** true if any active mode uses a transform widget */
bool FEditorModeTools::UsesTransformWidget() const
{
bool bUsesTransformWidget = false;
ForEachEdMode<const ILegacyEdModeWidgetInterface>([&bUsesTransformWidget](const ILegacyEdModeWidgetInterface* LegacyMode)
{
bUsesTransformWidget |= LegacyMode->UsesTransformWidget();
return true;
});
return bUsesTransformWidget;
}
/** true if any active mode uses the passed in transform widget */
bool FEditorModeTools::UsesTransformWidget( UE::Widget::EWidgetMode CheckMode ) const
{
bool bUsesTransformWidget = false;
ForEachEdMode<const ILegacyEdModeWidgetInterface>([&bUsesTransformWidget, CheckMode](const ILegacyEdModeWidgetInterface* LegacyMode)
{
bUsesTransformWidget |= LegacyMode->UsesTransformWidget(CheckMode);
return true;
});
return bUsesTransformWidget;
}
/** Sets the current widget axis */
void FEditorModeTools::SetCurrentWidgetAxis( EAxisList::Type NewAxis )
{
ForEachEdMode<ILegacyEdModeWidgetInterface>([NewAxis](ILegacyEdModeWidgetInterface* LegacyMode)
{
LegacyMode->SetCurrentWidgetAxis(NewAxis);
return true;
});
}
/** Notifies all active modes of mouse click messages. */
bool FEditorModeTools::HandleClick(FEditorViewportClient* InViewportClient, HHitProxy *HitProxy, const FViewportClick& Click )
{
bool bHandled = false;
ForEachEdMode<ILegacyEdModeViewportInterface>([&bHandled, InViewportClient, HitProxy, Click](ILegacyEdModeViewportInterface* Mode)
{
bHandled |= Mode->HandleClick(InViewportClient, HitProxy, Click);
return true;
});
return bHandled;
}
bool FEditorModeTools::ComputeBoundingBoxForViewportFocus(AActor* Actor, UPrimitiveComponent* PrimitiveComponent, FBox& InOutBox)
{
bool bHandled = false;
for (const UEdMode* Mode : ActiveScriptableModes)
{
bHandled |= Mode->ComputeBoundingBoxForViewportFocus(Actor, PrimitiveComponent, InOutBox);
}
return bHandled;
}
/** true if the passed in brush actor should be drawn in wireframe */
bool FEditorModeTools::ShouldDrawBrushWireframe( AActor* InActor ) const
{
bool bShouldDraw = false;
for (const UEdMode* Mode : ActiveScriptableModes)
{
bShouldDraw |= Mode->ShouldDrawBrushWireframe(InActor);
}
if((ActiveScriptableModes.Num() == 0))
{
// We can get into a state where there are no active modes at editor startup if the builder brush is created before the default mode is activated.
// Ensure we can see the builder brush when no modes are active.
bShouldDraw = true;
}
return bShouldDraw;
}
/** true if brush vertices should be drawn */
bool FEditorModeTools::ShouldDrawBrushVertices() const
{
if(UBrushEditingSubsystem* BrushSubsystem = GEditor->GetEditorSubsystem<UBrushEditingSubsystem>())
{
// Currently only geometry mode being active prevents vertices from being drawn.
return !BrushSubsystem->IsGeometryEditorModeActive();
}
return true;
}
/** Ticks all active modes */
void FEditorModeTools::Tick( FEditorViewportClient* ViewportClient, float DeltaTime )
{
// Remove anything pending destruction
ExitAllModesPendingDeactivate();
if (ActiveScriptableModes.Num() == 0)
{
// Ensure the default mode is active if there are no active modes.
ActivateDefaultMode();
}
InteractiveToolsContext->Tick(ViewportClient, DeltaTime);
ForEachEdMode([ViewportClient, DeltaTime](UEdMode* Mode)
{
if (ILegacyEdModeViewportInterface* ViewportInterface = Cast<ILegacyEdModeViewportInterface>(Mode))
{
ViewportInterface->Tick(ViewportClient, DeltaTime);
}
Mode->ModeTick(DeltaTime);
return true;
});
}
/** Notifies all active modes of any change in mouse movement */
bool FEditorModeTools::InputDelta( FEditorViewportClient* InViewportClient,FViewport* InViewport,FVector& InDrag,FRotator& InRot,FVector& InScale )
{
bool bHandled = false;
ForEachEdMode<ILegacyEdModeViewportInterface>([&bHandled, InViewportClient, InViewport, &InDrag, &InRot, &InScale](ILegacyEdModeViewportInterface* Mode)
{
bHandled |= Mode->InputDelta(InViewportClient, InViewport, InDrag, InRot, InScale);
return true;
});
return bHandled;
}
/** Notifies all active modes of captured mouse movement */
bool FEditorModeTools::CapturedMouseMove( FEditorViewportClient* InViewportClient, FViewport* InViewport, int32 InMouseX, int32 InMouseY )
{
bool bHandled = InteractiveToolsContext->CapturedMouseMove(InViewportClient, InViewport, InMouseX, InMouseY);
ForEachEdMode<ILegacyEdModeViewportInterface>([&bHandled, InViewportClient, InViewport, InMouseX, InMouseY](ILegacyEdModeViewportInterface* Mode)
{
bHandled |= Mode->CapturedMouseMove(InViewportClient, InViewport, InMouseX, InMouseY);
return true;
});
return bHandled;
}
/** Notifies all active modes of all captured mouse movement */
bool FEditorModeTools::ProcessCapturedMouseMoves( FEditorViewportClient* InViewportClient, FViewport* InViewport, const TArrayView<FIntPoint>& CapturedMouseMoves )
{
bool bHandled = false;
ForEachEdMode<ILegacyEdModeViewportInterface>([&bHandled, InViewportClient, InViewport, &CapturedMouseMoves](ILegacyEdModeViewportInterface* Mode)
{
bHandled |= Mode->ProcessCapturedMouseMoves(InViewportClient, InViewport, CapturedMouseMoves);
return true;
});
return bHandled;
}
/** Notifies all active modes of keyboard input via a viewport client */
bool FEditorModeTools::InputKey(FEditorViewportClient* InViewportClient, FViewport* Viewport, FKey Key, EInputEvent Event, bool bRouteToToolsContext)
{
const bool bHadOngoingTransform = bHasOngoingTransform;
bool bWasHandledByToolsContext = false;
if (bRouteToToolsContext)
{
bWasHandledByToolsContext = InteractiveToolsContext->InputKey(InViewportClient, Viewport, Key, Event);
}
else
{
// If we're not routing to the tools context, we still need to let it look at the event so that it can update
// its internal memory of which mouse keys are down, to pass the correct mouse state later.
InteractiveToolsContext->UpdateStateWithoutRoutingInputKey(InViewportClient, Viewport, Key, Event);
}
if (bWasHandledByToolsContext && !bIsTracking && GetInteractiveToolsContext()->InputRouter->HasActiveMouseCapture())
{
StartTracking(InViewportClient, Viewport);
}
else if (bRouteToToolsContext && bIsTracking && !GetInteractiveToolsContext()->InputRouter->HasActiveMouseCapture())
{
EndTracking(InViewportClient, Viewport);
}
// no need to go further if bHasOngoingTransform state has changed
// NOTE, this should probably be done comparing HasActiveMouseCapture changes instead as it means that the ITF handled the event
// however, as with StartTracking/EndTracking and bTrackingHandled, testing bWasHandledByToolsContext is not reliable as
// InteractiveToolsContext->InputKey will return false when the mouse is released, even if there was an ongoing capture before the release event ended it.
if (bHasOngoingTransform != bHadOngoingTransform)
{
return true;
}
// If the toolkit should process the command, it should not have been handled by ITF, or be tracked elsewhere.
const bool bPassToToolkitCommands = bRouteToToolsContext && !bWasHandledByToolsContext;
bool bHandled = bWasHandledByToolsContext;
ForEachEdMode([&bHandled, bPassToToolkitCommands, Event, Key, InViewportClient, Viewport](UEdMode* Mode)
{
// First, always give the legacy viewport interface a chance to process they key press. This is to support any of the FModeTools that may still exist.
if (ILegacyEdModeViewportInterface* ViewportInterface = Cast<ILegacyEdModeViewportInterface>(Mode))
{
if (ViewportInterface->InputKey(InViewportClient, Viewport, Key, Event))
{
bHandled |= true;
return true; // Skip passing to the mode's toolkit if the legacy mode interface handled the input.
}
}
// Next, give the toolkit commands a chance to process the key press if the tools context did not handle the key press.
if (bPassToToolkitCommands && (Event != IE_Released) && Mode->UsesToolkits() && Mode->GetToolkit().IsValid())
{
bHandled |= Mode->GetToolkit().Pin()->GetToolkitCommands()->ProcessCommandBindings(Key, FSlateApplication::Get().GetModifierKeys(), (Event == EInputEvent::IE_Repeat));
return true;
}
return true;
});
PRAGMA_DISABLE_DEPRECATION_WARNINGS // Begin AActor::EditorKeyPressed
// Finally, pass input to selected actors if nothing else handled the input (Deprecated in 5.4)
if (!bHandled)
{
GetEditorSelectionSet()->ForEachSelectedObject<AActor>([Key, Event](AActor* ActorPtr)
{
ActorPtr->EditorKeyPressed(Key, Event);
return true;
});
}
PRAGMA_ENABLE_DEPRECATION_WARNINGS // End AActor::EditorKeyPressed
return bHandled;
}
/** Notifies all active modes of axis movement */
bool FEditorModeTools::InputAxis(FEditorViewportClient* InViewportClient, FViewport* Viewport, int32 ControllerId, FKey Key, float Delta, float DeltaTime)
{
bool bHandled = false;
ForEachEdMode<ILegacyEdModeViewportInterface>([&bHandled, InViewportClient, Viewport, ControllerId, Key, Delta, DeltaTime](ILegacyEdModeViewportInterface* Mode)
{
bHandled |= Mode->InputAxis(InViewportClient, Viewport, ControllerId, Key, Delta, DeltaTime);
return true;
});
return bHandled;
}
bool FEditorModeTools::GetPivotForOrbit( FVector& Pivot ) const
{
bool bHandled = false;
// Just return the first pivot point specified by a mode
ForEachEdMode([&Pivot, &bHandled](const UEdMode* Mode)
{
bHandled = Mode->GetPivotForOrbit(Pivot);
return !bHandled;
});
return bHandled;
}
bool FEditorModeTools::MouseEnter( FEditorViewportClient* InViewportClient, FViewport* Viewport, int32 X, int32 Y )
{
HoveredViewportClient = InViewportClient;
bool bHandled = InteractiveToolsContext->MouseEnter(InViewportClient, Viewport, X, Y);
ForEachEdMode<ILegacyEdModeViewportInterface>([&bHandled, InViewportClient, Viewport, X, Y](ILegacyEdModeViewportInterface* Mode)
{
bHandled |= Mode->MouseEnter(InViewportClient, Viewport, X, Y);
return true;
});
return bHandled;
}
bool FEditorModeTools::MouseLeave( FEditorViewportClient* InViewportClient, FViewport* Viewport )
{
// TODO: HoveredViewportClient should be reset here, but there is currently a bug (UE-119516)
// that makes it so that flying in viewports can create mismatches between MouseEnter and MouseLeave.
// For this reason, we currently use HoveredViewportClient as if it were LastHoveredViewportClient,
// which works for the purposes that we use it for.
// If we never fix the bug, we should probably just rename it.
//HoveredViewportClient = nullptr;
bool bHandled = InteractiveToolsContext->MouseLeave(InViewportClient, Viewport);
ForEachEdMode<ILegacyEdModeViewportInterface>([&bHandled, InViewportClient, Viewport](ILegacyEdModeViewportInterface* Mode)
{
bHandled |= Mode->MouseLeave(InViewportClient, Viewport);
return true;
});
return bHandled;
}
/** Notifies all active modes that the mouse has moved */
bool FEditorModeTools::MouseMove( FEditorViewportClient* InViewportClient, FViewport* Viewport, int32 X, int32 Y )
{
bool bHandled = InteractiveToolsContext->MouseMove(InViewportClient, Viewport, X, Y);
ForEachEdMode<ILegacyEdModeViewportInterface>([&bHandled, InViewportClient, Viewport, X, Y](ILegacyEdModeViewportInterface* Mode)
{
bHandled |= Mode->MouseMove(InViewportClient, Viewport, X, Y);
return true;
});
return bHandled;
}
bool FEditorModeTools::ReceivedFocus( FEditorViewportClient* InViewportClient, FViewport* Viewport )
{
FocusedViewportClient = InViewportClient;
bool bHandled = false;
ForEachEdMode<ILegacyEdModeViewportInterface>([&bHandled, InViewportClient, Viewport](ILegacyEdModeViewportInterface* Mode)
{
bHandled |= Mode->ReceivedFocus(InViewportClient, Viewport);
return true;
});
return bHandled;
}
bool FEditorModeTools::LostFocus( FEditorViewportClient* InViewportClient, FViewport* Viewport )
{
// Note that we don't reset FocusedViewportClient intentionally. EdModeInteractiveToolsContext
// only ticks its objects once for the focused viewport to avoid multi-ticking, so if we cleared
// it here, we'd stop ticking things in the level editor when clicking out of the viewport.
// TODO: Conceptually, we should probably clear FocusedViewportClient here, but also have a
// LastFocusedViewportClient property that we don't clear, to use in ticking.
bool bHandled = InteractiveToolsContext->LostFocus(InViewportClient, Viewport);
ForEachEdMode<ILegacyEdModeViewportInterface>([&bHandled, InViewportClient, Viewport](ILegacyEdModeViewportInterface* Mode)
{
bHandled |= Mode->LostFocus(InViewportClient, Viewport);
return true;
});
return bHandled;
}
/** Draws all active mode components */
void FEditorModeTools::DrawActiveModes( const FSceneView* InView, FPrimitiveDrawInterface* PDI )
{
ForEachEdMode<ILegacyEdModeDrawHelperInterface>([InView, PDI](ILegacyEdModeDrawHelperInterface* DrawHelper)
{
DrawHelper->Draw(InView, PDI);
return true;
});
}
/** Renders all active modes */
void FEditorModeTools::Render( const FSceneView* InView, FViewport* Viewport, FPrimitiveDrawInterface* PDI )
{
InteractiveToolsContext->Render(InView, Viewport, PDI);
ForEachEdMode<ILegacyEdModeWidgetInterface>([InView, Viewport, PDI](ILegacyEdModeWidgetInterface* Mode)
{
Mode->Render(InView, Viewport, PDI);
return true;
});
}
/** Draws the HUD for all active modes */
void FEditorModeTools::DrawHUD( FEditorViewportClient* InViewportClient,FViewport* Viewport, const FSceneView* View, FCanvas* Canvas )
{
InteractiveToolsContext->DrawHUD(InViewportClient, Viewport, View, Canvas);
DrawBrackets(InViewportClient, Viewport, View, Canvas);
if (!(InViewportClient->EngineShowFlags.ModeWidgets))
{
return;
}
// Clear Hit proxies
const bool bIsHitTesting = Canvas->IsHitTesting();
if (!bIsHitTesting)
{
Canvas->SetHitProxy(nullptr);
}
ForEachEdMode<ILegacyEdModeWidgetInterface>([InViewportClient, Viewport, View, Canvas](ILegacyEdModeWidgetInterface* Mode)
{
Mode->DrawHUD(InViewportClient, Viewport, View, Canvas);
return true;
});
// Draw vertices for selected BSP brushes and static meshes if the large vertices show flag is set.
if (!InViewportClient->bDrawVertices)
{
return;
}
const bool bLargeVertices = View->Family->EngineShowFlags.LargeVertices;
if (!bLargeVertices)
{
return;
}
// Temporaries.
const bool bShowBrushes = View->Family->EngineShowFlags.Brushes;
const bool bShowBSP = View->Family->EngineShowFlags.BSP;
const bool bShowBuilderBrush = View->Family->EngineShowFlags.BuilderBrush != 0;
UTexture2D* VertexTexture = GetVertexTexture();
const float TextureSizeX = VertexTexture->GetSizeX() * (bLargeVertices ? 1.0f : 0.5f);
const float TextureSizeY = VertexTexture->GetSizeY() * (bLargeVertices ? 1.0f : 0.5f);
GetEditorSelectionSet()->ForEachSelectedObject<AStaticMeshActor>([View, Canvas, VertexTexture, TextureSizeX, TextureSizeY, bIsHitTesting](AStaticMeshActor* Actor)
{
TArray<FVector> Vertices;
FCanvasItemTestbed::bTestState = !FCanvasItemTestbed::bTestState;
// Static mesh vertices
if (Actor->GetStaticMeshComponent() && Actor->GetStaticMeshComponent()->GetStaticMesh()
&& Actor->GetStaticMeshComponent()->GetStaticMesh()->GetRenderData())
{
FTransform ActorToWorld = Actor->ActorToWorld();
const FPositionVertexBuffer& VertexBuffer = Actor->GetStaticMeshComponent()->GetStaticMesh()->GetRenderData()->LODResources[0].VertexBuffers.PositionVertexBuffer;
for (uint32 i = 0; i < VertexBuffer.GetNumVertices(); i++)
{
Vertices.AddUnique(ActorToWorld.TransformPosition((FVector)VertexBuffer.VertexPosition(i)));
}
const float InvDpiScale = 1.0f / Canvas->GetDPIScale();
FCanvasTileItem TileItem(FVector2D(0.0f, 0.0f), FVector2D(0.0f, 0.0f), FLinearColor::White);
TileItem.BlendMode = SE_BLEND_Translucent;
for (int32 VertexIndex = 0; VertexIndex < Vertices.Num(); ++VertexIndex)
{
const FVector& Vertex = Vertices[VertexIndex];
FVector2D PixelLocation;
if (View->ScreenToPixel(View->WorldToScreen(Vertex), PixelLocation))
{
PixelLocation *= InvDpiScale;
const bool bOutside =
PixelLocation.X < 0.0f || PixelLocation.X > View->UnscaledViewRect.Width() * InvDpiScale ||
PixelLocation.Y < 0.0f || PixelLocation.Y > View->UnscaledViewRect.Height() * InvDpiScale;
if (!bOutside)
{
const double X = PixelLocation.X - (TextureSizeX / 2);
const double Y = PixelLocation.Y - (TextureSizeY / 2);
if (bIsHitTesting)
{
Canvas->SetHitProxy(new HStaticMeshVert(Actor, Vertex));
}
TileItem.Texture = VertexTexture->GetResource();
TileItem.Size = FVector2D(TextureSizeX, TextureSizeY);
Canvas->DrawItem(TileItem, FVector2D(X, Y));
if (bIsHitTesting)
{
Canvas->SetHitProxy(nullptr);
}
}
}
}
}
return true;
});
}
/** Calls PostUndo on all active modes */
void FEditorModeTools::PostUndo(bool bSuccess)
{
if (bSuccess)
{
ForEachEdMode([](UEdMode* Mode)
{
Mode->PostUndo();
return true;
});
}
}
void FEditorModeTools::PostRedo(bool bSuccess)
{
PostUndo(bSuccess);
}
/** true if we should allow widget move */
bool FEditorModeTools::AllowWidgetMove() const
{
bool bAllow = false;
ForEachEdMode<ILegacyEdModeWidgetInterface>([&bAllow](ILegacyEdModeWidgetInterface* LegacyMode)
{
bAllow |= LegacyMode->AllowWidgetMove();
return true;
});
return bAllow;
}
bool FEditorModeTools::DisallowMouseDeltaTracking() const
{
bool bDisallow = false;
ForEachEdMode<ILegacyEdModeViewportInterface>([&bDisallow](ILegacyEdModeViewportInterface* LegacyMode)
{
bDisallow |= LegacyMode->DisallowMouseDeltaTracking();
return true;
});
return bDisallow;
}
bool FEditorModeTools::GetCursor(EMouseCursor::Type& OutCursor) const
{
bool bHandled = false;
if (CVarEnableITFCursorOverrideSupport.GetValueOnGameThread() > 0)
{
bHandled = InteractiveToolsContext->GetCursor(OutCursor);
}
if (!bHandled)
{
for (const UEdMode* Mode : ActiveScriptableModes)
{
bHandled |= Mode->GetCursor(OutCursor);
}
}
return bHandled;
}
bool FEditorModeTools::GetOverrideCursorVisibility(bool& bWantsOverride, bool& bHardwareCursorVisible, bool bSoftwareCursorVisible) const
{
bool bHandled = false;
for (const UEdMode* Mode : ActiveScriptableModes)
{
bHandled |= Mode->GetOverrideCursorVisibility(bWantsOverride, bHardwareCursorVisible, bSoftwareCursorVisible);
}
return bHandled;
}
bool FEditorModeTools::PreConvertMouseMovement(FEditorViewportClient* InViewportClient)
{
bool bHandled = false;
ForEachEdMode<ILegacyEdModeViewportInterface>([&bHandled, InViewportClient](ILegacyEdModeViewportInterface* Mode)
{
bHandled |= Mode->PreConvertMouseMovement(InViewportClient);
return true;
});
return bHandled;
}
bool FEditorModeTools::PostConvertMouseMovement(FEditorViewportClient* InViewportClient)
{
bool bHandled = false;
ForEachEdMode<ILegacyEdModeViewportInterface>([&bHandled, InViewportClient](ILegacyEdModeViewportInterface* ViewportInterface)
{
bHandled |= ViewportInterface->PostConvertMouseMovement(InViewportClient);
return true;
});
return bHandled;
}
bool FEditorModeTools::GetShowWidget() const
{
if (!bShowWidget)
{
return false;
}
bool bDrawModeSupportsWidgetDrawing = false;
// Check to see of any active modes support widget drawing
ForEachEdMode<ILegacyEdModeWidgetInterface>([&bDrawModeSupportsWidgetDrawing](ILegacyEdModeWidgetInterface* LegacyMode)
{
bDrawModeSupportsWidgetDrawing |= LegacyMode->ShouldDrawWidget();
return !bDrawModeSupportsWidgetDrawing;
});
return bDrawModeSupportsWidgetDrawing;
}
/**
* Used to cycle widget modes
*/
void FEditorModeTools::CycleWidgetMode (void)
{
//make sure we're not currently tracking mouse movement. If we are, changing modes could cause a crash due to referencing an axis/plane that is incompatible with the widget
for (FLevelEditorViewportClient* ViewportClient : GEditor->GetLevelViewportClients())
{
if (ViewportClient->IsTracking())
{
return;
}
}
//only cycle when the mode is requesting the drawing of a widget
if( GetShowWidget() )
{
const int32 CurrentWk = GetWidgetMode();
int32 Wk = CurrentWk;
do
{
Wk++;
if ((Wk == UE::Widget::WM_TranslateRotateZ) && (!GetDefault<ULevelEditorViewportSettings>()->bAllowTranslateRotateZWidget))
{
Wk++;
}
// Roll back to the start if we go past UE::Widget::WM_Scale
if( Wk >= UE::Widget::WM_Max)
{
Wk -= UE::Widget::WM_Max;
}
}
while (!UsesTransformWidget((UE::Widget::EWidgetMode)Wk) && Wk != CurrentWk);
SetWidgetMode( (UE::Widget::EWidgetMode)Wk );
FEditorSupportDelegates::RedrawAllViewports.Broadcast();
}
}
/**Save Widget Settings to Ini file*/
void FEditorModeTools::SaveWidgetSettings(void)
{
GetMutableDefault<UEditorPerProjectUserSettings>()->SaveConfig();
}
/**Load Widget Settings from Ini file*/
void FEditorModeTools::LoadWidgetSettings(void)
{
}
/**
* Returns a good location to draw the widget at.
*/
FVector FEditorModeTools::GetWidgetLocation() const
{
for (int Index = ActiveScriptableModes.Num() - 1; Index >= 0; Index--)
{
if (ILegacyEdModeWidgetInterface* LegacyMode = Cast<ILegacyEdModeWidgetInterface>(ActiveScriptableModes[Index]))
{
if (LegacyMode->UsesTransformWidget())
{
return LegacyMode->GetWidgetLocation();
}
}
}
return FVector(ForceInitToZero);
}
/**
* Changes the current widget mode.
*/
void FEditorModeTools::SetWidgetMode( UE::Widget::EWidgetMode InWidgetMode )
{
WidgetMode = InWidgetMode;
}
/**
* Allows you to temporarily override the widget mode. Call this function again
* with UE::Widget::WM_None to turn off the override.
*/
void FEditorModeTools::SetWidgetModeOverride( UE::Widget::EWidgetMode InWidgetMode )
{
OverrideWidgetMode = InWidgetMode;
}
/**
* Retrieves the current widget mode, taking overrides into account.
*/
UE::Widget::EWidgetMode FEditorModeTools::GetWidgetMode() const
{
if( OverrideWidgetMode != UE::Widget::WM_None )
{
return OverrideWidgetMode;
}
return WidgetMode;
}
/**
* Set Scale On The Widget
*/
void FEditorModeTools::SetWidgetScale(float InScale)
{
WidgetScale = InScale;
}
/**
* Get Scale On The Widget
*/
float FEditorModeTools::GetWidgetScale() const
{
return WidgetScale;
}
void FEditorModeTools::AddReferencedObjects( FReferenceCollector& Collector )
{
Collector.AddReferencedObjects(ActiveScriptableModes);
Collector.AddReferencedObjects(PendingDeactivateModes);
Collector.AddReferencedObjects(RecycledScriptableModes);
Collector.AddReferencedObject(InteractiveToolsContext);
}
FEdMode* FEditorModeTools::GetActiveMode( FEditorModeID InID )
{
if (UEdMode* Mode = GetActiveScriptableMode(InID))
{
return Mode->AsLegacyMode();
}
return nullptr;
}
const FEdMode* FEditorModeTools::GetActiveMode( FEditorModeID InID ) const
{
if (UEdMode* Mode = GetActiveScriptableMode(InID))
{
return Mode->AsLegacyMode();
}
return nullptr;
}
const FModeTool* FEditorModeTools::GetActiveTool( FEditorModeID InID ) const
{
ILegacyEdModeToolInterface* ActiveMode = Cast<ILegacyEdModeToolInterface>(GetActiveScriptableMode( InID ));
const FModeTool* Tool = nullptr;
if( ActiveMode )
{
Tool = ActiveMode->GetCurrentTool();
}
return Tool;
}
bool FEditorModeTools::IsModeActive( FEditorModeID InID ) const
{
return (GetActiveScriptableMode(InID) != nullptr);
}
bool FEditorModeTools::IsDefaultModeActive() const
{
bool bAllDefaultModesActive = true;
for( const FEditorModeID& ModeID : DefaultModeIDs )
{
if( !IsModeActive( ModeID ) )
{
bAllDefaultModesActive = false;
break;
}
}
return bAllDefaultModesActive;
}
bool FEditorModeTools::CanCycleWidgetMode() const
{
bool bCanCycleWidget = false;
ForEachEdMode<ILegacyEdModeWidgetInterface>([&bCanCycleWidget](ILegacyEdModeWidgetInterface* LegacyMode)
{
bCanCycleWidget = LegacyMode->CanCycleWidgetMode();
return !bCanCycleWidget;
});
return bCanCycleWidget;
}
bool FEditorModeTools::CanAutoSave() const
{
return FEditorModeTools::TestAllModes([](UEdMode* Mode) { return Mode->CanAutoSave(); }, true);
}
bool FEditorModeTools::OnRequestClose()
{
return FEditorModeTools::TestAllModes([](UEdMode* Mode) { return Mode->OnRequestClose(); }, true);
}
bool FEditorModeTools::IsOperationSupportedForCurrentAsset(EAssetOperation InOperation) const
{
return FEditorModeTools::TestAllModes([InOperation](UEdMode* Mode) { return Mode->IsOperationSupportedForCurrentAsset(InOperation); }, true);
}
UModeManagerInteractiveToolsContext* FEditorModeTools::GetInteractiveToolsContext() const
{
return InteractiveToolsContext;
}
IGizmoStateTarget* FEditorModeTools::GetGizmoStateTarget()
{
if (!WeakGizmoStateTarget.IsValid())
{
WeakGizmoStateTarget = UEditorGizmoStateTarget::Construct(
this,
NSLOCTEXT("UTransformGizmo", "UTransformGizmoTransaction", "Transform"),
InteractiveToolsContext->GizmoManager);
}
return WeakGizmoStateTarget.Get();
}
UEditorTransformGizmoContextObject* FEditorModeTools::GetGizmoContext() const
{
if (!WeakGizmoContext.IsValid())
{
const UModeManagerInteractiveToolsContext* ToolsContext = GetInteractiveToolsContext();
const UContextObjectStore* ContextStore = ToolsContext ? ToolsContext->ToolManager->GetContextObjectStore() : nullptr;
WeakGizmoContext = ContextStore ? ContextStore->FindContext<UEditorTransformGizmoContextObject>() : nullptr;
}
return WeakGizmoContext.Get();
}
const FRotationContext& FEditorModeTools::GetRotationContext() const
{
static const FRotationContext DefaultContext;
return WeakGizmoContext.IsValid() ? WeakGizmoContext->RotationContext : DefaultContext;
}
bool FEditorModeTools::BeginTransform(const FGizmoState& InState)
{
bool bHandled = false;
ForEachEdMode<IGizmoEdModeInterface>([&bHandled, &InState](IGizmoEdModeInterface* GizmoInterface)
{
bHandled |= GizmoInterface->BeginTransform(InState);
return true;
});
// Give the focused VPC an opportunity to open the transform transacting if it has not been handled before
if (!bHandled && FocusedViewportClient)
{
bHandled = FocusedViewportClient->BeginTransform(InState);
}
bHasOngoingTransform = bHandled;
return bHandled;
}
bool FEditorModeTools::EndTransform(const FGizmoState& InState) const
{
bool bHandled = false;
ForEachEdMode<IGizmoEdModeInterface>([&bHandled, &InState](IGizmoEdModeInterface* GizmoInterface)
{
bHandled |= GizmoInterface->EndTransform(InState);
return true;
});
// Give the focused VPC an opportunity to close the transform transacting if it has not been handled before
if (!bHandled && FocusedViewportClient)
{
bHandled = FocusedViewportClient->EndTransform(InState);
}
// NOTE bHasOngoingTransform is not set to false here but in EndTracking as its needed there.
// See header file more more explanations
return bHandled;
}
bool FEditorModeTools::HasOngoingTransform() const
{
return bHasOngoingTransform;
}