├── ComponentPicker.cpp ├── ComponentPicker.h ├── ComponentPickerCustomization.cpp ├── ComponentPickerCustomization.h ├── LICENSE ├── README.md ├── SComponentPicker.cpp └── SComponentPicker.h /ComponentPicker.cpp: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | #include "ComponentPicker.h" 4 | 5 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 6 | FComponentPicker::FComponentPicker( UActorComponent* pComponent ) 7 | : m_pPickedComponent( pComponent ) 8 | { 9 | // Empty 10 | } 11 | 12 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 13 | UActorComponent* FComponentPicker::GetComponent( ) const 14 | { 15 | return m_pPickedComponent.IsValid( ) ? m_pPickedComponent.Get( ) : nullptr; 16 | } 17 | 18 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 19 | bool FComponentPicker::operator==( const FComponentPicker& rOther ) const 20 | { 21 | return m_pPickedComponent == rOther.m_pPickedComponent; 22 | } 23 | -------------------------------------------------------------------------------- /ComponentPicker.h: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | #pragma once 4 | 5 | #include "ComponentPicker.generated.h" 6 | 7 | class UActorComponent; 8 | 9 | // UPROPERTY's that have this type will display a component picker in the editor, allowing users to select a component 10 | // from an actor in the scene. 11 | USTRUCT( ) 12 | struct FComponentPicker 13 | { 14 | GENERATED_BODY( ) 15 | 16 | // Default constructor 17 | FComponentPicker( ) = default; 18 | 19 | // Construct with default component selected 20 | FComponentPicker( UActorComponent* pComponent ); 21 | 22 | // Get the component that was picked from the scene 23 | UActorComponent* GetComponent( ) const; 24 | 25 | // Comparison operator 26 | bool operator== ( const FComponentPicker& rOther ) const; 27 | 28 | private: 29 | // The component that has been picked from the scene 30 | UPROPERTY( ) 31 | TWeakObjectPtr m_pPickedComponent = nullptr; 32 | }; -------------------------------------------------------------------------------- /ComponentPickerCustomization.cpp: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | #include "ComponentPickerCustomization.h" 4 | #include "ComponentPicker.h" 5 | #include "SComponentPicker.h" 6 | 7 | #include "DetailLayoutBuilder.h" 8 | #include "Engine/LevelScriptActor.h" 9 | #include "IDetailChildrenBuilder.h" 10 | #include "Kismet2/ComponentEditorUtils.h" 11 | #include "Styling/SlateIconFinder.h" 12 | #include "Widgets/Layout/SWidgetSwitcher.h" 13 | 14 | static const FName NAME_AllowAnyActor = "AllowAnyActor"; 15 | static const FName NAME_AllowedClasses = "AllowedClasses"; 16 | static const FName NAME_DisallowedClasses = "DisallowedClasses"; 17 | 18 | #define LOCTEXT_NAMESPACE "ComponentPickerCustomization" 19 | 20 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 21 | TSharedRef FComponentPickerCustomization::MakeInstance( ) 22 | { 23 | return MakeShareable( new FComponentPickerCustomization ); 24 | } 25 | 26 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 27 | void FComponentPickerCustomization::CustomizeHeader( TSharedRef pInPropertyHandle, 28 | FDetailWidgetRow& rHeaderRow, 29 | IPropertyTypeCustomizationUtils& rCustomizationUtils ) 30 | { 31 | m_pPropertyHandle = pInPropertyHandle; 32 | 33 | m_pCachedComponent.Reset( ); 34 | m_pCachedFirstOuterActor.Reset( ); 35 | m_eCachedPropertyAccess = FPropertyAccess::Fail; 36 | 37 | FProperty* pProperty = pInPropertyHandle->GetProperty( ); 38 | 39 | check( CastField( pProperty ) && 40 | FComponentPicker::StaticStruct( ) == CastFieldChecked( pProperty )->Struct ); 41 | 42 | m_bAllowClear = !( pInPropertyHandle->GetMetaDataProperty( )->PropertyFlags & CPF_NoClear ); 43 | m_bAllowAnyActor = pInPropertyHandle->HasMetaData( NAME_AllowAnyActor ); 44 | 45 | BuildClassFilters( ); 46 | BuildComboBox( ); 47 | 48 | pInPropertyHandle->SetOnPropertyValueChanged( 49 | FSimpleDelegate::CreateSP( this, &FComponentPickerCustomization::OnPropertyValueChanged ) ); 50 | 51 | // set cached values 52 | { 53 | m_pCachedComponent.Reset( ); 54 | m_pCachedFirstOuterActor = GetFirstOuterActor( ); 55 | 56 | FComponentPicker TmpComponentReference; 57 | m_eCachedPropertyAccess = GetValue( TmpComponentReference ); 58 | 59 | if( m_eCachedPropertyAccess == FPropertyAccess::Success ) 60 | { 61 | m_pCachedComponent = TmpComponentReference.GetComponent( ); 62 | 63 | if( !IsComponentPickerValid( TmpComponentReference ) ) 64 | { 65 | m_pCachedComponent.Reset( ); 66 | } 67 | } 68 | } 69 | 70 | rHeaderRow.NameContent( ) 71 | [ 72 | pInPropertyHandle->CreatePropertyNameWidget( ) 73 | ] 74 | .ValueContent( ) 75 | [ 76 | m_pComponentComboButton.ToSharedRef( ) 77 | ] 78 | .IsEnabled( MakeAttributeSP( this, &FComponentPickerCustomization::CanEdit ) ); 79 | } 80 | 81 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 82 | void FComponentPickerCustomization::CustomizeChildren( TSharedRef pInPropertyHandle, 83 | IDetailChildrenBuilder& rStructBuilder, 84 | IPropertyTypeCustomizationUtils& rCustomizationUtils ) 85 | { 86 | uint32 unNumberOfChild; 87 | 88 | if( pInPropertyHandle->GetNumChildren( unNumberOfChild ) == FPropertyAccess::Success ) 89 | { 90 | for( uint32 unIndex = 0; unIndex < unNumberOfChild; ++unIndex ) 91 | { 92 | TSharedRef pChildPropertyHandle = 93 | pInPropertyHandle->GetChildHandle( unIndex ).ToSharedRef( ); 94 | 95 | pChildPropertyHandle->SetOnPropertyValueChanged( 96 | FSimpleDelegate::CreateSP( this, &FComponentPickerCustomization::OnPropertyValueChanged ) ); 97 | 98 | rStructBuilder.AddProperty( pChildPropertyHandle ) 99 | .ShowPropertyButtons( true ) 100 | .IsEnabled( MakeAttributeSP( this, &FComponentPickerCustomization::CanEditChildren ) ); 101 | } 102 | } 103 | } 104 | 105 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 106 | void FComponentPickerCustomization::BuildClassFilters( ) 107 | { 108 | auto oAddToClassFilters = [this]( const UClass* Class, 109 | TArray& ActorList, 110 | TArray& ComponentList ) 111 | { 112 | if( m_bAllowAnyActor && Class->IsChildOf( AActor::StaticClass( ) ) ) 113 | { 114 | ActorList.Add( Class ); 115 | } 116 | else if( Class->IsChildOf( UActorComponent::StaticClass( ) ) ) 117 | { 118 | ComponentList.Add( Class ); 119 | } 120 | }; 121 | 122 | auto oParseClassFilters = [this, oAddToClassFilters]( const FString& MetaDataString, 123 | TArray& ActorList, 124 | TArray& ComponentList ) 125 | { 126 | if( !MetaDataString.IsEmpty( ) ) 127 | { 128 | TArray ClassFilterNames; 129 | MetaDataString.ParseIntoArrayWS( ClassFilterNames, TEXT( "," ), true ); 130 | 131 | for( const FString& ClassName : ClassFilterNames ) 132 | { 133 | UClass* Class = FindObject( ANY_PACKAGE, *ClassName ); 134 | 135 | if( !Class ) 136 | { 137 | Class = LoadObject( nullptr, *ClassName ); 138 | } 139 | 140 | if( Class ) 141 | { 142 | // If the class is an interface, expand it to be all classes in memory that implement the class. 143 | if( Class->HasAnyClassFlags( CLASS_Interface ) ) 144 | { 145 | for( TObjectIterator ClassIt; ClassIt; ++ClassIt ) 146 | { 147 | UClass* const ClassWithInterface = ( *ClassIt ); 148 | 149 | if( ClassWithInterface->ImplementsInterface( Class ) ) 150 | { 151 | oAddToClassFilters( ClassWithInterface, ActorList, ComponentList ); 152 | } 153 | } 154 | } 155 | else 156 | { 157 | oAddToClassFilters( Class, ActorList, ComponentList ); 158 | } 159 | } 160 | } 161 | } 162 | }; 163 | 164 | // Account for the allowed classes specified in the property metadata 165 | const FString& strAllowedClassesFilterString = m_pPropertyHandle->GetMetaData( NAME_AllowedClasses ); 166 | oParseClassFilters( strAllowedClassesFilterString, 167 | m_oAllowedActorClassFilters, 168 | m_oAllowedComponentClassFilters ); 169 | 170 | const FString& strDisallowedClassesFilterString = m_pPropertyHandle->GetMetaData( NAME_DisallowedClasses ); 171 | oParseClassFilters( strDisallowedClassesFilterString, 172 | m_oDisallowedActorClassFilters, 173 | m_oDisallowedComponentClassFilters ); 174 | } 175 | 176 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 177 | void FComponentPickerCustomization::BuildComboBox( ) 178 | { 179 | TAttribute bIsEnabledAttribute( this, &FComponentPickerCustomization::CanEdit ); 180 | TAttribute strTooltipAttribute; 181 | 182 | if( m_pPropertyHandle->GetMetaDataProperty( )->HasAnyPropertyFlags( CPF_EditConst | CPF_DisableEditOnTemplate ) ) 183 | { 184 | TArray oObjectList; 185 | m_pPropertyHandle->GetOuterObjects( oObjectList ); 186 | 187 | // If there is no objects, that means we must have a struct asset managing this property 188 | if( oObjectList.Num( ) == 0 ) 189 | { 190 | bIsEnabledAttribute.Set( false ); 191 | strTooltipAttribute.Set( LOCTEXT( "VariableHasDisableEditOnTemplate", 192 | "Editing this value in structure's defaults is not allowed" ) ); 193 | } 194 | else 195 | { 196 | // Go through all the found objects and see if any are a CDO, we can't set an actor in a CDO default. 197 | for( UObject* pObj : oObjectList ) 198 | { 199 | if( pObj->IsTemplate( ) && !pObj->IsA( ) ) 200 | { 201 | bIsEnabledAttribute.Set( false ); 202 | strTooltipAttribute.Set( LOCTEXT( "VariableHasDisableEditOnTemplateTooltip", 203 | "Editing this value in a Class Default Object is not allowed" ) ); 204 | break; 205 | } 206 | } 207 | } 208 | } 209 | 210 | TSharedRef pObjectContent = SNew( SVerticalBox ); 211 | 212 | if( m_bAllowAnyActor ) 213 | { 214 | pObjectContent->AddSlot( ) 215 | [ 216 | SNew( SHorizontalBox ) 217 | + SHorizontalBox::Slot( ) 218 | .AutoWidth( ) 219 | .HAlign( HAlign_Left ) 220 | .VAlign( VAlign_Center ) 221 | [ 222 | SNew( SImage ) 223 | .Image( this, &FComponentPickerCustomization::GetActorIcon ) 224 | ] 225 | + SHorizontalBox::Slot( ) 226 | .FillWidth( 1 ) 227 | .VAlign( VAlign_Center ) 228 | [ 229 | // Show the name of the asset or actor 230 | SNew( STextBlock ) 231 | .Font( IDetailLayoutBuilder::GetDetailFont( ) ) 232 | .Text( this, &FComponentPickerCustomization::OnGetActorName ) 233 | ] 234 | ]; 235 | } 236 | 237 | pObjectContent->AddSlot( ) 238 | [ 239 | SNew( SHorizontalBox ) 240 | + SHorizontalBox::Slot( ) 241 | .AutoWidth( ) 242 | .HAlign( HAlign_Left ) 243 | .VAlign( VAlign_Center ) 244 | [ 245 | SNew( SImage ) 246 | .Image( this, &FComponentPickerCustomization::GetComponentIcon ) 247 | ] 248 | + SHorizontalBox::Slot( ) 249 | .FillWidth( 1 ) 250 | .VAlign( VAlign_Center ) 251 | [ 252 | // Show the name of the asset or actor 253 | SNew( STextBlock ) 254 | .Font( IDetailLayoutBuilder::GetDetailFont( ) ) 255 | .Text( this, &FComponentPickerCustomization::OnGetComponentName ) 256 | ] 257 | ]; 258 | 259 | TSharedRef pComboButtonContent = SNew( SHorizontalBox ) 260 | + SHorizontalBox::Slot( ) 261 | .AutoWidth( ) 262 | .HAlign( HAlign_Left ) 263 | .VAlign( VAlign_Center ) 264 | [ 265 | SNew( SImage ) 266 | .Image( this, &FComponentPickerCustomization::GetStatusIcon ) 267 | ] 268 | + SHorizontalBox::Slot( ) 269 | .FillWidth( 1 ) 270 | .VAlign( VAlign_Center ) 271 | [ 272 | pObjectContent 273 | ]; 274 | 275 | m_pComponentComboButton = SNew( SComboButton ) 276 | .ToolTipText( strTooltipAttribute ) 277 | .ButtonStyle( FEditorStyle::Get( ), "PropertyEditor.AssetComboStyle" ) 278 | .ForegroundColor( FEditorStyle::GetColor( "PropertyEditor.AssetName.ColorAndOpacity" ) ) 279 | .OnGetMenuContent( this, &FComponentPickerCustomization::OnGetMenuContent ) 280 | .OnMenuOpenChanged( this, &FComponentPickerCustomization::OnMenuOpenChanged ) 281 | .IsEnabled( bIsEnabledAttribute ) 282 | .ContentPadding( 2.0f ) 283 | .ButtonContent( ) 284 | [ 285 | SNew( SWidgetSwitcher ) 286 | .WidgetIndex( this, &FComponentPickerCustomization::OnGetComboContentWidgetIndex ) 287 | + SWidgetSwitcher::Slot( ) 288 | [ 289 | SNew( STextBlock ) 290 | .Text( LOCTEXT( "MultipleValuesText", "" ) ) 291 | .Font( IDetailLayoutBuilder::GetDetailFont( ) ) 292 | ] 293 | + SWidgetSwitcher::Slot( ) 294 | [ 295 | pComboButtonContent 296 | ] 297 | ]; 298 | } 299 | 300 | 301 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 302 | AActor* FComponentPickerCustomization::GetFirstOuterActor( ) const 303 | { 304 | TArray oObjectList; 305 | m_pPropertyHandle->GetOuterObjects( oObjectList ); 306 | 307 | for( UObject* pObj : oObjectList ) 308 | { 309 | while( pObj ) 310 | { 311 | if( AActor* pActor = Cast( pObj ) ) 312 | { 313 | return pActor; 314 | } 315 | 316 | if( UActorComponent* pComponent = Cast( pObj ) ) 317 | { 318 | if( pComponent->GetOwner( ) ) 319 | { 320 | return pComponent->GetOwner( ); 321 | } 322 | } 323 | 324 | pObj = pObj->GetOuter( ); 325 | } 326 | } 327 | 328 | return nullptr; 329 | } 330 | 331 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 332 | void FComponentPickerCustomization::SetValue( const FComponentPicker& rValue ) 333 | { 334 | m_pComponentComboButton->SetIsOpen( false ); 335 | 336 | const bool bIsEmpty = rValue.GetComponent( ) == nullptr; 337 | const bool bAllowedToSetBasedOnFilter = IsComponentPickerValid( rValue ); 338 | 339 | if( bIsEmpty || bAllowedToSetBasedOnFilter ) 340 | { 341 | FString strTextValue; 342 | CastFieldChecked( m_pPropertyHandle->GetProperty( ) )->Struct->ExportText( 343 | strTextValue, 344 | &rValue, 345 | &rValue, 346 | nullptr, 347 | EPropertyPortFlags::PPF_None, 348 | nullptr ); 349 | ensure( m_pPropertyHandle->SetValueFromFormattedString( strTextValue ) == FPropertyAccess::Result::Success ); 350 | } 351 | } 352 | 353 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 354 | FPropertyAccess::Result FComponentPickerCustomization::GetValue( FComponentPicker& rOutValue ) const 355 | { 356 | // Potentially accessing the value while garbage collecting or saving the package could trigger a crash. 357 | // so we fail to get the value when that is occurring. 358 | if( GIsSavingPackage || IsGarbageCollecting( ) ) 359 | { 360 | return FPropertyAccess::Fail; 361 | } 362 | 363 | FPropertyAccess::Result eResult = FPropertyAccess::Fail; 364 | 365 | if( m_pPropertyHandle.IsValid( ) && m_pPropertyHandle->IsValidHandle( ) ) 366 | { 367 | TArray oRawData; 368 | m_pPropertyHandle->AccessRawData( oRawData ); 369 | const UActorComponent* pCurrentComponent = nullptr; 370 | 371 | for( const void* pRawPtr : oRawData ) 372 | { 373 | if( pRawPtr ) 374 | { 375 | const FComponentPicker& rThisReference = *reinterpret_cast( pRawPtr ); 376 | 377 | if( eResult == FPropertyAccess::Success ) 378 | { 379 | if( rThisReference.GetComponent( ) != pCurrentComponent ) 380 | { 381 | eResult = FPropertyAccess::MultipleValues; 382 | break; 383 | } 384 | } 385 | else 386 | { 387 | rOutValue = rThisReference; 388 | pCurrentComponent = rOutValue.GetComponent( ); 389 | eResult = FPropertyAccess::Success; 390 | } 391 | } 392 | else if( eResult == FPropertyAccess::Success ) 393 | { 394 | eResult = FPropertyAccess::MultipleValues; 395 | break; 396 | } 397 | } 398 | } 399 | 400 | return eResult; 401 | } 402 | 403 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 404 | bool FComponentPickerCustomization::IsComponentPickerValid( const FComponentPicker& rValue ) const 405 | { 406 | if( !m_bAllowAnyActor && 407 | rValue.GetComponent( ) != nullptr && 408 | ( rValue.GetComponent( )->GetOwner( ) != m_pCachedFirstOuterActor.Get( ) ) ) 409 | { 410 | return false; 411 | } 412 | 413 | AActor* pCachedActor = m_pCachedFirstOuterActor.Get( ); 414 | 415 | if( const UActorComponent* pNewComponent = rValue.GetComponent( ) ) 416 | { 417 | if( !IsFilteredComponent( pNewComponent ) ) 418 | { 419 | return false; 420 | } 421 | 422 | if( m_bAllowAnyActor ) 423 | { 424 | if( pNewComponent->GetOwner( ) == nullptr ) 425 | { 426 | return false; 427 | } 428 | 429 | 430 | TArray oObjectList; 431 | m_pPropertyHandle->GetOuterObjects( oObjectList ); 432 | 433 | // Is the Outer object in the same world/level 434 | for( UObject* pObj : oObjectList ) 435 | { 436 | AActor* pActor = Cast( pObj ); 437 | 438 | if( pActor == nullptr ) 439 | { 440 | if( UActorComponent* pActorComponent = Cast( pObj ) ) 441 | { 442 | pActor = pActorComponent->GetOwner( ); 443 | } 444 | } 445 | 446 | if( pActor ) 447 | { 448 | if( pNewComponent->GetOwner( )->GetLevel( ) != pActor->GetLevel( ) ) 449 | { 450 | return false; 451 | } 452 | } 453 | } 454 | } 455 | } 456 | 457 | return true; 458 | } 459 | 460 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 461 | void FComponentPickerCustomization::OnPropertyValueChanged( ) 462 | { 463 | m_pCachedComponent.Reset( ); 464 | m_pCachedFirstOuterActor = GetFirstOuterActor( ); 465 | 466 | FComponentPicker oTmpComponentReference; 467 | m_eCachedPropertyAccess = GetValue( oTmpComponentReference ); 468 | 469 | if( m_eCachedPropertyAccess == FPropertyAccess::Success ) 470 | { 471 | m_pCachedComponent = oTmpComponentReference.GetComponent( ); 472 | 473 | if( !IsComponentPickerValid( oTmpComponentReference ) ) 474 | { 475 | m_pCachedComponent.Reset( ); 476 | 477 | if( !( oTmpComponentReference == FComponentPicker( ) ) ) 478 | { 479 | SetValue( FComponentPicker( ) ); 480 | } 481 | } 482 | } 483 | } 484 | 485 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 486 | int32 FComponentPickerCustomization::OnGetComboContentWidgetIndex( ) const 487 | { 488 | switch( m_eCachedPropertyAccess ) 489 | { 490 | case FPropertyAccess::MultipleValues: 491 | { 492 | return 0; 493 | } 494 | case FPropertyAccess::Success: 495 | default: 496 | { 497 | return 1; 498 | } 499 | } 500 | } 501 | 502 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 503 | bool FComponentPickerCustomization::CanEdit( ) const 504 | { 505 | return m_pPropertyHandle.IsValid( ) ? !m_pPropertyHandle->IsEditConst( ) : true; 506 | } 507 | 508 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 509 | bool FComponentPickerCustomization::CanEditChildren( ) const 510 | { 511 | return CanEdit( ) && !m_pCachedFirstOuterActor.IsValid( ); 512 | } 513 | 514 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 515 | const FSlateBrush* FComponentPickerCustomization::GetActorIcon( ) const 516 | { 517 | if( const UActorComponent* pComponent = m_pCachedComponent.Get( ) ) 518 | { 519 | if( AActor* pOwner = pComponent->GetOwner( ) ) 520 | { 521 | return FSlateIconFinder::FindIconBrushForClass( pOwner->GetClass( ) ); 522 | } 523 | } 524 | 525 | return FSlateIconFinder::FindIconBrushForClass( AActor::StaticClass( ) );; 526 | } 527 | 528 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 529 | FText FComponentPickerCustomization::OnGetActorName( ) const 530 | { 531 | if( const UActorComponent* pComponent = m_pCachedComponent.Get( ) ) 532 | { 533 | if( AActor* pOwner = pComponent->GetOwner( ) ) 534 | { 535 | return FText::AsCultureInvariant( pOwner->GetActorLabel( ) ); 536 | } 537 | } 538 | 539 | return LOCTEXT( "NoActor", "None" ); 540 | } 541 | 542 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 543 | const FSlateBrush* FComponentPickerCustomization::GetComponentIcon( ) const 544 | { 545 | if( const UActorComponent* pActorComponent = m_pCachedComponent.Get( ) ) 546 | { 547 | return FSlateIconFinder::FindIconBrushForClass( pActorComponent->GetClass( ) ); 548 | } 549 | 550 | return FSlateIconFinder::FindIconBrushForClass( UActorComponent::StaticClass( ) ); 551 | } 552 | 553 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 554 | FText FComponentPickerCustomization::OnGetComponentName( ) const 555 | { 556 | if( m_eCachedPropertyAccess == FPropertyAccess::Success ) 557 | { 558 | if( const UActorComponent* pActorComponent = m_pCachedComponent.Get( ) ) 559 | { 560 | const FName strComponentName = 561 | FComponentEditorUtils::FindVariableNameGivenComponentInstance( pActorComponent ); 562 | 563 | const bool bIsArrayVariable = !strComponentName.IsNone( ) && 564 | pActorComponent->GetOwner( ) != nullptr && 565 | FindFProperty( pActorComponent->GetOwner( )->GetClass( ), strComponentName ); 566 | 567 | if( !strComponentName.IsNone( ) && !bIsArrayVariable ) 568 | { 569 | return FText::FromName( strComponentName ); 570 | } 571 | 572 | return FText::AsCultureInvariant( pActorComponent->GetName( ) ); 573 | } 574 | } 575 | else if( m_eCachedPropertyAccess == FPropertyAccess::MultipleValues ) 576 | { 577 | return LOCTEXT( "MultipleValues", "Multiple Values" ); 578 | } 579 | 580 | return LOCTEXT( "NoComponent", "None" ); 581 | } 582 | 583 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 584 | const FSlateBrush* FComponentPickerCustomization::GetStatusIcon( ) const 585 | { 586 | static FSlateNoResource EmptyBrush = FSlateNoResource( ); 587 | 588 | if( m_eCachedPropertyAccess == FPropertyAccess::Fail ) 589 | { 590 | return FEditorStyle::GetBrush( "Icons.Error" ); 591 | } 592 | 593 | return &EmptyBrush; 594 | } 595 | 596 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 597 | TSharedRef FComponentPickerCustomization::OnGetMenuContent( ) 598 | { 599 | UActorComponent* pInitialComponent = m_pCachedComponent.Get( ); 600 | 601 | return SNew( SComponentPicker ) 602 | .pInitialComponent( pInitialComponent ) 603 | .bAllowClear( m_bAllowClear ) 604 | .oActorFilter( FOnShouldFilterActor::CreateSP( this, &FComponentPickerCustomization::IsAllowedActor ) ) 605 | .oComponentFilter( FOnShouldFilterComponent::CreateSP( this, &FComponentPickerCustomization::IsFilteredComponent ) ) 606 | .oOnSet( FOnComponentPicked::CreateSP( this, &FComponentPickerCustomization::OnComponentSelected ) ) 607 | .oOnClose( FSimpleDelegate::CreateSP( this, &FComponentPickerCustomization::CloseComboButton ) ); 608 | } 609 | 610 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 611 | void FComponentPickerCustomization::OnMenuOpenChanged( bool bOpen ) 612 | { 613 | if( !bOpen ) 614 | { 615 | m_pComponentComboButton->SetMenuContent( SNullWidget::NullWidget ); 616 | } 617 | } 618 | 619 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 620 | bool FComponentPickerCustomization::IsAllowedActor( const AActor* const pActor ) const 621 | { 622 | return m_bAllowAnyActor || pActor == m_pCachedFirstOuterActor.Get( ); 623 | } 624 | 625 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 626 | bool FComponentPickerCustomization::IsFilteredComponent( const UActorComponent* const pComponent ) const 627 | { 628 | const USceneComponent* pSceneComp = Cast( pComponent ); 629 | const AActor* pOuterActor = m_pCachedFirstOuterActor.Get( ); 630 | 631 | return pComponent->GetOwner( ) && 632 | ( IsAllowedActor( pComponent->GetOwner( ) ) ) && 633 | ( !m_bAllowAnyActor || ( pOuterActor != nullptr && 634 | pComponent->GetOwner( )->GetLevel( ) == pOuterActor->GetLevel( ) ) ) && 635 | FComponentEditorUtils::CanEditComponentInstance( pComponent, pSceneComp, false ) && 636 | IsFilteredObject( pComponent, m_oAllowedComponentClassFilters, m_oDisallowedComponentClassFilters ) && 637 | IsFilteredObject( pComponent->GetOwner( ), m_oAllowedActorClassFilters, m_oDisallowedActorClassFilters ); 638 | } 639 | 640 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 641 | bool FComponentPickerCustomization::IsFilteredObject( const UObject* const pObject, 642 | const TArray& rAllowedFilters, 643 | const TArray& rDisallowedFilters ) 644 | { 645 | bool bAllowedToSetBasedOnFilter = true; 646 | 647 | const UClass* pObjectClass = pObject->GetClass( ); 648 | 649 | if( rAllowedFilters.Num( ) > 0 ) 650 | { 651 | bAllowedToSetBasedOnFilter = false; 652 | 653 | for( const UClass* pAllowedClass : rAllowedFilters ) 654 | { 655 | const bool bAllowedClassIsInterface = pAllowedClass->HasAnyClassFlags( CLASS_Interface ); 656 | 657 | if( pObjectClass->IsChildOf( pAllowedClass ) || 658 | ( bAllowedClassIsInterface && pObjectClass->ImplementsInterface( pAllowedClass ) ) ) 659 | { 660 | bAllowedToSetBasedOnFilter = true; 661 | break; 662 | } 663 | } 664 | } 665 | 666 | if( rDisallowedFilters.Num( ) > 0 && bAllowedToSetBasedOnFilter ) 667 | { 668 | for( const UClass* pDisallowedClass : rDisallowedFilters ) 669 | { 670 | const bool bDisallowedClassIsInterface = pDisallowedClass->HasAnyClassFlags( CLASS_Interface ); 671 | 672 | if( pObjectClass->IsChildOf( pDisallowedClass ) || 673 | ( bDisallowedClassIsInterface && pObjectClass->ImplementsInterface( pDisallowedClass ) ) ) 674 | { 675 | bAllowedToSetBasedOnFilter = false; 676 | break; 677 | } 678 | } 679 | } 680 | 681 | return bAllowedToSetBasedOnFilter; 682 | } 683 | 684 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 685 | void FComponentPickerCustomization::OnComponentSelected( UActorComponent* pInComponent ) 686 | { 687 | m_pComponentComboButton->SetIsOpen( false ); 688 | FComponentPicker oComponentReference( pInComponent ); 689 | SetValue( oComponentReference ); 690 | } 691 | 692 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 693 | void FComponentPickerCustomization::CloseComboButton( ) 694 | { 695 | m_pComponentComboButton->SetIsOpen( false ); 696 | } 697 | 698 | #undef LOCTEXT_NAMESPACE 699 | -------------------------------------------------------------------------------- /ComponentPickerCustomization.h: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | #pragma once 4 | 5 | class SComboButton; 6 | class SWidget; 7 | struct FSlateBrush; 8 | struct FComponentPicker; 9 | 10 | class FComponentPickerCustomization : public IPropertyTypeCustomization 11 | { 12 | public: 13 | // Makes a new instance of this customization for a specific detail view requesting it. 14 | static TSharedRef MakeInstance( ); 15 | 16 | // START IPropertyTypeCustomization interface. 17 | virtual void CustomizeHeader( TSharedRef pInPropertyHandle, 18 | class FDetailWidgetRow& rHeaderRow, 19 | IPropertyTypeCustomizationUtils& rCustomizationUtils ) override; 20 | 21 | virtual void CustomizeChildren( TSharedRef pInPropertyHandle, 22 | class IDetailChildrenBuilder& rStructBuilder, 23 | IPropertyTypeCustomizationUtils& rCustomizationUtils ) override; 24 | // END IPropertyTypeCustomization interface. 25 | 26 | private: 27 | // From the property metadata, build the list of allowed and disallowed classes. 28 | void BuildClassFilters( ); 29 | 30 | // Build the combobox widget. 31 | void BuildComboBox( ); 32 | 33 | // From the Detail panel outer hierarchy, find the first actor or component owner we find. 34 | AActor* GetFirstOuterActor( ) const; 35 | 36 | // Set the value of the asset referenced by this property editor. 37 | // Will set the underlying property handle if there is one. 38 | void SetValue( const FComponentPicker& Value ); 39 | 40 | // Get the value referenced by this widget. 41 | FPropertyAccess::Result GetValue( FComponentPicker& rOutValue ) const; 42 | 43 | // Is the Value valid. 44 | bool IsComponentPickerValid( const FComponentPicker& rValue ) const; 45 | 46 | // Callback when the property value changed. 47 | void OnPropertyValueChanged( ); 48 | 49 | private: 50 | // Return 0 if we have multiple values to edit. 51 | // Return 1 if we display the widget normally. 52 | int32 OnGetComboContentWidgetIndex( ) const; 53 | 54 | bool CanEdit( ) const; 55 | bool CanEditChildren( ) const; 56 | 57 | const FSlateBrush* GetActorIcon( ) const; 58 | FText OnGetActorName( ) const; 59 | const FSlateBrush* GetComponentIcon( ) const; 60 | FText OnGetComponentName( ) const; 61 | const FSlateBrush* GetStatusIcon( ) const; 62 | 63 | // Get the content to be displayed in the asset/actor picker menu 64 | TSharedRef OnGetMenuContent( ); 65 | 66 | // Called when the asset menu is closed, we handle this to force the destruction of the asset menu to 67 | // ensure any settings the user set are saved. 68 | void OnMenuOpenChanged( bool bOpen ); 69 | 70 | // Returns whether the actor/component should be filtered out from selection. 71 | bool IsAllowedActor( const AActor* const pActor ) const; 72 | bool IsFilteredComponent( const UActorComponent* const pComponent ) const; 73 | static bool IsFilteredObject( const UObject* const pObject, 74 | const TArray& rAllowedFilters, 75 | const TArray& rDisallowedFilters ); 76 | 77 | // Delegate for handling selection in the scene outliner. 78 | void OnComponentSelected( UActorComponent* pInComponent ); 79 | 80 | // Closes the combo button. 81 | void CloseComboButton( ); 82 | 83 | private: 84 | // The property handle we are customizing 85 | TSharedPtr m_pPropertyHandle; 86 | 87 | // Main combo button 88 | TSharedPtr m_pComponentComboButton; 89 | 90 | // Classes that can be used with this property 91 | TArray m_oAllowedActorClassFilters; 92 | TArray m_oAllowedComponentClassFilters; 93 | 94 | // Classes that can NOT be used with this property 95 | TArray m_oDisallowedActorClassFilters; 96 | TArray m_oDisallowedComponentClassFilters; 97 | 98 | // Whether the asset can be 'None' in this case 99 | bool m_bAllowClear; 100 | 101 | // Can the actor be different/selected. 102 | bool m_bAllowAnyActor; 103 | 104 | // Cached values 105 | TWeakObjectPtr m_pCachedFirstOuterActor; 106 | TWeakObjectPtr m_pCachedComponent; 107 | FPropertyAccess::Result m_eCachedPropertyAccess; 108 | }; 109 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Creative Commons Legal Code 2 | 3 | CC0 1.0 Universal 4 | 5 | CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE 6 | LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN 7 | ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS 8 | INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES 9 | REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS 10 | PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM 11 | THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED 12 | HEREUNDER. 13 | 14 | Statement of Purpose 15 | 16 | The laws of most jurisdictions throughout the world automatically confer 17 | exclusive Copyright and Related Rights (defined below) upon the creator 18 | and subsequent owner(s) (each and all, an "owner") of an original work of 19 | authorship and/or a database (each, a "Work"). 20 | 21 | Certain owners wish to permanently relinquish those rights to a Work for 22 | the purpose of contributing to a commons of creative, cultural and 23 | scientific works ("Commons") that the public can reliably and without fear 24 | of later claims of infringement build upon, modify, incorporate in other 25 | works, reuse and redistribute as freely as possible in any form whatsoever 26 | and for any purposes, including without limitation commercial purposes. 27 | These owners may contribute to the Commons to promote the ideal of a free 28 | culture and the further production of creative, cultural and scientific 29 | works, or to gain reputation or greater distribution for their Work in 30 | part through the use and efforts of others. 31 | 32 | For these and/or other purposes and motivations, and without any 33 | expectation of additional consideration or compensation, the person 34 | associating CC0 with a Work (the "Affirmer"), to the extent that he or she 35 | is an owner of Copyright and Related Rights in the Work, voluntarily 36 | elects to apply CC0 to the Work and publicly distribute the Work under its 37 | terms, with knowledge of his or her Copyright and Related Rights in the 38 | Work and the meaning and intended legal effect of CC0 on those rights. 39 | 40 | 1. Copyright and Related Rights. A Work made available under CC0 may be 41 | protected by copyright and related or neighboring rights ("Copyright and 42 | Related Rights"). Copyright and Related Rights include, but are not 43 | limited to, the following: 44 | 45 | i. the right to reproduce, adapt, distribute, perform, display, 46 | communicate, and translate a Work; 47 | ii. moral rights retained by the original author(s) and/or performer(s); 48 | iii. publicity and privacy rights pertaining to a person's image or 49 | likeness depicted in a Work; 50 | iv. rights protecting against unfair competition in regards to a Work, 51 | subject to the limitations in paragraph 4(a), below; 52 | v. rights protecting the extraction, dissemination, use and reuse of data 53 | in a Work; 54 | vi. database rights (such as those arising under Directive 96/9/EC of the 55 | European Parliament and of the Council of 11 March 1996 on the legal 56 | protection of databases, and under any national implementation 57 | thereof, including any amended or successor version of such 58 | directive); and 59 | vii. other similar, equivalent or corresponding rights throughout the 60 | world based on applicable law or treaty, and any national 61 | implementations thereof. 62 | 63 | 2. Waiver. To the greatest extent permitted by, but not in contravention 64 | of, applicable law, Affirmer hereby overtly, fully, permanently, 65 | irrevocably and unconditionally waives, abandons, and surrenders all of 66 | Affirmer's Copyright and Related Rights and associated claims and causes 67 | of action, whether now known or unknown (including existing as well as 68 | future claims and causes of action), in the Work (i) in all territories 69 | worldwide, (ii) for the maximum duration provided by applicable law or 70 | treaty (including future time extensions), (iii) in any current or future 71 | medium and for any number of copies, and (iv) for any purpose whatsoever, 72 | including without limitation commercial, advertising or promotional 73 | purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each 74 | member of the public at large and to the detriment of Affirmer's heirs and 75 | successors, fully intending that such Waiver shall not be subject to 76 | revocation, rescission, cancellation, termination, or any other legal or 77 | equitable action to disrupt the quiet enjoyment of the Work by the public 78 | as contemplated by Affirmer's express Statement of Purpose. 79 | 80 | 3. Public License Fallback. Should any part of the Waiver for any reason 81 | be judged legally invalid or ineffective under applicable law, then the 82 | Waiver shall be preserved to the maximum extent permitted taking into 83 | account Affirmer's express Statement of Purpose. In addition, to the 84 | extent the Waiver is so judged Affirmer hereby grants to each affected 85 | person a royalty-free, non transferable, non sublicensable, non exclusive, 86 | irrevocable and unconditional license to exercise Affirmer's Copyright and 87 | Related Rights in the Work (i) in all territories worldwide, (ii) for the 88 | maximum duration provided by applicable law or treaty (including future 89 | time extensions), (iii) in any current or future medium and for any number 90 | of copies, and (iv) for any purpose whatsoever, including without 91 | limitation commercial, advertising or promotional purposes (the 92 | "License"). The License shall be deemed effective as of the date CC0 was 93 | applied by Affirmer to the Work. Should any part of the License for any 94 | reason be judged legally invalid or ineffective under applicable law, such 95 | partial invalidity or ineffectiveness shall not invalidate the remainder 96 | of the License, and in such case Affirmer hereby affirms that he or she 97 | will not (i) exercise any of his or her remaining Copyright and Related 98 | Rights in the Work or (ii) assert any associated claims and causes of 99 | action with respect to the Work, in either case contrary to Affirmer's 100 | express Statement of Purpose. 101 | 102 | 4. Limitations and Disclaimers. 103 | 104 | a. No trademark or patent rights held by Affirmer are waived, abandoned, 105 | surrendered, licensed or otherwise affected by this document. 106 | b. Affirmer offers the Work as-is and makes no representations or 107 | warranties of any kind concerning the Work, express, implied, 108 | statutory or otherwise, including without limitation warranties of 109 | title, merchantability, fitness for a particular purpose, non 110 | infringement, or the absence of latent or other defects, accuracy, or 111 | the present or absence of errors, whether or not discoverable, all to 112 | the greatest extent permissible under applicable law. 113 | c. Affirmer disclaims responsibility for clearing rights of other persons 114 | that may apply to the Work or any use thereof, including without 115 | limitation any person's Copyright and Related Rights in the Work. 116 | Further, Affirmer disclaims responsibility for obtaining any necessary 117 | consents, permissions or other rights required for any use of the 118 | Work. 119 | d. Affirmer understands and acknowledges that Creative Commons is not a 120 | party to this document and has no duty or obligation with respect to 121 | this CC0 or use of the Work. 122 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Unreal-Engine-Simple-Component-Picker 2 | A component picker that is simpler than the built-in FComponentReference. Full disclosure: This code appears to build, run, and serialize without problems on my machine, but I have not tested it in a full, packaged build. 3 | 4 | To use the component picker, add the source and header files in this repository to your project, then add the following lines to your game's module's StartupModule function: 5 | 6 | FPropertyEditorModule& rPropertyModule = FModuleManager::LoadModuleChecked( "PropertyEditor" ); 7 | 8 | rPropertyModule.RegisterCustomPropertyTypeLayout( 9 | "ComponentPicker", 10 | FOnGetPropertyTypeCustomizationInstance::CreateStatic( &FComponentPickerCustomization::MakeInstance ) ); 11 | 12 | Then you can add it as a property to your classes like this: 13 | 14 | UPROPERTY( EditInstanceOnly ) 15 | FComponentPicker m_oComponentPicker; 16 | 17 | It supports the following meta tags (which function the same as they do on FComponentReference): AllowAnyActor, AllowedClasses, and DisallowedClasses. For example: 18 | 19 | UPROPERTY( EditInstanceOnly, meta = ( AllowAnyActor, AllowedClasses = "PrimitiveComponent", DisallowedClasses = "SkeletalMeshComponent,BrushComponent"" ) ) 20 | FComponentPicker m_oComponentPicker; 21 | -------------------------------------------------------------------------------- /SComponentPicker.cpp: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | #include "SComponentPicker.h" 4 | 5 | #include "Editor/SceneOutliner/Public/SceneOutlinerModule.h" 6 | #include "HAL/PlatformApplicationMisc.h" 7 | #include "ActorTreeItem.h" 8 | #include "ComponentTreeItem.h" 9 | 10 | #define LOCTEXT_NAMESPACE "SComponentPicker" 11 | 12 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 13 | void SComponentPicker::Construct( const FArguments& rInArgs ) 14 | { 15 | m_pInitialComponent = rInArgs._pInitialComponent; 16 | m_bAllowClear = rInArgs._bAllowClear; 17 | m_oActorFilter = rInArgs._oActorFilter; 18 | m_oComponentFilter = rInArgs._oComponentFilter; 19 | m_oOnSet = rInArgs._oOnSet; 20 | m_oOnClose = rInArgs._oOnClose; 21 | 22 | FMenuBuilder MenuBuilder( true, NULL ); 23 | 24 | MenuBuilder.BeginSection( NAME_None, LOCTEXT( "CurrentComponentOperationsHeader", "Current Component" ) ); 25 | { 26 | if( m_pInitialComponent ) 27 | { 28 | MenuBuilder.AddMenuEntry( 29 | LOCTEXT( "EditComponent", "Edit" ), 30 | LOCTEXT( "EditComponent_Tooltip", "Edit this component" ), 31 | FSlateIcon( ), 32 | FUIAction( FExecuteAction::CreateSP( this, &SComponentPicker::OnEdit ) ) ); 33 | } 34 | 35 | MenuBuilder.AddMenuEntry( 36 | LOCTEXT( "CopyComponent", "Copy" ), 37 | LOCTEXT( "CopyComponent_Tooltip", "Copies the component to the clipboard" ), 38 | FSlateIcon( ), 39 | FUIAction( FExecuteAction::CreateSP( this, &SComponentPicker::OnCopy ) ) 40 | ); 41 | 42 | MenuBuilder.AddMenuEntry( 43 | LOCTEXT( "PasteComponent", "Paste" ), 44 | LOCTEXT( "PasteComponent_Tooltip", "Pastes an component from the clipboard to this field" ), 45 | FSlateIcon( ), 46 | FUIAction( 47 | FExecuteAction::CreateSP( this, &SComponentPicker::OnPaste ), 48 | FCanExecuteAction::CreateSP( this, &SComponentPicker::CanPaste ) ) 49 | ); 50 | 51 | if( m_bAllowClear ) 52 | { 53 | MenuBuilder.AddMenuEntry( 54 | LOCTEXT( "ClearComponent", "Clear" ), 55 | LOCTEXT( "ClearComponent_ToolTip", "Clears the component set on this field" ), 56 | FSlateIcon( ), 57 | FUIAction( FExecuteAction::CreateSP( this, &SComponentPicker::OnClear ) ) 58 | ); 59 | } 60 | } 61 | MenuBuilder.EndSection( ); 62 | 63 | MenuBuilder.BeginSection( NAME_None, LOCTEXT( "BrowseHeader", "Browse" ) ); 64 | { 65 | TSharedPtr MenuContent; 66 | 67 | FSceneOutlinerModule& SceneOutlinerModule = 68 | FModuleManager::Get( ).LoadModuleChecked( TEXT( "SceneOutliner" ) ); 69 | 70 | FSceneOutlinerInitializationOptions InitOptions; 71 | InitOptions.bFocusSearchBoxWhenOpened = true; 72 | 73 | struct FPickerFilter : public FSceneOutlinerFilter 74 | { 75 | FPickerFilter( const FOnShouldFilterActor& InActorFilter, const FOnShouldFilterComponent& InComponentFilter ) 76 | : FSceneOutlinerFilter( FSceneOutlinerFilter::EDefaultBehaviour::Fail ) 77 | , ActorFilter( InActorFilter ) 78 | , ComponentFilter( InComponentFilter ) 79 | { 80 | // Empty 81 | } 82 | 83 | virtual bool PassesFilter( const ISceneOutlinerTreeItem& InItem ) const override 84 | { 85 | if( const FActorTreeItem* ActorItem = InItem.CastTo( ) ) 86 | { 87 | return ActorItem->IsValid( ) && ActorFilter.Execute( ActorItem->Actor.Get( ) ); 88 | } 89 | else if( const FComponentTreeItem* ComponentItem = InItem.CastTo( ) ) 90 | { 91 | return ComponentItem->IsValid( ) && ComponentFilter.Execute( ComponentItem->Component.Get( ) ); 92 | } 93 | 94 | return DefaultBehaviour == FSceneOutlinerFilter::EDefaultBehaviour::Pass; 95 | } 96 | 97 | virtual bool GetInteractiveState( const ISceneOutlinerTreeItem& InItem ) const override 98 | { 99 | // All components which pass the filter are interactive 100 | if( const FComponentTreeItem* ComponentItem = InItem.CastTo( ) ) 101 | { 102 | return true; 103 | } 104 | 105 | return DefaultBehaviour == FSceneOutlinerFilter::EDefaultBehaviour::Pass; 106 | } 107 | 108 | FOnShouldFilterActor ActorFilter; 109 | FOnShouldFilterComponent ComponentFilter; 110 | }; 111 | 112 | TSharedRef Filter = MakeShared( m_oActorFilter, m_oComponentFilter ); 113 | InitOptions.Filters->Add( Filter ); 114 | 115 | InitOptions.ColumnMap.Add( FSceneOutlinerBuiltInColumnTypes::Label( ), 116 | FSceneOutlinerColumnInfo( ESceneOutlinerColumnVisibility::Visible, 0 ) ); 117 | 118 | // NOTE: Copied these constant width and height overrides from 119 | // Engine\Source\Editor\PropertyEditor\Private\UserInterface\PropertyEditor\PropertyEditorAssetConstants.h 120 | MenuContent = SNew( SBox ) 121 | .WidthOverride( 300.0f ) 122 | .HeightOverride( 300.0f ) 123 | [ 124 | SNew( SBorder ) 125 | .BorderImage( FEditorStyle::GetBrush( "Menu.Background" ) ) 126 | [ 127 | SceneOutlinerModule.CreateComponentPicker( 128 | InitOptions, FOnComponentPicked::CreateSP( this, &SComponentPicker::OnItemSelected ) ) 129 | ] 130 | ]; 131 | 132 | MenuBuilder.AddWidget( MenuContent.ToSharedRef( ), FText::GetEmpty( ), true ); 133 | 134 | } 135 | MenuBuilder.EndSection( ); 136 | 137 | ChildSlot 138 | [ 139 | MenuBuilder.MakeWidget( ) 140 | ]; 141 | } 142 | 143 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 144 | void SComponentPicker::OnEdit( ) 145 | { 146 | if( m_pInitialComponent ) 147 | { 148 | GEditor->EditObject( m_pInitialComponent ); 149 | } 150 | 151 | m_oOnClose.ExecuteIfBound( ); 152 | } 153 | 154 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 155 | void SComponentPicker::OnCopy( ) 156 | { 157 | if( m_pInitialComponent ) 158 | { 159 | FPlatformApplicationMisc::ClipboardCopy( *FString::Printf( TEXT( "%s %s" ), 160 | *m_pInitialComponent->GetClass( )->GetPathName( ), 161 | *m_pInitialComponent->GetPathName( ) ) ); 162 | } 163 | 164 | m_oOnClose.ExecuteIfBound( ); 165 | } 166 | 167 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 168 | void SComponentPicker::OnPaste( ) 169 | { 170 | FString ClipboardText; 171 | FPlatformApplicationMisc::ClipboardPaste( ClipboardText ); 172 | 173 | bool bFound = false; 174 | 175 | int32 SpaceIndex = INDEX_NONE; 176 | 177 | if( ClipboardText.FindChar( TEXT( ' ' ), SpaceIndex ) ) 178 | { 179 | FString ClassPath = ClipboardText.Left( SpaceIndex ); 180 | FString PossibleObjectPath = ClipboardText.Mid( SpaceIndex ); 181 | 182 | if( UClass* ClassPtr = LoadClass( nullptr, *ClassPath ) ) 183 | { 184 | UActorComponent* Component = FindObject( nullptr, *PossibleObjectPath ); 185 | 186 | if( Component && 187 | Component->IsA( ClassPtr ) && 188 | Component->GetOwner( ) && 189 | ( !m_oComponentFilter.IsBound( ) || m_oComponentFilter.Execute( Component ) ) ) 190 | { 191 | if( !m_oActorFilter.IsBound( ) || m_oActorFilter.Execute( Component->GetOwner( ) ) ) 192 | { 193 | SetValue( Component ); 194 | bFound = true; 195 | } 196 | } 197 | } 198 | } 199 | 200 | if( !bFound ) 201 | { 202 | SetValue( nullptr ); 203 | } 204 | 205 | m_oOnClose.ExecuteIfBound( ); 206 | } 207 | 208 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 209 | bool SComponentPicker::CanPaste( ) 210 | { 211 | FString ClipboardText; 212 | FPlatformApplicationMisc::ClipboardPaste( ClipboardText ); 213 | 214 | bool bCanPaste = false; 215 | 216 | int32 SpaceIndex = INDEX_NONE; 217 | 218 | if( ClipboardText.FindChar( TEXT( ' ' ), SpaceIndex ) ) 219 | { 220 | FString Class = ClipboardText.Left( SpaceIndex ); 221 | FString PossibleObjectPath = ClipboardText.Mid( SpaceIndex ); 222 | 223 | bCanPaste = !Class.IsEmpty( ) && !PossibleObjectPath.IsEmpty( ); 224 | 225 | if( bCanPaste ) 226 | { 227 | bCanPaste = LoadClass( nullptr, *Class ) != nullptr; 228 | } 229 | 230 | if( bCanPaste ) 231 | { 232 | bCanPaste = FindObject( nullptr, *PossibleObjectPath ) != nullptr; 233 | } 234 | } 235 | 236 | return bCanPaste; 237 | } 238 | 239 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 240 | void SComponentPicker::OnClear( ) 241 | { 242 | SetValue( nullptr ); 243 | m_oOnClose.ExecuteIfBound( ); 244 | } 245 | 246 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 247 | void SComponentPicker::OnItemSelected( UActorComponent* pComponent ) 248 | { 249 | SetValue( pComponent ); 250 | m_oOnClose.ExecuteIfBound( ); 251 | } 252 | 253 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 254 | void SComponentPicker::SetValue( UActorComponent* pInComponent ) 255 | { 256 | m_oOnSet.ExecuteIfBound( pInComponent ); 257 | } 258 | 259 | #undef LOCTEXT_NAMESPACE 260 | -------------------------------------------------------------------------------- /SComponentPicker.h: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | #pragma once 4 | 5 | #include "PropertyCustomizationHelpers.h" 6 | 7 | class UActorComponent; 8 | 9 | DECLARE_DELEGATE_OneParam( FOnComponentPicked, UActorComponent* ); 10 | 11 | namespace SceneOutliner 12 | { 13 | struct ISceneOutlinerTreeItem; 14 | } 15 | 16 | // Essentially a duplicate of SPropertyMenuComponentPicker. A widget that allows picking components from the scene. 17 | class SComponentPicker : public SCompoundWidget 18 | { 19 | public: 20 | SLATE_BEGIN_ARGS( SComponentPicker ) 21 | : _pInitialComponent( nullptr ) 22 | , _bAllowClear( true ) 23 | , _oActorFilter( ) 24 | { 25 | } 26 | 27 | SLATE_ARGUMENT( UActorComponent*, pInitialComponent ) 28 | SLATE_ARGUMENT( bool, bAllowClear ) 29 | SLATE_ARGUMENT( FOnShouldFilterActor, oActorFilter ) 30 | SLATE_ARGUMENT( FOnShouldFilterComponent, oComponentFilter ) 31 | SLATE_EVENT( FOnComponentPicked, oOnSet ) 32 | SLATE_EVENT( FSimpleDelegate, oOnClose ) 33 | SLATE_END_ARGS( ) 34 | 35 | // Construct the widget. 36 | void Construct( const FArguments& rInArgs ); 37 | 38 | private: 39 | // Edit the object referenced by this widget. 40 | void OnEdit( ); 41 | 42 | 43 | // Delegate handling ctrl+c. 44 | void OnCopy( ); 45 | 46 | // Delegate handling ctrl+v. 47 | void OnPaste( ); 48 | 49 | // Returns true if the current clipboard contents can be pasted. 50 | bool CanPaste( ); 51 | 52 | // Clear the referenced object. 53 | void OnClear( ); 54 | 55 | // Delegate for handling selection by the actor picker. 56 | void OnItemSelected( UActorComponent* pComponent ); 57 | 58 | // Set the value of the asset referenced by this property editor. Will set the underlying property handle if there 59 | // is one. 60 | void SetValue( UActorComponent* pInComponent ); 61 | 62 | private: 63 | UActorComponent* m_pInitialComponent; 64 | 65 | // Whether the asset can be 'None' in this case. 66 | bool m_bAllowClear; 67 | 68 | // Delegates used to test whether a item should be displayed or not. 69 | FOnShouldFilterActor m_oActorFilter; 70 | FOnShouldFilterComponent m_oComponentFilter; 71 | 72 | // Delegate to call when our object value should be set. 73 | FOnComponentPicked m_oOnSet; 74 | 75 | // Delegate to call when closing the containing menu. 76 | FSimpleDelegate m_oOnClose; 77 | }; 78 | --------------------------------------------------------------------------------