// Copyright Epic Games, Inc. All Rights Reserved. #include "KismetPins/SGraphPinObject.h" #include "AssetRegistry/ARFilter.h" #include "AssetRegistry/AssetRegistryModule.h" #include "AssetRegistry/IAssetRegistry.h" #include "Containers/Set.h" #include "ContentBrowserDelegates.h" #include "ContentBrowserModule.h" #include "Delegates/Delegate.h" #include "EdGraph/EdGraphNode.h" #include "EdGraph/EdGraphPin.h" #include "EdGraph/EdGraphSchema.h" #include "EdGraphSchema_K2.h" #include "Editor.h" #include "Editor/EditorEngine.h" #include "Fonts/SlateFontInfo.h" #include "HAL/PlatformCrt.h" #include "HAL/PlatformMath.h" #include "IContentBrowserSingleton.h" #include "Internationalization/Internationalization.h" #include "Layout/Margin.h" #include "Math/Color.h" #include "Misc/AssertionMacros.h" #include "Misc/Attribute.h" #include "Misc/CString.h" #include "Misc/PackageName.h" #include "Modules/ModuleManager.h" #include "ScopedTransaction.h" #include "Selection.h" #include "SlotBase.h" #include "Styling/AppStyle.h" #include "Templates/Casts.h" #include "Types/SlateEnums.h" #include "Types/SlateStructs.h" #include "UObject/Class.h" #include "UObject/NameTypes.h" #include "UObject/Object.h" #include "UObject/ObjectPtr.h" #include "UObject/TopLevelAssetPath.h" #include "UObject/WeakObjectPtrTemplates.h" #include "Widgets/Images/SImage.h" #include "Widgets/Input/SButton.h" #include "Widgets/Input/SComboButton.h" #include "Widgets/Input/SEditableTextBox.h" #include "Widgets/Input/SMenuAnchor.h" #include "Widgets/Layout/SBorder.h" #include "Widgets/Layout/SBox.h" #include "Widgets/SBoxPanel.h" #include "Widgets/SNullWidget.h" #include "Widgets/Text/STextBlock.h" class SWidget; #define LOCTEXT_NAMESPACE "SGraphPinObject" namespace GraphPinObjectDefs { // Active Combo pin alpha static const float ActiveComboAlpha = 1.f; // InActive Combo pin alpha static const float InActiveComboAlpha = 0.6f; // Active foreground pin alpha static const float ActivePinForegroundAlpha = 1.f; // InActive foreground pin alpha static const float InactivePinForegroundAlpha = 0.15f; // Active background pin alpha static const float ActivePinBackgroundAlpha = 0.8f; // InActive background pin alpha static const float InactivePinBackgroundAlpha = 0.4f; }; void SGraphPinObject::Construct(const FArguments& InArgs, UEdGraphPin* InGraphPinObj) { SGraphPin::Construct(SGraphPin::FArguments(), InGraphPinObj); } TSharedRef SGraphPinObject::GetDefaultValueWidget() { if (GraphPinObj == nullptr) { return SNullWidget::NullWidget; } const UEdGraphSchema* Schema = GraphPinObj->GetSchema(); if (Schema == nullptr) { return SNullWidget::NullWidget; } if(ShouldDisplayAsSelfPin()) { return SNew(SEditableTextBox) .Style( FAppStyle::Get(), "Graph.EditableTextBox" ) .Text( this, &SGraphPinObject::GetValue ) .SelectAllTextWhenFocused(false) .Visibility( this, &SGraphPinObject::GetDefaultValueVisibility ) .IsReadOnly( true ) .ForegroundColor( FSlateColor::UseForeground() ); } // Don't show literal buttons for component type objects if (Schema->ShouldShowAssetPickerForPin(GraphPinObj)) { return SNew(SHorizontalBox) .Visibility( this, &SGraphPin::GetDefaultValueVisibility ) +SHorizontalBox::Slot() .AutoWidth() .Padding(2.0f,0.0f) .MaxWidth(100.0f) [ SAssignNew(AssetPickerAnchor, SComboButton) .ButtonStyle( FAppStyle::Get(), "PropertyEditor.AssetComboStyle" ) .ForegroundColor( this, &SGraphPinObject::OnGetComboForeground) .ContentPadding( FMargin(2.0f,2.0f,2.0f,1.0f) ) .ButtonColorAndOpacity( this, &SGraphPinObject::OnGetWidgetBackground ) .MenuPlacement(MenuPlacement_BelowAnchor) .IsEnabled(this, &SGraphPin::IsEditingEnabled) .ButtonContent() [ SNew(STextBlock) .ColorAndOpacity( this, &SGraphPinObject::OnGetComboForeground ) .TextStyle( FAppStyle::Get(), "PropertyEditor.AssetClass" ) .Font( FAppStyle::GetFontStyle( "PropertyWindow.NormalFont" ) ) .Text( this, &SGraphPinObject::OnGetComboTextValue ) .ToolTipText( this, &SGraphPinObject::GetObjectToolTip ) ] .OnGetMenuContent(this, &SGraphPinObject::GenerateAssetPicker) ] // Use button +SHorizontalBox::Slot() .AutoWidth() .Padding(1.0f,0.0f) .VAlign(VAlign_Center) [ SAssignNew(UseButton, SButton) .ButtonStyle( FAppStyle::Get(), "NoBorder" ) .ButtonColorAndOpacity( this, &SGraphPinObject::OnGetWidgetBackground ) .OnClicked( GetOnUseButtonDelegate() ) .ContentPadding(1.f) .ToolTipText(NSLOCTEXT("GraphEditor", "ObjectGraphPin_Use_Tooltip", "Use asset browser selection")) .IsEnabled(this, &SGraphPin::IsEditingEnabled) [ SNew(SImage) .ColorAndOpacity( this, &SGraphPinObject::OnGetWidgetForeground ) .Image( FAppStyle::GetBrush(TEXT("Icons.CircleArrowLeft")) ) ] ] // Browse button +SHorizontalBox::Slot() .AutoWidth() .Padding(1.0f,0.0f) .VAlign(VAlign_Center) [ SAssignNew(BrowseButton, SButton) .ButtonStyle( FAppStyle::Get(), "NoBorder" ) .ButtonColorAndOpacity( this, &SGraphPinObject::OnGetWidgetBackground ) .OnClicked( GetOnBrowseButtonDelegate() ) .ContentPadding(0.0f) .ToolTipText(NSLOCTEXT("GraphEditor", "ObjectGraphPin_Browse_Tooltip", "Browse")) [ SNew(SImage) .ColorAndOpacity( this, &SGraphPinObject::OnGetWidgetForeground ) .Image( FAppStyle::GetBrush(TEXT("Icons.Search")) ) ] ]; } return SNullWidget::NullWidget; } FOnClicked SGraphPinObject::GetOnUseButtonDelegate() { return FOnClicked::CreateSP( this, &SGraphPinObject::OnClickUse ); } FOnClicked SGraphPinObject::GetOnBrowseButtonDelegate() { return FOnClicked::CreateSP( this, &SGraphPinObject::OnClickBrowse ); } FText SGraphPinObject::GetObjectToolTip() const { return GetValue(); } FString SGraphPinObject::GetObjectToolTipAsString() const { return GetValue().ToString(); } FReply SGraphPinObject::OnClickUse() { FEditorDelegates::LoadSelectedAssetsIfNeeded.Broadcast(); UClass* ObjectClass = Cast(GraphPinObj->PinType.PinSubCategoryObject.Get()); if (ObjectClass != NULL) { UObject* SelectedObject = GEditor->GetSelectedObjects()->GetTop(ObjectClass); if(SelectedObject != NULL) { const FScopedTransaction Transaction(NSLOCTEXT("GraphEditor", "ChangeObjectPinValue", "Change Object Pin Value")); GraphPinObj->Modify(); GraphPinObj->GetSchema()->TrySetDefaultObject(*GraphPinObj, SelectedObject); } } return FReply::Handled(); } FReply SGraphPinObject::OnClickBrowse() { const FAssetData& AssetData = GetAssetData(false); if (AssetData.IsValid()) { TArray Objects; Objects.Add(AssetData); GEditor->SyncBrowserToObjects(Objects); } return FReply::Handled(); } TSharedRef SGraphPinObject::GenerateAssetPicker() { // This class and its children are the classes that we can show objects for UClass* AllowedClass = Cast(GraphPinObj->PinType.PinSubCategoryObject.Get()); if (AllowedClass == NULL) { AllowedClass = UObject::StaticClass(); } FContentBrowserModule& ContentBrowserModule = FModuleManager::Get().LoadModuleChecked(TEXT("ContentBrowser")); FAssetPickerConfig AssetPickerConfig; AssetPickerConfig.Filter.ClassPaths.Add(AllowedClass->GetClassPathName()); AssetPickerConfig.bAllowNullSelection = true; AssetPickerConfig.Filter.bRecursiveClasses = true; AssetPickerConfig.OnAssetSelected = FOnAssetSelected::CreateSP(this, &SGraphPinObject::OnAssetSelectedFromPicker); AssetPickerConfig.OnAssetEnterPressed = FOnAssetEnterPressed::CreateSP(this, &SGraphPinObject::OnAssetEnterPressedInPicker); AssetPickerConfig.InitialAssetViewType = EAssetViewType::List; AssetPickerConfig.bAllowDragging = false; AssetPickerConfig.InitialAssetSelection = CachedAssetData.GetAsset(); UObject* OwningAsset = GraphPinObj->GetOwningNode(); while (OwningAsset && !OwningAsset->IsAsset()) { OwningAsset = OwningAsset->GetOuter(); } if (OwningAsset) { AssetPickerConfig.AdditionalReferencingAssets.Add(FAssetData(OwningAsset)); } // Check with the node to see if there is any "AllowClasses" or "DisallowedClasses" metadata for the pin FString AllowedClassesFilterString = GraphPinObj->GetOwningNode()->GetPinMetaData(GraphPinObj->PinName, FName(TEXT("AllowedClasses"))); if( !AllowedClassesFilterString.IsEmpty() ) { // Clear out the allowed class names and have the pin's metadata override. AssetPickerConfig.Filter.ClassPaths.Empty(); // Parse and add the classes from the metadata TArray AllowedClassesFilterNames; AllowedClassesFilterString.ParseIntoArrayWS(AllowedClassesFilterNames, TEXT(","), true); for(const FString& AllowedClassesFilterName : AllowedClassesFilterNames) { ensureAlwaysMsgf(!FPackageName::IsShortPackageName(AllowedClassesFilterName), TEXT("Short class names are not supported as AllowedClasses on pin \"%s\": class \"%s\""), *GraphPinObj->PinName.ToString(), *AllowedClassesFilterName); AssetPickerConfig.Filter.ClassPaths.Add(FTopLevelAssetPath(AllowedClassesFilterName)); } } FString DisallowedClassesFilterString = GraphPinObj->GetOwningNode()->GetPinMetaData(GraphPinObj->PinName, FName(TEXT("DisallowedClasses"))); if(!DisallowedClassesFilterString.IsEmpty()) { TArray DisallowedClassesFilterNames; DisallowedClassesFilterString.ParseIntoArrayWS(DisallowedClassesFilterNames, TEXT(","), true); for(const FString& DisallowedClassesFilterName : DisallowedClassesFilterNames) { ensureAlwaysMsgf(!FPackageName::IsShortPackageName(DisallowedClassesFilterName), TEXT("Short class names are not supported as DisallowedClasses on pin \"%s\": class \"%s\""), *GraphPinObj->PinName.ToString(), *DisallowedClassesFilterName); AssetPickerConfig.Filter.RecursiveClassPathsExclusionSet.Add(FTopLevelAssetPath(DisallowedClassesFilterName)); } } return SNew(SBox) .HeightOverride(300.0f) .WidthOverride(300.0f) [ SNew(SBorder) .BorderImage( FAppStyle::GetBrush("Menu.Background") ) [ ContentBrowserModule.Get().CreateAssetPicker(AssetPickerConfig) ] ]; } void SGraphPinObject::OnAssetSelectedFromPicker(const struct FAssetData& AssetData) { if(GraphPinObj->IsPendingKill()) { return; } const FAssetData& CurrentAssetData = GetAssetData(true); if(CurrentAssetData != AssetData) { const FScopedTransaction Transaction( NSLOCTEXT("GraphEditor", "ChangeObjectPinValue", "Change Object Pin Value" ) ); GraphPinObj->Modify(); // Close the asset picker AssetPickerAnchor->SetIsOpen(false); // Set the object found from the asset picker GraphPinObj->GetSchema()->TrySetDefaultValue(*GraphPinObj, AssetData.GetObjectPathString()); } } void SGraphPinObject::OnAssetEnterPressedInPicker(const TArray& InSelectedAssets) { if(InSelectedAssets.Num() > 0) { OnAssetSelectedFromPicker(InSelectedAssets[0]); } } bool SGraphPinObject::ShouldDisplayAsSelfPin() const { if (AllowSelfPinWidget() && GraphPinObj) { if (GraphPinObj->GetSchema()->IsSelfPin(*GraphPinObj)) { return true; } // Also check function/node metadata const FString DefaultToSelfString = GraphPinObj->GetOwningNode()->GetPinMetaData(GraphPinObj->PinName, FBlueprintMetadata::MD_DefaultToSelf); if (DefaultToSelfString == GraphPinObj->PinName.ToString()) { return true; } } return false; } FText SGraphPinObject::GetValue() const { const FAssetData& CurrentAssetData = GetAssetData(true); FText Value; if (CurrentAssetData.IsValid()) { Value = FText::FromString(CurrentAssetData.GetFullName()); } else { if (GraphPinObj->GetSchema()->IsSelfPin(*GraphPinObj)) { Value = FText::FromName(GraphPinObj->PinName); } else if (ShouldDisplayAsSelfPin()) { Value = FText::FromName(UEdGraphSchema_K2::PN_Self); } else { Value = FText::GetEmpty(); } } return Value; } FText SGraphPinObject::GetObjectName() const { FText Value = FText::GetEmpty(); if (GraphPinObj != NULL) { const FAssetData& CurrentAssetData = GetAssetData(true); if (CurrentAssetData.IsValid()) { Value = FText::FromString(CurrentAssetData.AssetName.ToString()); int32 StringLen = Value.ToString().Len(); //If string is too long, then truncate (eg. "abcdefgijklmnopq" is converted as "abcd...nopq") const int32 MaxAllowedLength = 16; if (StringLen > MaxAllowedLength) { //Take first 4 characters FString TruncatedStr(Value.ToString().Left(4)); TruncatedStr += FString( TEXT("...")); //Take last 4 characters TruncatedStr += Value.ToString().Right(4); Value = FText::FromString(TruncatedStr); } } } return Value; } FText SGraphPinObject::GetDefaultComboText() const { return LOCTEXT( "DefaultComboText", "Select Asset" ); } FText SGraphPinObject::OnGetComboTextValue() const { FText Value = GetDefaultComboText(); if (GraphPinObj != nullptr) { const FAssetData& CurrentAssetData = GetAssetData(true); UObject* DefaultObject = GraphPinObj->DefaultObject; if (UField* Field = Cast(DefaultObject)) { Value = Field->GetDisplayNameText(); } else if (CurrentAssetData.IsValid()) { Value = FText::FromString(CurrentAssetData.AssetName.ToString()); } } return Value; } FSlateColor SGraphPinObject::OnGetComboForeground() const { float Alpha = (IsHovered() || bOnlyShowDefaultValue) ? GraphPinObjectDefs::ActiveComboAlpha : GraphPinObjectDefs::InActiveComboAlpha; return FSlateColor( FLinearColor( 1.f, 1.f, 1.f, Alpha ) ); } FSlateColor SGraphPinObject::OnGetWidgetForeground() const { float Alpha = (IsHovered() || bOnlyShowDefaultValue) ? GraphPinObjectDefs::ActivePinForegroundAlpha : GraphPinObjectDefs::InactivePinForegroundAlpha; return FSlateColor( FLinearColor( 1.f, 1.f, 1.f, Alpha ) ); } FSlateColor SGraphPinObject::OnGetWidgetBackground() const { float Alpha = (IsHovered() || bOnlyShowDefaultValue) ? GraphPinObjectDefs::ActivePinBackgroundAlpha : GraphPinObjectDefs::InactivePinBackgroundAlpha; return FSlateColor( FLinearColor( 1.f, 1.f, 1.f, Alpha ) ); } const FAssetData& SGraphPinObject::GetAssetData(bool bRuntimePath) const { // For normal assets, the editor and runtime path are the same if (GraphPinObj->DefaultObject) { if (!GraphPinObj->DefaultObject->GetPathName().Equals(CachedAssetData.GetObjectPathString(), ESearchCase::CaseSensitive)) { // This always uses the exact object pointed at CachedAssetData = FAssetData(GraphPinObj->DefaultObject, true); } } else if (!GraphPinObj->DefaultValue.IsEmpty()) { FSoftObjectPath ObjectPath = FSoftObjectPath(GraphPinObj->DefaultValue); if (ObjectPath != CachedAssetData.GetSoftObjectPath()) { const FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked(TEXT("AssetRegistry")); CachedAssetData = AssetRegistryModule.Get().GetAssetByObjectPath(ObjectPath); if (!CachedAssetData.IsValid()) { FString PackageName = FPackageName::ObjectPathToPackageName(GraphPinObj->DefaultValue); FString PackagePath = FPackageName::GetLongPackagePath(PackageName); FString ObjectName = FPackageName::ObjectPathToObjectName(GraphPinObj->DefaultValue); // Fake one CachedAssetData = FAssetData(FName(*PackageName), FName(*PackagePath), FName(*ObjectName), UObject::StaticClass()->GetClassPathName()); } } } else { if (CachedAssetData.IsValid()) { CachedAssetData = FAssetData(); } } return CachedAssetData; } #undef LOCTEXT_NAMESPACE