// RadialMenuWidget.cpp #include "RadialMenuWidget.h" #include "RadialSliceWidget.h" #include "Components/CanvasPanel.h" #include "Components/CanvasPanelSlot.h" #include "Input/CommonUIInputTypes.h" // FBindUIActionArgs void URadialMenuWidget::InitializeFromController( URadialMenuController* InController, const TArray& InEntries) { OwningController = InController; BuildSlices(InEntries); } void URadialMenuWidget::NativeOnActivated() { Super::NativeOnActivated(); SetIsFocusable(true); // Bind Confirm. FBindUIActionArgs accepts the row handle directly. if (!ConfirmAction.IsNull()) { FBindUIActionArgs ConfirmArgs( ConfirmAction, FSimpleDelegate::CreateUObject(this, &URadialMenuWidget::HandleConfirm)); // Show in the on-screen action bar so the player sees the prompt. ConfirmArgs.bDisplayInActionBar = true; ConfirmHandle = RegisterUIActionBinding(ConfirmArgs); } if (!BackAction.IsNull()) { FBindUIActionArgs BackArgs( BackAction, FSimpleDelegate::CreateUObject(this, &URadialMenuWidget::HandleBack)); BackArgs.bDisplayInActionBar = true; BackHandle = RegisterUIActionBinding(BackArgs); } } void URadialMenuWidget::NativeOnDeactivated() { // Clean up bindings so they don't linger if the widget is pooled/reused. if (ConfirmHandle.IsValid()) { ConfirmHandle.Unregister(); } if (BackHandle.IsValid()) { BackHandle.Unregister(); } Super::NativeOnDeactivated(); } UWidget* URadialMenuWidget::NativeGetDesiredFocusTarget() const { // Keep focus on the menu root so the bound actions receive input. return const_cast(this); } void URadialMenuWidget::HandleConfirm() { // Route to the controller, which owns the state, time dilation, and the // OnSelectionConfirmed broadcast. The controller also tears down the widget. if (OwningController.IsValid()) { OwningController->CloseAndConfirm(); } } void URadialMenuWidget::HandleBack() { if (OwningController.IsValid()) { OwningController->CloseAndCancel(); } } void URadialMenuWidget::BuildSlices(const TArray& InEntries) { if (!SliceCanvas || !SliceWidgetClass) { return; } SliceCanvas->ClearChildren(); Slices.Reset(); HoveredIndex = -1; const int32 Count = InEntries.Num(); if (Count == 0) { return; } const float AngleSize = 360.f / Count; for (int32 i = 0; i < Count; ++i) { URadialSliceWidget* Slice = CreateWidget(GetOwningPlayer(), SliceWidgetClass); if (!Slice) { continue; } const float StartAngle = i * AngleSize - AngleSize * 0.5f; Slice->Setup(SliceMaterial, StartAngle, AngleSize, InEntries[i].Icon, InEntries[i].bEnabled); SliceCanvas->AddChild(Slice); if (UCanvasPanelSlot* CanvasSlot = Cast(Slice->Slot)) { CanvasSlot->SetAnchors(FAnchors(0.5f, 0.5f)); CanvasSlot->SetAlignment(FVector2D(0.5f, 0.5f)); CanvasSlot->SetPosition(FVector2D::ZeroVector); CanvasSlot->SetSize(FVector2D(MenuDiameter, MenuDiameter)); } Slices.Add(Slice); } } void URadialMenuWidget::SetHoveredSegment(int32 Index) { HoveredIndex = Index; } void URadialMenuWidget::NativeTick(const FGeometry& MyGeometry, float InDeltaTime) { Super::NativeTick(MyGeometry, InDeltaTime); const float RealDelta = FApp::GetDeltaTime(); // undilated for (int32 i = 0; i < Slices.Num(); ++i) { if (Slices[i]) { Slices[i]->UpdateHighlight(i == HoveredIndex, RealDelta); } } }