More work on radial menus

This commit is contained in:
2026-05-08 17:55:11 -04:00
parent b00ec49e91
commit 420ea088d7
3 changed files with 147 additions and 1 deletions

View File

@@ -0,0 +1,86 @@
#include "RadialMenu.h"
void URadialMenu::Configure(int32 NItems, float ItemHeight, float InnerRadius, float MinSpoke, float Spread)
{
CNItems = NItems;
CItemHeight = ItemHeight;
CInnerRadius = InnerRadius;
CMinSpoke = MinSpoke;
CSpread = Spread;
CNumRight = (NItems / 2);
CNumLeft = NItems - CNumRight;
Items.SetNum(NItems);
LeftItems = View(Items.GetData(), CNumLeft);
RightItems = View(Items.GetData() + CNumLeft, CNumRight);
CalculateSide(LeftItems);
CalculateSide(RightItems);
FlipHorizontal(LeftItems);
}
FVector2D URadialMenu::PieSliceToVector(double Slice, double Slices)
{
double HalfRevolutions = (Slice + 0.5) / Slices;
double Radians = (HalfRevolutions * UE_PI);
return FVector2D(FMath::Sin(Radians), -FMath::Cos(Radians));
}
void URadialMenu::FlipHorizontal(View V)
{
for (FRadialMenuItem& Item : V)
{
Item.Point1.X = -Item.Point1.X;
Item.Point2.X = -Item.Point2.X;
Item.Point3.X = -Item.Point3.X;
Item.RightSide = !Item.RightSide;
}
}
void URadialMenu::CalculateSide(View V)
{
// Point1 is simple. RightSide is always initialized to
// true, it may get reversed in FlipHorizontal.
for (int32 I = 0; I < V.Num(); I++)
{
V[I].RightSide = true;
V[I].Point1 = PieSliceToVector(I, V.Num()) * CInnerRadius;
}
// Calculate point2 for all spokes.
double NextLineMin = CItemHeight * 0.5;
int32 Mid = (V.Num() / 2);
if (V.Num() & 1)
{
V[Mid].Point2 = FVector2D(CInnerRadius + CMinSpoke, 0.0);
NextLineMin = CItemHeight;
Mid += 1;
}
for (int32 I = Mid; I < V.Num(); I++)
{
FVector2D UnitVec = PieSliceToVector(I, V.Num());
double Y = (UnitVec.Y * (CInnerRadius + CMinSpoke));
if (Y < NextLineMin) Y = NextLineMin;
NextLineMin = Y + CItemHeight;
FVector2D Point2 = UnitVec * (Y / UnitVec.Y);
V[I].Point2 = Point2;
V[V.Num() - I].Point2 = Point2 * FVector2D(1.0,-1.0);
}
// The rule we use for calculating point2 may result in
// a very short horizontal spoke. If so, fix it.
if ((V.Num() & 1) && (V.Num() >= 3))
{
Mid = V.Num() / 2;
if (V[Mid].Point2.X < V[Mid + 1].Point2.X)
V[Mid].Point2.X = V[Mid + 1].Point2.X;
}
// Calculate Point3.
for (int32 I = 0; I < V.Num(); I++)
{
V[I].Point3 = V[I].Point2 + FVector2D(CSpread, 0.0);
}
}

View File

@@ -0,0 +1,60 @@
//
// This class implements the layout calculatations for a radial
// menu.
//
#pragma once
#include "CoreMinimal.h"
#include "RadialMenu.generated.h"
USTRUCT(BlueprintType)
struct FRadialMenuItem
{
GENERATED_BODY()
FVector2D Point1 = {0,0};
FVector2D Point2 = {0,0};
FVector2D Point3 = {0,0};
bool RightSide = false;
};
UCLASS(BlueprintType)
class URadialMenu : public UObject
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintCallable)
void Configure(int32 NItems, float ItemHeight, float InnerRadius, float MinSpoke, float Spread);
UFUNCTION(BlueprintCallable)
int32 NumItems() const { return Items.Num(); }
UFUNCTION(BlueprintCallable)
const TArray<FRadialMenuItem> GetItems() const { return Items; }
TArray<FRadialMenuItem> Items;
private:
using View = TArrayView<FRadialMenuItem>;
void CalculateSide(View V);
void FlipHorizontal(View V);
// The half-circle is divided into pie slices. Returns a direction vector
// which aims directly down the center of the pie slice.
FVector2D PieSliceToVector(double Slice, double Slices);
int32 CNItems = 0;
float CItemHeight = 0;
float CInnerRadius = 0;
float CMinSpoke = 0;
float CSpread = 0;
int32 CNumLeft = 0;
int32 CNumRight = 0;
View LeftItems;
View RightItems;
};