diff --git a/Source/Integration/RadialMenu.cpp b/Source/Integration/RadialMenu.cpp index 8d03464c..ab7a2193 100644 --- a/Source/Integration/RadialMenu.cpp +++ b/Source/Integration/RadialMenu.cpp @@ -1,7 +1,9 @@ #include "RadialMenu.h" +#include "Rendering/DrawElements.h" +#include "Widgets/SLeafWidget.h" -void URadialMenu::Configure(int32 NItems, float ItemHeight, float InnerRadius, float MinSpoke, float Spread) +void URadialMenuLayout::Configure(int32 NItems, float ItemHeight, float InnerRadius, float MinSpoke, float Spread) { NumRight = (NItems / 2); NumLeft = NItems - NumRight; @@ -21,7 +23,7 @@ void URadialMenu::Configure(int32 NItems, float ItemHeight, float InnerRadius, f FlipHorizontal(LeftItems); } -FVector2D URadialMenu::SpokeVector(int32 I, int32 NSide, int32 NTotal) +FVector2D URadialMenuLayout::SpokeVector(int32 I, int32 NSide, int32 NTotal) { double SpokeAngle = 1.0 / NTotal; double OffsetAngle = 0.5 * (0.5 - ((NSide - 1) * SpokeAngle)); @@ -30,7 +32,7 @@ FVector2D URadialMenu::SpokeVector(int32 I, int32 NSide, int32 NTotal) return FVector2D(FMath::Sin(Radians), -FMath::Cos(Radians)); } -void URadialMenu::FlipHorizontal(View V) +void URadialMenuLayout::FlipHorizontal(View V) { for (FRadialMenuItem& Item : V) { @@ -41,7 +43,7 @@ void URadialMenu::FlipHorizontal(View V) } } -double URadialMenu::WidestSpoke(View V) +double URadialMenuLayout::WidestSpoke(View V) { double Result = 0.0; for (const FRadialMenuItem &Item : V) @@ -51,7 +53,7 @@ double URadialMenu::WidestSpoke(View V) return Result; } -void URadialMenu::CalculateSpread(View V, double Offset) +void URadialMenuLayout::CalculateSpread(View V, double Offset) { for (FRadialMenuItem &Item : V) { @@ -59,7 +61,7 @@ void URadialMenu::CalculateSpread(View V, double Offset) } } -void URadialMenu::CalculateSpokes(View V, float ItemHeight, float InnerRadius, float MinSpoke) +void URadialMenuLayout::CalculateSpokes(View V, float ItemHeight, float InnerRadius, float MinSpoke) { if (V.Num() == 0) return; @@ -101,3 +103,98 @@ void URadialMenu::CalculateSpokes(View V, float ItemHeight, float InnerRadius, f V[Mid].Point2.X = V[Mid + 1].Point2.X; } } + + +// Slate widget that paints the radial menu's polylines. +class SRadialMenu : public SLeafWidget +{ +public: + SLATE_BEGIN_ARGS(SRadialMenu) {} + SLATE_END_ARGS() + + void Construct(const FArguments& InArgs, URadialMenuLayout* InLayout) + { + Layout = InLayout; + } + + void Refresh() + { + Invalidate(EInvalidateWidgetReason::Layout); + } + +protected: + virtual FVector2D ComputeDesiredSize(float) const override + { + if (!Layout.IsValid()) return FVector2D::ZeroVector; + FVector2D Min(0.0, 0.0); + FVector2D Max(0.0, 0.0); + for (const FRadialMenuItem &Item : Layout->GetItems()) + { + for (const FVector2D &P : { Item.Point1, Item.Point2, Item.Point3 }) + { + Min.X = FMath::Min(Min.X, P.X); + Min.Y = FMath::Min(Min.Y, P.Y); + Max.X = FMath::Max(Max.X, P.X); + Max.Y = FMath::Max(Max.Y, P.Y); + } + } + // Symmetric around the origin so the menu draws centered. + double HalfW = FMath::Max(FMath::Abs(Min.X), FMath::Abs(Max.X)); + double HalfH = FMath::Max(FMath::Abs(Min.Y), FMath::Abs(Max.Y)); + return FVector2D(HalfW * 2.0, HalfH * 2.0); + } + + virtual int32 OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, + const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, + int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const override + { + if (!Layout.IsValid()) return LayerId; + + const FVector2D Center = AllottedGeometry.GetLocalSize() * 0.5; + const FLinearColor Color = InWidgetStyle.GetColorAndOpacityTint(); + const FPaintGeometry PaintGeom = AllottedGeometry.ToPaintGeometry(); + + for (const FRadialMenuItem &Item : Layout->GetItems()) + { + TArray Points; + Points.Add(Center + Item.Point1); + Points.Add(Center + Item.Point2); + Points.Add(Center + Item.Point3); + FSlateDrawElement::MakeLines(OutDrawElements, LayerId, PaintGeom, Points, ESlateDrawEffect::None, Color, true, 1.0f); + } + return LayerId; + } + +private: + TWeakObjectPtr Layout; +}; + + +void URadialMenuWidget::Configure(int32 NItems, float ItemHeight, float InnerRadius, float MinSpoke, float Spread) +{ + if (!Layout) + { + Layout = NewObject(this); + } + Layout->Configure(NItems, ItemHeight, InnerRadius, MinSpoke, Spread); + if (MySlateWidget.IsValid()) + { + MySlateWidget->Refresh(); + } +} + +TSharedRef URadialMenuWidget::RebuildWidget() +{ + if (!Layout) + { + Layout = NewObject(this); + } + MySlateWidget = SNew(SRadialMenu, Layout); + return MySlateWidget.ToSharedRef(); +} + +void URadialMenuWidget::ReleaseSlateResources(bool bReleaseChildren) +{ + Super::ReleaseSlateResources(bReleaseChildren); + MySlateWidget.Reset(); +} diff --git a/Source/Integration/RadialMenu.h b/Source/Integration/RadialMenu.h index 9673d213..98deb6c7 100644 --- a/Source/Integration/RadialMenu.h +++ b/Source/Integration/RadialMenu.h @@ -6,8 +6,11 @@ #pragma once #include "CoreMinimal.h" +#include "Components/Widget.h" #include "RadialMenu.generated.h" +class SRadialMenu; + USTRUCT(BlueprintType) struct FRadialMenuItem @@ -21,7 +24,7 @@ struct FRadialMenuItem }; UCLASS(BlueprintType) -class URadialMenu : public UObject +class URadialMenuLayout : public UObject { GENERATED_BODY() @@ -36,7 +39,7 @@ public: int32 RightNum() const { return NumRight; } UFUNCTION(BlueprintCallable) - const TArray GetItems() const { return Items; } + const TArray &GetItems() const { return Items; } private: @@ -75,3 +78,27 @@ private: int32 NumRight = 0; }; + +UCLASS(BlueprintType, Blueprintable) +class URadialMenuWidget : public UWidget +{ + GENERATED_BODY() + +public: + UFUNCTION(BlueprintCallable) + void Configure(int32 NItems, float ItemHeight, float InnerRadius, float MinSpoke, float Spread); + + UFUNCTION(BlueprintCallable) + URadialMenuLayout* GetLayout() const { return Layout; } + +protected: + virtual TSharedRef RebuildWidget() override; + virtual void ReleaseSlateResources(bool bReleaseChildren) override; + +private: + UPROPERTY() + TObjectPtr Layout; + + TSharedPtr MySlateWidget; +}; + diff --git a/build.py b/build.py index accd9c3f..2fc6b99f 100755 --- a/build.py +++ b/build.py @@ -408,7 +408,7 @@ def build_clean(): which resets your git repository to its pristine state. DANGER: this deletes any new source code you've created! """ - shell(f"{INTEGRATION}/luprex", "make clean") + shell(f"{INTEGRATION}", "make -f luprex/Makefile clean") shell(INTEGRATION, f"{UNREALENGINE}/Engine/Build/BatchFiles/{BUILD_BAT} -waitmutex IntegrationEditor {OS} {DEBUG} {INTEGRATION}/Integration.uproject -clean") Path(f"{INTEGRATION}/.vscode/compile_commands.json").unlink(missing_ok = True)