#include "UI/NeuWidgets.h" #include "Core/Tuning/DescentTune_UI.h" #include "UI/NeuIcons.h" #include "Input/CursorReply.h" #include "Widgets/Layout/SBox.h" #include "Widgets/SBoxPanel.h" namespace UiTune = Descent::Tune::UI; namespace { /** Alpha multiplier for a widget: disabled widgets at 35 percent, plus the style's own fade. */ float NeuPaintAlpha(const FWidgetStyle& Style, bool bEnabled) { return Style.GetColorAndOpacityTint().A * (bEnabled ? 1.f : UiTune::DisabledOpacity); } } // --------------------------------------------------------------------------------------------------------- // Shared painting // --------------------------------------------------------------------------------------------------------- namespace Neu { void PaintGauge(FNeuPainter& P, FVector2f Center, const FGaugeStyle& St, float Value01, const FString& ValueText, const FString& Label, const FLinearColor& Color) { const float D = St.Diameter; if (St.bWell) { P.InsetDisc(Center, D, D < 90.f); } const float Th = St.Thickness > 0.f ? St.Thickness : FMath::Clamp(D * 0.1f, 4.f, 14.f); const float Rad = St.ArcRadius > 0.f ? St.ArcRadius : D * 0.5f - Th * 0.5f - FMath::Max(4.f, D * 0.07f); const float V = FMath::Clamp(Value01, 0.f, 1.f); // Track at 13 percent, then the value arc, both with round caps. No needle anywhere. P.Arc(Center, Rad, GaugeStartDeg, GaugeSweepDeg, Th, WithAlpha(Color, 0.13f), true); if (V > 0.004f) { if (St.bGlow) { P.ArcGlow(Center, Rad, GaugeStartDeg, GaugeSweepDeg * V, Th, Color); } P.Arc(Center, Rad, GaugeStartDeg, GaugeSweepDeg * V, Th, Color, true); } const float ValuePx = St.ValueTextPx > 0.f ? St.ValueTextPx : FMath::Max(11.f, D * 0.26f); const float InnerW = Rad * 1.5f; P.TextBox(ValueText, Center - FVector2f(InnerW * 0.5f, ValuePx * 0.62f), FVector2f(InnerW, ValuePx * 1.24f), ENeuFont::Bold, ValuePx, Txt(), ENeuAlign::Center, ENeuVAlign::Center, ValuePx * 0.6f); if (!Label.IsEmpty()) { const float LabelPx = St.LabelTextPx; P.TextBox(Label, FVector2f(Center.X - D * 0.7f, Center.Y + D * 0.5f + 2.f), FVector2f(D * 1.4f, LabelPx * 1.4f), ENeuFont::Bold, LabelPx, Dim(), ENeuAlign::Center, ENeuVAlign::Top, LabelPx * 0.7f); } } void PaintTrack(FNeuPainter& P, FVector2f Pos, FVector2f Size, float Value01, const FLinearColor& Color) { const float R = Size.Y * 0.5f; P.Inset(Pos, Size, R, true); const float V = FMath::Clamp(Value01, 0.f, 1.f); const float Pad = FMath::Max(2.f, Size.Y * 0.18f); const float FullW = Size.X - 2.f * Pad; const float FillW = FMath::Max(Size.Y - 2.f * Pad, FullW * V); if (V > 0.002f) { P.RoundRect(Pos + FVector2f(Pad, Pad), FVector2f(FillW, Size.Y - 2.f * Pad), (Size.Y - 2.f * Pad) * 0.5f, Color); } } float PaintChip(FNeuPainter& P, FVector2f Pos, float Height, const FString& Text, const FLinearColor& Color, bool bFilled) { const float Px = FMath::Max(10.f, Height * 0.56f); const float W = MeasureText(Text, ENeuFont::Bold, Px).X + Height * 0.9f; if (bFilled) { P.RoundRect(Pos, FVector2f(W, Height), Height * 0.5f, WithAlpha(Color, 0.9f)); P.TextBox(Text, Pos, FVector2f(W, Height), ENeuFont::Bold, Px, Bg(), ENeuAlign::Center, ENeuVAlign::Center); } else { P.Inset(Pos, FVector2f(W, Height), Height * 0.5f, true); P.TextBox(Text, Pos, FVector2f(W, Height), ENeuFont::Bold, Px, Color, ENeuAlign::Center, ENeuVAlign::Center); } return W; } void PaintSlot(FNeuPainter& P, FVector2f Pos, float Size, FName ItemId, int32 Count, bool bSelected, const FString& KeyLabel) { const FVector2f S(Size, Size); if (bSelected) { P.Inset(Pos, S, 12.f, true); P.RoundRectOutline(Pos + FVector2f(1.f, 1.f), S - FVector2f(2.f, 2.f), 11.f, 1.5f, WithAlpha(Cyan(), 0.7f)); } else { P.Extruded(Pos, S, 12.f, true); } if (!ItemId.IsNone()) { DrawItemIcon(P, Pos + S * 0.5f + FVector2f(0.f, -1.f), Size * 0.62f, ItemId); } if (Count > 1) { P.TextBox(FString::FromInt(Count), Pos + FVector2f(S.X * 0.4f, S.Y - 18.f), FVector2f(S.X * 0.56f, 16.f), ENeuFont::Bold, 12.f, Txt(), ENeuAlign::Right, ENeuVAlign::Center); } if (!KeyLabel.IsEmpty()) { P.TextBox(KeyLabel, Pos + FVector2f(6.f, 3.f), FVector2f(S.X * 0.5f, 14.f), ENeuFont::Bold, 11.f, bSelected ? Cyan() : Dim(), ENeuAlign::Left, ENeuVAlign::Center); } } void PaintDots(FNeuPainter& P, FVector2f Pos, float Dot, float Gap, int32 Total, int32 Filled, const FLinearColor& Color) { for (int32 I = 0; I < Total; ++I) { const FVector2f C = Pos + FVector2f(I * (Dot + Gap) + Dot * 0.5f, Dot * 0.5f); P.Disc(C, Dot * 0.5f, I < Filled ? Color : WithAlpha(Color, 0.16f)); } } } // --------------------------------------------------------------------------------------------------------- // SNeuText // --------------------------------------------------------------------------------------------------------- void SNeuText::Construct(const FArguments& InArgs) { Text = InArgs._Text; Color = InArgs._Color; Weight = InArgs._Weight; Px = InArgs._Px; HAlign = InArgs._HAlign; VAlign = InArgs._VAlign; MaxLines = FMath::Max(1, InArgs._MaxLines); MinPx = InArgs._MinPx; MinWidth = InArgs._MinWidth; SetCanTick(false); } FVector2D SNeuText::ComputeDesiredSize(float) const { const FVector2f Ext = Neu::MeasureText(Text.Get(), Weight, Px); const float LineH = Neu::MeasureText(TEXT("Ag"), Weight, Px).Y; return FVector2D(FMath::Max(MinWidth, Ext.X), LineH * MaxLines); } int32 SNeuText::OnPaint(const FPaintArgs&, const FGeometry& AllottedGeometry, const FSlateRect&, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const { FNeuPainter P(OutDrawElements, AllottedGeometry, LayerId, NeuPaintAlpha(InWidgetStyle, ShouldBeEnabled(bParentEnabled))); const FVector2f Size = AllottedGeometry.GetLocalSize(); const FString S = Text.Get(); if (MaxLines <= 1) { P.TextBox(S, FVector2f::ZeroVector, Size, Weight, Px, Color.Get(), HAlign, VAlign, MinPx); } else { TArray Lines; Neu::WrapText(S, Weight, Px, Size.X, MaxLines, Lines); const float LineH = Neu::MeasureText(TEXT("Ag"), Weight, Px).Y; for (int32 I = 0; I < Lines.Num(); ++I) { P.TextBox(Lines[I], FVector2f(0.f, I * LineH), FVector2f(Size.X, LineH), Weight, Px, Color.Get(), HAlign, ENeuVAlign::Top); } } return P.Layer + 1; } // --------------------------------------------------------------------------------------------------------- // SNeuIcon, SNeuChip // --------------------------------------------------------------------------------------------------------- void SNeuIcon::Construct(const FArguments& InArgs) { ItemId = InArgs._ItemId; Dim = InArgs._Dim; Size = InArgs._Size; SetCanTick(false); } int32 SNeuIcon::OnPaint(const FPaintArgs&, const FGeometry& AllottedGeometry, const FSlateRect&, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const { FNeuPainter P(OutDrawElements, AllottedGeometry, LayerId, NeuPaintAlpha(InWidgetStyle, ShouldBeEnabled(bParentEnabled))); const FVector2f S = AllottedGeometry.GetLocalSize(); Neu::DrawItemIcon(P, S * 0.5f, FMath::Min(S.X, S.Y), ItemId.Get(), Dim.Get(false)); return P.Layer + 1; } void SNeuChip::Construct(const FArguments& InArgs) { Text = InArgs._Text; Color = InArgs._Color; Height = InArgs._Height; bFilled = InArgs._Filled; SetCanTick(false); } FVector2D SNeuChip::ComputeDesiredSize(float) const { const float Px = FMath::Max(10.f, Height * 0.56f); return FVector2D(Neu::MeasureText(Text.Get(), ENeuFont::Bold, Px).X + Height * 0.9f, Height); } int32 SNeuChip::OnPaint(const FPaintArgs&, const FGeometry& AllottedGeometry, const FSlateRect&, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const { FNeuPainter P(OutDrawElements, AllottedGeometry, LayerId, NeuPaintAlpha(InWidgetStyle, ShouldBeEnabled(bParentEnabled))); Neu::PaintChip(P, FVector2f::ZeroVector, Height, Text.Get(), Color.Get(), bFilled); return P.Layer + 1; } // --------------------------------------------------------------------------------------------------------- // SNeuPanel // --------------------------------------------------------------------------------------------------------- void SNeuPanel::Construct(const FArguments& InArgs) { bInset = InArgs._Inset; bSmall = InArgs._Small; Radius = InArgs._Radius; Body = InArgs._Body; ChildSlot.Padding(InArgs._Padding) [ InArgs._Content.Widget ]; } int32 SNeuPanel::OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const { FNeuPainter P(OutDrawElements, AllottedGeometry, LayerId, NeuPaintAlpha(InWidgetStyle, ShouldBeEnabled(bParentEnabled))); const FLinearColor* BodyPtr = Body.A > 0.f ? &Body : nullptr; if (bInset) { P.Inset(FVector2f::ZeroVector, AllottedGeometry.GetLocalSize(), Radius, bSmall, BodyPtr); } else { P.Extruded(FVector2f::ZeroVector, AllottedGeometry.GetLocalSize(), Radius, bSmall, BodyPtr); } return SCompoundWidget::OnPaint(Args, AllottedGeometry, MyCullingRect, OutDrawElements, P.Layer + 1, InWidgetStyle, bParentEnabled); } // --------------------------------------------------------------------------------------------------------- // SNeuButton // --------------------------------------------------------------------------------------------------------- void SNeuButton::Construct(const FArguments& InArgs) { Text = InArgs._Text; On = InArgs._On; IconItem = InArgs._IconItem; Px = InArgs._Px; Height = InArgs._Height; MinWidth = InArgs._MinWidth; Radius = InArgs._Radius; Accent = InArgs._Accent; OnClick = InArgs._OnClick; SetCanTick(false); } FVector2D SNeuButton::ComputeDesiredSize(float) const { float W = Neu::MeasureText(Text.Get(), ENeuFont::Bold, Px).X + 36.f; if (!IconItem.IsNone()) { W += Height * 0.8f; } return FVector2D(FMath::Max(MinWidth, W), Height); } void SNeuButton::Click() { if (IsEnabled() && OnClick) { OnClick(); } } int32 SNeuButton::OnPaint(const FPaintArgs&, const FGeometry& AllottedGeometry, const FSlateRect&, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const { const bool bEnabled = ShouldBeEnabled(bParentEnabled); FNeuPainter P(OutDrawElements, AllottedGeometry, LayerId, NeuPaintAlpha(InWidgetStyle, bEnabled)); const FVector2f Size = AllottedGeometry.GetLocalSize(); const bool bIsOn = On.Get(false); const bool bDown = bPressed && bHovered; const FLinearColor AccentColor = Accent.A > 0.f ? Accent : Neu::Cyan(); if (bIsOn || bDown) { P.Inset(FVector2f::ZeroVector, Size, Radius, true); } else { const FLinearColor Lit = Neu::Lerp(Neu::Bg(), Neu::Hi(), 0.55f); P.Extruded(FVector2f::ZeroVector, Size, Radius, true, (bHovered && bEnabled) ? &Lit : nullptr); } FLinearColor TextColor = (bIsOn || bDown) ? AccentColor : (bHovered && bEnabled ? Neu::Txt() : Neu::Txt() * 0.92f); TextColor.A = 1.f; const FVector2f Nudge = bDown ? FVector2f(0.f, 1.f) : FVector2f::ZeroVector; float TextLeft = 0.f; float TextWidth = Size.X; if (!IconItem.IsNone()) { const float IconSize = Size.Y * 0.62f; Neu::DrawItemIcon(P, FVector2f(Size.Y * 0.5f + 4.f, Size.Y * 0.5f) + Nudge, IconSize, IconItem); TextLeft = Size.Y * 0.8f; TextWidth = Size.X - TextLeft; } P.TextBox(Text.Get(), FVector2f(TextLeft, 0.f) + Nudge, FVector2f(TextWidth, Size.Y), ENeuFont::Bold, Px, TextColor, ENeuAlign::Center, ENeuVAlign::Center, Px * 0.75f); return P.Layer + 1; } FReply SNeuButton::OnMouseButtonDown(const FGeometry&, const FPointerEvent& MouseEvent) { if (MouseEvent.GetEffectingButton() == EKeys::LeftMouseButton && IsEnabled()) { bPressed = true; bHovered = true; return FReply::Handled().CaptureMouse(AsShared()); } return FReply::Unhandled(); } FReply SNeuButton::OnMouseButtonUp(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) { if (MouseEvent.GetEffectingButton() == EKeys::LeftMouseButton && bPressed) { bPressed = false; const bool bInside = MyGeometry.IsUnderLocation(MouseEvent.GetScreenSpacePosition()); if (bInside) { Click(); } return FReply::Handled().ReleaseMouseCapture(); } return FReply::Unhandled(); } void SNeuButton::OnMouseEnter(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) { SLeafWidget::OnMouseEnter(MyGeometry, MouseEvent); bHovered = true; } void SNeuButton::OnMouseLeave(const FPointerEvent& MouseEvent) { SLeafWidget::OnMouseLeave(MouseEvent); bHovered = false; } FCursorReply SNeuButton::OnCursorQuery(const FGeometry&, const FPointerEvent&) const { return IsEnabled() ? FCursorReply::Cursor(EMouseCursor::Hand) : FCursorReply::Unhandled(); } // --------------------------------------------------------------------------------------------------------- // SNeuGauge // --------------------------------------------------------------------------------------------------------- void SNeuGauge::Construct(const FArguments& InArgs) { Value = InArgs._Value; ValueText = InArgs._ValueText; Label = InArgs._Label; Color = InArgs._Color; Style.Diameter = InArgs._Diameter; Style.Thickness = InArgs._Thickness; Style.bGlow = InArgs._Glow; SetCanTick(false); } FVector2D SNeuGauge::ComputeDesiredSize(float) const { return FVector2D(Style.Diameter, Style.Diameter + (Label.Get().IsEmpty() ? 0.f : Style.LabelTextPx * 1.4f + 2.f)); } int32 SNeuGauge::OnPaint(const FPaintArgs&, const FGeometry& AllottedGeometry, const FSlateRect&, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const { FNeuPainter P(OutDrawElements, AllottedGeometry, LayerId, NeuPaintAlpha(InWidgetStyle, ShouldBeEnabled(bParentEnabled))); const FVector2f Local = AllottedGeometry.GetLocalSize(); Neu::PaintGauge(P, FVector2f(Local.X * 0.5f, Style.Diameter * 0.5f), Style, Value.Get(0.f), ValueText.Get(), Label.Get(), Color.Get()); return P.Layer + 1; } // --------------------------------------------------------------------------------------------------------- // SNeuSlot, SNeuCell // --------------------------------------------------------------------------------------------------------- void SNeuSlot::Construct(const FArguments& InArgs) { ItemId = InArgs._ItemId; Count = InArgs._Count; Selected = InArgs._Selected; KeyLabel = InArgs._KeyLabel; Size = InArgs._Size; SetCanTick(false); } int32 SNeuSlot::OnPaint(const FPaintArgs&, const FGeometry& AllottedGeometry, const FSlateRect&, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const { FNeuPainter P(OutDrawElements, AllottedGeometry, LayerId, NeuPaintAlpha(InWidgetStyle, ShouldBeEnabled(bParentEnabled))); Neu::PaintSlot(P, FVector2f::ZeroVector, AllottedGeometry.GetLocalSize().X, ItemId.Get(NAME_None), Count.Get(0), Selected.Get(false), KeyLabel.Get()); return P.Layer + 1; } void SNeuCell::Construct(const FArguments& InArgs) { ItemId = InArgs._ItemId; Count = InArgs._Count; Selected = InArgs._Selected; Caption = InArgs._Caption; bGreyed = InArgs._Greyed; Size = InArgs._Size; OnClick = InArgs._OnClick; SetCanTick(false); } FReply SNeuCell::OnMouseButtonUp(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) { if (MouseEvent.GetEffectingButton() == EKeys::LeftMouseButton && OnClick && !bGreyed && MyGeometry.IsUnderLocation(MouseEvent.GetScreenSpacePosition())) { OnClick(); return FReply::Handled(); } return FReply::Unhandled(); } int32 SNeuCell::OnPaint(const FPaintArgs&, const FGeometry& AllottedGeometry, const FSlateRect&, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const { FNeuPainter P(OutDrawElements, AllottedGeometry, LayerId, NeuPaintAlpha(InWidgetStyle, ShouldBeEnabled(bParentEnabled))); const FVector2f S = AllottedGeometry.GetLocalSize(); const FName Item = ItemId.Get(NAME_None); const bool bEmpty = Item.IsNone(); const bool bSel = Selected.Get(false); if (bEmpty || bGreyed) { P.Inset(FVector2f::ZeroVector, S, 14.f, true); } else { P.Extruded(FVector2f::ZeroVector, S, 14.f, true); } if (bSel) { P.RoundRectOutline(FVector2f(1.f, 1.f), S - FVector2f(2.f, 2.f), 13.f, 1.5f, Neu::WithAlpha(Neu::Cyan(), 0.8f)); } if (bGreyed) { // The locked weapon slot: a dim silhouette and its caption, nothing to click. Neu::DrawItemIcon(P, S * 0.5f + FVector2f(0.f, -6.f), S.X * 0.5f, TEXT("weapon"), true); } else if (!bEmpty) { Neu::DrawItemIcon(P, S * 0.5f + FVector2f(0.f, -6.f), S.X * 0.5f, Item); } const FString Cap = Caption.Get(); if (!Cap.IsEmpty()) { P.TextBox(Cap, FVector2f(4.f, S.Y - 20.f), FVector2f(S.X - 8.f, 16.f), ENeuFont::Bold, 11.f, bGreyed ? Neu::Dim() * 0.8f : Neu::Dim(), ENeuAlign::Center, ENeuVAlign::Center, 8.f); } const int32 N = Count.Get(0); if (N > 1 && !bGreyed) { P.TextBox(FString::FromInt(N), FVector2f(S.X * 0.4f, 4.f), FVector2f(S.X * 0.56f, 16.f), ENeuFont::Bold, 13.f, Neu::Txt(), ENeuAlign::Right, ENeuVAlign::Center); } return P.Layer + 1; } // --------------------------------------------------------------------------------------------------------- // SNeuRow // --------------------------------------------------------------------------------------------------------- void SNeuRow::Construct(const FArguments& InArgs) { // Chips go under the description, so a row stays readable in a half-width column. TSharedRef Chips = SNew(SHorizontalBox); for (const FNeuChipData& C : InArgs._Chips) { Chips->AddSlot().AutoWidth().VAlign(VAlign_Center).Padding(0.f, 0.f, 6.f, 0.f) [ SNew(SNeuChip).Text(C.Text).Color(C.Color).Height(22.f) ]; } const bool bHasChips = InArgs._Chips.Num() > 0; TSharedRef TextColumn = SNew(SVerticalBox) + SVerticalBox::Slot().AutoHeight() [ SNew(SNeuText).Text(InArgs._Name).Weight(ENeuFont::Bold).Px(17.f).MinPx(13.f).Color(Neu::Txt()) ] + SVerticalBox::Slot().AutoHeight().Padding(0.f, 1.f, 0.f, 0.f) [ SNew(SNeuText).Text(InArgs._Desc).Px(13.f).MinPx(11.f).Color(Neu::Dim()) ]; if (bHasChips) { TextColumn->AddSlot().AutoHeight().Padding(0.f, 5.f, 0.f, 0.f)[ Chips ]; } TSharedRef Row = SNew(SHorizontalBox) + SHorizontalBox::Slot().AutoWidth().VAlign(VAlign_Center).Padding(14.f, 0.f, 12.f, 0.f) [ SNew(SNeuIcon).ItemId(InArgs._ItemId).Size(UiTune::RowIconPx).Dim(InArgs._Locked) ] + SHorizontalBox::Slot().FillWidth(1.f).VAlign(VAlign_Center) [ TextColumn ]; if (!InArgs._ButtonText.IsEmpty()) { Row->AddSlot().AutoWidth().VAlign(VAlign_Center).Padding(8.f, 0.f, 14.f, 0.f) [ SAssignNew(Button, SNeuButton).Text(InArgs._ButtonText).MinWidth(86.f).Px(15.f).Height(36.f).OnClick(InArgs._OnButton) ]; Button->SetEnabled(InArgs._ButtonEnabled && !InArgs._Locked); } else { Row->AddSlot().AutoWidth().Padding(0.f, 0.f, 10.f, 0.f)[ SNullWidget::NullWidget ]; } ChildSlot [ SNew(SBox).HeightOverride(InArgs._Height > 0.f ? InArgs._Height : (bHasChips ? 80.f : 64.f)) [ Row ] ]; if (InArgs._Locked) { SetEnabled(false); } } int32 SNeuRow::OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const { FNeuPainter P(OutDrawElements, AllottedGeometry, LayerId, NeuPaintAlpha(InWidgetStyle, ShouldBeEnabled(bParentEnabled))); P.Extruded(FVector2f::ZeroVector, AllottedGeometry.GetLocalSize(), 14.f, true); return SCompoundWidget::OnPaint(Args, AllottedGeometry, MyCullingRect, OutDrawElements, P.Layer + 1, InWidgetStyle, bParentEnabled); } // --------------------------------------------------------------------------------------------------------- // SNeuWindow // --------------------------------------------------------------------------------------------------------- void SNeuWindow::Construct(const FArguments& InArgs) { DesiredWidth = InArgs._DesiredWidth; DesiredHeight = InArgs._DesiredHeight; TSharedRef Tabs = SNew(SHorizontalBox); const TAttribute Active = InArgs._ActiveTab; const TFunction OnTab = InArgs._OnTab; for (const FNeuTab& Tab : InArgs._Tabs) { const FName Id = Tab.Id; Tabs->AddSlot().AutoWidth().VAlign(VAlign_Center).Padding(5.f, 0.f) [ SNew(SNeuButton) .Text(Tab.Label) .Px(15.f) .Height(36.f) .MinWidth(InArgs._TabMinWidth) .On_Lambda([Active, Id]() { return Active.Get(NAME_None) == Id; }) .OnClick([OnTab, Id]() { if (OnTab) { OnTab(Id); } }) ]; } TSharedRef Header = SNew(SHorizontalBox) + SHorizontalBox::Slot().AutoWidth().VAlign(VAlign_Center).Padding(28.f, 0.f, 24.f, 0.f) [ SNew(SNeuText).Text(InArgs._Title).Weight(ENeuFont::Bold).Px(20.f).MinPx(14.f).Color(Neu::Cyan()) ] + SHorizontalBox::Slot().FillWidth(1.f).VAlign(VAlign_Center).HAlign(HAlign_Left) [ Tabs ]; if (InArgs._ShowClose) { const FNeuAction Close = InArgs._OnClose; Header->AddSlot().AutoWidth().VAlign(VAlign_Center).Padding(8.f, 0.f, 22.f, 0.f) [ SNew(SNeuButton).Text(TEXT("X")).Px(16.f).Height(36.f).MinWidth(42.f).OnClick([Close]() { if (Close) { Close(); } }) ]; } ChildSlot [ SNew(SVerticalBox) + SVerticalBox::Slot().AutoHeight() [ SNew(SBox).HeightOverride(UiTune::WindowHeaderHeightPx) [ Header ] ] + SVerticalBox::Slot().FillHeight(1.f).Padding(20.f, 12.f, 20.f, 22.f) [ InArgs._Content.Widget ] ]; } FVector2D SNeuWindow::ComputeDesiredSize(float) const { return FVector2D(DesiredWidth, DesiredHeight); } int32 SNeuWindow::OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const { FNeuPainter P(OutDrawElements, AllottedGeometry, LayerId, NeuPaintAlpha(InWidgetStyle, ShouldBeEnabled(bParentEnabled))); const FVector2f S = AllottedGeometry.GetLocalSize(); P.Extruded(FVector2f::ZeroVector, S, 22.f, false); // The groove under the header. P.Inset(FVector2f(24.f, UiTune::WindowHeaderHeightPx), FVector2f(S.X - 48.f, 5.f), 2.5f, true); return SCompoundWidget::OnPaint(Args, AllottedGeometry, MyCullingRect, OutDrawElements, P.Layer + 1, InWidgetStyle, bParentEnabled); } // --------------------------------------------------------------------------------------------------------- // SNeuSlider // --------------------------------------------------------------------------------------------------------- void SNeuSlider::Construct(const FArguments& InArgs) { Value = InArgs._Value; ValueText = InArgs._ValueText; Color = InArgs._Color; Width = InArgs._Width; OnValue = InArgs._OnValue; SetCanTick(false); } FVector2D SNeuSlider::ComputeDesiredSize(float) const { return FVector2D(Width, UiTune::SliderThumbPx + 8.f); } float SNeuSlider::ValueAtX(float LocalX, float LocalWidth, float ThumbPx) { const float Span = FMath::Max(1.f, LocalWidth - ThumbPx); return FMath::Clamp((LocalX - ThumbPx * 0.5f) / Span, 0.f, 1.f); } int32 SNeuSlider::OnPaint(const FPaintArgs&, const FGeometry& AllottedGeometry, const FSlateRect&, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const { FNeuPainter P(OutDrawElements, AllottedGeometry, LayerId, NeuPaintAlpha(InWidgetStyle, ShouldBeEnabled(bParentEnabled))); const FVector2f S = AllottedGeometry.GetLocalSize(); const FString Label = ValueText.Get(); const float LabelW = Label.IsEmpty() ? 0.f : 64.f; const float TrackW = S.X - LabelW; const float Thumb = UiTune::SliderThumbPx; const float V = FMath::Clamp(Value.Get(0.f), 0.f, 1.f); const FLinearColor Fill = Color.A > 0.f ? Color : Neu::Cyan(); const float TrackY = (S.Y - UiTune::SliderTrackPx) * 0.5f; P.Inset(FVector2f(0.f, TrackY), FVector2f(TrackW, UiTune::SliderTrackPx), UiTune::SliderTrackPx * 0.5f, true); const float ThumbX = Thumb * 0.5f + V * (TrackW - Thumb); if (V > 0.002f) { P.RoundRect(FVector2f(3.f, TrackY + 3.f), FVector2f(FMath::Max(UiTune::SliderTrackPx - 6.f, ThumbX - 3.f), UiTune::SliderTrackPx - 6.f), (UiTune::SliderTrackPx - 6.f) * 0.5f, Fill); } P.ExtrudedDisc(FVector2f(ThumbX, S.Y * 0.5f), Thumb, true); P.Disc(FVector2f(ThumbX, S.Y * 0.5f), Thumb * 0.16f, bDragging ? Fill : Neu::Dim()); if (!Label.IsEmpty()) { P.TextBox(Label, FVector2f(TrackW + 8.f, 0.f), FVector2f(LabelW - 8.f, S.Y), ENeuFont::Bold, 15.f, Neu::Txt(), ENeuAlign::Right, ENeuVAlign::Center, 11.f); } return P.Layer + 1; } void SNeuSlider::SetFromPointer(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) { const FVector2f Local = MyGeometry.AbsoluteToLocal(MouseEvent.GetScreenSpacePosition()); const float LabelW = ValueText.Get().IsEmpty() ? 0.f : 64.f; const float V = ValueAtX(Local.X, MyGeometry.GetLocalSize().X - LabelW, UiTune::SliderThumbPx); if (OnValue) { OnValue(V); } } FReply SNeuSlider::OnMouseButtonDown(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) { if (MouseEvent.GetEffectingButton() == EKeys::LeftMouseButton && IsEnabled()) { bDragging = true; SetFromPointer(MyGeometry, MouseEvent); return FReply::Handled().CaptureMouse(AsShared()); } return FReply::Unhandled(); } FReply SNeuSlider::OnMouseMove(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) { if (bDragging) { SetFromPointer(MyGeometry, MouseEvent); return FReply::Handled(); } return FReply::Unhandled(); } FReply SNeuSlider::OnMouseButtonUp(const FGeometry&, const FPointerEvent& MouseEvent) { if (MouseEvent.GetEffectingButton() == EKeys::LeftMouseButton && bDragging) { bDragging = false; return FReply::Handled().ReleaseMouseCapture(); } return FReply::Unhandled(); } // --------------------------------------------------------------------------------------------------------- // SNeuProgress // --------------------------------------------------------------------------------------------------------- void SNeuProgress::Construct(const FArguments& InArgs) { Value = InArgs._Value; Text = InArgs._Text; Color = InArgs._Color; Height = InArgs._Height; Width = InArgs._Width; SetCanTick(false); } int32 SNeuProgress::OnPaint(const FPaintArgs&, const FGeometry& AllottedGeometry, const FSlateRect&, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const { FNeuPainter P(OutDrawElements, AllottedGeometry, LayerId, NeuPaintAlpha(InWidgetStyle, ShouldBeEnabled(bParentEnabled))); const FVector2f S = AllottedGeometry.GetLocalSize(); const FLinearColor Fill = Color.Get(FLinearColor::Transparent).A > 0.f ? Color.Get() : Neu::Cyan(); Neu::PaintTrack(P, FVector2f::ZeroVector, S, Value.Get(0.f), Fill); const FString T = Text.Get(); if (!T.IsEmpty()) { P.TextBox(T, FVector2f::ZeroVector, S, ENeuFont::Bold, FMath::Max(10.f, S.Y * 0.62f), Neu::Txt(), ENeuAlign::Center, ENeuVAlign::Center); } return P.Layer + 1; }