2020-07-22 09:35:57 -04:00
// Copyright Epic Games, Inc. All Rights Reserved.
# include "ActorMode.h"
# include "ActorHierarchy.h"
# include "Engine/Selection.h"
# include "Editor/UnrealEdEngine.h"
# include "SceneOutlinerDelegates.h"
# include "ActorEditorUtils.h"
# include "LevelUtils.h"
# include "GameFramework/WorldSettings.h"
# include "DragAndDrop/ActorDragDropOp.h"
# include "Framework/MultiBox/MultiBoxBuilder.h"
# include "ActorTreeItem.h"
2022-01-07 10:01:19 -05:00
# include "LevelTreeItem.h"
2020-07-22 09:35:57 -04:00
# include "FolderTreeItem.h"
# include "ComponentTreeItem.h"
2021-03-10 12:35:33 -04:00
# include "ActorDescTreeItem.h"
2020-07-22 09:35:57 -04:00
# include "WorldTreeItem.h"
2022-03-29 09:19:22 -04:00
# include "LevelInstance/LevelInstanceInterface.h"
2021-11-23 14:43:39 -05:00
# include "LevelInstance/LevelInstanceSubsystem.h"
2020-08-25 06:31:46 -04:00
# include "LevelInstance/LevelInstanceEditorInstanceActor.h"
2022-05-10 14:20:42 -04:00
# include "LevelEditor.h"
# include "Modules/ModuleManager.h"
2020-07-22 09:35:57 -04:00
# define LOCTEXT_NAMESPACE "SceneOutliner_ActorMode"
2020-07-28 09:25:56 -04:00
using FActorFilter = TSceneOutlinerPredicateFilter < FActorTreeItem > ;
2021-11-23 14:43:39 -05:00
using FFolderFilter = TSceneOutlinerPredicateFilter < FFolderTreeItem > ;
2020-07-28 09:25:56 -04:00
2020-07-22 09:35:57 -04:00
namespace SceneOutliner
{
2020-07-28 09:25:56 -04:00
bool FWeakActorSelector : : operator ( ) ( const TWeakPtr < ISceneOutlinerTreeItem > & Item , TWeakObjectPtr < AActor > & DataOut ) const
2020-07-22 09:35:57 -04:00
{
2020-09-08 14:16:22 -04:00
if ( TSharedPtr < ISceneOutlinerTreeItem > ItemPtr = Item . Pin ( ) )
2020-07-22 09:35:57 -04:00
{
2020-09-08 14:16:22 -04:00
if ( FActorTreeItem * ActorItem = ItemPtr - > CastTo < FActorTreeItem > ( ) )
2020-07-22 09:35:57 -04:00
{
2020-09-08 14:16:22 -04:00
if ( ActorItem - > IsValid ( ) )
{
DataOut = ActorItem - > Actor ;
return true ;
}
2020-07-22 09:35:57 -04:00
}
}
return false ;
}
2020-07-28 09:25:56 -04:00
bool FActorSelector : : operator ( ) ( const TWeakPtr < ISceneOutlinerTreeItem > & Item , AActor * & ActorPtrOut ) const
2020-07-22 09:35:57 -04:00
{
2020-09-08 14:16:22 -04:00
if ( TSharedPtr < ISceneOutlinerTreeItem > ItemPtr = Item . Pin ( ) )
2020-07-22 09:35:57 -04:00
{
2020-09-08 14:16:22 -04:00
if ( FActorTreeItem * ActorItem = ItemPtr - > CastTo < FActorTreeItem > ( ) )
2020-07-22 09:35:57 -04:00
{
2020-09-08 14:16:22 -04:00
if ( ActorItem - > IsValid ( ) )
2020-07-22 09:35:57 -04:00
{
2020-09-08 14:16:22 -04:00
AActor * Actor = ActorItem - > Actor . Get ( ) ;
if ( Actor )
{
ActorPtrOut = Actor ;
return true ;
}
2020-07-22 09:35:57 -04:00
}
}
2020-09-08 14:16:22 -04:00
// If a component is selected, we meant for the owning actor to be selected
else if ( FComponentTreeItem * ComponentItem = ItemPtr - > CastTo < FComponentTreeItem > ( ) )
2020-07-22 09:35:57 -04:00
{
2020-09-08 14:16:22 -04:00
if ( ComponentItem - > IsValid ( ) )
2020-07-22 09:35:57 -04:00
{
2020-09-08 14:16:22 -04:00
AActor * Actor = ComponentItem - > Component - > GetOwner ( ) ;
if ( Actor )
{
ActorPtrOut = Actor ;
return true ;
}
2020-07-22 09:35:57 -04:00
}
}
}
return false ;
}
2020-07-28 09:25:56 -04:00
}
2020-07-22 09:35:57 -04:00
2020-08-13 10:46:40 -04:00
FActorMode : : FActorMode ( const FActorModeParams & Params )
: ISceneOutlinerMode ( Params . SceneOutliner )
2021-03-17 17:02:30 -04:00
, SpecifiedWorldToDisplay ( Params . SpecifiedWorldToDisplay )
2020-08-13 10:46:40 -04:00
, bHideComponents ( Params . bHideComponents )
2021-07-15 12:33:03 -04:00
, bHideActorWithNoComponent ( Params . bHideActorWithNoComponent )
2020-08-25 06:31:46 -04:00
, bHideLevelInstanceHierarchy ( Params . bHideLevelInstanceHierarchy )
2021-03-17 17:02:30 -04:00
, bHideUnloadedActors ( Params . bHideUnloadedActors )
2020-07-28 09:25:56 -04:00
{
2020-08-13 10:46:40 -04:00
SceneOutliner - > AddFilter ( MakeShared < FActorFilter > ( FActorTreeItem : : FFilterPredicate : : CreateLambda ( [ this ] ( const AActor * Actor )
2022-01-07 10:01:19 -05:00
{
return IsActorDisplayable ( Actor ) ;
} ) , FSceneOutlinerFilter : : EDefaultBehaviour : : Pass ) ) ;
2021-11-23 14:43:39 -05:00
2022-01-07 10:01:19 -05:00
auto FolderPassesFilter = [ this ] ( const FFolder & InFolder , bool bInCheckHideLevelInstanceFlag )
{
2022-03-29 09:19:22 -04:00
if ( ILevelInstanceInterface * LevelInstance = Cast < ILevelInstanceInterface > ( InFolder . GetRootObjectPtr ( ) ) )
2022-01-07 10:01:19 -05:00
{
if ( LevelInstance - > IsEditing ( ) )
2021-11-23 14:43:39 -05:00
{
return true ;
}
2022-01-07 10:01:19 -05:00
if ( bInCheckHideLevelInstanceFlag )
2021-11-23 14:43:39 -05:00
{
2022-01-07 10:01:19 -05:00
return ! bHideLevelInstanceHierarchy ;
2021-11-23 14:43:39 -05:00
}
2022-01-07 10:01:19 -05:00
}
if ( ULevel * Level = Cast < ULevel > ( InFolder . GetRootObjectPtr ( ) ) )
{
return true ;
}
return false ;
} ;
SceneOutliner - > AddFilter ( MakeShared < FFolderFilter > ( FFolderTreeItem : : FFilterPredicate : : CreateLambda ( [ FolderPassesFilter ] ( const FFolder & InFolder )
{
return FolderPassesFilter ( InFolder , /*bCheckHideLevelInstanceFlag*/ true ) ;
} ) , FSceneOutlinerFilter : : EDefaultBehaviour : : Pass ) ) ;
SceneOutliner - > AddInteractiveFilter ( MakeShared < FFolderFilter > ( FFolderTreeItem : : FFilterPredicate : : CreateLambda ( [ FolderPassesFilter ] ( const FFolder & InFolder )
{
return FolderPassesFilter ( InFolder , /*bCheckHideLevelInstanceFlag*/ false ) ;
} ) , FSceneOutlinerFilter : : EDefaultBehaviour : : Pass ) ) ;
2020-07-28 09:25:56 -04:00
}
FActorMode : : ~ FActorMode ( )
{
2020-07-22 09:35:57 -04:00
}
2020-07-28 09:25:56 -04:00
TUniquePtr < ISceneOutlinerHierarchy > FActorMode : : CreateHierarchy ( )
{
2020-08-13 10:46:40 -04:00
TUniquePtr < FActorHierarchy > ActorHierarchy = FActorHierarchy : : Create ( this , RepresentingWorld ) ;
2020-07-28 09:25:56 -04:00
ActorHierarchy - > SetShowingComponents ( ! bHideComponents ) ;
2021-07-15 12:33:03 -04:00
ActorHierarchy - > SetShowingOnlyActorWithValidComponents ( ! bHideComponents & & bHideActorWithNoComponent ) ;
2020-08-25 06:31:46 -04:00
ActorHierarchy - > SetShowingLevelInstances ( ! bHideLevelInstanceHierarchy ) ;
2021-03-17 17:02:30 -04:00
ActorHierarchy - > SetShowingUnloadedActors ( ! bHideUnloadedActors ) ;
2020-07-28 09:25:56 -04:00
return ActorHierarchy ;
}
void FActorMode : : Rebuild ( )
{
ChooseRepresentingWorld ( ) ;
Hierarchy = CreateHierarchy ( ) ;
}
void FActorMode : : ChooseRepresentingWorld ( )
{
// Select a world to represent
RepresentingWorld = nullptr ;
// If a specified world was provided, represent it
if ( SpecifiedWorldToDisplay . IsValid ( ) )
{
RepresentingWorld = SpecifiedWorldToDisplay . Get ( ) ;
}
// check if the user-chosen world is valid and in the editor contexts
if ( ! RepresentingWorld . IsValid ( ) & & UserChosenWorld . IsValid ( ) )
{
for ( const FWorldContext & Context : GEngine - > GetWorldContexts ( ) )
{
if ( UserChosenWorld . Get ( ) = = Context . World ( ) )
{
RepresentingWorld = UserChosenWorld . Get ( ) ;
break ;
}
}
}
// If the user did not manually select a world, try to pick the most suitable world context
if ( ! RepresentingWorld . IsValid ( ) )
{
// ideally we want a PIE world that is standalone or the first client
2021-03-03 19:39:53 -04:00
int32 LowestClientInstanceSeen = MAX_int32 ;
2020-07-28 09:25:56 -04:00
for ( const FWorldContext & Context : GEngine - > GetWorldContexts ( ) )
{
UWorld * World = Context . World ( ) ;
if ( World & & Context . WorldType = = EWorldType : : PIE )
{
if ( World - > GetNetMode ( ) = = NM_Standalone )
{
RepresentingWorld = World ;
break ;
}
2021-03-03 19:39:53 -04:00
else if ( ( World - > GetNetMode ( ) = = NM_Client ) & & ( Context . PIEInstance < LowestClientInstanceSeen ) )
2020-07-28 09:25:56 -04:00
{
RepresentingWorld = World ;
2021-03-03 19:39:53 -04:00
LowestClientInstanceSeen = Context . PIEInstance ;
2020-07-28 09:25:56 -04:00
}
}
}
}
if ( RepresentingWorld = = nullptr )
{
2021-03-03 19:39:53 -04:00
// still no world so fallback to old logic where we just prefer PIE over Editor
2020-07-28 09:25:56 -04:00
for ( const FWorldContext & Context : GEngine - > GetWorldContexts ( ) )
{
if ( Context . WorldType = = EWorldType : : PIE )
{
RepresentingWorld = Context . World ( ) ;
break ;
}
else if ( Context . WorldType = = EWorldType : : Editor )
{
RepresentingWorld = Context . World ( ) ;
}
}
}
}
void FActorMode : : BuildWorldPickerMenu ( FMenuBuilder & MenuBuilder )
{
MenuBuilder . BeginSection ( " Worlds " , LOCTEXT ( " WorldsHeading " , " Worlds " ) ) ;
{
MenuBuilder . AddMenuEntry (
LOCTEXT ( " AutoWorld " , " Auto " ) ,
LOCTEXT ( " AutoWorldToolTip " , " Automatically pick the world to display based on context. " ) ,
FSlateIcon ( ) ,
FUIAction (
FExecuteAction : : CreateRaw ( this , & FActorMode : : OnSelectWorld , TWeakObjectPtr < UWorld > ( ) ) ,
FCanExecuteAction ( ) ,
FIsActionChecked : : CreateRaw ( this , & FActorMode : : IsWorldChecked , TWeakObjectPtr < UWorld > ( ) )
) ,
NAME_None ,
EUserInterfaceActionType : : RadioButton
) ;
for ( const FWorldContext & Context : GEngine - > GetWorldContexts ( ) )
{
UWorld * World = Context . World ( ) ;
if ( World & & ( World - > WorldType = = EWorldType : : PIE | | Context . WorldType = = EWorldType : : Editor ) )
{
MenuBuilder . AddMenuEntry (
SceneOutliner : : GetWorldDescription ( World ) ,
LOCTEXT ( " ChooseWorldToolTip " , " Display actors for this world. " ) ,
FSlateIcon ( ) ,
FUIAction (
FExecuteAction : : CreateRaw ( this , & FActorMode : : OnSelectWorld , MakeWeakObjectPtr ( World ) ) ,
FCanExecuteAction ( ) ,
FIsActionChecked : : CreateRaw ( this , & FActorMode : : IsWorldChecked , MakeWeakObjectPtr ( World ) )
) ,
NAME_None ,
EUserInterfaceActionType : : RadioButton
) ;
}
}
}
MenuBuilder . EndSection ( ) ;
}
void FActorMode : : OnSelectWorld ( TWeakObjectPtr < UWorld > World )
{
UserChosenWorld = World ;
SceneOutliner - > FullRefresh ( ) ;
}
bool FActorMode : : IsWorldChecked ( TWeakObjectPtr < UWorld > World ) const
{
return ( UserChosenWorld = = World ) | | ( World . IsExplicitlyNull ( ) & & ! UserChosenWorld . IsValid ( ) ) ;
}
void FActorMode : : SynchronizeActorSelection ( )
{
USelection * SelectedActors = GEditor - > GetSelectedActors ( ) ;
// Deselect actors in the tree that are no longer selected in the world
const FSceneOutlinerItemSelection Selection ( SceneOutliner - > GetSelection ( ) ) ;
auto DeselectActors = [ this ] ( FActorTreeItem & Item )
{
if ( ! Item . Actor . IsValid ( ) | | ! Item . Actor . Get ( ) - > IsSelected ( ) )
{
SceneOutliner - > SetItemSelection ( Item . AsShared ( ) , false ) ;
}
} ;
Selection . ForEachItem < FActorTreeItem > ( DeselectActors ) ;
// Show actor selection but only if sub objects are not selected
if ( ! Selection . Has < FComponentTreeItem > ( ) )
{
// See if the tree view selector is pointing at a selected item
bool bSelectorInSelectionSet = false ;
TArray < FSceneOutlinerTreeItemPtr > ActorItems ;
for ( FSelectionIterator SelectionIt ( * SelectedActors ) ; SelectionIt ; + + SelectionIt )
{
AActor * Actor = CastChecked < AActor > ( * SelectionIt ) ;
if ( FSceneOutlinerTreeItemPtr ActorItem = SceneOutliner - > GetTreeItem ( Actor ) )
{
if ( ! bSelectorInSelectionSet & & SceneOutliner - > HasSelectorFocus ( ActorItem ) )
{
bSelectorInSelectionSet = true ;
}
ActorItems . Add ( ActorItem ) ;
}
}
// If NOT bSelectorInSelectionSet then we want to just move the selector to the first selected item.
ESelectInfo : : Type SelectInfo = bSelectorInSelectionSet ? ESelectInfo : : Direct : ESelectInfo : : OnMouseClick ;
SceneOutliner - > AddToSelection ( ActorItems , SelectInfo ) ;
}
FSceneOutlinerDelegates : : Get ( ) . SelectionChanged . Broadcast ( ) ;
}
bool FActorMode : : IsActorDisplayable ( const AActor * Actor ) const
2021-01-25 16:15:52 -04:00
{
return FActorMode : : IsActorDisplayable ( SceneOutliner , Actor ) ;
}
2022-05-24 06:58:06 -04:00
FFolder : : FRootObject FActorMode : : GetRootObject ( ) const
{
return FFolder : : GetWorldRootFolder ( RepresentingWorld . Get ( ) ) . GetRootObject ( ) ;
}
FFolder : : FRootObject FActorMode : : GetPasteTargetRootObject ( ) const
{
if ( UWorld * World = RepresentingWorld . Get ( ) )
{
return FFolder : : GetOptionalFolderRootObject ( World - > GetCurrentLevel ( ) ) . Get ( FFolder : : GetWorldRootFolder ( World ) . GetRootObject ( ) ) ;
}
return FFolder : : GetInvalidRootObject ( ) ;
}
2021-01-25 16:15:52 -04:00
bool FActorMode : : IsActorDisplayable ( const SSceneOutliner * SceneOutliner , const AActor * Actor )
2020-07-28 09:25:56 -04:00
{
static const FName SequencerActorTag ( TEXT ( " SequencerActor " ) ) ;
2020-08-13 10:46:40 -04:00
return Actor & &
2021-08-18 10:18:25 -04:00
! SceneOutliner - > GetSharedData ( ) . bOnlyShowFolders & & // Don't show actors if we're only showing folders
Actor - > IsEditable ( ) & & // Only show actors that are allowed to be selected and drawn in editor
2020-07-28 09:25:56 -04:00
Actor - > IsListedInSceneOutliner ( ) & &
2021-08-18 10:18:25 -04:00
( ( ( Actor - > GetWorld ( ) & & Actor - > GetWorld ( ) - > IsPlayInEditor ( ) ) | | ! Actor - > HasAnyFlags ( RF_Transient ) ) | |
( SceneOutliner - > GetSharedData ( ) . bShowTransient & & Actor - > HasAnyFlags ( RF_Transient ) ) | | // Don't show transient actors in non-play worlds
( Actor - > ActorHasTag ( SequencerActorTag ) ) ) & &
! Actor - > IsTemplate ( ) & & // Should never happen, but we never want CDOs displayed
! FActorEditorUtils : : IsABuilderBrush ( Actor ) & & // Don't show the builder brush
! Actor - > IsA ( AWorldSettings : : StaticClass ( ) ) & & // Don't show the WorldSettings actor, even though it is technically editable
2021-09-06 12:23:53 -04:00
IsValidChecked ( Actor ) & & // We don't want to show actors that are about to go away
2021-08-18 10:18:25 -04:00
FLevelUtils : : IsLevelVisible ( Actor - > GetLevel ( ) ) ; // Only show Actors whose level is visible
2020-07-28 09:25:56 -04:00
}
void FActorMode : : OnFilterTextChanged ( const FText & InFilterText )
{
// Scroll last item (if it passes the filter) into view - this means if we are multi-selecting, we show newest selection that passes the filter
if ( const AActor * LastSelectedActor = GEditor - > GetSelectedActors ( ) - > GetBottom < AActor > ( ) )
{
// This part is different than that of OnLevelSelectionChanged(nullptr) because IsItemVisible(TreeItem) & ScrollItemIntoView(TreeItem) are applied to
// the current visual state, not to the one after applying the filter. Thus, the scroll would go to the place where the object was located
// before applying the FilterText
// If the object is already in the list, but it does not passes the filter, then we do not want to re-add it, because it will be removed by the filter
const FSceneOutlinerTreeItemPtr TreeItem = SceneOutliner - > GetTreeItem ( LastSelectedActor ) ;
if ( ! TreeItem . IsValid ( ) | | ! SceneOutliner - > PassesTextFilter ( TreeItem ) )
{
return ;
}
// If the object is not in the list, and it does not passes the filter, then we should not re-add it, because it would be removed by the filter again. Unfortunately,
// there is no code to check if a future element (i.e., one that is currently not in the TreeItemMap list) will pass the filter. Therefore, we kind of overkill it
// by re-adding that element (even though it will be removed). However, AddItemToTree(FSceneOutlinerTreeItemRef Item) and similar functions already check the element before
// adding it. So this solution is fine.
// This solution might affect the performance of the World Outliner when a key is pressed, but it will still work properly when the remove/del keys are pressed. Not
// updating the filter when !TreeItem.IsValid() would result in the focus not being updated when the remove/del keys are pressed.
// In any other case (i.e., if the object passes the current filter), re-add it
SceneOutliner - > ScrollItemIntoView ( TreeItem ) ;
2022-05-10 14:20:42 -04:00
SetAsMostRecentOutliner ( ) ;
}
}
void FActorMode : : SetAsMostRecentOutliner ( ) const
{
TWeakPtr < ILevelEditor > LevelEditor = FModuleManager : : GetModuleChecked < FLevelEditorModule > ( TEXT ( " LevelEditor " ) ) . GetLevelEditorInstance ( ) ;
if ( TSharedPtr < ILevelEditor > LevelEditorPin = LevelEditor . Pin ( ) )
{
LevelEditorPin - > SetMostRecentlyUsedSceneOutliner ( SceneOutliner - > GetOutlinerIdentifier ( ) ) ;
2020-07-28 09:25:56 -04:00
}
}
int32 FActorMode : : GetTypeSortPriority ( const ISceneOutlinerTreeItem & Item ) const
{
if ( Item . IsA < FWorldTreeItem > ( ) )
{
return EItemSortOrder : : World ;
}
2022-01-07 10:01:19 -05:00
else if ( Item . IsA < FLevelTreeItem > ( ) )
{
return EItemSortOrder : : Level ;
}
2020-07-28 09:25:56 -04:00
else if ( Item . IsA < FFolderTreeItem > ( ) )
{
return EItemSortOrder : : Folder ;
}
else if ( Item . IsA < FActorTreeItem > ( ) | | Item . IsA < FComponentTreeItem > ( ) )
{
return EItemSortOrder : : Actor ;
}
2021-03-10 12:35:33 -04:00
else if ( Item . IsA < FActorDescTreeItem > ( ) )
{
return EItemSortOrder : : Unloaded ;
}
2020-07-28 09:25:56 -04:00
// Warning: using actor mode with an unsupported item type!
check ( false ) ;
return - 1 ;
}
2020-07-22 09:35:57 -04:00
# undef LOCTEXT_NAMESPACE