// 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 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(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()->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(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(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 FEditorModeTools::GetToolkitHost() const { TSharedPtr Result = ToolkitHost.Pin(); check(ToolkitHost.IsValid()); return Result; } bool FEditorModeTools::HasToolkitHost() const { return ToolkitHost.Pin().IsValid(); } void FEditorModeTools::SetToolkitHost(TSharedRef InHost) { checkf(!ToolkitHost.IsValid(), TEXT("SetToolkitHost can only be called once")); ToolkitHost = InHost; if (HasToolkitHost()) { UAssetEditorContextObject* AssetEditorContextObject = NewObject(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& InPreDuplicateSelection, TArray& 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(*It); if(Actor != nullptr && Actor->FindComponentByClass() != nullptr) { bSelectionHasSceneComponent = true; break; } } } else { // If selecting an actor, move the pivot location. AActor* Actor = Cast(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()->bHighlightWithBrackets) { return; } if (UTypedElementSelectionSet* CurrentSelection = GetEditorSelectionSet()) { CurrentSelection->ForEachSelectedObject([Canvas, View, Viewport, ViewportClient](AActor* Actor) { const FLinearColor SelectedActorBoxColor(0.6f, 0.6f, 1.0f); const bool bDrawBracket = Actor->IsA(); ViewportClient->DrawActorScreenSpaceBoundingBox(Canvas, View, Viewport, Actor, SelectedActorBoxColor, bDrawBracket); return true; }); } } void FEditorModeTools::ForEachEdMode(TFunctionRef 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 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 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()) { 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 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 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()->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 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& InElement, FTransform& OutTransform) { InElement.GetWorldTransform(OutTransform); }); } FMatrix FEditorModeTools::GetParentSpaceCoordinateSystem() const { return GetCustomCoordinateSystem([](const TTypedElement& 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& 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&, FTransform&)>&& InGetTransformFunc) const { FMatrix Matrix = FMatrix::Identity; // Let the current mode have a shot at setting the local coordinate system. bool CustomCoordinateSystemProvided = false; ForEachEdMode([&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 LastSelected; if ((this == &GLevelEditorModeTools()) && GCurrentLevelEditingViewportClient) { // Use the cache from the viewport when available LastSelected = GCurrentLevelEditingViewportClient->GetElementsToManipulate()->GetBottomElement(); } 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(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([&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([&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([&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([&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([&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([&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([&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([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([&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()) { // 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(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([&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([&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& CapturedMouseMoves ) { bool bHandled = false; ForEachEdMode([&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(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([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([&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([&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([&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([&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([&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([&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([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([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([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([View, Canvas, VertexTexture, TextureSizeX, TextureSizeY, bIsHitTesting](AStaticMeshActor* Actor) { TArray 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([&bAllow](ILegacyEdModeWidgetInterface* LegacyMode) { bAllow |= LegacyMode->AllowWidgetMove(); return true; }); return bAllow; } bool FEditorModeTools::DisallowMouseDeltaTracking() const { bool bDisallow = false; ForEachEdMode([&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([&bHandled, InViewportClient](ILegacyEdModeViewportInterface* Mode) { bHandled |= Mode->PreConvertMouseMovement(InViewportClient); return true; }); return bHandled; } bool FEditorModeTools::PostConvertMouseMovement(FEditorViewportClient* InViewportClient) { bool bHandled = false; ForEachEdMode([&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([&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()->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()->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(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(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([&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() : 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([&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([&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; }