Additional sublevels, custom property options, BaseLevel highlight, lightmap fix
- Add AdditionalSublevels to FPS_Editor_BaseLevelEntry for always-loaded streaming levels that aren't auto-loaded when base level is a sublevel - Add LoadBaseLevelAsSublevel overload with ExtraSublevels parameter - Add GetCustomOptionsForProperty to IPS_Editor_EditableInterface for data-driven dropdowns (e.g. weapon lists from DataTables) - Add custom options combo in property panel (interface-driven, no CachedEnum) - Add BaseLevel selection highlight: selected card stays highlighted until validate or change - Fix deferred lightmap refresh with 0.5s timer instead of NextTick - Fix enum detection scope error in RebuildDynamicProperties Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -75,17 +75,19 @@ void APS_Editor_PlayerController::BeginPlay()
|
|||||||
UE_LOG(LogTemp, Log, TEXT("PS_Editor: Restoring from subsystem - BaseLevel='%s', Scenario='%s'"),
|
UE_LOG(LogTemp, Log, TEXT("PS_Editor: Restoring from subsystem - BaseLevel='%s', Scenario='%s'"),
|
||||||
*Sub->PendingBaseLevel, *Sub->PendingScenarioName);
|
*Sub->PendingBaseLevel, *Sub->PendingScenarioName);
|
||||||
|
|
||||||
// Resolve full map path from MasterCatalog
|
// Resolve full map path and extra sublevels from MasterCatalog
|
||||||
FString ResolvedMapPath;
|
FString ResolvedMapPath;
|
||||||
|
TArray<FString> ResolvedExtras;
|
||||||
for (const FPS_Editor_BaseLevelEntry& Entry : Master->BaseLevels)
|
for (const FPS_Editor_BaseLevelEntry& Entry : Master->BaseLevels)
|
||||||
{
|
{
|
||||||
if (Entry.DisplayName == Sub->PendingBaseLevel)
|
if (Entry.DisplayName == Sub->PendingBaseLevel)
|
||||||
{
|
{
|
||||||
ResolvedMapPath = Entry.MapPath;
|
ResolvedMapPath = Entry.MapPath;
|
||||||
|
ResolvedExtras = Entry.AdditionalSublevels;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
LoadBaseLevelAsSublevel(Sub->PendingBaseLevel, ResolvedMapPath);
|
LoadBaseLevelAsSublevel(Sub->PendingBaseLevel, ResolvedMapPath, ResolvedExtras);
|
||||||
|
|
||||||
if (!Sub->PendingScenarioName.IsEmpty() && SpawnManager && SceneSerializer)
|
if (!Sub->PendingScenarioName.IsEmpty() && SpawnManager && SceneSerializer)
|
||||||
{
|
{
|
||||||
@@ -381,6 +383,11 @@ void APS_Editor_PlayerController::ShowLoadingScreen(bool bShow)
|
|||||||
}
|
}
|
||||||
|
|
||||||
void APS_Editor_PlayerController::LoadBaseLevelAsSublevel(const FString& LevelName, const FString& MapPath)
|
void APS_Editor_PlayerController::LoadBaseLevelAsSublevel(const FString& LevelName, const FString& MapPath)
|
||||||
|
{
|
||||||
|
LoadBaseLevelAsSublevel(LevelName, MapPath, TArray<FString>());
|
||||||
|
}
|
||||||
|
|
||||||
|
void APS_Editor_PlayerController::LoadBaseLevelAsSublevel(const FString& LevelName, const FString& MapPath, const TArray<FString>& ExtraSublevels)
|
||||||
{
|
{
|
||||||
// Unload previous sublevel first
|
// Unload previous sublevel first
|
||||||
if (CurrentSublevel)
|
if (CurrentSublevel)
|
||||||
@@ -392,6 +399,18 @@ void APS_Editor_PlayerController::LoadBaseLevelAsSublevel(const FString& LevelNa
|
|||||||
UE_LOG(LogTemp, Log, TEXT("PS_Editor: Unloaded previous sublevel"));
|
UE_LOG(LogTemp, Log, TEXT("PS_Editor: Unloaded previous sublevel"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Unload additional sublevels from previous base level
|
||||||
|
for (TObjectPtr<ULevelStreamingDynamic>& Extra : AdditionalSublevels)
|
||||||
|
{
|
||||||
|
if (Extra)
|
||||||
|
{
|
||||||
|
Extra->SetShouldBeLoaded(false);
|
||||||
|
Extra->SetShouldBeVisible(false);
|
||||||
|
Extra->SetIsRequestingUnloadAndRemoval(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AdditionalSublevels.Empty();
|
||||||
|
|
||||||
// Hide/show persistent level scene actors (lights, meshes, post-process, etc.)
|
// Hide/show persistent level scene actors (lights, meshes, post-process, etc.)
|
||||||
// Exclude PS_Editor system actors (pawn, controller, gizmo, gamemode, spawned objects)
|
// Exclude PS_Editor system actors (pawn, controller, gizmo, gamemode, spawned objects)
|
||||||
if (ULevel* PersistentLevel = GetWorld()->PersistentLevel)
|
if (ULevel* PersistentLevel = GetWorld()->PersistentLevel)
|
||||||
@@ -456,6 +475,32 @@ void APS_Editor_PlayerController::LoadBaseLevelAsSublevel(const FString& LevelNa
|
|||||||
CurrentSublevel->OnLevelShown.AddUniqueDynamic(this, &APS_Editor_PlayerController::DisableSublevelPostProcessMaterials);
|
CurrentSublevel->OnLevelShown.AddUniqueDynamic(this, &APS_Editor_PlayerController::DisableSublevelPostProcessMaterials);
|
||||||
|
|
||||||
UE_LOG(LogTemp, Log, TEXT("PS_Editor: Loaded sublevel '%s' (path: '%s')"), *LevelName, *LevelToLoad);
|
UE_LOG(LogTemp, Log, TEXT("PS_Editor: Loaded sublevel '%s' (path: '%s')"), *LevelName, *LevelToLoad);
|
||||||
|
|
||||||
|
// Load additional sublevels (always-loaded streaming levels)
|
||||||
|
for (const FString& ExtraPath : ExtraSublevels)
|
||||||
|
{
|
||||||
|
if (ExtraPath.IsEmpty()) continue;
|
||||||
|
|
||||||
|
bool bExtraSuccess = false;
|
||||||
|
ULevelStreamingDynamic* ExtraSublevel = ULevelStreamingDynamic::LoadLevelInstance(
|
||||||
|
GetWorld(),
|
||||||
|
ExtraPath,
|
||||||
|
FVector::ZeroVector,
|
||||||
|
FRotator::ZeroRotator,
|
||||||
|
bExtraSuccess);
|
||||||
|
|
||||||
|
if (bExtraSuccess && ExtraSublevel)
|
||||||
|
{
|
||||||
|
ExtraSublevel->SetShouldBeLoaded(true);
|
||||||
|
ExtraSublevel->SetShouldBeVisible(true);
|
||||||
|
AdditionalSublevels.Add(ExtraSublevel);
|
||||||
|
UE_LOG(LogTemp, Log, TEXT("PS_Editor: Loaded additional sublevel '%s'"), *ExtraPath);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
UE_LOG(LogTemp, Warning, TEXT("PS_Editor: Failed to load additional sublevel '%s'"), *ExtraPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -470,8 +515,24 @@ void APS_Editor_PlayerController::DisableSublevelPostProcessMaterials()
|
|||||||
ULevel* Level = CurrentSublevel->GetLoadedLevel();
|
ULevel* Level = CurrentSublevel->GetLoadedLevel();
|
||||||
if (!Level) return;
|
if (!Level) return;
|
||||||
|
|
||||||
// Force lighting/rendering resources to initialize properly for baked lightmaps
|
// Deferred lightmap refresh: MapBuildData may be loaded async and not ready yet
|
||||||
Level->InitializeRenderingResources();
|
TWeakObjectPtr<ULevelStreamingDynamic> WeakSublevel = CurrentSublevel;
|
||||||
|
FTimerHandle LightmapTimer;
|
||||||
|
GetWorldTimerManager().SetTimer(LightmapTimer, [WeakSublevel]()
|
||||||
|
{
|
||||||
|
if (!WeakSublevel.IsValid()) return;
|
||||||
|
ULevel* DelayedLevel = WeakSublevel->GetLoadedLevel();
|
||||||
|
if (!DelayedLevel) return;
|
||||||
|
|
||||||
|
for (AActor* Actor : DelayedLevel->Actors)
|
||||||
|
{
|
||||||
|
if (Actor)
|
||||||
|
{
|
||||||
|
Actor->MarkComponentsRenderStateDirty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
UE_LOG(LogTemp, Log, TEXT("PS_Editor: Deferred lightmap refresh on sublevel"));
|
||||||
|
}, 0.5f, false);
|
||||||
|
|
||||||
APS_Editor_CameraStart* CameraStart = nullptr;
|
APS_Editor_CameraStart* CameraStart = nullptr;
|
||||||
|
|
||||||
|
|||||||
@@ -75,11 +75,15 @@ public:
|
|||||||
bool IsSimulating() const { return bSimulating; }
|
bool IsSimulating() const { return bSimulating; }
|
||||||
|
|
||||||
/** Load a base level as sublevel for visual context. Unloads previous sublevel.
|
/** Load a base level as sublevel for visual context. Unloads previous sublevel.
|
||||||
* @param LevelName Display name stored in CurrentBaseLevel (used for scene filtering).
|
* @param LevelName Display name stored in CurrentBaseLevel (used for scene filtering).
|
||||||
* @param MapPath Full package path for LoadLevelInstance (e.g. "/Game/Maps/Warehouse").
|
* @param MapPath Full package path for LoadLevelInstance (e.g. "/Game/Maps/Warehouse").
|
||||||
* If empty, LevelName is used as-is (legacy behavior).
|
* If empty, LevelName is used as-is (legacy behavior).
|
||||||
|
* @param ExtraSublevels Additional sublevels to load alongside (e.g. always-loaded streaming levels).
|
||||||
*/
|
*/
|
||||||
UFUNCTION(BlueprintCallable, Category = "PS Editor")
|
UFUNCTION(BlueprintCallable, Category = "PS Editor")
|
||||||
|
void LoadBaseLevelAsSublevel(const FString& LevelName, const FString& MapPath, const TArray<FString>& ExtraSublevels);
|
||||||
|
|
||||||
|
/** Overload without extra sublevels. */
|
||||||
void LoadBaseLevelAsSublevel(const FString& LevelName, const FString& MapPath = TEXT(""));
|
void LoadBaseLevelAsSublevel(const FString& LevelName, const FString& MapPath = TEXT(""));
|
||||||
|
|
||||||
// ---- Editor Mode (Normal / SplinePlacement) ----
|
// ---- Editor Mode (Normal / SplinePlacement) ----
|
||||||
@@ -157,6 +161,10 @@ private:
|
|||||||
UPROPERTY()
|
UPROPERTY()
|
||||||
TObjectPtr<ULevelStreamingDynamic> CurrentSublevel;
|
TObjectPtr<ULevelStreamingDynamic> CurrentSublevel;
|
||||||
|
|
||||||
|
/** Additional sublevels loaded alongside the base level. */
|
||||||
|
UPROPERTY()
|
||||||
|
TArray<TObjectPtr<ULevelStreamingDynamic>> AdditionalSublevels;
|
||||||
|
|
||||||
/** True when a sublevel is loading and needs fade-in when ready. */
|
/** True when a sublevel is loading and needs fade-in when ready. */
|
||||||
bool bWaitingForSublevelFadeIn = false;
|
bool bWaitingForSublevelFadeIn = false;
|
||||||
|
|
||||||
|
|||||||
@@ -67,4 +67,14 @@ public:
|
|||||||
*/
|
*/
|
||||||
UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "PS Editor")
|
UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "PS Editor")
|
||||||
void OnEditorTick(float DeltaTime);
|
void OnEditorTick(float DeltaTime);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return custom dropdown options for a given property.
|
||||||
|
* If the returned array is non-empty, the editor shows a combo box with these options
|
||||||
|
* instead of the default widget for that property type.
|
||||||
|
* @param PropertyPath The property path (e.g. "WeaponName" or "MyComponent.WeaponType").
|
||||||
|
* @return List of option strings. Empty = use default widget.
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "PS Editor")
|
||||||
|
TArray<FString> GetCustomOptionsForProperty(const FString& PropertyPath) const;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -68,6 +68,15 @@ struct FPS_Editor_BaseLevelEntry
|
|||||||
/** Thumbnail image shown in the BaseLevel selection popup. */
|
/** Thumbnail image shown in the BaseLevel selection popup. */
|
||||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS Editor")
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS Editor")
|
||||||
TObjectPtr<UTexture2D> Thumbnail;
|
TObjectPtr<UTexture2D> Thumbnail;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Additional sublevels to load alongside this base level in the editor.
|
||||||
|
* Used for streaming levels that are "always loaded" in the persistent level
|
||||||
|
* but not automatically loaded when the base level is a sublevel itself.
|
||||||
|
* Full map paths, e.g. "/Game/Maps/Warehouse_Lighting"
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS Editor")
|
||||||
|
TArray<FString> AdditionalSublevels;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -960,7 +960,12 @@ TSharedRef<SWidget> UPS_Editor_MainWidget::BuildSceneButtons()
|
|||||||
{
|
{
|
||||||
SceneMapPath = *Found;
|
SceneMapPath = *Found;
|
||||||
}
|
}
|
||||||
EditorPC->LoadBaseLevelAsSublevel(SceneLevelName, SceneMapPath);
|
TArray<FString> SceneExtras;
|
||||||
|
if (TArray<FString>* FoundExtras = BaseLevelExtraSublevels.Find(SceneLevelName))
|
||||||
|
{
|
||||||
|
SceneExtras = *FoundExtras;
|
||||||
|
}
|
||||||
|
EditorPC->LoadBaseLevelAsSublevel(SceneLevelName, SceneMapPath, SceneExtras);
|
||||||
CurrentLevelFilter = EditorPC->CurrentBaseLevel;
|
CurrentLevelFilter = EditorPC->CurrentBaseLevel;
|
||||||
for (const TSharedPtr<FString>& Opt : BaseLevelOptions)
|
for (const TSharedPtr<FString>& Opt : BaseLevelOptions)
|
||||||
{
|
{
|
||||||
@@ -1092,6 +1097,10 @@ void UPS_Editor_MainWidget::NativeConstruct()
|
|||||||
{
|
{
|
||||||
BaseLevelPathMap.Add(Entry.DisplayName, Entry.MapPath);
|
BaseLevelPathMap.Add(Entry.DisplayName, Entry.MapPath);
|
||||||
}
|
}
|
||||||
|
if (Entry.AdditionalSublevels.Num() > 0)
|
||||||
|
{
|
||||||
|
BaseLevelExtraSublevels.Add(Entry.DisplayName, Entry.AdditionalSublevels);
|
||||||
|
}
|
||||||
if (Entry.Thumbnail)
|
if (Entry.Thumbnail)
|
||||||
{
|
{
|
||||||
BaseLevelThumbnails.Add(Entry.DisplayName, Entry.Thumbnail);
|
BaseLevelThumbnails.Add(Entry.DisplayName, Entry.Thumbnail);
|
||||||
@@ -1184,7 +1193,12 @@ void UPS_Editor_MainWidget::PopulateSceneList()
|
|||||||
{
|
{
|
||||||
SceneMapPath = *Found;
|
SceneMapPath = *Found;
|
||||||
}
|
}
|
||||||
EditorPC->LoadBaseLevelAsSublevel(SceneLevelName, SceneMapPath);
|
TArray<FString> ScnExtras;
|
||||||
|
if (TArray<FString>* FoundExtras = BaseLevelExtraSublevels.Find(SceneLevelName))
|
||||||
|
{
|
||||||
|
ScnExtras = *FoundExtras;
|
||||||
|
}
|
||||||
|
EditorPC->LoadBaseLevelAsSublevel(SceneLevelName, SceneMapPath, ScnExtras);
|
||||||
CurrentLevelFilter = EditorPC->CurrentBaseLevel;
|
CurrentLevelFilter = EditorPC->CurrentBaseLevel;
|
||||||
|
|
||||||
if (BaseLevelButtonText.IsValid())
|
if (BaseLevelButtonText.IsValid())
|
||||||
@@ -1890,7 +1904,50 @@ void UPS_Editor_MainWidget::RebuildDynamicProperties()
|
|||||||
// Value widget depends on property type
|
// Value widget depends on property type
|
||||||
TSharedRef<SWidget> ValueWidget = SNullWidget::NullWidget;
|
TSharedRef<SWidget> ValueWidget = SNullWidget::NullWidget;
|
||||||
|
|
||||||
|
// Check for custom dropdown options from the interface
|
||||||
|
TArray<FString> CustomOptions;
|
||||||
|
if (Actor->GetClass()->ImplementsInterface(UPS_Editor_EditableInterface::StaticClass()))
|
||||||
|
{
|
||||||
|
CustomOptions = IPS_Editor_EditableInterface::Execute_GetCustomOptionsForProperty(Actor, Row.Key);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (CustomOptions.Num() > 0)
|
||||||
|
{
|
||||||
|
Row.EnumOptions = MakeShared<TArray<TSharedPtr<FString>>>();
|
||||||
|
for (const FString& Opt : CustomOptions)
|
||||||
|
{
|
||||||
|
Row.EnumOptions->Add(MakeShared<FString>(Opt));
|
||||||
|
}
|
||||||
|
|
||||||
|
ValueWidget = SAssignNew(Row.EnumCombo, SComboBox<TSharedPtr<FString>>)
|
||||||
|
.OptionsSource(Row.EnumOptions.Get())
|
||||||
|
.Method(EPopupMethod::UseCurrentWindow)
|
||||||
|
.IsFocusable(true)
|
||||||
|
.OnSelectionChanged_Lambda([this, RowIndex](TSharedPtr<FString> Selected, ESelectInfo::Type SelectInfo)
|
||||||
|
{
|
||||||
|
if (!Selected.IsValid() || SelectInfo == ESelectInfo::Direct) return;
|
||||||
|
FPS_Editor_PropertyRowWidgets& R = DynamicPropertyRows[RowIndex];
|
||||||
|
if (R.EnumComboText.IsValid())
|
||||||
|
{
|
||||||
|
R.EnumComboText->SetText(FText::FromString(*Selected));
|
||||||
|
}
|
||||||
|
ApplyPropertyFromUI(RowIndex);
|
||||||
|
})
|
||||||
|
.OnGenerateWidget_Lambda([](TSharedPtr<FString> Item) -> TSharedRef<SWidget>
|
||||||
|
{
|
||||||
|
return SNew(STextBlock)
|
||||||
|
.Text(FText::FromString(Item.IsValid() ? *Item : TEXT("")))
|
||||||
|
.Font(FCoreStyle::GetDefaultFontStyle("Regular", 9));
|
||||||
|
})
|
||||||
|
[
|
||||||
|
SAssignNew(Row.EnumComboText, STextBlock)
|
||||||
|
.Text(FText::FromString(TEXT("Select...")))
|
||||||
|
.Font(ValueFont)
|
||||||
|
];
|
||||||
|
}
|
||||||
// Detect enum type (FEnumProperty for C++ enum class, FByteProperty with UEnum for legacy)
|
// Detect enum type (FEnumProperty for C++ enum class, FByteProperty with UEnum for legacy)
|
||||||
|
else
|
||||||
|
{
|
||||||
UEnum* FoundEnum = nullptr;
|
UEnum* FoundEnum = nullptr;
|
||||||
if (FEnumProperty* EnumProp = CastField<FEnumProperty>(Row.CachedProperty))
|
if (FEnumProperty* EnumProp = CastField<FEnumProperty>(Row.CachedProperty))
|
||||||
{
|
{
|
||||||
@@ -2024,6 +2081,8 @@ void UPS_Editor_MainWidget::RebuildDynamicProperties()
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
} // end else (enum/struct/default type detection)
|
||||||
|
|
||||||
DynamicPropertiesContainer->AddSlot().AutoHeight().Padding(0.0f, 2.0f)
|
DynamicPropertiesContainer->AddSlot().AutoHeight().Padding(0.0f, 2.0f)
|
||||||
[ ValueWidget ];
|
[ ValueWidget ];
|
||||||
|
|
||||||
@@ -2050,7 +2109,31 @@ void UPS_Editor_MainWidget::RefreshDynamicPropertyValues()
|
|||||||
UObject* Target = FindPropertyTarget(Actor, Row.ComponentName);
|
UObject* Target = FindPropertyTarget(Actor, Row.ComponentName);
|
||||||
if (!Target) continue;
|
if (!Target) continue;
|
||||||
|
|
||||||
if (Row.CachedEnum && Row.EnumCombo.IsValid() && !Row.EnumCombo->IsOpen())
|
// Custom options combo (no CachedEnum, has EnumCombo)
|
||||||
|
if (!Row.CachedEnum && Row.EnumCombo.IsValid() && !Row.EnumCombo->IsOpen())
|
||||||
|
{
|
||||||
|
// Read current property value as text
|
||||||
|
FString CurrentVal;
|
||||||
|
Row.CachedProperty->ExportText_InContainer(0, CurrentVal, Target, Target, nullptr, PPF_None);
|
||||||
|
|
||||||
|
if (Row.EnumComboText.IsValid() && Row.EnumComboText->GetText().ToString() != CurrentVal)
|
||||||
|
{
|
||||||
|
// Find matching option
|
||||||
|
if (Row.EnumOptions.IsValid())
|
||||||
|
{
|
||||||
|
for (const TSharedPtr<FString>& Opt : *Row.EnumOptions)
|
||||||
|
{
|
||||||
|
if (*Opt == CurrentVal)
|
||||||
|
{
|
||||||
|
Row.EnumCombo->SetSelectedItem(Opt);
|
||||||
|
Row.EnumComboText->SetText(FText::FromString(CurrentVal));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (Row.CachedEnum && Row.EnumCombo.IsValid() && !Row.EnumCombo->IsOpen())
|
||||||
{
|
{
|
||||||
// Read current enum value index
|
// Read current enum value index
|
||||||
int64 EnumValue = 0;
|
int64 EnumValue = 0;
|
||||||
@@ -2187,7 +2270,12 @@ void UPS_Editor_MainWidget::ApplyPropertyFromUI(int32 RowIndex)
|
|||||||
// Build new value text from UI and import
|
// Build new value text from UI and import
|
||||||
FString NewValueText;
|
FString NewValueText;
|
||||||
|
|
||||||
if (Row.CachedEnum && Row.EnumComboText.IsValid())
|
if (!Row.CachedEnum && Row.EnumComboText.IsValid())
|
||||||
|
{
|
||||||
|
// Custom options combo: use selected text directly as property value
|
||||||
|
NewValueText = Row.EnumComboText->GetText().ToString();
|
||||||
|
}
|
||||||
|
else if (Row.CachedEnum && Row.EnumComboText.IsValid())
|
||||||
{
|
{
|
||||||
FString DisplayName = Row.EnumComboText->GetText().ToString();
|
FString DisplayName = Row.EnumComboText->GetText().ToString();
|
||||||
for (int32 Idx = 0; Idx < Row.CachedEnum->NumEnums() - 1; ++Idx)
|
for (int32 Idx = 0; Idx < Row.CachedEnum->NumEnums() - 1; ++Idx)
|
||||||
@@ -2378,6 +2466,7 @@ void UPS_Editor_MainWidget::OpenBaseLevelPopup()
|
|||||||
if (!BaseLevelGridContainer.IsValid() || !BaseLevelPopup.IsValid()) return;
|
if (!BaseLevelGridContainer.IsValid() || !BaseLevelPopup.IsValid()) return;
|
||||||
|
|
||||||
BaseLevelGridContainer->ClearChildren();
|
BaseLevelGridContainer->ClearChildren();
|
||||||
|
BaseLevelCardBorders.Empty();
|
||||||
PendingBaseLevelSelection.Empty();
|
PendingBaseLevelSelection.Empty();
|
||||||
|
|
||||||
const float CardWidth = 160.0f;
|
const float CardWidth = 160.0f;
|
||||||
@@ -2457,23 +2546,32 @@ void UPS_Editor_MainWidget::OpenBaseLevelPopup()
|
|||||||
.ColorAndOpacity(FLinearColor::White)
|
.ColorAndOpacity(FLinearColor::White)
|
||||||
];
|
];
|
||||||
|
|
||||||
// Add card to current row
|
// Add card to current row with highlight border
|
||||||
|
TSharedPtr<SBorder> CardBorder;
|
||||||
CurrentRow->AddSlot().AutoWidth().Padding(8.0f, 0.0f)
|
CurrentRow->AddSlot().AutoWidth().Padding(8.0f, 0.0f)
|
||||||
[
|
[
|
||||||
SNew(SBox)
|
SNew(SBox)
|
||||||
.MinDesiredWidth(CardWidth + 16.0f)
|
.MinDesiredWidth(CardWidth + 16.0f)
|
||||||
[
|
[
|
||||||
SNew(SButton)
|
SAssignNew(CardBorder, SBorder)
|
||||||
.OnClicked_Lambda([this, Name]() -> FReply
|
.BorderImage(FCoreStyle::Get().GetBrush("WhiteBrush"))
|
||||||
{
|
.BorderBackgroundColor(FLinearColor(0.1f, 0.1f, 0.1f, 0.0f))
|
||||||
PendingBaseLevelSelection = Name;
|
.Padding(FMargin(2.0f))
|
||||||
return FReply::Handled();
|
|
||||||
})
|
|
||||||
[
|
[
|
||||||
Card
|
SNew(SButton)
|
||||||
|
.OnClicked_Lambda([this, Name]() -> FReply
|
||||||
|
{
|
||||||
|
PendingBaseLevelSelection = Name;
|
||||||
|
UpdateBaseLevelHighlight();
|
||||||
|
return FReply::Handled();
|
||||||
|
})
|
||||||
|
[
|
||||||
|
Card
|
||||||
|
]
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
];
|
];
|
||||||
|
BaseLevelCardBorders.Add(Name, CardBorder);
|
||||||
|
|
||||||
ColIndex++;
|
ColIndex++;
|
||||||
}
|
}
|
||||||
@@ -2519,7 +2617,12 @@ void UPS_Editor_MainWidget::ConfirmBaseLevelSelection()
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
EditorPC->LoadBaseLevelAsSublevel(PendingBaseLevelSelection, SublevelPath);
|
TArray<FString> Extras;
|
||||||
|
if (TArray<FString>* Found = BaseLevelExtraSublevels.Find(PendingBaseLevelSelection))
|
||||||
|
{
|
||||||
|
Extras = *Found;
|
||||||
|
}
|
||||||
|
EditorPC->LoadBaseLevelAsSublevel(PendingBaseLevelSelection, SublevelPath, Extras);
|
||||||
}
|
}
|
||||||
|
|
||||||
CurrentLevelFilter = EditorPC->CurrentBaseLevel;
|
CurrentLevelFilter = EditorPC->CurrentBaseLevel;
|
||||||
@@ -2535,6 +2638,20 @@ void UPS_Editor_MainWidget::ConfirmBaseLevelSelection()
|
|||||||
PendingBaseLevelSelection.Empty();
|
PendingBaseLevelSelection.Empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void UPS_Editor_MainWidget::UpdateBaseLevelHighlight()
|
||||||
|
{
|
||||||
|
for (auto& Pair : BaseLevelCardBorders)
|
||||||
|
{
|
||||||
|
if (Pair.Value.IsValid())
|
||||||
|
{
|
||||||
|
const bool bSelected = (Pair.Key == PendingBaseLevelSelection);
|
||||||
|
Pair.Value->SetBorderBackgroundColor(bSelected
|
||||||
|
? FLinearColor(0.2f, 0.6f, 1.0f, 0.8f)
|
||||||
|
: FLinearColor(0.1f, 0.1f, 0.1f, 0.0f));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void UPS_Editor_MainWidget::CancelBaseLevelPopup()
|
void UPS_Editor_MainWidget::CancelBaseLevelPopup()
|
||||||
{
|
{
|
||||||
if (BaseLevelPopup.IsValid())
|
if (BaseLevelPopup.IsValid())
|
||||||
|
|||||||
@@ -82,6 +82,8 @@ private:
|
|||||||
FString CurrentLevelFilter;
|
FString CurrentLevelFilter;
|
||||||
/** Maps DisplayName → full MapPath for base levels (from MasterCatalog). */
|
/** Maps DisplayName → full MapPath for base levels (from MasterCatalog). */
|
||||||
TMap<FString, FString> BaseLevelPathMap;
|
TMap<FString, FString> BaseLevelPathMap;
|
||||||
|
/** Maps DisplayName → additional sublevels to load (from MasterCatalog). */
|
||||||
|
TMap<FString, TArray<FString>> BaseLevelExtraSublevels;
|
||||||
/** Maps DisplayName → Thumbnail texture for base levels. */
|
/** Maps DisplayName → Thumbnail texture for base levels. */
|
||||||
TMap<FString, TObjectPtr<UTexture2D>> BaseLevelThumbnails;
|
TMap<FString, TObjectPtr<UTexture2D>> BaseLevelThumbnails;
|
||||||
TArray<TSharedPtr<FSlateBrush>> CachedBrushes;
|
TArray<TSharedPtr<FSlateBrush>> CachedBrushes;
|
||||||
@@ -90,7 +92,9 @@ private:
|
|||||||
TSharedPtr<SWidget> BaseLevelPopup;
|
TSharedPtr<SWidget> BaseLevelPopup;
|
||||||
TSharedPtr<SVerticalBox> BaseLevelGridContainer;
|
TSharedPtr<SVerticalBox> BaseLevelGridContainer;
|
||||||
FString PendingBaseLevelSelection;
|
FString PendingBaseLevelSelection;
|
||||||
|
TMap<FString, TSharedPtr<SBorder>> BaseLevelCardBorders;
|
||||||
void OpenBaseLevelPopup();
|
void OpenBaseLevelPopup();
|
||||||
|
void UpdateBaseLevelHighlight();
|
||||||
void ConfirmBaseLevelSelection();
|
void ConfirmBaseLevelSelection();
|
||||||
void CancelBaseLevelPopup();
|
void CancelBaseLevelPopup();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user