#pragma once #include "CoreMinimal.h" #include "UI/NeuStyle.h" #include "Widgets/DeclarativeSyntaxSupport.h" #include "Widgets/SCompoundWidget.h" #include "Widgets/SLeafWidget.h" /** * The neumorphic widget kit (DESIGN_UE.md section 8.5 widget table). Every widget is Slate, painted in code with * FNeuPainter; none needs a texture or font asset. The same widgets are used on screen and, through * UWidgetComponent::SetSlateWidget, on the world dashboards. * * SNeuPanel extruded card or inset well around a child * SNeuButton extruded; pressed or "on" becomes inset with cyan text; disabled is drawn at 35 percent * SNeuGauge inset circular well with a 270 degree arc, value in the centre, label below, no needle * SNeuSlot hotbar slot, 52 px, selected = inset * SNeuCell inventory cell, 74 px, empty = inset * SNeuRow list row: icon, name, description, cost chips, button * SNeuWindow the PDA / station frame: header with tab buttons, close button, body * SNeuSlider inset track, extruded thumb * SNeuProgress inset track with a fill * plus the small helpers SNeuText, SNeuIcon and SNeuChip. */ using FNeuAction = TFunction; // --------------------------------------------------------------------------------------------------------- // Shared painting (also used by the HUD and the dashboards, which paint themselves in one leaf widget) // --------------------------------------------------------------------------------------------------------- namespace Neu { /** Start angle (degrees, screen space, 0 = right, 90 = down) and sweep of the 270 degree gauge arc: the gap is at the bottom. */ constexpr float GaugeStartDeg = 135.f; constexpr float GaugeSweepDeg = 270.f; struct FGaugeStyle { float Diameter = 72.f; float Thickness = 7.f; float ArcRadius = 0.f; // 0 = derived from the diameter and thickness float ValueTextPx = 0.f; // 0 = derived from the diameter float LabelTextPx = 12.f; bool bGlow = false; bool bWell = true; // draw the inset circular well behind the arc }; /** The gauge: inset well, 13 percent track, value arc with round caps, value text in the centre, label under the well. */ THEDESCENT_API void PaintGauge(FNeuPainter& P, FVector2f Center, const FGaugeStyle& Style, float Value01, const FString& ValueText, const FString& Label, const FLinearColor& Color); /** Inset track with a fill (progress bars, slider tracks): Value01 of the width, in Color. */ THEDESCENT_API void PaintTrack(FNeuPainter& P, FVector2f Pos, FVector2f Size, float Value01, const FLinearColor& Color); /** A small pill with text. Returns its width. */ THEDESCENT_API float PaintChip(FNeuPainter& P, FVector2f Pos, float Height, const FString& Text, const FLinearColor& Color, bool bFilled = false); /** A hotbar slot (extruded, or inset when Selected) with its icon, count and key label. */ THEDESCENT_API void PaintSlot(FNeuPainter& P, FVector2f Pos, float Size, FName ItemId, int32 Count, bool bSelected, const FString& KeyLabel); /** A row of small bars that reads like a signal / filled dots: Filled of Total in Color. */ THEDESCENT_API void PaintDots(FNeuPainter& P, FVector2f Pos, float Dot, float Gap, int32 Total, int32 Filled, const FLinearColor& Color); } // --------------------------------------------------------------------------------------------------------- // Helpers // --------------------------------------------------------------------------------------------------------- /** A line (or a few wrapped lines) of text. Shrinks to MinPx, then elides. */ class THEDESCENT_API SNeuText : public SLeafWidget { public: SLATE_BEGIN_ARGS(SNeuText) : _Text(FString()) , _Weight(ENeuFont::Regular) , _Px(15.f) , _Color(FLinearColor::White) , _HAlign(ENeuAlign::Left) , _VAlign(ENeuVAlign::Center) , _MaxLines(1) , _MinPx(0.f) , _MinWidth(0.f) {} SLATE_ATTRIBUTE(FString, Text) SLATE_ARGUMENT(ENeuFont, Weight) SLATE_ARGUMENT(float, Px) SLATE_ATTRIBUTE(FLinearColor, Color) SLATE_ARGUMENT(ENeuAlign, HAlign) SLATE_ARGUMENT(ENeuVAlign, VAlign) SLATE_ARGUMENT(int32, MaxLines) SLATE_ARGUMENT(float, MinPx) SLATE_ARGUMENT(float, MinWidth) SLATE_END_ARGS() void Construct(const FArguments& InArgs); virtual int32 OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const override; virtual FVector2D ComputeDesiredSize(float LayoutScaleMultiplier) const override; private: TAttribute Text; TAttribute Color; ENeuFont Weight = ENeuFont::Regular; float Px = 15.f; ENeuAlign HAlign = ENeuAlign::Left; ENeuVAlign VAlign = ENeuVAlign::Center; int32 MaxLines = 1; float MinPx = 0.f; float MinWidth = 0.f; }; /** An item icon (section 8.5 "icons drawn in code"). */ class THEDESCENT_API SNeuIcon : public SLeafWidget { public: SLATE_BEGIN_ARGS(SNeuIcon) : _ItemId(NAME_None), _Size(40.f), _Dim(false) {} SLATE_ATTRIBUTE(FName, ItemId) SLATE_ARGUMENT(float, Size) SLATE_ATTRIBUTE(bool, Dim) SLATE_END_ARGS() void Construct(const FArguments& InArgs); virtual int32 OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const override; virtual FVector2D ComputeDesiredSize(float) const override { return FVector2D(Size, Size); } private: TAttribute ItemId; TAttribute Dim; float Size = 40.f; }; /** A small pill of text (a cost, a count, a tag). */ class THEDESCENT_API SNeuChip : public SLeafWidget { public: SLATE_BEGIN_ARGS(SNeuChip) : _Text(FString()), _Color(FLinearColor::White), _Height(24.f), _Filled(false) {} SLATE_ATTRIBUTE(FString, Text) SLATE_ATTRIBUTE(FLinearColor, Color) SLATE_ARGUMENT(float, Height) SLATE_ARGUMENT(bool, Filled) SLATE_END_ARGS() void Construct(const FArguments& InArgs); virtual int32 OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const override; virtual FVector2D ComputeDesiredSize(float) const override; private: TAttribute Text; TAttribute Color; float Height = 24.f; bool bFilled = false; }; // --------------------------------------------------------------------------------------------------------- // The nine widgets of the table // --------------------------------------------------------------------------------------------------------- /** Extruded card or inset well around one child. */ class THEDESCENT_API SNeuPanel : public SCompoundWidget { public: SLATE_BEGIN_ARGS(SNeuPanel) : _Inset(false), _Small(false), _Radius(16.f), _Padding(FMargin(14.f)), _Body(FLinearColor::Transparent) {} SLATE_ARGUMENT(bool, Inset) SLATE_ARGUMENT(bool, Small) SLATE_ARGUMENT(float, Radius) SLATE_ARGUMENT(FMargin, Padding) /** Alpha 0 = the style's own body colour. */ SLATE_ARGUMENT(FLinearColor, Body) SLATE_DEFAULT_SLOT(FArguments, Content) SLATE_END_ARGS() void Construct(const FArguments& InArgs); virtual int32 OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const override; private: bool bInset = false; bool bSmall = false; float Radius = 16.f; FLinearColor Body = FLinearColor::Transparent; }; /** Button. Pressed, hovered and "on" states; disabled at 35 percent. Click fires on release inside the button. */ class THEDESCENT_API SNeuButton : public SLeafWidget { public: SLATE_BEGIN_ARGS(SNeuButton) : _Text(FString()) , _IconItem(NAME_None) , _On(false) , _Px(16.f) , _Height(38.f) , _MinWidth(0.f) , _Accent(FLinearColor::Transparent) , _Radius(12.f) {} SLATE_ATTRIBUTE(FString, Text) SLATE_ARGUMENT(FName, IconItem) /** "On" (toggled, selected tab): drawn inset with cyan text. */ SLATE_ATTRIBUTE(bool, On) SLATE_ARGUMENT(float, Px) SLATE_ARGUMENT(float, Height) SLATE_ARGUMENT(float, MinWidth) /** Alpha 0 = the style's cyan. */ SLATE_ARGUMENT(FLinearColor, Accent) SLATE_ARGUMENT(float, Radius) SLATE_ARGUMENT(FNeuAction, OnClick) SLATE_END_ARGS() void Construct(const FArguments& InArgs); virtual int32 OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const override; virtual FVector2D ComputeDesiredSize(float) const override; virtual FReply OnMouseButtonDown(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override; virtual FReply OnMouseButtonUp(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override; virtual void OnMouseEnter(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override; virtual void OnMouseLeave(const FPointerEvent& MouseEvent) override; virtual FCursorReply OnCursorQuery(const FGeometry& MyGeometry, const FPointerEvent& CursorEvent) const override; bool IsPressed() const { return bPressed; } /** Fires the click action as a real click would (tests and the keyboard use it). */ void Click(); /** Pins the hover / pressed look (the gallery and tests show states without a mouse). */ void ForceState(bool bInHovered, bool bInPressed) { bHovered = bInHovered; bPressed = bInPressed; } private: TAttribute Text; TAttribute On; FName IconItem; float Px = 16.f; float Height = 38.f; float MinWidth = 0.f; float Radius = 12.f; FLinearColor Accent = FLinearColor::Transparent; FNeuAction OnClick; bool bPressed = false; bool bHovered = false; }; /** Gauge: inset circular well, 270 degree arc (13 percent track), value in the centre, label below. No needle. */ class THEDESCENT_API SNeuGauge : public SLeafWidget { public: SLATE_BEGIN_ARGS(SNeuGauge) : _Value(0.f) , _ValueText(FString()) , _Label(FString()) , _Color(FLinearColor::White) , _Diameter(72.f) , _Thickness(0.f) , _Glow(false) {} /** 0..1 */ SLATE_ATTRIBUTE(float, Value) SLATE_ATTRIBUTE(FString, ValueText) SLATE_ATTRIBUTE(FString, Label) SLATE_ATTRIBUTE(FLinearColor, Color) SLATE_ARGUMENT(float, Diameter) /** 0 = a tenth of the diameter. */ SLATE_ARGUMENT(float, Thickness) SLATE_ARGUMENT(bool, Glow) SLATE_END_ARGS() void Construct(const FArguments& InArgs); virtual int32 OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const override; virtual FVector2D ComputeDesiredSize(float) const override; private: TAttribute Value; TAttribute ValueText; TAttribute Label; TAttribute Color; Neu::FGaugeStyle Style; }; /** Hotbar slot: extruded square, selected = inset. */ class THEDESCENT_API SNeuSlot : public SLeafWidget { public: SLATE_BEGIN_ARGS(SNeuSlot) : _ItemId(NAME_None), _Count(0), _Selected(false), _KeyLabel(FString()), _Size(52.f) {} SLATE_ATTRIBUTE(FName, ItemId) SLATE_ATTRIBUTE(int32, Count) SLATE_ATTRIBUTE(bool, Selected) SLATE_ATTRIBUTE(FString, KeyLabel) SLATE_ARGUMENT(float, Size) SLATE_END_ARGS() void Construct(const FArguments& InArgs); virtual int32 OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const override; virtual FVector2D ComputeDesiredSize(float) const override { return FVector2D(Size, Size); } private: TAttribute ItemId; TAttribute Count; TAttribute Selected; TAttribute KeyLabel; float Size = 52.f; }; /** Inventory cell: extruded 74 px square, empty = inset. A greyed cell (the weapon slot) shows its caption and a dim icon. */ class THEDESCENT_API SNeuCell : public SLeafWidget { public: SLATE_BEGIN_ARGS(SNeuCell) : _ItemId(NAME_None), _Count(0), _Selected(false), _Caption(FString()), _Greyed(false), _Size(74.f) {} SLATE_ATTRIBUTE(FName, ItemId) SLATE_ATTRIBUTE(int32, Count) SLATE_ATTRIBUTE(bool, Selected) /** Small text under the icon (the item's short name, or "WEAPON" on the locked slot). */ SLATE_ATTRIBUTE(FString, Caption) SLATE_ARGUMENT(bool, Greyed) SLATE_ARGUMENT(float, Size) SLATE_ARGUMENT(FNeuAction, OnClick) SLATE_END_ARGS() void Construct(const FArguments& InArgs); virtual int32 OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const override; virtual FVector2D ComputeDesiredSize(float) const override { return FVector2D(Size, Size); } virtual FReply OnMouseButtonUp(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override; private: TAttribute ItemId; TAttribute Count; TAttribute Selected; TAttribute Caption; bool bGreyed = false; float Size = 74.f; FNeuAction OnClick; }; /** One cost chip of a row. */ struct FNeuChipData { FString Text; FLinearColor Color = FLinearColor::White; }; /** List row: 40 px icon, name, description, cost chips, button. */ class THEDESCENT_API SNeuRow : public SCompoundWidget { public: SLATE_BEGIN_ARGS(SNeuRow) : _ItemId(NAME_None) , _Name(FString()) , _Desc(FString()) , _ButtonText(FString()) , _ButtonEnabled(true) , _Locked(false) , _Height(0.f) {} SLATE_ARGUMENT(FName, ItemId) SLATE_ARGUMENT(FString, Name) SLATE_ARGUMENT(FString, Desc) SLATE_ARGUMENT(TArray, Chips) /** Empty = no button. */ SLATE_ARGUMENT(FString, ButtonText) SLATE_ARGUMENT(bool, ButtonEnabled) /** A locked row (a recipe that is not available) is drawn dimmed. */ SLATE_ARGUMENT(bool, Locked) /** 0 = 64, or 80 when the row has chips. */ SLATE_ARGUMENT(float, Height) SLATE_ARGUMENT(FNeuAction, OnButton) SLATE_END_ARGS() void Construct(const FArguments& InArgs); virtual int32 OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const override; private: TSharedPtr Button; }; /** One tab of a window header. */ struct FNeuTab { FName Id; FString Label; }; /** The PDA and station frame: extruded frame, header with title, tab buttons and a close button, then the body. */ class THEDESCENT_API SNeuWindow : public SCompoundWidget { public: SLATE_BEGIN_ARGS(SNeuWindow) : _Title(FString()), _ShowClose(true), _DesiredWidth(980.f), _DesiredHeight(640.f), _TabMinWidth(96.f) {} SLATE_ATTRIBUTE(FString, Title) SLATE_ARGUMENT(TArray, Tabs) SLATE_ATTRIBUTE(FName, ActiveTab) SLATE_ARGUMENT(TFunction, OnTab) SLATE_ARGUMENT(FNeuAction, OnClose) SLATE_ARGUMENT(bool, ShowClose) SLATE_ARGUMENT(float, DesiredWidth) SLATE_ARGUMENT(float, DesiredHeight) SLATE_ARGUMENT(float, TabMinWidth) SLATE_DEFAULT_SLOT(FArguments, Content) SLATE_END_ARGS() void Construct(const FArguments& InArgs); virtual int32 OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const override; virtual FVector2D ComputeDesiredSize(float LayoutScaleMultiplier) const override; private: float DesiredWidth = 980.f; float DesiredHeight = 640.f; }; /** Slider: inset track with a cyan fill, extruded round thumb. Enabled state comes from the widget (SetEnabled). */ class THEDESCENT_API SNeuSlider : public SLeafWidget { public: SLATE_BEGIN_ARGS(SNeuSlider) : _Value(0.f), _ValueText(FString()), _Color(FLinearColor::Transparent), _Width(240.f) {} /** 0..1 */ SLATE_ATTRIBUTE(float, Value) SLATE_ATTRIBUTE(FString, ValueText) SLATE_ARGUMENT(FLinearColor, Color) SLATE_ARGUMENT(float, Width) SLATE_ARGUMENT(TFunction, OnValue) SLATE_END_ARGS() void Construct(const FArguments& InArgs); virtual int32 OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const override; virtual FVector2D ComputeDesiredSize(float) const override; virtual FReply OnMouseButtonDown(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override; virtual FReply OnMouseButtonUp(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override; virtual FReply OnMouseMove(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override; /** The value 0..1 the pointer position maps to, for a slider of the given local width. Static so tests can check it. */ static float ValueAtX(float LocalX, float LocalWidth, float ThumbPx); private: void SetFromPointer(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent); TAttribute Value; TAttribute ValueText; FLinearColor Color = FLinearColor::Transparent; float Width = 240.f; TFunction OnValue; bool bDragging = false; }; /** Progress bar: inset track with a fill and optional text on it. */ class THEDESCENT_API SNeuProgress : public SLeafWidget { public: SLATE_BEGIN_ARGS(SNeuProgress) : _Value(0.f), _Text(FString()), _Color(FLinearColor::Transparent), _Height(14.f), _Width(0.f) {} SLATE_ATTRIBUTE(float, Value) SLATE_ATTRIBUTE(FString, Text) SLATE_ATTRIBUTE(FLinearColor, Color) SLATE_ARGUMENT(float, Height) SLATE_ARGUMENT(float, Width) SLATE_END_ARGS() void Construct(const FArguments& InArgs); virtual int32 OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const override; virtual FVector2D ComputeDesiredSize(float) const override { return FVector2D(Width, Height); } private: TAttribute Value; TAttribute Text; TAttribute Color; float Height = 14.f; float Width = 0.f; };