If anyone else runs into this issue, here’s how I worked around it. Example code is at the bottom.
We have 2 problems. The first is that RootButton is private inside UCommonButtonBase. The second is that MyCommonButton inside UCommonButtonInternalBase is protected. These two make it so you can’t access it directly from your own button class.
Here is how I fixed this:
- Subclass
UCommonButtonInternalBase
- In your subclass, create a getter function that returns
MyCommonButton. - This lets you safely expose it for later use.
- Override
ConstructInternalButton()in your button class (which inherits fromUCommonButtonBase)
- This function is
protectedandvirtual, so you can create your own subclass of UCommonButtonInternalBase instead of the default internal button. - At the same time, you can cache a pointer to your custom internal button so you can use it later.
- Override
NativeOnFocusReceived()
- When your button receives focus, you can forward that focus directly to the actual Slate button (
MyCommonButton) using the getter you exposed earlier. - This ensures the Slate widget behaves correctly when navigating with keyboard or controller.
This way you don’t need to touch the Common UI’s plugin code, which I really wanted to avoid.
Here’s what that looks like in code:
Header file:
UCLASS()
class UCustomCommonButtonInternalBase : public UCommonButtonInternalBase
{
GENERATED_BODY()
public:
TSharedPtr<SCommonButton> GetSlateButton() const { return MyCommonButton; }
};
UCLASS()
class UCustomCommonButton : public UCommonButtonBase
{
GENERATED_BODY()
protected:
UPROPERTY(Transient)
TObjectPtr<UCustomCommonButtonInternalBase> CachedInternalButton{};
virtual UCommonButtonInternalBase* ConstructInternalButton() override;
virtual FReply NativeOnFocusReceived(const FGeometry& InGeometry, const FFocusEvent& InFocusEvent) override;
};
CPP file:
UCommonButtonInternalBase* UCustomCommonButton::ConstructInternalButton()
{
CachedInternalButton = WidgetTree->ConstructWidget<UCustomCommonButtonInternalBase>(
UCustomCommonButtonInternalBase::StaticClass(),
FName(TEXT("InternalRootButtonBase"))
);
return Cast<UCommonButtonInternalBase>(CachedInternalButton);
}
FReply UCustomCommonButton::NativeOnFocusReceived(const FGeometry& InGeometry, const FFocusEvent& InFocusEvent)
{
Super::NativeOnFocusReceived(InGeometry, InFocusEvent);
if (CachedInternalButton->GetSlateButton())
{
FSlateApplication::Get().SetKeyboardFocus(CachedInternalButton->GetSlateButton());
return FReply::Handled();
}
return FReply::Unhandled();
}