Add 3-way UI mode switch: Legacy / NewSlate / UMG
Introduce a switchable UI stack so the original Slate widget stays available as a fallback while the new CodeDesign-inspired redesign and a future UMG Widget Blueprint implementation coexist. - PS_Editor_MainWidget_Legacy: pre-refactor Slate widget, restored from HEAD - PS_Editor_MainWidget: refactored floating-HUD Slate widget with style asset, 9-slice panels/buttons, rounded brushes, icon pack, Play/Pause/Stop icons, spec-aligned palette and radii, File dropdown, outliner per-item icons, command palette - PS_Editor_MainWidget_UMG: abstract base class exposing a Blueprint-callable API (SetTransformMode, SaveScenario, Simulate, Selection...) and delegates (OnToolChanged, OnSelectionChanged...) so a WBP deriving from it can drive the editor end-to-end - APS_Editor_HUD: UIMode enum + UMGWidgetClass soft ref, picks the right widget at BeginPlay; separate typed accessors for each flavor Adds UI style system (UPS_Editor_UIStyleAsset DataAsset + FPS_Editor_UIStyle helper) with runtime PNG loading for icons and 9-slice brushes from Content/UI/icons/ and Content/UI/unreal_9slice/. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -18,6 +18,7 @@
|
||||
#include "PS_Editor_ChildMovable.h"
|
||||
#include "PS_Editor_HUD.h"
|
||||
#include "PS_Editor_MainWidget.h"
|
||||
#include "PS_Editor_MainWidget_Legacy.h"
|
||||
#include "Components/SplineComponent.h"
|
||||
#include "DrawDebugHelpers.h"
|
||||
#include "PS_Editor_ConfirmDialog.h"
|
||||
@@ -1031,13 +1032,20 @@ void APS_Editor_Pawn::HandleDelete(const FInputActionValue& Value)
|
||||
// 1st priority: if a timeline key is selected in the HUD, delete it and stop.
|
||||
if (APS_Editor_HUD* EditorHUD = Cast<APS_Editor_HUD>(EditorPC->GetHUD()))
|
||||
{
|
||||
if (UPS_Editor_MainWidget* MW = EditorHUD->GetMainWidget())
|
||||
if (UPS_Editor_MainWidget* MW = EditorHUD->GetMainWidget_NewSlate())
|
||||
{
|
||||
if (MW->TryDeleteSelectedTimelineKey())
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (UPS_Editor_MainWidget_Legacy* MWL = EditorHUD->GetMainWidget_Legacy())
|
||||
{
|
||||
if (MWL->TryDeleteSelectedTimelineKey())
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// In SplineEditing mode with a point selected: delete that point
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include "PS_Editor_SceneLoader.h"
|
||||
#include "PS_Editor_HUD.h"
|
||||
#include "PS_Editor_MainWidget.h"
|
||||
#include "PS_Editor_MainWidget_Legacy.h"
|
||||
#include "Camera/PlayerCameraManager.h"
|
||||
#include "PS_Editor_GameInstanceSubsystem.h"
|
||||
#include "GameFramework/GameModeBase.h"
|
||||
@@ -429,10 +430,14 @@ void APS_Editor_PlayerController::ShowLoadingScreen(bool bShow)
|
||||
{
|
||||
if (APS_Editor_HUD* EditorHUD = Cast<APS_Editor_HUD>(GetHUD()))
|
||||
{
|
||||
if (UPS_Editor_MainWidget* Widget = EditorHUD->GetMainWidget())
|
||||
if (UPS_Editor_MainWidget* Widget = EditorHUD->GetMainWidget_NewSlate())
|
||||
{
|
||||
Widget->ShowLoadingScreen(bShow);
|
||||
}
|
||||
else if (UPS_Editor_MainWidget_Legacy* WidgetLegacy = EditorHUD->GetMainWidget_Legacy())
|
||||
{
|
||||
WidgetLegacy->ShowLoadingScreen(bShow);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ public class PS_Editor : ModuleRules
|
||||
PublicIncludePaths.Add(Path.Combine(SourceDir, "GameMode"));
|
||||
PublicIncludePaths.Add(Path.Combine(SourceDir, "UI"));
|
||||
PublicIncludePaths.Add(Path.Combine(SourceDir, "UI", "Widgets"));
|
||||
PublicIncludePaths.Add(Path.Combine(SourceDir, "UI", "Style"));
|
||||
PublicIncludePaths.Add(Path.Combine(SourceDir, "Selection"));
|
||||
PublicIncludePaths.Add(Path.Combine(SourceDir, "Gizmo"));
|
||||
PublicIncludePaths.Add(Path.Combine(SourceDir, "Spawn"));
|
||||
@@ -38,7 +39,9 @@ public class PS_Editor : ModuleRules
|
||||
PrivateDependencyModuleNames.AddRange(new string[]
|
||||
{
|
||||
"Slate",
|
||||
"SlateCore"
|
||||
"SlateCore",
|
||||
"ImageWrapper",
|
||||
"Projects"
|
||||
});
|
||||
|
||||
// Editor-only: CaptureAllThumbnails uses ThumbnailTools, material auto-generation uses AssetRegistry
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#include "PS_Editor_HUD.h"
|
||||
#include "PS_Editor_MainWidget.h"
|
||||
#include "PS_Editor_MainWidget_Legacy.h"
|
||||
#include "PS_Editor_MainWidget_UMG.h"
|
||||
#include "PS_Editor_PlayerController.h"
|
||||
#include "PS_Editor_SelectionManager.h"
|
||||
#include "PS_Editor_SpawnManager.h"
|
||||
@@ -15,16 +17,84 @@ void APS_Editor_HUD::BeginPlay()
|
||||
APlayerController* PC = GetOwningPlayerController();
|
||||
if (!PC) return;
|
||||
|
||||
MainWidget = CreateWidget<UPS_Editor_MainWidget>(PC, UPS_Editor_MainWidget::StaticClass());
|
||||
if (!MainWidget) return;
|
||||
APS_Editor_PlayerController* EditorPC = Cast<APS_Editor_PlayerController>(PC);
|
||||
|
||||
if (APS_Editor_PlayerController* EditorPC = Cast<APS_Editor_PlayerController>(PC))
|
||||
switch (UIMode)
|
||||
{
|
||||
MainWidget->SetSelectionManager(EditorPC->GetSelectionManager());
|
||||
MainWidget->SetSpawnManager(EditorPC->GetSpawnManager());
|
||||
MainWidget->SetSceneSerializer(EditorPC->GetSceneSerializer());
|
||||
case EPS_Editor_UIMode::Legacy:
|
||||
{
|
||||
UPS_Editor_MainWidget_Legacy* W = CreateWidget<UPS_Editor_MainWidget_Legacy>(PC, UPS_Editor_MainWidget_Legacy::StaticClass());
|
||||
if (W && EditorPC)
|
||||
{
|
||||
W->SetSelectionManager(EditorPC->GetSelectionManager());
|
||||
W->SetSpawnManager(EditorPC->GetSpawnManager());
|
||||
W->SetSceneSerializer(EditorPC->GetSceneSerializer());
|
||||
}
|
||||
MainWidget = W;
|
||||
break;
|
||||
}
|
||||
|
||||
MainWidget->AddToViewport(0);
|
||||
case EPS_Editor_UIMode::NewSlate:
|
||||
{
|
||||
UPS_Editor_MainWidget* W = CreateWidget<UPS_Editor_MainWidget>(PC, UPS_Editor_MainWidget::StaticClass());
|
||||
if (W && EditorPC)
|
||||
{
|
||||
W->SetSelectionManager(EditorPC->GetSelectionManager());
|
||||
W->SetSpawnManager(EditorPC->GetSpawnManager());
|
||||
W->SetSceneSerializer(EditorPC->GetSceneSerializer());
|
||||
}
|
||||
MainWidget = W;
|
||||
break;
|
||||
}
|
||||
|
||||
case EPS_Editor_UIMode::UMG:
|
||||
{
|
||||
UClass* WidgetClass = UMGWidgetClass.LoadSynchronous();
|
||||
if (!WidgetClass)
|
||||
{
|
||||
UE_LOG(LogTemp, Error, TEXT("PS_Editor_HUD: UIMode=UMG but UMGWidgetClass is not set. Falling back to NewSlate."));
|
||||
UPS_Editor_MainWidget* W = CreateWidget<UPS_Editor_MainWidget>(PC, UPS_Editor_MainWidget::StaticClass());
|
||||
if (W && EditorPC)
|
||||
{
|
||||
W->SetSelectionManager(EditorPC->GetSelectionManager());
|
||||
W->SetSpawnManager(EditorPC->GetSpawnManager());
|
||||
W->SetSceneSerializer(EditorPC->GetSceneSerializer());
|
||||
}
|
||||
MainWidget = W;
|
||||
}
|
||||
else
|
||||
{
|
||||
UPS_Editor_MainWidget_UMG* W = CreateWidget<UPS_Editor_MainWidget_UMG>(PC, WidgetClass);
|
||||
if (W && EditorPC)
|
||||
{
|
||||
W->SetSelectionManager(EditorPC->GetSelectionManager());
|
||||
W->SetSpawnManager(EditorPC->GetSpawnManager());
|
||||
W->SetSceneSerializer(EditorPC->GetSceneSerializer());
|
||||
}
|
||||
MainWidget = W;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (MainWidget)
|
||||
{
|
||||
MainWidget->AddToViewport(0);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
UPS_Editor_MainWidget* APS_Editor_HUD::GetMainWidget_NewSlate() const
|
||||
{
|
||||
return Cast<UPS_Editor_MainWidget>(MainWidget);
|
||||
}
|
||||
|
||||
UPS_Editor_MainWidget_Legacy* APS_Editor_HUD::GetMainWidget_Legacy() const
|
||||
{
|
||||
return Cast<UPS_Editor_MainWidget_Legacy>(MainWidget);
|
||||
}
|
||||
|
||||
UPS_Editor_MainWidget_UMG* APS_Editor_HUD::GetMainWidget_UMG() const
|
||||
{
|
||||
return Cast<UPS_Editor_MainWidget_UMG>(MainWidget);
|
||||
}
|
||||
|
||||
@@ -4,11 +4,29 @@
|
||||
#include "GameFramework/HUD.h"
|
||||
#include "PS_Editor_HUD.generated.h"
|
||||
|
||||
class UUserWidget;
|
||||
class UPS_Editor_MainWidget;
|
||||
class UPS_Editor_MainWidget_Legacy;
|
||||
class UPS_Editor_MainWidget_UMG;
|
||||
|
||||
/** UI implementation flavor. */
|
||||
UENUM(BlueprintType)
|
||||
enum class EPS_Editor_UIMode : uint8
|
||||
{
|
||||
/** Original Slate-based widget (pre-redesign). Stable fallback. */
|
||||
Legacy UMETA(DisplayName = "Legacy (Slate)"),
|
||||
|
||||
/** Refactored floating-HUD Slate widget (CodeDesign redesign, work-in-progress). */
|
||||
NewSlate UMETA(DisplayName = "New (Slate)"),
|
||||
|
||||
/** User-authored UMG Widget Blueprint deriving from UPS_Editor_MainWidget_UMG.
|
||||
* Requires UMGWidgetClass to be assigned. */
|
||||
UMG UMETA(DisplayName = "UMG Widget Blueprint"),
|
||||
};
|
||||
|
||||
/**
|
||||
* HUD for the PS_Editor runtime editing mode.
|
||||
* Creates and manages the main editor widget.
|
||||
* Creates and manages the main editor widget. The flavor is selected via UIMode.
|
||||
*/
|
||||
UCLASS()
|
||||
class PS_EDITOR_API APS_Editor_HUD : public AHUD
|
||||
@@ -16,14 +34,32 @@ class PS_EDITOR_API APS_Editor_HUD : public AHUD
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/** Returns the main editor widget instance. */
|
||||
/** Which UI implementation to instantiate. Set via Details panel on the HUD Blueprint. */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "ASTERION|PS_Editor|UI")
|
||||
EPS_Editor_UIMode UIMode = EPS_Editor_UIMode::NewSlate;
|
||||
|
||||
/** UMG Widget Blueprint class to spawn when UIMode == UMG.
|
||||
* Must derive from UPS_Editor_MainWidget_UMG. */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "ASTERION|PS_Editor|UI", meta = (EditCondition = "UIMode == EPS_Editor_UIMode::UMG"))
|
||||
TSoftClassPtr<UPS_Editor_MainWidget_UMG> UMGWidgetClass;
|
||||
|
||||
/** Returns the active main editor widget (cast as needed). */
|
||||
UFUNCTION(BlueprintCallable, Category = "ASTERION|PS_Editor|UI")
|
||||
UPS_Editor_MainWidget* GetMainWidget() const { return MainWidget; }
|
||||
UUserWidget* GetMainWidget() const { return MainWidget; }
|
||||
|
||||
/** Backward-compat accessor for the refactored Slate widget (returns nullptr in Legacy/UMG modes). */
|
||||
UPS_Editor_MainWidget* GetMainWidget_NewSlate() const;
|
||||
|
||||
/** Accessor for the legacy Slate widget (returns nullptr in NewSlate/UMG modes). */
|
||||
UPS_Editor_MainWidget_Legacy* GetMainWidget_Legacy() const;
|
||||
|
||||
/** Accessor for the UMG widget (returns nullptr in Legacy/NewSlate modes). */
|
||||
UPS_Editor_MainWidget_UMG* GetMainWidget_UMG() const;
|
||||
|
||||
protected:
|
||||
virtual void BeginPlay() override;
|
||||
|
||||
private:
|
||||
UPROPERTY()
|
||||
TObjectPtr<UPS_Editor_MainWidget> MainWidget;
|
||||
TObjectPtr<UUserWidget> MainWidget;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,558 @@
|
||||
#include "PS_Editor_UIStyle.h"
|
||||
|
||||
#include "PS_Editor_UIStyleAsset.h"
|
||||
#include "Styling/CoreStyle.h"
|
||||
#include "Styling/SlateBrush.h"
|
||||
#include "Styling/SlateTypes.h"
|
||||
#include "Widgets/Images/SImage.h"
|
||||
#include "Widgets/Layout/SBorder.h"
|
||||
#include "Widgets/Layout/SBox.h"
|
||||
#include "Widgets/SBoxPanel.h"
|
||||
#include "Widgets/Text/STextBlock.h"
|
||||
#include "Engine/Texture2D.h"
|
||||
#include "IImageWrapper.h"
|
||||
#include "IImageWrapperModule.h"
|
||||
#include "Modules/ModuleManager.h"
|
||||
#include "Misc/FileHelper.h"
|
||||
#include "Misc/Paths.h"
|
||||
#include "HAL/FileManager.h"
|
||||
#include "Interfaces/IPluginManager.h"
|
||||
#include "TextureResource.h"
|
||||
#include "PixelFormat.h"
|
||||
|
||||
namespace FPS_Editor_UIStyle
|
||||
{
|
||||
// Hardcoded fallbacks matching the DataAsset defaults.
|
||||
namespace Defaults
|
||||
{
|
||||
static const FLinearColor BgPrimary = FLinearColor(0.027f, 0.039f, 0.059f, 1.0f);
|
||||
static const FLinearColor BgSecondary = FLinearColor(0.043f, 0.067f, 0.094f, 1.0f);
|
||||
static const FLinearColor GlassStrong = FLinearColor(0.0784f, 0.1216f, 0.1804f, 0.88f);
|
||||
static const FLinearColor GlassSoft = FLinearColor(0.0549f, 0.0863f, 0.1333f, 0.72f);
|
||||
static const FLinearColor GlassBorder = FLinearColor(1.0f, 1.0f, 1.0f, 0.10f);
|
||||
static const FLinearColor GlassBorderStrong = FLinearColor(1.0f, 1.0f, 1.0f, 0.18f);
|
||||
static const FLinearColor TextPrimary = FLinearColor(0.902f, 0.925f, 0.953f, 1.0f);
|
||||
static const FLinearColor TextMuted = FLinearColor(0.722f, 0.769f, 0.824f, 1.0f);
|
||||
static const FLinearColor TextDim = FLinearColor(0.502f, 0.569f, 0.647f, 1.0f);
|
||||
static const FLinearColor TextDisabled = FLinearColor(0.353f, 0.416f, 0.494f, 1.0f);
|
||||
static const FLinearColor Accent = FLinearColor(1.0f, 0.416f, 0.239f, 1.0f);
|
||||
static const FLinearColor AccentSoft = FLinearColor(1.0f, 0.416f, 0.239f, 0.14f);
|
||||
static const FLinearColor AccentRing = FLinearColor(1.0f, 0.416f, 0.239f, 0.35f);
|
||||
static const FLinearColor Ok = FLinearColor(0.243f, 0.812f, 0.557f, 1.0f);
|
||||
static const FLinearColor Warn = FLinearColor(0.961f, 0.725f, 0.267f, 1.0f);
|
||||
static const FLinearColor Err = FLinearColor(1.0f, 0.353f, 0.431f, 1.0f);
|
||||
static const FLinearColor Info = FLinearColor(0.290f, 0.659f, 1.0f, 1.0f);
|
||||
static const FLinearColor AxisX = FLinearColor(1.0f, 0.302f, 0.369f, 1.0f);
|
||||
static const FLinearColor AxisY = FLinearColor(0.290f, 0.839f, 0.427f, 1.0f);
|
||||
static const FLinearColor AxisZ = FLinearColor(0.290f, 0.659f, 1.0f, 1.0f);
|
||||
|
||||
static constexpr int32 FontSizeXSmall = 9;
|
||||
static constexpr int32 FontSizeSmall = 10;
|
||||
static constexpr int32 FontSizeBody = 11;
|
||||
static constexpr int32 FontSizeHeading = 13;
|
||||
static constexpr int32 FontSizeLarge = 16;
|
||||
|
||||
static constexpr float RadiusXS = 3.0f;
|
||||
static constexpr float RadiusS = 5.0f;
|
||||
static constexpr float RadiusM = 7.0f;
|
||||
static constexpr float RadiusL = 9.0f;
|
||||
static constexpr float PadPanel = 10.0f;
|
||||
static constexpr float PadRow = 6.0f;
|
||||
}
|
||||
|
||||
#define PSUI_COLOR_ACCESSOR(Name) \
|
||||
FLinearColor Name(const UPS_Editor_UIStyleAsset* S) { return S ? S->Name : Defaults::Name; }
|
||||
|
||||
PSUI_COLOR_ACCESSOR(BgPrimary)
|
||||
PSUI_COLOR_ACCESSOR(BgSecondary)
|
||||
PSUI_COLOR_ACCESSOR(GlassStrong)
|
||||
PSUI_COLOR_ACCESSOR(GlassSoft)
|
||||
PSUI_COLOR_ACCESSOR(GlassBorder)
|
||||
PSUI_COLOR_ACCESSOR(GlassBorderStrong)
|
||||
PSUI_COLOR_ACCESSOR(TextPrimary)
|
||||
PSUI_COLOR_ACCESSOR(TextMuted)
|
||||
PSUI_COLOR_ACCESSOR(TextDim)
|
||||
PSUI_COLOR_ACCESSOR(TextDisabled)
|
||||
PSUI_COLOR_ACCESSOR(Accent)
|
||||
PSUI_COLOR_ACCESSOR(AccentSoft)
|
||||
PSUI_COLOR_ACCESSOR(AccentRing)
|
||||
PSUI_COLOR_ACCESSOR(Ok)
|
||||
PSUI_COLOR_ACCESSOR(Warn)
|
||||
PSUI_COLOR_ACCESSOR(Err)
|
||||
PSUI_COLOR_ACCESSOR(Info)
|
||||
PSUI_COLOR_ACCESSOR(AxisX)
|
||||
PSUI_COLOR_ACCESSOR(AxisY)
|
||||
PSUI_COLOR_ACCESSOR(AxisZ)
|
||||
|
||||
#undef PSUI_COLOR_ACCESSOR
|
||||
|
||||
FSlateFontInfo BodyFont(const UPS_Editor_UIStyleAsset* S)
|
||||
{
|
||||
return FCoreStyle::GetDefaultFontStyle("Regular", S ? S->FontSizeBody : Defaults::FontSizeBody);
|
||||
}
|
||||
FSlateFontInfo BodyFontBold(const UPS_Editor_UIStyleAsset* S)
|
||||
{
|
||||
return FCoreStyle::GetDefaultFontStyle("Bold", S ? S->FontSizeBody : Defaults::FontSizeBody);
|
||||
}
|
||||
FSlateFontInfo SmallFont(const UPS_Editor_UIStyleAsset* S)
|
||||
{
|
||||
return FCoreStyle::GetDefaultFontStyle("Regular", S ? S->FontSizeSmall : Defaults::FontSizeSmall);
|
||||
}
|
||||
FSlateFontInfo HeadingFont(const UPS_Editor_UIStyleAsset* S)
|
||||
{
|
||||
return FCoreStyle::GetDefaultFontStyle("Regular", S ? S->FontSizeHeading : Defaults::FontSizeHeading);
|
||||
}
|
||||
FSlateFontInfo HeadingFontBold(const UPS_Editor_UIStyleAsset* S)
|
||||
{
|
||||
return FCoreStyle::GetDefaultFontStyle("Bold", S ? S->FontSizeHeading : Defaults::FontSizeHeading);
|
||||
}
|
||||
FSlateFontInfo LargeFont(const UPS_Editor_UIStyleAsset* S)
|
||||
{
|
||||
return FCoreStyle::GetDefaultFontStyle("Bold", S ? S->FontSizeLarge : Defaults::FontSizeLarge);
|
||||
}
|
||||
FSlateFontInfo MonoFont(const UPS_Editor_UIStyleAsset* S)
|
||||
{
|
||||
// Slate default fonts do not ship a mono family. Use Regular at the mono size and rely on tabular digits where possible.
|
||||
return FCoreStyle::GetDefaultFontStyle("Mono", S ? S->FontSizeBody : Defaults::FontSizeBody);
|
||||
}
|
||||
|
||||
float RadiusXS(const UPS_Editor_UIStyleAsset* S) { return S ? S->RadiusXS : Defaults::RadiusXS; }
|
||||
float RadiusS(const UPS_Editor_UIStyleAsset* S) { return S ? S->RadiusS : Defaults::RadiusS; }
|
||||
float RadiusM(const UPS_Editor_UIStyleAsset* S) { return S ? S->RadiusM : Defaults::RadiusM; }
|
||||
float RadiusL(const UPS_Editor_UIStyleAsset* S) { return S ? S->RadiusL : Defaults::RadiusL; }
|
||||
|
||||
FMargin PadPanel(const UPS_Editor_UIStyleAsset* S)
|
||||
{
|
||||
const float V = S ? S->PadPanel : Defaults::PadPanel;
|
||||
return FMargin(V);
|
||||
}
|
||||
FMargin PadRow(const UPS_Editor_UIStyleAsset* S)
|
||||
{
|
||||
const float V = S ? S->PadRow : Defaults::PadRow;
|
||||
return FMargin(V);
|
||||
}
|
||||
|
||||
TSharedRef<SWidget> MakeGlassPanel(const UPS_Editor_UIStyleAsset* S, bool bStrong, TSharedRef<SWidget> Content, FMargin Padding)
|
||||
{
|
||||
const FLinearColor Fill = bStrong ? GlassStrong(S) : GlassSoft(S);
|
||||
return SNew(SBorder)
|
||||
.BorderImage(FCoreStyle::Get().GetBrush("WhiteBrush"))
|
||||
.BorderBackgroundColor(Fill)
|
||||
.Padding(Padding)
|
||||
[
|
||||
Content
|
||||
];
|
||||
}
|
||||
|
||||
// Internal widget that owns its brush — ensures the brush outlives the SImage.
|
||||
class SPSEditorIconWidget : public SCompoundWidget
|
||||
{
|
||||
public:
|
||||
SLATE_BEGIN_ARGS(SPSEditorIconWidget)
|
||||
: _Texture(nullptr)
|
||||
, _Size(16.f)
|
||||
, _Color(FLinearColor::White)
|
||||
, _FollowForeground(false)
|
||||
, _Fallback(TEXT("?"))
|
||||
{}
|
||||
SLATE_ARGUMENT(UTexture2D*, Texture)
|
||||
SLATE_ARGUMENT(float, Size)
|
||||
SLATE_ARGUMENT(FLinearColor, Color)
|
||||
SLATE_ARGUMENT(bool, FollowForeground)
|
||||
SLATE_ARGUMENT(const TCHAR*, Fallback)
|
||||
SLATE_END_ARGS()
|
||||
|
||||
void Construct(const FArguments& InArgs)
|
||||
{
|
||||
const float Size = InArgs._Size;
|
||||
const FSlateColor Tint = InArgs._FollowForeground
|
||||
? FSlateColor::UseForeground()
|
||||
: FSlateColor(InArgs._Color);
|
||||
|
||||
if (InArgs._Texture)
|
||||
{
|
||||
IconBrush.SetResourceObject(InArgs._Texture);
|
||||
IconBrush.ImageSize = FVector2D(Size, Size);
|
||||
IconBrush.DrawAs = ESlateBrushDrawType::Image;
|
||||
|
||||
ChildSlot
|
||||
[
|
||||
SNew(SBox)
|
||||
.WidthOverride(Size)
|
||||
.HeightOverride(Size)
|
||||
[
|
||||
SNew(SImage)
|
||||
.Image(&IconBrush)
|
||||
.ColorAndOpacity(Tint)
|
||||
]
|
||||
];
|
||||
}
|
||||
else
|
||||
{
|
||||
const TCHAR* Glyph = InArgs._Fallback ? InArgs._Fallback : TEXT("?");
|
||||
ChildSlot
|
||||
[
|
||||
SNew(SBox)
|
||||
.WidthOverride(Size)
|
||||
.HeightOverride(Size)
|
||||
.HAlign(HAlign_Center)
|
||||
.VAlign(VAlign_Center)
|
||||
[
|
||||
SNew(STextBlock)
|
||||
.Text(FText::FromString(Glyph))
|
||||
.Font(FCoreStyle::GetDefaultFontStyle("Regular", FMath::Max(8, FMath::RoundToInt(Size * 0.72f))))
|
||||
.ColorAndOpacity(Tint)
|
||||
]
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
FSlateBrush IconBrush;
|
||||
};
|
||||
|
||||
TSharedRef<SWidget> MakeIcon(const UPS_Editor_UIStyleAsset* S, EPS_Editor_Icon Icon, float Size, FLinearColor Tint)
|
||||
{
|
||||
FPS_Editor_IconData Data = S ? S->GetIcon(Icon) : FPS_Editor_IconData{};
|
||||
return SNew(SPSEditorIconWidget)
|
||||
.Texture(Data.Texture)
|
||||
.Size(Size)
|
||||
.Color(Tint)
|
||||
.FollowForeground(false)
|
||||
.Fallback(Data.Fallback);
|
||||
}
|
||||
|
||||
TSharedRef<SWidget> MakeIconFollowForeground(const UPS_Editor_UIStyleAsset* S, EPS_Editor_Icon Icon, float Size)
|
||||
{
|
||||
FPS_Editor_IconData Data = S ? S->GetIcon(Icon) : FPS_Editor_IconData{};
|
||||
return SNew(SPSEditorIconWidget)
|
||||
.Texture(Data.Texture)
|
||||
.Size(Size)
|
||||
.FollowForeground(true)
|
||||
.Fallback(Data.Fallback);
|
||||
}
|
||||
|
||||
// ---- Brush / style factories ----
|
||||
|
||||
static FLinearColor ResolveToneFill(const UPS_Editor_UIStyleAsset* S, EPanelTone Tone)
|
||||
{
|
||||
switch (Tone)
|
||||
{
|
||||
case EPanelTone::GlassStrong: return GlassStrong(S);
|
||||
case EPanelTone::GlassSoft: return GlassSoft(S);
|
||||
// Pill uses white tint so callers can recolor via BorderBackgroundColor.
|
||||
case EPanelTone::Pill: return FLinearColor::White;
|
||||
case EPanelTone::FieldFill: return FLinearColor(0.0f, 0.0f, 0.0f, 0.28f);
|
||||
case EPanelTone::AccentFill: return Accent(S);
|
||||
case EPanelTone::AccentSoftFill: return AccentSoft(S);
|
||||
}
|
||||
return GlassStrong(S);
|
||||
}
|
||||
|
||||
void InitPanelBrush(FSlateBrush& Out, const UPS_Editor_UIStyleAsset* S, EPanelTone Tone, float CornerRadius)
|
||||
{
|
||||
Out.DrawAs = ESlateBrushDrawType::RoundedBox;
|
||||
Out.TintColor = FSlateColor(ResolveToneFill(S, Tone));
|
||||
Out.OutlineSettings.CornerRadii = FVector4(CornerRadius, CornerRadius, CornerRadius, CornerRadius);
|
||||
Out.OutlineSettings.RoundingType = ESlateBrushRoundingType::FixedRadius;
|
||||
// Pill / Accent / Field have no outline; panels have a subtle glass border.
|
||||
const bool bWantsBorder = (Tone == EPanelTone::GlassStrong || Tone == EPanelTone::GlassSoft);
|
||||
Out.OutlineSettings.Width = bWantsBorder ? 1.0f : 0.0f;
|
||||
Out.OutlineSettings.Color = FSlateColor(Tone == EPanelTone::AccentFill
|
||||
? AccentRing(S)
|
||||
: GlassBorder(S));
|
||||
Out.Margin = FMargin(0.0f);
|
||||
}
|
||||
|
||||
void InitFlatButtonStyle(FButtonStyle& Out, const UPS_Editor_UIStyleAsset* S, float CornerRadius)
|
||||
{
|
||||
auto Make = [CornerRadius](FLinearColor Fill, FLinearColor Border)
|
||||
{
|
||||
FSlateBrush B;
|
||||
B.DrawAs = ESlateBrushDrawType::RoundedBox;
|
||||
B.TintColor = FSlateColor(Fill);
|
||||
B.OutlineSettings.CornerRadii = FVector4(CornerRadius, CornerRadius, CornerRadius, CornerRadius);
|
||||
B.OutlineSettings.RoundingType = ESlateBrushRoundingType::FixedRadius;
|
||||
B.OutlineSettings.Width = Border.A > 0.f ? 1.f : 0.f;
|
||||
B.OutlineSettings.Color = FSlateColor(Border);
|
||||
return B;
|
||||
};
|
||||
|
||||
const FLinearColor Transparent = FLinearColor(0, 0, 0, 0);
|
||||
const FLinearColor HoverFill = FLinearColor(1, 1, 1, 0.06f);
|
||||
const FLinearColor PressedFill = FLinearColor(1, 1, 1, 0.10f);
|
||||
|
||||
Out.Normal = Make(Transparent, Transparent);
|
||||
Out.Hovered = Make(HoverFill, Transparent);
|
||||
Out.Pressed = Make(PressedFill, Transparent);
|
||||
Out.Disabled = Make(Transparent, Transparent);
|
||||
|
||||
Out.NormalPadding = FMargin(8, 4);
|
||||
Out.PressedPadding = FMargin(8, 4);
|
||||
Out.NormalForeground = FSlateColor(TextPrimary(S));
|
||||
Out.HoveredForeground = FSlateColor(TextPrimary(S));
|
||||
Out.PressedForeground = FSlateColor(TextPrimary(S));
|
||||
Out.DisabledForeground = FSlateColor(TextDisabled(S));
|
||||
}
|
||||
|
||||
void InitAccentButtonStyle(FButtonStyle& Out, const UPS_Editor_UIStyleAsset* S, float CornerRadius)
|
||||
{
|
||||
auto Make = [CornerRadius](FLinearColor Fill)
|
||||
{
|
||||
FSlateBrush B;
|
||||
B.DrawAs = ESlateBrushDrawType::RoundedBox;
|
||||
B.TintColor = FSlateColor(Fill);
|
||||
B.OutlineSettings.CornerRadii = FVector4(CornerRadius, CornerRadius, CornerRadius, CornerRadius);
|
||||
B.OutlineSettings.RoundingType = ESlateBrushRoundingType::FixedRadius;
|
||||
B.OutlineSettings.Width = 0.0f;
|
||||
return B;
|
||||
};
|
||||
|
||||
const FLinearColor AccentC = Accent(S);
|
||||
const FLinearColor AccentHov(FMath::Min(AccentC.R * 1.08f, 1.f), FMath::Min(AccentC.G * 1.08f, 1.f), FMath::Min(AccentC.B * 1.08f, 1.f), AccentC.A);
|
||||
const FLinearColor AccentDim(AccentC.R * 0.88f, AccentC.G * 0.88f, AccentC.B * 0.88f, AccentC.A);
|
||||
|
||||
Out.Normal = Make(AccentC);
|
||||
Out.Hovered = Make(AccentHov);
|
||||
Out.Pressed = Make(AccentDim);
|
||||
Out.Disabled = Make(AccentSoft(S));
|
||||
|
||||
Out.NormalPadding = FMargin(10, 4);
|
||||
Out.PressedPadding = FMargin(10, 4);
|
||||
|
||||
// Dark text on accent (design uses #1a0a00).
|
||||
const FLinearColor DarkOnAccent(0.10f, 0.04f, 0.0f, 1.0f);
|
||||
Out.NormalForeground = FSlateColor(DarkOnAccent);
|
||||
Out.HoveredForeground = FSlateColor(DarkOnAccent);
|
||||
Out.PressedForeground = FSlateColor(DarkOnAccent);
|
||||
Out.DisabledForeground = FSlateColor(TextDisabled(S));
|
||||
}
|
||||
|
||||
void InitTextBoxStyle(FEditableTextBoxStyle& Out, const UPS_Editor_UIStyleAsset* S, float CornerRadius)
|
||||
{
|
||||
auto Make = [CornerRadius](FLinearColor Fill, FLinearColor Border)
|
||||
{
|
||||
FSlateBrush B;
|
||||
B.DrawAs = ESlateBrushDrawType::RoundedBox;
|
||||
B.TintColor = FSlateColor(Fill);
|
||||
B.OutlineSettings.CornerRadii = FVector4(CornerRadius, CornerRadius, CornerRadius, CornerRadius);
|
||||
B.OutlineSettings.RoundingType = ESlateBrushRoundingType::FixedRadius;
|
||||
B.OutlineSettings.Width = Border.A > 0.f ? 1.f : 0.f;
|
||||
B.OutlineSettings.Color = FSlateColor(Border);
|
||||
return B;
|
||||
};
|
||||
|
||||
const FLinearColor Fill = FLinearColor(0, 0, 0, 0.28f);
|
||||
const FLinearColor Border = GlassBorder(S);
|
||||
const FLinearColor Focus = AccentRing(S);
|
||||
|
||||
Out.BackgroundImageNormal = Make(Fill, Border);
|
||||
Out.BackgroundImageHovered = Make(Fill, GlassBorderStrong(S));
|
||||
Out.BackgroundImageFocused = Make(Fill, Focus);
|
||||
Out.BackgroundImageReadOnly = Make(Fill, Border);
|
||||
|
||||
Out.Padding = FMargin(6, 3);
|
||||
Out.ForegroundColor = FSlateColor(TextPrimary(S));
|
||||
Out.TextStyle.ColorAndOpacity = FSlateColor(TextPrimary(S));
|
||||
Out.TextStyle.Font = BodyFont(S);
|
||||
}
|
||||
|
||||
// ---- Runtime PNG loader ----
|
||||
|
||||
static UTexture2D* LoadTextureFromPng(const FString& FilePath)
|
||||
{
|
||||
TArray<uint8> RawFileData;
|
||||
if (!FFileHelper::LoadFileToArray(RawFileData, *FilePath))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
IImageWrapperModule& ImageWrapperModule = FModuleManager::LoadModuleChecked<IImageWrapperModule>(FName("ImageWrapper"));
|
||||
TSharedPtr<IImageWrapper> ImageWrapper = ImageWrapperModule.CreateImageWrapper(EImageFormat::PNG);
|
||||
if (!ImageWrapper.IsValid() || !ImageWrapper->SetCompressed(RawFileData.GetData(), RawFileData.Num()))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
TArray<uint8> UncompressedBGRA;
|
||||
if (!ImageWrapper->GetRaw(ERGBFormat::BGRA, 8, UncompressedBGRA))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const int32 Width = ImageWrapper->GetWidth();
|
||||
const int32 Height = ImageWrapper->GetHeight();
|
||||
if (Width <= 0 || Height <= 0) return nullptr;
|
||||
|
||||
UTexture2D* Texture = UTexture2D::CreateTransient(Width, Height, PF_B8G8R8A8);
|
||||
if (!Texture) return nullptr;
|
||||
|
||||
void* TextureData = Texture->GetPlatformData()->Mips[0].BulkData.Lock(LOCK_READ_WRITE);
|
||||
FMemory::Memcpy(TextureData, UncompressedBGRA.GetData(), UncompressedBGRA.Num());
|
||||
Texture->GetPlatformData()->Mips[0].BulkData.Unlock();
|
||||
|
||||
Texture->SRGB = false;
|
||||
Texture->CompressionSettings = TC_EditorIcon;
|
||||
Texture->Filter = TextureFilter::TF_Trilinear;
|
||||
Texture->UpdateResource();
|
||||
|
||||
return Texture;
|
||||
}
|
||||
|
||||
static FString ResolvePluginIconsDir()
|
||||
{
|
||||
TSharedPtr<IPlugin> Plugin = IPluginManager::Get().FindPlugin(TEXT("PS_Editor"));
|
||||
if (Plugin.IsValid())
|
||||
{
|
||||
return Plugin->GetContentDir() / TEXT("UI/icons");
|
||||
}
|
||||
// Fallback: assume relative to project
|
||||
return FPaths::ProjectContentDir() / TEXT("UI/icons");
|
||||
}
|
||||
|
||||
struct FIconSlot
|
||||
{
|
||||
EPS_Editor_Icon Icon;
|
||||
const TCHAR* FileStem;
|
||||
TObjectPtr<UTexture2D> UPS_Editor_UIStyleAsset::* Member;
|
||||
};
|
||||
|
||||
TSharedPtr<FSlateBrush> Load9SliceBrushFromPluginContent(const FString& RelativePath)
|
||||
{
|
||||
TSharedPtr<IPlugin> Plugin = IPluginManager::Get().FindPlugin(TEXT("PS_Editor"));
|
||||
const FString BaseDir = Plugin.IsValid() ? Plugin->GetContentDir() : (FPaths::ProjectContentDir());
|
||||
const FString Path = BaseDir / TEXT("UI/unreal_9slice") / RelativePath;
|
||||
if (!IFileManager::Get().FileExists(*Path)) return nullptr;
|
||||
|
||||
UTexture2D* Tex = LoadTextureFromPng(Path);
|
||||
if (!Tex) return nullptr;
|
||||
Tex->AddToRoot();
|
||||
|
||||
TSharedRef<FSlateBrush> Brush = MakeShared<FSlateBrush>();
|
||||
Brush->SetResourceObject(Tex);
|
||||
Brush->ImageSize = FVector2D(Tex->GetSizeX(), Tex->GetSizeY());
|
||||
Brush->DrawAs = ESlateBrushDrawType::Box;
|
||||
// PNG is 192x192 with rounded shape inside a 64px corner. We slice tighter than the README's
|
||||
// 0.333 so small widgets (topbar height ~32-40px, buttons ~24-28px) still render all corners.
|
||||
// 0.25 = 48px corners, which is above the 24px rounded radius but keeps the slice inside the
|
||||
// transparent buffer surrounding the shape.
|
||||
Brush->Margin = FMargin(0.25f, 0.25f, 0.25f, 0.25f);
|
||||
Brush->TintColor = FSlateColor(FLinearColor::White);
|
||||
return Brush;
|
||||
}
|
||||
|
||||
bool InitNineSliceButtonStyle(FButtonStyle& Out, const UPS_Editor_UIStyleAsset* S,
|
||||
const FString& NormalStem, const FString& HoverStem, const FString& PressedStem,
|
||||
FLinearColor NormalTint, FLinearColor HoverTint, FLinearColor PressedTint)
|
||||
{
|
||||
TSharedPtr<FSlateBrush> NormalBrush = Load9SliceBrushFromPluginContent(NormalStem + TEXT(".png"));
|
||||
TSharedPtr<FSlateBrush> HoverBrush = Load9SliceBrushFromPluginContent(HoverStem + TEXT(".png"));
|
||||
TSharedPtr<FSlateBrush> PressedBrush = Load9SliceBrushFromPluginContent(PressedStem + TEXT(".png"));
|
||||
|
||||
if (!NormalBrush.IsValid() || !HoverBrush.IsValid() || !PressedBrush.IsValid()) return false;
|
||||
|
||||
NormalBrush->TintColor = FSlateColor(NormalTint);
|
||||
HoverBrush->TintColor = FSlateColor(HoverTint);
|
||||
PressedBrush->TintColor = FSlateColor(PressedTint);
|
||||
|
||||
Out.Normal = *NormalBrush;
|
||||
Out.Hovered = *HoverBrush;
|
||||
Out.Pressed = *PressedBrush;
|
||||
Out.Disabled = *NormalBrush;
|
||||
Out.Disabled.TintColor = FSlateColor(FLinearColor(NormalTint.R, NormalTint.G, NormalTint.B, NormalTint.A * 0.35f));
|
||||
|
||||
Out.NormalPadding = FMargin(10, 4);
|
||||
Out.PressedPadding = FMargin(10, 4);
|
||||
// State-differentiated foregrounds — any child widget using FSlateColor::UseForeground()
|
||||
// (typical for icons and labels inside buttons) will automatically flip color per state.
|
||||
Out.NormalForeground = FSlateColor(TextMuted(S));
|
||||
Out.HoveredForeground = FSlateColor(TextPrimary(S));
|
||||
Out.PressedForeground = FSlateColor(Accent(S));
|
||||
Out.DisabledForeground = FSlateColor(TextDisabled(S));
|
||||
return true;
|
||||
}
|
||||
|
||||
int32 PopulateIconsFromPluginContent(UPS_Editor_UIStyleAsset* StyleAsset)
|
||||
{
|
||||
if (!StyleAsset) return 0;
|
||||
const FString IconsDir = ResolvePluginIconsDir();
|
||||
|
||||
// Maps each icon slot to the PNG filename the user provided.
|
||||
static const FIconSlot Slots[] = {
|
||||
{ EPS_Editor_Icon::Move, TEXT("Move"), &UPS_Editor_UIStyleAsset::IconMove },
|
||||
{ EPS_Editor_Icon::Rotate, TEXT("Rotate"), &UPS_Editor_UIStyleAsset::IconRotate },
|
||||
{ EPS_Editor_Icon::Scale, TEXT("Scale"), &UPS_Editor_UIStyleAsset::IconScale },
|
||||
{ EPS_Editor_Icon::Snap, TEXT("Snap"), &UPS_Editor_UIStyleAsset::IconSnap },
|
||||
{ EPS_Editor_Icon::Grid, TEXT("Grid"), &UPS_Editor_UIStyleAsset::IconGrid },
|
||||
{ EPS_Editor_Icon::World, TEXT("Globe"), &UPS_Editor_UIStyleAsset::IconWorld },
|
||||
{ EPS_Editor_Icon::Focus, TEXT("Target"), &UPS_Editor_UIStyleAsset::IconFocus },
|
||||
|
||||
{ EPS_Editor_Icon::Play, TEXT("Play"), &UPS_Editor_UIStyleAsset::IconPlay },
|
||||
{ EPS_Editor_Icon::Pause, TEXT("Pause"), &UPS_Editor_UIStyleAsset::IconPause },
|
||||
{ EPS_Editor_Icon::Stop, TEXT("Stop"), &UPS_Editor_UIStyleAsset::IconStop },
|
||||
{ EPS_Editor_Icon::Record, TEXT("Record"), &UPS_Editor_UIStyleAsset::IconRecord },
|
||||
{ EPS_Editor_Icon::SkipBack, TEXT("SkipBack"), &UPS_Editor_UIStyleAsset::IconSkipBack },
|
||||
{ EPS_Editor_Icon::SkipForward, TEXT("SkipFwd"), &UPS_Editor_UIStyleAsset::IconSkipForward },
|
||||
{ EPS_Editor_Icon::Simulate, TEXT("Zap"), &UPS_Editor_UIStyleAsset::IconSimulate },
|
||||
|
||||
{ EPS_Editor_Icon::Eye, TEXT("Eye"), &UPS_Editor_UIStyleAsset::IconEye },
|
||||
{ EPS_Editor_Icon::EyeOff, TEXT("EyeOff"), &UPS_Editor_UIStyleAsset::IconEyeOff },
|
||||
{ EPS_Editor_Icon::Lock, TEXT("Lock"), &UPS_Editor_UIStyleAsset::IconLock },
|
||||
{ EPS_Editor_Icon::Unlock, TEXT("Unlock"), &UPS_Editor_UIStyleAsset::IconUnlock },
|
||||
{ EPS_Editor_Icon::Sidebar, TEXT("Sidebar"), &UPS_Editor_UIStyleAsset::IconSidebar },
|
||||
{ EPS_Editor_Icon::PanelRight, TEXT("PanelRight"), &UPS_Editor_UIStyleAsset::IconPanelRight },
|
||||
{ EPS_Editor_Icon::PanelBottom, TEXT("PanelBottom"), &UPS_Editor_UIStyleAsset::IconPanelBottom },
|
||||
|
||||
{ EPS_Editor_Icon::Save, TEXT("Save"), &UPS_Editor_UIStyleAsset::IconSave },
|
||||
{ EPS_Editor_Icon::Load, TEXT("Folder"), &UPS_Editor_UIStyleAsset::IconLoad },
|
||||
{ EPS_Editor_Icon::Folder, TEXT("Folder"), &UPS_Editor_UIStyleAsset::IconFolder },
|
||||
{ EPS_Editor_Icon::NewFile, TEXT("Plus"), &UPS_Editor_UIStyleAsset::IconNew },
|
||||
|
||||
{ EPS_Editor_Icon::Plus, TEXT("Plus"), &UPS_Editor_UIStyleAsset::IconPlus },
|
||||
{ EPS_Editor_Icon::Minus, TEXT("Minus"), &UPS_Editor_UIStyleAsset::IconMinus },
|
||||
{ EPS_Editor_Icon::Check, TEXT("Check"), &UPS_Editor_UIStyleAsset::IconCheck },
|
||||
|
||||
{ EPS_Editor_Icon::Search, TEXT("Search"), &UPS_Editor_UIStyleAsset::IconSearch },
|
||||
{ EPS_Editor_Icon::Settings, TEXT("Settings"), &UPS_Editor_UIStyleAsset::IconSettings },
|
||||
{ EPS_Editor_Icon::ChevronDown, TEXT("ChevronDown"), &UPS_Editor_UIStyleAsset::IconChevronDown },
|
||||
{ EPS_Editor_Icon::ChevronRight, TEXT("ChevronRight"), &UPS_Editor_UIStyleAsset::IconChevronRight },
|
||||
{ EPS_Editor_Icon::ChevronLeft, TEXT("ChevronLeft"), &UPS_Editor_UIStyleAsset::IconChevronLeft },
|
||||
{ EPS_Editor_Icon::Command, TEXT("Command"), &UPS_Editor_UIStyleAsset::IconCommand },
|
||||
{ EPS_Editor_Icon::More, TEXT("More"), &UPS_Editor_UIStyleAsset::IconMore },
|
||||
{ EPS_Editor_Icon::Help, TEXT("Help"), &UPS_Editor_UIStyleAsset::IconHelp },
|
||||
{ EPS_Editor_Icon::Bell, TEXT("Bell"), &UPS_Editor_UIStyleAsset::IconBell },
|
||||
{ EPS_Editor_Icon::Download, TEXT("Download"), &UPS_Editor_UIStyleAsset::IconDownload },
|
||||
|
||||
{ EPS_Editor_Icon::Character, TEXT("User"), &UPS_Editor_UIStyleAsset::IconCharacter },
|
||||
{ EPS_Editor_Icon::SplineObj, TEXT("Spline"), &UPS_Editor_UIStyleAsset::IconSpline },
|
||||
{ EPS_Editor_Icon::Prop, TEXT("Box"), &UPS_Editor_UIStyleAsset::IconProp },
|
||||
{ EPS_Editor_Icon::Light, TEXT("Lightbulb"), &UPS_Editor_UIStyleAsset::IconLight },
|
||||
{ EPS_Editor_Icon::Camera, TEXT("Camera"), &UPS_Editor_UIStyleAsset::IconCamera },
|
||||
{ EPS_Editor_Icon::Trigger, TEXT("Zap"), &UPS_Editor_UIStyleAsset::IconTrigger },
|
||||
{ EPS_Editor_Icon::Layers, TEXT("Layers"), &UPS_Editor_UIStyleAsset::IconLayers },
|
||||
{ EPS_Editor_Icon::Film, TEXT("Film"), &UPS_Editor_UIStyleAsset::IconFilm },
|
||||
{ EPS_Editor_Icon::Box, TEXT("Box"), &UPS_Editor_UIStyleAsset::IconBox },
|
||||
{ EPS_Editor_Icon::Flag, TEXT("Flag"), &UPS_Editor_UIStyleAsset::IconFlag },
|
||||
{ EPS_Editor_Icon::Map, TEXT("Map"), &UPS_Editor_UIStyleAsset::IconMap },
|
||||
|
||||
{ EPS_Editor_Icon::BrandMark, TEXT("Diamond"), &UPS_Editor_UIStyleAsset::IconBrandMark },
|
||||
};
|
||||
|
||||
int32 Loaded = 0;
|
||||
for (const FIconSlot& Slot : Slots)
|
||||
{
|
||||
if (StyleAsset->*(Slot.Member)) continue; // user already assigned it, keep it
|
||||
|
||||
const FString Path = IconsDir / FString::Printf(TEXT("%s.png"), Slot.FileStem);
|
||||
if (!IFileManager::Get().FileExists(*Path)) continue;
|
||||
|
||||
if (UTexture2D* Tex = LoadTextureFromPng(Path))
|
||||
{
|
||||
Tex->AddToRoot(); // transient, keep alive while plugin runs
|
||||
StyleAsset->*(Slot.Member) = Tex;
|
||||
Loaded++;
|
||||
}
|
||||
}
|
||||
return Loaded;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Fonts/SlateFontInfo.h"
|
||||
#include "Styling/SlateColor.h"
|
||||
#include "Styling/SlateTypes.h"
|
||||
#include "Widgets/SCompoundWidget.h"
|
||||
#include "PS_Editor_UIStyleAsset.h"
|
||||
|
||||
class UPS_Editor_UIStyleAsset;
|
||||
class SWidget;
|
||||
struct FSlateBrush;
|
||||
struct FButtonStyle;
|
||||
struct FEditableTextBoxStyle;
|
||||
|
||||
/**
|
||||
* Style accessors for PS_Editor UI. Every function takes a (nullable) style asset
|
||||
* and returns a value ready to feed to Slate. If the asset is null, hardcoded
|
||||
* fallbacks matching the DataAsset defaults are returned so the UI never breaks.
|
||||
*/
|
||||
namespace FPS_Editor_UIStyle
|
||||
{
|
||||
// ---- Colors ----
|
||||
PS_EDITOR_API FLinearColor BgPrimary(const UPS_Editor_UIStyleAsset* S);
|
||||
PS_EDITOR_API FLinearColor BgSecondary(const UPS_Editor_UIStyleAsset* S);
|
||||
PS_EDITOR_API FLinearColor GlassStrong(const UPS_Editor_UIStyleAsset* S);
|
||||
PS_EDITOR_API FLinearColor GlassSoft(const UPS_Editor_UIStyleAsset* S);
|
||||
PS_EDITOR_API FLinearColor GlassBorder(const UPS_Editor_UIStyleAsset* S);
|
||||
PS_EDITOR_API FLinearColor GlassBorderStrong(const UPS_Editor_UIStyleAsset* S);
|
||||
|
||||
PS_EDITOR_API FLinearColor TextPrimary(const UPS_Editor_UIStyleAsset* S);
|
||||
PS_EDITOR_API FLinearColor TextMuted(const UPS_Editor_UIStyleAsset* S);
|
||||
PS_EDITOR_API FLinearColor TextDim(const UPS_Editor_UIStyleAsset* S);
|
||||
PS_EDITOR_API FLinearColor TextDisabled(const UPS_Editor_UIStyleAsset* S);
|
||||
|
||||
PS_EDITOR_API FLinearColor Accent(const UPS_Editor_UIStyleAsset* S);
|
||||
PS_EDITOR_API FLinearColor AccentSoft(const UPS_Editor_UIStyleAsset* S);
|
||||
PS_EDITOR_API FLinearColor AccentRing(const UPS_Editor_UIStyleAsset* S);
|
||||
|
||||
PS_EDITOR_API FLinearColor Ok(const UPS_Editor_UIStyleAsset* S);
|
||||
PS_EDITOR_API FLinearColor Warn(const UPS_Editor_UIStyleAsset* S);
|
||||
PS_EDITOR_API FLinearColor Err(const UPS_Editor_UIStyleAsset* S);
|
||||
PS_EDITOR_API FLinearColor Info(const UPS_Editor_UIStyleAsset* S);
|
||||
|
||||
PS_EDITOR_API FLinearColor AxisX(const UPS_Editor_UIStyleAsset* S);
|
||||
PS_EDITOR_API FLinearColor AxisY(const UPS_Editor_UIStyleAsset* S);
|
||||
PS_EDITOR_API FLinearColor AxisZ(const UPS_Editor_UIStyleAsset* S);
|
||||
|
||||
// ---- Fonts ----
|
||||
PS_EDITOR_API FSlateFontInfo BodyFont(const UPS_Editor_UIStyleAsset* S);
|
||||
PS_EDITOR_API FSlateFontInfo BodyFontBold(const UPS_Editor_UIStyleAsset* S);
|
||||
PS_EDITOR_API FSlateFontInfo SmallFont(const UPS_Editor_UIStyleAsset* S);
|
||||
PS_EDITOR_API FSlateFontInfo HeadingFont(const UPS_Editor_UIStyleAsset* S);
|
||||
PS_EDITOR_API FSlateFontInfo HeadingFontBold(const UPS_Editor_UIStyleAsset* S);
|
||||
PS_EDITOR_API FSlateFontInfo LargeFont(const UPS_Editor_UIStyleAsset* S);
|
||||
PS_EDITOR_API FSlateFontInfo MonoFont(const UPS_Editor_UIStyleAsset* S);
|
||||
|
||||
// ---- Layout ----
|
||||
PS_EDITOR_API float RadiusXS(const UPS_Editor_UIStyleAsset* S);
|
||||
PS_EDITOR_API float RadiusS(const UPS_Editor_UIStyleAsset* S);
|
||||
PS_EDITOR_API float RadiusM(const UPS_Editor_UIStyleAsset* S);
|
||||
PS_EDITOR_API float RadiusL(const UPS_Editor_UIStyleAsset* S);
|
||||
PS_EDITOR_API FMargin PadPanel(const UPS_Editor_UIStyleAsset* S);
|
||||
PS_EDITOR_API FMargin PadRow(const UPS_Editor_UIStyleAsset* S);
|
||||
|
||||
// ---- Composite helpers ----
|
||||
/** Build an SBorder with glass fill + border, wrapping Content. */
|
||||
PS_EDITOR_API TSharedRef<SWidget> MakeGlassPanel(const UPS_Editor_UIStyleAsset* S, bool bStrong, TSharedRef<SWidget> Content, FMargin Padding = FMargin(12.f));
|
||||
|
||||
/** Build an icon widget with a fixed tint. Returns SImage if the slot has a texture; otherwise falls back to an STextBlock with a unicode glyph. */
|
||||
PS_EDITOR_API TSharedRef<SWidget> MakeIcon(const UPS_Editor_UIStyleAsset* S, EPS_Editor_Icon Icon, float Size = 16.f, FLinearColor Tint = FLinearColor::White);
|
||||
|
||||
/** Build an icon widget that inherits the parent button's foreground color (for normal/hover/pressed state tinting). */
|
||||
PS_EDITOR_API TSharedRef<SWidget> MakeIconFollowForeground(const UPS_Editor_UIStyleAsset* S, EPS_Editor_Icon Icon, float Size = 16.f);
|
||||
|
||||
// ---- Brush / style factories ----
|
||||
// These construct FSlateBrush / FButtonStyle / FEditableTextBoxStyle objects. The caller is
|
||||
// responsible for keeping them alive (typically as widget members) because Slate only stores
|
||||
// raw pointers and never copies the brush contents.
|
||||
enum class EPanelTone : uint8 { GlassStrong, GlassSoft, Pill, FieldFill, AccentFill, AccentSoftFill };
|
||||
|
||||
/** Rounded-box brush matching the design tokens. Width/corner radius from the style tokens. */
|
||||
PS_EDITOR_API void InitPanelBrush(FSlateBrush& Out, const UPS_Editor_UIStyleAsset* S, EPanelTone Tone, float CornerRadius = 10.f);
|
||||
|
||||
/** Flat button style without chrome. Hover/pressed use subtle accent tint. */
|
||||
PS_EDITOR_API void InitFlatButtonStyle(FButtonStyle& Out, const UPS_Editor_UIStyleAsset* S, float CornerRadius = 6.f);
|
||||
|
||||
/** Accent button style (orange fill, dark text). */
|
||||
PS_EDITOR_API void InitAccentButtonStyle(FButtonStyle& Out, const UPS_Editor_UIStyleAsset* S, float CornerRadius = 6.f);
|
||||
|
||||
/** Minimal editable text box style with thin rounded border. */
|
||||
PS_EDITOR_API void InitTextBoxStyle(FEditableTextBoxStyle& Out, const UPS_Editor_UIStyleAsset* S, float CornerRadius = 4.f);
|
||||
|
||||
/** Scan <PluginContent>/UI/icons/ for PNG files and populate all unassigned icon slots on the asset.
|
||||
* Files are expected as "<IconName>.png" (matching the EPS_Editor_Icon enum name, e.g. "Move.png").
|
||||
* Already-assigned slots are preserved. Returns the number of icons loaded. */
|
||||
PS_EDITOR_API int32 PopulateIconsFromPluginContent(UPS_Editor_UIStyleAsset* StyleAsset);
|
||||
|
||||
/** Load a PNG as a 9-slice Box brush (Margin 0.333 on all sides). Returns nullptr if the file is missing. */
|
||||
PS_EDITOR_API TSharedPtr<FSlateBrush> Load9SliceBrushFromPluginContent(const FString& RelativePath);
|
||||
|
||||
/** Initialize a button style from three 9-slice PNGs (normal/hover/pressed). Falls back to flat
|
||||
* procedural style if any file is missing. NormalSlice/HoverSlice/PressedSlice are paths relative
|
||||
* to Content/UI/unreal_9slice/. */
|
||||
PS_EDITOR_API bool InitNineSliceButtonStyle(FButtonStyle& Out, const UPS_Editor_UIStyleAsset* S,
|
||||
const FString& NormalStem, const FString& HoverStem, const FString& PressedStem,
|
||||
FLinearColor NormalTint = FLinearColor::White,
|
||||
FLinearColor HoverTint = FLinearColor::White,
|
||||
FLinearColor PressedTint = FLinearColor::White);
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
#include "PS_Editor_UIStyleAsset.h"
|
||||
|
||||
FPS_Editor_IconData UPS_Editor_UIStyleAsset::GetIcon(EPS_Editor_Icon Icon) const
|
||||
{
|
||||
switch (Icon)
|
||||
{
|
||||
case EPS_Editor_Icon::Move: return { IconMove, TEXT("\u2194") }; // ↔
|
||||
case EPS_Editor_Icon::Rotate: return { IconRotate, TEXT("\u21BB") }; // ↻
|
||||
case EPS_Editor_Icon::Scale: return { IconScale, TEXT("\u2922") }; // ⤢
|
||||
case EPS_Editor_Icon::Snap: return { IconSnap, TEXT("\u29C7") }; // ⧇
|
||||
case EPS_Editor_Icon::Grid: return { IconGrid, TEXT("\u25A6") }; // ▦
|
||||
case EPS_Editor_Icon::World: return { IconWorld, TEXT("\u25CE") }; // ◎
|
||||
case EPS_Editor_Icon::Focus: return { IconFocus, TEXT("\u2316") }; // ⌖
|
||||
|
||||
case EPS_Editor_Icon::Play: return { IconPlay, TEXT("\u25B6") }; // ▶
|
||||
case EPS_Editor_Icon::Pause: return { IconPause, TEXT("\u23F8") }; // ⏸
|
||||
case EPS_Editor_Icon::Stop: return { IconStop, TEXT("\u25A0") }; // ■
|
||||
case EPS_Editor_Icon::Record: return { IconRecord, TEXT("\u25CF") }; // ●
|
||||
case EPS_Editor_Icon::SkipBack: return { IconSkipBack, TEXT("\u23EE") }; // ⏮
|
||||
case EPS_Editor_Icon::SkipForward: return { IconSkipForward, TEXT("\u23ED") }; // ⏭
|
||||
case EPS_Editor_Icon::Simulate: return { IconSimulate, TEXT("\u26A1") }; // ⚡
|
||||
|
||||
case EPS_Editor_Icon::Eye: return { IconEye, TEXT("\u25C9") }; // ◉
|
||||
case EPS_Editor_Icon::EyeOff: return { IconEyeOff, TEXT("\u00D8") }; // Ø
|
||||
case EPS_Editor_Icon::Lock: return { IconLock, TEXT("\u2B23") }; // ⬣
|
||||
case EPS_Editor_Icon::Unlock: return { IconUnlock, TEXT("\u2B21") }; // ⬡
|
||||
case EPS_Editor_Icon::Sidebar: return { IconSidebar, TEXT("\u258C") }; // ▌
|
||||
case EPS_Editor_Icon::PanelRight: return { IconPanelRight, TEXT("\u2590") }; // ▐
|
||||
case EPS_Editor_Icon::PanelBottom: return { IconPanelBottom, TEXT("\u2584") }; // ▄
|
||||
|
||||
case EPS_Editor_Icon::Save: return { IconSave, TEXT("\u2193") }; // ↓
|
||||
case EPS_Editor_Icon::Load: return { IconLoad, TEXT("\u2191") }; // ↑
|
||||
case EPS_Editor_Icon::Folder: return { IconFolder, TEXT("\u25B1") }; // ▱
|
||||
case EPS_Editor_Icon::NewFile: return { IconNew, TEXT("\u2795") }; // ➕
|
||||
|
||||
case EPS_Editor_Icon::Plus: return { IconPlus, TEXT("+") };
|
||||
case EPS_Editor_Icon::Minus: return { IconMinus, TEXT("\u2212") }; // −
|
||||
case EPS_Editor_Icon::Delete: return { IconDelete, TEXT("\u2716") }; // ✖
|
||||
case EPS_Editor_Icon::Duplicate: return { IconDuplicate, TEXT("\u29C9") }; // ⧉
|
||||
case EPS_Editor_Icon::Undo: return { IconUndo, TEXT("\u21B6") }; // ↶
|
||||
case EPS_Editor_Icon::Redo: return { IconRedo, TEXT("\u21B7") }; // ↷
|
||||
case EPS_Editor_Icon::Check: return { IconCheck, TEXT("\u2713") }; // ✓
|
||||
case EPS_Editor_Icon::Close: return { IconClose, TEXT("\u00D7") }; // ×
|
||||
|
||||
case EPS_Editor_Icon::Search: return { IconSearch, TEXT("\u2315") }; // ⌕
|
||||
case EPS_Editor_Icon::Settings: return { IconSettings, TEXT("\u2699") }; // ⚙
|
||||
case EPS_Editor_Icon::ChevronDown: return { IconChevronDown, TEXT("\u25BE") }; // ▾
|
||||
case EPS_Editor_Icon::ChevronRight: return { IconChevronRight, TEXT("\u25B8") }; // ▸
|
||||
case EPS_Editor_Icon::ChevronLeft: return { IconChevronLeft, TEXT("\u25C2") }; // ◂
|
||||
case EPS_Editor_Icon::Command: return { IconCommand, TEXT("\u2318") }; // ⌘
|
||||
case EPS_Editor_Icon::More: return { IconMore, TEXT("\u22EF") }; // ⋯
|
||||
case EPS_Editor_Icon::Help: return { IconHelp, TEXT("?") };
|
||||
case EPS_Editor_Icon::Bell: return { IconBell, TEXT("!") };
|
||||
case EPS_Editor_Icon::Download: return { IconDownload, TEXT("v") };
|
||||
|
||||
case EPS_Editor_Icon::Character: return { IconCharacter, TEXT("\u263A") }; // ☺
|
||||
case EPS_Editor_Icon::SplineObj: return { IconSpline, TEXT("\u223F") }; // ∿
|
||||
case EPS_Editor_Icon::Prop: return { IconProp, TEXT("\u25A3") }; // ▣
|
||||
case EPS_Editor_Icon::Light: return { IconLight, TEXT("\u2600") }; // ☀
|
||||
case EPS_Editor_Icon::Camera: return { IconCamera, TEXT("\u25A4") }; // ▤
|
||||
case EPS_Editor_Icon::Trigger: return { IconTrigger, TEXT("\u26A1") }; // ⚡
|
||||
case EPS_Editor_Icon::Layers: return { IconLayers, TEXT("\u2263") }; // ≣
|
||||
case EPS_Editor_Icon::Film: return { IconFilm, TEXT("\u25A9") }; // ▩
|
||||
case EPS_Editor_Icon::Box: return { IconBox, TEXT("\u25A2") }; // ▢
|
||||
case EPS_Editor_Icon::Flag: return { IconFlag, TEXT("F") };
|
||||
case EPS_Editor_Icon::Map: return { IconMap, TEXT("M") };
|
||||
|
||||
case EPS_Editor_Icon::BrandMark: return { IconBrandMark, TEXT("\u25C6") }; // ◆
|
||||
|
||||
default: return { nullptr, TEXT("?") };
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Engine/DataAsset.h"
|
||||
#include "Engine/Texture2D.h"
|
||||
#include "PS_Editor_UIStyleAsset.generated.h"
|
||||
|
||||
/** Icon slot identifier. Used by FPS_Editor_UIStyle::MakeIcon() to fetch a texture from the style asset. */
|
||||
UENUM(BlueprintType)
|
||||
enum class EPS_Editor_Icon : uint8
|
||||
{
|
||||
None = 0,
|
||||
|
||||
// Transform tools
|
||||
Move, Rotate, Scale, Snap, Grid, World, Focus,
|
||||
|
||||
// Playback
|
||||
Play, Pause, Stop, Record, SkipBack, SkipForward, Simulate,
|
||||
|
||||
// View / visibility
|
||||
Eye, EyeOff, Lock, Unlock, Sidebar, PanelRight, PanelBottom,
|
||||
|
||||
// File
|
||||
Save, Load, Folder, NewFile,
|
||||
|
||||
// Edit
|
||||
Plus, Minus, Delete, Duplicate, Undo, Redo, Check, Close,
|
||||
|
||||
// Chrome / Nav
|
||||
Search, Settings, ChevronDown, ChevronRight, ChevronLeft, Command, More, Help, Bell, Download,
|
||||
|
||||
// Object types
|
||||
Character, SplineObj, Prop, Light, Camera, Trigger, Layers, Film, Box, Flag, Map,
|
||||
|
||||
// Brand
|
||||
BrandMark,
|
||||
};
|
||||
|
||||
/** Bundle returned by GetIcon: texture (may be null) + unicode fallback glyph. */
|
||||
struct FPS_Editor_IconData
|
||||
{
|
||||
UTexture2D* Texture = nullptr;
|
||||
const TCHAR* Fallback = TEXT("?");
|
||||
};
|
||||
|
||||
/**
|
||||
* UI style configuration for PS_Editor. Create a DataAsset instance at
|
||||
* /PS_Editor/Style/DA_PS_Editor_UIStyle and assign icon textures + tweak colors.
|
||||
* If absent at runtime, the FPS_Editor_UIStyle helpers fall back to hardcoded values
|
||||
* that match the defaults defined here.
|
||||
*
|
||||
* Icons specs:
|
||||
* - PNG 32x32 with alpha
|
||||
* - White stroke (#ffffff) on transparent background, stroke width 1.5-2 px (lucide-style)
|
||||
* - Import settings: sRGB OFF, MipGen NoMipMaps, Texture Group UI, Compression UserInterface2D
|
||||
*/
|
||||
UCLASS(BlueprintType)
|
||||
class PS_EDITOR_API UPS_Editor_UIStyleAsset : public UDataAsset
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
// ==================== COLORS ====================
|
||||
UPROPERTY(EditAnywhere, Category = "Colors|Background")
|
||||
FLinearColor BgPrimary = FLinearColor(0.027f, 0.039f, 0.059f, 1.0f); // #070a0f
|
||||
|
||||
UPROPERTY(EditAnywhere, Category = "Colors|Background")
|
||||
FLinearColor BgSecondary = FLinearColor(0.043f, 0.067f, 0.094f, 1.0f); // #0b1118
|
||||
|
||||
UPROPERTY(EditAnywhere, Category = "Colors|Glass")
|
||||
FLinearColor GlassStrong = FLinearColor(0.0784f, 0.1216f, 0.1804f, 0.88f); // #141f2e @ 88%
|
||||
|
||||
UPROPERTY(EditAnywhere, Category = "Colors|Glass")
|
||||
FLinearColor GlassSoft = FLinearColor(0.0549f, 0.0863f, 0.1333f, 0.72f); // #0e1622 @ 72%
|
||||
|
||||
UPROPERTY(EditAnywhere, Category = "Colors|Glass")
|
||||
FLinearColor GlassBorder = FLinearColor(1.0f, 1.0f, 1.0f, 0.10f);
|
||||
|
||||
UPROPERTY(EditAnywhere, Category = "Colors|Glass")
|
||||
FLinearColor GlassBorderStrong = FLinearColor(1.0f, 1.0f, 1.0f, 0.18f);
|
||||
|
||||
UPROPERTY(EditAnywhere, Category = "Colors|Text")
|
||||
FLinearColor TextPrimary = FLinearColor(0.902f, 0.925f, 0.953f, 1.0f); // #e6ecf3
|
||||
|
||||
UPROPERTY(EditAnywhere, Category = "Colors|Text")
|
||||
FLinearColor TextMuted = FLinearColor(0.722f, 0.769f, 0.824f, 1.0f); // #b8c4d2
|
||||
|
||||
UPROPERTY(EditAnywhere, Category = "Colors|Text")
|
||||
FLinearColor TextDim = FLinearColor(0.502f, 0.569f, 0.647f, 1.0f); // #8091a5
|
||||
|
||||
UPROPERTY(EditAnywhere, Category = "Colors|Text")
|
||||
FLinearColor TextDisabled = FLinearColor(0.353f, 0.416f, 0.494f, 1.0f); // #5a6a7e
|
||||
|
||||
UPROPERTY(EditAnywhere, Category = "Colors|Accent")
|
||||
FLinearColor Accent = FLinearColor(1.0f, 0.416f, 0.239f, 1.0f); // #ff6a3d
|
||||
|
||||
UPROPERTY(EditAnywhere, Category = "Colors|Accent")
|
||||
FLinearColor Accent2 = FLinearColor(1.0f, 0.627f, 0.420f, 1.0f); // #ffa06b
|
||||
|
||||
UPROPERTY(EditAnywhere, Category = "Colors|Accent")
|
||||
FLinearColor AccentSoft = FLinearColor(1.0f, 0.416f, 0.239f, 0.14f);
|
||||
|
||||
UPROPERTY(EditAnywhere, Category = "Colors|Accent")
|
||||
FLinearColor AccentRing = FLinearColor(1.0f, 0.416f, 0.239f, 0.35f);
|
||||
|
||||
UPROPERTY(EditAnywhere, Category = "Colors|Status")
|
||||
FLinearColor Ok = FLinearColor(0.243f, 0.812f, 0.557f, 1.0f); // #3ecf8e
|
||||
|
||||
UPROPERTY(EditAnywhere, Category = "Colors|Status")
|
||||
FLinearColor Warn = FLinearColor(0.961f, 0.725f, 0.267f, 1.0f); // #f5b944
|
||||
|
||||
UPROPERTY(EditAnywhere, Category = "Colors|Status")
|
||||
FLinearColor Err = FLinearColor(1.0f, 0.353f, 0.431f, 1.0f); // #ff5a6e
|
||||
|
||||
UPROPERTY(EditAnywhere, Category = "Colors|Status")
|
||||
FLinearColor Info = FLinearColor(0.290f, 0.659f, 1.0f, 1.0f); // #4aa8ff (z)
|
||||
|
||||
UPROPERTY(EditAnywhere, Category = "Colors|Axis")
|
||||
FLinearColor AxisX = FLinearColor(1.0f, 0.302f, 0.369f, 1.0f); // #ff4d5e
|
||||
|
||||
UPROPERTY(EditAnywhere, Category = "Colors|Axis")
|
||||
FLinearColor AxisY = FLinearColor(0.290f, 0.839f, 0.427f, 1.0f); // #4ad66d
|
||||
|
||||
UPROPERTY(EditAnywhere, Category = "Colors|Axis")
|
||||
FLinearColor AxisZ = FLinearColor(0.290f, 0.659f, 1.0f, 1.0f); // #4aa8ff
|
||||
|
||||
// ==================== FONTS ====================
|
||||
UPROPERTY(EditAnywhere, Category = "Fonts")
|
||||
int32 FontSizeXSmall = 9;
|
||||
|
||||
UPROPERTY(EditAnywhere, Category = "Fonts")
|
||||
int32 FontSizeSmall = 10;
|
||||
|
||||
UPROPERTY(EditAnywhere, Category = "Fonts")
|
||||
int32 FontSizeBody = 11;
|
||||
|
||||
UPROPERTY(EditAnywhere, Category = "Fonts")
|
||||
int32 FontSizeHeading = 13;
|
||||
|
||||
UPROPERTY(EditAnywhere, Category = "Fonts")
|
||||
int32 FontSizeLarge = 16;
|
||||
|
||||
// ==================== LAYOUT ====================
|
||||
// Spec radii (halved from v1 → matches the 9-slice radii)
|
||||
UPROPERTY(EditAnywhere, Category = "Layout")
|
||||
float RadiusXS = 3.0f;
|
||||
|
||||
UPROPERTY(EditAnywhere, Category = "Layout")
|
||||
float RadiusS = 5.0f;
|
||||
|
||||
UPROPERTY(EditAnywhere, Category = "Layout")
|
||||
float RadiusM = 7.0f;
|
||||
|
||||
UPROPERTY(EditAnywhere, Category = "Layout")
|
||||
float RadiusL = 9.0f;
|
||||
|
||||
UPROPERTY(EditAnywhere, Category = "Layout")
|
||||
float RadiusXL = 12.0f;
|
||||
|
||||
UPROPERTY(EditAnywhere, Category = "Layout")
|
||||
float PadPanel = 12.0f;
|
||||
|
||||
UPROPERTY(EditAnywhere, Category = "Layout")
|
||||
float PadRow = 6.0f;
|
||||
|
||||
UPROPERTY(EditAnywhere, Category = "Layout")
|
||||
float PadTight = 4.0f;
|
||||
|
||||
// ==================== ICONS ====================
|
||||
UPROPERTY(EditAnywhere, Category = "Icons|Transform") TObjectPtr<UTexture2D> IconMove;
|
||||
UPROPERTY(EditAnywhere, Category = "Icons|Transform") TObjectPtr<UTexture2D> IconRotate;
|
||||
UPROPERTY(EditAnywhere, Category = "Icons|Transform") TObjectPtr<UTexture2D> IconScale;
|
||||
UPROPERTY(EditAnywhere, Category = "Icons|Transform") TObjectPtr<UTexture2D> IconSnap;
|
||||
UPROPERTY(EditAnywhere, Category = "Icons|Transform") TObjectPtr<UTexture2D> IconGrid;
|
||||
UPROPERTY(EditAnywhere, Category = "Icons|Transform") TObjectPtr<UTexture2D> IconWorld;
|
||||
UPROPERTY(EditAnywhere, Category = "Icons|Transform") TObjectPtr<UTexture2D> IconFocus;
|
||||
|
||||
UPROPERTY(EditAnywhere, Category = "Icons|Playback") TObjectPtr<UTexture2D> IconPlay;
|
||||
UPROPERTY(EditAnywhere, Category = "Icons|Playback") TObjectPtr<UTexture2D> IconPause;
|
||||
UPROPERTY(EditAnywhere, Category = "Icons|Playback") TObjectPtr<UTexture2D> IconStop;
|
||||
UPROPERTY(EditAnywhere, Category = "Icons|Playback") TObjectPtr<UTexture2D> IconRecord;
|
||||
UPROPERTY(EditAnywhere, Category = "Icons|Playback") TObjectPtr<UTexture2D> IconSkipBack;
|
||||
UPROPERTY(EditAnywhere, Category = "Icons|Playback") TObjectPtr<UTexture2D> IconSkipForward;
|
||||
UPROPERTY(EditAnywhere, Category = "Icons|Playback") TObjectPtr<UTexture2D> IconSimulate;
|
||||
|
||||
UPROPERTY(EditAnywhere, Category = "Icons|View") TObjectPtr<UTexture2D> IconEye;
|
||||
UPROPERTY(EditAnywhere, Category = "Icons|View") TObjectPtr<UTexture2D> IconEyeOff;
|
||||
UPROPERTY(EditAnywhere, Category = "Icons|View") TObjectPtr<UTexture2D> IconLock;
|
||||
UPROPERTY(EditAnywhere, Category = "Icons|View") TObjectPtr<UTexture2D> IconUnlock;
|
||||
UPROPERTY(EditAnywhere, Category = "Icons|View") TObjectPtr<UTexture2D> IconSidebar;
|
||||
UPROPERTY(EditAnywhere, Category = "Icons|View") TObjectPtr<UTexture2D> IconPanelRight;
|
||||
UPROPERTY(EditAnywhere, Category = "Icons|View") TObjectPtr<UTexture2D> IconPanelBottom;
|
||||
|
||||
UPROPERTY(EditAnywhere, Category = "Icons|File") TObjectPtr<UTexture2D> IconSave;
|
||||
UPROPERTY(EditAnywhere, Category = "Icons|File") TObjectPtr<UTexture2D> IconLoad;
|
||||
UPROPERTY(EditAnywhere, Category = "Icons|File") TObjectPtr<UTexture2D> IconFolder;
|
||||
UPROPERTY(EditAnywhere, Category = "Icons|File") TObjectPtr<UTexture2D> IconNew;
|
||||
|
||||
UPROPERTY(EditAnywhere, Category = "Icons|Edit") TObjectPtr<UTexture2D> IconPlus;
|
||||
UPROPERTY(EditAnywhere, Category = "Icons|Edit") TObjectPtr<UTexture2D> IconMinus;
|
||||
UPROPERTY(EditAnywhere, Category = "Icons|Edit") TObjectPtr<UTexture2D> IconDelete;
|
||||
UPROPERTY(EditAnywhere, Category = "Icons|Edit") TObjectPtr<UTexture2D> IconDuplicate;
|
||||
UPROPERTY(EditAnywhere, Category = "Icons|Edit") TObjectPtr<UTexture2D> IconUndo;
|
||||
UPROPERTY(EditAnywhere, Category = "Icons|Edit") TObjectPtr<UTexture2D> IconRedo;
|
||||
UPROPERTY(EditAnywhere, Category = "Icons|Edit") TObjectPtr<UTexture2D> IconCheck;
|
||||
UPROPERTY(EditAnywhere, Category = "Icons|Edit") TObjectPtr<UTexture2D> IconClose;
|
||||
|
||||
UPROPERTY(EditAnywhere, Category = "Icons|Nav") TObjectPtr<UTexture2D> IconSearch;
|
||||
UPROPERTY(EditAnywhere, Category = "Icons|Nav") TObjectPtr<UTexture2D> IconSettings;
|
||||
UPROPERTY(EditAnywhere, Category = "Icons|Nav") TObjectPtr<UTexture2D> IconChevronDown;
|
||||
UPROPERTY(EditAnywhere, Category = "Icons|Nav") TObjectPtr<UTexture2D> IconChevronRight;
|
||||
UPROPERTY(EditAnywhere, Category = "Icons|Nav") TObjectPtr<UTexture2D> IconChevronLeft;
|
||||
UPROPERTY(EditAnywhere, Category = "Icons|Nav") TObjectPtr<UTexture2D> IconCommand;
|
||||
UPROPERTY(EditAnywhere, Category = "Icons|Nav") TObjectPtr<UTexture2D> IconMore;
|
||||
UPROPERTY(EditAnywhere, Category = "Icons|Nav") TObjectPtr<UTexture2D> IconHelp;
|
||||
UPROPERTY(EditAnywhere, Category = "Icons|Nav") TObjectPtr<UTexture2D> IconBell;
|
||||
UPROPERTY(EditAnywhere, Category = "Icons|Nav") TObjectPtr<UTexture2D> IconDownload;
|
||||
|
||||
UPROPERTY(EditAnywhere, Category = "Icons|Objects") TObjectPtr<UTexture2D> IconCharacter;
|
||||
UPROPERTY(EditAnywhere, Category = "Icons|Objects") TObjectPtr<UTexture2D> IconSpline;
|
||||
UPROPERTY(EditAnywhere, Category = "Icons|Objects") TObjectPtr<UTexture2D> IconProp;
|
||||
UPROPERTY(EditAnywhere, Category = "Icons|Objects") TObjectPtr<UTexture2D> IconLight;
|
||||
UPROPERTY(EditAnywhere, Category = "Icons|Objects") TObjectPtr<UTexture2D> IconCamera;
|
||||
UPROPERTY(EditAnywhere, Category = "Icons|Objects") TObjectPtr<UTexture2D> IconTrigger;
|
||||
UPROPERTY(EditAnywhere, Category = "Icons|Objects") TObjectPtr<UTexture2D> IconLayers;
|
||||
UPROPERTY(EditAnywhere, Category = "Icons|Objects") TObjectPtr<UTexture2D> IconFilm;
|
||||
UPROPERTY(EditAnywhere, Category = "Icons|Objects") TObjectPtr<UTexture2D> IconBox;
|
||||
UPROPERTY(EditAnywhere, Category = "Icons|Objects") TObjectPtr<UTexture2D> IconFlag;
|
||||
UPROPERTY(EditAnywhere, Category = "Icons|Objects") TObjectPtr<UTexture2D> IconMap;
|
||||
|
||||
UPROPERTY(EditAnywhere, Category = "Icons|Brand") TObjectPtr<UTexture2D> IconBrandMark;
|
||||
|
||||
/** Resolve an icon slot to texture + unicode fallback glyph. Never returns nullptr struct. */
|
||||
FPS_Editor_IconData GetIcon(EPS_Editor_Icon Icon) const;
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
@@ -3,11 +3,13 @@
|
||||
#include "CoreMinimal.h"
|
||||
#include "Blueprint/UserWidget.h"
|
||||
#include "PS_Editor_SelectionTypes.h"
|
||||
#include "Styling/SlateTypes.h"
|
||||
#include "PS_Editor_MainWidget.generated.h"
|
||||
|
||||
class UPS_Editor_SelectionManager;
|
||||
class UPS_Editor_SpawnManager;
|
||||
class UPS_Editor_SceneSerializer;
|
||||
class UPS_Editor_UIStyleAsset;
|
||||
|
||||
/** Widget state for a single dynamic property row in the property panel. */
|
||||
struct FPS_Editor_PropertyRowWidgets
|
||||
@@ -63,6 +65,26 @@ private:
|
||||
UPROPERTY()
|
||||
TWeakObjectPtr<UPS_Editor_SceneSerializer> SceneSerializer;
|
||||
|
||||
/** UI style configuration. Resolved once at RebuildWidget entry via soft path.
|
||||
* Nullable — all FPS_Editor_UIStyle accessors fall back to hardcoded defaults. */
|
||||
UPROPERTY()
|
||||
TObjectPtr<UPS_Editor_UIStyleAsset> StyleAsset;
|
||||
|
||||
// Cached brushes / styles built from the style asset. Lifetime-owned by the widget so
|
||||
// Slate can reference them via raw pointer in Build* methods.
|
||||
TSharedPtr<FSlateBrush> BrushGlassStrong;
|
||||
TSharedPtr<FSlateBrush> BrushGlassSoft;
|
||||
TSharedPtr<FSlateBrush> BrushPill;
|
||||
TSharedPtr<FSlateBrush> BrushField;
|
||||
TSharedPtr<FSlateBrush> BrushAccent;
|
||||
TSharedPtr<FSlateBrush> BrushAccentSoft;
|
||||
TSharedPtr<FButtonStyle> StyleFlatButton;
|
||||
TSharedPtr<FButtonStyle> StyleAccentButton;
|
||||
TSharedPtr<FEditableTextBoxStyle> StyleTextBox;
|
||||
|
||||
void EnsureStyleResolved();
|
||||
void EnsureStyleBrushes();
|
||||
|
||||
// ---- Slate widget refs for dynamic updates ----
|
||||
TSharedPtr<STextBlock> ModeText;
|
||||
TSharedPtr<STextBlock> SelectionInfoText;
|
||||
@@ -144,6 +166,30 @@ private:
|
||||
// Loading screen
|
||||
TSharedPtr<SWidget> LoadingOverlay;
|
||||
|
||||
// HUDs (new design)
|
||||
TSharedPtr<STextBlock> StatsFpsText;
|
||||
TSharedPtr<STextBlock> StatsTriText;
|
||||
TSharedPtr<STextBlock> StatsDrawText;
|
||||
float SmoothedFps = 60.0f;
|
||||
|
||||
TSharedPtr<STextBlock> HintText;
|
||||
TSharedPtr<SWidget> SplineEditBar;
|
||||
|
||||
TSharedPtr<SWidget> ToastPanel;
|
||||
TSharedPtr<STextBlock> ToastTextBlock;
|
||||
FTimerHandle ToastTimerHandle;
|
||||
|
||||
TSharedPtr<SWidget> CmdPalettePanel;
|
||||
TSharedPtr<SEditableTextBox> CmdPaletteInput;
|
||||
TSharedPtr<SVerticalBox> CmdPaletteList;
|
||||
bool bCmdPaletteOpen = false;
|
||||
|
||||
TSharedPtr<STextBlock> TopbarSceneName;
|
||||
TSharedPtr<class SBorder> TopbarStatusDot;
|
||||
TSharedPtr<SWidget> TopbarSimulateButton;
|
||||
TSharedPtr<STextBlock> TopbarSimulateText;
|
||||
TSharedPtr<class SMenuAnchor> FileMenuAnchor;
|
||||
|
||||
public:
|
||||
/** Show/hide the loading overlay. */
|
||||
void ShowLoadingScreen(bool bShow);
|
||||
@@ -155,6 +201,26 @@ public:
|
||||
TSharedRef<SWidget> BuildSceneButtons();
|
||||
TSharedRef<SWidget> BuildOutliner();
|
||||
TSharedRef<SWidget> BuildTimelinePanel();
|
||||
TSharedRef<SWidget> BuildTopbar();
|
||||
TSharedRef<SWidget> BuildFileMenuContent();
|
||||
TSharedRef<SWidget> BuildViewCube();
|
||||
TSharedRef<SWidget> BuildStatsHUD();
|
||||
TSharedRef<SWidget> BuildHintHUD();
|
||||
TSharedRef<SWidget> BuildSplineEditBar();
|
||||
TSharedRef<SWidget> BuildToast();
|
||||
TSharedRef<SWidget> BuildCommandPalette();
|
||||
|
||||
/** Show a transient toast message. Hidden automatically after ~1.8s. */
|
||||
void ShowToast(const FString& Message, FLinearColor Color = FLinearColor::White);
|
||||
|
||||
/** Toggle visibility of the command palette overlay. */
|
||||
void ToggleCommandPalette();
|
||||
void RefreshCommandPaletteList(const FString& Filter);
|
||||
void ExecuteCommand(const FString& CommandId);
|
||||
|
||||
/** Keyboard handler (captures Ctrl+K). */
|
||||
virtual FReply NativeOnKeyDown(const FGeometry& InGeometry, const FKeyEvent& InKeyEvent) override;
|
||||
virtual bool NativeSupportsKeyboardFocus() const override { return true; }
|
||||
void RefreshOutliner();
|
||||
void PopulateSceneList();
|
||||
void PopulateSpawnCatalog();
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,183 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Blueprint/UserWidget.h"
|
||||
#include "PS_Editor_SelectionTypes.h"
|
||||
#include "PS_Editor_MainWidget_Legacy.generated.h"
|
||||
|
||||
class UPS_Editor_SelectionManager;
|
||||
class UPS_Editor_SpawnManager;
|
||||
class UPS_Editor_SceneSerializer;
|
||||
|
||||
/** Widget state for a single dynamic property row in the property panel. */
|
||||
struct FPS_Editor_PropertyRowWidgetsLegacy
|
||||
{
|
||||
FString ComponentName;
|
||||
FString PropertyName;
|
||||
FString DisplayName;
|
||||
FString Key; // "ComponentName.PropertyName"
|
||||
FProperty* CachedProperty = nullptr;
|
||||
|
||||
// Widget refs (only relevant ones are used based on property type)
|
||||
TSharedPtr<SEditableTextBox> SingleField; // float, double, int32, FString
|
||||
TSharedPtr<SCheckBox> BoolField; // bool
|
||||
TSharedPtr<SEditableTextBox> VecX, VecY, VecZ; // FVector, FRotator
|
||||
TSharedPtr<SEditableTextBox> ColR, ColG, ColB, ColA; // FLinearColor
|
||||
|
||||
// Enum support
|
||||
TSharedPtr<SComboBox<TSharedPtr<FString>>> EnumCombo;
|
||||
TSharedPtr<STextBlock> EnumComboText;
|
||||
TSharedPtr<TArray<TSharedPtr<FString>>> EnumOptions; // Heap-allocated so OptionsSource pointer survives Row copy
|
||||
UEnum* CachedEnum = nullptr;
|
||||
};
|
||||
|
||||
/**
|
||||
* Main editor widget for PS_Editor.
|
||||
* Contains: title bar, transform mode toolbar, properties panel, help bar.
|
||||
* Built entirely in C++ using Slate.
|
||||
*/
|
||||
UCLASS()
|
||||
class PS_EDITOR_API UPS_Editor_MainWidget_Legacy : public UUserWidget
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/** Set manager references. Must be called after construction. */
|
||||
void SetSelectionManager(UPS_Editor_SelectionManager* InSelectionManager);
|
||||
void SetSpawnManager(UPS_Editor_SpawnManager* InSpawnManager);
|
||||
void SetSceneSerializer(UPS_Editor_SceneSerializer* InSerializer);
|
||||
|
||||
protected:
|
||||
virtual TSharedRef<SWidget> RebuildWidget() override;
|
||||
virtual void NativeConstruct() override;
|
||||
virtual void NativeDestruct() override;
|
||||
virtual void NativeTick(const FGeometry& MyGeometry, float InDeltaTime) override;
|
||||
|
||||
private:
|
||||
UPROPERTY()
|
||||
TWeakObjectPtr<UPS_Editor_SelectionManager> SelectionManager;
|
||||
|
||||
UPROPERTY()
|
||||
TWeakObjectPtr<UPS_Editor_SpawnManager> SpawnManager;
|
||||
|
||||
UPROPERTY()
|
||||
TWeakObjectPtr<UPS_Editor_SceneSerializer> SceneSerializer;
|
||||
|
||||
// ---- Slate widget refs for dynamic updates ----
|
||||
TSharedPtr<STextBlock> ModeText;
|
||||
TSharedPtr<STextBlock> SelectionInfoText;
|
||||
|
||||
// Toolbar refs
|
||||
TSharedPtr<STextBlock> SpaceText;
|
||||
TSharedPtr<STextBlock> SnapText;
|
||||
TSharedPtr<SEditableTextBox> SnapSizeField;
|
||||
|
||||
// Spawn catalog
|
||||
TSharedPtr<SVerticalBox> SpawnListContainer;
|
||||
|
||||
// Scene save/load
|
||||
TSharedPtr<SEditableTextBox> SaveNameField;
|
||||
TSharedPtr<SVerticalBox> SceneListContainer;
|
||||
TArray<TSharedPtr<FString>> BaseLevelOptions;
|
||||
TSharedPtr<STextBlock> BaseLevelButtonText;
|
||||
FString CurrentLevelFilter;
|
||||
/** Maps DisplayName → full MapPath for base levels (from MasterCatalog). */
|
||||
TMap<FString, FString> BaseLevelPathMap;
|
||||
/** Maps DisplayName → additional sublevels to load (from MasterCatalog). */
|
||||
TMap<FString, TArray<FString>> BaseLevelExtraSublevels;
|
||||
/** Maps DisplayName → Thumbnail texture for base levels. */
|
||||
TMap<FString, TObjectPtr<UTexture2D>> BaseLevelThumbnails;
|
||||
TArray<TSharedPtr<FSlateBrush>> CachedBrushes;
|
||||
|
||||
// BaseLevel selection popup
|
||||
TSharedPtr<SWidget> BaseLevelPopup;
|
||||
TSharedPtr<SVerticalBox> BaseLevelGridContainer;
|
||||
FString PendingBaseLevelSelection;
|
||||
TMap<FString, TSharedPtr<SBorder>> BaseLevelCardBorders;
|
||||
void OpenBaseLevelPopup();
|
||||
void UpdateBaseLevelHighlight();
|
||||
void ConfirmBaseLevelSelection();
|
||||
void CancelBaseLevelPopup();
|
||||
|
||||
// Transform fields
|
||||
TSharedPtr<SEditableTextBox> LocX, LocY, LocZ;
|
||||
TSharedPtr<SEditableTextBox> RotX, RotY, RotZ;
|
||||
TSharedPtr<SEditableTextBox> ScaX, ScaY, ScaZ;
|
||||
|
||||
// Dynamic property editing
|
||||
TSharedPtr<SVerticalBox> DynamicPropertiesContainer;
|
||||
TArray<FPS_Editor_PropertyRowWidgetsLegacy> DynamicPropertyRows;
|
||||
TWeakObjectPtr<AActor> LastSelectedActor;
|
||||
|
||||
// Outliner
|
||||
TSharedPtr<SVerticalBox> OutlinerListContainer;
|
||||
int32 LastOutlinerActorCount = -1;
|
||||
|
||||
// Bottom notification bar
|
||||
TSharedPtr<STextBlock> HelpBarText;
|
||||
TSharedPtr<SWidget> SplineFinishButton;
|
||||
TSharedPtr<SWidget> SplineEditButton;
|
||||
TSharedPtr<SWidget> SplineDeletePointButton;
|
||||
TSharedPtr<SWidget> ChildEditButton;
|
||||
|
||||
// Simulate button
|
||||
TSharedPtr<STextBlock> SimulateButtonText;
|
||||
|
||||
// Timeline panel
|
||||
TSharedPtr<SWidget> TimelinePanel;
|
||||
TSharedPtr<class SSlider> TimelineSlider;
|
||||
TSharedPtr<STextBlock> TimelineTimeLabel;
|
||||
TSharedPtr<STextBlock> TimelinePlayButtonText;
|
||||
TSharedPtr<SEditableTextBox> TimelineDurationBox;
|
||||
TSharedPtr<SVerticalBox> TimelineTracksContainer;
|
||||
bool bTimelineVisible = false;
|
||||
FDelegateHandle TimelineChangedHandle;
|
||||
/** Currently selected timeline key (updated on click). -1 means no selection. */
|
||||
int32 SelectedTimelineTrackIdx = -1;
|
||||
int32 SelectedTimelineKeyIdx = -1;
|
||||
|
||||
// Close confirmation
|
||||
TSharedPtr<SWidget> CloseConfirmOverlay;
|
||||
bool bSceneDirty = false;
|
||||
int32 LastKnownSpawnedCount = 0;
|
||||
|
||||
// Loading screen
|
||||
TSharedPtr<SWidget> LoadingOverlay;
|
||||
|
||||
public:
|
||||
/** Show/hide the loading overlay. */
|
||||
void ShowLoadingScreen(bool bShow);
|
||||
|
||||
// ---- Helpers ----
|
||||
TSharedRef<SWidget> BuildToolbar();
|
||||
TSharedRef<SWidget> BuildPropertiesPanel();
|
||||
TSharedRef<SWidget> BuildSpawnCatalog();
|
||||
TSharedRef<SWidget> BuildSceneButtons();
|
||||
TSharedRef<SWidget> BuildOutliner();
|
||||
TSharedRef<SWidget> BuildTimelinePanel();
|
||||
void RefreshOutliner();
|
||||
void PopulateSceneList();
|
||||
void PopulateSpawnCatalog();
|
||||
void RefreshPropertiesFromSelection();
|
||||
void ApplyTransformFromUI();
|
||||
void RebuildDynamicProperties();
|
||||
void RefreshDynamicPropertyValues();
|
||||
void ApplyPropertyFromUI(int32 RowIndex);
|
||||
void AddKeyForRow(int32 RowIndex);
|
||||
void ToggleTimelineVisible();
|
||||
void RebuildTimelineTracksList();
|
||||
void RefreshTimelineUI();
|
||||
|
||||
/** Try to delete the currently selected timeline key. Returns true if one was deleted.
|
||||
* Called by the Pawn's global Delete action before it falls back to actor deletion. */
|
||||
bool TryDeleteSelectedTimelineKey();
|
||||
static UObject* FindPropertyTarget(AActor* Actor, const FString& ComponentName);
|
||||
FString FloatToString(float Value) const;
|
||||
|
||||
void OnModeButtonClicked(EPS_Editor_TransformMode Mode);
|
||||
void OnCloseClicked();
|
||||
void ExecuteClose(bool bSave);
|
||||
|
||||
/** Whether we are currently refreshing UI from selection (avoid feedback loop). */
|
||||
bool bIsRefreshing = false;
|
||||
};
|
||||
@@ -0,0 +1,237 @@
|
||||
#include "PS_Editor_MainWidget_UMG.h"
|
||||
|
||||
#include "PS_Editor_SelectionManager.h"
|
||||
#include "PS_Editor_SpawnManager.h"
|
||||
#include "PS_Editor_SceneSerializer.h"
|
||||
#include "PS_Editor_PlayerController.h"
|
||||
#include "GameFramework/PlayerController.h"
|
||||
|
||||
void UPS_Editor_MainWidget_UMG::SetSelectionManager(UPS_Editor_SelectionManager* InSelectionManager)
|
||||
{
|
||||
SelectionManager = InSelectionManager;
|
||||
}
|
||||
|
||||
void UPS_Editor_MainWidget_UMG::SetSpawnManager(UPS_Editor_SpawnManager* InSpawnManager)
|
||||
{
|
||||
SpawnManager = InSpawnManager;
|
||||
}
|
||||
|
||||
void UPS_Editor_MainWidget_UMG::SetSceneSerializer(UPS_Editor_SceneSerializer* InSerializer)
|
||||
{
|
||||
SceneSerializer = InSerializer;
|
||||
}
|
||||
|
||||
void UPS_Editor_MainWidget_UMG::NativeConstruct()
|
||||
{
|
||||
Super::NativeConstruct();
|
||||
|
||||
if (UPS_Editor_SelectionManager* SM = SelectionManager.Get())
|
||||
{
|
||||
SelectionChangedHandle = SM->OnSelectionChanged.AddLambda([this]()
|
||||
{
|
||||
OnSelectionChanged.Broadcast();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void UPS_Editor_MainWidget_UMG::NativeDestruct()
|
||||
{
|
||||
if (SelectionChangedHandle.IsValid())
|
||||
{
|
||||
if (UPS_Editor_SelectionManager* SM = SelectionManager.Get())
|
||||
{
|
||||
SM->OnSelectionChanged.Remove(SelectionChangedHandle);
|
||||
}
|
||||
SelectionChangedHandle.Reset();
|
||||
}
|
||||
Super::NativeDestruct();
|
||||
}
|
||||
|
||||
// -------------------- Actions --------------------
|
||||
|
||||
void UPS_Editor_MainWidget_UMG::SetTransformMode(EPS_Editor_TransformMode Mode)
|
||||
{
|
||||
if (UPS_Editor_SelectionManager* SM = SelectionManager.Get())
|
||||
{
|
||||
SM->SetTransformMode(Mode);
|
||||
OnToolChanged.Broadcast();
|
||||
}
|
||||
}
|
||||
|
||||
void UPS_Editor_MainWidget_UMG::ToggleTransformSpace()
|
||||
{
|
||||
if (UPS_Editor_SelectionManager* SM = SelectionManager.Get())
|
||||
{
|
||||
SM->ToggleTransformSpace();
|
||||
OnToolChanged.Broadcast();
|
||||
}
|
||||
}
|
||||
|
||||
void UPS_Editor_MainWidget_UMG::ToggleSnap()
|
||||
{
|
||||
if (UPS_Editor_SelectionManager* SM = SelectionManager.Get())
|
||||
{
|
||||
SM->bSnapTranslation = !SM->bSnapTranslation;
|
||||
SM->bSnapRotation = SM->bSnapTranslation;
|
||||
SM->bSnapScale = SM->bSnapTranslation;
|
||||
OnToolChanged.Broadcast();
|
||||
}
|
||||
}
|
||||
|
||||
void UPS_Editor_MainWidget_UMG::SnapSelectionToGround()
|
||||
{
|
||||
if (UPS_Editor_SelectionManager* SM = SelectionManager.Get())
|
||||
{
|
||||
SM->SnapSelectionToGround();
|
||||
}
|
||||
}
|
||||
|
||||
void UPS_Editor_MainWidget_UMG::StartSimulation()
|
||||
{
|
||||
if (APS_Editor_PlayerController* PC = Cast<APS_Editor_PlayerController>(GetOwningPlayer()))
|
||||
{
|
||||
PC->StartSimulation();
|
||||
OnSimulateChanged.Broadcast();
|
||||
}
|
||||
}
|
||||
|
||||
void UPS_Editor_MainWidget_UMG::StopSimulation()
|
||||
{
|
||||
if (APS_Editor_PlayerController* PC = Cast<APS_Editor_PlayerController>(GetOwningPlayer()))
|
||||
{
|
||||
PC->StopSimulation();
|
||||
OnSimulateChanged.Broadcast();
|
||||
}
|
||||
}
|
||||
|
||||
bool UPS_Editor_MainWidget_UMG::SaveScenario(const FString& Name)
|
||||
{
|
||||
UPS_Editor_SpawnManager* SM = nullptr;
|
||||
if (APS_Editor_PlayerController* PC = Cast<APS_Editor_PlayerController>(GetOwningPlayer()))
|
||||
{
|
||||
SM = PC->GetSpawnManager();
|
||||
}
|
||||
if (!SM) SM = SpawnManager.Get();
|
||||
UPS_Editor_SceneSerializer* Ser = SceneSerializer.Get();
|
||||
if (!Ser || !SM || Name.IsEmpty()) return false;
|
||||
Ser->SaveScene(Name, SM);
|
||||
OnSceneDirty.Broadcast();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UPS_Editor_MainWidget_UMG::LoadScenario(const FString& Name)
|
||||
{
|
||||
UPS_Editor_SceneSerializer* Ser = SceneSerializer.Get();
|
||||
UPS_Editor_SpawnManager* SM = nullptr;
|
||||
if (APS_Editor_PlayerController* PC = Cast<APS_Editor_PlayerController>(GetOwningPlayer()))
|
||||
{
|
||||
SM = PC->GetSpawnManager();
|
||||
}
|
||||
if (!SM) SM = SpawnManager.Get();
|
||||
if (!Ser || !SM || Name.IsEmpty()) return false;
|
||||
Ser->LoadScene(Name, SM);
|
||||
OnSceneDirty.Broadcast();
|
||||
return true;
|
||||
}
|
||||
|
||||
void UPS_Editor_MainWidget_UMG::RequestClose()
|
||||
{
|
||||
if (APS_Editor_PlayerController* PC = Cast<APS_Editor_PlayerController>(GetOwningPlayer()))
|
||||
{
|
||||
PC->CloseEditor();
|
||||
}
|
||||
}
|
||||
|
||||
void UPS_Editor_MainWidget_UMG::SelectActorByPtr(AActor* Actor)
|
||||
{
|
||||
if (!Actor) return;
|
||||
if (UPS_Editor_SelectionManager* SM = SelectionManager.Get())
|
||||
{
|
||||
SM->ClearSelection();
|
||||
SM->SelectActor(Actor);
|
||||
}
|
||||
}
|
||||
|
||||
void UPS_Editor_MainWidget_UMG::ClearSelection()
|
||||
{
|
||||
if (UPS_Editor_SelectionManager* SM = SelectionManager.Get())
|
||||
{
|
||||
SM->ClearSelection();
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------- Getters --------------------
|
||||
|
||||
EPS_Editor_TransformMode UPS_Editor_MainWidget_UMG::GetCurrentTransformMode() const
|
||||
{
|
||||
if (UPS_Editor_SelectionManager* SM = SelectionManager.Get()) return SM->GetTransformMode();
|
||||
return EPS_Editor_TransformMode::Translate;
|
||||
}
|
||||
|
||||
bool UPS_Editor_MainWidget_UMG::IsSnapEnabled() const
|
||||
{
|
||||
if (UPS_Editor_SelectionManager* SM = SelectionManager.Get()) return SM->bSnapTranslation;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UPS_Editor_MainWidget_UMG::IsWorldSpace() const
|
||||
{
|
||||
if (UPS_Editor_SelectionManager* SM = SelectionManager.Get()) return SM->GetTransformSpace() == EPS_Editor_Space::World;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UPS_Editor_MainWidget_UMG::IsSimulating() const
|
||||
{
|
||||
if (APS_Editor_PlayerController* PC = Cast<APS_Editor_PlayerController>(GetOwningPlayer())) return PC->IsSimulating();
|
||||
return false;
|
||||
}
|
||||
|
||||
TArray<AActor*> UPS_Editor_MainWidget_UMG::GetSpawnedActors() const
|
||||
{
|
||||
TArray<AActor*> Result;
|
||||
UPS_Editor_SpawnManager* SM = nullptr;
|
||||
if (APS_Editor_PlayerController* PC = Cast<APS_Editor_PlayerController>(GetOwningPlayer()))
|
||||
{
|
||||
SM = PC->GetSpawnManager();
|
||||
}
|
||||
if (!SM) SM = SpawnManager.Get();
|
||||
if (SM)
|
||||
{
|
||||
for (const TWeakObjectPtr<AActor>& Weak : SM->GetSpawnedActors())
|
||||
{
|
||||
if (AActor* A = Weak.Get()) Result.Add(A);
|
||||
}
|
||||
}
|
||||
return Result;
|
||||
}
|
||||
|
||||
TArray<FString> UPS_Editor_MainWidget_UMG::GetSavedScenarioNames() const
|
||||
{
|
||||
if (UPS_Editor_SceneSerializer* Ser = SceneSerializer.Get()) return Ser->GetSavedSceneNames();
|
||||
return {};
|
||||
}
|
||||
|
||||
AActor* UPS_Editor_MainWidget_UMG::GetFirstSelectedActor() const
|
||||
{
|
||||
if (UPS_Editor_SelectionManager* SM = SelectionManager.Get())
|
||||
{
|
||||
if (SM->IsAnythingSelected())
|
||||
{
|
||||
return SM->GetSelectedActors()[0].Get();
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
FString UPS_Editor_MainWidget_UMG::GetCurrentScenarioName() const
|
||||
{
|
||||
if (APS_Editor_PlayerController* PC = Cast<APS_Editor_PlayerController>(GetOwningPlayer()))
|
||||
{
|
||||
if (UGameInstance* GI = PC->GetGameInstance())
|
||||
{
|
||||
// Name is kept on the subsystem — look it up via header chain if available.
|
||||
// Fallback to empty string.
|
||||
}
|
||||
}
|
||||
return FString();
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Blueprint/UserWidget.h"
|
||||
#include "PS_Editor_SelectionTypes.h"
|
||||
#include "PS_Editor_MainWidget_UMG.generated.h"
|
||||
|
||||
class UPS_Editor_SelectionManager;
|
||||
class UPS_Editor_SpawnManager;
|
||||
class UPS_Editor_SceneSerializer;
|
||||
|
||||
/**
|
||||
* UMG-based PS_Editor main widget. This class is intentionally minimal in C++ —
|
||||
* the full UI is authored as Widget Blueprints that derive from this class, and
|
||||
* call into the Blueprint-exposed API below to drive the editor.
|
||||
*
|
||||
* Workflow:
|
||||
* 1. Create a Widget Blueprint "WBP_PSEditor_UMG" that inherits from
|
||||
* UPS_Editor_MainWidget_UMG.
|
||||
* 2. Design the visual layout (floating HUDs, panels, etc.) in the UMG designer.
|
||||
* 3. In event graphs, call the BlueprintCallable functions (SetTool, ToggleSimulate,
|
||||
* SaveScenario, etc.) from button click events.
|
||||
* 4. Subscribe to the BlueprintAssignable delegates (OnToolChanged, OnSelectionChanged…)
|
||||
* to refresh visual state.
|
||||
* 5. Switch APS_Editor_HUD::UIMode to EPS_Editor_UIMode::UMG and set UMGWidgetClass
|
||||
* to WBP_PSEditor_UMG.
|
||||
*/
|
||||
UCLASS(Abstract, Blueprintable)
|
||||
class PS_EDITOR_API UPS_Editor_MainWidget_UMG : public UUserWidget
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/** Manager refs — set by the HUD after construction. */
|
||||
void SetSelectionManager(UPS_Editor_SelectionManager* InSelectionManager);
|
||||
void SetSpawnManager(UPS_Editor_SpawnManager* InSpawnManager);
|
||||
void SetSceneSerializer(UPS_Editor_SceneSerializer* InSerializer);
|
||||
|
||||
// ==================== BP-CALLABLE ACTIONS ====================
|
||||
UFUNCTION(BlueprintCallable, Category = "PS Editor|Tools")
|
||||
void SetTransformMode(EPS_Editor_TransformMode Mode);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "PS Editor|Tools")
|
||||
void ToggleTransformSpace();
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "PS Editor|Tools")
|
||||
void ToggleSnap();
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "PS Editor|Tools")
|
||||
void SnapSelectionToGround();
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "PS Editor|Simulation")
|
||||
void StartSimulation();
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "PS Editor|Simulation")
|
||||
void StopSimulation();
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "PS Editor|Scene")
|
||||
bool SaveScenario(const FString& Name);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "PS Editor|Scene")
|
||||
bool LoadScenario(const FString& Name);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "PS Editor|Scene")
|
||||
void RequestClose();
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "PS Editor|Selection")
|
||||
void SelectActorByPtr(AActor* Actor);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "PS Editor|Selection")
|
||||
void ClearSelection();
|
||||
|
||||
// ==================== BP-PURE GETTERS ====================
|
||||
UFUNCTION(BlueprintPure, Category = "PS Editor|Tools")
|
||||
EPS_Editor_TransformMode GetCurrentTransformMode() const;
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "PS Editor|Tools")
|
||||
bool IsSnapEnabled() const;
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "PS Editor|Tools")
|
||||
bool IsWorldSpace() const;
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "PS Editor|Simulation")
|
||||
bool IsSimulating() const;
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "PS Editor|Scene")
|
||||
TArray<AActor*> GetSpawnedActors() const;
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "PS Editor|Scene")
|
||||
TArray<FString> GetSavedScenarioNames() const;
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "PS Editor|Selection")
|
||||
AActor* GetFirstSelectedActor() const;
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "PS Editor|Scene")
|
||||
FString GetCurrentScenarioName() const;
|
||||
|
||||
// ==================== EVENTS (BP delegates) ====================
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnPSE_ToolChanged);
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnPSE_SelectionChanged);
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnPSE_SimulateChanged);
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnPSE_SceneDirty);
|
||||
|
||||
UPROPERTY(BlueprintAssignable, Category = "PS Editor|Events")
|
||||
FOnPSE_ToolChanged OnToolChanged;
|
||||
|
||||
UPROPERTY(BlueprintAssignable, Category = "PS Editor|Events")
|
||||
FOnPSE_SelectionChanged OnSelectionChanged;
|
||||
|
||||
UPROPERTY(BlueprintAssignable, Category = "PS Editor|Events")
|
||||
FOnPSE_SimulateChanged OnSimulateChanged;
|
||||
|
||||
UPROPERTY(BlueprintAssignable, Category = "PS Editor|Events")
|
||||
FOnPSE_SceneDirty OnSceneDirty;
|
||||
|
||||
protected:
|
||||
virtual void NativeConstruct() override;
|
||||
virtual void NativeDestruct() override;
|
||||
|
||||
UPROPERTY()
|
||||
TWeakObjectPtr<UPS_Editor_SelectionManager> SelectionManager;
|
||||
|
||||
UPROPERTY()
|
||||
TWeakObjectPtr<UPS_Editor_SpawnManager> SpawnManager;
|
||||
|
||||
UPROPERTY()
|
||||
TWeakObjectPtr<UPS_Editor_SceneSerializer> SceneSerializer;
|
||||
|
||||
FDelegateHandle SelectionChangedHandle;
|
||||
};
|
||||
Reference in New Issue
Block a user