You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
Fix for being able to add a setter for the SCS generated component variables. UE-8135 This changed the SCS added component variables to be CPF_BlueprintReadOnly which means that blueprints that include setters will no longer compile. [CL 2425873 by Ben Marsh in Main branch]
494 lines
15 KiB
C++
494 lines
15 KiB
C++
// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "UnrealEd.h"
|
|
#include "SlateBasics.h"
|
|
#include "SComponentClassCombo.h"
|
|
#include "ComponentAssetBroker.h"
|
|
#include "ClassIconFinder.h"
|
|
#include "BlueprintGraphDefinitions.h"
|
|
#include "IDocumentation.h"
|
|
#include "SListViewSelectorDropdownMenu.h"
|
|
#include "EditorClassUtils.h"
|
|
#include "SSearchBox.h"
|
|
#include "Engine/Selection.h"
|
|
#include "HotReloadInterface.h"
|
|
#include "KismetEditorUtilities.h"
|
|
|
|
#define LOCTEXT_NAMESPACE "ComponentClassCombo"
|
|
|
|
|
|
void SComponentClassCombo::Construct(const FArguments& InArgs)
|
|
{
|
|
PrevSelectedIndex = INDEX_NONE;
|
|
OnComponentClassSelected = InArgs._OnComponentClassSelected;
|
|
|
|
UpdateComponentClassList();
|
|
GenerateFilteredComponentList(FString());
|
|
|
|
IHotReloadInterface& HotReloadSupport = FModuleManager::LoadModuleChecked<IHotReloadInterface>("HotReload");
|
|
HotReloadSupport.OnHotReload().AddSP( this, &SComponentClassCombo::OnProjectHotReloaded );
|
|
|
|
SAssignNew(ComponentClassListView, SListView<FComponentClassComboEntryPtr>)
|
|
.ListItemsSource( &FilteredComponentClassList )
|
|
.OnSelectionChanged( this, &SComponentClassCombo::OnAddComponentSelectionChanged )
|
|
.OnGenerateRow( this, &SComponentClassCombo::GenerateAddComponentRow )
|
|
.SelectionMode(ESelectionMode::Single);
|
|
|
|
SAssignNew(SearchBox, SSearchBox)
|
|
.HintText( LOCTEXT( "BlueprintAddComponentSearchBoxHint", "Search Components" ) )
|
|
.OnTextChanged( this, &SComponentClassCombo::OnSearchBoxTextChanged )
|
|
.OnTextCommitted( this, &SComponentClassCombo::OnSearchBoxTextCommitted );
|
|
|
|
// Create the Construct arguments for the parent class (SComboButton)
|
|
SComboButton::FArguments Args;
|
|
Args.ButtonContent()
|
|
[
|
|
SNew(SHorizontalBox)
|
|
+SHorizontalBox::Slot()
|
|
.VAlign(VAlign_Center)
|
|
.AutoWidth()
|
|
.Padding(2.f,1.f)
|
|
[
|
|
SNew(SImage)
|
|
.Image(FEditorStyle::GetBrush(TEXT("Plus")))
|
|
]
|
|
+ SHorizontalBox::Slot()
|
|
.VAlign(VAlign_Center)
|
|
.Padding(1.f)
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(LOCTEXT("AddComponentButtonLabel", "Add Component"))
|
|
.TextStyle(FEditorStyle::Get(), "ContentBrowser.TopBar.Font")
|
|
.Visibility(InArgs._IncludeText.Get() ? EVisibility::Visible : EVisibility::Collapsed)
|
|
]
|
|
]
|
|
.MenuContent()
|
|
[
|
|
|
|
SNew(SListViewSelectorDropdownMenu<FComponentClassComboEntryPtr>, SearchBox, ComponentClassListView)
|
|
[
|
|
SNew(SBorder)
|
|
.BorderImage(FEditorStyle::GetBrush("Menu.Background"))
|
|
.Padding(2)
|
|
[
|
|
SNew(SBox)
|
|
.WidthOverride(250)
|
|
[
|
|
SNew(SVerticalBox)
|
|
+SVerticalBox::Slot()
|
|
.Padding(1.f)
|
|
.AutoHeight()
|
|
[
|
|
SearchBox.ToSharedRef()
|
|
]
|
|
+SVerticalBox::Slot()
|
|
.MaxHeight(400)
|
|
[
|
|
ComponentClassListView.ToSharedRef()
|
|
]
|
|
]
|
|
]
|
|
]
|
|
]
|
|
.IsFocusable(true)
|
|
.ContentPadding(FMargin(0))
|
|
.ComboButtonStyle(FEditorStyle::Get(), "ContentBrowser.NewAsset.Style")
|
|
.ForegroundColor(FLinearColor::White)
|
|
.OnComboBoxOpened(this, &SComponentClassCombo::ClearSelection);
|
|
|
|
SComboButton::Construct(Args);
|
|
|
|
ComponentClassListView->EnableToolTipForceField( true );
|
|
// The base class can automatically handle setting focus to a specified control when the combo button is opened
|
|
SetMenuContentWidgetToFocus( SearchBox );
|
|
}
|
|
|
|
void SComponentClassCombo::ClearSelection()
|
|
{
|
|
SearchBox->SetText(FText::GetEmpty());
|
|
|
|
PrevSelectedIndex = INDEX_NONE;
|
|
|
|
// Clear the selection in such a way as to also clear the keyboard selector
|
|
ComponentClassListView->SetSelection(NULL, ESelectInfo::OnNavigation);
|
|
}
|
|
|
|
void SComponentClassCombo::GenerateFilteredComponentList(const FString& InSearchText)
|
|
{
|
|
if ( InSearchText.IsEmpty() )
|
|
{
|
|
FilteredComponentClassList = ComponentClassList;
|
|
}
|
|
else
|
|
{
|
|
FilteredComponentClassList.Empty();
|
|
for ( int32 ComponentIndex = 0; ComponentIndex < ComponentClassList.Num(); ComponentIndex++ )
|
|
{
|
|
FComponentClassComboEntryPtr& CurrentEntry = ComponentClassList[ComponentIndex];
|
|
|
|
if (CurrentEntry->IsClass() && CurrentEntry->IsIncludedInFilter())
|
|
{
|
|
FString FriendlyComponentName = GetSanitizedComponentName( CurrentEntry->GetComponentClass() );
|
|
|
|
if ( FriendlyComponentName.Contains( InSearchText, ESearchCase::IgnoreCase ) )
|
|
{
|
|
FilteredComponentClassList.Add( CurrentEntry );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
FText SComponentClassCombo::GetCurrentSearchString() const
|
|
{
|
|
return CurrentSearchString;
|
|
}
|
|
|
|
void SComponentClassCombo::OnSearchBoxTextChanged( const FText& InSearchText )
|
|
{
|
|
CurrentSearchString = InSearchText;
|
|
|
|
// Generate a filtered list
|
|
GenerateFilteredComponentList(CurrentSearchString.ToString());
|
|
|
|
// Ask the combo to update its contents on next tick
|
|
ComponentClassListView->RequestListRefresh();
|
|
}
|
|
|
|
void SComponentClassCombo::OnSearchBoxTextCommitted(const FText& NewText, ETextCommit::Type CommitInfo)
|
|
{
|
|
if(CommitInfo == ETextCommit::OnEnter)
|
|
{
|
|
auto SelectedItems = ComponentClassListView->GetSelectedItems();
|
|
if(SelectedItems.Num() > 0)
|
|
{
|
|
ComponentClassListView->SetSelection(SelectedItems[0]);
|
|
}
|
|
}
|
|
}
|
|
|
|
void SComponentClassCombo::OnAddComponentSelectionChanged( FComponentClassComboEntryPtr InItem, ESelectInfo::Type SelectInfo )
|
|
{
|
|
if ( InItem.IsValid() && InItem->IsClass() && SelectInfo != ESelectInfo::OnNavigation)
|
|
{
|
|
// We don't want the item to remain selected
|
|
ClearSelection();
|
|
|
|
if ( InItem->IsClass() )
|
|
{
|
|
OnComponentClassSelected.ExecuteIfBound(InItem->GetComponentClass(), InItem->GetComponentCreateAction() );
|
|
|
|
// Neither do we want the combo dropdown staying open once the user has clicked on a valid option
|
|
SetIsOpen( false, false );
|
|
}
|
|
}
|
|
else if ( InItem.IsValid() && SelectInfo != ESelectInfo::OnMouseClick )
|
|
{
|
|
int32 SelectedIdx = INDEX_NONE;
|
|
FilteredComponentClassList.Find(InItem, SelectedIdx);
|
|
|
|
if(SelectedIdx != INDEX_NONE)
|
|
{
|
|
if(!InItem->IsClass())
|
|
{
|
|
int32 SelectionDirection = SelectedIdx - PrevSelectedIndex;
|
|
|
|
// Update the previous selected index
|
|
PrevSelectedIndex = SelectedIdx;
|
|
|
|
if(SelectedIdx + SelectionDirection >= 0 && SelectedIdx + SelectionDirection < FilteredComponentClassList.Num())
|
|
{
|
|
ComponentClassListView->SetSelection(FilteredComponentClassList[SelectedIdx + SelectionDirection], ESelectInfo::OnNavigation);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Update the previous selected index
|
|
PrevSelectedIndex = SelectedIdx;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
TSharedRef<ITableRow> SComponentClassCombo::GenerateAddComponentRow( FComponentClassComboEntryPtr Entry, const TSharedRef<STableViewBase> &OwnerTable ) const
|
|
{
|
|
check( Entry->IsHeading() || Entry->IsSeparator() || Entry->IsClass() );
|
|
|
|
if ( Entry->IsHeading() )
|
|
{
|
|
return
|
|
SNew( STableRow< TSharedPtr<FString> >, OwnerTable )
|
|
.ShowSelection(false)
|
|
[
|
|
SNew(SBox)
|
|
.Padding(1.f)
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(FText::FromString(Entry->GetHeadingText()))
|
|
.TextStyle(FEditorStyle::Get(), TEXT("Menu.Heading"))
|
|
]
|
|
];
|
|
}
|
|
else if ( Entry->IsSeparator() )
|
|
{
|
|
return
|
|
SNew( STableRow< TSharedPtr<FString> >, OwnerTable )
|
|
.ShowSelection(false)
|
|
[
|
|
SNew(SBox)
|
|
.Padding(1.f)
|
|
[
|
|
SNew(SBorder)
|
|
.Padding(FEditorStyle::GetMargin(TEXT("Menu.Separator.Padding")))
|
|
.BorderImage(FEditorStyle::GetBrush(TEXT("Menu.Separator")))
|
|
]
|
|
];
|
|
}
|
|
else
|
|
{
|
|
|
|
return
|
|
SNew( SComboRow< TSharedPtr<FString> >, OwnerTable )
|
|
.ToolTip( FEditorClassUtils::GetTooltip(Entry->GetComponentClass()) )
|
|
[
|
|
SNew(SHorizontalBox)
|
|
+SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
SNew(SSpacer)
|
|
.Size(FVector2D(8.0f,1.0f))
|
|
]
|
|
+SHorizontalBox::Slot()
|
|
.Padding(1.0f)
|
|
.AutoWidth()
|
|
[
|
|
SNew(SImage)
|
|
.Image( FClassIconFinder::FindIconForClass( Entry->GetComponentClass() ) )
|
|
]
|
|
+SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
SNew(SSpacer)
|
|
.Size(FVector2D(3.0f,1.0f))
|
|
]
|
|
+SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
SNew(STextBlock)
|
|
.HighlightText(this, &SComponentClassCombo::GetCurrentSearchString)
|
|
.Text(this, &SComponentClassCombo::GetFriendlyComponentName, Entry)
|
|
]
|
|
];
|
|
}
|
|
}
|
|
|
|
struct SortComboEntry
|
|
{
|
|
static const FString CommonClassGroup;
|
|
|
|
bool operator () (const FComponentClassComboEntryPtr& A, const FComponentClassComboEntryPtr& B) const
|
|
{
|
|
bool bResult = false;
|
|
|
|
// check headings first, if they are the same compare the individual entries
|
|
int32 HeadingCompareResult = FCString::Stricmp( *A->GetHeadingText(), *B->GetHeadingText() );
|
|
if ( HeadingCompareResult == 0 )
|
|
{
|
|
check(A->GetComponentClass()); check(B->GetComponentClass());
|
|
bResult = FCString::Stricmp(*A->GetComponentClass()->GetName(), *B->GetComponentClass()->GetName()) < 0;
|
|
}
|
|
else if (CommonClassGroup == A->GetHeadingText())
|
|
{
|
|
bResult = true;
|
|
}
|
|
else if (CommonClassGroup == B->GetHeadingText())
|
|
{
|
|
bResult = false;
|
|
}
|
|
else
|
|
{
|
|
bResult = HeadingCompareResult < 0;
|
|
}
|
|
|
|
return bResult;
|
|
}
|
|
};
|
|
const FString SortComboEntry::CommonClassGroup(TEXT("Common"));
|
|
|
|
void SComponentClassCombo::UpdateComponentClassList()
|
|
{
|
|
ComponentClassList.Empty();
|
|
|
|
const UEdGraphSchema_K2* K2Schema = GetDefault<UEdGraphSchema_K2>();
|
|
|
|
if( GetDefault<UEditorExperimentalSettings>()->bScriptableComponentsOnActors )
|
|
{
|
|
FString NewComponentsHeading = LOCTEXT("NewComponentsHeading", "Behavioral").ToString();
|
|
// Add new C++ component class
|
|
FComponentClassComboEntryPtr NewClassHeader = MakeShareable(new FComponentClassComboEntry(NewComponentsHeading));
|
|
ComponentClassList.Add(NewClassHeader);
|
|
|
|
FComponentClassComboEntryPtr NewCPPClass = MakeShareable(new FComponentClassComboEntry(NewComponentsHeading, UActorComponent::StaticClass(), true, EComponentCreateAction::CreateNewCPPClass));
|
|
ComponentClassList.Add(NewCPPClass);
|
|
|
|
FComponentClassComboEntryPtr NewBPClass = MakeShareable(new FComponentClassComboEntry(NewComponentsHeading, UActorComponent::StaticClass(), true, EComponentCreateAction::CreateNewBlueprintClass));
|
|
ComponentClassList.Add(NewBPClass);
|
|
}
|
|
|
|
TArray<FComponentClassComboEntryPtr> SortedClassList;
|
|
for (TObjectIterator<UClass> It; It; ++It)
|
|
{
|
|
UClass* Class = *It;
|
|
// If this is a subclass of Actor Component, not abstract, and tagged as spawnable from Kismet
|
|
if (Class->IsChildOf(UActorComponent::StaticClass()) && !Class->HasAnyClassFlags(CLASS_Abstract) && Class->HasMetaData(FBlueprintMetadata::MD_BlueprintSpawnableComponent) && !FKismetEditorUtilities::IsClassABlueprintSkeleton(Class)) //@TODO: Fold this logic together with the one in UEdGraphSchema_K2::GetAddComponentClasses
|
|
{
|
|
TArray<FString> ClassGroupNames;
|
|
Class->GetClassGroupNames( ClassGroupNames );
|
|
|
|
if (ClassGroupNames.Contains(SortComboEntry::CommonClassGroup))
|
|
{
|
|
FString ClassGroup = SortComboEntry::CommonClassGroup;
|
|
FComponentClassComboEntryPtr NewEntry(new FComponentClassComboEntry(ClassGroup, Class, ClassGroupNames.Num() <= 1, EComponentCreateAction::SpawnExistingClass));
|
|
SortedClassList.Add(NewEntry);
|
|
}
|
|
if (ClassGroupNames.Num() && !ClassGroupNames[0].Equals(SortComboEntry::CommonClassGroup))
|
|
{
|
|
const bool bIncludeInFilter = true;
|
|
|
|
FString ClassGroup = ClassGroupNames[0];
|
|
FComponentClassComboEntryPtr NewEntry(new FComponentClassComboEntry(ClassGroup, Class, bIncludeInFilter, EComponentCreateAction::SpawnExistingClass));
|
|
SortedClassList.Add(NewEntry);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (SortedClassList.Num() > 0)
|
|
{
|
|
Sort(SortedClassList.GetData(), SortedClassList.Num(), SortComboEntry());
|
|
|
|
FString PreviousHeading;
|
|
for ( int32 ClassIndex = 0; ClassIndex < SortedClassList.Num(); ClassIndex++ )
|
|
{
|
|
FComponentClassComboEntryPtr& CurrentEntry = SortedClassList[ClassIndex];
|
|
|
|
const FString& CurrentHeadingText = CurrentEntry->GetHeadingText();
|
|
|
|
if ( CurrentHeadingText != PreviousHeading )
|
|
{
|
|
// This avoids a redundant separator being added to the very top of the list
|
|
if ( ClassIndex > 0 )
|
|
{
|
|
FComponentClassComboEntryPtr NewSeparator(new FComponentClassComboEntry());
|
|
ComponentClassList.Add( NewSeparator );
|
|
}
|
|
FComponentClassComboEntryPtr NewHeading(new FComponentClassComboEntry( CurrentHeadingText ));
|
|
ComponentClassList.Add( NewHeading );
|
|
|
|
PreviousHeading = CurrentHeadingText;
|
|
}
|
|
|
|
ComponentClassList.Add( CurrentEntry );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void SComponentClassCombo::OnProjectHotReloaded( bool bWasTriggeredAutomatically )
|
|
{
|
|
UpdateComponentClassList();
|
|
|
|
ComponentClassListView->RequestListRefresh();
|
|
}
|
|
|
|
|
|
FText SComponentClassCombo::GetFriendlyComponentName(FComponentClassComboEntryPtr Entry) const
|
|
{
|
|
// Get a user friendly string from the component name
|
|
FString FriendlyComponentName;
|
|
|
|
if( Entry->GetComponentCreateAction() == EComponentCreateAction::CreateNewCPPClass )
|
|
{
|
|
FriendlyComponentName = LOCTEXT("NewCPPComponentFriendlyName", "New C++ Component...").ToString();
|
|
}
|
|
else if (Entry->GetComponentCreateAction() == EComponentCreateAction::CreateNewBlueprintClass )
|
|
{
|
|
FriendlyComponentName = LOCTEXT("NewBlueprintComponentFriendlyName", "New Blueprint Component...").ToString();
|
|
}
|
|
else
|
|
{
|
|
FriendlyComponentName = GetSanitizedComponentName(Entry->GetComponentClass());
|
|
|
|
|
|
// Search the selected assets and look for any that can be used as a source asset for this type of component
|
|
// If there is one we append the asset name to the component name, if there are many we append "Multiple Assets"
|
|
FString AssetName;
|
|
UObject* PreviousMatchingAsset = NULL;
|
|
|
|
FEditorDelegates::LoadSelectedAssetsIfNeeded.Broadcast();
|
|
USelection* Selection = GEditor->GetSelectedObjects();
|
|
for(FSelectionIterator ObjectIter(*Selection); ObjectIter; ++ObjectIter)
|
|
{
|
|
UObject* Object = *ObjectIter;
|
|
UClass* Class = Object->GetClass();
|
|
|
|
TArray<TSubclassOf<UActorComponent> > ComponentClasses = FComponentAssetBrokerage::GetComponentsForAsset(Object);
|
|
for(int32 ComponentIndex = 0; ComponentIndex < ComponentClasses.Num(); ComponentIndex++)
|
|
{
|
|
if(ComponentClasses[ComponentIndex]->IsChildOf(Entry->GetComponentClass()))
|
|
{
|
|
if(AssetName.IsEmpty())
|
|
{
|
|
// If there is no previous asset then we just accept the name
|
|
AssetName = Object->GetName();
|
|
PreviousMatchingAsset = Object;
|
|
}
|
|
else
|
|
{
|
|
// if there is a previous asset then check that we didn't just find multiple appropriate components
|
|
// in a single asset - if the asset differs then we don't display the name, just "Multiple Assets"
|
|
if(PreviousMatchingAsset != Object)
|
|
{
|
|
AssetName = LOCTEXT("MultipleAssetsForComponentAnnotation", "Multiple Assets").ToString();
|
|
PreviousMatchingAsset = Object;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if(!AssetName.IsEmpty())
|
|
{
|
|
FriendlyComponentName += FString(" (") + AssetName + FString(")");
|
|
}
|
|
}
|
|
return FText::FromString(FriendlyComponentName);
|
|
}
|
|
|
|
FString SComponentClassCombo::GetSanitizedComponentName( UClass* ComponentClass )
|
|
{
|
|
FString DisplayName;
|
|
if (ComponentClass->HasMetaData(TEXT("DisplayName")))
|
|
{
|
|
DisplayName = ComponentClass->GetMetaData(TEXT("DisplayName"));
|
|
}
|
|
else
|
|
{
|
|
DisplayName = ComponentClass->GetName();
|
|
DisplayName = DisplayName.Replace( TEXT("Component"), TEXT(""), ESearchCase::IgnoreCase );
|
|
}
|
|
|
|
// Purge the _C for BP Components
|
|
if (DisplayName.EndsWith(TEXT("_C")))
|
|
{
|
|
const int32 NewLen = DisplayName.Len() - 2;
|
|
DisplayName = DisplayName.Left(NewLen);
|
|
}
|
|
|
|
return FName::NameToDisplayString(DisplayName, false);
|
|
}
|
|
|
|
#undef LOCTEXT_NAMESPACE
|