#pragma once #include "CoreMinimal.h" #include "Core/DescentHandle.h" #include "Core/DescentWorldSubsystem.h" #include "UI/DescentHudLayout.h" #include "UI/DescentHudModel.h" #include "UI/ViewModels/BuildMenuViewModel.h" #include "UI/ViewModels/CargoViewModel.h" #include "UI/ViewModels/DashViewModel.h" #include "UI/ViewModels/EndCardViewModel.h" #include "UI/ViewModels/FabricatorViewModel.h" #include "UI/ViewModels/LogsViewModel.h" #include "UI/ViewModels/MapViewModel.h" #include "UI/ViewModels/PackerViewModel.h" #include "UI/ViewModels/PauseViewModel.h" #include "UI/ViewModels/PdaViewModel.h" #include "UI/ViewModels/RefineryViewModel.h" #include "UI/ViewModels/StatusViewModel.h" #include "UI/ViewModels/StorageViewModel.h" #include "UI/ViewModels/TerminalViewModel.h" #include "UI/ViewModels/TitleViewModel.h" #include "UI/ViewModels/VatViewModel.h" #include "DescentUiSubsystem.generated.h" class SDescentHUD; class SDescentUiLayer; class SDescentWindowHost; /** The windows of DESIGN_UE.md 8.5. The PDA has four tabs; the fabricator has one tab per recipe category. */ enum class EDescentWindow : uint8 { None, Pda, Fabricator, Refinery, Packer, Storage, Terminal, Vat, Build, Pause, Title, EndCard }; /** Every view-model the windows draw from. A game system fills its own struct and calls NotifyChanged. */ struct FDescentUiModels { FDescentPdaViewModel Pda; FDescentMapViewModel Map; FDescentLogsViewModel Logs; FDescentStatusViewModel Status; FDescentFabricatorViewModel Fabricator; FDescentRefineryViewModel Refinery; FDescentPackerViewModel Packer; FDescentStorageViewModel Storage; FDescentTerminalViewModel Terminal; FDescentVatViewModel Vat; FDescentBuildMenuViewModel Build; FDescentPauseViewModel Pause; FDescentTitleViewModel Title; FDescentEndCardViewModel EndCard; FTenderDashModel Dash; FCargoDisplayModel Cargo; }; namespace Descent::Ui { /** "pda", "map", "fabricator"... to a window and tab; false if the name is unknown. "map", "logs", "status", "inventory" open the PDA on that tab. */ THEDESCENT_API bool ParseWindowName(const FString& Name, EDescentWindow& OutWindow, FName& OutTab); THEDESCENT_API FName WindowName(EDescentWindow Window); THEDESCENT_API FString WindowTitle(EDescentWindow Window); /** The nominal frame size of a window (UI units). The frame is scaled down uniformly to fit a smaller viewport. */ THEDESCENT_API FVector2f WindowDesignSize(EDescentWindow Window); /** How a window of the design size fits a layout of the given size: the uniform scale (never above 1) and the resulting size. */ struct FWindowFit { float Scale = 1.f; FVector2f Size = FVector2f::ZeroVector; FVector2f Pos = FVector2f::ZeroVector; bool bFits = true; }; THEDESCENT_API FWindowFit FitWindow(const FVector2f& Design, const FVector2f& Layout); } /** * The UI of the world: owns the view-models, creates the HUD and the window host in the viewport (from the boot step * `CreateUi`), and opens, closes and tabs the windows (DESIGN_UE.md 8.5). * * auto* Ui = UDescentUiSubsystem::Get(World); * Ui->Models().Fabricator = ...; Ui->NotifyChanged(); // a station fills its view-model * Ui->Open(EDescentWindow::Fabricator); // E on the station * if (Ui->IsWindowOpen()) { ... ignore look and move ... } // the player controller asks * * Opening a window shows the mouse cursor and puts the player controller (whatever APlayerController exists) in UI-only * input mode; closing restores game-only input. Opening the pause window also pauses the sim (late steps and the HUD keep * running); closing it resumes. Esc, Tab, M and E close an open window (Esc resumes from the pause window). */ UCLASS() class THEDESCENT_API UDescentUiSubsystem : public UDescentWorldSubsystem { GENERATED_BODY() public: static UDescentUiSubsystem* Get(const UWorld* World); virtual void Initialize(FSubsystemCollectionBase& Collection) override; virtual void Deinitialize() override; // ---- widgets ---- /** Creates the HUD and the window host and adds them to the game viewport. Safe to call twice. No-op without a viewport. */ void CreateWidgets(); void DestroyWidgets(); bool HasWidgets() const { return HudWidget.IsValid(); } // ---- windows ---- /** Opens a window (closing any other); Tab NAME_None = the window's first tab. Returns false if the window is unknown. */ bool Open(EDescentWindow Window, FName Tab = NAME_None); /** "pda", "map", "fabricator" ... as typed in the console. */ bool OpenByName(const FString& Name); void Close(); bool SetTab(FName Tab); bool IsWindowOpen() const { return Window != EDescentWindow::None; } /** True while the pause window is up: the controller should ignore game input. */ bool IsPaused() const { return Window == EDescentWindow::Pause; } EDescentWindow GetWindow() const { return Window; } FName GetTab() const { return Tab; } TArray GetTabs() const; FString GetTabLabel(FName TabId) const; /** Tells the UI a view-model changed, so an open window rebuilds its page. */ void NotifyChanged(); int32 GetRevision() const { return Revision; } FDescentUiModels& Models() { return UiModels; } const FDescentUiModels& Models() const { return UiModels; } DECLARE_MULTICAST_DELEGATE_TwoParams(FOnWindowChanged, EDescentWindow /*Window*/, FName /*Tab*/); FOnWindowChanged OnWindowChanged; // ---- the HUD model that is drawn ---- /** The core model, or the frozen demo copy while the demo runs. */ const FDescentHudModel* GetDisplayedHudModel() const; /** The frozen demo copy the screen draws while the demo runs (scenarios change it to show other modes). */ FDescentHudModel& DemoHudModel() { return DemoHud; } /** Demo (Descent.UI.Demo 1): fills every view-model and the HUD model with believable data; 0 clears them. */ void SetDemo(bool bOn); bool IsDemo() const { return bDemo; } // ---- the Bloom slider (pause window) ---- /** The console variable the slider is bound to (default descent.Look.Bloom, created by the look track). */ void SetBloomVariableName(const FString& Name) { BloomVariable = Name; } const FString& GetBloomVariableName() const { return BloomVariable; } /** True when that variable exists at this moment. */ bool IsBloomAvailable() const; float GetBloomValue() const; float GetBloomMax() const; void SetBloomValue(float Value); // ---- other top-level widgets that want to be listed in `ui.widgets` (dashboards shown on screen), in viewport pixels ---- void SetExtraWidget(FName Name, const FDescentHudRect& Rect) { ExtraWidgets.Add(Name, Rect); } void ClearExtraWidgets() { ExtraWidgets.Reset(); } const TMap& GetExtraWidgets() const { return ExtraWidgets; } // ---- sizes, for the state dump ---- FVector2f GetLayoutSize() const; float GetUiScale() const; TSharedPtr GetHud() const { return HudWidget; } TSharedPtr GetWindowHost() const { return HostWidget; } private: void ApplyInputMode(bool bWindowOpen); void FillDemoModels(); FDescentUiModels UiModels; EDescentWindow Window = EDescentWindow::None; FName Tab; int32 Revision = 0; bool bWePausedSim = false; bool bDemo = false; FDescentHudModel DemoHud; FString BloomVariable; TMap ExtraWidgets; TSharedPtr HudWidget; TSharedPtr HudLayer; TSharedPtr HostWidget; TSharedPtr HostLayer; };