Fixed radial menu input

This commit is contained in:
olehhapuk
2026-05-25 23:13:25 +03:00
parent ca3c81ada8
commit e4cc9ee66c
8 changed files with 131 additions and 10 deletions
@@ -5,6 +5,7 @@
#include "Components/CanvasPanel.h"
#include "Components/CanvasPanelSlot.h"
#include "Input/CommonUIInputTypes.h" // FBindUIActionArgs
#include "InputCoreTypes.h" // EKeys::LeftMouseButton
void URadialMenuWidget::InitializeFromController(
URadialMenuController* InController, const TArray<FRadialMenuEntry>& InEntries)
@@ -16,10 +17,22 @@ void URadialMenuWidget::InitializeFromController(
void URadialMenuWidget::NativeOnActivated()
{
Super::NativeOnActivated();
// Make sure the widget can actually hold focus; UCanvasPanel can't, so the
// activatable widget itself is our focus target (see GetDesiredFocusTarget).
SetIsFocusable(true);
// Must be hit-testable for NativeOnMouseButtonDown (left-click confirm) to
// fire. SelfHitTestInvisible/HitTestInvisible would let the click fall
// through and never reach us.
SetVisibility(ESlateVisibility::Visible);
// Bind Confirm. FBindUIActionArgs accepts the row handle directly.
// NOTE: Confirm must be mapped to a KEY (e.g. spacebar / face button), not
// left mouse button. CommonUI routes left-click through its separate Default
// Click Action path, so a click-mapped Confirm never reaches this binding.
// To confirm via trackpad/left-click instead, handle the pointer click
// directly (see NativeOnMouseButtonDown) rather than binding it here.
if (!ConfirmAction.IsNull())
{
FBindUIActionArgs ConfirmArgs(
@@ -38,6 +51,13 @@ void URadialMenuWidget::NativeOnActivated()
BackArgs.bDisplayInActionBar = true;
BackHandle = RegisterUIActionBinding(BackArgs);
}
// Reinforce: explicitly route focus to this widget for the owning player so
// gamepad/keyboard input reaches the bindings even if the layer didn't.
if (APlayerController* PC = GetOwningPlayer())
{
SetUserFocus(PC);
}
}
void URadialMenuWidget::NativeOnDeactivated()
@@ -57,10 +77,67 @@ void URadialMenuWidget::NativeOnDeactivated()
UWidget* URadialMenuWidget::NativeGetDesiredFocusTarget() const
{
// Keep focus on the menu root so the bound actions receive input.
// A UCanvasPanel is NOT focusable by default, so returning it here would
// silently fail and the bound actions wouldn't receive input. The radial
// menu has no single "default" child to land on (there's no list to
// highlight), so we focus the activatable widget itself, which participates
// in focus correctly. Requires bIsFocusable = true (set in NativeConstruct).
return const_cast<URadialMenuWidget*>(this);
}
FReply URadialMenuWidget::NativeOnMouseButtonDown(const FGeometry& InGeometry,
const FPointerEvent& InMouseEvent)
{
// Left-click anywhere on the menu confirms the currently hovered slice.
// The controller already tracks the hovered index each tick from the cursor
// angle, so the click itself carries no position logic — it just triggers
// the confirm. This bypasses CommonUI's Default Click Action entirely.
if (InMouseEvent.GetEffectingButton() == EKeys::LeftMouseButton)
{
if (OwningController.IsValid())
{
OwningController->CloseAndConfirm();
}
// Handle the event so it doesn't fall through to the click system.
return FReply::Handled();
}
return Super::NativeOnMouseButtonDown(InGeometry, InMouseEvent);
}
FReply URadialMenuWidget::NativeOnAnalogValueChanged(const FGeometry& InGeometry,
const FAnalogInputEvent& InAnalogEvent)
{
// Analog events arrive one axis per event, so we cache the latest value of
// each right-stick axis and recompute direction whenever either changes.
const FKey Key = InAnalogEvent.GetKey();
bool bRightStick = false;
if (Key == EKeys::Gamepad_RightX)
{
RightStickX = InAnalogEvent.GetAnalogValue();
bRightStick = true;
}
else if (Key == EKeys::Gamepad_RightY)
{
RightStickY = InAnalogEvent.GetAnalogValue();
bRightStick = true;
}
if (bRightStick && OwningController.IsValid())
{
// Stick Y is +up already (unlike screen space), so pass it straight
// through. The controller applies its own dead zone.
const float Mag = FVector2D(RightStickX, RightStickY).Size();
OwningController->UpdateHoverFromDirection(RightStickX, RightStickY, Mag);
// Consume so CommonUI's analog cursor doesn't also act on it.
return FReply::Handled();
}
return Super::NativeOnAnalogValueChanged(InGeometry, InAnalogEvent);
}
void URadialMenuWidget::HandleConfirm()
{
// Route to the controller, which owns the state, time dilation, and the