#include "UI/DescentWindows.h" #include "Core/Tuning/DescentTune_UI.h" #include "UI/NeuWidgets.h" #include "Framework/Application/SlateApplication.h" #include "InputCoreTypes.h" #include "Styling/CoreStyle.h" #include "Widgets/Images/SImage.h" #include "Widgets/Layout/SBox.h" #include "Widgets/Layout/SDPIScaler.h" #include "Widgets/SOverlay.h" namespace UiTune = Descent::Tune::UI; // --------------------------------------------------------------------------------------------------------- // Names, sizes, tabs // --------------------------------------------------------------------------------------------------------- namespace Descent::Ui { bool ParseWindowName(const FString& InName, EDescentWindow& OutWindow, FName& OutTab) { const FString N = InName.ToLower(); OutTab = NAME_None; struct FRow { const TCHAR* Name; EDescentWindow Window; const TCHAR* Tab; }; static const FRow Rows[] = { { TEXT("pda"), EDescentWindow::Pda, nullptr }, { TEXT("inventory"), EDescentWindow::Pda, TEXT("inventory") }, { TEXT("map"), EDescentWindow::Pda, TEXT("map") }, { TEXT("logs"), EDescentWindow::Pda, TEXT("logs") }, { TEXT("status"), EDescentWindow::Pda, TEXT("status") }, { TEXT("fabricator"), EDescentWindow::Fabricator, nullptr }, { TEXT("refinery"), EDescentWindow::Refinery, nullptr }, { TEXT("packer"), EDescentWindow::Packer, nullptr }, { TEXT("storage"), EDescentWindow::Storage, nullptr }, { TEXT("terminal"), EDescentWindow::Terminal, nullptr }, { TEXT("vat"), EDescentWindow::Vat, nullptr }, { TEXT("build"), EDescentWindow::Build, nullptr }, { TEXT("pause"), EDescentWindow::Pause, nullptr }, { TEXT("title"), EDescentWindow::Title, nullptr }, { TEXT("end"), EDescentWindow::EndCard, nullptr }, { TEXT("endcard"), EDescentWindow::EndCard, nullptr }, }; for (const FRow& R : Rows) { if (N == R.Name) { OutWindow = R.Window; OutTab = R.Tab ? FName(R.Tab) : NAME_None; return true; } } return false; } FName WindowName(EDescentWindow W) { switch (W) { case EDescentWindow::Pda: return TEXT("pda"); case EDescentWindow::Fabricator: return TEXT("fabricator"); case EDescentWindow::Refinery: return TEXT("refinery"); case EDescentWindow::Packer: return TEXT("packer"); case EDescentWindow::Storage: return TEXT("storage"); case EDescentWindow::Terminal: return TEXT("terminal"); case EDescentWindow::Vat: return TEXT("vat"); case EDescentWindow::Build: return TEXT("build"); case EDescentWindow::Pause: return TEXT("pause"); case EDescentWindow::Title: return TEXT("title"); case EDescentWindow::EndCard: return TEXT("end"); default: return NAME_None; } } FString WindowTitle(EDescentWindow W) { switch (W) { case EDescentWindow::Pda: return TEXT("PDA"); case EDescentWindow::Fabricator: return TEXT("FABRICATOR"); case EDescentWindow::Refinery: return TEXT("REFINERY"); case EDescentWindow::Packer: return TEXT("ORE PACKER"); case EDescentWindow::Storage: return TEXT("STORAGE"); case EDescentWindow::Terminal: return TEXT("TETHYS TERMINAL"); case EDescentWindow::Vat: return TEXT("NUTRIENT VAT"); case EDescentWindow::Build: return TEXT("BUILD"); case EDescentWindow::Pause: return TEXT("PAUSED"); default: return FString(); } } FVector2f WindowDesignSize(EDescentWindow W) { switch (W) { case EDescentWindow::Pda: return FVector2f(980.f, 640.f); case EDescentWindow::Fabricator: return FVector2f(980.f, 620.f); case EDescentWindow::Terminal: return FVector2f(900.f, 580.f); case EDescentWindow::Storage: return FVector2f(820.f, 560.f); case EDescentWindow::Refinery: return FVector2f(820.f, 540.f); case EDescentWindow::Packer: return FVector2f(820.f, 540.f); case EDescentWindow::Build: return FVector2f(780.f, 580.f); case EDescentWindow::Vat: return FVector2f(740.f, 520.f); case EDescentWindow::Pause: return FVector2f(820.f, 540.f); case EDescentWindow::Title: return FVector2f(700.f, 440.f); case EDescentWindow::EndCard: return FVector2f(680.f, 460.f); default: return FVector2f(UiTune::WindowMaxWidthPx, UiTune::WindowMaxHeightPx); } } FWindowFit FitWindow(const FVector2f& Design, const FVector2f& Layout) { FWindowFit F; const float M = UiTune::WindowScreenMarginPx; const float Sx = (Layout.X - 2.f * M) / FMath::Max(1.f, Design.X); const float Sy = (Layout.Y - 2.f * M) / FMath::Max(1.f, Design.Y); F.Scale = FMath::Clamp(FMath::Min(1.f, FMath::Min(Sx, Sy)), 0.05f, 1.f); F.Size = Design * F.Scale; F.Pos = (Layout - F.Size) * 0.5f; F.bFits = F.Pos.X >= -0.01f && F.Pos.Y >= -0.01f && F.Pos.X + F.Size.X <= Layout.X + 0.01f && F.Pos.Y + F.Size.Y <= Layout.Y + 0.01f; return F; } bool KeyClosesWindow(EDescentWindow W, const FKey& Key) { if (W == EDescentWindow::None || W == EDescentWindow::Title) { return false; } if (Key == EKeys::Escape) { return true; } if (W == EDescentWindow::Pda) { return Key == EKeys::Tab || Key == EKeys::M; } if (W == EDescentWindow::Pause || W == EDescentWindow::EndCard) { return false; } return Key == EKeys::E; } void GetWindowTabs(const UDescentUiSubsystem& Ui, EDescentWindow W, TArray& OutIds, TArray& OutLabels) { OutIds.Reset(); OutLabels.Reset(); if (W == EDescentWindow::Pda) { OutIds = { TEXT("inventory"), TEXT("map"), TEXT("logs"), TEXT("status") }; OutLabels = { TEXT("INVENTORY"), TEXT("SURVEY MAP"), TEXT("LOGS"), TEXT("STATUS") }; } else if (W == EDescentWindow::Fabricator) { for (const FDescentFabricatorCategory& C : Ui.Models().Fabricator.Categories) { OutIds.Add(C.Id); OutLabels.Add(C.Label); } } } } // --------------------------------------------------------------------------------------------------------- // SDescentWindowHost // --------------------------------------------------------------------------------------------------------- void SDescentWindowHost::Construct(const FArguments& InArgs) { Ui = InArgs._Ui; SetVisibility(EVisibility::Collapsed); ChildSlot [ SNew(SOverlay) + SOverlay::Slot() [ SNew(SImage) .Image(FCoreStyle::Get().GetBrush("WhiteBrush")) .ColorAndOpacity_Lambda([this]() { const UDescentUiSubsystem* U = Ui.Get(); if (U && U->GetWindow() == EDescentWindow::Title) { return Neu::Bg(); } return FLinearColor(0.f, 0.f, 0.f, UiTune::WindowScrimAlpha); }) ] + SOverlay::Slot().HAlign(HAlign_Center).VAlign(VAlign_Center) [ SAssignNew(Scaler, SDPIScaler) .DPIScale(this, &SDescentWindowHost::GetFitScale) [ SAssignNew(FrameBox, SBox) ] ] ]; } void SDescentWindowHost::Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime) { SCompoundWidget::Tick(AllottedGeometry, InCurrentTime, InDeltaTime); LayoutSize = AllottedGeometry.GetLocalSize(); const UDescentUiSubsystem* U = Ui.Get(); if (U && U->GetWindow() != EDescentWindow::None) { Fit = Descent::Ui::FitWindow(Descent::Ui::WindowDesignSize(U->GetWindow()), LayoutSize); if (U->GetRevision() != BuiltRevision || U->GetWindow() != BuiltWindow || U->GetTab() != BuiltTab) { Rebuild(); } } } void SDescentWindowHost::Rebuild() { UDescentUiSubsystem* U = Ui.Get(); if (!U || !FrameBox.IsValid()) { return; } const EDescentWindow W = U->GetWindow(); BuiltWindow = W; BuiltTab = U->GetTab(); BuiltRevision = U->GetRevision(); if (W == EDescentWindow::None) { SetVisibility(EVisibility::Collapsed); FrameBox->SetContent(SNullWidget::NullWidget); return; } SetVisibility(EVisibility::Visible); const FVector2f Design = Descent::Ui::WindowDesignSize(W); Fit = Descent::Ui::FitWindow(Design, LayoutSize); FrameBox->SetWidthOverride(Design.X); FrameBox->SetHeightOverride(Design.Y); TWeakObjectPtr Weak = Ui; const TSharedRef Page = Descent::Ui::BuildPage(*U, W, BuiltTab); if (W == EDescentWindow::Title || W == EDescentWindow::EndCard) { FrameBox->SetContent(SNew(SNeuPanel).Radius(24.f).Padding(FMargin(28.f))[ Page ]); return; } TArray Ids; TArray Labels; Descent::Ui::GetWindowTabs(*U, W, Ids, Labels); TArray Tabs; for (int32 I = 0; I < Ids.Num(); ++I) { Tabs.Add({ Ids[I], Labels[I] }); } FrameBox->SetContent( SNew(SNeuWindow) .Title(Descent::Ui::WindowTitle(W)) .Tabs(Tabs) .ActiveTab_Lambda([Weak]() { const UDescentUiSubsystem* S = Weak.Get(); return S ? S->GetTab() : NAME_None; }) .OnTab([Weak](FName T) { if (UDescentUiSubsystem* S = Weak.Get()) { S->SetTab(T); } }) .OnClose([Weak]() { if (UDescentUiSubsystem* S = Weak.Get()) { S->Close(); } }) .DesiredWidth(Design.X) .DesiredHeight(Design.Y) .TabMinWidth(W == EDescentWindow::Pda ? 130.f : 96.f) [ Page ]); } FReply SDescentWindowHost::OnKeyDown(const FGeometry&, const FKeyEvent& InKeyEvent) { UDescentUiSubsystem* U = Ui.Get(); if (!U || !U->IsWindowOpen()) { return FReply::Unhandled(); } const FKey Key = InKeyEvent.GetKey(); const EDescentWindow W = U->GetWindow(); if (Descent::Ui::KeyClosesWindow(W, Key)) { U->Close(); return FReply::Handled(); } if (W == EDescentWindow::Pda) { static const FKey Digits[9] = { EKeys::One, EKeys::Two, EKeys::Three, EKeys::Four, EKeys::Five, EKeys::Six, EKeys::Seven, EKeys::Eight, EKeys::Nine }; for (int32 D = 0; D < 9; ++D) { if (Key == Digits[D] && U->Models().Pda.OnAssignHotbar) { U->Models().Pda.OnAssignHotbar(D + 1); return FReply::Handled(); } } } if (Key == EKeys::Left || Key == EKeys::Right) { TArray Ids; TArray Labels; Descent::Ui::GetWindowTabs(*U, W, Ids, Labels); const int32 Index = Ids.IndexOfByKey(U->GetTab()); if (Index != INDEX_NONE && Ids.Num() > 1) { const int32 Next = (Index + (Key == EKeys::Right ? 1 : Ids.Num() - 1)) % Ids.Num(); U->SetTab(Ids[Next]); return FReply::Handled(); } } return FReply::Unhandled(); }