// RadialMenuWidget.h // Container for the radial menu, now a CommonUI activatable widget. It owns the // CommonUI input bindings (Confirm / Back) and routes them to the controller, // which still owns all state (time dilation, hover, Entries). The widget also // spawns one URadialSliceWidget per entry at runtime (variable slice count). #pragma once #include "CoreMinimal.h" #include "CommonActivatableWidget.h" #include "Engine/DataTable.h" // FDataTableRowHandle #include "RadialMenuController.h" // for FRadialMenuEntry #include "RadialMenuWidget.generated.h" class UCanvasPanel; class URadialSliceWidget; struct FUIActionBindingHandle; UCLASS(Abstract) class URadialMenuWidget : public UCommonActivatableWidget { GENERATED_BODY() public: // Called by the controller after CreateWidget, before activation. Stores the // owning controller so action handlers can route back to it, then builds the // slices. void InitializeFromController(URadialMenuController* InController, const TArray& InEntries); // Called when the hovered slice changes. Stored and applied each tick. void SetHoveredSegment(int32 Index); protected: virtual void NativeOnActivated() override; virtual void NativeOnDeactivated() override; virtual void NativeTick(const FGeometry& MyGeometry, float InDeltaTime) override; // Left-click confirm. Handled directly here rather than as a CommonUI action // binding, because CommonUI routes left mouse through its Default Click Action // system (separate from RegisterUIActionBinding), so a click-mapped action // never reaches a normal binding. Intercepting the pointer event sidesteps that. virtual FReply NativeOnMouseButtonDown(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent) override; // Gamepad slice selection. When this widget is the active CommonUI node, the // analog sticks are routed here (NOT to the PlayerController), which is why // GetInputAnalogStickState returns 0 from the controller while a menu is up. // We read the right stick from the analog event and drive hover directly. virtual FReply NativeOnAnalogValueChanged(const FGeometry& InGeometry, const FAnalogInputEvent& InAnalogEvent) override; // CommonUI focus: returning the root keeps gamepad/keyboard focus on us so // the bound actions fire while the wheel is open. virtual UWidget* NativeGetDesiredFocusTarget() const override; // --- CommonUI input action assets (assign in the widget BP defaults) ------ // CommonUI input action rows. These are FDataTableRowHandle pointing at your // CommonInputActionDataTable rows (the same rows your other screens use for // accept/back), so the on-screen action bar and key mapping stay consistent. UPROPERTY(EditDefaultsOnly, Category = "Radial Menu|Input", meta = (RowType = "/Script/CommonUI.CommonInputActionDataBase")) FDataTableRowHandle ConfirmAction; // Bound explicitly so our cancel path runs (restore time, broadcast -1) // rather than CommonUI just popping the widget on Back. UPROPERTY(EditDefaultsOnly, Category = "Radial Menu|Input", meta = (RowType = "/Script/CommonUI.CommonInputActionDataBase")) FDataTableRowHandle BackAction; // Root canvas the slices are spawned into. UPROPERTY(meta = (BindWidget)) TObjectPtr SliceCanvas = nullptr; UPROPERTY(EditDefaultsOnly, Category = "Radial Menu") TSubclassOf SliceWidgetClass; UPROPERTY(EditDefaultsOnly, Category = "Radial Menu") TObjectPtr SliceMaterial = nullptr; UPROPERTY(EditDefaultsOnly, Category = "Radial Menu") float MenuDiameter = 420.f; private: // Action handlers, bound in NativeOnActivated. void HandleConfirm(); void HandleBack(); void BuildSlices(const TArray& InEntries); UPROPERTY(Transient) TWeakObjectPtr OwningController; UPROPERTY(Transient) TArray> Slices; // Handles kept so we can unregister on deactivation. FUIActionBindingHandle ConfirmHandle; FUIActionBindingHandle BackHandle; int32 HoveredIndex = -1; // Latest right-stick axis values, cached across single-axis analog events so // we can recompute the full 2D direction whenever either axis updates. float RightStickX = 0.f; float RightStickY = 0.f; };