Files
UnrealEngine/Engine/Source/Editor/ContentBrowser/Private/CollectionViewUtils.cpp
2025-05-18 13:04:45 +08:00

275 lines
7.7 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "CollectionViewUtils.h"
#include "Containers/Map.h"
#include "Containers/UnrealString.h"
#include "CoreGlobals.h"
#include "HAL/Platform.h"
#include "HAL/PlatformCrt.h"
#include "ICollectionContainer.h"
#include "Misc/AssertionMacros.h"
#include "Misc/CString.h"
#include "Misc/ConfigCacheIni.h"
#include "Misc/Paths.h"
#include "Styling/AppStyle.h"
#include "Styling/SlateColor.h"
#define LOCTEXT_NAMESPACE "CollectionView"
namespace CollectionViewUtils
{
// Keep a map of all the collections that have custom colors, so updating the color in one location updates them all
static TMap<FString, TOptional<FLinearColor>> LegacyCollectionColors;
FLinearColor GetLegacyDefaultColor()
{
// The default tint the folder should appear as
return FLinearColor::Gray;
}
/** Create a string of the form "CollectionName:CollectionType */
FString ToConfigKey(const FString& InCollectionName, const ECollectionShareType::Type& InCollectionType)
{
static_assert(ECollectionShareType::CST_All == 4, "Update CollectionViewUtils::ToConfigKey for the updated ECollectionShareType values");
check(InCollectionType != ECollectionShareType::CST_All);
FString CollectionTypeStr;
switch(InCollectionType)
{
case ECollectionShareType::CST_System:
CollectionTypeStr = "System";
break;
case ECollectionShareType::CST_Local:
CollectionTypeStr = "Local";
break;
case ECollectionShareType::CST_Private:
CollectionTypeStr = "Private";
break;
case ECollectionShareType::CST_Shared:
CollectionTypeStr = "Shared";
break;
default:
break;
}
return InCollectionName + ":" + CollectionTypeStr;
}
/** Convert a string of the form "CollectionName:CollectionType back into its individual elements */
bool FromConfigKey(const FString& InKey, FString& OutCollectionName, ECollectionShareType::Type& OutCollectionType)
{
static_assert(ECollectionShareType::CST_All == 4, "Update CollectionViewUtils::FromConfigKey for the updated ECollectionShareType values");
FString CollectionTypeStr;
if(InKey.Split(":", &OutCollectionName, &CollectionTypeStr, ESearchCase::IgnoreCase, ESearchDir::FromEnd))
{
if(CollectionTypeStr == "System")
{
OutCollectionType = ECollectionShareType::CST_System;
}
else if(CollectionTypeStr == "Local")
{
OutCollectionType = ECollectionShareType::CST_Local;
}
else if(CollectionTypeStr == "Private")
{
OutCollectionType = ECollectionShareType::CST_Private;
}
else if(CollectionTypeStr == "Shared")
{
OutCollectionType = ECollectionShareType::CST_Shared;
}
else
{
return false;
}
return !OutCollectionName.IsEmpty();
}
return false;
}
const TOptional<FLinearColor> LoadLegacyColor(const FName InCollectionName, const ECollectionShareType::Type& InCollectionType)
{
check(InCollectionType != ECollectionShareType::CST_All);
const FString ColorKeyStr = ToConfigKey(InCollectionName.ToString(), InCollectionType);
// See if we have a value cached first
{
TOptional<FLinearColor>* const CachedColor = LegacyCollectionColors.Find(ColorKeyStr);
if(CachedColor)
{
return *CachedColor;
}
}
// Loads the color of collection at the given path from the config
if(FPaths::FileExists(GEditorPerProjectIni))
{
// Create a new entry from the config, skip if it's default
FString ColorStr;
if(GConfig->GetString(TEXT("CollectionColor"), *ColorKeyStr, ColorStr, GEditorPerProjectIni))
{
FLinearColor Color;
if(Color.InitFromString(ColorStr) && !Color.Equals(CollectionViewUtils::GetLegacyDefaultColor()))
{
return LegacyCollectionColors.Add(ColorKeyStr, Color);
}
}
}
// Cache an empty color to avoid hitting the disk again
return LegacyCollectionColors.Add(ColorKeyStr, TOptional<FLinearColor>());
}
void ClearLegacyColor(const FName InCollectionName, const ECollectionShareType::Type& InCollectionType)
{
check(InCollectionType != ECollectionShareType::CST_All);
const FString ColorKeyStr = ToConfigKey(InCollectionName.ToString(), InCollectionType);
// Saves the color of the collection to the config
if(FPaths::FileExists(GEditorPerProjectIni))
{
// If this is no longer custom, remove it
GConfig->RemoveKey(TEXT("CollectionColor"), *ColorKeyStr, GEditorPerProjectIni);
}
// Update the map too
LegacyCollectionColors.Remove(ColorKeyStr);
}
bool HasLegacyCustomColors(const ICollectionContainer& InCollectionContainer, TArray<FLinearColor>* OutColors)
{
if(!FPaths::FileExists(GEditorPerProjectIni))
{
return false;
}
// Read individual entries from a config file.
TArray<FString> Section;
GConfig->GetSection(TEXT("CollectionColor"), Section, GEditorPerProjectIni);
bool bHasCustom = false;
for(FString& EntryStr : Section)
{
EntryStr.TrimStartInline();
FString ColorKeyStr;
FString ColorStr;
if(!EntryStr.Split("=", &ColorKeyStr, &ColorStr))
{
continue;
}
// Ignore any that have invalid or default colors
FLinearColor CurrentColor;
if(!CurrentColor.InitFromString(ColorStr) || CurrentColor.Equals(CollectionViewUtils::GetLegacyDefaultColor()))
{
continue;
}
// Ignore any that reference old collections
FString CollectionName;
ECollectionShareType::Type CollectionType;
if(!FromConfigKey(ColorKeyStr, CollectionName, CollectionType) || !InCollectionContainer.CollectionExists(*CollectionName, CollectionType))
{
continue;
}
bHasCustom = true;
if(OutColors)
{
// Only add if not already present (ignores near matches too)
bool bAdded = false;
for(const FLinearColor& Color : *OutColors)
{
if(CurrentColor.Equals(Color))
{
bAdded = true;
break;
}
}
if(!bAdded)
{
OutColors->Add(CurrentColor);
}
}
else
{
break;
}
}
return bHasCustom;
}
FLinearColor ResolveColor(const ICollectionContainer& InCollectionContainer, const FName InCollectionName, const ECollectionShareType::Type& InCollectionType)
{
TOptional<FLinearColor> CollectionColor = GetCustomColor(&InCollectionContainer, InCollectionName, InCollectionType);
if (!CollectionColor)
{
CollectionColor = GetDefaultColor();
}
check(CollectionColor);
return CollectionColor.GetValue();
}
TOptional<FLinearColor> GetCustomColor(const ICollectionContainer* InCollectionContainer, const FName InCollectionName, const ECollectionShareType::Type& InCollectionType)
{
// First try and use the color set on the collection itself
TOptional<FLinearColor> CollectionColor;
if (InCollectionContainer != nullptr)
{
InCollectionContainer->GetCollectionColor(InCollectionName, InCollectionType, CollectionColor);
}
// Failing that, try and use the legacy local color set for this collection
if (!CollectionColor)
{
CollectionColor = LoadLegacyColor(InCollectionName, InCollectionType);
}
return CollectionColor;
}
void SetCustomColor(ICollectionContainer& InCollectionContainer, const FName InCollectionName, const ECollectionShareType::Type& InCollectionType, const TOptional<FLinearColor>& CollectionColor)
{
if (InCollectionContainer.SetCollectionColor(InCollectionName, InCollectionType, CollectionColor))
{
// Set correctly, so clear any legacy color
ClearLegacyColor(InCollectionName, InCollectionType);
}
}
bool HasCustomColors(const ICollectionContainer& InCollectionContainer, TArray<FLinearColor>* OutColors)
{
bool bHasColors = false;
// First get the colors from the collection manager
bHasColors |= InCollectionContainer.HasCollectionColors(OutColors);
// Then add in any legacy colors
bHasColors |= HasLegacyCustomColors(InCollectionContainer, OutColors);
return bHasColors;
}
FLinearColor GetDefaultColor()
{
// Use the selection accent color as the default
const FSlateColor NewSlateColor = FAppStyle::GetSlateColor("SelectionColor");
return NewSlateColor.IsColorSpecified() ? NewSlateColor.GetSpecifiedColor() : FLinearColor::White;
}
} // namespace CollectionViewUtils
#undef LOCTEXT_NAMESPACE