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

1942 lines
67 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "ActorBrowsingMode.h"
#include "ActorBrowsingModeCommands.h"
#include "Engine/Blueprint.h"
#include "SceneOutlinerFilters.h"
#include "SceneOutlinerModule.h"
#include "SceneOutlinerMenuContext.h"
#include "ActorHierarchy.h"
#include "SceneOutlinerDelegates.h"
#include "Editor.h"
#include "Editor/GroupActor.h"
#include "UnrealEdGlobals.h"
#include "ToolMenus.h"
#include "Engine/Selection.h"
#include "Engine/World.h"
#include "Editor/UnrealEdEngine.h"
#include "HAL/PlatformApplicationMisc.h"
#include "HAL/FileManager.h"
#include "SceneOutlinerDragDrop.h"
#include "EditorActorFolders.h"
#include "EditorFolderUtils.h"
#include "ActorEditorUtils.h"
#include "DragAndDrop/ActorDragDropOp.h"
#include "DragAndDrop/ActorDragDropGraphEdOp.h"
#include "DragAndDrop/FolderDragDropOp.h"
#include "Logging/MessageLog.h"
#include "SSocketChooser.h"
#include "ActorFolderPickingMode.h"
#include "ActorTreeItem.h"
#include "LevelTreeItem.h"
#include "ActorFolderTreeItem.h"
#include "WorldTreeItem.h"
#include "ComponentTreeItem.h"
#include "ActorBrowsingModeSettings.h"
#include "ActorDescTreeItem.h"
#include "ScopedTransaction.h"
#include "LevelInstance/LevelInstanceInterface.h"
#include "LevelInstance/LevelInstanceSubsystem.h"
#include "LevelInstance/LevelInstanceEditorInstanceActor.h"
#include "WorldPartition/WorldPartition.h"
#include "WorldPartition/WorldPartitionSubsystem.h"
#include "WorldPartition/DataLayer/DataLayerManager.h"
#include "WorldPartition/IWorldPartitionEditorModule.h"
#include "WorldPartition/ActorDescContainerInstance.h"
#include "WorldPartition/WorldPartitionActorDescInstance.h"
#include "Subsystems/ActorEditorContextSubsystem.h"
#include "EditorLevelUtils.h"
#include "EditorViewportCommands.h"
#include "Misc/ScopedSlowTask.h"
#include "Elements/Framework/EngineElementsLibrary.h"
#include "Elements/Framework/TypedElementHandle.h"
#include "Framework/Commands/GenericCommands.h"
DEFINE_LOG_CATEGORY_STATIC(LogActorBrowser, Log, All);
#define LOCTEXT_NAMESPACE "SceneOutliner_ActorBrowsingMode"
using FActorFilter = TSceneOutlinerPredicateFilter<FActorTreeItem>;
using FActorDescFilter = TSceneOutlinerPredicateFilter<FActorDescTreeItem>;
using FFolderFilter = TSceneOutlinerPredicateFilter<FFolderTreeItem>;
FActorBrowsingMode::FActorBrowsingMode(SSceneOutliner* InSceneOutliner, TWeakObjectPtr<UWorld> InSpecifiedWorldToDisplay)
: FActorModeInteractive(FActorModeParams(InSceneOutliner, InSpecifiedWorldToDisplay, /* bHideComponents */ true, /* bHideLevelInstanceHierarchy */ false, /* bHideUnloadedActors */ false, /* bHideEmptyFolders */ false))
, FilteredActorCount(0)
{
WorldPartitionEditorModule = FModuleManager::GetModulePtr<IWorldPartitionEditorModule>("WorldPartitionEditor");
// Capture selection changes of bones from mesh selection in fracture tools
FSceneOutlinerDelegates::Get().OnComponentsUpdated.AddRaw(this, &FActorBrowsingMode::OnComponentsUpdated);
GEngine->OnLevelActorDeleted().AddRaw(this, &FActorBrowsingMode::OnLevelActorDeleted);
GEditor->OnSelectUnloadedActorsEvent().AddRaw(this, &FActorBrowsingMode::OnSelectUnloadedActors);
UActorEditorContextSubsystem::Get()->OnActorEditorContextSubsystemChanged().AddRaw(this, &FActorBrowsingMode::OnActorEditorContextSubsystemChanged);
FEditorDelegates::OnEditCutActorsBegin.AddRaw(this, &FActorBrowsingMode::OnEditCutActorsBegin);
FEditorDelegates::OnEditCutActorsEnd.AddRaw(this, &FActorBrowsingMode::OnEditCutActorsEnd);
FEditorDelegates::OnEditCopyActorsBegin.AddRaw(this, &FActorBrowsingMode::OnEditCopyActorsBegin);
FEditorDelegates::OnEditCopyActorsEnd.AddRaw(this, &FActorBrowsingMode::OnEditCopyActorsEnd);
FEditorDelegates::OnEditPasteActorsBegin.AddRaw(this, &FActorBrowsingMode::OnEditPasteActorsBegin);
FEditorDelegates::OnEditPasteActorsEnd.AddRaw(this, &FActorBrowsingMode::OnEditPasteActorsEnd);
FEditorDelegates::OnDuplicateActorsBegin.AddRaw(this, &FActorBrowsingMode::OnDuplicateActorsBegin);
FEditorDelegates::OnDuplicateActorsEnd.AddRaw(this, &FActorBrowsingMode::OnDuplicateActorsEnd);
FEditorDelegates::OnDeleteActorsBegin.AddRaw(this, &FActorBrowsingMode::OnDeleteActorsBegin);
FEditorDelegates::OnDeleteActorsEnd.AddRaw(this, &FActorBrowsingMode::OnDeleteActorsEnd);
UActorBrowserConfig::Initialize();
UActorBrowserConfig::Get()->LoadEditorConfig();
// Get a MutableConfig here to force create a config for the current outliner if it doesn't exist
const FActorBrowsingModeConfig* SavedSettings = GetMutableConfig();
// Create a local struct to use the default values if this outliner doesn't want to save configs
FActorBrowsingModeConfig LocalSettings;
// If this outliner doesn't want to save config (OutlinerIdentifier is empty, use the defaults)
if (SavedSettings)
{
LocalSettings = *SavedSettings;
}
bHideLevelInstanceHierarchy = LocalSettings.bHideLevelInstanceHierarchy;
InSceneOutliner->SetShowTransient(!LocalSettings.bHideTemporaryActors);
bShouldUpdateContentWhileInPIEFocused = LocalSettings.bShouldUpdateContentWhileInPIEFocused;
// Get the OutlinerModule to register FilterInfos with the FilterInfoMap
FSceneOutlinerFilterInfo ShowOnlySelectedActorsInfo(LOCTEXT("ToggleShowOnlySelected", "Only Selected"), LOCTEXT("ToggleShowOnlySelectedToolTip", "When enabled, only displays actors that are currently selected."), LocalSettings.bShowOnlySelectedActors, FCreateSceneOutlinerFilter::CreateStatic(&FActorBrowsingMode::CreateShowOnlySelectedActorsFilter));
ShowOnlySelectedActorsInfo.OnToggle().AddLambda([this](bool bIsActive)
{
FActorBrowsingModeConfig* Settings = GetMutableConfig();
if(Settings)
{
Settings->bShowOnlySelectedActors = bIsActive;
SaveConfig();
}
});
FilterInfoMap.Add(TEXT("ShowOnlySelectedActors"), ShowOnlySelectedActorsInfo);
FSceneOutlinerFilterInfo OnlyCurrentLevelInfo(LOCTEXT("ToggleShowOnlyCurrentLevel", "Only in Current Level"), LOCTEXT("ToggleShowOnlyCurrentLevelToolTip", "When enabled, only shows Actors that are in the Current Level."), LocalSettings.bShowOnlyActorsInCurrentLevel, FCreateSceneOutlinerFilter::CreateStatic(&FActorBrowsingMode::CreateIsInCurrentLevelFilter));
OnlyCurrentLevelInfo.OnToggle().AddLambda([this](bool bIsActive)
{
FActorBrowsingModeConfig* Settings = GetMutableConfig();
if(Settings)
{
Settings->bShowOnlyActorsInCurrentLevel = bIsActive;
SaveConfig();
}
});
FilterInfoMap.Add(TEXT("ShowOnlyCurrentLevel"), OnlyCurrentLevelInfo);
FSceneOutlinerFilterInfo OnlyCurrentDataLayersInfo(LOCTEXT("ToggleShowOnlyCurrentDataLayers", "Only in any Current Data Layers"), LOCTEXT("ToggleShowOnlyCurrentDataLayersToolTip", "When enabled, only shows Actors that are in any Current Data Layers."), LocalSettings.bShowOnlyActorsInCurrentDataLayers, FCreateSceneOutlinerFilter::CreateStatic(&FActorBrowsingMode::CreateIsInCurrentDataLayersFilter));
OnlyCurrentDataLayersInfo.OnToggle().AddLambda([this](bool bIsActive)
{
FActorBrowsingModeConfig* Settings = GetMutableConfig();
if (Settings)
{
Settings->bShowOnlyActorsInCurrentDataLayers = bIsActive;
SaveConfig();
}
});
FilterInfoMap.Add(TEXT("ShowOnlyCurrentDataLayers"), OnlyCurrentDataLayersInfo);
// Add a filter for unloaded actors to properly reflect the bShowOnlyActorsInCurrentDataLayers flag.
SceneOutliner->AddFilter(MakeShared<FActorDescFilter>(FActorDescTreeItem::FFilterPredicate::CreateLambda([this](const FWorldPartitionActorDescInstance* ActorDescInstance)
{
FActorBrowsingModeConfig* Settings = GetMutableConfig();
if (Settings && Settings->bShowOnlyActorsInCurrentDataLayers)
{
const UDataLayerManager* DataLayerManager = UDataLayerManager::GetDataLayerManager(RepresentingWorld.Get());
if (!DataLayerManager || DataLayerManager->GetActorEditorContextDataLayers().IsEmpty())
{
return true;
}
for(const UDataLayerInstance* const DataLayerInstance : DataLayerManager->GetDataLayerInstances(ActorDescInstance->GetDataLayerInstanceNames().ToArray()))
{
if (DataLayerInstance->IsInActorEditorContext())
{
return true;
}
}
return false;
}
return true;
}), FSceneOutlinerFilter::EDefaultBehaviour::Pass));
FSceneOutlinerFilterInfo OnlyCurrentContentBundleInfo(LOCTEXT("ToggleShowOnlyCurrentContentBundle", "Only in Current Content Bundle"), LOCTEXT("ToggleShowOnlyCurrentContentBundleToolTip", "When enabled, only shows Actors that are in the Current Content Bundle."), LocalSettings.bShowOnlyActorsInCurrentContentBundle, FCreateSceneOutlinerFilter::CreateRaw(this, &FActorBrowsingMode::CreateIsInCurrentContentBundleFilter));
OnlyCurrentContentBundleInfo.OnToggle().AddLambda([this](bool bIsActive)
{
FActorBrowsingModeConfig* Settings = GetMutableConfig();
if (Settings)
{
Settings->bShowOnlyActorsInCurrentContentBundle = bIsActive;
SaveConfig();
}
});
FilterInfoMap.Add(TEXT("ShowOnlyCurrentContentBundle"), OnlyCurrentContentBundleInfo);
// Add a filter for unloaded actors to properly reflect the bShowOnlyActorsInCurrentContentBundle flag.
SceneOutliner->AddFilter(MakeShared<FActorDescFilter>(FActorDescTreeItem::FFilterPredicate::CreateLambda([this](const FWorldPartitionActorDescInstance* ActorDescInstance)
{
FActorBrowsingModeConfig* Settings = GetMutableConfig();
if (Settings && Settings->bShowOnlyActorsInCurrentContentBundle)
{
if (!WorldPartitionEditorModule || !WorldPartitionEditorModule->IsEditingContentBundle())
{
return true;
}
return ActorDescInstance->GetContentBundleGuid().IsValid() && WorldPartitionEditorModule->IsEditingContentBundle(ActorDescInstance->GetContentBundleGuid());
}
return true;
}), FSceneOutlinerFilter::EDefaultBehaviour::Pass));
bHideComponents = LocalSettings.bHideActorComponents;
FSceneOutlinerFilterInfo HideComponentsInfo(LOCTEXT("ToggleHideActorComponents", "Hide Actor Components"), LOCTEXT("ToggleHideActorComponentsToolTip", "When enabled, hides components belonging to actors."), LocalSettings.bHideActorComponents, FCreateSceneOutlinerFilter::CreateStatic(&FActorBrowsingMode::CreateHideComponentsFilter));
HideComponentsInfo.OnToggle().AddLambda([this](bool bIsActive)
{
FActorBrowsingModeConfig* Settings = GetMutableConfig();
if(Settings)
{
Settings->bHideActorComponents = bHideComponents = bIsActive;
SaveConfig();
}
if (auto ActorHierarchy = StaticCast<FActorHierarchy*>(Hierarchy.Get()))
{
ActorHierarchy->SetShowingComponents(!bIsActive);
}
});
FilterInfoMap.Add(TEXT("HideComponentsFilter"), HideComponentsInfo);
bHideUnloadedActors = LocalSettings.bHideUnloadedActors;
FSceneOutlinerFilterInfo HideUnloadedActorsInfo(LOCTEXT("ToggleHideUnloadedActors", "Hide Unloaded Actors"), LOCTEXT("ToggleHideUnloadedActorsToolTip", "When enabled, hides all unloaded world partition actors."), LocalSettings.bHideUnloadedActors, FCreateSceneOutlinerFilter::CreateStatic(&FActorBrowsingMode::CreateHideUnloadedActorsFilter));
HideUnloadedActorsInfo.OnToggle().AddLambda([this] (bool bIsActive)
{
FActorBrowsingModeConfig* Settings = GetMutableConfig();
if(Settings)
{
Settings->bHideUnloadedActors = bHideUnloadedActors = bIsActive;
SaveConfig();
}
if (auto ActorHierarchy = StaticCast<FActorHierarchy*>(Hierarchy.Get()))
{
ActorHierarchy->SetShowingUnloadedActors(!bIsActive);
}
});
FilterInfoMap.Add(TEXT("HideUnloadedActorsFilter"), HideUnloadedActorsInfo);
bHideEmptyFolders = LocalSettings.bHideEmptyFolders;
FSceneOutlinerFilterInfo HideEmptyFoldersInfo(LOCTEXT("ToggleHideEmptyFolders", "Hide Empty Folders"), LOCTEXT("ToggleHideEmptyFoldersToolTip", "When enabled, hides all empty folders."), LocalSettings.bHideEmptyFolders, FCreateSceneOutlinerFilter::CreateStatic(&FActorBrowsingMode::CreateHideEmptyFoldersFilter));
HideEmptyFoldersInfo.OnToggle().AddLambda([this](bool bIsActive)
{
FActorBrowsingModeConfig* Settings = GetMutableConfig();
if (Settings)
{
Settings->bHideEmptyFolders = bHideEmptyFolders = bIsActive;
SaveConfig();
}
if (auto ActorHierarchy = StaticCast<FActorHierarchy*>(Hierarchy.Get()))
{
ActorHierarchy->SetShowingEmptyFolders(!bIsActive);
}
});
FilterInfoMap.Add(TEXT("HideEmptyFoldersFilter"), HideEmptyFoldersInfo);
// Add a filter which sets the interactive mode of LevelInstance items and their children
SceneOutliner->AddInteractiveFilter(MakeShared<FActorFilter>(FActorTreeItem::FFilterPredicate::CreateLambda([this](const AActor* Actor)
{
if (Actor->SupportsSubRootSelection())
{
return true;
}
if (!bHideLevelInstanceHierarchy)
{
if (const ULevelInstanceSubsystem* LevelInstanceSubsystem = RepresentingWorld->GetSubsystem<ULevelInstanceSubsystem>())
{
// if actor has a valid parent and the parent is not being edited,
// then the actor should not be selectable.
if (const ILevelInstanceInterface* ParentLevelInstance = LevelInstanceSubsystem->GetParentLevelInstance(Actor))
{
if (!LevelInstanceSubsystem->IsEditingLevelInstance(ParentLevelInstance) && !LevelInstanceSubsystem->IsEditingLevelInstancePropertyOverrides(ParentLevelInstance))
{
return false;
}
}
}
}
return true;
}), FSceneOutlinerFilter::EDefaultBehaviour::Pass));
bAlwaysFrameSelection = LocalSettings.bAlwaysFrameSelection;
bCollapseOutlinerTreeOnNewSelection = LocalSettings.bCollapseOutlinerTreeOnNewSelection;
Rebuild();
}
FActorBrowsingMode::~FActorBrowsingMode()
{
if (RepresentingWorld.IsValid())
{
if (UWorldPartition* const WorldPartition = RepresentingWorld->GetWorldPartition())
{
WorldPartition->OnActorDescInstanceRemovedEvent.RemoveAll(this);
}
}
FSceneOutlinerDelegates::Get().OnComponentsUpdated.RemoveAll(this);
GEngine->OnLevelActorDeleted().RemoveAll(this);
GEditor->OnSelectUnloadedActorsEvent().RemoveAll(this);
if (UActorEditorContextSubsystem* ActorEditorContextSubsystem = UActorEditorContextSubsystem::Get())
{
ActorEditorContextSubsystem->OnActorEditorContextSubsystemChanged().RemoveAll(this);
}
FEditorDelegates::OnEditCutActorsBegin.RemoveAll(this);
FEditorDelegates::OnEditCutActorsEnd.RemoveAll(this);
FEditorDelegates::OnEditCopyActorsBegin.RemoveAll(this);
FEditorDelegates::OnEditCopyActorsEnd.RemoveAll(this);
FEditorDelegates::OnEditPasteActorsBegin.RemoveAll(this);
FEditorDelegates::OnEditPasteActorsEnd.RemoveAll(this);
FEditorDelegates::OnDuplicateActorsBegin.RemoveAll(this);
FEditorDelegates::OnDuplicateActorsEnd.RemoveAll(this);
FEditorDelegates::OnDeleteActorsBegin.RemoveAll(this);
FEditorDelegates::OnDeleteActorsEnd.RemoveAll(this);
}
void FActorBrowsingMode::Rebuild()
{
// If we used to be representing a wp world, unbind delegates before rebuilding begins
if (RepresentingWorld.IsValid())
{
if (UWorldPartition* const WorldPartition = RepresentingWorld->GetWorldPartition())
{
WorldPartition->OnActorDescInstanceRemovedEvent.RemoveAll(this);
}
}
FActorMode::Rebuild();
FilteredActorCount = 0;
FilteredUnloadedActorCount = 0;
ApplicableUnloadedActors.Empty();
ApplicableActors.Empty();
bRepresentingWorldGameWorld = RepresentingWorld.IsValid() && RepresentingWorld->IsGameWorld();
bRepresentingWorldPartitionedWorld = RepresentingWorld.IsValid() && RepresentingWorld->IsPartitionedWorld();
if (bRepresentingWorldPartitionedWorld)
{
UWorldPartition* const WorldPartition = RepresentingWorld->GetWorldPartition();
WorldPartition->OnActorDescInstanceRemovedEvent.AddRaw(this, &FActorBrowsingMode::OnActorDescInstanceRemoved);
}
}
FText FActorBrowsingMode::GetStatusText() const
{
if (!RepresentingWorld.IsValid())
{
return FText();
}
// The number of actors in the outliner before applying the text filter
const int32 TotalActorCount = ApplicableActors.Num() + ApplicableUnloadedActors.Num();
const int32 SelectedActorCount = SceneOutliner->GetSelection().Num<FActorTreeItem, FActorDescTreeItem>();
if (!SceneOutliner->IsTextFilterActive())
{
if (SelectedActorCount == 0) //-V547
{
if (bRepresentingWorldPartitionedWorld)
{
return FText::Format(LOCTEXT("ShowingAllLoadedActorsFmt", "{0} actors ({1} loaded)"), FText::AsNumber(FilteredActorCount), FText::AsNumber(FilteredActorCount - FilteredUnloadedActorCount));
}
else
{
return FText::Format(LOCTEXT("ShowingAllActorsFmt", "{0} actors"), FText::AsNumber(FilteredActorCount));
}
}
else
{
return FText::Format(LOCTEXT("ShowingAllActorsSelectedFmt", "{0} actors ({1} selected)"), FText::AsNumber(FilteredActorCount), FText::AsNumber(SelectedActorCount));
}
}
else if (SceneOutliner->IsTextFilterActive() && FilteredActorCount == 0)
{
return FText::Format(LOCTEXT("ShowingNoActorsFmt", "No matching actors ({0} total)"), FText::AsNumber(TotalActorCount));
}
else if (SelectedActorCount != 0) //-V547
{
return FText::Format(LOCTEXT("ShowingOnlySomeActorsSelectedFmt", "Showing {0} of {1} actors ({2} selected)"), FText::AsNumber(FilteredActorCount), FText::AsNumber(TotalActorCount), FText::AsNumber(SelectedActorCount));
}
else
{
return FText::Format(LOCTEXT("ShowingOnlySomeActorsFmt", "Showing {0} of {1} actors"), FText::AsNumber(FilteredActorCount), FText::AsNumber(TotalActorCount));
}
}
FSlateColor FActorBrowsingMode::GetStatusTextColor() const
{
if (!SceneOutliner->IsTextFilterActive())
{
return FSlateColor::UseForeground();
}
else if (FilteredActorCount == 0)
{
return FAppStyle::Get().GetSlateColor("Colors.AccentRed");
}
else
{
return FAppStyle::Get().GetSlateColor("Colors.AccentGreen");
}
}
void FActorBrowsingMode::OnActorEditorContextSubsystemChanged()
{
// For performance reasons avoid doing full refresh if we don't have an active filter relying on the current Actor Editor Context
if (const FActorBrowsingModeConfig* Settings = GetConstConfig(); Settings && (Settings->bShowOnlyActorsInCurrentDataLayers || Settings->bShowOnlyActorsInCurrentContentBundle))
{
SceneOutliner->FullRefresh();
}
}
void FActorBrowsingMode::OnToggleAlwaysFrameSelection()
{
bAlwaysFrameSelection = !bAlwaysFrameSelection;
FActorBrowsingModeConfig* Settings = GetMutableConfig();
if(Settings)
{
Settings->bAlwaysFrameSelection = bAlwaysFrameSelection;
SaveConfig();
}
}
bool FActorBrowsingMode::ShouldAlwaysFrameSelection() const
{
return bAlwaysFrameSelection;
}
void FActorBrowsingMode::OnToggleHideTemporaryActors()
{
FActorBrowsingModeConfig* Settings = GetMutableConfig();
if (Settings)
{
Settings->bHideTemporaryActors = !Settings->bHideTemporaryActors;
SaveConfig();
SceneOutliner->SetShowTransient(!Settings->bHideTemporaryActors);
SceneOutliner->FullRefresh();
}
}
bool FActorBrowsingMode::ShouldHideTemporaryActors() const
{
const FActorBrowsingModeConfig* Settings = GetConstConfig();
if (Settings)
{
return Settings->bHideTemporaryActors;
}
return false;
}
void FActorBrowsingMode::OnToggleHideLevelInstanceHierarchy()
{
bHideLevelInstanceHierarchy = !bHideLevelInstanceHierarchy;
FActorBrowsingModeConfig* Settings = GetMutableConfig();
if (Settings)
{
Settings->bHideLevelInstanceHierarchy = bHideLevelInstanceHierarchy;
SaveConfig();
}
if (auto ActorHierarchy = StaticCast<FActorHierarchy*>(Hierarchy.Get()))
{
ActorHierarchy->SetShowingLevelInstances(!bHideLevelInstanceHierarchy);
}
SceneOutliner->FullRefresh();
}
bool FActorBrowsingMode::ShouldHideLevelInstanceHierarchy() const
{
const FActorBrowsingModeConfig* Settings = GetConstConfig();
if (Settings)
{
return Settings->bHideLevelInstanceHierarchy;
}
return false;
}
void FActorBrowsingMode::InitializeViewMenuExtender(TSharedPtr<FExtender> Extender)
{
FActorModeInteractive::InitializeViewMenuExtender(Extender);
Extender->AddMenuExtension(SceneOutliner::ExtensionHooks::Show, EExtensionHook::First, nullptr, FMenuExtensionDelegate::CreateLambda([this](FMenuBuilder& MenuBuilder)
{
MenuBuilder.AddMenuEntry(
LOCTEXT("ToggleHideTemporaryActors", "Hide Temporary Actors"),
LOCTEXT("ToggleHideTemporaryActorsToolTip", "When enabled, hides temporary/run-time Actors."),
FSlateIcon(),
FUIAction(
FExecuteAction::CreateRaw(this, &FActorBrowsingMode::OnToggleHideTemporaryActors),
FCanExecuteAction(),
FIsActionChecked::CreateRaw(this, &FActorBrowsingMode::ShouldHideTemporaryActors)
),
NAME_None,
EUserInterfaceActionType::ToggleButton
);
MenuBuilder.AddMenuEntry(
LOCTEXT("ToggleHideLevelInstanceContent", "Hide Level Instance Content"),
LOCTEXT("ToggleHideLevelInstancesToolTip", "When enabled, hides all level instance content."),
FSlateIcon(),
FUIAction(
FExecuteAction::CreateRaw(this, &FActorBrowsingMode::OnToggleHideLevelInstanceHierarchy),
FCanExecuteAction(),
FIsActionChecked::CreateRaw(this, &FActorBrowsingMode::ShouldHideLevelInstanceHierarchy)
),
NAME_None,
EUserInterfaceActionType::ToggleButton
);
}));
Extender->AddMenuExtension(SceneOutliner::ExtensionHooks::Show, EExtensionHook::After, nullptr, FMenuExtensionDelegate::CreateLambda([this](FMenuBuilder& MenuBuilder)
{
MenuBuilder.BeginSection("OutlinerSelectionOptions", LOCTEXT("OptionsHeading", "Options"));
MenuBuilder.AddMenuEntry(
LOCTEXT("AlwaysFrameSelectionLabel", "Always Frame Selection"),
LOCTEXT("AlwaysFrameSelectionTooltip", "When enabled, selecting an Actor in the Viewport also scrolls to that Actor in the Outliner."),
FSlateIcon(),
FUIAction(
FExecuteAction::CreateRaw(this, &FActorBrowsingMode::OnToggleAlwaysFrameSelection),
FCanExecuteAction(),
FIsActionChecked::CreateRaw(this, &FActorBrowsingMode::ShouldAlwaysFrameSelection)
),
NAME_None,
EUserInterfaceActionType::ToggleButton
);
MenuBuilder.AddMenuEntry(
LOCTEXT("FolderDoubleClickToggleCurrentFolderLabel", "Double Click toggles Current Folder"),
LOCTEXT(
"FolderDoubleClickToggleCurrentFolderTooltip",
"When enabled, double clicking on a folder will result in it toggling its Current Folder state"
"instead of its expansion."
),
FSlateIcon(),
FUIAction(
FExecuteAction::CreateRaw(this, &FActorBrowsingMode::OnToggleFolderDoubleClickMarkCurrentFolder),
FCanExecuteAction(),
FIsActionChecked::CreateRaw(this, &FActorBrowsingMode::DoesFolderDoubleClickMarkCurrentFolder)
),
NAME_None,
EUserInterfaceActionType::ToggleButton
);
MenuBuilder.AddMenuEntry(
LOCTEXT("ShouldUpdateContentWhileInPIEFocusedLabel", "Update In PIE"),
LOCTEXT("ShouldUpdateContentWhileInPIEFocusedLabelTooltip", "When enabled, the Outliner will update in PIE."),
FSlateIcon(),
FUIAction(
FExecuteAction::CreateRaw(this, &FActorBrowsingMode::OnToggleShouldUpdateContentWhileInPIEFocused),
FCanExecuteAction(),
FIsActionChecked::CreateRaw(this, &FActorBrowsingMode::ShouldUpdateContentWhileInPIEFocused),
FIsActionButtonVisible::CreateLambda([this]() { return (WorldPartitionEditorModule ? !WorldPartitionEditorModule->GetDisablePIE() : true); })
),
NAME_None,
EUserInterfaceActionType::ToggleButton
);
MenuBuilder.AddMenuEntry(
LOCTEXT("CollapseOutlinerTreeOnNewSelectionLabel", "Collapse Hierarchy on Viewport Selection"),
LOCTEXT("CollapseOutlinerTreeOnNewSelectionTooltip", "When enabled, all items except the one that was just selected are collapsed."),
FSlateIcon(),
FUIAction(
FExecuteAction::CreateRaw(this, &FActorBrowsingMode::OnToggleCollapseOutlinerTreeOnNewSelection),
FCanExecuteAction(),
FIsActionChecked::CreateRaw(this, &FActorBrowsingMode::CollapseOutlinerTreeOnNewSelection)
),
NAME_None,
EUserInterfaceActionType::ToggleButton
);
MenuBuilder.EndSection();
MenuBuilder.BeginSection("World", LOCTEXT("ShowWorldHeading", "World"));
MenuBuilder.AddSubMenu(
LOCTEXT("ChooseWorldSubMenu", "Choose World"),
LOCTEXT("ChooseWorldSubMenuToolTip", "Choose the world to display in the outliner."),
FNewMenuDelegate::CreateRaw(this, &FActorMode::BuildWorldPickerMenu)
);
MenuBuilder.EndSection();
}));
}
TSharedRef<FSceneOutlinerFilter> FActorBrowsingMode::CreateShowOnlySelectedActorsFilter()
{
auto IsActorSelected = [](const AActor* InActor)
{
return InActor && InActor->IsSelected();
};
return MakeShareable(new FActorFilter(FActorTreeItem::FFilterPredicate::CreateStatic(IsActorSelected), FSceneOutlinerFilter::EDefaultBehaviour::Fail, FActorTreeItem::FFilterPredicate::CreateStatic(IsActorSelected)));
}
TSharedRef<FSceneOutlinerFilter> FActorBrowsingMode::CreateIsInCurrentLevelFilter()
{
return MakeShareable(new TSceneOutlinerPredicateFilter<ISceneOutlinerTreeItem>(ISceneOutlinerTreeItem::FFilterPredicate::CreateStatic([](const ISceneOutlinerTreeItem& InItem)
{
if (const FActorTreeItem* ActorItem = InItem.CastTo<FActorTreeItem>())
{
if (const AActor* Actor = ActorItem->Actor.Get(); Actor && Actor->GetWorld())
{
if (const ILevelInstanceInterface* LevelInstance = Cast<ILevelInstanceInterface>(Actor))
{
if (LevelInstance->IsEditing())
{
return Actor->GetWorld()->GetCurrentLevel() == LevelInstance->GetLoadedLevel();
}
}
return Actor->GetLevel()->IsCurrentLevel();
}
}
else if (const FActorFolderTreeItem* FolderItem = InItem.CastTo<FActorFolderTreeItem>())
{
if (const UWorld* FolderWorld = FolderItem->World.Get())
{
if (ULevel* FolderLevel = FolderItem->GetFolder().GetRootObjectAssociatedLevel())
{
return FolderLevel->IsCurrentLevel();
}
}
}
else if (const FComponentTreeItem* ComponentItem = InItem.CastTo<FComponentTreeItem>())
{
if (const UActorComponent* Component = ComponentItem->Component.Get(); Component && Component->GetComponentLevel())
{
return Component->GetComponentLevel()->IsCurrentLevel();
}
}
return false;
}), FSceneOutlinerFilter::EDefaultBehaviour::Pass));
}
TSharedRef<FSceneOutlinerFilter> FActorBrowsingMode::CreateIsInCurrentDataLayersFilter()
{
return MakeShareable(new FActorFilter(FActorTreeItem::FFilterPredicate::CreateStatic([](const AActor* InActor)
{
const UDataLayerManager* DataLayerManager = UDataLayerManager::GetDataLayerManager(InActor->GetWorld());
if (!DataLayerManager || DataLayerManager->GetActorEditorContextDataLayers().IsEmpty())
{
return true;
}
for (const UDataLayerInstance* DataLayerInstance : InActor->GetDataLayerInstances())
{
if (DataLayerInstance->IsInActorEditorContext())
{
return true;
}
}
return false;
}), FSceneOutlinerFilter::EDefaultBehaviour::Pass));
}
TSharedRef<FSceneOutlinerFilter> FActorBrowsingMode::CreateIsInCurrentContentBundleFilter()
{
return MakeShareable(new FActorFilter(FActorTreeItem::FFilterPredicate::CreateLambda([this](const AActor* InActor)
{
if (!WorldPartitionEditorModule || !WorldPartitionEditorModule->IsEditingContentBundle())
{
return true;
}
return InActor->GetContentBundleGuid().IsValid() && WorldPartitionEditorModule->IsEditingContentBundle(InActor->GetContentBundleGuid());
}), FSceneOutlinerFilter::EDefaultBehaviour::Pass));
}
TSharedRef<FSceneOutlinerFilter> FActorBrowsingMode::CreateHideComponentsFilter()
{
return MakeShared<TSceneOutlinerPredicateFilter<FComponentTreeItem>>(TSceneOutlinerPredicateFilter<FComponentTreeItem>(
FComponentTreeItem::FFilterPredicate::CreateStatic([](const UActorComponent*) { return false; }),
FSceneOutlinerFilter::EDefaultBehaviour::Pass));
}
TSharedRef<FSceneOutlinerFilter> FActorBrowsingMode::CreateHideUnloadedActorsFilter()
{
return MakeShareable(new FActorDescFilter(FActorDescTreeItem::FFilterPredicate::CreateStatic(
[](const FWorldPartitionActorDescInstance* ActorDescInstance) { return false; }), FSceneOutlinerFilter::EDefaultBehaviour::Pass));
}
TSharedRef<FSceneOutlinerFilter> FActorBrowsingMode::CreateHideEmptyFoldersFilter()
{
return MakeShareable(new FFolderFilter(FFolderTreeItem::FFilterPredicate::CreateStatic(
[](const FFolder& Folder) { return true; }), FSceneOutlinerFilter::EDefaultBehaviour::Pass));
}
static const FName DefaultContextBaseMenuName("SceneOutliner.DefaultContextMenuBase");
static const FName DefaultContextMenuName("SceneOutliner.DefaultContextMenu");
void FActorBrowsingMode::RegisterContextMenu()
{
UToolMenus* ToolMenus = UToolMenus::Get();
if (!ToolMenus->IsMenuRegistered(DefaultContextBaseMenuName))
{
UToolMenu* Menu = ToolMenus->RegisterMenu(DefaultContextBaseMenuName);
Menu->AddDynamicSection("DynamicHierarchySection", FNewToolMenuDelegate::CreateStatic(&FActorBrowsingMode::FillDefaultContextBaseMenu));
}
if (!ToolMenus->IsMenuRegistered(DefaultContextMenuName))
{
ToolMenus->RegisterMenu(DefaultContextMenuName, DefaultContextBaseMenuName);
}
}
void FActorBrowsingMode::FillDefaultContextBaseMenu(UToolMenu* InMenu)
{
USceneOutlinerMenuContext* Context = InMenu->FindContext<USceneOutlinerMenuContext>();
if (!Context)
{
return;
}
TSharedPtr<SSceneOutliner> SharedOutliner = Context->SceneOutliner.Pin();
SSceneOutliner* SceneOutliner = SharedOutliner.Get();
if (!SceneOutliner)
{
return;
}
{
// NOTE: the name "Section" is used in many other places
FToolMenuSection& Section = InMenu->FindOrAddSection("Section");
Section.Label = LOCTEXT("HierarchySectionName", "Hierarchy");
if (Context->bShowParentTree)
{
if (Context->NumSelectedItems == 0)
{
FSceneOutlinerMenuHelper::AddMenuEntryCreateFolder(Section, *SceneOutliner);
}
else
{
if (Context->NumSelectedItems == 1)
{
SceneOutliner->GetTree().GetSelectedItems()[0]->GenerateContextMenu(InMenu, *SceneOutliner);
}
// If we've only got folders selected, show the selection and edit sub menus
if (Context->NumSelectedItems > 0 && Context->NumSelectedFolders == Context->NumSelectedItems)
{
Section.AddSubMenu(
"SelectSubMenu",
LOCTEXT("SelectSubmenu", "Select"),
LOCTEXT("SelectSubmenu_Tooltip", "Select the contents of the current selection"),
FNewToolMenuDelegate::CreateSP(SceneOutliner, &SSceneOutliner::FillSelectionSubMenu));
}
}
}
}
{
// We always create a section here, even if there is no parent so that clients can still extend the menu
FToolMenuSection& MainSection = InMenu->AddSection("MainSection", LOCTEXT("OutlinerSectionName", "Outliner"));
// Don't add any of these menu items if we're not showing the parent tree
// Can't move worlds or level blueprints
if (Context->bShowParentTree && Context->NumSelectedItems > 0 && Context->NumWorldsSelected == 0)
{
MainSection.AddSubMenu(
"MoveActorsTo",
LOCTEXT("MoveActorsTo", "Move To"),
LOCTEXT("MoveActorsTo_Tooltip", "Move selection to another folder"),
FNewToolMenuDelegate::CreateSP(SceneOutliner, &SSceneOutliner::FillFoldersSubMenu));
}
if (Context->bShowParentTree && Context->NumSelectedItems > 0)
{
// Only add the menu option to wp levels
if (!Context->bRepresentingGameWorld && Context->bRepresentingPartitionedWorld)
{
MainSection.AddMenuEntry(
"ForceLoadItems",
LOCTEXT("ForceLoad", "Force Load"),
LOCTEXT("ForceLoadTooltip", "Keep the selected items loaded in the editor even when they don't overlap a loaded World Partition region"),
FSlateIcon(),
FUIAction(
FExecuteAction::CreateSP(SceneOutliner, &SSceneOutliner::PinSelectedItems),
FCanExecuteAction::CreateLambda([SceneOutliner, Context]()
{
if (Context->NumPinnedItems != Context->NumSelectedItems || Context->NumSelectedFolders > 0)
{
return SceneOutliner->CanPinSelectedItems();
}
return false;
})));
MainSection.AddMenuEntry(
"ReleaseForceLoadItems",
LOCTEXT("ReleaseForceLoad", "Release Force Load"),
LOCTEXT("ReleaseForceLoadTooltip", "Allow the World Partition system to load and unload the selected items automatically"),
FSlateIcon(),
FUIAction(
FExecuteAction::CreateSP(SceneOutliner, &SSceneOutliner::UnpinSelectedItems),
FCanExecuteAction::CreateLambda([SceneOutliner, Context]()
{
if (Context->NumPinnedItems != 0 || Context->NumSelectedFolders > 0)
{
return SceneOutliner->CanUnpinSelectedItems();
}
return false;
})));
}
}
if (Context->NumSelectedItems > 0)
{
SceneOutliner->AddSourceControlMenuOptions(InMenu);
}
}
if (Context->bShowParentTree)
{
FToolMenuSection& ActorEditorContextSection = InMenu->AddSection("ActorEditorContextSection", LOCTEXT("ActorEditorContextSectionName", "Actor Editor Context"));
if ((Context->NumSelectedItems == 1) && (Context->NumSelectedFolders == 1))
{
ActorEditorContextSection.AddMenuEntry(
"MakeCurrentFolder",
LOCTEXT("MakeCurrentFolder", "Make Current Folder"),
FText(),
FSlateIcon(),
FUIAction(
FExecuteAction::CreateLambda([SceneOutliner]()
{
const FSceneOutlinerItemSelection& Selection = SceneOutliner->GetSelection();
if (Selection.SelectedItems.Num() == 1)
{
FSceneOutlinerTreeItemPtr Item = Selection.SelectedItems[0].Pin();
if (FActorFolderTreeItem* FolderItem = Item->CastTo<FActorFolderTreeItem>())
{
if (FolderItem->World.IsValid())
{
const FScopedTransaction Transaction(NSLOCTEXT("UnrealEd", "MakeCurrentActorFolder", "Make Current Actor Folder"));
FActorFolders::Get().SetActorEditorContextFolder(*FolderItem->World, FolderItem->GetFolder());
}
}
}
}),
FCanExecuteAction::CreateLambda([SceneOutliner]
{
const FSceneOutlinerItemSelection& Selection = SceneOutliner->GetSelection();
if (Selection.SelectedItems.Num() == 1)
{
FSceneOutlinerTreeItemPtr Item = Selection.SelectedItems[0].Pin();
if (FActorFolderTreeItem* FolderItem = Item->CastTo<FActorFolderTreeItem>())
{
return FolderItem->World.IsValid() &&
(FolderItem->World->GetCurrentLevel() == FolderItem->GetFolder().GetRootObjectAssociatedLevel()) &&
(FActorFolders::Get().GetActorEditorContextFolder(*FolderItem->World) != FolderItem->GetFolder());
}
}
return false;
})
)
);
}
const FActorBrowsingMode* Mode = static_cast<const FActorBrowsingMode*>(SceneOutliner->GetMode());
check(Mode);
ActorEditorContextSection.AddMenuEntry(
"ClearCurrentFolder",
LOCTEXT("ClearCurrentFolder", "Clear Current Folder"),
FText(),
FSlateIcon(),
FUIAction(
FExecuteAction::CreateLambda([Mode]()
{
if (Mode->RepresentingWorld.IsValid())
{
const FScopedTransaction Transaction(NSLOCTEXT("UnrealEd", "ClearCurrentActorFolder", "Clear Current Actor Folder"));
FActorFolders::Get().SetActorEditorContextFolder(*Mode->RepresentingWorld.Get(), FFolder::GetWorldRootFolder(Mode->RepresentingWorld.Get()));
}
}),
FCanExecuteAction::CreateLambda([Mode]
{
return Mode->RepresentingWorld.IsValid() && !FActorFolders::Get().GetActorEditorContextFolder(*Mode->RepresentingWorld.Get()).IsNone();
})
)
);
}
}
TSharedPtr<SWidget> FActorBrowsingMode::BuildContextMenu()
{
FActorBrowsingMode::RegisterContextMenu();
const FSceneOutlinerItemSelection ItemSelection(SceneOutliner->GetSelection());
USceneOutlinerMenuContext* ContextObject = NewObject<USceneOutlinerMenuContext>();
ContextObject->SceneOutliner = StaticCastSharedRef<SSceneOutliner>(SceneOutliner->AsShared());
ContextObject->bShowParentTree = SceneOutliner->GetSharedData().bShowParentTree;
ContextObject->NumSelectedItems = ItemSelection.Num();
ContextObject->NumSelectedFolders = ItemSelection.Num<FFolderTreeItem>();
ContextObject->NumWorldsSelected = ItemSelection.Num<FWorldTreeItem>();
ContextObject->bRepresentingGameWorld = bRepresentingWorldGameWorld;
ContextObject->bRepresentingPartitionedWorld = bRepresentingWorldPartitionedWorld;
int32 NumPinnedItems = 0;
if (RepresentingWorld.IsValid())
{
if (const UWorldPartition* const WorldPartition = RepresentingWorld->GetWorldPartition())
{
ItemSelection.ForEachItem<IActorBaseTreeItem>([WorldPartition, &NumPinnedItems](const IActorBaseTreeItem& ActorItem)
{
if (WorldPartition->IsActorPinned(ActorItem.GetGuid()))
{
++NumPinnedItems;
}
return true;
});
}
}
ContextObject->NumPinnedItems = NumPinnedItems;
FToolMenuContext Context(ContextObject);
FName MenuName = DefaultContextMenuName;
SceneOutliner->GetSharedData().ModifyContextMenu.ExecuteIfBound(MenuName, Context);
// Build up the menu for a selection
UToolMenus* ToolMenus = UToolMenus::Get();
UToolMenu* Menu = ToolMenus->GenerateMenu(MenuName, Context);
for (const FToolMenuSection& Section : Menu->Sections)
{
if (Section.Blocks.Num() > 0)
{
return ToolMenus->GenerateWidget(Menu);
}
}
return nullptr;
}
TSharedPtr<SWidget> FActorBrowsingMode::CreateContextMenu()
{
TArray<AActor*> SelectedActors;
GEditor->GetSelectedActors()->GetSelectedObjects<AActor>(SelectedActors);
// Make sure that no components are selected
if (GEditor->GetSelectedComponentCount() > 0)
{
// We want to be able to undo to regain the previous component selection
const FScopedTransaction Transaction(NSLOCTEXT("UnrealEd", "ClickingOnActorsContextMenu", "Clicking on Actors (context menu)"));
USelection* ComponentSelection = GEditor->GetSelectedComponents();
ComponentSelection->Modify(false);
ComponentSelection->DeselectAll();
GUnrealEd->UpdatePivotLocationForSelection();
GEditor->RedrawLevelEditingViewports(false);
}
return BuildContextMenu();
}
void FActorBrowsingMode::OnItemAdded(FSceneOutlinerTreeItemPtr Item)
{
if (const FActorTreeItem* ActorItem = Item->CastTo<FActorTreeItem>())
{
// We incremented the count regardless of Flags.bIsFilteredOut because the count should match what the user sees, which includes things like
// actors which don't pass the filter themselves but are force shown by children being visible.
++FilteredActorCount;
if (!Item->Flags.bIsFilteredOut)
{
// Synchronize selection
if (GEditor->GetSelectedActors()->IsSelected(ActorItem->Actor.Get()))
{
SceneOutliner->SetItemSelection(Item, true);
}
}
}
else if (Item->IsA<FActorDescTreeItem>())
{
++FilteredActorCount;
++FilteredUnloadedActorCount;
}
}
void FActorBrowsingMode::OnItemRemoved(FSceneOutlinerTreeItemPtr Item)
{
if (Item->IsA<FActorTreeItem>())
{
--FilteredActorCount;
}
else if (Item->IsA<FActorDescTreeItem>())
{
--FilteredActorCount;
--FilteredUnloadedActorCount;
}
}
void FActorBrowsingMode::OnComponentsUpdated()
{
UE_LOG(LogSceneOutliner, VeryVerbose, TEXT("FullRefresh requested by FActorBrowsingMode::OnComponentsUpdated"));
SceneOutliner->FullRefresh();
}
void FActorBrowsingMode::OnLevelActorDeleted(AActor* Actor)
{
ApplicableActors.Remove(Actor);
}
void FActorBrowsingMode::OnSelectUnloadedActors(const TArray<FGuid>& ActorGuids)
{
if (RepresentingWorld.IsValid())
{
if (UWorldPartition* WorldPartition = RepresentingWorld->GetWorldPartition())
{
TArray<FSceneOutlinerTreeItemPtr> ItemsToSelect;
ItemsToSelect.Reserve(ActorGuids.Num());
for (const FGuid& ActorGuid : ActorGuids)
{
if (FWorldPartitionActorDescInstance* ActorDescInstance = WorldPartition->GetActorDescInstance(ActorGuid))
{
if (FSceneOutlinerTreeItemPtr ItemPtr = SceneOutliner->GetTreeItem(FActorDescTreeItem::ComputeTreeItemID(ActorDescInstance->GetGuid(), ActorDescInstance->GetContainerInstance())))
{
ItemsToSelect.Add(ItemPtr);
}
}
}
if (ItemsToSelect.Num())
{
SceneOutliner->SetItemSelection(ItemsToSelect, true);
SceneOutliner->ScrollItemIntoView(ItemsToSelect.Last());
if (const FActorDescTreeItem* ActorDescItem = ItemsToSelect.Last()->CastTo<FActorDescTreeItem>())
{
ActorDescItem->FocusActorBounds();
}
}
}
}
}
void FActorBrowsingMode::OnActorDescInstanceRemoved(FWorldPartitionActorDescInstance* InActorDescInstance)
{
ApplicableUnloadedActors.Remove(InActorDescInstance);
}
void FActorBrowsingMode::OnItemSelectionChanged(FSceneOutlinerTreeItemPtr TreeItem, ESelectInfo::Type SelectionType, const FSceneOutlinerItemSelection& Selection)
{
TSet<AActor*> OutlinerSelectedActors(Selection.GetData<AActor*>(SceneOutliner::FActorSelector()));
TSet<AActor*> ActorsToSelect;
ActorsToSelect.Reserve(OutlinerSelectedActors.Num());
SynchronizeSelectedActorDescs();
USelection* ActorSelection = GEditor->GetSelectedActors();
if (UTypedElementSelectionSet* SelectionSet = ActorSelection->GetElementSelectionSet())
{
TSet<AActor*> EditorSelectedActors(SelectionSet->GetSelectedObjects<AActor>());
bool bChanged = false;
bool bAnyInPIE = false;
for (AActor* Actor : OutlinerSelectedActors)
{
if (!bAnyInPIE && Actor && Actor->GetPackage()->HasAnyPackageFlags(PKG_PlayInEditor))
{
bAnyInPIE = true;
}
if (!EditorSelectedActors.Contains(Actor))
{
bChanged = true;
}
// Allow selection of Sub Roots only if Roots aren't in the outliners selection
AActor* RootSelectionParent = Actor ? Actor->GetRootSelectionParent() : nullptr;
if (!RootSelectionParent || !OutlinerSelectedActors.Contains(RootSelectionParent))
{
ActorsToSelect.Add(Actor);
}
}
for (const AActor* SelectedActor : EditorSelectedActors)
{
if (!bAnyInPIE && SelectedActor && SelectedActor->GetPackage()->HasAnyPackageFlags(PKG_PlayInEditor))
{
bAnyInPIE = true;
}
if (!ActorsToSelect.Contains(SelectedActor))
{
// Actor has been deselected
bChanged = true;
// If actor was a group actor, remove its members from the ActorsToSelect list
if (const AGroupActor* DeselectedGroupActor = Cast<AGroupActor>(SelectedActor))
{
TArray<AActor*> GroupActors;
DeselectedGroupActor->GetGroupActors(GroupActors);
for (AActor* GroupActor : GroupActors)
{
ActorsToSelect.Remove(GroupActor);
}
}
}
}
// If there's a discrepancy, update the selected actors to reflect this list.
if (bChanged)
{
const FScopedTransaction Transaction(NSLOCTEXT("UnrealEd", "ClickingOnActors", "Clicking on Actors"), !bAnyInPIE);
TArray<FTypedElementHandle> ElementsToSelect;
for (auto* Actor : ActorsToSelect)
{
UE_LOG(LogActorBrowser, Verbose, TEXT("Clicking on Actor (world outliner): %s (%s)"), *Actor->GetClass()->GetName(), *Actor->GetActorLabel());
ElementsToSelect.Add(UEngineElementsLibrary::AcquireEditorActorElementHandle(Actor));
}
{
const FTypedElementSelectionOptions SelectionOptions = FTypedElementSelectionOptions()
.SetAllowHidden(true)
.SetWarnIfLocked(false)
.SetAllowLegacyNotifications(false)
.SetAllowSubRootSelection(true);
// Avoid senting out notification via typed element and call NoteSelectionChange to preserve previous behavior
FTypedElementList::FScopedClearNewPendingChange ClearNewPendingChange = SelectionSet->GetScopedClearNewPendingChange();
SelectionSet->SetSelection(ElementsToSelect, SelectionOptions);
SelectionSet->NotifyPendingChanges();
}
// Fire selection changed event
GEditor->NoteSelectionChange();
// Set this outliner as the most recently interacted with
SetAsMostRecentOutliner();
}
SceneOutliner->RefreshSelection();
}
}
void FActorBrowsingMode::OnItemDoubleClick(FSceneOutlinerTreeItemPtr Item)
{
if (const FActorTreeItem* ActorItem = Item->CastTo<FActorTreeItem>())
{
if (AActor* Actor = ActorItem->Actor.Get())
{
ILevelInstanceInterface* LevelInstance = Cast<ILevelInstanceInterface>(Actor);
if (LevelInstance && FSlateApplication::Get().GetModifierKeys().IsAltDown())
{
if (LevelInstance->CanEnterEdit())
{
LevelInstance->EnterEdit();
}
else if (LevelInstance->CanExitEdit())
{
LevelInstance->ExitEdit();
}
}
else if (Item->CanInteract())
{
FSceneOutlinerItemSelection Selection(SceneOutliner->GetSelection());
if (Selection.Has<FActorTreeItem>())
{
const bool bActiveViewportOnly = false;
GEditor->MoveViewportCamerasToActor(Selection.GetData<AActor*>(SceneOutliner::FActorSelector()), bActiveViewportOnly);
}
}
else
{
const bool bActiveViewportOnly = false;
GEditor->MoveViewportCamerasToActor(*Actor, bActiveViewportOnly);
}
}
}
else if (const FActorDescTreeItem* ActorDescItem = Item->CastTo<FActorDescTreeItem>())
{
ActorDescItem->FocusActorBounds();
}
else if (const FActorFolderTreeItem* FolderItem = Item->CastTo<FActorFolderTreeItem>())
{
if (DoesFolderDoubleClickMarkCurrentFolder())
{
if (UWorld* World = FolderItem->World.Get())
{
const FScopedTransaction Transaction(
LOCTEXT("ToggleCurrentActorFolder", "Toggle Current Actor Folder")
);
const FFolder CurrentContextFolder = FActorFolders::Get().GetActorEditorContextFolder(*World);
if (CurrentContextFolder == FolderItem->GetFolder())
{
FActorFolders::Get().SetActorEditorContextFolder(*World, FFolder::GetWorldRootFolder(World));
}
else
{
FActorFolders::Get().SetActorEditorContextFolder(*World, FolderItem->GetFolder());
}
}
}
}
}
bool FActorBrowsingMode::HasCustomFolderDoubleClick() const
{
return DoesFolderDoubleClickMarkCurrentFolder();
}
void FActorBrowsingMode::OnToggleFolderDoubleClickMarkCurrentFolder()
{
if (FActorBrowsingModeConfig* Settings = GetMutableConfig())
{
if (Settings->FolderDoubleClickMethod == EActorBrowsingFolderDoubleClickMethod::ToggleCurrentFolder)
{
Settings->FolderDoubleClickMethod = EActorBrowsingFolderDoubleClickMethod::ToggleExpansion;
}
else
{
Settings->FolderDoubleClickMethod = EActorBrowsingFolderDoubleClickMethod::ToggleCurrentFolder;
}
SaveConfig();
}
}
bool FActorBrowsingMode::DoesFolderDoubleClickMarkCurrentFolder() const
{
if (const FActorBrowsingModeConfig* Settings = GetConstConfig())
{
return Settings->FolderDoubleClickMethod == EActorBrowsingFolderDoubleClickMethod::ToggleCurrentFolder;
}
return false;
}
void FActorBrowsingMode::OnToggleShouldUpdateContentWhileInPIEFocused()
{
if (FActorBrowsingModeConfig* Settings = GetMutableConfig())
{
Settings->bShouldUpdateContentWhileInPIEFocused = !Settings->bShouldUpdateContentWhileInPIEFocused;
bShouldUpdateContentWhileInPIEFocused = Settings->bShouldUpdateContentWhileInPIEFocused;
SaveConfig();
}
}
bool FActorBrowsingMode::ShouldUpdateContentWhileInPIEFocused() const
{
return bShouldUpdateContentWhileInPIEFocused;
}
void FActorBrowsingMode::OnToggleCollapseOutlinerTreeOnNewSelection()
{
if (FActorBrowsingModeConfig* Settings = GetMutableConfig())
{
Settings->bCollapseOutlinerTreeOnNewSelection = !Settings->bCollapseOutlinerTreeOnNewSelection;
bCollapseOutlinerTreeOnNewSelection = Settings->bCollapseOutlinerTreeOnNewSelection;
SaveConfig();
}
}
bool FActorBrowsingMode::CollapseOutlinerTreeOnNewSelection() const
{
return bCollapseOutlinerTreeOnNewSelection;
}
void FActorBrowsingMode::OnFilterTextCommited(FSceneOutlinerItemSelection& Selection, ETextCommit::Type CommitType)
{
// Start batching selection changes
GEditor->GetSelectedActors()->BeginBatchSelectOperation();
// Select actors (and only the actors) that match the filter text
const bool bNoteSelectionChange = false;
const bool bDeselectBSPSurfs = false;
const bool WarnAboutManyActors = true;
GEditor->SelectNone(bNoteSelectionChange, bDeselectBSPSurfs, WarnAboutManyActors);
for (AActor* Actor : Selection.GetData<AActor*>(SceneOutliner::FActorSelector()))
{
const bool bShouldSelect = true;
const bool bSelectEvenIfHidden = false;
GEditor->SelectActor(Actor, bShouldSelect, bNoteSelectionChange, bSelectEvenIfHidden);
}
// Commit selection changes
GEditor->GetSelectedActors()->EndBatchSelectOperation(/*bNotify*/false);
// Fire selection changed event
GEditor->NoteSelectionChange();
// Set keyboard focus to the SceneOutliner, so the user can perform keyboard commands that interact
// with selected actors (such as Delete, to delete selected actors.)
SceneOutliner->SetKeyboardFocus();
SetAsMostRecentOutliner();
}
void FActorBrowsingMode::OnItemPassesFilters(const ISceneOutlinerTreeItem& Item)
{
if (const FActorTreeItem* const ActorItem = Item.CastTo<FActorTreeItem>())
{
ApplicableActors.Add(ActorItem->Actor);
}
else if (const FActorDescTreeItem* const ActorDescItem = Item.CastTo<FActorDescTreeItem>(); ActorDescItem && ActorDescItem->IsValid())
{
ApplicableUnloadedActors.Add(ActorDescItem->ActorDescHandle);
}
}
FReply FActorBrowsingMode::OnKeyDown(const FKeyEvent& InKeyEvent)
{
const FSceneOutlinerItemSelection& Selection = SceneOutliner->GetSelection();
const FModifierKeysState ModifierKeys = FSlateApplication::Get().GetModifierKeys();
const FInputChord CheckChord( InKeyEvent.GetKey(), EModifierKey::FromBools(ModifierKeys.IsControlDown(), ModifierKeys.IsAltDown(), ModifierKeys.IsShiftDown(), ModifierKeys.IsCommandDown()) );
// Use the keyboard shortcut bound to 'Focus Viewport To Selection'
if (FEditorViewportCommands::Get().FocusViewportToSelection->HasActiveChord(CheckChord))
{
if (Selection.Num() == 1)
{
FSceneOutlinerTreeItemPtr ItemToFocus = Selection.SelectedItems[0].Pin();
if (ItemToFocus.IsValid())
{
SceneOutliner->ScrollItemIntoView(ItemToFocus);
}
}
}
// Always return Unhandled here even if it entered the previous if so that the level editor viewport can handle the FocusViewport command as well
return FReply::Unhandled();
}
bool FActorBrowsingMode::CanDelete() const
{
const FSceneOutlinerItemSelection ItemSelection = SceneOutliner->GetSelection();
const uint32 NumberOfFolders = ItemSelection.Num<FFolderTreeItem>();
return (NumberOfFolders > 0 && NumberOfFolders == ItemSelection.Num());
}
bool FActorBrowsingMode::CanRename() const
{
const FSceneOutlinerItemSelection ItemSelection = SceneOutliner->GetSelection();
const uint32 NumberOfFolders = ItemSelection.Num<FFolderTreeItem>();
return (NumberOfFolders == 1 && NumberOfFolders == ItemSelection.Num());
}
bool FActorBrowsingMode::CanRenameItem(const ISceneOutlinerTreeItem& Item) const
{
// Can only rename actor and folder items when in actor browsing mode
return (Item.IsValid() && (Item.IsA<FActorTreeItem>() || Item.IsA<FFolderTreeItem>()));
}
bool FActorBrowsingMode::CanCut() const
{
const FSceneOutlinerItemSelection ItemSelection = SceneOutliner->GetSelection();
const uint32 NumberOfFolders = ItemSelection.Num<FFolderTreeItem>();
return (NumberOfFolders > 0 && NumberOfFolders == ItemSelection.Num());
}
bool FActorBrowsingMode::CanCopy() const
{
const FSceneOutlinerItemSelection ItemSelection = SceneOutliner->GetSelection();
const uint32 NumberOfFolders = ItemSelection.Num<FFolderTreeItem>();
return (NumberOfFolders > 0 && NumberOfFolders == ItemSelection.Num());
}
bool FActorBrowsingMode::CanPaste() const
{
return CanPasteFoldersOnlyFromClipboard();
}
bool FActorBrowsingMode::HasErrors() const
{
if (RepresentingWorld.IsValid() && !bRepresentingWorldGameWorld && bRepresentingWorldPartitionedWorld && WorldPartitionEditorModule)
{
return WorldPartitionEditorModule->HasErrors(RepresentingWorld.Get());
}
return false;
}
FText FActorBrowsingMode::GetErrorsText() const
{
return LOCTEXT("WorldHasInvalidActorFiles", "The world contains invalid actor files. Click the Repair button to repair them.");
}
void FActorBrowsingMode::RepairErrors() const
{
if (RepresentingWorld.IsValid() && !bRepresentingWorldGameWorld && bRepresentingWorldPartitionedWorld && WorldPartitionEditorModule)
{
WorldPartitionEditorModule->RepairErrors(RepresentingWorld.Get());
}
}
void FActorBrowsingMode::BindCommands(const TSharedRef<FUICommandList>& OutCommandList)
{
OutCommandList->MapAction(
FGenericCommands::Get().Rename,
FExecuteAction::CreateRaw(this, &FActorBrowsingMode::OnExecuteRename ),
FCanExecuteAction::CreateRaw(this, &FActorBrowsingMode::CanExecuteRename));
OutCommandList->MapAction(
FGenericCommands::Get().Delete,
FExecuteAction::CreateRaw(this, &FActorBrowsingMode::OnExecuteDelete),
FCanExecuteAction::CreateRaw(this, &FActorBrowsingMode::CanDelete));
OutCommandList->MapAction(
FActorBrowsingModeCommands::Get().Refresh,
FExecuteAction::CreateRaw(this, &FActorBrowsingMode::OnExecuteRefresh));
FInputBindingManager::Get().RegisterCommandList(FActorBrowsingModeCommands::Get().GetContextName(), OutCommandList);
}
bool FActorBrowsingMode::CanPasteFoldersOnlyFromClipboard() const
{
// Intentionally not checking if the level is locked/hidden here, as it's better feedback for the user if they attempt to paste
// and get the message explaining why it's failed, than just not having the option available to them.
FString PasteString;
FPlatformApplicationMisc::ClipboardPaste(PasteString);
return PasteString.StartsWith("BEGIN FOLDERLIST");
}
void FActorBrowsingMode::OnExecuteDelete()
{
const FSceneOutlinerItemSelection& Selection = SceneOutliner->GetSelection();
if (SceneOutliner->GetSharedData().CustomDelete.IsBound())
{
SceneOutliner->GetSharedData().CustomDelete.Execute(Selection.SelectedItems);
}
else
{
if (RepresentingWorld.IsValid())
{
GUnrealEd->Exec(RepresentingWorld.Get(), TEXT("DELETE"));
}
}
}
void FActorBrowsingMode::OnExecuteRefresh()
{
SceneOutliner->FullRefresh();
}
void FActorBrowsingMode::OnExecuteRename()
{
const FSceneOutlinerItemSelection& Selection = SceneOutliner->GetSelection();
if (Selection.Num() == 1)
{
FSceneOutlinerTreeItemPtr ItemToRename = Selection.SelectedItems[0].Pin();
if (ItemToRename.IsValid() && CanRenameItem(*ItemToRename) && ItemToRename->CanInteract())
{
SceneOutliner->SetPendingRenameItem(ItemToRename);
SceneOutliner->ScrollItemIntoView(ItemToRename);
}
}
}
bool FActorBrowsingMode::CanExecuteRename()
{
const FSceneOutlinerItemSelection& Selection = SceneOutliner->GetSelection();
if (Selection.Num() == 1)
{
FSceneOutlinerTreeItemPtr ItemToRename = Selection.SelectedItems[0].Pin();
return ItemToRename.IsValid() && CanRenameItem(*ItemToRename) && ItemToRename->CanInteract();
}
return false;
}
void FActorBrowsingMode::SynchronizeSelectedActorDescs()
{
if (UWorldPartitionSubsystem* WorldPartitionSubsystem = UWorld::GetSubsystem<UWorldPartitionSubsystem>(RepresentingWorld.Get()))
{
const FSceneOutlinerItemSelection Selection = SceneOutliner->GetSelection();
TArray<FWorldPartitionHandle> SelectedActorHandles = Selection.GetData<FWorldPartitionHandle>(SceneOutliner::FActorHandleSelector());
WorldPartitionSubsystem->SelectedActorHandles.Empty();
for (const FWorldPartitionHandle& ActorHandle : SelectedActorHandles)
{
WorldPartitionSubsystem->SelectedActorHandles.Add(ActorHandle);
}
}
}
FFolder FActorBrowsingMode::CreateNewFolder()
{
if (TStrongObjectPtr<UWorld> World = RepresentingWorld.Pin())
{
const FScopedTransaction Transaction(LOCTEXT("UndoAction_CreateFolder", "Create Folder"));
TArray<FFolder> SelectedFolders = SceneOutliner->GetSelection().GetData<FFolder>(SceneOutliner::FFolderPathSelector());
const FFolder NewFolderName = FActorFolders::Get().GetDefaultFolderForSelection(*World, &SelectedFolders);
FActorFolders::Get().CreateFolderContainingSelection(*World, NewFolderName);
return NewFolderName;
}
return FFolder();
}
FFolder FActorBrowsingMode::GetFolder(const FFolder& ParentPath, const FName& LeafName)
{
// Return a unique folder under the provided parent path & root object and using the provided leaf name
if (TStrongObjectPtr<UWorld> World = RepresentingWorld.Pin())
{
return FActorFolders::Get().GetFolderName(*World, ParentPath, LeafName);
}
return FFolder();
}
bool FActorBrowsingMode::CreateFolder(const FFolder& NewPath)
{
if (TStrongObjectPtr<UWorld> World = RepresentingWorld.Pin())
{
return FActorFolders::Get().CreateFolder(*World, NewPath);
}
return false;
}
bool FActorBrowsingMode::ReparentItemToFolder(const FFolder& FolderPath, const FSceneOutlinerTreeItemPtr& Item)
{
if (FActorTreeItem* ActorItem = Item->CastTo<FActorTreeItem>())
{
// Make sure actor has the same root object before updating path
if (ActorItem->Actor->GetFolderRootObject() == FolderPath.GetRootObject())
{
ActorItem->Actor->SetFolderPath_Recursively(FolderPath.GetPath());
return true;
}
}
return false;
}
namespace ActorBrowsingModeUtils
{
static void RecursiveFolderExpandChildren(SSceneOutliner* SceneOutliner, const FSceneOutlinerTreeItemPtr& Item)
{
if (Item.IsValid())
{
for (const TWeakPtr<ISceneOutlinerTreeItem>& Child : Item->GetChildren())
{
FSceneOutlinerTreeItemPtr ChildPtr = Child.Pin();
SceneOutliner->SetItemExpansion(ChildPtr, true);
RecursiveFolderExpandChildren(SceneOutliner, ChildPtr);
}
}
}
static void RecursiveActorSelect(SSceneOutliner* SceneOutliner, const FSceneOutlinerTreeItemPtr& Item, bool bSelectImmediateChildrenOnly)
{
if (Item.IsValid())
{
// If the current item is an actor, ensure to select it as well
if (FActorTreeItem* ActorItem = Item->CastTo<FActorTreeItem>())
{
if (AActor* Actor = ActorItem->Actor.Get())
{
GEditor->SelectActor(Actor, true, false);
}
}
// Select all children
for (const TWeakPtr<ISceneOutlinerTreeItem>& Child : Item->GetChildren())
{
FSceneOutlinerTreeItemPtr ChildPtr = Child.Pin();
if (ChildPtr.IsValid())
{
if (FActorTreeItem* ActorItem = ChildPtr->CastTo<FActorTreeItem>())
{
if (AActor* Actor = ActorItem->Actor.Get())
{
GEditor->SelectActor(Actor, true, false);
}
}
else if (FFolderTreeItem* FolderItem = ChildPtr->CastTo<FFolderTreeItem>())
{
SceneOutliner->SetItemSelection(FolderItem->AsShared(), true);
}
if (!bSelectImmediateChildrenOnly)
{
for (const TWeakPtr<ISceneOutlinerTreeItem>& Grandchild : ChildPtr->GetChildren())
{
RecursiveActorSelect(SceneOutliner, Grandchild.Pin(), bSelectImmediateChildrenOnly);
}
}
}
}
}
}
static void RecursiveAddItemsToActorGuidList(const TArray<FSceneOutlinerTreeItemPtr>& Items, TArray<FGuid>& List, bool bSearchForHiddenUnloadedActors)
{
// In the case where we want the list of unloaded actors under a folder and the option to hide unloaded actors is enabled, we need to find them through FActorFolders
TMap<UWorld*, TSet<FName>> UnloadedActorsFolderPaths;
for (const FSceneOutlinerTreeItemPtr& Item : Items)
{
if (const FActorDescTreeItem* const ActorDescTreeItem = Item->CastTo<FActorDescTreeItem>())
{
List.Add(ActorDescTreeItem->GetGuid());
}
else if (const FActorTreeItem* const ActorTreeItem = Item->CastTo<FActorTreeItem>())
{
if (ActorTreeItem->Actor.IsValid())
{
List.Add(ActorTreeItem->Actor->GetActorGuid());
}
}
else if (const FActorFolderTreeItem* const ActorFolderTreeItem = Item->CastTo<FActorFolderTreeItem>(); ActorFolderTreeItem && bSearchForHiddenUnloadedActors)
{
if (UWorld* FolderWorld = ActorFolderTreeItem->World.Get())
{
UnloadedActorsFolderPaths.FindOrAdd(FolderWorld).Add(ActorFolderTreeItem->GetPath());
}
}
TArray<FSceneOutlinerTreeItemPtr> ChildrenItems;
for (const auto& Child : Item->GetChildren())
{
if (Child.IsValid())
{
ChildrenItems.Add(Child.Pin());
}
}
if (ChildrenItems.Num())
{
RecursiveAddItemsToActorGuidList(ChildrenItems, List, bSearchForHiddenUnloadedActors);
}
}
for (const TTuple<UWorld*, TSet<FName>>& Pair : UnloadedActorsFolderPaths)
{
FActorFolders::ForEachActorDescInstanceInFolders(*Pair.Key, Pair.Value, [&List](const FWorldPartitionActorDescInstance* ActorDescInstance)
{
if (!ActorDescInstance->IsLoaded())
{
List.Add(ActorDescInstance->GetGuid());
}
return true;
});
}
};
bool CanChangePinnedStates(const TArray<FSceneOutlinerTreeItemPtr>& InItems)
{
for (const FSceneOutlinerTreeItemPtr& Item : InItems)
{
if (Item->ShouldShowPinnedState())
{
return true;
}
else if (const FActorFolderTreeItem* FolderItem = Item->CastTo<FActorFolderTreeItem>(); FolderItem && FolderItem->CanChangeChildrenPinnedState())
{
return true;
}
}
return false;
}
}
void FActorBrowsingMode::SelectFoldersDescendants(const TArray<FFolderTreeItem*>& FolderItems, bool bSelectImmediateChildrenOnly)
{
// Expand everything before beginning selection
for (FFolderTreeItem* Folder : FolderItems)
{
FSceneOutlinerTreeItemPtr FolderPtr = Folder->AsShared();
SceneOutliner->SetItemExpansion(FolderPtr, true);
if (!bSelectImmediateChildrenOnly)
{
ActorBrowsingModeUtils::RecursiveFolderExpandChildren(SceneOutliner, FolderPtr);
}
}
// batch selection
GEditor->GetSelectedActors()->BeginBatchSelectOperation();
for (FFolderTreeItem* Folder : FolderItems)
{
ActorBrowsingModeUtils::RecursiveActorSelect(SceneOutliner, Folder->AsShared(), bSelectImmediateChildrenOnly);
}
GEditor->GetSelectedActors()->EndBatchSelectOperation(/*bNotify*/false);
GEditor->NoteSelectionChange();
}
bool FActorBrowsingMode::CanPinItems(const TArray<FSceneOutlinerTreeItemPtr>& InItems) const
{
return ActorBrowsingModeUtils::CanChangePinnedStates(InItems);
}
void FActorBrowsingMode::PinItems(const TArray<FSceneOutlinerTreeItemPtr>& InItems)
{
UWorldPartition* const WorldPartition = RepresentingWorld->GetWorldPartition();
if (!WorldPartition)
{
return;
}
TArray<FGuid> ActorsToPin;
// If Unloaded actors are hidden and we are pinning folders we need to find them through FActorFolders
const bool bSearchForHiddenUnloadedActors = bHideUnloadedActors;
ActorBrowsingModeUtils::RecursiveAddItemsToActorGuidList(InItems, ActorsToPin, bSearchForHiddenUnloadedActors);
if (ActorsToPin.Num())
{
GEditor->GetSelectedActors()->BeginBatchSelectOperation();
GEditor->SelectNone(/*bNoteSelectionChange=*/false, /*bDeselectBSPSurfs=*/true);
WorldPartition->PinActors(ActorsToPin);
AActor* LastPinnedActor = nullptr;
for (const FGuid& ActorGuid : ActorsToPin)
{
if (FWorldPartitionHandle ActorHandle(WorldPartition, ActorGuid); ActorHandle.IsValid())
{
if (AActor* PinnedActor = ActorHandle->GetActor())
{
GEditor->SelectActor(PinnedActor, /*bInSelected=*/true, /*bNotify=*/false);
LastPinnedActor = PinnedActor;
}
}
}
GEditor->GetSelectedActors()->EndBatchSelectOperation(/*bNotify=*/true);
if (LastPinnedActor)
{
SceneOutliner->OnItemAdded(LastPinnedActor, SceneOutliner::ENewItemAction::ScrollIntoView);
}
}
}
bool FActorBrowsingMode::CanUnpinItems(const TArray<FSceneOutlinerTreeItemPtr>& InItems) const
{
return ActorBrowsingModeUtils::CanChangePinnedStates(InItems);
}
void FActorBrowsingMode::UnpinItems(const TArray<FSceneOutlinerTreeItemPtr>& InItems)
{
UWorldPartition* const WorldPartition = RepresentingWorld->GetWorldPartition();
if (!WorldPartition)
{
return;
}
TArray<FGuid> ActorsToUnpin;
// No need to search for hidden unloaded actors when unloading
const bool bSearchForHiddenUnloadedActors = false;
ActorBrowsingModeUtils::RecursiveAddItemsToActorGuidList(InItems, ActorsToUnpin, bSearchForHiddenUnloadedActors);
if (ActorsToUnpin.Num())
{
WorldPartition->UnpinActors(ActorsToUnpin);
}
}
void FActorBrowsingMode::SynchronizeSelection()
{
FActorModeInteractive::SynchronizeSelection();
SynchronizeSelectedActorDescs();
}
FCreateSceneOutlinerMode FActorBrowsingMode::CreateFolderPickerMode(const FFolder::FRootObject& InRootObject) const
{
auto MoveSelectionTo = [this, InRootObject](const FSceneOutlinerTreeItemRef& NewParent)
{
if (FWorldTreeItem* WorldItem = NewParent->CastTo<FWorldTreeItem>())
{
SceneOutliner->MoveSelectionTo(GetWorldDefaultRootFolder());
}
else if (FFolderTreeItem* FolderItem = NewParent->CastTo<FFolderTreeItem>())
{
SceneOutliner->MoveSelectionTo(FolderItem->GetFolder());
}
else if (FActorTreeItem* ActorItem = NewParent->CastTo<FActorTreeItem>())
{
if (FFolder::IsRootObjectValid(InRootObject))
{
SceneOutliner->MoveSelectionTo(FFolder(InRootObject));
}
}
else if (FLevelTreeItem* LevelItem = NewParent->CastTo<FLevelTreeItem>())
{
if (FFolder::IsRootObjectValid(InRootObject))
{
SceneOutliner->MoveSelectionTo(FFolder(InRootObject));
}
}
};
return FCreateSceneOutlinerMode::CreateLambda([this, MoveSelectionTo, InRootObject](SSceneOutliner* Outliner)
{
return new FActorFolderPickingMode(Outliner, FOnSceneOutlinerItemPicked::CreateLambda(MoveSelectionTo), nullptr, InRootObject);
});
}
void FActorBrowsingMode::OnDuplicateSelected()
{
GUnrealEd->Exec(RepresentingWorld.Get(), TEXT("DUPLICATE"));
}
void FActorBrowsingMode::OnEditCutActorsBegin()
{
// Only a callback in actor browsing mode
SceneOutliner->CopyFoldersBegin();
SceneOutliner->DeleteFoldersBegin();
}
void FActorBrowsingMode::OnEditCutActorsEnd()
{
// Only a callback in actor browsing mode
SceneOutliner->CopyFoldersEnd();
SceneOutliner->DeleteFoldersEnd();
}
void FActorBrowsingMode::OnEditCopyActorsBegin()
{
// Only a callback in actor browsing mode
SceneOutliner->CopyFoldersBegin();
}
void FActorBrowsingMode::OnEditCopyActorsEnd()
{
// Only a callback in actor browsing mode
SceneOutliner->CopyFoldersEnd();
}
void FActorBrowsingMode::OnEditPasteActorsBegin()
{
// Only a callback in actor browsing mode
const TArray<FName> FolderPaths = SceneOutliner->GetClipboardPasteFolders();
SceneOutliner->PasteFoldersBegin(FolderPaths);
}
void FActorBrowsingMode::OnEditPasteActorsEnd()
{
// Only a callback in actor browsing mode
SceneOutliner->PasteFoldersEnd();
}
void FActorBrowsingMode::OnDuplicateActorsBegin()
{
// Only a callback in actor browsing mode
FFolder::FRootObject CommonRootObject;
TArray<FName> SelectedFolderPaths;
FFolder::GetFolderPathsAndCommonRootObject(SceneOutliner->GetSelection().GetData<FFolder>(SceneOutliner::FFolderPathSelector()), SelectedFolderPaths, CommonRootObject);
SceneOutliner->PasteFoldersBegin(SelectedFolderPaths);
}
void FActorBrowsingMode::OnDuplicateActorsEnd()
{
// Only a callback in actor browsing mode
SceneOutliner->PasteFoldersEnd();
}
void FActorBrowsingMode::OnDeleteActorsBegin()
{
SceneOutliner->DeleteFoldersBegin();
}
void FActorBrowsingMode::OnDeleteActorsEnd()
{
SceneOutliner->DeleteFoldersEnd();
}
struct FActorBrowsingModeConfig* FActorBrowsingMode::GetMutableConfig()
{
FName OutlinerIdentifier = SceneOutliner->GetOutlinerIdentifier();
if (OutlinerIdentifier.IsNone())
{
return nullptr;
}
return &UActorBrowserConfig::Get()->ActorBrowsers.FindOrAdd(OutlinerIdentifier);
}
const FActorBrowsingModeConfig* FActorBrowsingMode::GetConstConfig() const
{
FName OutlinerIdentifier = SceneOutliner->GetOutlinerIdentifier();
if (OutlinerIdentifier.IsNone())
{
return nullptr;
}
return UActorBrowserConfig::Get()->ActorBrowsers.Find(OutlinerIdentifier);
}
void FActorBrowsingMode::SaveConfig()
{
UActorBrowserConfig::Get()->SaveEditorConfig();
}
bool FActorBrowsingMode::CompareItemWithClassName(SceneOutliner::FilterBarType InItem, const TSet<FTopLevelAssetPath>& AssetClassPaths) const
{
// Type filtering only supported for Actors (and unloaded actors) currently
if (const FActorTreeItem* ActorItem = InItem.CastTo<FActorTreeItem>())
{
AActor* Actor = ActorItem->Actor.Get();
if(!Actor)
{
return false;
}
FTopLevelAssetPath AssetClassPath = Actor->GetClass()->GetClassPathName();
// For Blueprints, we check both the parent type (e.g Pawn/Actor etc) and the Blueprint class itself
if (UBlueprint* ClassBP = UBlueprint::GetBlueprintFromClass(Actor->GetClass()))
{
return AssetClassPaths.Contains(ClassBP->GetClass()->GetClassPathName()) || AssetClassPaths.Contains(AssetClassPath);
}
return AssetClassPaths.Contains(AssetClassPath);
}
else if (const FActorDescTreeItem* ActorDescItem = InItem.CastTo<FActorDescTreeItem>())
{
if (const FWorldPartitionActorDescInstance* ActorDescInstance = *ActorDescItem->ActorDescHandle)
{
// For Unloaded Actors, grab the native class
FTopLevelAssetPath ClassPath = ActorDescInstance->GetNativeClass();
return AssetClassPaths.Contains(ClassPath);
}
}
return FActorModeInteractive::CompareItemWithClassName(InItem, AssetClassPaths);
}
#undef LOCTEXT_NAMESPACE