Kagalive

SWorldHierarchyImpl.cpp

Sep 30th, 2020 (edited)
145
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C++ 46.99 KB | None | 0 0
  1. https://bit.ly/freePS2020
  2. // Copyright Epic Games, Inc. All Rights Reserved.
  3.  
  4. #include "SWorldHierarchyImpl.h"
  5. #include "SLevelsTreeWidget.h"
  6. #include "SWorldHierarchyItem.h"
  7. #include "SWorldHierarchy.h"
  8.  
  9. #include "WorldBrowserModule.h"
  10. #include "Modules/ModuleManager.h"
  11.  
  12. #include "Framework/Application/SlateApplication.h"
  13. #include "Framework/MultiBox/MultiBoxBuilder.h"
  14. #include "ToolMenus.h"
  15. #include "Widgets/Layout/SSeparator.h"
  16. #include "Widgets/Images/SImage.h"
  17. #include "Widgets/Input/SComboButton.h"
  18. #include "Widgets/Input/SSearchBox.h"
  19. #include "Widgets/Input/SButton.h"
  20.  
  21. #include "WorldTreeItemTypes.h"
  22. #include "LevelFolders.h"
  23. #include "ScopedTransaction.h"
  24. #include "Editor.h"
  25.  
  26. #define LOCTEXT_NAMESPACE "WorldBrowser"
  27.  
  28. DEFINE_LOG_CATEGORY_STATIC(LogWorldHierarchy, Log, All);
  29.  
  30. SWorldHierarchyImpl::SWorldHierarchyImpl()
  31.     : bUpdatingSelection(false)
  32.     , bIsReentrant(false)
  33.     , bFullRefresh(true)
  34.     , bNeedsRefresh(true)
  35.     , bRebuildFolders(false)
  36.     , bSortDirty(false)
  37.     , bFoldersOnlyMode(false)
  38. {
  39. }
  40.  
  41. SWorldHierarchyImpl::~SWorldHierarchyImpl()
  42. {
  43.     GEditor->UnregisterForUndo(this);
  44.  
  45.     WorldModel->SelectionChanged.RemoveAll(this);
  46.     WorldModel->HierarchyChanged.RemoveAll(this);
  47.     WorldModel->CollectionChanged.RemoveAll(this);
  48.     WorldModel->PreLevelsUnloaded.RemoveAll(this);
  49.  
  50.     if (SearchBoxLevelFilter.IsValid())
  51.     {
  52.         WorldModel->RemoveFilter(SearchBoxLevelFilter.ToSharedRef());
  53.     }
  54.  
  55.     if (FLevelFolders::IsAvailable())
  56.     {
  57.         FLevelFolders& LevelFolders = FLevelFolders::Get();
  58.         LevelFolders.OnFolderCreate.RemoveAll(this);
  59.         LevelFolders.OnFolderMove.RemoveAll(this);
  60.         LevelFolders.OnFolderDelete.RemoveAll(this);
  61.     }
  62.  
  63.     FEditorDelegates::PostSaveWorld.RemoveAll(this);
  64. }
  65.  
  66. void SWorldHierarchyImpl::Construct(const FArguments& InArgs)
  67. {
  68.     WorldModel = InArgs._InWorldModel;
  69.     check(WorldModel.IsValid());
  70.  
  71.     WorldModel->SelectionChanged.AddSP(this, &SWorldHierarchyImpl::OnUpdateSelection);
  72.     WorldModel->HierarchyChanged.AddSP(this, &SWorldHierarchyImpl::RebuildFoldersAndFullRefresh);
  73.     WorldModel->CollectionChanged.AddSP(this, &SWorldHierarchyImpl::RebuildFoldersAndFullRefresh);
  74.     WorldModel->PreLevelsUnloaded.AddSP(this, &SWorldHierarchyImpl::OnBroadcastLevelsUnloaded);
  75.    
  76.     bFoldersOnlyMode = InArgs._ShowFoldersOnly;
  77.     ExcludedFolders = InArgs._InExcludedFolders;
  78.     OnItemPicked = InArgs._OnItemPickedDelegate;
  79.  
  80.     if (!bFoldersOnlyMode)
  81.     {
  82.         SearchBoxLevelFilter = MakeShareable(new LevelTextFilter(
  83.             LevelTextFilter::FItemToStringArray::CreateSP(this, &SWorldHierarchyImpl::TransformLevelToString)
  84.         ));
  85.     }
  86.  
  87.     SearchBoxHierarchyFilter = MakeShareable(new HierarchyFilter(
  88.         HierarchyFilter::FItemToStringArray::CreateSP(this, &SWorldHierarchyImpl::TransformItemToString)
  89.     ));
  90.  
  91.     // Might be overkill to have both filters call full refresh on change, but this should just request a full refresh
  92.     //  twice instead of actually performing the refresh itself.
  93.     if (SearchBoxLevelFilter.IsValid())
  94.     {
  95.         SearchBoxLevelFilter->OnChanged().AddSP(this, &SWorldHierarchyImpl::FullRefresh);
  96.     }
  97.     SearchBoxHierarchyFilter->OnChanged().AddSP(this, &SWorldHierarchyImpl::FullRefresh);
  98.  
  99.     HeaderRowWidget =
  100.         SNew( SHeaderRow )
  101.         .Visibility(EVisibility::Collapsed)
  102.  
  103.         /** Level visibility column */
  104.         + SHeaderRow::Column(HierarchyColumns::ColumnID_Visibility)
  105.         .Visibility(bFoldersOnlyMode ? EVisibility::Collapsed : EVisibility::Visible)
  106.         .FixedWidth(24.0f)
  107.         .HeaderContent()
  108.         [
  109.             SNew(STextBlock)
  110.             .ToolTipText(NSLOCTEXT("WorldBrowser", "Visibility", "Visibility"))
  111.         ]
  112.  
  113.         /** LevelName label column */
  114.         + SHeaderRow::Column( HierarchyColumns::ColumnID_LevelLabel )
  115.             .FillWidth( 0.45f )
  116.             .HeaderContent()
  117.             [
  118.                 SNew(STextBlock)
  119.                     .ToolTipText(LOCTEXT("Column_LevelNameLabel", "Level"))
  120.                    
  121.             ]
  122.  
  123.         /** Lighting Scenario column */
  124.         + SHeaderRow::Column(HierarchyColumns::ColumnID_LightingScenario)
  125.             .Visibility(bFoldersOnlyMode ? EVisibility::Collapsed : EVisibility::Visible)
  126.             .FixedWidth( 18.0f )
  127.             .HeaderContent()
  128.             [
  129.                 SNew(STextBlock)
  130.                     .ToolTipText(NSLOCTEXT("WorldBrowser", "Lighting Scenario", "Lighting Scenario"))
  131.             ]
  132.    
  133.  
  134.         /** Level lock column */
  135.         +SHeaderRow::Column(HierarchyColumns::ColumnID_Lock)
  136.             .Visibility(bFoldersOnlyMode ? EVisibility::Collapsed : EVisibility::Visible)
  137.             .FixedWidth( 24.0f )
  138.             .HeaderContent()
  139.             [
  140.                 SNew(STextBlock)
  141.                     .ToolTipText(NSLOCTEXT("WorldBrowser", "Lock", "Lock"))
  142.             ]
  143.    
  144.         /** Level kismet column */
  145.         + SHeaderRow::Column(HierarchyColumns::ColumnID_Kismet)
  146.             .Visibility(bFoldersOnlyMode ? EVisibility::Collapsed : EVisibility::Visible)
  147.             .FixedWidth( 24.0f )
  148.             .HeaderContent()
  149.             [
  150.                 SNew(STextBlock)
  151.                     .ToolTipText(NSLOCTEXT("WorldBrowser", "Blueprint", "Open the level blueprint for this Level"))
  152.             ]
  153.  
  154.         /** Level SCC status column */
  155.         + SHeaderRow::Column(HierarchyColumns::ColumnID_SCCStatus)
  156.             .Visibility(bFoldersOnlyMode ? EVisibility::Collapsed : EVisibility::Visible)
  157.             .FixedWidth( 24.0f )
  158.             .HeaderContent()
  159.             [
  160.                 SNew(STextBlock)
  161.                     .ToolTipText(NSLOCTEXT("WorldBrowser", "SCCStatus", "Status in Source Control"))
  162.             ]
  163.  
  164.         /** Level save column */
  165.         + SHeaderRow::Column(HierarchyColumns::ColumnID_Save)
  166.             .Visibility(bFoldersOnlyMode ? EVisibility::Collapsed : EVisibility::Visible)
  167.             .FixedWidth( 24.0f )
  168.             .HeaderContent()
  169.             [
  170.                 SNew(STextBlock)
  171.                     .ToolTipText(NSLOCTEXT("WorldBrowser", "Save", "Save this Level"))
  172.             ]
  173.  
  174.         /** Level color column */
  175.         + SHeaderRow::Column(HierarchyColumns::ColumnID_Color)
  176.             .Visibility(bFoldersOnlyMode ? EVisibility::Collapsed : EVisibility::Visible)
  177.             .FixedWidth(24.0f)
  178.             .HeaderContent()
  179.             [
  180.                 SNew(STextBlock)
  181.                 .ToolTipText(NSLOCTEXT("WorldBrowser", "Color", "Color used for visualization of Level"))
  182.             ];
  183.  
  184.  
  185.     FOnContextMenuOpening ContextMenuEvent;
  186.     if (!bFoldersOnlyMode)
  187.     {
  188.         ContextMenuEvent = FOnContextMenuOpening::CreateSP(this, &SWorldHierarchyImpl::ConstructLevelContextMenu);
  189.     }
  190.  
  191.     TSharedRef<SWidget> CreateNewFolderButton = SNullWidget::NullWidget;
  192.     if (!bFoldersOnlyMode)
  193.     {
  194.         CreateNewFolderButton = SNew(SButton)
  195.             .ButtonStyle(FEditorStyle::Get(), "HoverHintOnly")
  196.             .ToolTipText(LOCTEXT("CreateFolderTooltip", "Create a new folder containing the current selection"))
  197.             .OnClicked(this, &SWorldHierarchyImpl::OnCreateFolderClicked)
  198.             .Visibility(WorldModel->HasFolderSupport() ? EVisibility::Visible : EVisibility::Collapsed)
  199.             [
  200.                 SNew(SImage)
  201.                 .Image(FEditorStyle::GetBrush("WorldBrowser.NewFolderIcon"))
  202.             ];
  203.     }
  204.  
  205.     ChildSlot
  206.     [
  207.         SNew(SVerticalBox)
  208.         // Hierarchy Toolbar
  209.         +SVerticalBox::Slot()
  210.         .AutoHeight()
  211.         [
  212.             SNew(SHorizontalBox)
  213.            
  214.             // Filter box
  215.             + SHorizontalBox::Slot()
  216.             .FillWidth(1.0f)
  217.             [
  218.                 SNew(SSearchBox)
  219.                 .ToolTipText(LOCTEXT("FilterSearchToolTip", "Type here to search Levels"))
  220.                 .HintText(LOCTEXT("FilterSearchHint", "Search Levels"))
  221.                 .OnTextChanged(this, &SWorldHierarchyImpl::SetFilterText)
  222.             ]
  223.  
  224.             // Create New Folder icon
  225.             + SHorizontalBox::Slot()
  226.             .AutoWidth()
  227.             .Padding(4.0f, 0.0f, 0.0f, 0.0f)
  228.             [
  229.                 CreateNewFolderButton
  230.             ]
  231.         ]
  232.         // Empty Label
  233.         +SVerticalBox::Slot()
  234.         .HAlign(HAlign_Center)
  235.         [
  236.             SNew(STextBlock)
  237.             .Visibility(this, &SWorldHierarchyImpl::GetEmptyLabelVisibility)
  238.             .Text(LOCTEXT("EmptyLabel", "Empty"))
  239.             .ColorAndOpacity(FLinearColor(0.4f, 1.0f, 0.4f))
  240.         ]
  241.  
  242.         // Hierarchy
  243.         +SVerticalBox::Slot()
  244.         .FillHeight(1.f)
  245.         [
  246.             SAssignNew(TreeWidget, SLevelsTreeWidget, WorldModel, SharedThis(this))
  247.             .TreeItemsSource(&RootTreeItems)
  248.             .SelectionMode(ESelectionMode::Multi)
  249.             .OnGenerateRow(this, &SWorldHierarchyImpl::GenerateTreeRow)
  250.             .OnGetChildren(this, &SWorldHierarchyImpl::GetChildrenForTree)
  251.             .OnSelectionChanged(this, &SWorldHierarchyImpl::OnSelectionChanged)
  252.             .OnExpansionChanged(this, &SWorldHierarchyImpl::OnExpansionChanged)
  253.             .OnMouseButtonDoubleClick(this, &SWorldHierarchyImpl::OnTreeViewMouseButtonDoubleClick)
  254.             .OnContextMenuOpening(ContextMenuEvent)
  255.             .OnItemScrolledIntoView(this, &SWorldHierarchyImpl::OnTreeItemScrolledIntoView)
  256.             .HeaderRow(HeaderRowWidget.ToSharedRef())
  257.         ]
  258.  
  259.         // Separator
  260.         +SVerticalBox::Slot()
  261.         .AutoHeight()
  262.         .Padding(0, 0, 0, 1)
  263.         [
  264.             SNew(SSeparator)
  265.             .Visibility(bFoldersOnlyMode ? EVisibility::Collapsed : EVisibility::Visible)
  266.         ]
  267.        
  268.         // View options
  269.         +SVerticalBox::Slot()
  270.         .AutoHeight()
  271.         [
  272.             SNew(SHorizontalBox)
  273.             .Visibility(bFoldersOnlyMode ? EVisibility::Collapsed : EVisibility::Visible)
  274.  
  275.             // Asset count
  276.             +SHorizontalBox::Slot()
  277.             .FillWidth(1.f)
  278.             .VAlign(VAlign_Center)
  279.             .Padding(8, 0)
  280.             [
  281.                 SNew( STextBlock )
  282.                 .Text( this, &SWorldHierarchyImpl::GetFilterStatusText )
  283.                 .ColorAndOpacity( this, &SWorldHierarchyImpl::GetFilterStatusTextColor )
  284.             ]
  285.  
  286.             // View mode combo button
  287.             +SHorizontalBox::Slot()
  288.             .AutoWidth()
  289.             [
  290.                 SAssignNew( ViewOptionsComboButton, SComboButton )
  291.                 .ContentPadding(0)
  292.                 .ForegroundColor( this, &SWorldHierarchyImpl::GetViewButtonForegroundColor )
  293.                 .ButtonStyle( FEditorStyle::Get(), "ToggleButton" ) // Use the tool bar item style for this button
  294.                 .OnGetMenuContent( this, &SWorldHierarchyImpl::GetViewButtonContent )
  295.                 .ButtonContent()
  296.                 [
  297.                     SNew(SHorizontalBox)
  298.  
  299.                     +SHorizontalBox::Slot()
  300.                     .AutoWidth()
  301.                     .VAlign(VAlign_Center)
  302.                     [
  303.                         SNew(SImage).Image( FEditorStyle::GetBrush("GenericViewButton") )
  304.                     ]
  305.  
  306.                     +SHorizontalBox::Slot()
  307.                     .AutoWidth()
  308.                     .Padding(2, 0, 0, 0)
  309.                     .VAlign(VAlign_Center)
  310.                     [
  311.                         SNew(STextBlock).Text( LOCTEXT("ViewButton", "View Options") )
  312.                     ]
  313.                 ]
  314.             ]
  315.         ]
  316.     ];
  317.    
  318.     if (FLevelFolders::IsAvailable())
  319.     {
  320.         FLevelFolders& LevelFolders = FLevelFolders::Get();
  321.         LevelFolders.OnFolderCreate.AddSP(this, &SWorldHierarchyImpl::OnBroadcastFolderCreate);
  322.         LevelFolders.OnFolderMove.AddSP(this, &SWorldHierarchyImpl::OnBroadcastFolderMove);
  323.         LevelFolders.OnFolderDelete.AddSP(this, &SWorldHierarchyImpl::OnBroadcastFolderDelete);
  324.  
  325.         if (!bFoldersOnlyMode)
  326.         {
  327.             FEditorDelegates::PostSaveWorld.AddSP(this, &SWorldHierarchyImpl::OnWorldSaved);
  328.         }
  329.     }
  330.  
  331.     if (SearchBoxLevelFilter.IsValid())
  332.     {
  333.         WorldModel->AddFilter(SearchBoxLevelFilter.ToSharedRef());
  334.     }
  335.  
  336.     OnUpdateSelection();
  337.  
  338.     GEditor->RegisterForUndo(this);
  339. }
  340.  
  341. void SWorldHierarchyImpl::Tick( const FGeometry& AllotedGeometry, const double InCurrentTime, const float InDeltaTime )
  342. {
  343.     SCompoundWidget::Tick(AllotedGeometry, InCurrentTime, InDeltaTime);
  344.  
  345.     if (bNeedsRefresh)
  346.     {
  347.         if (!bIsReentrant)
  348.         {
  349.             Populate();
  350.         }
  351.     }
  352.  
  353.     if (bSortDirty)
  354.     {
  355.         SortItems(RootTreeItems);
  356.         for (const auto& Pair : TreeItemMap)
  357.         {
  358.             Pair.Value->Flags.bChildrenRequiresSort = true;
  359.         }
  360.  
  361.         bSortDirty = false;
  362.     }
  363. }
  364.  
  365. void SWorldHierarchyImpl::OnWorldSaved(uint32 SaveFlags, UWorld* World, bool bSuccess)
  366. {
  367.     if (FLevelFolders::IsAvailable())
  368.     {
  369.         for (TSharedPtr<FLevelModel> RootLevel : WorldModel->GetRootLevelList())
  370.         {
  371.             FLevelFolders::Get().SaveLevel(RootLevel.ToSharedRef());
  372.         }
  373.     }
  374. }
  375.  
  376. void SWorldHierarchyImpl::RefreshView()
  377. {
  378.     bNeedsRefresh = true;
  379. }
  380.  
  381. TSharedRef<ITableRow> SWorldHierarchyImpl::GenerateTreeRow(WorldHierarchy::FWorldTreeItemPtr Item, const TSharedRef<STableViewBase>& OwnerTable)
  382. {
  383.     check(Item.IsValid());
  384.  
  385.     return SNew(SWorldHierarchyItem, OwnerTable)
  386.         .InWorldModel(WorldModel)
  387.         .InHierarchy(SharedThis(this))
  388.         .InItemModel(Item)
  389.         .IsItemExpanded(Item->Flags.bExpanded)
  390.         .HighlightText(this, &SWorldHierarchyImpl::GetSearchBoxText)
  391.         .FoldersOnlyMode(bFoldersOnlyMode)
  392.         ;
  393. }
  394.  
  395. void SWorldHierarchyImpl::GetChildrenForTree(WorldHierarchy::FWorldTreeItemPtr Item, TArray<WorldHierarchy::FWorldTreeItemPtr>& OutChildren)
  396. {
  397.     OutChildren = Item->GetChildren();
  398.  
  399.     if (Item->Flags.bChildrenRequiresSort)
  400.     {
  401.         if (OutChildren.Num() > 0)
  402.         {
  403.             SortItems(OutChildren);
  404.  
  405.             // Empty out the children and repopulate them in the correct order
  406.             Item->RemoveAllChildren();
  407.  
  408.             for (WorldHierarchy::FWorldTreeItemPtr Child : OutChildren)
  409.             {
  410.                 Item->AddChild(Child.ToSharedRef());
  411.             }
  412.         }
  413.  
  414.         Item->Flags.bChildrenRequiresSort = false;
  415.     }
  416. }
  417.  
  418. bool SWorldHierarchyImpl::PassesFilter(const WorldHierarchy::IWorldTreeItem& Item)
  419. {
  420.     bool bPassesFilter = true;
  421.  
  422.     WorldHierarchy::FFolderTreeItem* Folder = Item.GetAsFolderTreeItem();
  423.  
  424.     if (bFoldersOnlyMode && Folder == nullptr)
  425.     {
  426.         // Level items should fail to pass the filter if we only want to display folders
  427.         bPassesFilter = false;
  428.     }
  429.     else
  430.     {
  431.         bPassesFilter = SearchBoxHierarchyFilter->PassesFilter(Item);
  432.     }
  433.  
  434.     if (bPassesFilter && ExcludedFolders.Num() > 0)
  435.     {
  436.         if (Folder != nullptr)
  437.         {
  438.             FName CheckPath = Folder->GetFullPath();
  439.  
  440.             // Folders should not be shown if it or its parent have been excluded
  441.             while (!CheckPath.IsNone())
  442.             {
  443.                 if (ExcludedFolders.Contains(CheckPath))
  444.                 {
  445.                     bPassesFilter = false;
  446.                     break;
  447.                 }
  448.  
  449.                 CheckPath = WorldHierarchy::GetParentPath(CheckPath);
  450.             }
  451.         }
  452.     }
  453.  
  454.     return bPassesFilter;
  455. }
  456.  
  457. TSharedPtr<SWidget> SWorldHierarchyImpl::ConstructLevelContextMenu() const
  458. {
  459.     TSharedRef<SWidget> MenuWidget = SNullWidget::NullWidget;
  460.  
  461.     if (!WorldModel->IsReadOnly())
  462.     {
  463.         UToolMenus* ToolMenus = UToolMenus::Get();
  464.         static const FName MenuName = "WorldBrowser.WorldHierarchy.LevelContextMenu";
  465.         if (!ToolMenus->IsMenuRegistered(MenuName))
  466.         {
  467.             ToolMenus->RegisterMenu(MenuName);
  468.         }
  469.  
  470.         FToolMenuContext Context(WorldModel->GetCommandList(), TSharedPtr<FExtender>());
  471.         UToolMenu* Menu = ToolMenus->GenerateMenu(MenuName, Context);
  472.  
  473.         TArray<WorldHierarchy::FWorldTreeItemPtr> SelectedItems = GetSelectedTreeItems();
  474.  
  475.         if (SelectedItems.Num() == 1)
  476.         {
  477.             // If exactly one item is selected, allow it to generate its own context menu
  478.             SelectedItems[0]->GenerateContextMenu(Menu, *this);
  479.         }
  480.         else if (SelectedItems.Num() == 0)
  481.         {
  482.             // If no items are selected, allow the first root level item to create a context menu
  483.             RootTreeItems[0]->GenerateContextMenu(Menu, *this);
  484.         }
  485.  
  486.         Menu->AddDynamicSection("HierarchyDynamicSection", FNewToolMenuDelegateLegacy::CreateLambda([=](FMenuBuilder& MenuBuilder, UToolMenu* Menu)
  487.         {
  488.             WorldModel->BuildHierarchyMenu(MenuBuilder);
  489.         }));
  490.  
  491.         // Generate the "Move To" and "Select" submenus based on the current selection
  492.         if (WorldModel->HasFolderSupport())
  493.         {
  494.             bool bOnlyFoldersSelected = SelectedItems.Num() > 0;
  495.             bool bAllSelectedItemsCanMove = SelectedItems.Num() > 0;
  496.  
  497.             for (WorldHierarchy::FWorldTreeItemPtr Item : SelectedItems)
  498.             {
  499.                 bOnlyFoldersSelected &= Item->GetAsFolderTreeItem() != nullptr;
  500.                 bAllSelectedItemsCanMove &= Item->CanChangeParents();
  501.  
  502.                 if (!bOnlyFoldersSelected && !bAllSelectedItemsCanMove)
  503.                 {
  504.                     // Neither submenu can be built, kill the check
  505.                     break;
  506.                 }
  507.             }
  508.  
  509.             if (bAllSelectedItemsCanMove && FLevelFolders::IsAvailable())
  510.             {
  511.                 FToolMenuSection& Section = Menu->AddSection("Section");
  512.                 Section.AddSubMenu(
  513.                     "MoveSelectionTo",
  514.                     LOCTEXT("MoveSelectionTo", "Move To"),
  515.                     LOCTEXT("MoveSelectionTo_Tooltip", "Move selection to another folder"),
  516.                     FNewMenuDelegate::CreateSP(const_cast<SWorldHierarchyImpl*>(this), &SWorldHierarchyImpl::FillFoldersSubmenu)
  517.                 );
  518.             }
  519.  
  520.             if (bOnlyFoldersSelected)
  521.             {
  522.                 FToolMenuSection& Section = Menu->AddSection("Section");
  523.                 Section.AddSubMenu(
  524.                     "SelectSubmenu",
  525.                     LOCTEXT("SelectSubmenu", "Select"),
  526.                     LOCTEXT("SelectSubmenu_Tooltip", "Select child items of the current selection"),
  527.                     FNewMenuDelegate::CreateSP(const_cast<SWorldHierarchyImpl*>(this), &SWorldHierarchyImpl::FillSelectionSubmenu)
  528.                 );
  529.             }
  530.         }
  531.  
  532.         MenuWidget = ToolMenus->GenerateWidget(Menu);
  533.     }
  534.  
  535.     return MenuWidget;
  536. }
  537.  
  538. void SWorldHierarchyImpl::FillFoldersSubmenu(FMenuBuilder& MenuBuilder)
  539. {
  540.     TArray<WorldHierarchy::FWorldTreeItemPtr> SelectedItems = GetSelectedTreeItems();
  541.     check(SelectedItems.Num() > 0);
  542.  
  543.     // Assume that the root item of the first selected item is the root for all of them
  544.     TSharedPtr<FLevelModel> RootItem = SelectedItems[0]->GetRootItem();
  545.     FName RootPath = NAME_None;
  546.  
  547.     MenuBuilder.AddMenuEntry(
  548.         LOCTEXT("CreateNewFolder", "Create New Folder"),
  549.         LOCTEXT("CreateNewFolder_Tooltip", "Move the selection to a new folder"),
  550.         FSlateIcon(FEditorStyle::GetStyleSetName(), "WorldBrowser.NewFolderIcon"),
  551.         FExecuteAction::CreateSP(this, &SWorldHierarchyImpl::CreateFolder, RootItem, RootPath)
  552.     );
  553.  
  554.     AddMoveToFolderOutliner(MenuBuilder, SelectedItems, RootItem.ToSharedRef());
  555. }
  556.  
  557. void SWorldHierarchyImpl::AddMoveToFolderOutliner(FMenuBuilder& MenuBuilder, const TArray<WorldHierarchy::FWorldTreeItemPtr>& SelectedItems, TSharedRef<FLevelModel> RootItem)
  558. {
  559.     FLevelFolders& LevelFolders = FLevelFolders::Get();
  560.  
  561.     if (LevelFolders.GetFolderProperties(RootItem).Num() > 0)
  562.     {
  563.         TSet<FName> ExcludedFolderPaths;
  564.  
  565.         // Exclude selected folders
  566.         for (WorldHierarchy::FWorldTreeItemPtr Item : SelectedItems)
  567.         {
  568.             if (WorldHierarchy::FFolderTreeItem* Folder = Item->GetAsFolderTreeItem())
  569.             {
  570.                 ExcludedFolderPaths.Add(Folder->GetFullPath());
  571.             }
  572.         }
  573.  
  574.         // Copy the world model to ensure that any delegates fired for the mini hierarchy doesn't affect the main hierarchy
  575.         FWorldBrowserModule& WorldBrowserModule = FModuleManager::LoadModuleChecked<FWorldBrowserModule>("WorldBrowser");
  576.         TSharedPtr<FLevelCollectionModel> WorldModelCopy = WorldBrowserModule.SharedWorldModel(WorldModel->GetWorld());
  577.  
  578.         TSharedRef<SWidget> MiniHierarchy =
  579.             SNew(SVerticalBox)
  580.             + SVerticalBox::Slot()
  581.             .MaxHeight(400.0f)
  582.             [
  583.                 SNew(SWorldHierarchyImpl)
  584.                 .InWorldModel(WorldModelCopy)
  585.                 .ShowFoldersOnly(true)
  586.                 .InExcludedFolders(ExcludedFolderPaths)
  587.                 .OnItemPickedDelegate(FOnWorldHierarchyItemPicked::CreateSP(this, &SWorldHierarchyImpl::MoveSelectionTo))
  588.             ];
  589.  
  590.         MenuBuilder.BeginSection(FName(), LOCTEXT("ExistingFolders", "Existing:"));
  591.         MenuBuilder.AddWidget(MiniHierarchy, FText::GetEmpty(), false);
  592.         MenuBuilder.EndSection();
  593.     }
  594. }
  595.  
  596. void SWorldHierarchyImpl::MoveSelectionTo(WorldHierarchy::FWorldTreeItemRef Item)
  597. {
  598.     FSlateApplication::Get().DismissAllMenus();
  599.  
  600.     TSharedPtr<FLevelModel> RootLevel = Item->GetRootItem();
  601.     FName Path = NAME_None;
  602.  
  603.     if (WorldHierarchy::FFolderTreeItem* Folder = Item->GetAsFolderTreeItem())
  604.     {
  605.         Path = Folder->GetFullPath();
  606.     }
  607.  
  608.     MoveItemsTo(RootLevel, Path);
  609.  
  610.     RefreshView();
  611. }
  612.  
  613. void SWorldHierarchyImpl::FillSelectionSubmenu(FMenuBuilder& MenuBuilder)
  614. {
  615.     const bool bSelectAllDescendants = true;
  616.  
  617.     MenuBuilder.AddMenuEntry(
  618.         LOCTEXT("SelectImmediateChildren", "Immediate Children"),
  619.         LOCTEXT("SelectImmediateChildren_Tooltip", "Select all immediate children of the selected folders"),
  620.         FSlateIcon(),
  621.         FExecuteAction::CreateSP(this, &SWorldHierarchyImpl::SelectFolderDescendants, !bSelectAllDescendants)
  622.     );
  623.  
  624.     MenuBuilder.AddMenuEntry(
  625.         LOCTEXT("SelectAllDescendants", "All Descendants"),
  626.         LOCTEXT("SelectAllDescendants_Tooltip", "Selects all descendants of the selected folders"),
  627.         FSlateIcon(),
  628.         FExecuteAction::CreateSP(this, &SWorldHierarchyImpl::SelectFolderDescendants, bSelectAllDescendants)
  629.     );
  630. }
  631.  
  632. void SWorldHierarchyImpl::SelectFolderDescendants(bool bSelectAllDescendants)
  633. {
  634.     TArray<WorldHierarchy::FWorldTreeItemPtr> OldSelection = GetSelectedTreeItems();
  635.     FLevelModelList SelectedLevels;
  636.  
  637.     TreeWidget->ClearSelection();
  638.  
  639.     for (WorldHierarchy::FWorldTreeItemPtr Item : OldSelection)
  640.     {
  641.         for (WorldHierarchy::FWorldTreeItemPtr Child : Item->GetChildren())
  642.         {
  643.             if (bSelectAllDescendants)
  644.             {
  645.                 SelectedLevels.Append(Child->GetLevelModels());
  646.             }
  647.             else
  648.             {
  649.                 SelectedLevels.Append(Child->GetModel());
  650.             }
  651.         }
  652.     }
  653.  
  654.     if (SelectedLevels.Num() > 0)
  655.     {
  656.         WorldModel->SetSelectedLevels(SelectedLevels);
  657.     }
  658. }
  659.  
  660. void SWorldHierarchyImpl::MoveDroppedItems(const TArray<WorldHierarchy::FWorldTreeItemPtr>& DraggedItems, FName FolderPath)
  661. {
  662.     if (DraggedItems.Num() > 0)
  663.     {
  664.         // Ensure that the dragged items are selected in the tree
  665.         TreeWidget->ClearSelection();
  666.  
  667.         for (WorldHierarchy::FWorldTreeItemPtr Item : DraggedItems)
  668.         {
  669.             TreeWidget->SetItemSelection(Item, true);
  670.         }
  671.  
  672.         // Assume that the root of the first is the root of all the items
  673.         const FScopedTransaction Transaction(LOCTEXT("ItemsMoved", "Move World Hierarchy Items"));
  674.         MoveItemsTo(DraggedItems[0]->GetRootItem(), FolderPath);
  675.  
  676.         RefreshView();
  677.     }
  678. }
  679.  
  680. void SWorldHierarchyImpl::AddDroppedLevelsToFolder(const TArray<FAssetData>& WorldAssetList, FName FolderPath)
  681. {
  682.     if (WorldAssetList.Num() > 0)
  683.     {
  684.         // Populate the set of existing levels in the world
  685.         TSet<FName> ExistingLevels;
  686.         for (TSharedPtr<FLevelModel> Level : WorldModel->GetAllLevels())
  687.         {
  688.             ExistingLevels.Add(Level->GetLongPackageName());
  689.         }
  690.  
  691.         WorldModel->AddExistingLevelsFromAssetData(WorldAssetList);
  692.  
  693.         // Set the folder path of any newly added levels
  694.         for (TSharedPtr<FLevelModel> Level : WorldModel->GetAllLevels())
  695.         {
  696.             if (!ExistingLevels.Contains(Level->GetLongPackageName()))
  697.             {
  698.                 Level->SetFolderPath(FolderPath);
  699.             }
  700.         }
  701.  
  702.         RefreshView();
  703.     }
  704. }
  705.  
  706. void SWorldHierarchyImpl::OnTreeItemScrolledIntoView( WorldHierarchy::FWorldTreeItemPtr Item, const TSharedPtr<ITableRow>& Widget )
  707. {
  708.     if (Item == ItemPendingRename)
  709.     {
  710.         ItemPendingRename = nullptr;
  711.         Item->RenameRequestEvent.ExecuteIfBound();
  712.     }
  713. }
  714.  
  715. void SWorldHierarchyImpl::OnExpansionChanged(WorldHierarchy::FWorldTreeItemPtr Item, bool bIsItemExpanded)
  716. {
  717.     Item->SetExpansion(bIsItemExpanded);
  718.  
  719.     WorldHierarchy::FFolderTreeItem* Folder = Item->GetAsFolderTreeItem();
  720.     if (FLevelFolders::IsAvailable() && Folder != nullptr)
  721.     {
  722.         if (FLevelFolderProps* Props = FLevelFolders::Get().GetFolderProperties(Item->GetRootItem().ToSharedRef(), Folder->GetFullPath()))
  723.         {
  724.             Props->bExpanded = Item->Flags.bExpanded;
  725.         }
  726.     }
  727.  
  728.     RefreshView();
  729. }
  730.  
  731. void SWorldHierarchyImpl::OnSelectionChanged(const WorldHierarchy::FWorldTreeItemPtr Item, ESelectInfo::Type SelectInfo)
  732. {
  733.     if (bUpdatingSelection)
  734.     {
  735.         return;
  736.     }
  737.  
  738.     bUpdatingSelection = true;
  739.  
  740.     TArray<WorldHierarchy::FWorldTreeItemPtr> SelectedItems = GetSelectedTreeItems();
  741.     FLevelModelList SelectedLevels;
  742.  
  743.     for (const WorldHierarchy::FWorldTreeItemPtr& TreeItem : SelectedItems)
  744.     {
  745.         // Folder items should return all child models, but anything else should only return the model for that item
  746.         if (TreeItem->GetAsFolderTreeItem() != nullptr)
  747.         {
  748.             SelectedLevels.Append(TreeItem->GetLevelModels());
  749.         }
  750.         else
  751.         {
  752.             SelectedLevels.Append(TreeItem->GetModel());
  753.         }
  754.     }
  755.  
  756.     if (!bFoldersOnlyMode)
  757.     {
  758.         WorldModel->SetSelectedLevels(SelectedLevels);
  759.     }
  760.     bUpdatingSelection = false;
  761.  
  762.     if (TreeWidget->GetNumItemsSelected() > 0)
  763.     {
  764.         OnItemPicked.ExecuteIfBound(GetSelectedTreeItems()[0].ToSharedRef());
  765.     }
  766. }
  767.  
  768. void SWorldHierarchyImpl::OnUpdateSelection()
  769. {
  770.     if (bUpdatingSelection)
  771.     {
  772.         return;
  773.     }
  774.  
  775.     bUpdatingSelection = true;
  776.  
  777.     ItemsSelectedAfterRefresh.Empty();
  778.     const FLevelModelList& SelectedItems = WorldModel->GetSelectedLevels();
  779.     TreeWidget->ClearSelection();
  780.  
  781.     // To get the list of items that should be displayed as selected we need to find the level tree items belonging to the selected level models.
  782.     if (SelectedItems.Num() > 0)
  783.     {
  784.         for (auto It = TreeItemMap.CreateConstIterator(); It; ++It)
  785.         {
  786.             WorldHierarchy::FWorldTreeItemPtr TreeItemPtr = It->Value;
  787.             if (TreeItemPtr.IsValid())
  788.             {
  789.                 for (auto SelectedItemIt = SelectedItems.CreateConstIterator(); SelectedItemIt; ++SelectedItemIt)
  790.                 {
  791.                     if (TreeItemPtr->HasModel(*SelectedItemIt))
  792.                     {
  793.                         ItemsSelectedAfterRefresh.Add(It->Key);
  794.                         break;
  795.                     }
  796.                 }
  797.             }
  798.         }
  799.     }
  800.  
  801.  
  802.     RefreshView();
  803.  
  804.     bUpdatingSelection = false;
  805. }
  806.  
  807. void SWorldHierarchyImpl::OnTreeViewMouseButtonDoubleClick(WorldHierarchy::FWorldTreeItemPtr Item)
  808. {
  809.     if (Item->CanBeCurrent())
  810.     {
  811.         Item->MakeCurrent();
  812.     }
  813.     else
  814.     {
  815.         Item->SetExpansion(!Item->Flags.bExpanded);
  816.         TreeWidget->SetItemExpansion(Item, Item->Flags.bExpanded);
  817.     }
  818. }
  819.  
  820. FReply SWorldHierarchyImpl::OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent)
  821. {
  822.     if (WorldModel->GetCommandList()->ProcessCommandBindings(InKeyEvent))
  823.     {
  824.         return FReply::Handled();
  825.     }
  826.     else if ( InKeyEvent.GetKey() == EKeys::F2 )
  827.     {
  828.         // If a single folder is selected, F2 should attempt to rename it
  829.         if (TreeWidget->GetNumItemsSelected() == 1)
  830.         {
  831.             WorldHierarchy::FWorldTreeItemPtr ItemToRename = GetSelectedTreeItems()[0];
  832.  
  833.             if (ItemToRename->GetAsFolderTreeItem() != nullptr)
  834.             {
  835.                 ItemPendingRename = ItemToRename;
  836.                 ScrollItemIntoView(ItemToRename);
  837.  
  838.                 return FReply::Handled();
  839.             }
  840.         }
  841.     }
  842.     else if ( InKeyEvent.GetKey() == EKeys::Platform_Delete )
  843.     {
  844.         // Delete was pressed, but no levels were unloaded. Any selected folders should be removed transactionally
  845.         const bool bTransactional = true;
  846.         DeleteFolders(GetSelectedTreeItems(), bTransactional);
  847.     }
  848.     // F5 (Refresh) should be handled by the world model
  849.  
  850.     return SCompoundWidget::OnKeyDown(MyGeometry, InKeyEvent);
  851. }
  852.  
  853. void SWorldHierarchyImpl::OnBroadcastLevelsUnloaded()
  854. {
  855.     // We deleted levels from the hierarchy, so do not record the folder delete transaction either
  856.     const bool bTransactional = false;
  857.     DeleteFolders(GetSelectedTreeItems(), bTransactional);
  858. }
  859.  
  860. void SWorldHierarchyImpl::InitiateRename(WorldHierarchy::FWorldTreeItemRef InItem)
  861. {
  862.     // Only folders items are valid for rename in this view
  863.     if (InItem->GetAsFolderTreeItem() != nullptr)
  864.     {
  865.         ItemPendingRename = InItem;
  866.         ScrollItemIntoView(InItem);
  867.     }
  868. }
  869.  
  870. void SWorldHierarchyImpl::EmptyTreeItems()
  871. {
  872.     for (auto& Pair : TreeItemMap)
  873.     {
  874.         Pair.Value->RemoveAllChildren();
  875.     }
  876.  
  877.     PendingOperations.Empty();
  878.     TreeItemMap.Reset();
  879.     PendingTreeItemMap.Reset();
  880.  
  881.     RootTreeItems.Empty();
  882.     NewItemActions.Empty();
  883.     ItemPendingRename.Reset();
  884. }
  885.  
  886. void SWorldHierarchyImpl::RepopulateEntireTree()
  887. {
  888.     EmptyTreeItems();
  889.  
  890.     for (const TSharedPtr<FLevelModel>& Level : WorldModel->GetFilteredLevels())
  891.     {
  892.         if (Level.IsValid())
  893.         {
  894.             ConstructItemFor<WorldHierarchy::FLevelModelTreeItem>(Level.ToSharedRef());
  895.         }
  896.     }
  897.  
  898.     if (FLevelFolders::IsAvailable() && WorldModel->HasFolderSupport())
  899.     {
  900.         FLevelFolders& LevelFolders = FLevelFolders::Get();
  901.  
  902.         // Add any folders which might match the search terms for each root level
  903.         for (TSharedPtr<FLevelModel> RootLevel : WorldModel->GetRootLevelList())
  904.         {
  905.             for (const auto& Pair : LevelFolders.GetFolderProperties(RootLevel.ToSharedRef()))
  906.             {
  907.                 if (!TreeItemMap.Contains(Pair.Key))
  908.                 {
  909.                     ConstructItemFor<WorldHierarchy::FFolderTreeItem>(Pair.Key);
  910.                 }
  911.             }
  912.         }
  913.     }
  914. }
  915.  
  916. TMap<WorldHierarchy::FWorldTreeItemID, bool> SWorldHierarchyImpl::GetParentsExpansionState() const
  917. {
  918.     TMap<WorldHierarchy::FWorldTreeItemID, bool> ExpansionStates;
  919.  
  920.     for (const auto& Pair : TreeItemMap)
  921.     {
  922.         if (Pair.Value->GetChildren().Num() > 0)
  923.         {
  924.             ExpansionStates.Add(Pair.Key, Pair.Value->Flags.bExpanded);
  925.         }
  926.     }
  927.  
  928.     return ExpansionStates;
  929. }
  930.  
  931. void SWorldHierarchyImpl::SetParentsExpansionState(const TMap<WorldHierarchy::FWorldTreeItemID, bool>& ExpansionInfo)
  932. {
  933.     for (const auto& Pair : TreeItemMap)
  934.     {
  935.         auto& Item = Pair.Value;
  936.         if (Item->GetChildren().Num() > 0)
  937.         {
  938.             const bool* bExpandedPtr = ExpansionInfo.Find(Pair.Key);
  939.             bool bExpanded = bExpandedPtr != nullptr ? *bExpandedPtr : Item->Flags.bExpanded;
  940.  
  941.             TreeWidget->SetItemExpansion(Item, bExpanded);
  942.         }
  943.     }
  944. }
  945.  
  946. void SWorldHierarchyImpl::OnBroadcastFolderCreate(TSharedPtr<FLevelModel> LevelModel, FName NewPath)
  947. {
  948.     if (!TreeItemMap.Contains(NewPath))
  949.     {
  950.         ConstructItemFor<WorldHierarchy::FFolderTreeItem>(NewPath);
  951.     }
  952. }
  953.  
  954. void SWorldHierarchyImpl::OnBroadcastFolderDelete(TSharedPtr<FLevelModel> LevelModel, FName Path)
  955. {
  956.     WorldHierarchy::FWorldTreeItemPtr* Folder = TreeItemMap.Find(Path);
  957.  
  958.     if (Folder != nullptr)
  959.     {
  960.         PendingOperations.Emplace(WorldHierarchy::FPendingWorldTreeOperation::Removed, Folder->ToSharedRef());
  961.         RefreshView();
  962.     }
  963. }
  964.  
  965. void SWorldHierarchyImpl::OnBroadcastFolderMove(TSharedPtr<FLevelModel> LevelModel, FName OldPath, FName NewPath)
  966. {
  967.     WorldHierarchy::FWorldTreeItemPtr Folder = TreeItemMap.FindRef(OldPath);
  968.  
  969.     if (Folder.IsValid())
  970.     {
  971.         // Remove the item with the old ID
  972.         TreeItemMap.Remove(Folder->GetID());
  973.  
  974.         // Get all items that were moved
  975.         TArray<WorldHierarchy::FWorldTreeItemPtr> AllSelectedItems = GetSelectedTreeItems();
  976.  
  977.         // Change the path, and place it back in the tree with the new ID
  978.         {
  979.             WorldHierarchy::FFolderTreeItem* FolderItem = Folder->GetAsFolderTreeItem();
  980.             FolderItem->SetNewPath(NewPath);
  981.         }
  982.  
  983.         for (WorldHierarchy::FWorldTreeItemPtr Child : Folder->GetChildren())
  984.         {
  985.             // Any level model children that were not explicitly moved will need to be moved here to remain in
  986.             // sync with their parent folders
  987.             if (!AllSelectedItems.Contains(Child) && Child->GetAsLevelModelTreeItem() != nullptr)
  988.             {
  989.                 Child->SetParentPath(NewPath);
  990.             }
  991.         }
  992.  
  993.         TreeItemMap.Add(Folder->GetID(), Folder);
  994.  
  995.         PendingOperations.Emplace(WorldHierarchy::FPendingWorldTreeOperation::Moved, Folder.ToSharedRef());
  996.         RefreshView();
  997.     }
  998. }
  999.  
  1000. void SWorldHierarchyImpl::FullRefresh()
  1001. {
  1002.     bFullRefresh = true;
  1003.     RefreshView();
  1004. }
  1005.  
  1006. void SWorldHierarchyImpl::RebuildFoldersAndFullRefresh()
  1007. {
  1008.     bRebuildFolders = true;
  1009.     FullRefresh();
  1010. }
  1011.  
  1012. void SWorldHierarchyImpl::RequestSort()
  1013. {
  1014.     bSortDirty = true;
  1015. }
  1016.  
  1017. void SWorldHierarchyImpl::Populate()
  1018. {
  1019.     TGuardValue<bool> ReentrantGuard(bIsReentrant, true);
  1020.  
  1021.     bool bMadeSignificantChanges = false;
  1022.  
  1023.     const TMap<WorldHierarchy::FWorldTreeItemID, bool> ExpansionStateInfo = GetParentsExpansionState();
  1024.  
  1025.     if (bRebuildFolders)
  1026.     {
  1027.         if (FLevelFolders::IsAvailable())
  1028.         {
  1029.             FLevelFolders& LevelFolders = FLevelFolders::Get();
  1030.  
  1031.             for (TSharedPtr<FLevelModel> LevelModel : WorldModel->GetRootLevelList())
  1032.             {
  1033.                 LevelFolders.RebuildFolderList(LevelModel.ToSharedRef());
  1034.             }
  1035.         }
  1036.  
  1037.         bRebuildFolders = false;
  1038.     }
  1039.  
  1040.     if (bFullRefresh)
  1041.     {
  1042.         RepopulateEntireTree();
  1043.  
  1044.         bFullRefresh = false;
  1045.         bMadeSignificantChanges = true;
  1046.     }
  1047.  
  1048.     if (PendingOperations.Num() > 0)
  1049.     {
  1050.         const int32 End = FMath::Min(PendingOperations.Num(), MaxPendingOperations);
  1051.         for (int32 Index = 0; Index < End; ++Index)
  1052.         {
  1053.             const WorldHierarchy::FPendingWorldTreeOperation& PendingOp = PendingOperations[Index];
  1054.             switch (PendingOp.Operation)
  1055.             {
  1056.             case WorldHierarchy::FPendingWorldTreeOperation::Added:
  1057.                 bMadeSignificantChanges = AddItemToTree(PendingOp.Item);
  1058.                 break;
  1059.  
  1060.             case WorldHierarchy::FPendingWorldTreeOperation::Moved:
  1061.                 bMadeSignificantChanges = true;
  1062.                 OnItemMoved(PendingOp.Item);
  1063.                 break;
  1064.  
  1065.             case WorldHierarchy::FPendingWorldTreeOperation::Removed:
  1066.                 bMadeSignificantChanges = true;
  1067.                 RemoveItemFromTree(PendingOp.Item);
  1068.                 break;
  1069.  
  1070.             default:
  1071.                 check(false);
  1072.                 break;
  1073.             }
  1074.         }
  1075.  
  1076.         PendingOperations.RemoveAt(0, End);
  1077.     }
  1078.  
  1079.     SetParentsExpansionState(ExpansionStateInfo);
  1080.  
  1081.     if (ItemsSelectedAfterRefresh.Num() > 0)
  1082.     {
  1083.         bool bScrolledIntoView = false;
  1084.         for (const WorldHierarchy::FWorldTreeItemID& ID : ItemsSelectedAfterRefresh)
  1085.         {
  1086.             if (TreeItemMap.Contains(ID))
  1087.             {
  1088.                 WorldHierarchy::FWorldTreeItemPtr Item = TreeItemMap[ID];
  1089.                 TreeWidget->SetItemSelection(Item, true);
  1090.  
  1091.                 if (!bScrolledIntoView)
  1092.                 {
  1093.                     bScrolledIntoView = true;
  1094.                     TreeWidget->RequestScrollIntoView(Item);
  1095.                 }
  1096.             }
  1097.         }
  1098.  
  1099.         ItemsSelectedAfterRefresh.Empty();
  1100.     }
  1101.  
  1102.     if (bMadeSignificantChanges)
  1103.     {
  1104.         RequestSort();
  1105.     }
  1106.  
  1107.     TreeWidget->RequestTreeRefresh();
  1108.  
  1109.     if (PendingOperations.Num() == 0)
  1110.     {
  1111.         NewItemActions.Empty();
  1112.         bNeedsRefresh = false;
  1113.     }
  1114. }
  1115.  
  1116. bool SWorldHierarchyImpl::AddItemToTree(WorldHierarchy::FWorldTreeItemRef InItem)
  1117. {
  1118.     const WorldHierarchy::FWorldTreeItemID ItemID = InItem->GetID();
  1119.  
  1120.     bool bItemAdded = false;
  1121.  
  1122.     PendingTreeItemMap.Remove(ItemID);
  1123.     if (!TreeItemMap.Find(ItemID))
  1124.     {
  1125.         // Not currently in the tree, check if the item passes the current filter
  1126.  
  1127.         bool bFilteredOut = !PassesFilter(*InItem);
  1128.  
  1129.         InItem->Flags.bFilteredOut = bFilteredOut;
  1130.  
  1131.         if (!bFilteredOut)
  1132.         {
  1133.             AddUnfilteredItemToTree(InItem);
  1134.             bItemAdded = true;
  1135.  
  1136.             if (WorldHierarchy::ENewItemAction* ActionPtr = NewItemActions.Find(ItemID))
  1137.             {
  1138.                 WorldHierarchy::ENewItemAction Actions = *ActionPtr;
  1139.  
  1140.                 if ((Actions & WorldHierarchy::ENewItemAction::Select) != WorldHierarchy::ENewItemAction::None)
  1141.                 {
  1142.                     TreeWidget->ClearSelection();
  1143.                     TreeWidget->SetItemSelection(InItem, true);
  1144.                 }
  1145.  
  1146.                 if ((Actions & WorldHierarchy::ENewItemAction::Rename) != WorldHierarchy::ENewItemAction::None)
  1147.                 {
  1148.                     ItemPendingRename = InItem;
  1149.                 }
  1150.  
  1151.                 WorldHierarchy::ENewItemAction ScrollIntoView = WorldHierarchy::ENewItemAction::ScrollIntoView | WorldHierarchy::ENewItemAction::Rename;
  1152.                 if ((Actions & ScrollIntoView) != WorldHierarchy::ENewItemAction::None)
  1153.                 {
  1154.                     ScrollItemIntoView(InItem);
  1155.                 }
  1156.             }
  1157.         }
  1158.     }
  1159.  
  1160.     return bItemAdded;
  1161. }
  1162.  
  1163. void SWorldHierarchyImpl::AddUnfilteredItemToTree(WorldHierarchy::FWorldTreeItemRef InItem)
  1164. {
  1165.     WorldHierarchy::FWorldTreeItemPtr Parent = EnsureParentForItem(InItem);
  1166.     const WorldHierarchy::FWorldTreeItemID ItemID = InItem->GetID();
  1167.  
  1168.     if (TreeItemMap.Contains(ItemID))
  1169.     {
  1170.         UE_LOG(LogWorldHierarchy, Error, TEXT("(%d | %s) already exists in the World Hierarchy. Dumping map..."), GetTypeHash(ItemID), *InItem->GetDisplayString());
  1171.  
  1172.         for (const auto& Entry : TreeItemMap)
  1173.         {
  1174.             UE_LOG(LogWorldHierarchy, Log, TEXT("(%d | %s)"), GetTypeHash(Entry.Key), *Entry.Value->GetDisplayString());
  1175.         }
  1176.  
  1177.         // Treat this as a fatal error
  1178.         check(false);
  1179.     }
  1180.  
  1181.     TreeItemMap.Add(ItemID, InItem);
  1182.  
  1183.     if (Parent.IsValid())
  1184.     {
  1185.         Parent->AddChild(InItem);
  1186.     }
  1187.     else
  1188.     {
  1189.         RootTreeItems.Add(InItem);
  1190.     }
  1191.  
  1192.     if (FLevelFolders::IsAvailable())
  1193.     {
  1194.         WorldHierarchy::FFolderTreeItem* Folder = InItem->GetAsFolderTreeItem();
  1195.  
  1196.         if (Folder != nullptr)
  1197.         {
  1198.             if (const FLevelFolderProps* Props = FLevelFolders::Get().GetFolderProperties(InItem->GetRootItem().ToSharedRef(), Folder->GetFullPath()))
  1199.             {
  1200.                 InItem->SetExpansion(Props->bExpanded);
  1201.             }
  1202.         }
  1203.     }
  1204. }
  1205.  
  1206. void SWorldHierarchyImpl::RemoveItemFromTree(WorldHierarchy::FWorldTreeItemRef InItem)
  1207. {
  1208.     if (TreeItemMap.Contains(InItem->GetID()))
  1209.     {
  1210.         WorldHierarchy::FWorldTreeItemPtr Parent = InItem->GetParent();
  1211.  
  1212.         if (Parent.IsValid())
  1213.         {
  1214.             Parent->RemoveChild(InItem);
  1215.             OnChildRemovedFromParent(Parent.ToSharedRef());
  1216.         }
  1217.         else
  1218.         {
  1219.             RootTreeItems.Remove(InItem);
  1220.         }
  1221.  
  1222.         TreeItemMap.Remove(InItem->GetID());
  1223.     }
  1224. }
  1225.  
  1226. void SWorldHierarchyImpl::OnItemMoved(WorldHierarchy::FWorldTreeItemRef InItem)
  1227. {
  1228.     // If the item no longer matches the filter, remove it from the tree
  1229.     if (!InItem->Flags.bFilteredOut && !PassesFilter(*InItem))
  1230.     {
  1231.         RemoveItemFromTree(InItem);
  1232.     }
  1233.     else
  1234.     {
  1235.         WorldHierarchy::FWorldTreeItemPtr Parent = InItem->GetParent();
  1236.  
  1237.         if (Parent.IsValid())
  1238.         {
  1239.             Parent->RemoveChild(InItem);
  1240.             OnChildRemovedFromParent(Parent.ToSharedRef());
  1241.         }
  1242.         else
  1243.         {
  1244.             RootTreeItems.Remove(InItem);
  1245.         }
  1246.  
  1247.         Parent = EnsureParentForItem(InItem);
  1248.         if (Parent.IsValid())
  1249.         {
  1250.             Parent->AddChild(InItem);
  1251.             Parent->SetExpansion(true);
  1252.             TreeWidget->SetItemExpansion(Parent, true);
  1253.         }
  1254.         else
  1255.         {
  1256.             RootTreeItems.Add(InItem);
  1257.         }
  1258.     }
  1259. }
  1260.  
  1261. void SWorldHierarchyImpl::ScrollItemIntoView(WorldHierarchy::FWorldTreeItemPtr Item)
  1262. {
  1263.     WorldHierarchy::FWorldTreeItemPtr Parent = Item->GetParent();
  1264.  
  1265.     while (Parent.IsValid())
  1266.     {
  1267.         TreeWidget->SetItemExpansion(Parent, true);
  1268.         Parent = Parent->GetParent();
  1269.     }
  1270.  
  1271.     TreeWidget->RequestScrollIntoView(Item);
  1272. }
  1273.  
  1274. void SWorldHierarchyImpl::OnChildRemovedFromParent(WorldHierarchy::FWorldTreeItemRef InParent)
  1275. {
  1276.     if (InParent->Flags.bFilteredOut && InParent->GetChildren().Num() == 0)
  1277.     {
  1278.         // Parent does not match the search terms nor does it have any children that matches the search terms
  1279.         RemoveItemFromTree(InParent);
  1280.     }
  1281. }
  1282.  
  1283. WorldHierarchy::FWorldTreeItemPtr SWorldHierarchyImpl::EnsureParentForItem(WorldHierarchy::FWorldTreeItemRef Item)
  1284. {
  1285.     WorldHierarchy::FWorldTreeItemID ParentID = Item->GetParentID();
  1286.     WorldHierarchy::FWorldTreeItemPtr ParentPtr;
  1287.  
  1288.     if (TreeItemMap.Contains(ParentID))
  1289.     {
  1290.         ParentPtr = TreeItemMap[ParentID];
  1291.     }
  1292.     else
  1293.     {
  1294.         ParentPtr = Item->CreateParent();
  1295.         if (ParentPtr.IsValid())
  1296.         {
  1297.             AddUnfilteredItemToTree(ParentPtr.ToSharedRef());
  1298.         }
  1299.     }
  1300.  
  1301.  
  1302.     return ParentPtr;
  1303. }
  1304.  
  1305. bool SWorldHierarchyImpl::IsTreeItemExpanded(WorldHierarchy::FWorldTreeItemPtr Item) const
  1306. {
  1307.     return Item->Flags.bExpanded;
  1308. }
  1309.  
  1310. void SWorldHierarchyImpl::SortItems(TArray<WorldHierarchy::FWorldTreeItemPtr>& Items)
  1311. {
  1312.     if (Items.Num() > 1)
  1313.     {
  1314.         Items.Sort([](WorldHierarchy::FWorldTreeItemPtr Item1, WorldHierarchy::FWorldTreeItemPtr Item2) {
  1315.             const int32 Priority1 = Item1->GetSortPriority();
  1316.             const int32 Priority2 = Item2->GetSortPriority();
  1317.  
  1318.             if (Priority1 == Priority2)
  1319.             {
  1320.                 return Item1->GetDisplayString() < Item2->GetDisplayString();
  1321.             }
  1322.  
  1323.             return Priority1 > Priority2;
  1324.         });
  1325.     }
  1326. }
  1327.  
  1328. void SWorldHierarchyImpl::TransformLevelToString(const FLevelModel* Level, TArray<FString>& OutSearchStrings) const
  1329. {
  1330.     if (Level != nullptr && Level->HasValidPackage())
  1331.     {
  1332.         OutSearchStrings.Add(FPackageName::GetShortName(Level->GetLongPackageName()));
  1333.     }
  1334. }
  1335.  
  1336. void SWorldHierarchyImpl::TransformItemToString(const WorldHierarchy::IWorldTreeItem& Item, TArray<FString>& OutSearchStrings) const
  1337. {
  1338.     OutSearchStrings.Add(Item.GetDisplayString());
  1339. }
  1340.  
  1341. void SWorldHierarchyImpl::SetFilterText(const FText& InFilterText)
  1342. {
  1343.     // Ensure that the level and hierarchy filters remain in sync
  1344.     if (SearchBoxLevelFilter.IsValid())
  1345.     {
  1346.         SearchBoxLevelFilter->SetRawFilterText(InFilterText);
  1347.     }
  1348.     SearchBoxHierarchyFilter->SetRawFilterText(InFilterText);
  1349. }
  1350.  
  1351. FText SWorldHierarchyImpl::GetSearchBoxText() const
  1352. {
  1353.     return SearchBoxHierarchyFilter->GetRawFilterText();
  1354. }
  1355.  
  1356. FText SWorldHierarchyImpl::GetFilterStatusText() const
  1357. {
  1358.     const int32 SelectedLevelsCount = WorldModel->GetSelectedLevels().Num();
  1359.     const int32 TotalLevelsCount = WorldModel->GetAllLevels().Num();
  1360.     const int32 FilteredLevelsCount = WorldModel->GetFilteredLevels().Num();
  1361.  
  1362.     if (!WorldModel->IsFilterActive())
  1363.     {
  1364.         if (SelectedLevelsCount == 0)
  1365.         {
  1366.             return FText::Format(LOCTEXT("ShowingAllLevelsFmt", "{0} levels"), FText::AsNumber(TotalLevelsCount));
  1367.         }
  1368.         else
  1369.         {
  1370.             return FText::Format(LOCTEXT("ShowingAllLevelsSelectedFmt", "{0} levels ({1} selected)"), FText::AsNumber(TotalLevelsCount), FText::AsNumber(SelectedLevelsCount));
  1371.         }
  1372.     }
  1373.     else if (WorldModel->IsFilterActive() && FilteredLevelsCount == 0)
  1374.     {
  1375.         return FText::Format(LOCTEXT("ShowingNoLevelsFmt", "No matching levels ({0} total)"), FText::AsNumber(TotalLevelsCount));
  1376.     }
  1377.     else if (SelectedLevelsCount != 0)
  1378.     {
  1379.         return FText::Format(LOCTEXT("ShowingOnlySomeLevelsSelectedFmt", "Showing {0} of {1} levels ({2} selected)"), FText::AsNumber(FilteredLevelsCount), FText::AsNumber(TotalLevelsCount), FText::AsNumber(SelectedLevelsCount));
  1380.     }
  1381.     else
  1382.     {
  1383.         return FText::Format(LOCTEXT("ShowingOnlySomeLevelsFmt", "Showing {0} of {1} levels"), FText::AsNumber(FilteredLevelsCount), FText::AsNumber(TotalLevelsCount));
  1384.     }
  1385. }
  1386.  
  1387. FReply SWorldHierarchyImpl::OnCreateFolderClicked()
  1388. {
  1389.     // Assume that the folder will be created for the first persistent level
  1390.     TSharedPtr<FLevelModel> PersistentLevel = WorldModel->GetRootLevelList()[0];
  1391.  
  1392.     CreateFolder(PersistentLevel);
  1393.  
  1394.     return FReply::Handled();
  1395. }
  1396.  
  1397. EVisibility SWorldHierarchyImpl::GetEmptyLabelVisibility() const
  1398. {
  1399.     return ( !bFoldersOnlyMode || RootTreeItems.Num() > 0 ) ? EVisibility::Collapsed : EVisibility::Visible;
  1400. }
  1401.  
  1402. void SWorldHierarchyImpl::CreateFolder(TSharedPtr<FLevelModel> InModel, FName ParentPath /* = NAME_None */)
  1403. {
  1404.     if (FLevelFolders::IsAvailable())
  1405.     {
  1406.         TSharedPtr<FLevelModel> PersistentLevelModel = InModel;
  1407.         if (!InModel.IsValid())
  1408.         {
  1409.             // We're not making this for any specific level...assume it's the first persistent level in the world
  1410.             PersistentLevelModel = WorldModel->GetRootLevelList()[0];
  1411.         }
  1412.    
  1413.         const FScopedTransaction Transaction(LOCTEXT("UndoAction_CreateFolder", "Create Folder"));
  1414.  
  1415.         FLevelFolders& LevelFolders = FLevelFolders::Get();
  1416.         FName NewFolderName = ParentPath;
  1417.  
  1418.         // Get the folder name for the selected level items
  1419.         if (NewFolderName.IsNone())
  1420.         {
  1421.             // Attempt to find the most relevant shared folder for all selected items
  1422.             TArray<WorldHierarchy::FWorldTreeItemPtr> SelectedItems = GetSelectedTreeItems();
  1423.  
  1424.             TSet<FName> SharedAncestorPaths = SelectedItems.Num() > 0 ? SelectedItems[0]->GetAncestorPaths() : TSet<FName>();
  1425.  
  1426.             for (int32 Index = 1; Index < SelectedItems.Num(); ++Index)
  1427.             {
  1428.                 SharedAncestorPaths = SharedAncestorPaths.Intersect(SelectedItems[Index]->GetAncestorPaths());
  1429.  
  1430.                 if (SharedAncestorPaths.Num() == 0)
  1431.                 {
  1432.                     // No common ancestor path found, put them at the root
  1433.                     break;
  1434.                 }
  1435.             }
  1436.  
  1437.             // Find the longest name in the shared ancestor paths, because that's the most local "root" folder
  1438.             for (FName Ancestor : SharedAncestorPaths)
  1439.             {
  1440.                 if (Ancestor.ToString().Len() > NewFolderName.ToString().Len())
  1441.                 {
  1442.                     NewFolderName = Ancestor;
  1443.                 }
  1444.             }
  1445.         }
  1446.  
  1447.         NewFolderName = LevelFolders.GetDefaultFolderName(PersistentLevelModel.ToSharedRef(), NewFolderName);
  1448.  
  1449.         MoveItemsTo(PersistentLevelModel, NewFolderName);
  1450.     }
  1451. }
  1452.  
  1453. void SWorldHierarchyImpl::MoveItemsTo(TSharedPtr<FLevelModel> InModel, FName InPath)
  1454. {
  1455.     if (FLevelFolders::IsAvailable())
  1456.     {
  1457.         FLevelFolders& LevelFolders = FLevelFolders::Get();
  1458.  
  1459.         // Get the selected folders first before any items move
  1460.         TArray<WorldHierarchy::FWorldTreeItemPtr> PreviouslySelectedItems = GetSelectedTreeItems();
  1461.         TArray<WorldHierarchy::FFolderTreeItem*> SelectedFolders;
  1462.  
  1463.         for (WorldHierarchy::FWorldTreeItemPtr Item : PreviouslySelectedItems)
  1464.         {
  1465.             if (WorldHierarchy::FFolderTreeItem* Folder = Item->GetAsFolderTreeItem())
  1466.             {
  1467.                 SelectedFolders.Add(Folder);
  1468.             }
  1469.         }
  1470.  
  1471.         // Move the levels first
  1472.         LevelFolders.CreateFolderContainingSelectedLevels(WorldModel.ToSharedRef(), InModel.ToSharedRef(), InPath);
  1473.  
  1474.         // Ensure that any moved levels will have their hierarchy items updated
  1475.         for (TSharedPtr<FLevelModel> SelectedLevel : WorldModel->GetSelectedLevels())
  1476.         {
  1477.             WorldHierarchy::FWorldTreeItemID LevelID(SelectedLevel->GetLevelObject());
  1478.  
  1479.             if (TreeItemMap.Contains(LevelID))
  1480.             {
  1481.                 PendingOperations.Emplace(WorldHierarchy::FPendingWorldTreeOperation::Moved, TreeItemMap[LevelID].ToSharedRef());
  1482.             }
  1483.         }
  1484.  
  1485.         // Move any of the previously selected folders
  1486.         for (WorldHierarchy::FFolderTreeItem* Folder : SelectedFolders)
  1487.         {
  1488.             FName OldPath = Folder->GetFullPath();
  1489.             FName NewPath = FName(*(InPath.ToString() / Folder->GetLeafName().ToString()));
  1490.  
  1491.             LevelFolders.RenameFolder(Folder->GetRootItem().ToSharedRef(), OldPath, NewPath);
  1492.         }
  1493.  
  1494.         NewItemActions.Add(InPath, WorldHierarchy::ENewItemAction::Select | WorldHierarchy::ENewItemAction::Rename);
  1495.     }
  1496. }
  1497.  
  1498. void SWorldHierarchyImpl::DeleteFolders(TArray<WorldHierarchy::FWorldTreeItemPtr> SelectedItems, bool bTransactional/* = true*/)
  1499. {
  1500.     TArray<WorldHierarchy::FWorldTreeItemPtr> FolderItems;
  1501.     TSet<FName> DeletedPaths;
  1502.    
  1503.     for (WorldHierarchy::FWorldTreeItemPtr Item : SelectedItems)
  1504.     {
  1505.         // Only take folder items
  1506.         if (WorldHierarchy::FFolderTreeItem* Folder = Item->GetAsFolderTreeItem())
  1507.         {
  1508.             FolderItems.Add(Item);
  1509.             DeletedPaths.Add(Folder->GetFullPath());
  1510.         }
  1511.     }
  1512.  
  1513.     FScopedTransaction Transaction(LOCTEXT("DeleteFolderTransaction", "Delete Folder"));
  1514.     FLevelFolders& LevelFolders = FLevelFolders::Get();
  1515.  
  1516.     // Folders are deleted one at a time
  1517.     for (WorldHierarchy::FWorldTreeItemPtr Item : FolderItems)
  1518.     {
  1519.         TSharedRef<FLevelModel> LevelModel = Item->GetRootItem().ToSharedRef();
  1520.  
  1521.         // First, move the folder's children up to the ancestor that will not be deleted
  1522.         FName ItemPath = Item->GetAsFolderTreeItem()->GetFullPath();
  1523.  
  1524.         FName ParentPath = ItemPath;
  1525.         do
  1526.         {
  1527.             ParentPath = WorldHierarchy::GetParentPath(ParentPath);
  1528.         } while (DeletedPaths.Contains(ParentPath) && !ParentPath.IsNone());
  1529.  
  1530.         TArray<WorldHierarchy::FWorldTreeItemPtr> Children = Item->GetChildren();
  1531.         for (WorldHierarchy::FWorldTreeItemPtr Child : Children)
  1532.         {
  1533.             if (!SelectedItems.Contains(Child))
  1534.             {
  1535.                 if (WorldHierarchy::FFolderTreeItem* ChildFolder = Child->GetAsFolderTreeItem())
  1536.                 {
  1537.                     FName NewChildPath = ChildFolder->GetLeafName();
  1538.                     if (!ParentPath.IsNone())
  1539.                     {
  1540.                         NewChildPath = FName(*(ParentPath.ToString() / NewChildPath.ToString()));
  1541.                     }
  1542.  
  1543.                     LevelFolders.RenameFolder(LevelModel, ChildFolder->GetFullPath(), NewChildPath);
  1544.                 }
  1545.                 else
  1546.                 {
  1547.                     Child->SetParentPath(ParentPath);
  1548.                     OnItemMoved(Child.ToSharedRef());
  1549.                 }
  1550.             }
  1551.         }
  1552.  
  1553.         // Then delete the folder
  1554.         LevelFolders.DeleteFolder(LevelModel, ItemPath);
  1555.     }
  1556.  
  1557.     if (!bTransactional || FolderItems.Num() == 0)
  1558.     {
  1559.         Transaction.Cancel();
  1560.     }
  1561. }
  1562.  
  1563. FSlateColor SWorldHierarchyImpl::GetFilterStatusTextColor() const
  1564. {
  1565.     if (!WorldModel->IsFilterActive())
  1566.     {
  1567.         // White = no text filter
  1568.         return FLinearColor(1.0f, 1.0f, 1.0f);
  1569.     }
  1570.     else if (WorldModel->GetFilteredLevels().Num() == 0)
  1571.     {
  1572.         // Red = no matching actors
  1573.         return FLinearColor(1.0f, 0.4f, 0.4f);
  1574.     }
  1575.     else
  1576.     {
  1577.         // Green = found at least one match!
  1578.         return FLinearColor(0.4f, 1.0f, 0.4f);
  1579.     }
  1580. }
  1581.  
  1582. TSharedRef<SWidget> SWorldHierarchyImpl::GetViewButtonContent()
  1583. {
  1584.     FMenuBuilder MenuBuilder(true, NULL);
  1585.  
  1586.     MenuBuilder.BeginSection("SubLevelsViewMenu", LOCTEXT("ShowHeading", "Show"));
  1587.     {
  1588.         MenuBuilder.AddMenuEntry(LOCTEXT("ToggleDisplayPaths", "Display Paths"),
  1589.             LOCTEXT("ToggleDisplayPaths_Tooltip", "If enabled, displays the path for each level"),
  1590.             FSlateIcon(),
  1591.             FUIAction(
  1592.                 FExecuteAction::CreateSP(this, &SWorldHierarchyImpl::ToggleDisplayPaths_Executed),
  1593.                 FCanExecuteAction(),
  1594.                 FIsActionChecked::CreateSP(this, &SWorldHierarchyImpl::GetDisplayPathsState)),
  1595.             NAME_None,
  1596.             EUserInterfaceActionType::ToggleButton
  1597.         );
  1598.  
  1599.         MenuBuilder.AddMenuEntry(LOCTEXT("ToggleDisplayActorsCount", "Display Actors Count"),
  1600.             LOCTEXT("ToggleDisplayActorsCount_Tooltip", "If enabled, displays actors count for each level"),
  1601.             FSlateIcon(),
  1602.             FUIAction(
  1603.                 FExecuteAction::CreateSP(this, &SWorldHierarchyImpl::ToggleDisplayActorsCount_Executed),
  1604.                 FCanExecuteAction(),
  1605.                 FIsActionChecked::CreateSP(this, &SWorldHierarchyImpl::GetDisplayActorsCountState)),
  1606.             NAME_None,
  1607.             EUserInterfaceActionType::ToggleButton
  1608.         );
  1609.  
  1610.     }
  1611.     MenuBuilder.EndSection();
  1612.  
  1613.     return MenuBuilder.MakeWidget();
  1614. }
  1615.  
  1616. FSlateColor SWorldHierarchyImpl::GetViewButtonForegroundColor() const
  1617. {
  1618.     static const FName InvertedForegroundName("InvertedForeground");
  1619.     static const FName DefaultForegroundName("DefaultForeground");
  1620.  
  1621.     return ViewOptionsComboButton->IsHovered() ? FEditorStyle::GetSlateColor(InvertedForegroundName) : FEditorStyle::GetSlateColor(DefaultForegroundName);
  1622. }
  1623.  
  1624. bool SWorldHierarchyImpl::GetDisplayPathsState() const
  1625. {
  1626.     return WorldModel->GetDisplayPathsState();
  1627. }
  1628.  
  1629. void SWorldHierarchyImpl::ToggleDisplayActorsCount_Executed()
  1630. {
  1631.     WorldModel->SetDisplayActorsCountState(!WorldModel->GetDisplayActorsCountState());
  1632. }
  1633.  
  1634. bool SWorldHierarchyImpl::GetDisplayActorsCountState() const
  1635. {
  1636.     return WorldModel->GetDisplayActorsCountState();
  1637. }
  1638.  
  1639. void SWorldHierarchyImpl::PostUndo(bool bSuccess)
  1640. {
  1641.     if (!bIsReentrant)
  1642.     {
  1643.         FullRefresh();
  1644.     }
  1645. }
  1646.  
  1647. #undef LOCTEXT_NAMESPACE
Add Comment
Please, Sign In to add comment