// Copyright Epic Games, Inc. All Rights Reserved. #include "LevelInstanceEditorMode.h" #include "LevelInstanceEditorModeToolkit.h" #include "LevelInstanceEditorModeCommands.h" #include "LevelInstanceEditorSettings.h" #include "Editor.h" #include "Selection.h" #include "EditorModes.h" #include "Engine/World.h" #include "EngineUtils.h" #include "LevelInstance/LevelInstanceSettings.h" #include "LevelInstance/LevelInstanceSubsystem.h" #include "LevelInstance/LevelInstanceInterface.h" #include "LevelInstance/ILevelInstanceEditorModule.h" #include "LevelEditor.h" #include "LevelEditorViewport.h" #include "LevelEditorActions.h" #include "EditorModeManager.h" #include "Framework/Application/SlateApplication.h" #include "Modules/ModuleManager.h" #include "InteractiveToolManager.h" #include "Tools/EdModeInteractiveToolsContext.h" #include "BaseBehaviors/MouseWheelBehavior.h" #include "InputRouter.h" #include "ToolContextInterfaces.h" #include "Elements/Framework/EngineElementsLibrary.h" #include "Elements/Framework/TypedElementHandle.h" #define LOCTEXT_NAMESPACE "LevelInstanceEditorMode" FEditorModeID ULevelInstanceEditorMode::EM_LevelInstanceEditorModeId("EditMode.LevelInstance"); class FMouseWheelBehaviorTarget : public IMouseWheelBehaviorTarget { public: FMouseWheelBehaviorTarget(UEditorInteractiveToolsContext* InInteractiveToolContext) : InteractiveToolContext(InInteractiveToolContext) {} UEditorInteractiveToolsContext* InteractiveToolContext = nullptr; // IMouseWheelBehaviorTarget virtual FInputRayHit ShouldRespondToMouseWheel(const FInputDeviceRay& CurrentPos) override { FInputRayHit ToReturn; TArray SelectionHierarchy; int32 SelectionIndex = INDEX_NONE; ToReturn.bHit = GetLevelInstanceSelectionHierarchy(CurrentPos, SelectionHierarchy, SelectionIndex); return ToReturn; } virtual void OnMouseWheelScrollUp(const FInputDeviceRay& CurrentPos) override { TArray SelectionHierarchy; int32 SelectionIndex = INDEX_NONE; if (GetLevelInstanceSelectionHierarchy(CurrentPos, SelectionHierarchy, SelectionIndex)) { SelectActorAt(SelectionIndex - 1, SelectionHierarchy); } } virtual void OnMouseWheelScrollDown(const FInputDeviceRay& CurrentPos) override { TArray SelectionHierarchy; int32 SelectionIndex = INDEX_NONE; if (GetLevelInstanceSelectionHierarchy(CurrentPos, SelectionHierarchy, SelectionIndex)) { SelectActorAt(SelectionIndex + 1, SelectionHierarchy); } } private: bool GetLevelInstanceSelectionHierarchy(const FInputDeviceRay& CurrentPos, TArray& OutSelectionHierarchy, int32& OutSelectionIndex) const { if (!GetDefault()->bIsViewportSubSelectionEnabled) { return false; } if (!FSlateApplication::Get().GetModifierKeys().IsShiftDown()) { return false; } TArray SelectedActors; GEditor->GetSelectedActors()->GetSelectedObjects(SelectedActors); // Only handle mouse wheel on single selection if (SelectedActors.Num() != 1) { return false; } AActor* SelectedActor = SelectedActors[0]; if (CurrentPos.bHas2D) { if (IToolsContextQueriesAPI* ContextAPI = InteractiveToolContext->ToolManager->GetContextQueriesAPI()) { if (FViewport* Viewport = ContextAPI->GetFocusedViewport()) { if (HHitProxy* HitResult = Viewport->GetHitProxy(CurrentPos.ScreenPosition.X, CurrentPos.ScreenPosition.Y)) { if (HActor* HitActor = HitProxyCast(HitResult)) { if (AActor* Actor = HitActor->Actor; Actor && Actor->IsInLevelInstance()) { OutSelectionIndex = Actor == SelectedActor ? 0 : INDEX_NONE; OutSelectionHierarchy.Add(Actor); ULevelInstanceSubsystem* LevelInstanceSubsystem = UWorld::GetSubsystem(Actor->GetWorld()); LevelInstanceSubsystem->ForEachLevelInstanceAncestors(Actor, [SelectedActor, &OutSelectionIndex, &OutSelectionHierarchy](ILevelInstanceInterface* LevelInstanceInterface) { if (AActor* LevelInstanceActor = Cast(LevelInstanceInterface)) { OutSelectionIndex = LevelInstanceActor == SelectedActor ? OutSelectionHierarchy.Num() : OutSelectionIndex; OutSelectionHierarchy.Add(LevelInstanceActor); } return true; }); return OutSelectionIndex != INDEX_NONE && OutSelectionHierarchy.Num() > 1; } } } } } } return false; } void SelectActorAt(int32 SelectionIndex, const TArray& SelectionHierarchy) { if (SelectionHierarchy.IsValidIndex(SelectionIndex)) { AActor* ActorToSelect = SelectionHierarchy[SelectionIndex]; if (ActorToSelect->SupportsSubRootSelection()) { if (UTypedElementSelectionSet* SelectionSet = GEditor->GetSelectedActors()->GetElementSelectionSet()) { const FTypedElementSelectionOptions SelectionOptions = FTypedElementSelectionOptions() .SetAllowHidden(true) .SetWarnIfLocked(false) .SetAllowLegacyNotifications(false) .SetAllowSubRootSelection(true); FTypedElementHandle ActorElementHandle = UEngineElementsLibrary::AcquireEditorActorElementHandle(SelectionHierarchy[SelectionIndex]); if(SelectionSet->CanSelectElement(ActorElementHandle, SelectionOptions)) { SelectionSet->SetSelection(MakeArrayView(&ActorElementHandle,1), SelectionOptions); } } } } } }; ULevelInstanceEditorMode::ULevelInstanceEditorMode() : UEdMode() { Info = FEditorModeInfo(ULevelInstanceEditorMode::EM_LevelInstanceEditorModeId, LOCTEXT("LevelInstanceEditorModeName", "LevelInstanceEditorMode"), FSlateIcon(), false); bContextRestriction = true; } ULevelInstanceEditorMode::~ULevelInstanceEditorMode() { } void ULevelInstanceEditorBehaviorSource::Initialize(UEditorInteractiveToolsContext* InteractiveToolsContext) { InputBehaviorSet = NewObject(); UMouseWheelInputBehavior* MouseWheelInputBehavior = NewObject(); MouseWheelBehaviorTarget = MakeUnique(InteractiveToolsContext); MouseWheelInputBehavior->Initialize(MouseWheelBehaviorTarget.Get()); MouseWheelInputBehavior->ModifierCheckFunc = [](const FInputDeviceState&) { return GetDefault()->bIsViewportSubSelectionEnabled; }; InputBehaviorSet->Add(MouseWheelInputBehavior); } TScriptInterface ULevelInstanceEditorMode::CreateDefaultModeBehaviorSource(UEditorInteractiveToolsContext* InteractiveToolContext) { ULevelInstanceEditorBehaviorSource* NewBehaviorSource = NewObject(); NewBehaviorSource->Initialize(InteractiveToolContext); return NewBehaviorSource; } void ULevelInstanceEditorMode::OnPreBeginPIE(bool bSimulate) { ExitModeCommand(); } void ULevelInstanceEditorMode::UpdateEngineShowFlags() { for (FLevelEditorViewportClient* LevelVC : GEditor->GetLevelViewportClients()) { if (LevelVC && LevelVC->GetWorld()) { if(ULevelInstanceSubsystem* LevelInstanceSubsystem = LevelVC->GetWorld()->GetSubsystem()) { const bool bEditingLevelInstance = IsContextRestrictedForWorld(LevelVC->GetWorld()); // Make sure we update both Game/Editor showflags LevelVC->EngineShowFlags.EditingLevelInstance = bEditingLevelInstance; LevelVC->LastEngineShowFlags.EditingLevelInstance = bEditingLevelInstance; } } } } void ULevelInstanceEditorMode::Enter() { UEdMode::Enter(); UpdateEngineShowFlags(); if (UEditorInteractiveToolsContext* InteractiveToolContext = GetInteractiveToolsContext(EToolsContextScope::EdMode)) { // UEdMode::Exit() can be deferred to on Tick which can cause potentially out of order Enter/Exit calls. // In the event that this does happen, we reregister the ModeBehaviorSource to prevent crashes, but ensure // because the subsequent Exit will deregister the newly reregistered source and break viewport sub selection. if (!ensureMsgf(ModeBehaviorSource == nullptr, TEXT("ModeBehaviorSource is already registered. Re-registering a new behavior source."))) { InteractiveToolContext->InputRouter->DeregisterSource(ModeBehaviorSource.GetInterface()); ModeBehaviorSource = nullptr; } // Here we create a BehaviorSource specific to the Level Instance Editor Mode, for now it is the same type as the default one. ModeBehaviorSource = CreateDefaultModeBehaviorSource(InteractiveToolContext); InteractiveToolContext->InputRouter->RegisterSource(ModeBehaviorSource.GetInterface()); } FEditorDelegates::PreBeginPIE.AddUObject(this, &ULevelInstanceEditorMode::OnPreBeginPIE); } void ULevelInstanceEditorMode::Exit() { if (UEditorInteractiveToolsContext* InteractiveToolContext = GetInteractiveToolsContext(EToolsContextScope::EdMode)) { InteractiveToolContext->InputRouter->DeregisterSource(ModeBehaviorSource.GetInterface()); ModeBehaviorSource = nullptr; } UEdMode::Exit(); UpdateEngineShowFlags(); bContextRestriction = true; FEditorDelegates::PreBeginPIE.RemoveAll(this); } void ULevelInstanceEditorMode::CreateToolkit() { Toolkit = MakeShared(); } void ULevelInstanceEditorMode::ModeTick(float DeltaTime) { Super::ModeTick(DeltaTime); UpdateEngineShowFlags(); } bool ULevelInstanceEditorMode::IsCompatibleWith(FEditorModeID OtherModeID) const { return (OtherModeID != FBuiltinEditorModes::EM_Foliage) && ((OtherModeID != FBuiltinEditorModes::EM_Landscape) || ULevelInstanceSettings::Get()->IsLevelInstanceEditCompatibleWithLandscapeEdit()); } void ULevelInstanceEditorMode::BindCommands() { UEdMode::BindCommands(); const TSharedRef& CommandList = Toolkit->GetToolkitCommands(); const FLevelInstanceEditorModeCommands& Commands = FLevelInstanceEditorModeCommands::Get(); CommandList->MapAction( Commands.ExitMode, FExecuteAction::CreateUObject(this, &ULevelInstanceEditorMode::ExitModeCommand), FCanExecuteAction::CreateLambda([&] { // If some actors are selected make sure we don't interfere with the SelectNone command if(GEditor->GetSelectedActors()->Num() > 0) { const FInputChord& SelectNonePrimary = FLevelEditorCommands::Get().SelectNone->GetActiveChord(EMultipleKeyBindingIndex::Primary).Get(); if (SelectNonePrimary.IsValidChord() && Commands.ExitMode->HasActiveChord(SelectNonePrimary)) { return false; } const FInputChord& SelectNoneSecondary = FLevelEditorCommands::Get().SelectNone->GetActiveChord(EMultipleKeyBindingIndex::Secondary).Get(); if (SelectNoneSecondary.IsValidChord() && Commands.ExitMode->HasActiveChord(SelectNoneSecondary)) { return false; } } return true; })); CommandList->MapAction( Commands.ToggleContextRestriction, FExecuteAction::CreateUObject(this, &ULevelInstanceEditorMode::ToggleContextRestrictionCommand), FCanExecuteAction(), FIsActionChecked::CreateUObject(this, &ULevelInstanceEditorMode::IsContextRestrictionCommandEnabled)); } bool ULevelInstanceEditorMode::IsEditingDisallowed(AActor* InActor) const { return IsSelectionDisallowed(InActor, true); } bool ULevelInstanceEditorMode::IsSelectionDisallowed(AActor* InActor, bool bInSelection) const { UWorld* World = InActor->GetWorld(); const bool bRestrict = bInSelection && IsContextRestrictedForWorld(World); if (bRestrict) { check(World); if (ULevelInstanceSubsystem* LevelInstanceSubsystem = UWorld::GetSubsystem(World)) { const ILevelInstanceInterface* PropertyOverrideLevelInstance = LevelInstanceSubsystem->GetEditingPropertyOverridesLevelInstance(); const ILevelInstanceInterface* EditLevelInstance = LevelInstanceSubsystem->GetEditingLevelInstance(); if (ILevelInstanceInterface* LevelInstance = Cast(InActor)) { // If Actor is itself a Level Instance and is one of the edits, allow selection if (LevelInstance == PropertyOverrideLevelInstance || LevelInstance == EditLevelInstance) { return false; } } const ILevelInstanceInterface* ParentLevelInstance = LevelInstanceSubsystem->GetParentLevelInstance(InActor); auto IsAncestorOrSelf = [LevelInstanceSubsystem](const ILevelInstanceInterface* LevelInstance, const ILevelInstanceInterface* Ancestor) { while (LevelInstance != nullptr) { if (LevelInstance == Ancestor) { return true; } LevelInstance = LevelInstanceSubsystem->GetParentLevelInstance(CastChecked(LevelInstance)); } return false; }; // If we have a PropertyOverride Edit in progress, actor can be selected if it is part of the PropertyOverrides hierarchy if (PropertyOverrideLevelInstance) { return !IsAncestorOrSelf(ParentLevelInstance, PropertyOverrideLevelInstance); } // If we have a Edit in progress, actor can be selected if it is part of the Edit hierarchy if (EditLevelInstance) { return !IsAncestorOrSelf(ParentLevelInstance, EditLevelInstance); } return false; } } return bRestrict; } void ULevelInstanceEditorMode::ExitModeCommand() { // Ignore command when any modal window is open if (FSlateApplication::IsInitialized() && FSlateApplication::Get().GetActiveModalWindow().IsValid()) { return; } if (ILevelInstanceEditorModule* EditorModule = FModuleManager::GetModulePtr("LevelInstanceEditor")) { EditorModule->BroadcastTryExitEditorMode(); } } void ULevelInstanceEditorMode::ToggleContextRestrictionCommand() { bContextRestriction = !bContextRestriction; UpdateEngineShowFlags(); FLevelEditorModule& LevelEditorModule = FModuleManager::GetModuleChecked("LevelEditor"); if (TSharedPtr FirstLevelEditor = LevelEditorModule.GetFirstLevelEditor()) { FirstLevelEditor->GetEditorModeManager().BroadcastIsEditingDisallowedChanged(); } } bool ULevelInstanceEditorMode::IsContextRestrictionCommandEnabled() const { return bContextRestriction; } bool ULevelInstanceEditorMode::IsContextRestrictedForWorld(UWorld* InWorld) const { if (ULevelInstanceSubsystem* LevelInstanceSubsystem = InWorld? InWorld->GetSubsystem() : nullptr) { if (ILevelInstanceInterface* EditingPropertyOverrides = LevelInstanceSubsystem->GetEditingPropertyOverridesLevelInstance()) { // Always restrict outside selection while editing property overrides return true; } else if (ILevelInstanceInterface* EditingLevelInstance = LevelInstanceSubsystem->GetEditingLevelInstance()) { return bContextRestriction && LevelInstanceSubsystem->GetLevelInstanceLevel(EditingLevelInstance) == InWorld->GetCurrentLevel(); } } return false; } #undef LOCTEXT_NAMESPACE