I am attempting to create an editor widget for ActorComponents on the current Actor, and am getting mostly there.
in the EditorModule.Build.cs: {“Core”, “CoreUObject”, “Engine”, “Slate”, “SlateCore”, “PropertyEditor”}
the header for my ComponentPicker
#pragma once
“CoreMinimal.h”
“IPropertyTypeCustomization.h” // implementing the interface so should have this
“DetailWidgetRow.h” // it is a struct in function definition (declared outside the interface)
/**
*
*/
class RUINSGAMEEDITOR_API FActorComponentPicker : public IPropertyTypeCustomization
{
public:
static TSharedRef MakeInstance();
virtual void CustomizeHeader(TSharedRef<IPropertyHandle> PropertyHandle, FDetailWidgetRow& HeaderRow,
IPropertyTypeCustomizationUtils& CustomizationUtils) override;
virtual void CustomizeChildren(TSharedRef<IPropertyHandle> PropertyHandle, IDetailChildrenBuilder& ChildBuilder,
IPropertyTypeCustomizationUtils& CustomizationUtils) override {}
private:
TArray<TWeakObjectPtr> Components;
TWeakObjectPtr CurrentlyEditedObject;
TSharedPtr cPH; // currentPropertyHandle
bool bUsingDefaultEditor = false;
void CollectComponents(UObject* EditedObject, UClass* RequiredClass, TArray<TWeakObjectPtr<UActorComponent>>& OutComponents);
// helper functions to avoid raw this capture in Lambda
TSharedRef<SWidget> OnGenerateWidget(TWeakObjectPtr<UActorComponent> Item) const;
void OnSelectionChanged(TWeakObjectPtr<UActorComponent> NewValue, ESelectInfo::Type SelectInfo);
FText GetCurrentSelectionText() const;
FReply OnDefaultClicked();
};
then the Cpp
“ActorComponentPicker.h”
// supposedly I need all of these
“Widgets/Text/STextBlock.h”
“Widgets/Input/SComboBox.h”
“Widgets/SWidget.h”
“Widgets/SBoxPanel.h”
void FActorComponentPicker::CustomizeHeader(TSharedRef InPropertyHandle, FDetailWidgetRow& HeaderRow, IPropertyTypeCustomizationUtils& CustomizationUtils)
{
cPH = InPropertyHandle;
// Detect type: UClass of Property
FObjectPropertyBase* ObjProp = CastField<FObjectPropertyBase>(cPH->GetProperty());
// not an Object Property
if ( ObjProp == nullptr )
{
HeaderRow.NameContent()[cPH->CreatePropertyNameWidget()];
HeaderRow.ValueContent()[cPH->CreatePropertyValueWidget()];
return;
}
UClass* RequiredClass = ObjProp->PropertyClass;
// GetEditing objects
TArray<UObject*> OuterObjects;
cPH->GetOuterObjects(OuterObjects);
// there is no EditingObjects
if ( OuterObjects.IsEmpty() )
{
HeaderRow.NameContent()[cPH->CreatePropertyNameWidget()];
HeaderRow.ValueContent()[cPH->CreatePropertyValueWidget()];
return;
}
// should be the Object itself
CurrentlyEditedObject = OuterObjects[0];
CollectComponents(CurrentlyEditedObject.Get(), RequiredClass, Components);
if ( bUsingDefaultEditor == true || Components.Num() == 0 )
{
HeaderRow.NameContent()[cPH->CreatePropertyNameWidget()];
HeaderRow.ValueContent()[cPH->CreatePropertyValueWidget()];
return;
}
HeaderRow.NameContent()[cPH->CreatePropertyNameWidget()].ValueContent()[
SNew(SHorizontalBox)
//* // attempting to trouble shoot and isolate something about the Lambda
+ SHorizontalBox::Slot().FillWidth(1)
[
SNew(SComboBox<TWeakObjectPtr>)
.OptionsSource(&Components)
// my guess is here
.OnGenerateWidget_Lambda(
(TWeakObjectPtr Item)
{
return SNew(STextBlock).Text(FText::FromString(Item.IsValid() ? Item->GetName() : TEXT(“None”)));
})
// or here
.OnSelectionChanged_Lambda(
(TWeakObjectPtr NewValue, ESelectInfo::Type){})
]
//*/
/* // attempting to avoid passing this as raw Lambda capture, by using function as arguments
+ SHorizontalBox::Slot().FillWidth(1)
[
SNew(SComboBox<TWeakObjectPtr>)
.OptionsSource(&Components)
.OnGenerateWidget(this, &FActorComponentPicker::OnGenerateWidget)
.OnSelectionChanged(this, &FActorComponentPicker::OnSelectionChanged)
[
SNew(STextBlock).Text(this,&FActorComponentPicker::GetCurrentSelectionText)
]
]
*/
+ SHorizontalBox::Slot().AutoWidth().Padding(2.0f)
[
SNew(SButton)
.ToolTipText(FText::FromString(“Use Default picker”))
.Text(FText::FromString(“Default”))
.OnClicked(this, &FActorComponentPicker::OnDefaultClicked)
]
];
}
void FActorComponentPicker::CollectComponents(UObject* EditedObject, UClass* RequiredClass, TArray<TWeakObjectPtr>& OutComponents)
{
AActor* OwnerActor = nullptr;
if ( AActor* AsActor = Cast(CurrentlyEditedObject) )
{
OwnerActor = AsActor;
}
else
{
OwnerActor = EditedObject ? EditedObject->GetTypedOuter() : nullptr;
}
if ( OwnerActor == nullptr ) { return; }
for ( UActorComponent* Comp : OwnerActor->GetComponents() )
{
if ( Comp != nullptr && Comp->IsA(RequiredClass) )
{
OutComponents.Add(Comp);
}
}
}
TSharedRef FActorComponentPicker::OnGenerateWidget(TWeakObjectPtr Item) const
{
return SNew(STextBlock).Text(FText::FromString(Item.IsValid() ? Item->GetName() : TEXT(“None”)));
}
void FActorComponentPicker::OnSelectionChanged(TWeakObjectPtr NewValue, ESelectInfo::Type SelectInfo)
{
UObject* Obj = (NewValue.IsValid() ? NewValue.Get() : nullptr);
if ( cPH.IsValid() ) { cPH->SetValue(Obj); }
}
FText FActorComponentPicker::GetCurrentSelectionText() const
{
UObject* Value = nullptr;
if ( cPH.IsValid() ) { cPH->GetValue(Value); }
return FText::FromString(Value ? Value->GetName() : TEXT(“None”));
}
FReply FActorComponentPicker::OnDefaultClicked()
{
bUsingDefaultEditor = !bUsingDefaultEditor;
return FReply::Handled();
}
The helper LLM got bent out of shape because the .FillWidth()wasn’t on a new line, showing it doesn’t understand C++ human readable formatting vs compliable code.
I am guessing I am still missing include an because with both parts of the combobox commented out I get a full build, with either section active I get 10 instances of unresolved Externals.
wishful questions:
- add the checkbox to the default implementation so that I can toggle back to this changed version without unselecting the field, and selecting it again.
- select a different actor with the ActorPicker, and then get the list of their components.