You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
#rb patrick.enfedaque, richard.malo #preflight 628faefee17a02240dd5dfe3 #rnx [CL 20391677 by JeanFrancois Dube in ue5-main branch]
2047 lines
74 KiB
C++
2047 lines
74 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "ActorBrowsingMode.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 "Editor/UnrealEdEngine.h"
|
|
#include "HAL/PlatformApplicationMisc.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 "EditorLevelUtils.h"
|
|
|
|
DEFINE_LOG_CATEGORY_STATIC(LogActorBrowser, Log, All);
|
|
|
|
#define LOCTEXT_NAMESPACE "SceneOutliner_ActorBrowsingMode"
|
|
|
|
using FActorFilter = TSceneOutlinerPredicateFilter<FActorTreeItem>;
|
|
using FActorDescFilter = TSceneOutlinerPredicateFilter<FActorDescTreeItem>;
|
|
|
|
FActorBrowsingMode::FActorBrowsingMode(SSceneOutliner* InSceneOutliner, TWeakObjectPtr<UWorld> InSpecifiedWorldToDisplay)
|
|
: FActorModeInteractive(FActorModeParams(InSceneOutliner, InSpecifiedWorldToDisplay, /* bHideComponents */ true, /* bHideLevelInstanceHierarchy */ false, /* bHideUnloadedActors */ false))
|
|
, FilteredActorCount(0)
|
|
{
|
|
// 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);
|
|
|
|
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;
|
|
}
|
|
|
|
// 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 HideTemporaryActorsInfo(LOCTEXT("ToggleHideTemporaryActors", "Hide Temporary Actors"), LOCTEXT("ToggleHideTemporaryActorsToolTip", "When enabled, hides temporary/run-time Actors."), LocalSettings.bHideTemporaryActors, FCreateSceneOutlinerFilter::CreateStatic(&FActorBrowsingMode::CreateHideTemporaryActorsFilter));
|
|
HideTemporaryActorsInfo.OnToggle().AddLambda([this](bool bIsActive)
|
|
{
|
|
FActorBrowsingModeConfig* Settings = GetMutableConfig();
|
|
if(Settings)
|
|
{
|
|
Settings->bHideTemporaryActors = bIsActive;
|
|
SaveConfig();
|
|
}
|
|
});
|
|
FilterInfoMap.Add(TEXT("HideTemporaryActors"), HideTemporaryActorsInfo);
|
|
|
|
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);
|
|
|
|
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);
|
|
|
|
FSceneOutlinerFilterInfo HideLevelInstancesInfo(LOCTEXT("ToggleHideLevelInstanceContent", "Hide Level Instance Content"), LOCTEXT("ToggleHideLevelInstancesToolTip", "When enabled, hides all level instance content."), LocalSettings.bHideLevelInstanceHierarchy, FCreateSceneOutlinerFilter::CreateStatic(&FActorBrowsingMode::CreateHideLevelInstancesFilter));
|
|
HideLevelInstancesInfo.OnToggle().AddLambda([this](bool bIsActive)
|
|
{
|
|
FActorBrowsingModeConfig* Settings = GetMutableConfig();
|
|
if(Settings)
|
|
{
|
|
Settings->bHideLevelInstanceHierarchy = bHideLevelInstanceHierarchy = bIsActive;
|
|
SaveConfig();
|
|
}
|
|
|
|
if (auto ActorHierarchy = StaticCast<FActorHierarchy*>(Hierarchy.Get()))
|
|
{
|
|
ActorHierarchy->SetShowingLevelInstances(!bIsActive);
|
|
}
|
|
});
|
|
FilterInfoMap.Add(TEXT("HideLevelInstancesFilter"), HideLevelInstancesInfo);
|
|
|
|
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);
|
|
|
|
// Add a filter which sets the interactive mode of LevelInstance items and their children
|
|
SceneOutliner->AddFilter(MakeShared<FActorFilter>(FActorTreeItem::FFilterPredicate::CreateStatic([](const AActor* Actor) {return true; }), FSceneOutlinerFilter::EDefaultBehaviour::Pass, FActorTreeItem::FFilterPredicate::CreateLambda([this](const AActor* Actor)
|
|
{
|
|
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))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
})));
|
|
|
|
bAlwaysFrameSelection = LocalSettings.bAlwaysFrameSelection;
|
|
|
|
Rebuild();
|
|
}
|
|
|
|
FActorBrowsingMode::~FActorBrowsingMode()
|
|
{
|
|
if (RepresentingWorld.IsValid())
|
|
{
|
|
if (UWorldPartition* const WorldPartition = RepresentingWorld->GetWorldPartition())
|
|
{
|
|
WorldPartition->OnActorDescRemovedEvent.RemoveAll(this);
|
|
}
|
|
}
|
|
FSceneOutlinerDelegates::Get().OnComponentsUpdated.RemoveAll(this);
|
|
|
|
GEngine->OnLevelActorDeleted().RemoveAll(this);
|
|
|
|
GEditor->OnSelectUnloadedActorsEvent().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->OnActorDescRemovedEvent.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->OnActorDescRemovedEvent.AddRaw(this, &FActorBrowsingMode::OnActorDescRemoved);
|
|
}
|
|
}
|
|
|
|
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::OnToggleAlwaysFrameSelection()
|
|
{
|
|
bAlwaysFrameSelection = !bAlwaysFrameSelection;
|
|
|
|
FActorBrowsingModeConfig* Settings = GetMutableConfig();
|
|
if(Settings)
|
|
{
|
|
Settings->bAlwaysFrameSelection = bAlwaysFrameSelection;
|
|
SaveConfig();
|
|
}
|
|
}
|
|
|
|
bool FActorBrowsingMode::ShouldAlwaysFrameSelection()
|
|
{
|
|
return bAlwaysFrameSelection;
|
|
}
|
|
|
|
void FActorBrowsingMode::CreateViewContent(FMenuBuilder& MenuBuilder)
|
|
{
|
|
MenuBuilder.BeginSection("OutlinerSelectionOptions", LOCTEXT("OptionsHeading", "Options"));
|
|
|
|
MenuBuilder.AddMenuEntry(
|
|
LOCTEXT("AlwaysFrameSelection", "Always Frame Selection"),
|
|
LOCTEXT("AlwaysFrameSelectionTooltip", "Select an item in the outliner when it is selected through the level editor viewport."),
|
|
FSlateIcon(),
|
|
FUIAction(
|
|
FExecuteAction::CreateRaw(this, &FActorBrowsingMode::OnToggleAlwaysFrameSelection),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateRaw(this, &FActorBrowsingMode::ShouldAlwaysFrameSelection)
|
|
),
|
|
NAME_None,
|
|
EUserInterfaceActionType::ToggleButton
|
|
);
|
|
|
|
MenuBuilder.EndSection();
|
|
|
|
|
|
MenuBuilder.BeginSection("AssetThumbnails", 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::CreateHideTemporaryActorsFilter()
|
|
{
|
|
return MakeShareable(new FActorFilter(FActorTreeItem::FFilterPredicate::CreateStatic([](const AActor* InActor)
|
|
{
|
|
return ((InActor->GetWorld() && InActor->GetWorld()->WorldType != EWorldType::PIE) || GEditor->ObjectsThatExistInEditorWorld.Get(InActor)) && !InActor->HasAnyFlags(EObjectFlags::RF_Transient);
|
|
}), FSceneOutlinerFilter::EDefaultBehaviour::Pass));
|
|
}
|
|
|
|
TSharedRef<FSceneOutlinerFilter> FActorBrowsingMode::CreateIsInCurrentLevelFilter()
|
|
{
|
|
return MakeShareable(new FActorFilter(FActorTreeItem::FFilterPredicate::CreateStatic([](const AActor* InActor)
|
|
{
|
|
if (InActor->GetWorld())
|
|
{
|
|
return InActor->GetLevel() == InActor->GetWorld()->GetCurrentLevel();
|
|
}
|
|
|
|
return false;
|
|
}), 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::CreateHideLevelInstancesFilter()
|
|
{
|
|
return MakeShareable(new FActorFilter(FActorTreeItem::FFilterPredicate::CreateStatic([](const AActor* Actor)
|
|
{
|
|
// Check if actor belongs to a LevelInstance
|
|
if (const ULevelInstanceSubsystem* LevelInstanceSubsystem = Actor->GetWorld()->GetSubsystem<ULevelInstanceSubsystem>())
|
|
{
|
|
if (const ILevelInstanceInterface* ParentLevelInstance = LevelInstanceSubsystem->GetParentLevelInstance(Actor))
|
|
{
|
|
if (!LevelInstanceSubsystem->IsEditingLevelInstance(ParentLevelInstance))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
// Or if the actor itself is a LevelInstance editor instance
|
|
return Cast<ALevelInstanceEditorInstanceActor>(Actor) == nullptr;
|
|
}), FSceneOutlinerFilter::EDefaultBehaviour::Pass));
|
|
}
|
|
|
|
TSharedRef<FSceneOutlinerFilter> FActorBrowsingMode::CreateHideUnloadedActorsFilter()
|
|
{
|
|
return MakeShareable(new FActorDescFilter(FActorDescTreeItem::FFilterPredicate::CreateStatic(
|
|
[](const FWorldPartitionActorDesc* ActorDesc) { return false; }), 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::CreateLambda([](UToolMenu* InMenu)
|
|
{
|
|
USceneOutlinerMenuContext* Context = InMenu->FindContext<USceneOutlinerMenuContext>();
|
|
if (!Context || !Context->SceneOutliner.IsValid())
|
|
{
|
|
return;
|
|
}
|
|
|
|
// NOTE: the name "Section" is used in many other places
|
|
FToolMenuSection& Section = InMenu->FindOrAddSection("Section");
|
|
Section.Label = LOCTEXT("HierarchySectionName", "Hierarchy");
|
|
|
|
SSceneOutliner* SceneOutliner = Context->SceneOutliner.Pin().Get();
|
|
if (Context->bShowParentTree)
|
|
{
|
|
if (Context->NumSelectedItems == 0)
|
|
{
|
|
Section.AddMenuEntry(
|
|
"CreateFolder",
|
|
LOCTEXT("CreateFolder", "Create Folder"),
|
|
FText(),
|
|
FSlateIcon(FAppStyle::GetAppStyleSetName(), "SceneOutliner.NewFolderIcon"),
|
|
FUIAction(FExecuteAction::CreateSP(SceneOutliner, &SSceneOutliner::CreateFolder)));
|
|
}
|
|
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));
|
|
}
|
|
}
|
|
}
|
|
}));
|
|
|
|
Menu->AddDynamicSection("DynamicMainSection", FNewToolMenuDelegate::CreateLambda([](UToolMenu* InMenu)
|
|
{
|
|
// We always create a section here, even if there is no parent so that clients can still extend the menu
|
|
FToolMenuSection& Section = InMenu->AddSection("MainSection", LOCTEXT("OutlinerSectionName", "Outliner"));
|
|
|
|
if (USceneOutlinerMenuContext* Context = InMenu->FindContext<USceneOutlinerMenuContext>())
|
|
{
|
|
// 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 && Context->SceneOutliner.IsValid())
|
|
{
|
|
Section.AddSubMenu(
|
|
"MoveActorsTo",
|
|
LOCTEXT("MoveActorsTo", "Move To"),
|
|
LOCTEXT("MoveActorsTo_Tooltip", "Move selection to another folder"),
|
|
FNewToolMenuDelegate::CreateSP(Context->SceneOutliner.Pin().Get(), &SSceneOutliner::FillFoldersSubMenu));
|
|
}
|
|
|
|
if (Context->bShowParentTree && Context->NumSelectedItems > 0 && Context->SceneOutliner.IsValid())
|
|
{
|
|
// Only add the menu option to wp levels
|
|
if (!Context->bRepresentingGameWorld && Context->bRepresentingPartitionedWorld)
|
|
{
|
|
// If selection contains some unpinned items, show the pin option
|
|
// If the selection contains folders, always show the pin option
|
|
if (Context->NumPinnedItems != Context->NumSelectedItems || Context->NumSelectedFolders > 0)
|
|
{
|
|
Section.AddMenuEntry(
|
|
"PinItems",
|
|
LOCTEXT("Pin", "Pin"),
|
|
FText(),
|
|
FSlateIcon(),
|
|
FUIAction(FExecuteAction::CreateSP(Context->SceneOutliner.Pin().Get(), &SSceneOutliner::PinSelectedItems)));
|
|
}
|
|
|
|
// If the selection contains some pinned items, show the unpin option
|
|
// If the selection contains folders, always show the unpin option
|
|
if (Context->NumPinnedItems != 0 || Context->NumSelectedFolders > 0)
|
|
{
|
|
Section.AddMenuEntry(
|
|
"UnpinItems",
|
|
LOCTEXT("Unpin", "Unpin"),
|
|
FText(),
|
|
FSlateIcon(),
|
|
FUIAction(FExecuteAction::CreateSP(Context->SceneOutliner.Pin().Get(), &SSceneOutliner::UnpinSelectedItems)));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}));
|
|
|
|
Menu->AddDynamicSection("DynamicActorEditorContext", FNewToolMenuDelegate::CreateLambda([this](UToolMenu* InMenu)
|
|
{
|
|
USceneOutlinerMenuContext* Context = InMenu->FindContext<USceneOutlinerMenuContext>();
|
|
if (Context && Context->SceneOutliner.IsValid() && Context->bShowParentTree)
|
|
{
|
|
FToolMenuSection& Section = InMenu->AddSection("ActorEditorContextSection", LOCTEXT("ActorEditorContextSectionName", "Actor Editor Context"));
|
|
SSceneOutliner* SceneOutliner = Context->SceneOutliner.Pin().Get();
|
|
|
|
if ((Context->NumSelectedItems == 1) && (Context->NumSelectedFolders == 1))
|
|
{
|
|
SceneOutliner->GetTree().GetSelectedItems()[0]->GenerateContextMenu(InMenu, *SceneOutliner);
|
|
|
|
Section.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());
|
|
}
|
|
}
|
|
return false;
|
|
})
|
|
)
|
|
);
|
|
}
|
|
|
|
Section.AddMenuEntry(
|
|
"ClearCurrentFolder",
|
|
LOCTEXT("ClearCurrentFolder", "Clear Current Folder"),
|
|
FText(),
|
|
FSlateIcon(),
|
|
FUIAction(
|
|
FExecuteAction::CreateLambda([this, SceneOutliner]()
|
|
{
|
|
if (RepresentingWorld.IsValid())
|
|
{
|
|
const FScopedTransaction Transaction(NSLOCTEXT("UnrealEd", "ClearCurrentActorFolder", "Clear Current Actor Folder"));
|
|
FActorFolders::Get().SetActorEditorContextFolder(*RepresentingWorld.Get(), FFolder::GetWorldRootFolder(RepresentingWorld.Get()));
|
|
}
|
|
}),
|
|
FCanExecuteAction::CreateLambda([this]
|
|
{
|
|
return RepresentingWorld.IsValid() && !FActorFolders::Get().GetActorEditorContextFolder(*RepresentingWorld.Get()).IsNone();
|
|
})
|
|
)
|
|
);
|
|
}
|
|
}));
|
|
}
|
|
|
|
if (!ToolMenus->IsMenuRegistered(DefaultContextMenuName))
|
|
{
|
|
ToolMenus->RegisterMenu(DefaultContextMenuName, DefaultContextBaseMenuName);
|
|
}
|
|
}
|
|
|
|
TSharedPtr<SWidget> FActorBrowsingMode::BuildContextMenu()
|
|
{
|
|
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 (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>())
|
|
{
|
|
if (!Item->Flags.bIsFilteredOut)
|
|
{
|
|
++FilteredActorCount;
|
|
|
|
// Synchronize selection
|
|
if (GEditor->GetSelectedActors()->IsSelected(ActorItem->Actor.Get()))
|
|
{
|
|
SceneOutliner->SetItemSelection(Item, true);
|
|
}
|
|
}
|
|
}
|
|
else if (FActorFolderTreeItem* FolderItem = Item->CastTo<FActorFolderTreeItem>())
|
|
{
|
|
if (FolderItem->World.IsValid())
|
|
{
|
|
FolderItem->Flags.bIsExpanded = FActorFolders::Get().IsFolderExpanded(*FolderItem->World, FolderItem->GetFolder());
|
|
}
|
|
}
|
|
else if (Item->IsA<FActorDescTreeItem>())
|
|
{
|
|
if (!Item->Flags.bIsFilteredOut)
|
|
{
|
|
++FilteredActorCount;
|
|
++FilteredUnloadedActorCount;
|
|
}
|
|
}
|
|
}
|
|
|
|
void FActorBrowsingMode::OnItemRemoved(FSceneOutlinerTreeItemPtr Item)
|
|
{
|
|
if (Item->IsA<FActorTreeItem>())
|
|
{
|
|
if (!Item->Flags.bIsFilteredOut)
|
|
{
|
|
--FilteredActorCount;
|
|
}
|
|
}
|
|
else if (Item->IsA<FActorDescTreeItem>())
|
|
{
|
|
if (!Item->Flags.bIsFilteredOut)
|
|
{
|
|
--FilteredActorCount;
|
|
--FilteredUnloadedActorCount;
|
|
}
|
|
}
|
|
}
|
|
|
|
void FActorBrowsingMode::OnComponentsUpdated()
|
|
{
|
|
SceneOutliner->FullRefresh();
|
|
}
|
|
|
|
void FActorBrowsingMode::OnLevelActorDeleted(AActor* Actor)
|
|
{
|
|
ApplicableActors.Remove(Actor);
|
|
}
|
|
|
|
void FActorBrowsingMode::OnSelectUnloadedActors(const TArray<FGuid>& ActorGuids)
|
|
{
|
|
TArray<FSceneOutlinerTreeItemPtr> ItemsToSelect;
|
|
ItemsToSelect.Reserve(ActorGuids.Num());
|
|
for (const FGuid& ActorGuid : ActorGuids)
|
|
{
|
|
if (FSceneOutlinerTreeItemPtr ItemPtr = SceneOutliner->GetTreeItem(ActorGuid))
|
|
{
|
|
ItemsToSelect.Add(ItemPtr);
|
|
}
|
|
}
|
|
|
|
if (ItemsToSelect.Num())
|
|
{
|
|
SceneOutliner->SetItemSelection(ItemsToSelect, true);
|
|
}
|
|
}
|
|
|
|
void FActorBrowsingMode::OnActorDescRemoved(FWorldPartitionActorDesc* InActorDesc)
|
|
{
|
|
ApplicableUnloadedActors.Remove(InActorDesc);
|
|
}
|
|
|
|
void FActorBrowsingMode::OnItemSelectionChanged(FSceneOutlinerTreeItemPtr TreeItem, ESelectInfo::Type SelectionType, const FSceneOutlinerItemSelection& Selection)
|
|
{
|
|
TArray<AActor*> SelectedActors = Selection.GetData<AActor*>(SceneOutliner::FActorSelector());
|
|
|
|
bool bChanged = false;
|
|
bool bAnyInPIE = false;
|
|
for (auto* Actor : SelectedActors)
|
|
{
|
|
if (!bAnyInPIE && Actor && Actor->GetOutermost()->HasAnyPackageFlags(PKG_PlayInEditor))
|
|
{
|
|
bAnyInPIE = true;
|
|
}
|
|
if (!GEditor->GetSelectedActors()->IsSelected(Actor))
|
|
{
|
|
bChanged = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
for (FSelectionIterator SelectionIt(*GEditor->GetSelectedActors()); SelectionIt && !bChanged; ++SelectionIt)
|
|
{
|
|
const AActor* Actor = CastChecked< AActor >(*SelectionIt);
|
|
if (!bAnyInPIE && Actor->GetOutermost()->HasAnyPackageFlags(PKG_PlayInEditor))
|
|
{
|
|
bAnyInPIE = true;
|
|
}
|
|
if (!SelectedActors.Contains(Actor))
|
|
{
|
|
// Actor has been deselected
|
|
bChanged = true;
|
|
|
|
// If actor was a group actor, remove its members from the ActorsToSelect list
|
|
const AGroupActor* DeselectedGroupActor = Cast<AGroupActor>(Actor);
|
|
if (DeselectedGroupActor)
|
|
{
|
|
TArray<AActor*> GroupActors;
|
|
DeselectedGroupActor->GetGroupActors(GroupActors);
|
|
|
|
for (auto* GroupActor : GroupActors)
|
|
{
|
|
SelectedActors.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);
|
|
GEditor->GetSelectedActors()->Modify();
|
|
|
|
// We'll batch selection changes instead by using BeginBatchSelectOperation()
|
|
GEditor->GetSelectedActors()->BeginBatchSelectOperation();
|
|
|
|
// Clear the selection.
|
|
GEditor->SelectNone(false, true, true);
|
|
|
|
const bool bShouldSelect = true;
|
|
const bool bNotifyAfterSelect = false;
|
|
const bool bSelectEvenIfHidden = true; // @todo outliner: Is this actually OK?
|
|
for (auto* Actor : SelectedActors)
|
|
{
|
|
UE_LOG(LogActorBrowser, Verbose, TEXT("Clicking on Actor (world outliner): %s (%s)"), *Actor->GetClass()->GetName(), *Actor->GetActorLabel());
|
|
GEditor->SelectActor(Actor, bShouldSelect, bNotifyAfterSelect, bSelectEvenIfHidden);
|
|
}
|
|
|
|
// Commit selection changes
|
|
GEditor->GetSelectedActors()->EndBatchSelectOperation(/*bNotify*/false);
|
|
|
|
// 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>())
|
|
{
|
|
AActor* Actor = ActorItem->Actor.Get();
|
|
check(Actor);
|
|
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
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.Get());
|
|
}
|
|
}
|
|
|
|
FReply FActorBrowsingMode::OnKeyDown(const FKeyEvent& InKeyEvent)
|
|
{
|
|
const FSceneOutlinerItemSelection& Selection = SceneOutliner->GetSelection();
|
|
|
|
// Rename key: Rename selected actors (not rebindable, because it doesn't make much sense to bind.)
|
|
if (InKeyEvent.GetKey() == EKeys::F2)
|
|
{
|
|
if (Selection.Num() == 1)
|
|
{
|
|
FSceneOutlinerTreeItemPtr ItemToRename = Selection.SelectedItems[0].Pin();
|
|
|
|
if (ItemToRename.IsValid() && CanRenameItem(*ItemToRename) && ItemToRename->CanInteract())
|
|
{
|
|
SceneOutliner->SetPendingRenameItem(ItemToRename);
|
|
SceneOutliner->ScrollItemIntoView(ItemToRename);
|
|
}
|
|
|
|
return FReply::Handled();
|
|
}
|
|
}
|
|
|
|
// F5 forces a full refresh
|
|
else if (InKeyEvent.GetKey() == EKeys::F5)
|
|
{
|
|
SceneOutliner->FullRefresh();
|
|
return FReply::Handled();
|
|
}
|
|
|
|
// Delete key: Delete selected actors (not rebindable, because it doesn't make much sense to bind.)
|
|
// Use Delete and Backspace instead of Platform_Delete because the LevelEditor default Edit Delete is bound to both
|
|
else if (InKeyEvent.GetKey() == EKeys::Delete || InKeyEvent.GetKey() == EKeys::BackSpace)
|
|
{
|
|
if (SceneOutliner->GetSharedData().CustomDelete.IsBound())
|
|
{
|
|
SceneOutliner->GetSharedData().CustomDelete.Execute(Selection.SelectedItems);
|
|
}
|
|
else
|
|
{
|
|
if (RepresentingWorld.IsValid())
|
|
{
|
|
GUnrealEd->Exec(RepresentingWorld.Get(), TEXT("DELETE"));
|
|
}
|
|
}
|
|
return FReply::Handled();
|
|
|
|
}
|
|
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::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");
|
|
}
|
|
|
|
bool FActorBrowsingMode::GetFolderNamesFromPayload(const FSceneOutlinerDragDropPayload& InPayload, TArray<FName>& OutFolders, FFolder::FRootObject& OutCommonRootObject) const
|
|
{
|
|
return FFolder::GetFolderPathsAndCommonRootObject(InPayload.GetData<FFolder>(SceneOutliner::FFolderPathSelector()), OutFolders, OutCommonRootObject);
|
|
}
|
|
|
|
TSharedPtr<FDragDropOperation> FActorBrowsingMode::CreateDragDropOperation(const FPointerEvent& MouseEvent, const TArray<FSceneOutlinerTreeItemPtr>& InTreeItems) const
|
|
{
|
|
FSceneOutlinerDragDropPayload DraggedObjects(InTreeItems);
|
|
|
|
// If the drag contains only actors, we shortcut and create a simple FActorDragDropGraphEdOp rather than an FSceneOutlinerDragDrop composite op.
|
|
if (DraggedObjects.Has<FActorTreeItem>() && !DraggedObjects.Has<FFolderTreeItem>())
|
|
{
|
|
return FActorDragDropGraphEdOp::New(DraggedObjects.GetData<TWeakObjectPtr<AActor>>(SceneOutliner::FWeakActorSelector()));
|
|
}
|
|
|
|
TSharedPtr<FSceneOutlinerDragDropOp> OutlinerOp = MakeShareable(new FSceneOutlinerDragDropOp());
|
|
|
|
if (DraggedObjects.Has<FActorTreeItem>())
|
|
{
|
|
TSharedPtr<FActorDragDropOp> ActorOperation = MakeShareable(new FActorDragDropGraphEdOp);
|
|
ActorOperation->Init(DraggedObjects.GetData<TWeakObjectPtr<AActor>>(SceneOutliner::FWeakActorSelector()));
|
|
OutlinerOp->AddSubOp(ActorOperation);
|
|
}
|
|
|
|
if (DraggedObjects.Has<FFolderTreeItem>())
|
|
{
|
|
FFolder::FRootObject CommonRootObject;
|
|
TArray<FName> DraggedFolders;
|
|
if (GetFolderNamesFromPayload(DraggedObjects, DraggedFolders, CommonRootObject))
|
|
{
|
|
TSharedPtr<FFolderDragDropOp> FolderOperation = MakeShareable(new FFolderDragDropOp);
|
|
FolderOperation->Init(DraggedFolders, RepresentingWorld.Get(), CommonRootObject);
|
|
OutlinerOp->AddSubOp(FolderOperation);
|
|
}
|
|
}
|
|
OutlinerOp->Construct();
|
|
return OutlinerOp;
|
|
}
|
|
|
|
bool FActorBrowsingMode::ParseDragDrop(FSceneOutlinerDragDropPayload& OutPayload, const FDragDropOperation& Operation) const
|
|
{
|
|
if (Operation.IsOfType<FSceneOutlinerDragDropOp>())
|
|
{
|
|
const auto& OutlinerOp = static_cast<const FSceneOutlinerDragDropOp&>(Operation);
|
|
if (const auto& FolderOp = OutlinerOp.GetSubOp<FFolderDragDropOp>())
|
|
{
|
|
for (const auto& Folder : FolderOp->Folders)
|
|
{
|
|
OutPayload.DraggedItems.Add(SceneOutliner->GetTreeItem(FFolder(FolderOp->RootObject, Folder)));
|
|
}
|
|
}
|
|
if (const auto& ActorOp = OutlinerOp.GetSubOp<FActorDragDropOp>())
|
|
{
|
|
for (const auto& Actor : ActorOp->Actors)
|
|
{
|
|
OutPayload.DraggedItems.Add(SceneOutliner->GetTreeItem(Actor.Get()));
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
else if (Operation.IsOfType<FActorDragDropOp>())
|
|
{
|
|
for (const TWeakObjectPtr<AActor>& Actor : static_cast<const FActorDragDropOp&>(Operation).Actors)
|
|
{
|
|
OutPayload.DraggedItems.Add(SceneOutliner->GetTreeItem(Actor.Get()));
|
|
}
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
FFolder FActorBrowsingMode::GetWorldDefaultRootFolder() const
|
|
{
|
|
return FFolder::GetWorldRootFolder(RepresentingWorld.Get());
|
|
}
|
|
|
|
FSceneOutlinerDragValidationInfo FActorBrowsingMode::ValidateDrop(const ISceneOutlinerTreeItem& DropTarget, const FSceneOutlinerDragDropPayload& Payload) const
|
|
{
|
|
if (Payload.Has<FFolderTreeItem>())
|
|
{
|
|
FFolder::FRootObject TargetRootObject = DropTarget.GetRootObject();
|
|
FFolder::FRootObject CommonPayloadFoldersRootObject;
|
|
TArray<FName> PayloadFolders;
|
|
const bool bHasCommonRootObject = GetFolderNamesFromPayload(Payload, PayloadFolders, CommonPayloadFoldersRootObject);
|
|
if (!bHasCommonRootObject)
|
|
{
|
|
return FSceneOutlinerDragValidationInfo(ESceneOutlinerDropCompatibility::IncompatibleGeneric, LOCTEXT("CantMoveFoldersWithMultipleRoots", "Cannot move folders with multiple roots"));
|
|
}
|
|
else if (CommonPayloadFoldersRootObject != TargetRootObject)
|
|
{
|
|
return FSceneOutlinerDragValidationInfo(ESceneOutlinerDropCompatibility::IncompatibleGeneric, LOCTEXT("CantChangeFoldersRoot", "Cannot change folders root"));
|
|
}
|
|
}
|
|
|
|
if (const FActorTreeItem* ActorItem = DropTarget.CastTo<FActorTreeItem>())
|
|
{
|
|
const AActor* ActorTarget = ActorItem->Actor.Get();
|
|
if (!ActorTarget)
|
|
{
|
|
return FSceneOutlinerDragValidationInfo(ESceneOutlinerDropCompatibility::IncompatibleGeneric, FText());
|
|
}
|
|
|
|
const ILevelInstanceInterface* LevelInstanceTarget = Cast<ILevelInstanceInterface>(ActorTarget);
|
|
const ULevelInstanceSubsystem* LevelInstanceSubsystem = RepresentingWorld->GetSubsystem<ULevelInstanceSubsystem>();
|
|
if (LevelInstanceTarget)
|
|
{
|
|
check(LevelInstanceSubsystem);
|
|
if (!LevelInstanceTarget->IsEditing())
|
|
{
|
|
return FSceneOutlinerDragValidationInfo(ESceneOutlinerDropCompatibility::IncompatibleGeneric, LOCTEXT("Error_AttachToClosedLevelInstance", "Cannot attach to LevelInstance which is not being edited"));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (Payload.Has<FFolderTreeItem>())
|
|
{
|
|
return FSceneOutlinerDragValidationInfo(ESceneOutlinerDropCompatibility::IncompatibleGeneric, LOCTEXT("FoldersOnActorError", "Cannot attach folders to actors"));
|
|
}
|
|
|
|
if (!Payload.Has<FActorTreeItem>())
|
|
{
|
|
return FSceneOutlinerDragValidationInfo(ESceneOutlinerDropCompatibility::IncompatibleGeneric, FText());
|
|
}
|
|
}
|
|
|
|
FText AttachErrorMsg;
|
|
bool bCanAttach = true;
|
|
bool bDraggedOntoAttachmentParent = true;
|
|
const auto& DragActors = Payload.GetData<TWeakObjectPtr<AActor>>(SceneOutliner::FWeakActorSelector());
|
|
for (const auto& DragActorPtr : DragActors)
|
|
{
|
|
AActor* DragActor = DragActorPtr.Get();
|
|
if (DragActor)
|
|
{
|
|
if (bCanAttach)
|
|
{
|
|
if (LevelInstanceSubsystem)
|
|
{
|
|
// Either all actors must be in a LevelInstance or none of them
|
|
if (const ILevelInstanceInterface* ParentLevelInstance = LevelInstanceSubsystem->GetParentLevelInstance(DragActor))
|
|
{
|
|
if (!ParentLevelInstance->IsEditing())
|
|
{
|
|
return FSceneOutlinerDragValidationInfo(ESceneOutlinerDropCompatibility::IncompatibleGeneric, LOCTEXT("Error_RemoveEditingLevelInstance", "Cannot detach from a LevelInstance which is not being edited"));
|
|
}
|
|
}
|
|
|
|
if (!LevelInstanceSubsystem->CanMoveActorToLevel(DragActor, &AttachErrorMsg))
|
|
{
|
|
bCanAttach = bDraggedOntoAttachmentParent = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (DragActor->IsChildActor())
|
|
{
|
|
AttachErrorMsg = FText::Format(LOCTEXT("Error_AttachChildActor", "Cannot move {0} as it is a child actor."), FText::FromString(DragActor->GetActorLabel()));
|
|
bCanAttach = bDraggedOntoAttachmentParent = false;
|
|
break;
|
|
}
|
|
if (!LevelInstanceTarget && !GEditor->CanParentActors(ActorTarget, DragActor, &AttachErrorMsg))
|
|
{
|
|
bCanAttach = false;
|
|
}
|
|
}
|
|
|
|
if (DragActor->GetSceneOutlinerParent() != ActorTarget)
|
|
{
|
|
bDraggedOntoAttachmentParent = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
const FText ActorLabel = FText::FromString(ActorTarget->GetActorLabel());
|
|
if (bDraggedOntoAttachmentParent)
|
|
{
|
|
if (DragActors.Num() == 1)
|
|
{
|
|
return FSceneOutlinerDragValidationInfo(ESceneOutlinerDropCompatibility::CompatibleDetach, ActorLabel);
|
|
}
|
|
else
|
|
{
|
|
return FSceneOutlinerDragValidationInfo(ESceneOutlinerDropCompatibility::CompatibleMultipleDetach, ActorLabel);
|
|
}
|
|
}
|
|
else if (bCanAttach)
|
|
{
|
|
if (DragActors.Num() == 1)
|
|
{
|
|
return FSceneOutlinerDragValidationInfo(ESceneOutlinerDropCompatibility::CompatibleAttach, ActorLabel);
|
|
}
|
|
else
|
|
{
|
|
return FSceneOutlinerDragValidationInfo(ESceneOutlinerDropCompatibility::CompatibleMultipleAttach, ActorLabel);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (DragActors.Num() == 1)
|
|
{
|
|
return FSceneOutlinerDragValidationInfo(ESceneOutlinerDropCompatibility::IncompatibleGeneric, AttachErrorMsg);
|
|
}
|
|
else
|
|
{
|
|
const FText ReasonText = FText::Format(LOCTEXT("DropOntoText", "{0}. {1}"), ActorLabel, AttachErrorMsg);
|
|
return FSceneOutlinerDragValidationInfo(ESceneOutlinerDropCompatibility::IncompatibleMultipleAttach, ReasonText);
|
|
}
|
|
}
|
|
}
|
|
else if (DropTarget.IsA<FFolderTreeItem>() || DropTarget.IsA<FWorldTreeItem>() || DropTarget.IsA<FLevelTreeItem>())
|
|
{
|
|
const FFolderTreeItem* FolderItem = DropTarget.CastTo<FFolderTreeItem>();
|
|
const FWorldTreeItem* WorldItem = DropTarget.CastTo<FWorldTreeItem>();
|
|
const FLevelTreeItem* LevelItem = DropTarget.CastTo<FLevelTreeItem>();
|
|
// WorldTreeItem and LevelTreeItem are treated as root folders (path = none), with the difference that LevelTreeItem has a RootObject.
|
|
const FFolder DestinationPath = FolderItem ? FolderItem->GetFolder() : (LevelItem ? FFolder(FFolder::GetOptionalFolderRootObject(LevelItem->Level.Get()).Get(FFolder::GetInvalidRootObject())) : GetWorldDefaultRootFolder());
|
|
const FFolder::FRootObject& DestinationRootObject = DestinationPath.GetRootObject();
|
|
ILevelInstanceInterface* LevelInstanceTarget = Cast<ILevelInstanceInterface>(DestinationPath.GetRootObjectPtr());
|
|
if (LevelInstanceTarget && !LevelInstanceTarget->IsEditing())
|
|
{
|
|
return FSceneOutlinerDragValidationInfo(ESceneOutlinerDropCompatibility::IncompatibleGeneric, LOCTEXT("Error_DragInNonEditingLevelInstance", "Cannot drag into a LevelInstance which is not being edited"));
|
|
}
|
|
|
|
if (Payload.Has<FFolderTreeItem>())
|
|
{
|
|
FFolder::FRootObject CommonFolderRootObject;
|
|
TArray<FName> DraggedFolders;
|
|
if (GetFolderNamesFromPayload(Payload, DraggedFolders, CommonFolderRootObject))
|
|
{
|
|
// Iterate over all the folders that have been dragged
|
|
for (const FName& DraggedFolder : DraggedFolders)
|
|
{
|
|
const FName Leaf = FEditorFolderUtils::GetLeafName(DraggedFolder);
|
|
const FName Parent = FEditorFolderUtils::GetParentPath(DraggedFolder);
|
|
|
|
if ((CommonFolderRootObject != DestinationRootObject) && FFolder::IsRootObjectValid(CommonFolderRootObject) && FFolder::IsRootObjectValid(DestinationRootObject))
|
|
{
|
|
FFormatNamedArguments Args;
|
|
Args.Add(TEXT("SourceName"), FText::FromName(Leaf));
|
|
FText Text = FText::Format(LOCTEXT("CantChangeFolderRoot", "Cannot change {SourceName} folder root"), Args);
|
|
return FSceneOutlinerDragValidationInfo(ESceneOutlinerDropCompatibility::IncompatibleGeneric, Text);
|
|
}
|
|
|
|
if (Parent == DestinationPath.GetPath())
|
|
{
|
|
FFormatNamedArguments Args;
|
|
Args.Add(TEXT("SourceName"), FText::FromName(Leaf));
|
|
|
|
FText Text;
|
|
if (DestinationPath.IsNone())
|
|
{
|
|
Text = FText::Format(LOCTEXT("FolderAlreadyAssignedRoot", "{SourceName} is already assigned to root"), Args);
|
|
}
|
|
else
|
|
{
|
|
Args.Add(TEXT("DestPath"), FText::FromName(DestinationPath.GetPath()));
|
|
Text = FText::Format(LOCTEXT("FolderAlreadyAssigned", "{SourceName} is already assigned to {DestPath}"), Args);
|
|
}
|
|
|
|
return FSceneOutlinerDragValidationInfo(ESceneOutlinerDropCompatibility::IncompatibleGeneric, Text);
|
|
}
|
|
|
|
const FString DragFolderPath = DraggedFolder.ToString();
|
|
const FString LeafName = Leaf.ToString();
|
|
const FString DstFolderPath = DestinationPath.IsNone() ? FString() : DestinationPath.ToString();
|
|
const FString NewPath = DstFolderPath / LeafName;
|
|
|
|
if (FActorFolders::Get().ContainsFolder(*RepresentingWorld, FFolder(DestinationRootObject, FName(*NewPath))))
|
|
{
|
|
// The folder already exists
|
|
FFormatNamedArguments Args;
|
|
Args.Add(TEXT("DragName"), FText::FromString(LeafName));
|
|
return FSceneOutlinerDragValidationInfo(ESceneOutlinerDropCompatibility::IncompatibleGeneric,
|
|
FText::Format(LOCTEXT("FolderAlreadyExistsRoot", "A folder called \"{DragName}\" already exists at this level"), Args));
|
|
}
|
|
else if (DragFolderPath == DstFolderPath || DstFolderPath.StartsWith(DragFolderPath + "/"))
|
|
{
|
|
// Cannot drag as a child of itself
|
|
FFormatNamedArguments Args;
|
|
Args.Add(TEXT("FolderPath"), FText::FromName(DraggedFolder));
|
|
return FSceneOutlinerDragValidationInfo(
|
|
ESceneOutlinerDropCompatibility::IncompatibleGeneric,
|
|
FText::Format(LOCTEXT("ChildOfItself", "Cannot move \"{FolderPath}\" to be a child of itself"), Args));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (Payload.Has<FActorTreeItem>())
|
|
{
|
|
const ULevelInstanceSubsystem* LevelInstanceSubsystem = RepresentingWorld->GetSubsystem<ULevelInstanceSubsystem>();
|
|
// Iterate over all the actors that have been dragged
|
|
for (const TWeakObjectPtr<AActor>& WeakActor : Payload.GetData<TWeakObjectPtr<AActor>>(SceneOutliner::FWeakActorSelector()))
|
|
{
|
|
const AActor* Actor = WeakActor.Get();
|
|
|
|
bool bActorContainedInLevelInstance = false;
|
|
if (LevelInstanceSubsystem)
|
|
{
|
|
if (const ILevelInstanceInterface* ParentLevelInstance = LevelInstanceSubsystem->GetParentLevelInstance(Actor))
|
|
{
|
|
if (!ParentLevelInstance->IsEditing())
|
|
{
|
|
return FSceneOutlinerDragValidationInfo(ESceneOutlinerDropCompatibility::IncompatibleGeneric, LOCTEXT("Error_RemoveEditingLevelInstance", "Cannot detach from a LevelInstance which is not being edited"));
|
|
}
|
|
bActorContainedInLevelInstance = true;
|
|
}
|
|
|
|
if (const ILevelInstanceInterface* LevelInstance = Cast<ILevelInstanceInterface>(Actor))
|
|
{
|
|
FText Reason;
|
|
if (!LevelInstanceSubsystem->CanMoveActorToLevel(Actor, &Reason))
|
|
{
|
|
return FSceneOutlinerDragValidationInfo(ESceneOutlinerDropCompatibility::IncompatibleGeneric, Reason);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (Actor->IsChildActor())
|
|
{
|
|
return FSceneOutlinerDragValidationInfo(ESceneOutlinerDropCompatibility::IncompatibleGeneric, FText::Format(LOCTEXT("Error_AttachChildActor", "Cannot move {0} as it is a child actor."), FText::FromString(Actor->GetActorLabel())));
|
|
}
|
|
else if ((Actor->GetFolderRootObject() != DestinationRootObject) && FFolder::IsRootObjectValid(Actor->GetFolderRootObject()) && FFolder::IsRootObjectValid(DestinationRootObject))
|
|
{
|
|
FFormatNamedArguments Args;
|
|
Args.Add(TEXT("SourceName"), FText::FromString(Actor->GetActorLabel()));
|
|
FText Text = FText::Format(LOCTEXT("CantChangeActorRoot", "Cannot change {SourceName} folder root"), Args);
|
|
return FSceneOutlinerDragValidationInfo(ESceneOutlinerDropCompatibility::IncompatibleGeneric, Text);
|
|
}
|
|
else if (Actor->GetFolder() == DestinationPath && !Actor->GetSceneOutlinerParent() && !bActorContainedInLevelInstance)
|
|
{
|
|
FFormatNamedArguments Args;
|
|
Args.Add(TEXT("SourceName"), FText::FromString(Actor->GetActorLabel()));
|
|
|
|
FText Text;
|
|
if (DestinationPath.IsNone())
|
|
{
|
|
Text = FText::Format(LOCTEXT("FolderAlreadyAssignedRoot", "{SourceName} is already assigned to root"), Args);
|
|
}
|
|
else
|
|
{
|
|
Args.Add(TEXT("DestPath"), FText::FromName(DestinationPath.GetPath()));
|
|
Text = FText::Format(LOCTEXT("FolderAlreadyAssigned", "{SourceName} is already assigned to {DestPath}"), Args);
|
|
}
|
|
|
|
return FSceneOutlinerDragValidationInfo(ESceneOutlinerDropCompatibility::IncompatibleGeneric, Text);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Everything else is a valid operation
|
|
if (DestinationPath.IsNone())
|
|
{
|
|
return FSceneOutlinerDragValidationInfo(ESceneOutlinerDropCompatibility::CompatibleGeneric, LOCTEXT("MoveToRoot", "Move to root"));
|
|
}
|
|
else
|
|
{
|
|
FFormatNamedArguments Args;
|
|
Args.Add(TEXT("DestPath"), FText::FromName(DestinationPath.GetPath()));
|
|
return FSceneOutlinerDragValidationInfo(ESceneOutlinerDropCompatibility::CompatibleGeneric, FText::Format(LOCTEXT("MoveInto", "Move into \"{DestPath}\""), Args));
|
|
}
|
|
}
|
|
else if (DropTarget.IsA<FComponentTreeItem>())
|
|
{
|
|
// we don't allow drag and drop on components for now
|
|
return FSceneOutlinerDragValidationInfo(ESceneOutlinerDropCompatibility::IncompatibleGeneric, FText());
|
|
}
|
|
return FSceneOutlinerDragValidationInfo::Invalid();
|
|
}
|
|
|
|
void FActorBrowsingMode::OnDrop(ISceneOutlinerTreeItem& DropTarget, const FSceneOutlinerDragDropPayload& Payload, const FSceneOutlinerDragValidationInfo& ValidationInfo) const
|
|
{
|
|
if (const FActorTreeItem* ActorItem = DropTarget.CastTo<FActorTreeItem>())
|
|
{
|
|
AActor* DropActor = ActorItem->Actor.Get();
|
|
if (!DropActor)
|
|
{
|
|
return;
|
|
}
|
|
|
|
FMessageLog EditorErrors("EditorErrors");
|
|
EditorErrors.NewPage(LOCTEXT("ActorAttachmentsPageLabel", "Actor attachment"));
|
|
|
|
if (ValidationInfo.CompatibilityType == ESceneOutlinerDropCompatibility::CompatibleMultipleDetach || ValidationInfo.CompatibilityType == ESceneOutlinerDropCompatibility::CompatibleDetach)
|
|
{
|
|
const FScopedTransaction Transaction(LOCTEXT("UndoAction_DetachActors", "Detach actors"));
|
|
|
|
TArray<TWeakObjectPtr<AActor>> DraggedActors = Payload.GetData<TWeakObjectPtr<AActor>>(SceneOutliner::FWeakActorSelector());
|
|
for (const auto& WeakActor : DraggedActors)
|
|
{
|
|
if (auto* DragActor = WeakActor.Get())
|
|
{
|
|
// Detach from parent
|
|
USceneComponent* RootComp = DragActor->GetRootComponent();
|
|
if (RootComp && RootComp->GetAttachParent())
|
|
{
|
|
AActor* OldParent = RootComp->GetAttachParent()->GetOwner();
|
|
// Attachment is persisted on the child so modify both actors for Undo/Redo but do not mark the Parent package dirty
|
|
OldParent->Modify(/*bAlwaysMarkDirty=*/false);
|
|
RootComp->DetachFromComponent(FDetachmentTransformRules::KeepWorldTransform);
|
|
|
|
DragActor->SetFolderPath_Recursively(OldParent->GetFolderPath());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (ValidationInfo.CompatibilityType == ESceneOutlinerDropCompatibility::CompatibleMultipleAttach || ValidationInfo.CompatibilityType == ESceneOutlinerDropCompatibility::CompatibleAttach)
|
|
{
|
|
// Show socket chooser if we have sockets to select
|
|
|
|
if (ILevelInstanceInterface* TargetLevelInstance = Cast<ILevelInstanceInterface>(DropActor))
|
|
{
|
|
check(TargetLevelInstance->IsEditing());
|
|
const FScopedTransaction Transaction(LOCTEXT("UndoAction_MoveActorsToLevelInstance", "Move actors to LevelInstance"));
|
|
|
|
const FFolder DestinationPath = FFolder(FFolder::FRootObject(DropActor));
|
|
auto MoveToDestination = [&DestinationPath](FFolderTreeItem& Item)
|
|
{
|
|
Item.MoveTo(DestinationPath);
|
|
};
|
|
Payload.ForEachItem<FFolderTreeItem>(MoveToDestination);
|
|
|
|
// Since target root is directly the Level Instance, clear folder path
|
|
TArray<AActor*> DraggedActors = Payload.GetData<AActor*>(SceneOutliner::FActorSelector());
|
|
for (auto& Actor : DraggedActors)
|
|
{
|
|
Actor->SetFolderPath_Recursively(FName());
|
|
}
|
|
|
|
ULevelInstanceSubsystem* LevelInstanceSubsystem = RepresentingWorld->GetSubsystem<ULevelInstanceSubsystem>();
|
|
check(LevelInstanceSubsystem);
|
|
LevelInstanceSubsystem->MoveActorsTo(TargetLevelInstance, DraggedActors);
|
|
}
|
|
else
|
|
{
|
|
auto PerformAttachment = [](FName SocketName, TWeakObjectPtr<AActor> Parent, const TArray<TWeakObjectPtr<AActor>> NewAttachments)
|
|
{
|
|
AActor* ParentActor = Parent.Get();
|
|
if (ParentActor)
|
|
{
|
|
// modify parent and child
|
|
const FScopedTransaction Transaction(LOCTEXT("UndoAction_PerformAttachment", "Attach actors"));
|
|
|
|
// Attach each child
|
|
bool bAttached = false;
|
|
for (auto& Child : NewAttachments)
|
|
{
|
|
AActor* ChildActor = Child.Get();
|
|
if (GEditor->CanParentActors(ParentActor, ChildActor))
|
|
{
|
|
GEditor->ParentActors(ParentActor, ChildActor, SocketName);
|
|
|
|
ChildActor->SetFolderPath_Recursively(ParentActor->GetFolderPath());
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
TArray<TWeakObjectPtr<AActor>> DraggedActors = Payload.GetData<TWeakObjectPtr<AActor>>(SceneOutliner::FWeakActorSelector());
|
|
//@TODO: Should create a menu for each component that contains sockets, or have some form of disambiguation within the menu (like a fully qualified path)
|
|
// Instead, we currently only display the sockets on the root component
|
|
USceneComponent* Component = DropActor->GetRootComponent();
|
|
if ((Component != NULL) && (Component->HasAnySockets()))
|
|
{
|
|
// Create the popup
|
|
FSlateApplication::Get().PushMenu(
|
|
SceneOutliner->AsShared(),
|
|
FWidgetPath(),
|
|
SNew(SSocketChooserPopup)
|
|
.SceneComponent(Component)
|
|
.OnSocketChosen_Lambda(PerformAttachment, DropActor, MoveTemp(DraggedActors)),
|
|
FSlateApplication::Get().GetCursorPos(),
|
|
FPopupTransitionEffect(FPopupTransitionEffect::TypeInPopup)
|
|
);
|
|
}
|
|
else
|
|
{
|
|
PerformAttachment(NAME_None, DropActor, MoveTemp(DraggedActors));
|
|
}
|
|
}
|
|
|
|
}
|
|
// Report errors
|
|
EditorErrors.Notify(NSLOCTEXT("ActorAttachmentError", "AttachmentsFailed", "Attachments Failed!"));
|
|
}
|
|
else if (DropTarget.IsA<FFolderTreeItem>() || DropTarget.IsA<FWorldTreeItem>() || DropTarget.IsA<FLevelTreeItem>())
|
|
{
|
|
const FFolderTreeItem* FolderItem = DropTarget.CastTo<FFolderTreeItem>();
|
|
const FWorldTreeItem* WorldItem = DropTarget.CastTo<FWorldTreeItem>();
|
|
const FLevelTreeItem* LevelItem = DropTarget.CastTo<FLevelTreeItem>();
|
|
// WorldTreeItem and LevelTreeItem are treated as root folders (path = none), with the difference that LevelTreeItem has a RootObject.
|
|
const FFolder DestinationPath = FolderItem ? FolderItem->GetFolder() : (LevelItem ? FFolder(FFolder::GetOptionalFolderRootObject(LevelItem->Level.Get()).Get(FFolder::GetInvalidRootObject())) : GetWorldDefaultRootFolder());
|
|
|
|
const FScopedTransaction Transaction(LOCTEXT("MoveOutlinerItems", "Move World Outliner Items"));
|
|
|
|
auto MoveToDestination = [&DestinationPath](FFolderTreeItem& Item)
|
|
{
|
|
Item.MoveTo(DestinationPath);
|
|
};
|
|
Payload.ForEachItem<FFolderTreeItem>(MoveToDestination);
|
|
|
|
// Set the folder path on all the dragged actors, and detach any that need to be moved
|
|
if (Payload.Has<FActorTreeItem>())
|
|
{
|
|
TSet<const AActor*> ParentActors;
|
|
TSet<const AActor*> ChildActors;
|
|
|
|
TArray<AActor*> MovingActorsToValidRootObject;
|
|
Payload.ForEachItem<FActorTreeItem>([&DestinationPath, &ParentActors, &ChildActors, &MovingActorsToValidRootObject](const FActorTreeItem& ActorItem)
|
|
{
|
|
AActor* Actor = ActorItem.Actor.Get();
|
|
if (Actor)
|
|
{
|
|
// First mark this object as a parent, then set its children's path
|
|
ParentActors.Add(Actor);
|
|
|
|
const FFolder SrcFolder = Actor->GetFolder();
|
|
|
|
// If the folder root object changes, 1st pass will put actors at root. 2nd pass will set the destination path.
|
|
FName NewPath = (SrcFolder.GetRootObject() == DestinationPath.GetRootObject()) ? DestinationPath.GetPath() : NAME_None;
|
|
|
|
Actor->SetFolderPath(NewPath);
|
|
FActorEditorUtils::TraverseActorTree_ParentFirst(Actor, [&](AActor* InActor) {
|
|
ChildActors.Add(InActor);
|
|
InActor->SetFolderPath(NewPath);
|
|
return true;
|
|
}, false);
|
|
|
|
if ((Actor->GetFolderRootObject() != DestinationPath.GetRootObject()) && SrcFolder.IsRootObjectPersistentLevel() && (DestinationPath.IsRootObjectValid() && !DestinationPath.IsRootObjectPersistentLevel()))
|
|
{
|
|
MovingActorsToValidRootObject.Add(Actor);
|
|
}
|
|
}
|
|
});
|
|
|
|
// Detach parent actors
|
|
for (const AActor* Parent : ParentActors)
|
|
{
|
|
auto* RootComp = Parent->GetRootComponent();
|
|
|
|
// We don't detach if it's a child of another that's been dragged
|
|
if (RootComp && RootComp->GetAttachParent() && !ChildActors.Contains(Parent))
|
|
{
|
|
if (AActor* OldParentActor = RootComp->GetAttachParent()->GetOwner())
|
|
{
|
|
// Attachment is persisted on the child so modify both actors for Undo/Redo but do not mark the Parent package dirty
|
|
OldParentActor->Modify(/*bAlwaysMarkDirty=*/false);
|
|
}
|
|
RootComp->DetachFromComponent(FDetachmentTransformRules::KeepWorldTransform);
|
|
}
|
|
}
|
|
|
|
auto MoveActorsToLevel = [](const TArray<AActor*>& InActorsToMove, ULevel* InDestLevel, const FName& InDestinationPath)
|
|
{
|
|
// We are moving actors to another level
|
|
const bool bWarnAboutReferences = true;
|
|
const bool bWarnAboutRenaming = true;
|
|
const bool bMoveAllOrFail = true;
|
|
TArray<AActor*> MovedActors;
|
|
if (!EditorLevelUtils::MoveActorsToLevel(InActorsToMove, InDestLevel, bWarnAboutReferences, bWarnAboutRenaming, bMoveAllOrFail, &MovedActors))
|
|
{
|
|
UE_LOG(LogActorBrowser, Warning, TEXT("Failed to move actors because not all actors could be moved"));
|
|
}
|
|
// Once moved, update actors folder path
|
|
for (AActor* Actor : MovedActors)
|
|
{
|
|
Actor->SetFolderPath_Recursively(InDestinationPath);
|
|
}
|
|
};
|
|
|
|
if (DestinationPath.IsRootObjectPersistentLevel())
|
|
{
|
|
const ULevelInstanceSubsystem* LevelInstanceSubsystem = RepresentingWorld->GetSubsystem<ULevelInstanceSubsystem>();
|
|
check(LevelInstanceSubsystem);
|
|
ULevel* DestinationLevel = RepresentingWorld->PersistentLevel;
|
|
check(DestinationLevel);
|
|
|
|
TArray<AActor*> LevelInstanceActorsToMove;
|
|
TArray<AActor*> ActorsToMoveToPersistentLevel;
|
|
Payload.ForEachItem<FActorTreeItem>([LevelInstanceSubsystem, &LevelInstanceActorsToMove, &ActorsToMoveToPersistentLevel](const FActorTreeItem& ActorItem)
|
|
{
|
|
AActor* Actor = ActorItem.Actor.Get();
|
|
if (const ILevelInstanceInterface* ParentLevelInstance = LevelInstanceSubsystem->GetParentLevelInstance(Actor))
|
|
{
|
|
check(ParentLevelInstance->IsEditing());
|
|
LevelInstanceActorsToMove.Add(Actor);
|
|
}
|
|
else
|
|
{
|
|
const FFolder ActorSrcFolder = Actor->GetFolder();
|
|
if (ActorSrcFolder.IsRootObjectValid() && !ActorSrcFolder.IsRootObjectPersistentLevel())
|
|
{
|
|
ActorsToMoveToPersistentLevel.Add(Actor);
|
|
}
|
|
}
|
|
});
|
|
|
|
// We are moving actors outside of an editing level instance to a folder (or root) into the persistent level.
|
|
if (LevelInstanceActorsToMove.Num() > 0)
|
|
{
|
|
TArray<AActor*> MovedActors;
|
|
LevelInstanceSubsystem->MoveActorsToLevel(LevelInstanceActorsToMove, DestinationLevel, &MovedActors);
|
|
// Once moved, update actors folder path
|
|
for (AActor* Actor : MovedActors)
|
|
{
|
|
Actor->SetFolderPath_Recursively(DestinationPath.GetPath());
|
|
}
|
|
}
|
|
if (ActorsToMoveToPersistentLevel.Num() > 0)
|
|
{
|
|
MoveActorsToLevel(ActorsToMoveToPersistentLevel, DestinationLevel, DestinationPath.GetPath());
|
|
}
|
|
}
|
|
else if (MovingActorsToValidRootObject.Num())
|
|
{
|
|
if (ILevelInstanceInterface* TargetLevelInstance = Cast<ILevelInstanceInterface>(DestinationPath.GetRootObjectPtr()))
|
|
{
|
|
// We are moving actors inside an editing level instance
|
|
check(TargetLevelInstance->IsEditing());
|
|
|
|
ULevelInstanceSubsystem* LevelInstanceSubsystem = RepresentingWorld->GetSubsystem<ULevelInstanceSubsystem>();
|
|
check(LevelInstanceSubsystem);
|
|
TArray<AActor*> MovedActors;
|
|
LevelInstanceSubsystem->MoveActorsTo(TargetLevelInstance, MovingActorsToValidRootObject, &MovedActors);
|
|
// Once moved, update actors folder path
|
|
for (AActor* Actor : MovedActors)
|
|
{
|
|
Actor->SetFolderPath_Recursively(DestinationPath.GetPath());
|
|
}
|
|
}
|
|
else if (ULevel* DestinationLevel = Cast<ULevel>(DestinationPath.GetRootObjectPtr()))
|
|
{
|
|
MoveActorsToLevel(MovingActorsToValidRootObject, DestinationLevel, DestinationPath.GetPath());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
FFolder FActorBrowsingMode::CreateNewFolder()
|
|
{
|
|
const FScopedTransaction Transaction(LOCTEXT("UndoAction_CreateFolder", "Create Folder"));
|
|
TArray<FFolder> SelectedFolders = SceneOutliner->GetSelection().GetData<FFolder>(SceneOutliner::FFolderPathSelector());
|
|
const FFolder NewFolderName = FActorFolders::Get().GetDefaultFolderForSelection(*RepresentingWorld, &SelectedFolders);
|
|
FActorFolders::Get().CreateFolderContainingSelection(*RepresentingWorld, NewFolderName);
|
|
|
|
return NewFolderName;
|
|
}
|
|
|
|
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
|
|
return FActorFolders::Get().GetFolderName(*RepresentingWorld, ParentPath, LeafName);
|
|
}
|
|
|
|
bool FActorBrowsingMode::CreateFolder(const FFolder& NewPath)
|
|
{
|
|
return FActorFolders::Get().CreateFolder(*RepresentingWorld, NewPath);
|
|
}
|
|
|
|
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)
|
|
{
|
|
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());
|
|
}
|
|
}
|
|
|
|
TArray<FSceneOutlinerTreeItemPtr> ChildrenItems;
|
|
for (const auto& Child : Item->GetChildren())
|
|
{
|
|
if (Child.IsValid())
|
|
{
|
|
ChildrenItems.Add(Child.Pin());
|
|
}
|
|
}
|
|
|
|
if (ChildrenItems.Num())
|
|
{
|
|
RecursiveAddItemsToActorGuidList(ChildrenItems, List);
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
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();
|
|
}
|
|
|
|
void FActorBrowsingMode::PinItems(const TArray<FSceneOutlinerTreeItemPtr>& InItems)
|
|
{
|
|
UWorldPartition* const WorldPartition = RepresentingWorld->GetWorldPartition();
|
|
if (!WorldPartition)
|
|
{
|
|
return;
|
|
}
|
|
|
|
TArray<FGuid> ActorsToPin;
|
|
ActorBrowsingModeUtils::RecursiveAddItemsToActorGuidList(InItems, ActorsToPin);
|
|
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
void FActorBrowsingMode::UnpinItems(const TArray<FSceneOutlinerTreeItemPtr>& InItems)
|
|
{
|
|
UWorldPartition* const WorldPartition = RepresentingWorld->GetWorldPartition();
|
|
if (!WorldPartition)
|
|
{
|
|
return;
|
|
}
|
|
|
|
TArray<FGuid> ActorsToUnpin;
|
|
ActorBrowsingModeUtils::RecursiveAddItemsToActorGuidList(InItems, ActorsToUnpin);
|
|
|
|
if (ActorsToUnpin.Num())
|
|
{
|
|
GEditor->GetSelectedActors()->BeginBatchSelectOperation();
|
|
|
|
AActor* LastPinnedActor = nullptr;
|
|
for (const FGuid& ActorGuid : ActorsToUnpin)
|
|
{
|
|
if (FWorldPartitionHandle ActorHandle(WorldPartition, ActorGuid); ActorHandle.IsValid())
|
|
{
|
|
if (AActor* PinnedActor = ActorHandle->GetActor())
|
|
{
|
|
GEditor->SelectActor(PinnedActor, /*bInSelected=*/false, /*bNotify=*/false);
|
|
}
|
|
}
|
|
}
|
|
|
|
WorldPartition->UnpinActors(ActorsToUnpin);
|
|
|
|
GEditor->GetSelectedActors()->EndBatchSelectOperation(/*bNotify=*/true);
|
|
}
|
|
}
|
|
|
|
void FActorBrowsingMode::PinSelectedItems()
|
|
{
|
|
const FSceneOutlinerItemSelection Selection = SceneOutliner->GetSelection();
|
|
if (Selection.Num())
|
|
{
|
|
TArray<FSceneOutlinerTreeItemPtr> ItemsToPin;
|
|
Selection.ForEachItem([this, &ItemsToPin](const FSceneOutlinerTreeItemPtr& TreeItem)
|
|
{
|
|
ItemsToPin.Add(TreeItem);
|
|
return true;
|
|
});
|
|
PinItems(ItemsToPin);
|
|
}
|
|
}
|
|
|
|
void FActorBrowsingMode::UnpinSelectedItems()
|
|
{
|
|
const FSceneOutlinerItemSelection Selection = SceneOutliner->GetSelection();
|
|
if(Selection.Num())
|
|
{
|
|
TArray<FSceneOutlinerTreeItemPtr> ItemsToUnpin;
|
|
Selection.ForEachItem([this, &ItemsToUnpin](const FSceneOutlinerTreeItemPtr& TreeItem)
|
|
{
|
|
ItemsToUnpin.Add(TreeItem);
|
|
return true;
|
|
});
|
|
UnpinItems(ItemsToUnpin);
|
|
}
|
|
}
|
|
|
|
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();
|
|
}
|
|
|
|
#undef LOCTEXT_NAMESPACE
|