// Copyright Epic Games, Inc. All Rights Reserved. #include "SContentBrowser.h" #include "Factories/Factory.h" #include "Framework/Commands/UIAction.h" #include "Textures/SlateIcon.h" #include "Framework/Commands/UICommandList.h" #include "Misc/ConfigCacheIni.h" #include "Misc/FeedbackContext.h" #include "Misc/ScopedSlowTask.h" #include "Widgets/SBoxPanel.h" #include "Layout/WidgetPath.h" #include "SlateOptMacros.h" #include "Framework/Application/SlateApplication.h" #include "Widgets/Layout/SBorder.h" #include "Widgets/Layout/SSeparator.h" #include "Widgets/Layout/SWrapBox.h" #include "Widgets/Images/SImage.h" #include "Widgets/Text/STextBlock.h" #include "Widgets/Layout/SBox.h" #include "Widgets/Layout/SExpandableArea.h" #include "Framework/MultiBox/MultiBoxBuilder.h" #include "Widgets/Input/SButton.h" #include "Widgets/Input/SComboButton.h" #include "Widgets/Input/SSearchBox.h" #include "Widgets/Layout/SSplitter.h" #include "Widgets/Layout/SWidgetSwitcher.h" #include "Framework/Docking/TabManager.h" #include "EditorStyleSet.h" #include "EditorFontGlyphs.h" #include "Settings/ContentBrowserSettings.h" #include "Settings/EditorSettings.h" #include "Editor.h" #include "FileHelpers.h" #include "AssetRegistryModule.h" #include "AssetRegistryState.h" #include "AssetToolsModule.h" #include "Widgets/Navigation/SBreadcrumbTrail.h" #include "ContentBrowserLog.h" #include "FrontendFilters.h" #include "ContentBrowserSingleton.h" #include "ContentBrowserUtils.h" #include "SourcesSearch.h" #include "SFilterList.h" #include "SPathView.h" #include "SCollectionView.h" #include "SAssetView.h" #include "AssetContextMenu.h" #include "NewAssetOrClassContextMenu.h" #include "PathContextMenu.h" #include "ContentBrowserModule.h" #include "ContentBrowserCommands.h" #include "Widgets/Docking/SDockTab.h" #include "Framework/Commands/GenericCommands.h" #include "IAddContentDialogModule.h" #include "UObject/GCObjectScopeGuard.h" #include "Engine/Selection.h" #include "NativeClassHierarchy.h" #include "AddToProjectConfig.h" #include "GameProjectGenerationModule.h" #include "Toolkits/GlobalEditorCommonCommands.h" #include "Subsystems/AssetEditorSubsystem.h" #include "ContentBrowserMenuContexts.h" #include "ToolMenus.h" #include "Brushes/SlateColorBrush.h" #include "IVREditorModule.h" #define LOCTEXT_NAMESPACE "ContentBrowser" const FString SContentBrowser::SettingsIniSection = TEXT("ContentBrowser"); namespace ContentBrowserSourcesWidgetSwitcherIndex { static const int32 PathView = 0; static const int32 CollectionsView = 1; } SContentBrowser::~SContentBrowser() { // Remove the listener for when view settings are changed UContentBrowserSettings::OnSettingChanged().RemoveAll( this ); // Remove listeners for when collections/paths are renamed/deleted if (FCollectionManagerModule::IsModuleAvailable()) { FCollectionManagerModule& CollectionManagerModule = FCollectionManagerModule::GetModule(); CollectionManagerModule.Get().OnCollectionRenamed().RemoveAll(this); CollectionManagerModule.Get().OnCollectionDestroyed().RemoveAll(this); } FAssetRegistryModule* AssetRegistryModule = FModuleManager::GetModulePtr(TEXT("AssetRegistry")); if (AssetRegistryModule != nullptr) { AssetRegistryModule->Get().OnPathRemoved().RemoveAll(this); } } BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION void SContentBrowser::Construct( const FArguments& InArgs, const FName& InInstanceName, const FContentBrowserConfig* Config ) { if ( InArgs._ContainingTab.IsValid() ) { // For content browsers that are placed in tabs, save settings when the tab is closing. ContainingTab = InArgs._ContainingTab; InArgs._ContainingTab->SetOnPersistVisualState( SDockTab::FOnPersistVisualState::CreateSP( this, &SContentBrowser::OnContainingTabSavingVisualState ) ); InArgs._ContainingTab->SetOnTabClosed( SDockTab::FOnTabClosedCallback::CreateSP( this, &SContentBrowser::OnContainingTabClosed ) ); InArgs._ContainingTab->SetOnTabActivated( SDockTab::FOnTabActivatedCallback::CreateSP( this, &SContentBrowser::OnContainingTabActivated ) ); } bIsLocked = InArgs._InitiallyLocked; bCanSetAsPrimaryBrowser = Config != nullptr ? Config->bCanSetAsPrimaryBrowser : true; HistoryManager.SetOnApplyHistoryData(FOnApplyHistoryData::CreateSP(this, &SContentBrowser::OnApplyHistoryData)); HistoryManager.SetOnUpdateHistoryData(FOnUpdateHistoryData::CreateSP(this, &SContentBrowser::OnUpdateHistoryData)); PathContextMenu = MakeShareable(new FPathContextMenu( AsShared() )); PathContextMenu->SetOnNewAssetRequested( FNewAssetOrClassContextMenu::FOnNewAssetRequested::CreateSP(this, &SContentBrowser::NewAssetRequested) ); PathContextMenu->SetOnNewClassRequested( FNewAssetOrClassContextMenu::FOnNewClassRequested::CreateSP(this, &SContentBrowser::NewClassRequested) ); PathContextMenu->SetOnImportAssetRequested(FNewAssetOrClassContextMenu::FOnImportAssetRequested::CreateSP(this, &SContentBrowser::ImportAsset)); PathContextMenu->SetOnRenameFolderRequested(FPathContextMenu::FOnRenameFolderRequested::CreateSP(this, &SContentBrowser::OnRenameFolderRequested)); PathContextMenu->SetOnFolderDeleted(FPathContextMenu::FOnFolderDeleted::CreateSP(this, &SContentBrowser::OnOpenedFolderDeleted)); PathContextMenu->SetOnFolderFavoriteToggled(FPathContextMenu::FOnFolderFavoriteToggled::CreateSP(this, &SContentBrowser::ToggleFolderFavorite)); FrontendFilters = MakeShareable(new FAssetFilterCollectionType()); TextFilter = MakeShareable( new FFrontendFilter_Text() ); SourcesSearch = MakeShared(); SourcesSearch->Initialize(); SourcesSearch->SetHintText(MakeAttributeSP(this, &SContentBrowser::GetSourcesSearchHintText)); CollectionViewPtr = SNew(SCollectionView) .OnCollectionSelected(this, &SContentBrowser::CollectionSelected) .AddMetaData(FTagMetaData(TEXT("ContentBrowserCollections"))) .AllowCollapsing(false) .AllowCollectionDrag(true) .AllowQuickAssetManagement(true) .ExternalSearch(SourcesSearch); static const FName DefaultForegroundName("DefaultForeground"); BindCommands(); UContentBrowserSettings::OnSettingChanged().AddSP(this, &SContentBrowser::OnContentBrowserSettingsChanged); ChildSlot [ SNew(SVerticalBox) // Path and history +SVerticalBox::Slot() .AutoHeight() .Padding( 0, 0, 0, 0 ) [ SNew( SWrapBox ) .UseAllottedSize( true ) .InnerSlotPadding( FVector2D( 5, 2 ) ) + SWrapBox::Slot() .FillLineWhenSizeLessThan( 600 ) .FillEmptySpace( true ) [ SNew( SHorizontalBox ) + SHorizontalBox::Slot() .FillWidth(1.0f) [ SNew( SBorder ) .Padding( FMargin( 3 ) ) .BorderImage( FEditorStyle::GetBrush( "ContentBrowser.TopBar.GroupBorder" ) ) [ SNew( SHorizontalBox ) // New + SHorizontalBox::Slot() .AutoWidth() .VAlign( VAlign_Center ) .HAlign( HAlign_Left ) [ SNew( SComboButton ) .ComboButtonStyle( FEditorStyle::Get(), "ToolbarComboButton" ) .ButtonStyle(FEditorStyle::Get(), "FlatButton.Success") .ForegroundColor(FLinearColor::White) .ContentPadding(FMargin(6, 2)) .OnGetMenuContent_Lambda( [this]{ return MakeAddNewContextMenu( true, false ); } ) .ToolTipText( this, &SContentBrowser::GetAddNewToolTipText ) .IsEnabled( this, &SContentBrowser::IsAddNewEnabled ) .AddMetaData(FTagMetaData(TEXT("ContentBrowserNewAsset"))) .HasDownArrow(false) .ButtonContent() [ SNew( SHorizontalBox ) // New Icon + SHorizontalBox::Slot() .VAlign(VAlign_Center) .AutoWidth() [ SNew(STextBlock) .TextStyle(FEditorStyle::Get(), "ContentBrowser.TopBar.Font") .Font(FEditorStyle::Get().GetFontStyle("FontAwesome.11")) .Text(FEditorFontGlyphs::File) ] // New Text + SHorizontalBox::Slot() .AutoWidth() .VAlign(VAlign_Center) .Padding(4, 0, 0, 0) [ SNew( STextBlock ) .TextStyle( FEditorStyle::Get(), "ContentBrowser.TopBar.Font" ) .Text( LOCTEXT( "NewButton", "Add New" ) ) ] // Down Arrow + SHorizontalBox::Slot() .VAlign(VAlign_Center) .AutoWidth() .Padding(4, 0, 0, 0) [ SNew(STextBlock) .TextStyle(FEditorStyle::Get(), "ContentBrowser.TopBar.Font") .Font(FEditorStyle::Get().GetFontStyle("FontAwesome.10")) .Text(FEditorFontGlyphs::Caret_Down) ] ] ] // Import + SHorizontalBox::Slot() .AutoWidth() .VAlign( VAlign_Center ) .HAlign( HAlign_Left ) .Padding(6, 0) [ SNew( SButton ) .ButtonStyle(FEditorStyle::Get(), "FlatButton") .ToolTipText( this, &SContentBrowser::GetImportTooltipText ) .IsEnabled( this, &SContentBrowser::IsImportEnabled ) .OnClicked( this, &SContentBrowser::HandleImportClicked ) .ContentPadding(FMargin(6, 2)) .AddMetaData(FTagMetaData(TEXT("ContentBrowserImportAsset"))) [ SNew( SHorizontalBox ) // Import Icon + SHorizontalBox::Slot() .VAlign(VAlign_Center) .AutoWidth() [ SNew(STextBlock) .TextStyle(FEditorStyle::Get(), "ContentBrowser.TopBar.Font") .Font(FEditorStyle::Get().GetFontStyle("FontAwesome.11")) .Text(FEditorFontGlyphs::Download) ] // Import Text + SHorizontalBox::Slot() .AutoWidth() .VAlign(VAlign_Center) .Padding(4, 0, 0, 0) [ SNew( STextBlock ) .TextStyle( FEditorStyle::Get(), "ContentBrowser.TopBar.Font" ) .Text( LOCTEXT( "Import", "Import" ) ) ] ] ] // Save + SHorizontalBox::Slot() .FillWidth(1.0f) .VAlign(VAlign_Center) .HAlign(HAlign_Left) [ SNew( SButton ) .ButtonStyle(FEditorStyle::Get(), "FlatButton") .ToolTipText( LOCTEXT( "SaveDirtyPackagesTooltip", "Save all modified assets." ) ) .ContentPadding(FMargin(6, 2)) .OnClicked( this, &SContentBrowser::OnSaveClicked ) .AddMetaData(FTagMetaData(TEXT("ContentBrowserSaveDirtyPackages"))) [ SNew( SHorizontalBox ) // Save All Icon + SHorizontalBox::Slot() .VAlign(VAlign_Center) .AutoWidth() [ SNew(STextBlock) .TextStyle(FEditorStyle::Get(), "ContentBrowser.TopBar.Font") .Font(FEditorStyle::Get().GetFontStyle("FontAwesome.11")) .Text(FEditorFontGlyphs::Floppy_O) ] // Save All Text + SHorizontalBox::Slot() .AutoWidth() .VAlign(VAlign_Center) .Padding(4, 0, 0, 0) [ SNew( STextBlock ) .TextStyle( FEditorStyle::Get(), "ContentBrowser.TopBar.Font" ) .Text( LOCTEXT( "SaveAll", "Save All" ) ) ] ] ] ] ] ] + SWrapBox::Slot() .FillEmptySpace( true ) [ SNew(SBorder) .Padding(FMargin(3)) .BorderImage( FEditorStyle::GetBrush("ContentBrowser.TopBar.GroupBorder") ) [ SNew(SHorizontalBox) // History Back Button +SHorizontalBox::Slot() .AutoWidth() [ SNew(SVerticalBox) + SVerticalBox::Slot() .FillHeight(1.0f) [ SNew(SButton) .VAlign(EVerticalAlignment::VAlign_Center) .ButtonStyle(FEditorStyle::Get(), "FlatButton") .ForegroundColor( FEditorStyle::GetSlateColor(DefaultForegroundName) ) .ToolTipText( this, &SContentBrowser::GetHistoryBackTooltip ) .ContentPadding( FMargin(1, 0) ) .OnClicked(this, &SContentBrowser::BackClicked) .IsEnabled(this, &SContentBrowser::IsBackEnabled) .AddMetaData(FTagMetaData(TEXT("ContentBrowserHistoryBack"))) [ SNew(STextBlock) .TextStyle(FEditorStyle::Get(), "ContentBrowser.TopBar.Font") .Font(FEditorStyle::Get().GetFontStyle("FontAwesome.11")) .Text(FText::FromString(FString(TEXT("\xf060"))) /*fa-arrow-left*/) ] ] ] // History Forward Button + SHorizontalBox::Slot() .AutoWidth() [ SNew(SVerticalBox) + SVerticalBox::Slot() .FillHeight(1.0f) [ SNew(SButton) .VAlign(EVerticalAlignment::VAlign_Center) .ButtonStyle(FEditorStyle::Get(), "FlatButton") .ForegroundColor( FEditorStyle::GetSlateColor(DefaultForegroundName) ) .ToolTipText( this, &SContentBrowser::GetHistoryForwardTooltip ) .ContentPadding( FMargin(1, 0) ) .OnClicked(this, &SContentBrowser::ForwardClicked) .IsEnabled(this, &SContentBrowser::IsForwardEnabled) .AddMetaData(FTagMetaData(TEXT("ContentBrowserHistoryForward"))) [ SNew(STextBlock) .TextStyle(FEditorStyle::Get(), "ContentBrowser.TopBar.Font") .Font(FEditorStyle::Get().GetFontStyle("FontAwesome.11")) .Text(FText::FromString(FString(TEXT("\xf061"))) /*fa-arrow-right*/) ] ] ] // Separator + SHorizontalBox::Slot() .AutoWidth() .Padding(3, 0) [ SNew(SSeparator) .Orientation(Orient_Vertical) ] // Path picker + SHorizontalBox::Slot() .AutoWidth() .VAlign( VAlign_Fill ) [ SAssignNew( PathPickerButton, SComboButton ) .Visibility( ( Config != nullptr ? Config->bUsePathPicker : true ) ? EVisibility::Visible : EVisibility::Collapsed ) .ButtonStyle(FEditorStyle::Get(), "FlatButton") .ForegroundColor(FLinearColor::White) .ToolTipText( LOCTEXT( "PathPickerTooltip", "Choose a path" ) ) .OnGetMenuContent( this, &SContentBrowser::GetPathPickerContent ) .HasDownArrow( false ) .AddMetaData(FTagMetaData(TEXT("ContentBrowserPathPicker"))) .ContentPadding(FMargin(3, 3)) .ButtonContent() [ SNew(STextBlock) .TextStyle(FEditorStyle::Get(), "ContentBrowser.TopBar.Font") .Font(FEditorStyle::Get().GetFontStyle("FontAwesome.11")) .Text(FText::FromString(FString(TEXT("\xf07c"))) /*fa-folder-open*/) ] ] // Path + SHorizontalBox::Slot() .VAlign(VAlign_Center) .HAlign(HAlign_Left) .FillWidth(1.0f) .Padding(FMargin(0)) [ SAssignNew(PathBreadcrumbTrail, SBreadcrumbTrail) .ButtonContentPadding(FMargin(2, 2)) .ButtonStyle(FEditorStyle::Get(), "FlatButton") .DelimiterImage(FEditorStyle::GetBrush("ContentBrowser.PathDelimiter")) .TextStyle(FEditorStyle::Get(), "ContentBrowser.PathText") .ShowLeadingDelimiter(false) .InvertTextColorOnHover(false) .OnCrumbClicked(this, &SContentBrowser::OnPathClicked) .HasCrumbMenuContent(this, &SContentBrowser::OnHasCrumbDelimiterContent) .GetCrumbMenuContent(this, &SContentBrowser::OnGetCrumbDelimiterContent) .AddMetaData(FTagMetaData(TEXT("ContentBrowserPath"))) ] // Lock button + SHorizontalBox::Slot() .AutoWidth() .VAlign(VAlign_Center) [ SNew(SVerticalBox) .Visibility( ( Config != nullptr ? Config->bCanShowLockButton : true ) ? EVisibility::SelfHitTestInvisible : EVisibility::Collapsed ) + SVerticalBox::Slot() .FillHeight(1.0f) [ SNew(SButton) .VAlign(EVerticalAlignment::VAlign_Center) .ButtonStyle(FEditorStyle::Get(), "FlatButton") .ToolTipText( LOCTEXT("LockToggleTooltip", "Toggle lock. If locked, this browser will ignore Find in Content Browser requests.") ) .ContentPadding( FMargin(1, 0) ) .OnClicked(this, &SContentBrowser::ToggleLockClicked) .AddMetaData(FTagMetaData(TEXT("ContentBrowserLock"))) [ SNew(SImage) .Image( this, &SContentBrowser::GetToggleLockImage) ] ] ] ] ] ] + SVerticalBox::Slot() .AutoHeight() .Padding(0,0,0,0) [ SNew(SBox) .HeightOverride(2.0f) [ SNew(SImage) .Image(new FSlateColorBrush(FLinearColor( FColor( 34, 34, 34) ) ) ) ] ] // Assets/tree + SVerticalBox::Slot() .FillHeight(1.0f) .Padding(0,2,0,0) [ // The tree/assets splitter SAssignNew(PathAssetSplitterPtr, SSplitter) .Style(FEditorStyle::Get(), "ContentBrowser.Splitter") .PhysicalSplitterHandleSize(2.0f) // Sources View + SSplitter::Slot() .Value(0.25f) [ SNew(SBorder) .Padding(FMargin(3)) .BorderImage(FEditorStyle::GetBrush("ContentBrowser.TopBar.GroupBorder")) .Visibility(this, &SContentBrowser::GetSourcesViewVisibility) [ SNew(SVerticalBox) // Paths expansion/search +SVerticalBox::Slot() .AutoHeight() [ SNew(SHorizontalBox) .AddMetaData(FTagMetaData(TEXT("ContentBrowserSourcesToggle1"))) +SHorizontalBox::Slot() .AutoWidth() .Padding(0, 0, 2, 0) [ SNew(SButton) .VAlign(EVerticalAlignment::VAlign_Center) .ButtonStyle(FEditorStyle::Get(), "ToggleButton") .ToolTipText(LOCTEXT("SourcesTreeToggleTooltip", "Show or hide the sources panel")) .ContentPadding(FMargin(1, 0)) .ForegroundColor(FEditorStyle::GetSlateColor(DefaultForegroundName)) .OnClicked(this, &SContentBrowser::SourcesViewExpandClicked) [ SNew(SImage) .Image(this, &SContentBrowser::GetSourcesToggleImage) ] ] +SHorizontalBox::Slot() [ SourcesSearch->GetWidget() ] +SHorizontalBox::Slot() .AutoWidth() .Padding(2, 0, 0, 0) [ SNew(SButton) .Visibility(this, &SContentBrowser::GetSourcesSwitcherVisibility) .VAlign(EVerticalAlignment::VAlign_Center) .ButtonStyle(FEditorStyle::Get(), "ToggleButton") .ToolTipText(this, &SContentBrowser::GetSourcesSwitcherToolTipText) .ContentPadding(FMargin(1, 0)) .ForegroundColor(FEditorStyle::GetSlateColor(DefaultForegroundName)) .OnClicked(this, &SContentBrowser::OnSourcesSwitcherClicked) [ SNew(SImage) .Image(this, &SContentBrowser::GetSourcesSwitcherIcon) ] ] ] +SVerticalBox::Slot() .FillHeight(1.0f) [ // Note: If adding more widgets here, fix ContentBrowserSourcesWidgetSwitcherIndex and the code that uses it! SAssignNew(SourcesWidgetSwitcher, SWidgetSwitcher) // Paths View +SWidgetSwitcher::Slot() [ SAssignNew(PathFavoriteSplitterPtr, SSplitter) .Style(FEditorStyle::Get(), "ContentBrowser.Splitter") .PhysicalSplitterHandleSize(2.0f) .HitDetectionSplitterHandleSize(2.0f) .Orientation(EOrientation::Orient_Vertical) .MinimumSlotHeight(70.0f) .Visibility( this, &SContentBrowser::GetSourcesViewVisibility ) +SSplitter::Slot() .Value(0.2f) [ SNew(SBox) .Visibility(this, &SContentBrowser::GetFavoriteFolderVisibility) [ SNew(SExpandableArea) .BorderImage(FEditorStyle::GetBrush("NoBorder")) .HeaderPadding(FMargin(0.0f, 3.0f, 0.0f, 0.0f)) .HeaderContent() [ SNew(SHorizontalBox) +SHorizontalBox::Slot() .AutoWidth() .Padding(0, 0, 2, 0) .VAlign(VAlign_Center) [ SNew(SImage) .Image(FEditorStyle::GetBrush("PropertyWindow.Favorites_Enabled")) ] +SHorizontalBox::Slot() .AutoWidth() .VAlign(VAlign_Center) [ SNew(STextBlock) .Text(LOCTEXT("Favorites", "Favorites")) .Font(FEditorStyle::GetFontStyle("ContentBrowser.SourceTreeRootItemFont")) ] ] .BodyContent() [ SNew(SBox) .Padding(FMargin(9, 0, 0, 0)) [ SAssignNew(FavoritePathViewPtr, SFavoritePathView) .OnPathSelected(this, &SContentBrowser::FavoritePathSelected) .OnGetFolderContextMenu(this, &SContentBrowser::GetFolderContextMenu, true) .OnGetPathContextMenuExtender(this, &SContentBrowser::GetPathContextMenuExtender) .FocusSearchBoxWhenOpened(false) .ShowTreeTitle(false) .ShowSeparator(false) .AllowClassesFolder(true) .AddMetaData(FTagMetaData(TEXT("ContentBrowserFavorites"))) .ExternalSearch(SourcesSearch) ] ] ] ] +SSplitter::Slot() .Value(0.8f) [ SNew(SBox) .Padding(FMargin(0.0f, 1.0f, 0.0f, 0.0f)) [ SAssignNew( PathViewPtr, SPathView ) .OnPathSelected( this, &SContentBrowser::PathSelected ) .OnGetFolderContextMenu( this, &SContentBrowser::GetFolderContextMenu, true ) .OnGetPathContextMenuExtender( this, &SContentBrowser::GetPathContextMenuExtender ) .FocusSearchBoxWhenOpened( false ) .ShowTreeTitle( false ) .ShowSeparator( false ) .AllowClassesFolder( true ) .AddMetaData(FTagMetaData(TEXT("ContentBrowserSources"))) .ExternalSearch(SourcesSearch) ] ] +SSplitter::Slot() .Value(0.4f) [ SNew(SBox) .Visibility(this, &SContentBrowser::GetDockedCollectionsVisibility) [ CollectionViewPtr.ToSharedRef() ] ] ] // Collections View +SWidgetSwitcher::Slot() [ SNew(SBox) .Padding(FMargin(0.0f, 4.0f, 0.0f, 0.0f)) [ CollectionViewPtr.ToSharedRef() ] ] ] ] ] // Asset View + SSplitter::Slot() .Value(0.75f) [ SNew(SBorder) .Padding(FMargin(3)) .BorderImage( FEditorStyle::GetBrush("ContentBrowser.TopBar.GroupBorder") ) [ SNew(SVerticalBox) // Search and commands + SVerticalBox::Slot() .AutoHeight() .Padding(0, 0, 0, 2) [ SNew(SHorizontalBox) // Expand/collapse sources button + SHorizontalBox::Slot() .AutoWidth() .Padding( 0, 0, 4, 0 ) [ SNew( SVerticalBox ) .Visibility(( Config != nullptr ? Config->bUseSourcesView : true ) ? EVisibility::SelfHitTestInvisible : EVisibility::Collapsed) .AddMetaData(FTagMetaData(TEXT("ContentBrowserSourcesToggle2"))) + SVerticalBox::Slot() .FillHeight( 1.0f ) [ SNew( SButton ) .VAlign( EVerticalAlignment::VAlign_Center ) .ButtonStyle( FEditorStyle::Get(), "ToggleButton" ) .ToolTipText( LOCTEXT( "SourcesTreeToggleTooltip", "Show or hide the sources panel" ) ) .ContentPadding( FMargin( 1, 0 ) ) .ForegroundColor( FEditorStyle::GetSlateColor(DefaultForegroundName) ) .OnClicked( this, &SContentBrowser::SourcesViewExpandClicked ) .Visibility( this, &SContentBrowser::GetPathExpanderVisibility ) [ SNew( SImage ) .Image( this, &SContentBrowser::GetSourcesToggleImage ) ] ] ] // Filter + SHorizontalBox::Slot() .AutoWidth() [ SNew( SComboButton ) .ComboButtonStyle( FEditorStyle::Get(), "GenericFilters.ComboButtonStyle" ) .ForegroundColor(FLinearColor::White) .ContentPadding(0) .ToolTipText( LOCTEXT( "AddFilterToolTip", "Add an asset filter." ) ) .OnGetMenuContent( this, &SContentBrowser::MakeAddFilterMenu ) .HasDownArrow( true ) .ContentPadding( FMargin( 1, 0 ) ) .AddMetaData(FTagMetaData(TEXT("ContentBrowserFiltersCombo"))) .Visibility( ( Config != nullptr ? Config->bCanShowFilters : true ) ? EVisibility::Visible : EVisibility::Collapsed ) .ButtonContent() [ SNew(SHorizontalBox) + SHorizontalBox::Slot() .AutoWidth() [ SNew(STextBlock) .TextStyle(FEditorStyle::Get(), "GenericFilters.TextStyle") .Font(FEditorStyle::Get().GetFontStyle("FontAwesome.9")) .Text(FText::FromString(FString(TEXT("\xf0b0"))) /*fa-filter*/) ] + SHorizontalBox::Slot() .AutoWidth() .Padding(2,0,0,0) [ SNew(STextBlock) .TextStyle(FEditorStyle::Get(), "GenericFilters.TextStyle") .Text(LOCTEXT("Filters", "Filters")) ] ] ] // Search +SHorizontalBox::Slot() .Padding(4, 1, 0, 0) .FillWidth(1.0f) [ SAssignNew(SearchBoxPtr, SAssetSearchBox) .HintText( this, &SContentBrowser::GetSearchAssetsHintText ) .OnTextChanged( this, &SContentBrowser::OnSearchBoxChanged ) .OnTextCommitted( this, &SContentBrowser::OnSearchBoxCommitted ) .OnAssetSearchBoxSuggestionFilter( this, &SContentBrowser::OnAssetSearchSuggestionFilter ) .OnAssetSearchBoxSuggestionChosen( this, &SContentBrowser::OnAssetSearchSuggestionChosen ) .DelayChangeNotificationsWhileTyping( true ) .Visibility( ( Config != nullptr ? Config->bCanShowAssetSearch : true ) ? EVisibility::Visible : EVisibility::Collapsed ) .AddMetaData(FTagMetaData(TEXT("ContentBrowserSearchAssets"))) ] // Save Search +SHorizontalBox::Slot() .AutoWidth() .VAlign(VAlign_Center) .Padding(2.0f, 0.0f, 0.0f, 0.0f) [ SNew(SButton) .ButtonStyle(FEditorStyle::Get(), "FlatButton") .ToolTipText(LOCTEXT("SaveSearchButtonTooltip", "Save the current search as a dynamic collection.")) .IsEnabled(this, &SContentBrowser::IsSaveSearchButtonEnabled) .OnClicked(this, &SContentBrowser::OnSaveSearchButtonClicked) .ContentPadding( FMargin(1, 1) ) .Visibility( ( Config != nullptr ? Config->bCanShowAssetSearch : true ) ? EVisibility::Visible : EVisibility::Collapsed ) [ SNew(STextBlock) .TextStyle(FEditorStyle::Get(), "GenericFilters.TextStyle") .Font(FEditorStyle::Get().GetFontStyle("FontAwesome.10")) .Text(FEditorFontGlyphs::Floppy_O) ] ] ] // Filters + SVerticalBox::Slot() .AutoHeight() [ SAssignNew(FilterListPtr, SFilterList) .OnFilterChanged(this, &SContentBrowser::OnFilterChanged) .OnGetContextMenu(this, &SContentBrowser::GetFilterContextMenu) .Visibility( ( Config != nullptr ? Config->bCanShowFilters : true ) ? EVisibility::Visible : EVisibility::Collapsed ) .FrontendFilters(FrontendFilters) .AddMetaData(FTagMetaData(TEXT("ContentBrowserFilters"))) ] // Assets + SVerticalBox::Slot() .FillHeight( 1.0f ) .Padding( 0 ) [ SAssignNew(AssetViewPtr, SAssetView) .ThumbnailLabel( Config != nullptr ? Config->ThumbnailLabel : EThumbnailLabel::ClassName ) .ThumbnailScale( Config != nullptr ? Config->ThumbnailScale : 0.18f ) .InitialViewType( Config != nullptr ? Config->InitialAssetViewType : EAssetViewType::Tile ) .ShowBottomToolbar( Config != nullptr ? Config->bShowBottomToolbar : true ) .OnPathSelected(this, &SContentBrowser::FolderEntered) .OnAssetSelected(this, &SContentBrowser::OnAssetSelectionChanged) .OnAssetsActivated(this, &SContentBrowser::OnAssetsActivated) .OnGetAssetContextMenu(this, &SContentBrowser::OnGetAssetContextMenu) .OnGetFolderContextMenu(this, &SContentBrowser::GetFolderContextMenu, false) .OnGetPathContextMenuExtender(this, &SContentBrowser::GetPathContextMenuExtender) .OnFindInAssetTreeRequested(this, &SContentBrowser::OnFindInAssetTreeRequested) .OnAssetRenameCommitted(this, &SContentBrowser::OnAssetRenameCommitted) .AreRealTimeThumbnailsAllowed(this, &SContentBrowser::IsHovered) .FrontendFilters(FrontendFilters) .HighlightedText(this, &SContentBrowser::GetHighlightedText) .AllowThumbnailEditMode(true) .AllowThumbnailHintLabel(false) .CanShowFolders(Config != nullptr ? Config->bCanShowFolders : true) .CanShowClasses(Config != nullptr ? Config->bCanShowClasses : true) .CanShowRealTimeThumbnails( Config != nullptr ? Config->bCanShowRealTimeThumbnails : true) .CanShowDevelopersFolder( Config != nullptr ? Config->bCanShowDevelopersFolder : true) .CanShowFavorites(true) .CanDockCollections(true) .AddMetaData(FTagMetaData(TEXT("ContentBrowserAssets"))) .OnSearchOptionsChanged(this, &SContentBrowser::HandleAssetViewSearchOptionsChanged) ] ] ] ] ]; AssetContextMenu = MakeShareable(new FAssetContextMenu(AssetViewPtr)); AssetContextMenu->BindCommands(Commands); AssetContextMenu->SetOnFindInAssetTreeRequested( FOnFindInAssetTreeRequested::CreateSP(this, &SContentBrowser::OnFindInAssetTreeRequested) ); AssetContextMenu->SetOnRenameRequested( FAssetContextMenu::FOnRenameRequested::CreateSP(this, &SContentBrowser::OnRenameRequested) ); AssetContextMenu->SetOnRenameFolderRequested( FAssetContextMenu::FOnRenameFolderRequested::CreateSP(this, &SContentBrowser::OnRenameFolderRequested) ); AssetContextMenu->SetOnDuplicateRequested( FAssetContextMenu::FOnDuplicateRequested::CreateSP(this, &SContentBrowser::OnDuplicateRequested) ); AssetContextMenu->SetOnAssetViewRefreshRequested( FAssetContextMenu::FOnAssetViewRefreshRequested::CreateSP( this, &SContentBrowser::OnAssetViewRefreshRequested) ); FavoritePathViewPtr->SetTreeTitle(LOCTEXT("Favorites", "Favorites")); if( Config != nullptr && Config->SelectedCollectionName.Name != NAME_None ) { // Select the specified collection by default FSourcesData DefaultSourcesData( Config->SelectedCollectionName ); TArray SelectedPaths; AssetViewPtr->SetSourcesData( DefaultSourcesData ); } else { // Select /Game by default FSourcesData DefaultSourcesData(FName("/Game")); TArray SelectedPaths; TArray SelectedFavoritePaths; SelectedPaths.Add(TEXT("/Game")); PathViewPtr->SetSelectedPaths(SelectedPaths); AssetViewPtr->SetSourcesData(DefaultSourcesData); FavoritePathViewPtr->SetSelectedPaths(SelectedFavoritePaths); } // Bind the favorites menu to update after folder changes in the path or asset view PathViewPtr->OnFolderPathChanged.BindSP(FavoritePathViewPtr.Get(), &SFavoritePathView::FixupFavoritesFromExternalChange); AssetViewPtr->OnFolderPathChanged.BindSP(FavoritePathViewPtr.Get(), &SFavoritePathView::FixupFavoritesFromExternalChange); // Set the initial history data HistoryManager.AddHistoryData(); // Load settings if they were specified this->InstanceName = InInstanceName; LoadSettings(InInstanceName); if( Config != nullptr ) { // Make sure the sources view is initially visible if we were asked to show it if( ( bSourcesViewExpanded && ( !Config->bExpandSourcesView || !Config->bUseSourcesView ) ) || ( !bSourcesViewExpanded && Config->bExpandSourcesView && Config->bUseSourcesView ) ) { SourcesViewExpandClicked(); } } else { // in case we do not have a config, see what the global default settings are for the Sources Panel if (!bSourcesViewExpanded && GetDefault()->bOpenSourcesPanelByDefault) { SourcesViewExpandClicked(); } } // Bindings to manage history when items are deleted FCollectionManagerModule& CollectionManagerModule = FCollectionManagerModule::GetModule(); CollectionManagerModule.Get().OnCollectionRenamed().AddSP(this, &SContentBrowser::HandleCollectionRenamed); CollectionManagerModule.Get().OnCollectionDestroyed().AddSP(this, &SContentBrowser::HandleCollectionRemoved); CollectionManagerModule.Get().OnCollectionUpdated().AddSP(this, &SContentBrowser::HandleCollectionUpdated); FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked(TEXT("AssetRegistry")); AssetRegistryModule.Get().OnPathRemoved().AddSP(this, &SContentBrowser::HandlePathRemoved); // We want to be able to search the feature packs in the super search so we need the module loaded IAddContentDialogModule& AddContentDialogModule = FModuleManager::LoadModuleChecked("AddContentDialog"); // Update the breadcrumb trail path OnContentBrowserSettingsChanged(NAME_None); } END_SLATE_FUNCTION_BUILD_OPTIMIZATION void SContentBrowser::BindCommands() { Commands = TSharedPtr< FUICommandList >(new FUICommandList); Commands->MapAction(FGenericCommands::Get().Rename, FUIAction( FExecuteAction::CreateSP(this, &SContentBrowser::HandleRenameCommand), FCanExecuteAction::CreateSP(this, &SContentBrowser::HandleRenameCommandCanExecute) )); Commands->MapAction(FGenericCommands::Get().Delete, FUIAction( FExecuteAction::CreateSP(this, &SContentBrowser::HandleDeleteCommandExecute), FCanExecuteAction::CreateSP(this, &SContentBrowser::HandleDeleteCommandCanExecute) )); Commands->MapAction(FContentBrowserCommands::Get().OpenAssetsOrFolders, FUIAction( FExecuteAction::CreateSP(this, &SContentBrowser::HandleOpenAssetsOrFoldersCommandExecute) )); Commands->MapAction(FContentBrowserCommands::Get().PreviewAssets, FUIAction( FExecuteAction::CreateSP(this, &SContentBrowser::HandlePreviewAssetsCommandExecute) )); Commands->MapAction(FContentBrowserCommands::Get().CreateNewFolder, FUIAction( FExecuteAction::CreateSP(this, &SContentBrowser::HandleCreateNewFolderCommandExecute) )); Commands->MapAction(FContentBrowserCommands::Get().DirectoryUp, FUIAction( FExecuteAction::CreateSP(this, &SContentBrowser::HandleDirectoryUpCommandExecute) )); Commands->MapAction(FContentBrowserCommands::Get().SaveSelectedAsset, FUIAction( FExecuteAction::CreateSP(this, &SContentBrowser::HandleSaveAssetCommand), FCanExecuteAction::CreateSP(this, &SContentBrowser::HandleSaveAssetCommandCanExecute) )); Commands->MapAction(FContentBrowserCommands::Get().SaveAllCurrentFolder, FUIAction( FExecuteAction::CreateSP(this, &SContentBrowser::HandleSaveAllCurrentFolderCommand) )); Commands->MapAction(FContentBrowserCommands::Get().ResaveAllCurrentFolder, FUIAction( FExecuteAction::CreateSP(this, &SContentBrowser::HandleResaveAllCurrentFolderCommand) )); // Allow extenders to add commands FContentBrowserModule& ContentBrowserModule = FModuleManager::GetModuleChecked(TEXT("ContentBrowser")); TArray CommmandExtenderDelegates = ContentBrowserModule.GetAllContentBrowserCommandExtenders(); for (int32 i = 0; i < CommmandExtenderDelegates.Num(); ++i) { if (CommmandExtenderDelegates[i].IsBound()) { CommmandExtenderDelegates[i].Execute(Commands.ToSharedRef(), FOnContentBrowserGetSelection::CreateSP(this, &SContentBrowser::GetSelectionState)); } } } EVisibility SContentBrowser::GetFavoriteFolderVisibility() const { return GetDefault()->GetDisplayFavorites() ? EVisibility::Visible : EVisibility::Collapsed; } EVisibility SContentBrowser::GetDockedCollectionsVisibility() const { return GetDefault()->GetDockCollections() ? EVisibility::Visible : EVisibility::Collapsed; } void SContentBrowser::ToggleFolderFavorite(const TArray& FolderPaths) { bool bAddedFavorite = false; for (FString FolderPath : FolderPaths) { if (ContentBrowserUtils::IsFavoriteFolder(FolderPath)) { ContentBrowserUtils::RemoveFavoriteFolder(FolderPath, false); } else { ContentBrowserUtils::AddFavoriteFolder(FolderPath, false); bAddedFavorite = true; } } GConfig->Flush(false, GEditorPerProjectIni); FavoritePathViewPtr->Populate(); if(bAddedFavorite) { FavoritePathViewPtr->SetSelectedPaths(FolderPaths); if (GetFavoriteFolderVisibility() == EVisibility::Collapsed) { UContentBrowserSettings* Settings = GetMutableDefault(); Settings->SetDisplayFavorites(true); Settings->SaveConfig(); } } } void SContentBrowser::HandleAssetViewSearchOptionsChanged() { TextFilter->SetIncludeClassName(AssetViewPtr->IsIncludingClassNames()); TextFilter->SetIncludeAssetPath(AssetViewPtr->IsIncludingAssetPaths()); TextFilter->SetIncludeCollectionNames(AssetViewPtr->IsIncludingCollectionNames()); } FText SContentBrowser::GetHighlightedText() const { return TextFilter->GetRawFilterText(); } void SContentBrowser::CreateNewAsset(const FString& DefaultAssetName, const FString& PackagePath, UClass* AssetClass, UFactory* Factory) { AssetViewPtr->CreateNewAsset(DefaultAssetName, PackagePath, AssetClass, Factory); } bool SContentBrowser::IsImportEnabled() const { const FSourcesData& SourcesData = AssetViewPtr->GetSourcesData(); return SourcesData.PackagePaths.Num() == 1 && !ContentBrowserUtils::IsClassPath(SourcesData.PackagePaths[0].ToString()); } FText SContentBrowser::GetImportTooltipText() const { const FSourcesData& SourcesData = AssetViewPtr->GetSourcesData(); if ( SourcesData.PackagePaths.Num() == 1 ) { const FString CurrentPath = SourcesData.PackagePaths[0].ToString(); if ( ContentBrowserUtils::IsClassPath( CurrentPath ) ) { return LOCTEXT( "ImportAssetToolTip_InvalidClassPath", "Cannot import assets to class paths." ); } else { return FText::Format( LOCTEXT( "ImportAssetToolTip", "Import to {0}..." ), FText::FromString( CurrentPath ) ); } } else if ( SourcesData.PackagePaths.Num() > 1 ) { return LOCTEXT( "ImportAssetToolTip_MultiplePaths", "Cannot import assets to multiple paths." ); } return LOCTEXT( "ImportAssetToolTip_NoPath", "No path is selected as an import target." ); } FReply SContentBrowser::HandleImportClicked() { ImportAsset( GetCurrentPath() ); return FReply::Handled(); } void SContentBrowser::ImportAsset( const FString& InPath ) { if ( ensure( !InPath.IsEmpty() ) ) { FAssetToolsModule& AssetToolsModule = FModuleManager::Get().LoadModuleChecked( "AssetTools" ); AssetToolsModule.Get().ImportAssetsWithDialog( InPath ); } } void SContentBrowser::PrepareToSync( const TArray& AssetDataList, const TArray& FolderPaths, const bool bDisableFiltersThatHideAssets ) { // Check to see if any of the assets require certain folders to be visible bool bDisplayDev = GetDefault()->GetDisplayDevelopersFolder(); bool bDisplayEngine = GetDefault()->GetDisplayEngineFolder(); bool bDisplayPlugins = GetDefault()->GetDisplayPluginFolders(); bool bDisplayLocalized = GetDefault()->GetDisplayL10NFolder(); if ( !bDisplayDev || !bDisplayEngine || !bDisplayPlugins || !bDisplayLocalized ) { TSet PackagePaths = TSet(FolderPaths); for (const FAssetData& AssetData : AssetDataList) { FString PackagePath; if (AssetData.AssetClass == NAME_Class) { // Classes are found in the /Classes_ roots TSharedRef NativeClassHierarchy = FContentBrowserSingleton::Get().GetNativeClassHierarchy(); NativeClassHierarchy->GetClassPath(Cast(AssetData.GetAsset()), PackagePath, false/*bIncludeClassName*/); } else { // All other assets are found by their package path PackagePath = AssetData.PackagePath.ToString(); } PackagePaths.Add(PackagePath); } bool bRepopulate = false; for (const FString& PackagePath : PackagePaths) { const ContentBrowserUtils::ECBFolderCategory FolderCategory = ContentBrowserUtils::GetFolderCategory( PackagePath ); if ( !bDisplayDev && FolderCategory == ContentBrowserUtils::ECBFolderCategory::DeveloperContent ) { bDisplayDev = true; GetMutableDefault()->SetDisplayDevelopersFolder(true, true); bRepopulate = true; } else if ( !bDisplayEngine && (FolderCategory == ContentBrowserUtils::ECBFolderCategory::EngineContent || FolderCategory == ContentBrowserUtils::ECBFolderCategory::EngineClasses) ) { bDisplayEngine = true; GetMutableDefault()->SetDisplayEngineFolder(true, true); bRepopulate = true; // Handle being a plugin as well if (!bDisplayPlugins && (FolderCategory == ContentBrowserUtils::ECBFolderCategory::EngineContent)) { EPluginLoadedFrom PluginSource; if (ContentBrowserUtils::IsPluginFolder(PackagePath, &PluginSource)) { bDisplayPlugins = true; GetMutableDefault()->SetDisplayPluginFolders(true, true); } } } else if ( !bDisplayPlugins && (FolderCategory == ContentBrowserUtils::ECBFolderCategory::PluginContent || FolderCategory == ContentBrowserUtils::ECBFolderCategory::PluginClasses) ) { bDisplayPlugins = true; GetMutableDefault()->SetDisplayPluginFolders(true, true); bRepopulate = true; } if (!bDisplayLocalized && ContentBrowserUtils::IsLocalizationFolder(PackagePath)) { bDisplayLocalized = true; GetMutableDefault()->SetDisplayL10NFolder(true); bRepopulate = true; } if (bDisplayDev && bDisplayEngine && bDisplayPlugins && bDisplayLocalized) { break; } } // If we have auto-enabled any flags, force a refresh if ( bRepopulate ) { PathViewPtr->Populate(); FavoritePathViewPtr->Populate(); } } if ( bDisableFiltersThatHideAssets ) { // Disable the filter categories FilterListPtr->DisableFiltersThatHideAssets(AssetDataList); } // Disable the filter search (reset the filter, then clear the search text) // Note: we have to remove the filter immediately, we can't wait for OnSearchBoxChanged to hit SetSearchBoxText(FText::GetEmpty()); SearchBoxPtr->SetText(FText::GetEmpty()); SearchBoxPtr->SetError(FText::GetEmpty()); } void SContentBrowser::SyncToAssets( const TArray& AssetDataList, const bool bAllowImplicitSync, const bool bDisableFiltersThatHideAssets ) { PrepareToSync(AssetDataList, TArray(), bDisableFiltersThatHideAssets); // Tell the sources view first so the asset view will be up to date by the time we request the sync PathViewPtr->SyncToAssets(AssetDataList, bAllowImplicitSync); FavoritePathViewPtr->SyncToAssets(AssetDataList, bAllowImplicitSync); AssetViewPtr->SyncToAssets(AssetDataList); } void SContentBrowser::SyncToFolders( const TArray& FolderList, const bool bAllowImplicitSync ) { PrepareToSync(TArray(), FolderList, false); // Tell the sources view first so the asset view will be up to date by the time we request the sync PathViewPtr->SyncToFolders(FolderList, bAllowImplicitSync); FavoritePathViewPtr->SyncToFolders(FolderList, bAllowImplicitSync); AssetViewPtr->SyncToFolders(FolderList); } void SContentBrowser::SyncTo( const FContentBrowserSelection& ItemSelection, const bool bAllowImplicitSync, const bool bDisableFiltersThatHideAssets ) { PrepareToSync(ItemSelection.SelectedAssets, ItemSelection.SelectedFolders, bDisableFiltersThatHideAssets); // Tell the sources view first so the asset view will be up to date by the time we request the sync PathViewPtr->SyncTo(ItemSelection, bAllowImplicitSync); FavoritePathViewPtr->SyncTo(ItemSelection, bAllowImplicitSync); AssetViewPtr->SyncTo(ItemSelection); } void SContentBrowser::SetIsPrimaryContentBrowser(bool NewIsPrimary) { if (!CanSetAsPrimaryContentBrowser()) { return; } bIsPrimaryBrowser = NewIsPrimary; if ( bIsPrimaryBrowser ) { SyncGlobalSelectionSet(); } else { USelection* EditorSelection = GEditor->GetSelectedObjects(); if ( ensure( EditorSelection != NULL ) ) { EditorSelection->DeselectAll(); } } } bool SContentBrowser::CanSetAsPrimaryContentBrowser() const { return bCanSetAsPrimaryBrowser; } TSharedPtr SContentBrowser::GetTabManager() const { if ( ContainingTab.IsValid() ) { return ContainingTab.Pin()->GetTabManager(); } return NULL; } void SContentBrowser::LoadSelectedObjectsIfNeeded() { // Get the selected assets in the asset view const TArray& SelectedAssets = AssetViewPtr->GetSelectedAssets(); // Load every asset that isn't already in memory for ( auto AssetIt = SelectedAssets.CreateConstIterator(); AssetIt; ++AssetIt ) { const FAssetData& AssetData = *AssetIt; const bool bShowProgressDialog = (!AssetData.IsAssetLoaded() && FEditorFileUtils::IsMapPackageAsset(AssetData.ObjectPath.ToString())); GWarn->BeginSlowTask(LOCTEXT("LoadingObjects", "Loading Objects..."), bShowProgressDialog); (*AssetIt).GetAsset(); GWarn->EndSlowTask(); } // Sync the global selection set if we are the primary browser if ( bIsPrimaryBrowser ) { SyncGlobalSelectionSet(); } } void SContentBrowser::GetSelectedAssets(TArray& SelectedAssets) { // Make sure the asset data is up to date AssetViewPtr->ProcessRecentlyLoadedOrChangedAssets(); SelectedAssets = AssetViewPtr->GetSelectedAssets(); } void SContentBrowser::GetSelectedFolders(TArray& SelectedFolders) { // Make sure the asset data is up to date AssetViewPtr->ProcessRecentlyLoadedOrChangedAssets(); SelectedFolders = AssetViewPtr->GetSelectedFolders(); } TArray SContentBrowser::GetSelectedPathViewFolders() { check(PathViewPtr.IsValid()); return PathViewPtr->GetSelectedPaths(); } void SContentBrowser::SaveSettings() const { const FString& SettingsString = InstanceName.ToString(); GConfig->SetBool(*SettingsIniSection, *(SettingsString + TEXT(".SourcesExpanded")), bSourcesViewExpanded, GEditorPerProjectIni); GConfig->SetBool(*SettingsIniSection, *(SettingsString + TEXT(".Locked")), bIsLocked, GEditorPerProjectIni); for(int32 SlotIndex = 0; SlotIndex < PathAssetSplitterPtr->GetChildren()->Num(); SlotIndex++) { float SplitterSize = PathAssetSplitterPtr->SlotAt(SlotIndex).SizeValue.Get(); GConfig->SetFloat(*SettingsIniSection, *(SettingsString + FString::Printf(TEXT(".VerticalSplitter.SlotSize%d"), SlotIndex)), SplitterSize, GEditorPerProjectIni); } for (int32 SlotIndex = 0; SlotIndex < PathFavoriteSplitterPtr->GetChildren()->Num(); SlotIndex++) { float SplitterSize = PathFavoriteSplitterPtr->SlotAt(SlotIndex).SizeValue.Get(); GConfig->SetFloat(*SettingsIniSection, *(SettingsString + FString::Printf(TEXT(".FavoriteSplitter.SlotSize%d"), SlotIndex)), SplitterSize, GEditorPerProjectIni); } // Save all our data using the settings string as a key in the user settings ini FilterListPtr->SaveSettings(GEditorPerProjectIni, SettingsIniSection, SettingsString); PathViewPtr->SaveSettings(GEditorPerProjectIni, SettingsIniSection, SettingsString); FavoritePathViewPtr->SaveSettings(GEditorPerProjectIni, SettingsIniSection, SettingsString + TEXT(".Favorites")); CollectionViewPtr->SaveSettings(GEditorPerProjectIni, SettingsIniSection, SettingsString); AssetViewPtr->SaveSettings(GEditorPerProjectIni, SettingsIniSection, SettingsString); } const FName SContentBrowser::GetInstanceName() const { return InstanceName; } bool SContentBrowser::IsLocked() const { return bIsLocked; } void SContentBrowser::SetKeyboardFocusOnSearch() const { // Focus on the search box FSlateApplication::Get().SetKeyboardFocus( SearchBoxPtr, EFocusCause::SetDirectly ); } FReply SContentBrowser::OnKeyDown( const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent ) { if( Commands->ProcessCommandBindings( InKeyEvent ) ) { return FReply::Handled(); } return FReply::Unhandled(); } FReply SContentBrowser::OnPreviewMouseButtonDown( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent ) { // Clicking in a content browser will shift it to be the primary browser FContentBrowserSingleton::Get().SetPrimaryContentBrowser(SharedThis(this)); return FReply::Unhandled(); } FReply SContentBrowser::OnMouseButtonDown( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent ) { // Mouse back and forward buttons traverse history if ( MouseEvent.GetEffectingButton() == EKeys::ThumbMouseButton) { HistoryManager.GoBack(); return FReply::Handled(); } else if ( MouseEvent.GetEffectingButton() == EKeys::ThumbMouseButton2) { HistoryManager.GoForward(); return FReply::Handled(); } return FReply::Unhandled(); } FReply SContentBrowser::OnMouseButtonDoubleClick( const FGeometry& InMyGeometry, const FPointerEvent& InMouseEvent ) { // Mouse back and forward buttons traverse history if ( InMouseEvent.GetEffectingButton() == EKeys::ThumbMouseButton) { HistoryManager.GoBack(); return FReply::Handled(); } else if ( InMouseEvent.GetEffectingButton() == EKeys::ThumbMouseButton2) { HistoryManager.GoForward(); return FReply::Handled(); } return FReply::Unhandled(); } void SContentBrowser::OnContainingTabSavingVisualState() const { SaveSettings(); } void SContentBrowser::OnContainingTabClosed(TSharedRef DockTab) { FContentBrowserSingleton::Get().ContentBrowserClosed( SharedThis(this) ); } void SContentBrowser::OnContainingTabActivated(TSharedRef DockTab, ETabActivationCause InActivationCause) { if(InActivationCause == ETabActivationCause::UserClickedOnTab) { FContentBrowserSingleton::Get().SetPrimaryContentBrowser(SharedThis(this)); } } void SContentBrowser::LoadSettings(const FName& InInstanceName) { FString SettingsString = InInstanceName.ToString(); // Test to see if we should load legacy settings from a previous instance name // First make sure there aren't any existing settings with the given instance name bool TestBool; if ( !GConfig->GetBool(*SettingsIniSection, *(SettingsString + TEXT(".SourcesExpanded")), TestBool, GEditorPerProjectIni) ) { // If there were not any settings and we are Content Browser 1, see if we have any settings under the legacy name "LevelEditorContentBrowser" if ( InInstanceName.ToString() == TEXT("ContentBrowserTab1") && GConfig->GetBool(*SettingsIniSection, TEXT("LevelEditorContentBrowser.SourcesExpanded"), TestBool, GEditorPerProjectIni) ) { // We have found some legacy settings with the old ID, use them. These settings will be saved out to the new id later SettingsString = TEXT("LevelEditorContentBrowser"); } // else see if we are Content Browser 2, and see if we have any settings under the legacy name "MajorContentBrowserTab" else if ( InInstanceName.ToString() == TEXT("ContentBrowserTab2") && GConfig->GetBool(*SettingsIniSection, TEXT("MajorContentBrowserTab.SourcesExpanded"), TestBool, GEditorPerProjectIni) ) { // We have found some legacy settings with the old ID, use them. These settings will be saved out to the new id later SettingsString = TEXT("MajorContentBrowserTab"); } } // Now that we have determined the appropriate settings string, actually load the settings GConfig->GetBool(*SettingsIniSection, *(SettingsString + TEXT(".SourcesExpanded")), bSourcesViewExpanded, GEditorPerProjectIni); GConfig->GetBool(*SettingsIniSection, *(SettingsString + TEXT(".Locked")), bIsLocked, GEditorPerProjectIni); for(int32 SlotIndex = 0; SlotIndex < PathAssetSplitterPtr->GetChildren()->Num(); SlotIndex++) { float SplitterSize = PathAssetSplitterPtr->SlotAt(SlotIndex).SizeValue.Get(); GConfig->GetFloat(*SettingsIniSection, *(SettingsString + FString::Printf(TEXT(".VerticalSplitter.SlotSize%d"), SlotIndex)), SplitterSize, GEditorPerProjectIni); PathAssetSplitterPtr->SlotAt(SlotIndex).SizeValue = SplitterSize; } for (int32 SlotIndex = 0; SlotIndex < PathFavoriteSplitterPtr->GetChildren()->Num(); SlotIndex++) { float SplitterSize = PathFavoriteSplitterPtr->SlotAt(SlotIndex).SizeValue.Get(); GConfig->GetFloat(*SettingsIniSection, *(SettingsString + FString::Printf(TEXT(".FavoriteSplitter.SlotSize%d"), SlotIndex)), SplitterSize, GEditorPerProjectIni); PathFavoriteSplitterPtr->SlotAt(SlotIndex).SizeValue = SplitterSize; } // Save all our data using the settings string as a key in the user settings ini FilterListPtr->LoadSettings(GEditorPerProjectIni, SettingsIniSection, SettingsString); PathViewPtr->LoadSettings(GEditorPerProjectIni, SettingsIniSection, SettingsString); FavoritePathViewPtr->LoadSettings(GEditorPerProjectIni, SettingsIniSection, SettingsString + TEXT(".Favorites")); CollectionViewPtr->LoadSettings(GEditorPerProjectIni, SettingsIniSection, SettingsString); AssetViewPtr->LoadSettings(GEditorPerProjectIni, SettingsIniSection, SettingsString); } void SContentBrowser::SourcesChanged(const TArray& SelectedPaths, const TArray& SelectedCollections) { FString NewSource = SelectedPaths.Num() > 0 ? SelectedPaths[0] : (SelectedCollections.Num() > 0 ? SelectedCollections[0].Name.ToString() : TEXT("None")); UE_LOG(LogContentBrowser, VeryVerbose, TEXT("The content browser source was changed by the sources view to '%s'"), *NewSource); FSourcesData SourcesData; { TArray SelectedPathNames; SelectedPathNames.Reserve(SelectedPaths.Num()); for (const FString& SelectedPath : SelectedPaths) { SelectedPathNames.Add(FName(*SelectedPath)); } SourcesData = FSourcesData(MoveTemp(SelectedPathNames), SelectedCollections); } // A dynamic collection should apply its search query to the CB search, so we need to stash the current search so that we can restore it again later if (SourcesData.IsDynamicCollection()) { // Only stash the user search term once in case we're switching between dynamic collections if (!StashedSearchBoxText.IsSet()) { StashedSearchBoxText = TextFilter->GetRawFilterText(); } FCollectionManagerModule& CollectionManagerModule = FCollectionManagerModule::GetModule(); const FCollectionNameType& DynamicCollection = SourcesData.Collections[0]; FString DynamicQueryString; CollectionManagerModule.Get().GetDynamicQueryText(DynamicCollection.Name, DynamicCollection.Type, DynamicQueryString); const FText DynamicQueryText = FText::FromString(DynamicQueryString); SetSearchBoxText(DynamicQueryText); SearchBoxPtr->SetText(DynamicQueryText); } else if (StashedSearchBoxText.IsSet()) { // Restore the stashed search term const FText StashedText = StashedSearchBoxText.GetValue(); StashedSearchBoxText.Reset(); SetSearchBoxText(StashedText); SearchBoxPtr->SetText(StashedText); } if (!AssetViewPtr->GetSourcesData().IsEmpty()) { // Update the current history data to preserve selection if there is a valid SourcesData HistoryManager.UpdateHistoryData(); } // Change the filter for the asset view AssetViewPtr->SetSourcesData(SourcesData); // Add a new history data now that the source has changed HistoryManager.AddHistoryData(); // Update the breadcrumb trail path UpdatePath(); } void SContentBrowser::FolderEntered(const FString& FolderPath) { // Have we entered a sub-collection folder? FName CollectionName; ECollectionShareType::Type CollectionFolderShareType = ECollectionShareType::CST_All; if (ContentBrowserUtils::IsCollectionPath(FolderPath, &CollectionName, &CollectionFolderShareType)) { const FCollectionNameType SelectedCollection(CollectionName, CollectionFolderShareType); TArray Collections; Collections.Add(SelectedCollection); CollectionViewPtr->SetSelectedCollections(Collections); CollectionSelected(SelectedCollection); } else { // set the path view to the incoming path TArray SelectedPaths; SelectedPaths.Add(FolderPath); PathViewPtr->SetSelectedPaths(SelectedPaths); PathSelected(SelectedPaths[0]); } } void SContentBrowser::PathSelected(const FString& FolderPath) { // You may not select both collections and paths CollectionViewPtr->ClearSelection(); TArray SelectedPaths = PathViewPtr->GetSelectedPaths(); // Selecting a folder shows it in the favorite list also FavoritePathViewPtr->SetSelectedPaths(SelectedPaths); TArray SelectedCollections; SourcesChanged(SelectedPaths, SelectedCollections); // Notify 'asset path changed' delegate FContentBrowserModule& ContentBrowserModule = FModuleManager::GetModuleChecked( TEXT("ContentBrowser") ); FContentBrowserModule::FOnAssetPathChanged& PathChangedDelegate = ContentBrowserModule.GetOnAssetPathChanged(); if(PathChangedDelegate.IsBound()) { PathChangedDelegate.Broadcast(FolderPath); } // Update the context menu's selected paths list PathContextMenu->SetSelectedPaths(SelectedPaths); } void SContentBrowser::FavoritePathSelected(const FString& FolderPath) { // You may not select both collections and paths CollectionViewPtr->ClearSelection(); TArray SelectedPaths = FavoritePathViewPtr->GetSelectedPaths(); // Selecting a favorite shows it in the main list also PathViewPtr->SetSelectedPaths(SelectedPaths); TArray SelectedCollections; SourcesChanged(SelectedPaths, SelectedCollections); // Notify 'asset path changed' delegate FContentBrowserModule& ContentBrowserModule = FModuleManager::GetModuleChecked(TEXT("ContentBrowser")); FContentBrowserModule::FOnAssetPathChanged& PathChangedDelegate = ContentBrowserModule.GetOnAssetPathChanged(); if (PathChangedDelegate.IsBound()) { PathChangedDelegate.Broadcast(FolderPath); } // Update the context menu's selected paths list PathContextMenu->SetSelectedPaths(SelectedPaths); } TSharedRef SContentBrowser::GetPathContextMenuExtender(const TArray& InSelectedPaths) const { return PathContextMenu->MakePathViewContextMenuExtender(InSelectedPaths); } void SContentBrowser::CollectionSelected(const FCollectionNameType& SelectedCollection) { // You may not select both collections and paths PathViewPtr->ClearSelection(); FavoritePathViewPtr->ClearSelection(); TArray SelectedCollections = CollectionViewPtr->GetSelectedCollections(); TArray SelectedPaths; if (SelectedCollections.Num() == 0) { // Select a dummy "None" collection to avoid the sources view switching to the paths view SelectedCollections.Add(FCollectionNameType(NAME_None, ECollectionShareType::CST_System)); } SourcesChanged(SelectedPaths, SelectedCollections); } void SContentBrowser::PathPickerPathSelected(const FString& FolderPath) { PathPickerButton->SetIsOpen(false); if ( !FolderPath.IsEmpty() ) { TArray Paths; Paths.Add(FolderPath); PathViewPtr->SetSelectedPaths(Paths); FavoritePathViewPtr->SetSelectedPaths(Paths); } PathSelected(FolderPath); } void SContentBrowser::SetSelectedPaths(const TArray& FolderPaths, bool bNeedsRefresh/* = false */) { if (FolderPaths.Num() > 0) { if (bNeedsRefresh) { PathViewPtr->Populate(); FavoritePathViewPtr->Populate(); } PathViewPtr->SetSelectedPaths(FolderPaths); FavoritePathViewPtr->SetSelectedPaths(FolderPaths); PathSelected(FolderPaths[0]); } } void SContentBrowser::ForceShowPluginContent(bool bEnginePlugin) { if (AssetViewPtr.IsValid()) { AssetViewPtr->ForceShowPluginFolder(bEnginePlugin); } } void SContentBrowser::PathPickerCollectionSelected(const FCollectionNameType& SelectedCollection) { PathPickerButton->SetIsOpen(false); TArray Collections; Collections.Add(SelectedCollection); CollectionViewPtr->SetSelectedCollections(Collections); CollectionSelected(SelectedCollection); } void SContentBrowser::OnApplyHistoryData( const FHistoryData& History ) { PathViewPtr->ApplyHistoryData(History); FavoritePathViewPtr->ApplyHistoryData(History); CollectionViewPtr->ApplyHistoryData(History); AssetViewPtr->ApplyHistoryData(History); // Update the breadcrumb trail path UpdatePath(); if (History.SourcesData.HasPackagePaths()) { // Notify 'asset path changed' delegate FContentBrowserModule& ContentBrowserModule = FModuleManager::GetModuleChecked(TEXT("ContentBrowser")); FContentBrowserModule::FOnAssetPathChanged& PathChangedDelegate = ContentBrowserModule.GetOnAssetPathChanged(); if (PathChangedDelegate.IsBound()) { PathChangedDelegate.Broadcast(History.SourcesData.PackagePaths[0].ToString()); } } } void SContentBrowser::OnUpdateHistoryData(FHistoryData& HistoryData) const { const FSourcesData& SourcesData = AssetViewPtr->GetSourcesData(); const TArray& SelectedAssets = AssetViewPtr->GetSelectedAssets(); const FText NewSource = SourcesData.HasPackagePaths() ? FText::FromName(SourcesData.PackagePaths[0]) : (SourcesData.HasCollections() ? FText::FromName(SourcesData.Collections[0].Name) : LOCTEXT("AllAssets", "All Assets")); HistoryData.HistoryDesc = NewSource; HistoryData.SourcesData = SourcesData; HistoryData.SelectionData.Reset(); HistoryData.SelectionData.SelectedFolders = TSet(AssetViewPtr->GetSelectedFolders()); for (const FAssetData& SelectedAsset : SelectedAssets) { HistoryData.SelectionData.SelectedAssets.Add(SelectedAsset.ObjectPath); } } void SContentBrowser::NewAssetRequested(const FString& SelectedPath, TWeakObjectPtr FactoryClass) { if ( ensure(SelectedPath.Len() > 0) && ensure(FactoryClass.IsValid()) ) { UFactory* NewFactory = NewObject(GetTransientPackage(), FactoryClass.Get()); // This factory may get gc'd as a side effect of various delegates potentially calling CollectGarbage so protect against it from being gc'd out from under us FGCObjectScopeGuard FactoryGCGuard(NewFactory); FEditorDelegates::OnConfigureNewAssetProperties.Broadcast(NewFactory); if ( NewFactory->ConfigureProperties() ) { FString DefaultAssetName; FString PackageNameToUse; static FName AssetToolsModuleName = FName("AssetTools"); FAssetToolsModule& AssetToolsModule = FModuleManager::GetModuleChecked(AssetToolsModuleName); AssetToolsModule.Get().CreateUniqueAssetName(SelectedPath + TEXT("/") + NewFactory->GetDefaultNewAssetName(), TEXT(""), PackageNameToUse, DefaultAssetName); CreateNewAsset(DefaultAssetName, SelectedPath, NewFactory->GetSupportedClass(), NewFactory); } } } void SContentBrowser::NewClassRequested(const FString& SelectedPath) { // Parse out the on disk location for the currently selected path, this will then be used as the default location for the new class (if a valid project module location) FString ExistingFolderPath; if (!SelectedPath.IsEmpty()) { TSharedRef NativeClassHierarchy = FContentBrowserSingleton::Get().GetNativeClassHierarchy(); NativeClassHierarchy->GetFileSystemPath(SelectedPath, ExistingFolderPath); } FGameProjectGenerationModule::Get().OpenAddCodeToProjectDialog( FAddToProjectConfig() .InitialPath(ExistingFolderPath) .ParentWindow(FGlobalTabmanager::Get()->GetRootWindow()) ); } void SContentBrowser::NewFolderRequested(const FString& SelectedPath) { if( ensure(SelectedPath.Len() > 0) && AssetViewPtr.IsValid() ) { CreateNewFolder(SelectedPath, FOnCreateNewFolder::CreateSP(AssetViewPtr.Get(), &SAssetView::OnCreateNewFolder)); } } void SContentBrowser::SetSearchBoxText(const FText& InSearchText) { // Has anything changed? (need to test case as the operators are case-sensitive) if (!InSearchText.ToString().Equals(TextFilter->GetRawFilterText().ToString(), ESearchCase::CaseSensitive)) { TextFilter->SetRawFilterText( InSearchText ); SearchBoxPtr->SetError( TextFilter->GetFilterErrorText() ); if(InSearchText.IsEmpty()) { FrontendFilters->Remove(TextFilter); AssetViewPtr->SetUserSearching(false); } else { FrontendFilters->Add(TextFilter); AssetViewPtr->SetUserSearching(true); } } } void SContentBrowser::OnSearchBoxChanged(const FText& InSearchText) { SetSearchBoxText(InSearchText); // Broadcast 'search box changed' delegate FContentBrowserModule& ContentBrowserModule = FModuleManager::GetModuleChecked( TEXT("ContentBrowser") ); ContentBrowserModule.GetOnSearchBoxChanged().Broadcast(InSearchText, bIsPrimaryBrowser); } void SContentBrowser::OnSearchBoxCommitted(const FText& InSearchText, ETextCommit::Type CommitInfo) { SetSearchBoxText(InSearchText); } bool SContentBrowser::IsSaveSearchButtonEnabled() const { return !TextFilter->GetRawFilterText().IsEmptyOrWhitespace(); } FReply SContentBrowser::OnSaveSearchButtonClicked() { // Need to make sure we can see the collections view if (!bSourcesViewExpanded) { SourcesViewExpandClicked(); } if (!GetDefault()->GetDockCollections() && ActiveSourcesWidgetIndex != ContentBrowserSourcesWidgetSwitcherIndex::CollectionsView) { ActiveSourcesWidgetIndex = ContentBrowserSourcesWidgetSwitcherIndex::CollectionsView; SourcesWidgetSwitcher->SetActiveWidgetIndex(ActiveSourcesWidgetIndex); } // We want to add any currently selected paths to the final saved query so that you get back roughly the same list of objects as what you're currently seeing FString SelectedPathsQuery; { const FSourcesData& SourcesData = AssetViewPtr->GetSourcesData(); for (int32 SelectedPathIndex = 0; SelectedPathIndex < SourcesData.PackagePaths.Num(); ++SelectedPathIndex) { SelectedPathsQuery.Append(TEXT("Path:'")); SelectedPathsQuery.Append(SourcesData.PackagePaths[SelectedPathIndex].ToString()); SelectedPathsQuery.Append(TEXT("'...")); if (SelectedPathIndex + 1 < SourcesData.PackagePaths.Num()) { SelectedPathsQuery.Append(TEXT(" OR ")); } } } // todo: should we automatically append any type filters too? // Produce the final query FText FinalQueryText; if (SelectedPathsQuery.IsEmpty()) { FinalQueryText = TextFilter->GetRawFilterText(); } else { FinalQueryText = FText::FromString(FString::Printf(TEXT("(%s) AND (%s)"), *TextFilter->GetRawFilterText().ToString(), *SelectedPathsQuery)); } CollectionViewPtr->MakeSaveDynamicCollectionMenu(FinalQueryText); return FReply::Handled(); } void SContentBrowser::OnPathClicked( const FString& CrumbData ) { FSourcesData SourcesData = AssetViewPtr->GetSourcesData(); if ( SourcesData.HasCollections() ) { // Collection crumb was clicked. See if we've clicked on a different collection in the hierarchy, and change the path if required. TOptional CollectionClicked; { FString CollectionName; FString CollectionTypeString; if (CrumbData.Split(TEXT("?"), &CollectionName, &CollectionTypeString)) { const int32 CollectionType = FCString::Atoi(*CollectionTypeString); if (CollectionType >= 0 && CollectionType < ECollectionShareType::CST_All) { CollectionClicked = FCollectionNameType(FName(*CollectionName), ECollectionShareType::Type(CollectionType)); } } } if ( CollectionClicked.IsSet() && SourcesData.Collections[0] != CollectionClicked.GetValue() ) { TArray Collections; Collections.Add(CollectionClicked.GetValue()); CollectionViewPtr->SetSelectedCollections(Collections); CollectionSelected(CollectionClicked.GetValue()); } } else if ( !SourcesData.HasPackagePaths() ) { // No collections or paths are selected. This is "All Assets". Don't change the path when this is clicked. } else if ( SourcesData.PackagePaths.Num() > 1 || SourcesData.PackagePaths[0].ToString() != CrumbData ) { // More than one path is selected or the crumb that was clicked is not the same path as the current one. Change the path. TArray SelectedPaths; SelectedPaths.Add(CrumbData); PathViewPtr->SetSelectedPaths(SelectedPaths); FavoritePathViewPtr->SetSelectedPaths(SelectedPaths); PathSelected(SelectedPaths[0]); } } void SContentBrowser::OnPathMenuItemClicked(FString ClickedPath) { OnPathClicked( ClickedPath ); } bool SContentBrowser::OnHasCrumbDelimiterContent(const FString& CrumbData) const { const FSourcesData SourcesData = AssetViewPtr->GetSourcesData(); if (SourcesData.HasCollections()) { TOptional CollectionClicked; { FString CollectionName; FString CollectionTypeString; if (CrumbData.Split(TEXT("?"), &CollectionName, &CollectionTypeString)) { const int32 CollectionType = FCString::Atoi(*CollectionTypeString); if (CollectionType >= 0 && CollectionType < ECollectionShareType::CST_All) { CollectionClicked = FCollectionNameType(FName(*CollectionName), ECollectionShareType::Type(CollectionType)); } } } TArray ChildCollections; if (CollectionClicked.IsSet()) { FCollectionManagerModule& CollectionManagerModule = FCollectionManagerModule::GetModule(); CollectionManagerModule.Get().GetChildCollections(CollectionClicked->Name, CollectionClicked->Type, ChildCollections); } return (ChildCollections.Num() > 0); } else if (SourcesData.HasPackagePaths()) { TArray SubPaths; const bool bRecurse = false; if (ContentBrowserUtils::IsClassPath(CrumbData)) { TSharedRef NativeClassHierarchy = FContentBrowserSingleton::Get().GetNativeClassHierarchy(); FNativeClassHierarchyFilter ClassFilter; ClassFilter.ClassPaths.Add(FName(*CrumbData)); ClassFilter.bRecursivePaths = bRecurse; NativeClassHierarchy->GetMatchingFolders(ClassFilter, SubPaths); } else { FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked(TEXT("AssetRegistry")); IAssetRegistry& AssetRegistry = AssetRegistryModule.Get(); AssetRegistry.GetSubPaths(CrumbData, SubPaths, bRecurse); } return (SubPaths.Num() > 0); } return false; } TSharedRef SContentBrowser::OnGetCrumbDelimiterContent(const FString& CrumbData) const { FSourcesData SourcesData = AssetViewPtr->GetSourcesData(); TSharedPtr Widget = SNullWidget::NullWidget; TSharedPtr MenuWidget; if( SourcesData.HasCollections() ) { TOptional CollectionClicked; { FString CollectionName; FString CollectionTypeString; if (CrumbData.Split(TEXT("?"), &CollectionName, &CollectionTypeString)) { const int32 CollectionType = FCString::Atoi(*CollectionTypeString); if (CollectionType >= 0 && CollectionType < ECollectionShareType::CST_All) { CollectionClicked = FCollectionNameType(FName(*CollectionName), ECollectionShareType::Type(CollectionType)); } } } if( CollectionClicked.IsSet() ) { FCollectionManagerModule& CollectionManagerModule = FCollectionManagerModule::GetModule(); TArray ChildCollections; CollectionManagerModule.Get().GetChildCollections(CollectionClicked->Name, CollectionClicked->Type, ChildCollections); if( ChildCollections.Num() > 0 ) { FMenuBuilder MenuBuilder( true, nullptr ); for( const FCollectionNameType& ChildCollection : ChildCollections ) { const FString ChildCollectionCrumbData = FString::Printf(TEXT("%s?%s"), *ChildCollection.Name.ToString(), *FString::FromInt(ChildCollection.Type)); MenuBuilder.AddMenuEntry( FText::FromName(ChildCollection.Name), FText::GetEmpty(), FSlateIcon(FEditorStyle::GetStyleSetName(), ECollectionShareType::GetIconStyleName(ChildCollection.Type)), FUIAction(FExecuteAction::CreateSP(const_cast(this), &SContentBrowser::OnPathMenuItemClicked, ChildCollectionCrumbData)) ); } MenuWidget = MenuBuilder.MakeWidget(); } } } else if( SourcesData.HasPackagePaths() ) { TArray SubPaths; const bool bRecurse = false; if( ContentBrowserUtils::IsClassPath(CrumbData) ) { TSharedRef NativeClassHierarchy = FContentBrowserSingleton::Get().GetNativeClassHierarchy(); FNativeClassHierarchyFilter ClassFilter; ClassFilter.ClassPaths.Add(FName(*CrumbData)); ClassFilter.bRecursivePaths = bRecurse; NativeClassHierarchy->GetMatchingFolders(ClassFilter, SubPaths); } else { FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked(TEXT("AssetRegistry")); IAssetRegistry& AssetRegistry = AssetRegistryModule.Get(); AssetRegistry.GetSubPaths( CrumbData, SubPaths, bRecurse ); FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked("AssetTools"); TSharedRef FolderBlacklist = AssetToolsModule.Get().GetFolderBlacklist(); if (FolderBlacklist->HasFiltering()) { SubPaths.RemoveAll([FolderBlacklist](const FString& SubPath) { return !FolderBlacklist->PassesStartsWithFilter(SubPath); }); } } if( SubPaths.Num() > 0 ) { FMenuBuilder MenuBuilder( true, nullptr ); for( int32 PathIndex = 0; PathIndex < SubPaths.Num(); ++PathIndex ) { const FString& SubPath = SubPaths[PathIndex]; // For displaying in the menu cut off the parent path since it is redundant FString PathWithoutParent = SubPath.RightChop( CrumbData.Len() + 1 ); MenuBuilder.AddMenuEntry( FText::FromString(PathWithoutParent), FText::GetEmpty(), FSlateIcon(FEditorStyle::GetStyleSetName(), "ContentBrowser.BreadcrumbPathPickerFolder"), FUIAction(FExecuteAction::CreateSP(const_cast(this), &SContentBrowser::OnPathMenuItemClicked, SubPath)) ); } MenuWidget = MenuBuilder.MakeWidget(); } } if( MenuWidget.IsValid() ) { // Do not allow the menu to become too large if there are many directories Widget = SNew( SVerticalBox ) +SVerticalBox::Slot() .MaxHeight( 400.0f ) [ MenuWidget.ToSharedRef() ]; } return Widget.ToSharedRef(); } TSharedRef SContentBrowser::GetPathPickerContent() { FPathPickerConfig PathPickerConfig; FSourcesData SourcesData = AssetViewPtr->GetSourcesData(); if ( SourcesData.HasPackagePaths() ) { PathPickerConfig.DefaultPath = SourcesData.PackagePaths[0].ToString(); } PathPickerConfig.OnPathSelected = FOnPathSelected::CreateSP(this, &SContentBrowser::PathPickerPathSelected); PathPickerConfig.bAllowContextMenu = false; PathPickerConfig.bAllowClassesFolder = true; return SNew(SBox) .WidthOverride(300) .HeightOverride(500) .Padding(4) [ SNew(SVerticalBox) // Path Picker +SVerticalBox::Slot() .FillHeight(1.f) [ FContentBrowserSingleton::Get().CreatePathPicker(PathPickerConfig) ] // Collection View +SVerticalBox::Slot() .AutoHeight() .Padding(0, 6, 0, 0) [ SNew(SCollectionView) .AllowCollectionButtons(false) .OnCollectionSelected(this, &SContentBrowser::PathPickerCollectionSelected) .AllowContextMenu(false) ] ]; } FString SContentBrowser::GetCurrentPath() const { FString CurrentPath; const FSourcesData& SourcesData = AssetViewPtr->GetSourcesData(); if ( SourcesData.HasPackagePaths() && SourcesData.PackagePaths[0] != NAME_None ) { CurrentPath = SourcesData.PackagePaths[0].ToString(); } return CurrentPath; } TSharedRef SContentBrowser::MakeAddNewContextMenu(bool bShowGetContent, bool bShowImport) { if (!UToolMenus::Get()->IsMenuRegistered("ContentBrowser.AddNewContextMenu")) { UToolMenu* Menu = UToolMenus::Get()->RegisterMenu("ContentBrowser.AddNewContextMenu"); Menu->AddDynamicSection("DynamicSection", FNewToolMenuDelegate::CreateLambda([](UToolMenu* InMenu) { UContentBrowserAddNewContextMenuContext* Context = InMenu->FindContext(); if (Context && Context->ContentBrowser.IsValid()) { Context->ContentBrowser.Pin()->PopulateAddNewContextMenu(InMenu, Context->bShowGetContent, Context->bShowImport, Context->NumAssetPaths); } })); } const FSourcesData& SourcesData = AssetViewPtr->GetSourcesData(); int32 NumAssetPaths, NumClassPaths; ContentBrowserUtils::CountPathTypes(SourcesData.PackagePaths, NumAssetPaths, NumClassPaths); // Get all menu extenders for this context menu from the content browser module FContentBrowserModule& ContentBrowserModule = FModuleManager::GetModuleChecked( TEXT("ContentBrowser") ); TArray MenuExtenderDelegates = ContentBrowserModule.GetAllAssetContextMenuExtenders(); // Delegate wants paths as FStrings TArray SelectPaths; for (FName PathName: SourcesData.PackagePaths) { SelectPaths.Add(PathName.ToString()); } TArray> Extenders; for (int32 i = 0; i < MenuExtenderDelegates.Num(); ++i) { if (MenuExtenderDelegates[i].IsBound()) { Extenders.Add(MenuExtenderDelegates[i].Execute(SelectPaths)); } } TSharedPtr MenuExtender = FExtender::Combine(Extenders); UContentBrowserAddNewContextMenuContext* ContextObject = NewObject(); ContextObject->ContentBrowser = SharedThis(this); ContextObject->NumAssetPaths = NumAssetPaths; ContextObject->bShowGetContent = bShowGetContent; ContextObject->bShowImport = bShowImport; FToolMenuContext ToolMenuContext(nullptr, MenuExtender, ContextObject); FDisplayMetrics DisplayMetrics; FSlateApplication::Get().GetCachedDisplayMetrics( DisplayMetrics ); const FVector2D DisplaySize( DisplayMetrics.PrimaryDisplayWorkAreaRect.Right - DisplayMetrics.PrimaryDisplayWorkAreaRect.Left, DisplayMetrics.PrimaryDisplayWorkAreaRect.Bottom - DisplayMetrics.PrimaryDisplayWorkAreaRect.Top ); return SNew(SVerticalBox) +SVerticalBox::Slot() .MaxHeight(DisplaySize.Y * 0.9) [ UToolMenus::Get()->GenerateWidget("ContentBrowser.AddNewContextMenu", ToolMenuContext) ]; } void SContentBrowser::PopulateAddNewContextMenu(class UToolMenu* Menu, bool bShowGetContent, bool bShowImport, const int32 NumAssetPaths) { const FSourcesData& SourcesData = AssetViewPtr->GetSourcesData(); // Only add "New Folder" item if we do not have a collection selected FNewAssetOrClassContextMenu::FOnNewFolderRequested OnNewFolderRequested; if (CollectionViewPtr->GetSelectedCollections().Num() == 0) { OnNewFolderRequested = FNewAssetOrClassContextMenu::FOnNewFolderRequested::CreateSP(this, &SContentBrowser::NewFolderRequested); } // New feature packs don't depend on the current paths, so we always add this item if it was requested FNewAssetOrClassContextMenu::FOnGetContentRequested OnGetContentRequested; if(bShowGetContent) { OnGetContentRequested = FNewAssetOrClassContextMenu::FOnGetContentRequested::CreateSP(this, &SContentBrowser::OnAddContentRequested); } // Only the asset items if we have an asset path selected FNewAssetOrClassContextMenu::FOnNewAssetRequested OnNewAssetRequested; FNewAssetOrClassContextMenu::FOnImportAssetRequested OnImportAssetRequested; if(NumAssetPaths > 0) { OnNewAssetRequested = FNewAssetOrClassContextMenu::FOnNewAssetRequested::CreateSP(this, &SContentBrowser::NewAssetRequested); if(bShowImport) { OnImportAssetRequested = FNewAssetOrClassContextMenu::FOnImportAssetRequested::CreateSP(this, &SContentBrowser::ImportAsset); } } // This menu always lets you create classes, but uses your default project source folder if the selected path is invalid for creating classes FNewAssetOrClassContextMenu::FOnNewClassRequested OnNewClassRequested = FNewAssetOrClassContextMenu::FOnNewClassRequested::CreateSP(this, &SContentBrowser::NewClassRequested); FNewAssetOrClassContextMenu::MakeContextMenu( Menu, SourcesData.PackagePaths, OnNewAssetRequested, OnNewClassRequested, OnNewFolderRequested, OnImportAssetRequested, OnGetContentRequested ); } bool SContentBrowser::IsAddNewEnabled() const { const FSourcesData& SourcesData = AssetViewPtr->GetSourcesData(); return SourcesData.PackagePaths.Num() == 1; } FText SContentBrowser::GetAddNewToolTipText() const { const FSourcesData& SourcesData = AssetViewPtr->GetSourcesData(); if ( SourcesData.PackagePaths.Num() == 1 ) { const FString CurrentPath = SourcesData.PackagePaths[0].ToString(); if ( ContentBrowserUtils::IsClassPath( CurrentPath ) ) { return FText::Format( LOCTEXT("AddNewToolTip_AddNewClass", "Create a new class in {0}..."), FText::FromString(CurrentPath) ); } else { return FText::Format( LOCTEXT("AddNewToolTip_AddNewAsset", "Create a new asset in {0}..."), FText::FromString(CurrentPath) ); } } else if ( SourcesData.PackagePaths.Num() > 1 ) { return LOCTEXT( "AddNewToolTip_MultiplePaths", "Cannot add assets or classes to multiple paths." ); } return LOCTEXT( "AddNewToolTip_NoPath", "No path is selected as an add target." ); } TSharedRef SContentBrowser::MakeAddFilterMenu() { return FilterListPtr->ExternalMakeAddFilterMenu(); } TSharedPtr SContentBrowser::GetFilterContextMenu() { return FilterListPtr->ExternalMakeAddFilterMenu(); } FReply SContentBrowser::OnSaveClicked() { ContentBrowserUtils::SaveDirtyPackages(); return FReply::Handled(); } void SContentBrowser::OnAddContentRequested() { IAddContentDialogModule& AddContentDialogModule = FModuleManager::LoadModuleChecked("AddContentDialog"); FWidgetPath WidgetPath; FSlateApplication::Get().GeneratePathToWidgetChecked(AsShared(), WidgetPath); AddContentDialogModule.ShowDialog(WidgetPath.GetWindow()); } void SContentBrowser::OnAssetSelectionChanged(const FAssetData& SelectedAsset) { if ( bIsPrimaryBrowser ) { SyncGlobalSelectionSet(); } // Notify 'asset selection changed' delegate FContentBrowserModule& ContentBrowserModule = FModuleManager::GetModuleChecked( TEXT("ContentBrowser") ); FContentBrowserModule::FOnAssetSelectionChanged& AssetSelectionChangedDelegate = ContentBrowserModule.GetOnAssetSelectionChanged(); const TArray& SelectedAssets = AssetViewPtr->GetSelectedAssets(); AssetContextMenu->SetSelectedAssets(SelectedAssets); CollectionViewPtr->SetSelectedAssets(SelectedAssets); if(AssetSelectionChangedDelegate.IsBound()) { AssetSelectionChangedDelegate.Broadcast(SelectedAssets, bIsPrimaryBrowser); } } void SContentBrowser::OnAssetsActivated(const TArray& ActivatedAssets, EAssetTypeActivationMethod::Type ActivationMethod) { TMap< TSharedRef, TArray > TypeActionsToObjects; TArray ObjectsWithoutTypeActions; const FText LoadingTemplate = LOCTEXT("LoadingAssetName", "Loading {0}..."); const FText DefaultText = ActivatedAssets.Num() == 1 ? FText::Format(LoadingTemplate, FText::FromName(ActivatedAssets[0].AssetName)) : LOCTEXT("LoadingObjects", "Loading Objects..."); FScopedSlowTask SlowTask(100, DefaultText); // Iterate over all activated assets to map them to AssetTypeActions. // This way individual asset type actions will get a batched list of assets to operate on for ( auto AssetIt = ActivatedAssets.CreateConstIterator(); AssetIt; ++AssetIt ) { const FAssetData& AssetData = *AssetIt; if (!AssetData.IsAssetLoaded() && FEditorFileUtils::IsMapPackageAsset(AssetData.ObjectPath.ToString())) { SlowTask.MakeDialog(); } SlowTask.EnterProgressFrame(75.f/ActivatedAssets.Num(), FText::Format(LoadingTemplate, FText::FromName(AssetData.AssetName))); UObject* Asset = (*AssetIt).GetAsset(); if ( Asset != NULL ) { FAssetToolsModule& AssetToolsModule = FModuleManager::Get().LoadModuleChecked("AssetTools"); TWeakPtr AssetTypeActions = AssetToolsModule.Get().GetAssetTypeActionsForClass(Asset->GetClass()); if ( AssetTypeActions.IsValid() ) { // Add this asset to the list associated with the asset type action object TArray& ObjList = TypeActionsToObjects.FindOrAdd(AssetTypeActions.Pin().ToSharedRef()); ObjList.AddUnique(Asset); } else { ObjectsWithoutTypeActions.AddUnique(Asset); } } } // Now that we have created our map, activate all the lists of objects for each asset type action. for ( auto TypeActionsIt = TypeActionsToObjects.CreateConstIterator(); TypeActionsIt; ++TypeActionsIt ) { SlowTask.EnterProgressFrame(25.f/TypeActionsToObjects.Num()); const TSharedRef& TypeActions = TypeActionsIt.Key(); const TArray& ObjList = TypeActionsIt.Value(); if (!TypeActions->AssetsActivatedOverride(ObjList, ActivationMethod)) { if (ActivationMethod == EAssetTypeActivationMethod::DoubleClicked || ActivationMethod == EAssetTypeActivationMethod::Opened) { if (ObjList.Num() == 1) { GEditor->GetEditorSubsystem()->OpenEditorForAsset(ObjList[0]); } else if (ObjList.Num() > 1) { GEditor->GetEditorSubsystem()->OpenEditorForAssets(ObjList); } } } } // Finally, open a simple asset editor for all assets which do not have asset type actions if activating with enter or double click if ( ActivationMethod == EAssetTypeActivationMethod::DoubleClicked || ActivationMethod == EAssetTypeActivationMethod::Opened ) { ContentBrowserUtils::OpenEditorForAsset(ObjectsWithoutTypeActions); } } TSharedPtr SContentBrowser::OnGetAssetContextMenu(const TArray& SelectedAssets) { if ( SelectedAssets.Num() == 0 ) { return MakeAddNewContextMenu( false, true ); } else { return AssetContextMenu->MakeContextMenu(SelectedAssets, AssetViewPtr->GetSourcesData(), Commands); } } FReply SContentBrowser::ToggleLockClicked() { bIsLocked = !bIsLocked; return FReply::Handled(); } const FSlateBrush* SContentBrowser::GetToggleLockImage() const { if ( bIsLocked ) { return FEditorStyle::GetBrush("ContentBrowser.LockButton_Locked"); } else { return FEditorStyle::GetBrush("ContentBrowser.LockButton_Unlocked"); } } EVisibility SContentBrowser::GetSourcesViewVisibility() const { return bSourcesViewExpanded ? EVisibility::Visible : EVisibility::Collapsed; } const FSlateBrush* SContentBrowser::GetSourcesToggleImage() const { if ( bSourcesViewExpanded ) { return FEditorStyle::GetBrush("ContentBrowser.HideSourcesView"); } else { return FEditorStyle::GetBrush("ContentBrowser.ShowSourcesView"); } } FReply SContentBrowser::SourcesViewExpandClicked() { bSourcesViewExpanded = !bSourcesViewExpanded; // Notify 'Sources View Expanded' delegate FContentBrowserModule& ContentBrowserModule = FModuleManager::GetModuleChecked( TEXT("ContentBrowser") ); FContentBrowserModule::FOnSourcesViewChanged& SourcesViewChangedDelegate = ContentBrowserModule.GetOnSourcesViewChanged(); if(SourcesViewChangedDelegate.IsBound()) { SourcesViewChangedDelegate.Broadcast(bSourcesViewExpanded); } return FReply::Handled(); } EVisibility SContentBrowser::GetPathExpanderVisibility() const { return bSourcesViewExpanded ? EVisibility::Collapsed : EVisibility::Visible; } EVisibility SContentBrowser::GetSourcesSwitcherVisibility() const { return GetDefault()->GetDockCollections() ? EVisibility::Collapsed : EVisibility::Visible; } const FSlateBrush* SContentBrowser::GetSourcesSwitcherIcon() const { switch (ActiveSourcesWidgetIndex) { case ContentBrowserSourcesWidgetSwitcherIndex::PathView: return FEditorStyle::GetBrush("ContentBrowser.Sources.Collections"); case ContentBrowserSourcesWidgetSwitcherIndex::CollectionsView: return FEditorStyle::GetBrush("ContentBrowser.Sources.Paths"); default: break; } check(false); return nullptr; } FText SContentBrowser::GetSourcesSwitcherToolTipText() const { switch (ActiveSourcesWidgetIndex) { case ContentBrowserSourcesWidgetSwitcherIndex::PathView: return LOCTEXT("SwitchToCollectionView_ToolTip", "Switch to the Collections view"); case ContentBrowserSourcesWidgetSwitcherIndex::CollectionsView: return LOCTEXT("SwitchToPathView_ToolTip", "Switch to the Paths view"); default: break; } check(false); return FText(); } FReply SContentBrowser::OnSourcesSwitcherClicked() { // This only works because we only have two switcher types ActiveSourcesWidgetIndex = !ActiveSourcesWidgetIndex; SourcesWidgetSwitcher->SetActiveWidgetIndex(ActiveSourcesWidgetIndex); return FReply::Handled(); } FText SContentBrowser::GetSourcesSearchHintText() const { switch (ActiveSourcesWidgetIndex) { case ContentBrowserSourcesWidgetSwitcherIndex::PathView: return LOCTEXT("SearchPathsHint", "Search Paths"); case ContentBrowserSourcesWidgetSwitcherIndex::CollectionsView: return LOCTEXT("SearchCollectionsHint", "Search Collections"); default: break; } check(false); return FText(); } void SContentBrowser::OnContentBrowserSettingsChanged(FName PropertyName) { const FName NAME_DockCollections = "DockCollections";//GET_MEMBER_NAME_CHECKED(UContentBrowserSettings, DockCollections); // Doesn't work as DockCollections is private :( if (PropertyName.IsNone() || PropertyName == NAME_DockCollections) { // Ensure the omni-search is enabled correctly CollectionViewPtr->SetAllowExternalSearch(!GetDefault()->GetDockCollections()); // Ensure the path is set to the correct view mode UpdatePath(); } } FReply SContentBrowser::BackClicked() { HistoryManager.GoBack(); return FReply::Handled(); } FReply SContentBrowser::ForwardClicked() { HistoryManager.GoForward(); return FReply::Handled(); } bool SContentBrowser::HandleRenameCommandCanExecute() const { const TArray>& SelectedItems = AssetViewPtr->GetSelectedItems(); if (SelectedItems.Num() > 0) { return AssetContextMenu->CanExecuteRename(); } else { const TArray& SelectedPaths = PathViewPtr->GetSelectedPaths(); if (SelectedPaths.Num() > 0) { return PathContextMenu->CanExecuteRename(); } } return false; } bool SContentBrowser::HandleSaveAssetCommandCanExecute() const { const TArray>& SelectedItems = AssetViewPtr->GetSelectedItems(); if (SelectedItems.Num() > 0 && !AssetViewPtr->IsRenamingAsset()) { return AssetContextMenu->CanExecuteSaveAsset(); } return false; } void SContentBrowser::HandleSaveAllCurrentFolderCommand() const { PathContextMenu->ExecuteSaveFolder(); } void SContentBrowser::HandleResaveAllCurrentFolderCommand() const { PathContextMenu->ExecuteResaveFolder(); } void SContentBrowser::HandleRenameCommand() { const TArray>& SelectedItems = AssetViewPtr->GetSelectedItems(); if (SelectedItems.Num() > 0) { AssetContextMenu->ExecuteRename(); } else { const TArray& SelectedPaths = PathViewPtr->GetSelectedPaths(); if (SelectedPaths.Num() > 0) { PathContextMenu->ExecuteRename(); } } } void SContentBrowser::HandleSaveAssetCommand() { const TArray>& SelectedItems = AssetViewPtr->GetSelectedItems(); if (SelectedItems.Num() > 0) { AssetContextMenu->ExecuteSaveAsset(); } } bool SContentBrowser::HandleDeleteCommandCanExecute() const { if (IVREditorModule::Get().IsVREditorModeActive()) { return false; } const TArray>& SelectedItems = AssetViewPtr->GetSelectedItems(); if (SelectedItems.Num() > 0) { return AssetContextMenu->CanExecuteDelete(); } else { const TArray& SelectedPaths = PathViewPtr->GetSelectedPaths(); if (SelectedPaths.Num() > 0) { return PathContextMenu->CanExecuteDelete(); } } return false; } void SContentBrowser::HandleDeleteCommandExecute() { if (PathViewPtr->HasFocusedDescendants()) { const TArray& SelectedPaths = PathViewPtr->GetSelectedPaths(); if (SelectedPaths.Num() > 0) { PathContextMenu->ExecuteDelete(); } } else { const TArray>& SelectedItems = AssetViewPtr->GetSelectedItems(); if (SelectedItems.Num() > 0) { AssetContextMenu->ExecuteDelete(); } else { const TArray& SelectedPaths = PathViewPtr->GetSelectedPaths(); if (SelectedPaths.Num() > 0) { PathContextMenu->ExecuteDelete(); } } } } void SContentBrowser::HandleOpenAssetsOrFoldersCommandExecute() { AssetViewPtr->OnOpenAssetsOrFolders(); } void SContentBrowser::HandlePreviewAssetsCommandExecute() { AssetViewPtr->OnPreviewAssets(); } void SContentBrowser::HandleCreateNewFolderCommandExecute() { TArray SelectedPaths = PathViewPtr->GetSelectedPaths(); // only create folders when a single path is selected const bool bCanCreateNewFolder = (SelectedPaths.Num() == 1) && ContentBrowserUtils::IsValidPathToCreateNewFolder(SelectedPaths[0]); if (bCanCreateNewFolder) { CreateNewFolder( SelectedPaths.Num() > 0 ? SelectedPaths[0] : FString(), FOnCreateNewFolder::CreateSP(AssetViewPtr.Get(), &SAssetView::OnCreateNewFolder)); } } void SContentBrowser::HandleDirectoryUpCommandExecute() { TArray SelectedPaths = PathViewPtr->GetSelectedPaths(); if(SelectedPaths.Num() == 1 && !ContentBrowserUtils::IsRootDir(SelectedPaths[0])) { FString ParentDir = SelectedPaths[0] / TEXT(".."); FPaths::CollapseRelativeDirectories(ParentDir); FolderEntered(ParentDir); } } void SContentBrowser::GetSelectionState(TArray& SelectedAssets, TArray& SelectedPaths) { SelectedAssets.Reset(); SelectedPaths.Reset(); if (AssetViewPtr->HasAnyUserFocusOrFocusedDescendants()) { SelectedAssets = AssetViewPtr->GetSelectedAssets(); SelectedPaths = AssetViewPtr->GetSelectedFolders(); } else if (PathViewPtr->HasAnyUserFocusOrFocusedDescendants()) { SelectedPaths = PathViewPtr->GetSelectedPaths(); } } bool SContentBrowser::IsBackEnabled() const { return HistoryManager.CanGoBack(); } bool SContentBrowser::IsForwardEnabled() const { return HistoryManager.CanGoForward(); } bool SContentBrowser::CanExecuteDirectoryUp() const { TArray SelectedPaths = PathViewPtr->GetSelectedPaths(); return (SelectedPaths.Num() == 1 && !ContentBrowserUtils::IsRootDir(SelectedPaths[0])); } FText SContentBrowser::GetHistoryBackTooltip() const { if ( HistoryManager.CanGoBack() ) { return FText::Format( LOCTEXT("HistoryBackTooltipFmt", "Back to {0}"), HistoryManager.GetBackDesc() ); } return FText::GetEmpty(); } FText SContentBrowser::GetHistoryForwardTooltip() const { if ( HistoryManager.CanGoForward() ) { return FText::Format( LOCTEXT("HistoryForwardTooltipFmt", "Forward to {0}"), HistoryManager.GetForwardDesc() ); } return FText::GetEmpty(); } FText SContentBrowser::GetDirectoryUpTooltip() const { TArray SelectedPaths = PathViewPtr->GetSelectedPaths(); if(SelectedPaths.Num() == 1 && !ContentBrowserUtils::IsRootDir(SelectedPaths[0])) { FString ParentDir = SelectedPaths[0] / TEXT(".."); FPaths::CollapseRelativeDirectories(ParentDir); return FText::Format(LOCTEXT("DirectoryUpTooltip", "Up to {0}"), FText::FromString(ParentDir) ); } return FText(); } EVisibility SContentBrowser::GetDirectoryUpToolTipVisibility() const { EVisibility ToolTipVisibility = EVisibility::Collapsed; // if we have text in our tooltip, make it visible. if(GetDirectoryUpTooltip().IsEmpty() == false) { ToolTipVisibility = EVisibility::Visible; } return ToolTipVisibility; } void SContentBrowser::SyncGlobalSelectionSet() { USelection* EditorSelection = GEditor->GetSelectedObjects(); if ( !ensure( EditorSelection != NULL ) ) { return; } // Get the selected assets in the asset view const TArray& SelectedAssets = AssetViewPtr->GetSelectedAssets(); EditorSelection->BeginBatchSelectOperation(); { TSet< UObject* > SelectedObjects; // Lets see what the user has selected and add any new selected objects to the global selection set for ( auto AssetIt = SelectedAssets.CreateConstIterator(); AssetIt; ++AssetIt ) { // Grab the object if it is loaded if ( (*AssetIt).IsAssetLoaded() ) { UObject* FoundObject = (*AssetIt).GetAsset(); if( FoundObject != NULL && FoundObject->GetClass() != UObjectRedirector::StaticClass() ) { SelectedObjects.Add( FoundObject ); // Select this object! EditorSelection->Select( FoundObject ); } } } // Now we'll build a list of objects that need to be removed from the global selection set for( int32 CurEditorObjectIndex = 0; CurEditorObjectIndex < EditorSelection->Num(); ++CurEditorObjectIndex ) { UObject* CurEditorObject = EditorSelection->GetSelectedObject( CurEditorObjectIndex ); if( CurEditorObject != NULL ) { if( !SelectedObjects.Contains( CurEditorObject ) ) { EditorSelection->Deselect( CurEditorObject ); } } } } EditorSelection->EndBatchSelectOperation(); } void SContentBrowser::UpdatePath() { FSourcesData SourcesData = AssetViewPtr->GetSourcesData(); PathBreadcrumbTrail->ClearCrumbs(); int32 NewSourcesWidgetIndex = ActiveSourcesWidgetIndex; if ( SourcesData.HasPackagePaths() ) { NewSourcesWidgetIndex = ContentBrowserSourcesWidgetSwitcherIndex::PathView; TArray Crumbs; SourcesData.PackagePaths[0].ToString().ParseIntoArray(Crumbs, TEXT("/"), true); FString CrumbPath = TEXT("/"); for ( auto CrumbIt = Crumbs.CreateConstIterator(); CrumbIt; ++CrumbIt ) { // If this is the root part of the path, try and get the localized display name to stay in sync with what we see in SPathView const FText DisplayName = (CrumbIt.GetIndex() == 0) ? ContentBrowserUtils::GetRootDirDisplayName(*CrumbIt) : FText::FromString(*CrumbIt); CrumbPath += *CrumbIt; PathBreadcrumbTrail->PushCrumb(DisplayName, CrumbPath); CrumbPath += TEXT("/"); } } else if ( SourcesData.HasCollections() ) { NewSourcesWidgetIndex = GetDefault()->GetDockCollections() ? ContentBrowserSourcesWidgetSwitcherIndex::PathView : ContentBrowserSourcesWidgetSwitcherIndex::CollectionsView; FCollectionManagerModule& CollectionManagerModule = FCollectionManagerModule::GetModule(); TArray CollectionPathItems; // Walk up the parents of this collection so that we can generate a complete path (this loop also adds the child collection to the array) for (TOptional CurrentCollection = SourcesData.Collections[0]; CurrentCollection.IsSet(); CurrentCollection = CollectionManagerModule.Get().GetParentCollection(CurrentCollection->Name, CurrentCollection->Type) ) { CollectionPathItems.Insert(CurrentCollection.GetValue(), 0); } // Now add each part of the path to the breadcrumb trail for (const FCollectionNameType& CollectionPathItem : CollectionPathItems) { const FString CrumbData = FString::Printf(TEXT("%s?%s"), *CollectionPathItem.Name.ToString(), *FString::FromInt(CollectionPathItem.Type)); FFormatNamedArguments Args; Args.Add(TEXT("CollectionName"), FText::FromName(CollectionPathItem.Name)); const FText DisplayName = FText::Format(LOCTEXT("CollectionPathIndicator", "{CollectionName} (Collection)"), Args); PathBreadcrumbTrail->PushCrumb(DisplayName, CrumbData); } } else { PathBreadcrumbTrail->PushCrumb(LOCTEXT("AllAssets", "All Assets"), TEXT("")); } if (ActiveSourcesWidgetIndex != NewSourcesWidgetIndex) { ActiveSourcesWidgetIndex = NewSourcesWidgetIndex; SourcesWidgetSwitcher->SetActiveWidgetIndex(ActiveSourcesWidgetIndex); } } void SContentBrowser::OnFilterChanged() { FARFilter Filter = FilterListPtr->GetCombinedBackendFilter(); AssetViewPtr->SetBackendFilter( Filter ); // Notify 'filter changed' delegate FContentBrowserModule& ContentBrowserModule = FModuleManager::GetModuleChecked( TEXT("ContentBrowser") ); ContentBrowserModule.GetOnFilterChanged().Broadcast(Filter, bIsPrimaryBrowser); } FText SContentBrowser::GetPathText() const { FText PathLabelText; if ( IsFilteredBySource() ) { const FSourcesData& SourcesData = AssetViewPtr->GetSourcesData(); // At least one source is selected const int32 NumSources = SourcesData.PackagePaths.Num() + SourcesData.Collections.Num(); if (NumSources > 0) { PathLabelText = FText::FromName(SourcesData.HasPackagePaths() ? SourcesData.PackagePaths[0] : SourcesData.Collections[0].Name); if (NumSources > 1) { PathLabelText = FText::Format(LOCTEXT("PathTextFmt", "{0} and {1} {1}|plural(one=other,other=others)..."), PathLabelText, NumSources - 1); } } } else { PathLabelText = LOCTEXT("AllAssets", "All Assets"); } return PathLabelText; } bool SContentBrowser::IsFilteredBySource() const { const FSourcesData& SourcesData = AssetViewPtr->GetSourcesData(); return !SourcesData.IsEmpty(); } void SContentBrowser::OnAssetRenameCommitted(const TArray& Assets) { // After a rename is committed we allow an implicit sync so as not to // disorientate the user if they are looking at a parent folder const bool bAllowImplicitSync = true; const bool bDisableFiltersThatHideAssets = false; SyncToAssets(Assets, bAllowImplicitSync, bDisableFiltersThatHideAssets); } void SContentBrowser::OnFindInAssetTreeRequested(const TArray& AssetsToFind) { SyncToAssets(AssetsToFind); } void SContentBrowser::OnRenameRequested(const FAssetData& AssetData) { AssetViewPtr->RenameAsset(AssetData); } void SContentBrowser::OnRenameFolderRequested(const FString& FolderToRename) { const TArray& SelectedFolders = AssetViewPtr->GetSelectedFolders(); if (SelectedFolders.Num() > 0) { AssetViewPtr->RenameFolder(FolderToRename); } else { const TArray& SelectedPaths = PathViewPtr->GetSelectedPaths(); if (SelectedPaths.Num() > 0) { PathViewPtr->RenameFolder(FolderToRename); } } } void SContentBrowser::OnOpenedFolderDeleted() { // Since the contents of the asset view have just been deleted, set the selected path to the default "/Game" TArray DefaultSelectedPaths; DefaultSelectedPaths.Add(TEXT("/Game")); PathViewPtr->SetSelectedPaths(DefaultSelectedPaths); PathSelected(TEXT("/Game")); } void SContentBrowser::OnDuplicateRequested(const TWeakObjectPtr& OriginalObject) { UObject* Object = OriginalObject.Get(); if ( Object ) { AssetViewPtr->DuplicateAsset(FPackageName::GetLongPackagePath(Object->GetOutermost()->GetName()), OriginalObject); } } void SContentBrowser::OnAssetViewRefreshRequested() { AssetViewPtr->RequestSlowFullListRefresh(); } void SContentBrowser::HandleCollectionRemoved(const FCollectionNameType& Collection) { AssetViewPtr->SetSourcesData(FSourcesData()); auto RemoveHistoryDelegate = [&](const FHistoryData& HistoryData) { return (HistoryData.SourcesData.Collections.Num() == 1 && HistoryData.SourcesData.PackagePaths.Num() == 0 && HistoryData.SourcesData.Collections.Contains(Collection)); }; HistoryManager.RemoveHistoryData(RemoveHistoryDelegate); } void SContentBrowser::HandleCollectionRenamed(const FCollectionNameType& OriginalCollection, const FCollectionNameType& NewCollection) { return HandleCollectionRemoved(OriginalCollection); } void SContentBrowser::HandleCollectionUpdated(const FCollectionNameType& Collection) { const FSourcesData& SourcesData = AssetViewPtr->GetSourcesData(); // If we're currently viewing the dynamic collection that was updated, make sure our active filter text is up-to-date if (SourcesData.IsDynamicCollection() && SourcesData.Collections[0] == Collection) { FCollectionManagerModule& CollectionManagerModule = FCollectionManagerModule::GetModule(); const FCollectionNameType& DynamicCollection = SourcesData.Collections[0]; FString DynamicQueryString; CollectionManagerModule.Get().GetDynamicQueryText(DynamicCollection.Name, DynamicCollection.Type, DynamicQueryString); const FText DynamicQueryText = FText::FromString(DynamicQueryString); SetSearchBoxText(DynamicQueryText); SearchBoxPtr->SetText(DynamicQueryText); } } void SContentBrowser::HandlePathRemoved(const FString& Path) { const FName PathName(*Path); auto RemoveHistoryDelegate = [&](const FHistoryData& HistoryData) { return (HistoryData.SourcesData.PackagePaths.Num() == 1 && HistoryData.SourcesData.Collections.Num() == 0 && HistoryData.SourcesData.PackagePaths.Contains(PathName)); }; HistoryManager.RemoveHistoryData(RemoveHistoryDelegate); } FText SContentBrowser::GetSearchAssetsHintText() const { if (PathViewPtr.IsValid()) { TArray Paths = PathViewPtr->GetSelectedPaths(); if (Paths.Num() != 0) { FString SearchHint = NSLOCTEXT( "ContentBrowser", "SearchBoxPartialHint", "Search" ).ToString(); SearchHint += TEXT(" "); for(int32 i = 0; i < Paths.Num(); i++) { const FString& Path = Paths[i]; if (ContentBrowserUtils::IsRootDir(Path)) { SearchHint += ContentBrowserUtils::GetRootDirDisplayName(Path).ToString(); } else { SearchHint += FPaths::GetCleanFilename(Path); } if (i + 1 < Paths.Num()) { SearchHint += ", "; } } return FText::FromString(SearchHint); } } return NSLOCTEXT( "ContentBrowser", "SearchBoxHint", "Search Assets" ); } void ExtractAssetSearchFilterTerms(const FText& SearchText, FString* OutFilterKey, FString* OutFilterValue, int32* OutSuggestionInsertionIndex) { const FString SearchString = SearchText.ToString(); if (OutFilterKey) { OutFilterKey->Reset(); } if (OutFilterValue) { OutFilterValue->Reset(); } if (OutSuggestionInsertionIndex) { *OutSuggestionInsertionIndex = SearchString.Len(); } // Build the search filter terms so that we can inspect the tokens FTextFilterExpressionEvaluator LocalFilter(ETextFilterExpressionEvaluatorMode::Complex); LocalFilter.SetFilterText(SearchText); // Inspect the tokens to see what the last part of the search term was // If it was a key->value pair then we'll use that to control what kinds of results we show // For anything else we just use the text from the last token as our filter term to allow incremental auto-complete const TArray& FilterTokens = LocalFilter.GetFilterExpressionTokens(); if (FilterTokens.Num() > 0) { const FExpressionToken& LastToken = FilterTokens.Last(); // If the last token is a text token, then consider it as a value and walk back to see if we also have a key if (LastToken.Node.Cast()) { if (OutFilterValue) { *OutFilterValue = LastToken.Context.GetString(); } if (OutSuggestionInsertionIndex) { *OutSuggestionInsertionIndex = FMath::Min(*OutSuggestionInsertionIndex, LastToken.Context.GetCharacterIndex()); } if (FilterTokens.IsValidIndex(FilterTokens.Num() - 2)) { const FExpressionToken& ComparisonToken = FilterTokens[FilterTokens.Num() - 2]; if (ComparisonToken.Node.Cast()) { if (FilterTokens.IsValidIndex(FilterTokens.Num() - 3)) { const FExpressionToken& KeyToken = FilterTokens[FilterTokens.Num() - 3]; if (KeyToken.Node.Cast()) { if (OutFilterKey) { *OutFilterKey = KeyToken.Context.GetString(); } if (OutSuggestionInsertionIndex) { *OutSuggestionInsertionIndex = FMath::Min(*OutSuggestionInsertionIndex, KeyToken.Context.GetCharacterIndex()); } } } } } } // If the last token is a comparison operator, then walk back and see if we have a key else if (LastToken.Node.Cast()) { if (FilterTokens.IsValidIndex(FilterTokens.Num() - 2)) { const FExpressionToken& KeyToken = FilterTokens[FilterTokens.Num() - 2]; if (KeyToken.Node.Cast()) { if (OutFilterKey) { *OutFilterKey = KeyToken.Context.GetString(); } if (OutSuggestionInsertionIndex) { *OutSuggestionInsertionIndex = FMath::Min(*OutSuggestionInsertionIndex, KeyToken.Context.GetCharacterIndex()); } } } } } } void SContentBrowser::OnAssetSearchSuggestionFilter(const FText& SearchText, TArray& PossibleSuggestions, FText& SuggestionHighlightText) const { // We don't bind the suggestion list, so this list should be empty as we populate it here based on the search term check(PossibleSuggestions.Num() == 0); FString FilterKey; FString FilterValue; ExtractAssetSearchFilterTerms(SearchText, &FilterKey, &FilterValue, nullptr); auto PassesValueFilter = [&FilterValue](const FString& InOther) { return FilterValue.IsEmpty() || InOther.Contains(FilterValue); }; if (FilterKey.IsEmpty() || (FilterKey == TEXT("Type") || FilterKey == TEXT("Class"))) { FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked("AssetTools"); TArray< TWeakPtr > AssetTypeActionsList; AssetToolsModule.Get().GetAssetTypeActionsList(AssetTypeActionsList); const FText TypesCategoryName = NSLOCTEXT("ContentBrowser", "TypesCategoryName", "Types"); for (auto TypeActionsIt = AssetTypeActionsList.CreateConstIterator(); TypeActionsIt; ++TypeActionsIt) { if ((*TypeActionsIt).IsValid()) { const TSharedPtr TypeActions = (*TypeActionsIt).Pin(); if (TypeActions->GetSupportedClass()) { const FString TypeName = TypeActions->GetSupportedClass()->GetName(); const FText TypeDisplayName = TypeActions->GetSupportedClass()->GetDisplayNameText(); FString TypeSuggestion = FString::Printf(TEXT("Type=%s"), *TypeName); if (PassesValueFilter(TypeSuggestion)) { PossibleSuggestions.Add(FAssetSearchBoxSuggestion{ MoveTemp(TypeSuggestion), TypeDisplayName, TypesCategoryName }); } } } } } if (FilterKey.IsEmpty() || (FilterKey == TEXT("Collection") || FilterKey == TEXT("Tag"))) { ICollectionManager& CollectionManager = FCollectionManagerModule::GetModule().Get(); TArray AllCollections; CollectionManager.GetCollections(AllCollections); const FText CollectionsCategoryName = NSLOCTEXT("ContentBrowser", "CollectionsCategoryName", "Collections"); for (const FCollectionNameType& Collection : AllCollections) { FString CollectionName = Collection.Name.ToString(); FString CollectionSuggestion = FString::Printf(TEXT("Collection=%s"), *CollectionName); if (PassesValueFilter(CollectionSuggestion)) { PossibleSuggestions.Add(FAssetSearchBoxSuggestion{ MoveTemp(CollectionSuggestion), FText::FromString(MoveTemp(CollectionName)), CollectionsCategoryName }); } } } if (FilterKey.IsEmpty()) { IAssetRegistry& AssetRegistry = FModuleManager::LoadModuleChecked(AssetRegistryConstants::ModuleName).Get(); if (const FAssetRegistryState* StatePtr = AssetRegistry.GetAssetRegistryState()) { const FText MetaDataCategoryName = NSLOCTEXT("ContentBrowser", "MetaDataCategoryName", "Meta-Data"); for (const auto& TagAndArrayPair : StatePtr->GetTagToAssetDatasMap()) { const FString TagNameStr = TagAndArrayPair.Key.ToString(); if (PassesValueFilter(TagNameStr)) { PossibleSuggestions.Add(FAssetSearchBoxSuggestion{ TagNameStr, FText::FromString(TagNameStr), MetaDataCategoryName }); } } } } SuggestionHighlightText = FText::FromString(FilterValue); } FText SContentBrowser::OnAssetSearchSuggestionChosen(const FText& SearchText, const FString& Suggestion) const { int32 SuggestionInsertionIndex = 0; ExtractAssetSearchFilterTerms(SearchText, nullptr, nullptr, &SuggestionInsertionIndex); FString SearchString = SearchText.ToString(); SearchString.RemoveAt(SuggestionInsertionIndex, SearchString.Len() - SuggestionInsertionIndex, false); SearchString.Append(Suggestion); return FText::FromString(SearchString); } TSharedPtr SContentBrowser::GetFolderContextMenu(const TArray& SelectedPaths, FContentBrowserMenuExtender_SelectedPaths InMenuExtender, FOnCreateNewFolder InOnCreateNewFolder, bool bPathView) { // Clear any selection in the asset view, as it'll conflict with other view info // This is important for determining which context menu may be open based on the asset selection for rename/delete operations if (bPathView) { AssetViewPtr->ClearSelection(); } // Ensure the path context menu has the up-to-date list of paths being worked on PathContextMenu->SetSelectedPaths(SelectedPaths); TSharedPtr Extender; if(InMenuExtender.IsBound()) { Extender = InMenuExtender.Execute(SelectedPaths); } if (!UToolMenus::Get()->IsMenuRegistered("ContentBrowser.FolderContextMenu")) { UToolMenu* Menu = UToolMenus::Get()->RegisterMenu("ContentBrowser.FolderContextMenu"); Menu->bCloseSelfOnly = true; Menu->AddDynamicSection("Section", FNewToolMenuDelegate::CreateLambda([](UToolMenu* InMenu) { UContentBrowserFolderContext* Context = InMenu->FindContext(); if (Context && Context->ContentBrowser.IsValid()) { Context->ContentBrowser.Pin()->PopulateFolderContextMenu(InMenu); } })); } UContentBrowserFolderContext* Context = NewObject(); Context->ContentBrowser = SharedThis(this); Context->OnCreateNewFolder = InOnCreateNewFolder; ContentBrowserUtils::CountPathTypes(SelectedPaths, Context->NumAssetPaths, Context->NumClassPaths); FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked("AssetTools"); Context->bCanBeModified = AssetToolsModule.Get().AllPassWritableFolderFilter(SelectedPaths); FToolMenuContext MenuContext(Commands, Extender, Context); return UToolMenus::Get()->GenerateWidget("ContentBrowser.FolderContextMenu", MenuContext); } void SContentBrowser::PopulateFolderContextMenu(UToolMenu* Menu) { UContentBrowserFolderContext* Context = Menu->FindContext(); check(Context); const TArray& SelectedPaths = PathContextMenu->GetSelectedPaths(); // We can only create folders when we have a single path selected const bool bCanCreateNewFolder = SelectedPaths.Num() == 1 && ContentBrowserUtils::IsValidPathToCreateNewFolder(SelectedPaths[0]); FText NewFolderToolTip; if(SelectedPaths.Num() == 1) { if(bCanCreateNewFolder) { NewFolderToolTip = FText::Format(LOCTEXT("NewFolderTooltip_CreateIn", "Create a new folder in {0}."), FText::FromString(SelectedPaths[0])); } else { NewFolderToolTip = FText::Format(LOCTEXT("NewFolderTooltip_InvalidPath", "Cannot create new folders in {0}."), FText::FromString(SelectedPaths[0])); } } else { NewFolderToolTip = LOCTEXT("NewFolderTooltip_InvalidNumberOfPaths", "Can only create folders when there is a single path selected."); } { FToolMenuSection& Section = Menu->AddSection("Section"); if (Context->bCanBeModified) { // New Folder Section.AddMenuEntry( "NewFolder", LOCTEXT("NewFolder", "New Folder"), NewFolderToolTip, FSlateIcon(FEditorStyle::GetStyleSetName(), "ContentBrowser.NewFolderIcon"), FUIAction( FExecuteAction::CreateSP(this, &SContentBrowser::CreateNewFolder, SelectedPaths.Num() > 0 ? SelectedPaths[0] : FString(), Context->OnCreateNewFolder), FCanExecuteAction::CreateLambda([bCanCreateNewFolder] { return bCanCreateNewFolder; }) ) ); } Section.AddMenuEntry( "FolderContext", LOCTEXT("ShowInNewContentBrowser", "Show in New Content Browser"), LOCTEXT("ShowInNewContentBrowserTooltip", "Opens a new Content Browser at this folder location (at least 1 Content Browser window needs to be locked)"), FSlateIcon(), FUIAction(FExecuteAction::CreateSP(this, &SContentBrowser::OpenNewContentBrowser)) ); } PathContextMenu->MakePathViewContextMenu(Menu); } void SContentBrowser::CreateNewFolder(FString FolderPath, FOnCreateNewFolder InOnCreateNewFolder) { // Create a valid base name for this folder FText DefaultFolderBaseName = LOCTEXT("DefaultFolderName", "NewFolder"); FText DefaultFolderName = DefaultFolderBaseName; int32 NewFolderPostfix = 1; while(ContentBrowserUtils::DoesFolderExist(FolderPath / DefaultFolderName.ToString())) { DefaultFolderName = FText::Format(LOCTEXT("DefaultFolderNamePattern", "{0}{1}"), DefaultFolderBaseName, FText::AsNumber(NewFolderPostfix)); NewFolderPostfix++; } InOnCreateNewFolder.ExecuteIfBound(DefaultFolderName.ToString(), FolderPath); } void SContentBrowser::OpenNewContentBrowser() { FContentBrowserSingleton::Get().SyncBrowserToFolders(PathContextMenu->GetSelectedPaths(), false, true, NAME_None, true); } #undef LOCTEXT_NAMESPACE