Files
integration/Source/Integration/RadialMenu.h

162 lines
4.4 KiB
C
Raw Normal View History

2026-05-08 17:55:11 -04:00
//
// This class implements the layout calculatations for a radial
// menu.
//
#pragma once
#include "CoreMinimal.h"
2026-05-11 00:13:30 -04:00
#include "Components/Widget.h"
2026-05-18 16:12:26 -04:00
#include "Fonts/SlateFontInfo.h"
2026-05-08 17:55:11 -04:00
#include "RadialMenu.generated.h"
2026-05-11 00:13:30 -04:00
class SRadialMenu;
2026-05-15 18:14:38 -04:00
class UTexture2D;
2026-05-11 00:13:30 -04:00
2026-05-08 17:55:11 -04:00
2026-05-18 02:26:28 -04:00
USTRUCT(BlueprintType)
struct FRadialMenuConfig
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, Category="RadialMenu")
2026-05-18 16:12:26 -04:00
TArray<FString> MenuItems;
2026-05-18 02:26:28 -04:00
UPROPERTY(EditAnywhere, Category="RadialMenu")
2026-05-18 16:12:26 -04:00
FSlateFontInfo Font;
2026-05-18 02:26:28 -04:00
UPROPERTY(EditAnywhere, Category="RadialMenu")
float InnerRadius = 20.0f;
UPROPERTY(EditAnywhere, Category="RadialMenu")
float MinSpoke = 20.0f;
UPROPERTY(EditAnywhere, Category="RadialMenu")
float Spread = 20.0f;
UPROPERTY(EditAnywhere, Category="RadialMenu")
float LineThickness = 4.0f;
UPROPERTY(EditAnywhere, Category="RadialMenu")
2026-05-18 16:12:26 -04:00
FLinearColor ItemColor = FLinearColor::White;
2026-05-18 02:26:28 -04:00
UPROPERTY(EditAnywhere, Category="RadialMenu")
TObjectPtr<UTexture2D> DotTexture;
UPROPERTY(EditAnywhere, Category="RadialMenu")
float DotRadius = 3.0f;
2026-05-18 16:12:26 -04:00
UPROPERTY(EditAnywhere, Category="RadialMenu")
TObjectPtr<UTexture2D> Teardrop;
UPROPERTY(EditAnywhere, Category="RadialMenu")
float TeardropRadius = 0.0f;
2026-05-18 02:26:28 -04:00
UPROPERTY(EditAnywhere, Category="RadialMenu")
int32 SelectedItem = -1;
UPROPERTY(EditAnywhere, Category="RadialMenu")
FLinearColor SelectedColor = FLinearColor::Yellow;
UPROPERTY(EditAnywhere, Category="RadialMenu")
float SelectedThickness = 2.0f;
};
2026-05-08 17:55:11 -04:00
USTRUCT(BlueprintType)
struct FRadialMenuItem
{
GENERATED_BODY()
2026-05-16 01:49:26 -04:00
UPROPERTY(BlueprintReadOnly)
2026-05-08 17:55:11 -04:00
FVector2D Point1 = {0,0};
2026-05-16 01:49:26 -04:00
UPROPERTY(BlueprintReadOnly)
2026-05-08 17:55:11 -04:00
FVector2D Point2 = {0,0};
2026-05-16 01:49:26 -04:00
UPROPERTY(BlueprintReadOnly)
2026-05-08 17:55:11 -04:00
FVector2D Point3 = {0,0};
2026-05-16 01:49:26 -04:00
UPROPERTY(BlueprintReadOnly)
2026-05-08 17:55:11 -04:00
bool RightSide = false;
2026-05-18 16:12:26 -04:00
UPROPERTY(BlueprintReadOnly)
FVector2D TextSize = {0,0};
2026-05-18 02:26:28 -04:00
static TArray<FRadialMenuItem> Calculate(const FRadialMenuConfig &Config);
2026-05-08 17:55:11 -04:00
2026-05-18 16:12:26 -04:00
// Like Calculate, but only produces the unit-vector direction of
// each spoke. Cheaper to evaluate when only the spoke directions
// are needed (e.g. for hit-testing a pointer against the wheel).
static TArray<FVector2D> CalculateDirections(int32 NumItems);
2026-05-08 17:55:11 -04:00
private:
using View = TArrayView<FRadialMenuItem>;
2026-05-09 03:16:41 -04:00
// Give the unit vector for the selected spoke. NSide is
// the number of spokes on the side of the wheel that
// we're calculating, and NTotal is the total number of
// spokes on both sides. The spokes on a given side are
// organized top-to-bottom, and the angle between the
// spokes is always equal to (1/NTotal) of the circle.
2026-05-18 02:26:28 -04:00
static FVector2D SpokeVector(int32 I, int32 NSide, int32 NTotal);
2026-05-08 17:55:11 -04:00
2026-05-09 03:16:41 -04:00
// Populate Point1 and Point2, these are the
// endpoints of the spoke segment. Spokes are
// designed to always be long enough to make room
// for MinSpoke, but also to make enough room to
// keep the menu items from overlapping.
2026-05-18 02:26:28 -04:00
static void CalculateSpokes(View V, int32 TotalItems, float ItemHeight, float InnerRadius, float MinSpoke);
2026-05-08 17:55:11 -04:00
2026-05-09 03:16:41 -04:00
// Search for the widest spoke, and return its X coordinate.
2026-05-18 02:26:28 -04:00
static double WidestSpoke(View V);
2026-05-09 03:16:41 -04:00
// Populate Point3, this is the endpoint of the spread
// line that goes horizontal.
2026-05-18 02:26:28 -04:00
static void CalculateSpread(View V, double Offset);
2026-05-09 03:16:41 -04:00
// Flip everything in the specified view horizontally.
2026-05-18 02:26:28 -04:00
static void FlipHorizontal(View V);
2026-05-08 17:55:11 -04:00
};
2026-05-11 00:13:30 -04:00
UCLASS(BlueprintType, Blueprintable)
class URadialMenuWidget : public UWidget
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintCallable)
2026-05-18 16:12:26 -04:00
void SetMenuItems(const TArray<FString>& NewMenuItems);
2026-05-15 18:14:38 -04:00
UFUNCTION(BlueprintCallable)
2026-05-18 16:12:26 -04:00
void ClearMenuItems() { SetMenuItems({}); }
2026-05-15 18:14:38 -04:00
UFUNCTION(BlueprintCallable)
2026-05-18 16:12:26 -04:00
void SetSelectedItem(int32 NewSelectedItem);
2026-05-15 18:14:38 -04:00
2026-05-18 02:26:28 -04:00
// Returns true if the selected item changed.
UFUNCTION(BlueprintCallable)
2026-05-18 16:12:26 -04:00
void AddPointer(FVector2D Direction, float Scale);
2026-05-15 18:14:38 -04:00
2026-05-18 02:26:28 -04:00
UFUNCTION(BlueprintCallable)
FVector2D GetPointer() const { return PointerVector; }
2026-05-18 16:12:26 -04:00
2026-05-18 02:26:28 -04:00
UFUNCTION(BlueprintCallable)
2026-05-18 16:12:26 -04:00
FString GetSelectedMenuItem() const;
2026-05-18 02:26:28 -04:00
protected:
UPROPERTY(EditAnywhere)
FRadialMenuConfig Config;
2026-05-15 18:14:38 -04:00
2026-05-18 16:12:26 -04:00
TArray<FVector2D> Directions;
2026-05-15 18:14:38 -04:00
2026-05-18 02:26:28 -04:00
FVector2D PointerVector = {0,0};
2026-05-15 18:14:38 -04:00
2026-05-18 02:26:28 -04:00
virtual TSharedRef<SWidget> RebuildWidget() override;
virtual void ReleaseSlateResources(bool bReleaseChildren) override;
virtual void SynchronizeProperties() override;
2026-05-11 00:13:30 -04:00
TSharedPtr<SRadialMenu> MySlateWidget;
};