Files
integration/Source/Integration/FormatDataLibrary.cpp
2026-05-26 18:20:35 -04:00

376 lines
14 KiB
C++

#include "FormatDataLibrary.h"
#include "Editor.h"
#include "EdGraphSchema_K2.h"
#include "Internationalization/TextFormatter.h"
#include "Layout/Geometry.h"
#include "Widgets/Layout/Anchors.h"
#include "Common.h"
#include "Engine/GameViewportClient.h"
#include "Slate/SGameLayerManager.h"
#include "Kismet/KismetTextLibrary.h"
#include "UObject/UObjectIterator.h"
void UlxFormatDataLibrary::Initialize(FSubsystemCollectionBase& Collection)
{
Super::Initialize(Collection);
ScanForConverters();
}
void UlxFormatDataLibrary::ScanForConverters()
{
Converters.Empty();
for (TObjectIterator<UClass> It; It; ++It)
{
UClass* Class = *It;
for (TFieldIterator<UFunction> FuncIt(Class, EFieldIteratorFlags::ExcludeSuper); FuncIt; ++FuncIt)
{
UFunction* Function = *FuncIt;
// Must have an AutoConvertedValue parameter.
if (Function->FindPropertyByName(TEXT("AutoConvertedValue")) == nullptr) continue;
// Must have a Name parameter that is a string.
FProperty* NameProp = Function->FindPropertyByName(TEXT("Name"));
if (NameProp == nullptr) continue;
if (CastField<FStrProperty>(NameProp) == nullptr) continue;
// Must return FFormatArgumentData.
FStructProperty* ReturnProp = CastField<FStructProperty>(Function->GetReturnProperty());
if (ReturnProp == nullptr) continue;
if (ReturnProp->Struct->GetFName() != TEXT("FormatArgumentData")) continue;
// Must have exactly three properties: AutoConvertedValue, Name, and ReturnValue.
int PropCount = 0;
for (TFieldIterator<FProperty> PropIt(Function); PropIt; ++PropIt) PropCount++;
if (PropCount != 3) continue;
Converters.Add(Function);
}
}
for (UFunction* Func : Converters)
{
UE_LOG(LogLuprexIntegration, Display, TEXT("FormatData converter: %s::%s"), *Func->GetOuterUClass()->GetName(), *Func->GetName());
}
}
UFunction* UlxFormatDataLibrary::GetConverterForPinType(const UEdGraphSchema_K2 *Schema, const FEdGraphPinType& PinType, bool AllowWild)
{
// Special case: wildcard pins are unconnected pins.
if ((PinType.PinCategory == UEdGraphSchema_K2::PC_Wildcard) && AllowWild)
{
return UlxFormatDataLibrary::StaticClass()->FindFunctionByName(GET_MEMBER_NAME_CHECKED(UlxFormatDataLibrary, FormatArgumentDataBlank));
}
// Scan the cached converter list for a matching type.
UlxFormatDataLibrary* FormatDataLib = GEditor->GetEditorSubsystem<UlxFormatDataLibrary>();
for (UFunction* Function : FormatDataLib->Converters)
{
FProperty* ValueProperty = Function->FindPropertyByName(TEXT("AutoConvertedValue"));
FEdGraphPinType ValuePinType;
if (!Schema->ConvertPropertyToPinType(ValueProperty, ValuePinType)) continue;
if (!Schema->ArePinTypesEquivalent(PinType, ValuePinType)) continue;
return Function;
}
// A general handler for Enums.
if ((PinType.PinCategory == UEdGraphSchema_K2::PC_Byte) && (nullptr != Cast<const UEnum>(PinType.PinSubCategoryObject)))
{
return UlxFormatDataLibrary::StaticClass()->FindFunctionByName(GET_MEMBER_NAME_CHECKED(UlxFormatDataLibrary, FormatArgumentDataEnum));
}
// A case for subclasses of 'Object' which are not exactly 'Object'.
if (PinType.PinCategory == UEdGraphSchema_K2::PC_Object)
{
return UlxFormatDataLibrary::StaticClass()->FindFunctionByName(GET_MEMBER_NAME_CHECKED(UlxFormatDataLibrary, FormatArgumentDataObject));
}
return nullptr;
}
ELogVerbosity::Type UlxFormatDataLibrary::ConvertElxFormatLogVerbosity(ElxFormatLogVerbosity Verbosity) {
switch (Verbosity) {
case ElxFormatLogVerbosity::Error: return ELogVerbosity::Error;
case ElxFormatLogVerbosity::Warning: return ELogVerbosity::Warning;
case ElxFormatLogVerbosity::Display: return ELogVerbosity::Display;
case ElxFormatLogVerbosity::Log: return ELogVerbosity::Log;
case ElxFormatLogVerbosity::ThrottledDisplay: return ELogVerbosity::Display;
case ElxFormatLogVerbosity::ThrottledLog: return ELogVerbosity::Log;
case ElxFormatLogVerbosity::Verbose: return ELogVerbosity::Verbose;
case ElxFormatLogVerbosity::VeryVerbose: return ELogVerbosity::VeryVerbose;
case ElxFormatLogVerbosity::Fatal: return ELogVerbosity::Fatal;
}
}
FFormatArgumentData UlxFormatDataLibrary::FormatArgumentDataBool(bool AutoConvertedValue, const FString &Name)
{
FFormatArgumentData Result;
Result.ArgumentValueType = EFormatArgumentType::Text;
Result.ArgumentName = Name;
Result.ArgumentValue = UKismetTextLibrary::Conv_BoolToText(AutoConvertedValue);
return Result;
}
FFormatArgumentData UlxFormatDataLibrary::FormatArgumentDataByte(uint8 AutoConvertedValue, const FString &Name)
{
FFormatArgumentData Result;
Result.ArgumentValueType = EFormatArgumentType::Int;
Result.ArgumentName = Name;
Result.ArgumentValueInt = AutoConvertedValue;
return Result;
}
FFormatArgumentData UlxFormatDataLibrary::FormatArgumentDataInt(int AutoConvertedValue, const FString &Name)
{
FFormatArgumentData Result;
Result.ArgumentValueType = EFormatArgumentType::Int;
Result.ArgumentName = Name;
Result.ArgumentValueInt = AutoConvertedValue;
return Result;
}
FFormatArgumentData UlxFormatDataLibrary::FormatArgumentDataInt64(int64 AutoConvertedValue, const FString &Name)
{
FFormatArgumentData Result;
Result.ArgumentValueType = EFormatArgumentType::Int;
Result.ArgumentName = Name;
Result.ArgumentValueInt = AutoConvertedValue;
return Result;
}
FFormatArgumentData UlxFormatDataLibrary::FormatArgumentDataFloat(float AutoConvertedValue, const FString &Name)
{
FFormatArgumentData Result;
Result.ArgumentValueType = EFormatArgumentType::Float;
Result.ArgumentName = Name;
Result.ArgumentValueFloat = AutoConvertedValue;
return Result;
}
FFormatArgumentData UlxFormatDataLibrary::FormatArgumentDataDouble(double AutoConvertedValue, const FString &Name)
{
FFormatArgumentData Result;
Result.ArgumentValueType = EFormatArgumentType::Double;
Result.ArgumentName = Name;
Result.ArgumentValueDouble = AutoConvertedValue;
return Result;
}
FFormatArgumentData UlxFormatDataLibrary::FormatArgumentDataText(FText AutoConvertedValue, const FString &Name)
{
FFormatArgumentData Result;
Result.ArgumentValueType = EFormatArgumentType::Text;
Result.ArgumentName = Name;
Result.ArgumentValue = AutoConvertedValue;
return Result;
}
FFormatArgumentData UlxFormatDataLibrary::FormatArgumentDataString(FString AutoConvertedValue, const FString &Name)
{
FFormatArgumentData Result;
Result.ArgumentValueType = EFormatArgumentType::Text;
Result.ArgumentName = Name;
Result.ArgumentValue = UKismetTextLibrary::Conv_StringToText(AutoConvertedValue);
return Result;
}
FFormatArgumentData UlxFormatDataLibrary::FormatArgumentDataName(FName AutoConvertedValue, const FString &Name)
{
FFormatArgumentData Result;
Result.ArgumentValueType = EFormatArgumentType::Text;
Result.ArgumentName = Name;
Result.ArgumentValue = UKismetTextLibrary::Conv_NameToText(AutoConvertedValue);
return Result;
}
FFormatArgumentData UlxFormatDataLibrary::FormatArgumentDataKey(FKey AutoConvertedValue, const FString &Name)
{
FFormatArgumentData Result;
Result.ArgumentValueType = EFormatArgumentType::Text;
Result.ArgumentName = Name;
Result.ArgumentValue = UKismetTextLibrary::Conv_NameToText(AutoConvertedValue.GetFName());
return Result;
}
FFormatArgumentData UlxFormatDataLibrary::FormatArgumentDataGender(ETextGender AutoConvertedValue, const FString &Name)
{
FFormatArgumentData Result;
Result.ArgumentValueType = EFormatArgumentType::Gender;
Result.ArgumentName = Name;
Result.ArgumentValueGender = AutoConvertedValue;
return Result;
}
FFormatArgumentData UlxFormatDataLibrary::FormatArgumentDataObject(UObject *AutoConvertedValue, const FString &Name)
{
FFormatArgumentData Result;
Result.ArgumentValueType = EFormatArgumentType::Text;
Result.ArgumentName = Name;
Result.ArgumentValue = UKismetTextLibrary::Conv_ObjectToText(AutoConvertedValue);
return Result;
}
FFormatArgumentData UlxFormatDataLibrary::FormatArgumentDataVector(const FVector &AutoConvertedValue, const FString &Name)
{
FFormatArgumentData Result;
Result.ArgumentValueType = EFormatArgumentType::Text;
Result.ArgumentName = Name;
Result.ArgumentValue = UKismetTextLibrary::Conv_VectorToText(AutoConvertedValue);
return Result;
}
FFormatArgumentData UlxFormatDataLibrary::FormatArgumentDataVector2D(const FVector2D &AutoConvertedValue, const FString &Name)
{
FFormatArgumentData Result;
Result.ArgumentValueType = EFormatArgumentType::Text;
Result.ArgumentName = Name;
Result.ArgumentValue = UKismetTextLibrary::Conv_Vector2dToText(AutoConvertedValue);
return Result;
}
FFormatArgumentData UlxFormatDataLibrary::FormatArgumentDataRotator(const FRotator &AutoConvertedValue, const FString &Name)
{
FFormatArgumentData Result;
Result.ArgumentValueType = EFormatArgumentType::Text;
Result.ArgumentName = Name;
Result.ArgumentValue = UKismetTextLibrary::Conv_RotatorToText(AutoConvertedValue);
return Result;
}
FFormatArgumentData UlxFormatDataLibrary::FormatArgumentDataTransform(const FTransform &AutoConvertedValue, const FString &Name)
{
FFormatArgumentData Result;
Result.ArgumentValueType = EFormatArgumentType::Text;
Result.ArgumentName = Name;
Result.ArgumentValue = UKismetTextLibrary::Conv_TransformToText(AutoConvertedValue);
return Result;
}
FFormatArgumentData UlxFormatDataLibrary::FormatArgumentDataGeometry(const FGeometry &AutoConvertedValue, const FString &Name)
{
FVector2D UL = AutoConvertedValue.GetAbsolutePosition();
FVector2D LR = AutoConvertedValue.GetAbsolutePositionAtCoordinates(FVector2f(1.0f, 1.0f));
if (GEngine && GEngine->GameViewport)
{
TSharedPtr<IGameLayerManager> GameLayerManager = GEngine->GameViewport->GetGameLayerManager();
if (GameLayerManager.IsValid())
{
const FGeometry ViewportGeometry = GameLayerManager->GetViewportWidgetHostGeometry();
const FVector2D ViewportLocalSize = FVector2D(ViewportGeometry.GetLocalSize());
FVector2D ViewportPixelSize;
GEngine->GameViewport->GetViewportSize(ViewportPixelSize);
if (ViewportLocalSize.X > 0.0 && ViewportLocalSize.Y > 0.0)
{
const FVector2D PixelScale = ViewportPixelSize / ViewportLocalSize;
UL = ViewportGeometry.AbsoluteToLocal(UL) * PixelScale;
LR = ViewportGeometry.AbsoluteToLocal(LR) * PixelScale;
}
}
}
FFormatArgumentData Result;
Result.ArgumentValueType = EFormatArgumentType::Text;
Result.ArgumentName = Name;
Result.ArgumentValue = FText::Format(
INVTEXT("UL={0},{1} LR={2},{3}"),
FText::AsNumber(UL.X), FText::AsNumber(UL.Y),
FText::AsNumber(LR.X), FText::AsNumber(LR.Y));
return Result;
}
FFormatArgumentData UlxFormatDataLibrary::FormatArgumentDataAnchors(const FAnchors &AutoConvertedValue, const FString &Name)
{
FFormatArgumentData Result;
Result.ArgumentValueType = EFormatArgumentType::Text;
Result.ArgumentName = Name;
Result.ArgumentValue = FText::Format(
INVTEXT("Anchors(Min={0},{1} Max={2},{3})"),
FText::AsNumber(AutoConvertedValue.Minimum.X), FText::AsNumber(AutoConvertedValue.Minimum.Y),
FText::AsNumber(AutoConvertedValue.Maximum.X), FText::AsNumber(AutoConvertedValue.Maximum.Y));
return Result;
}
FFormatArgumentData UlxFormatDataLibrary::FormatArgumentDataBlank(const FString &Name)
{
FFormatArgumentData Result;
Result.ArgumentValueType = EFormatArgumentType::Text;
Result.ArgumentName = Name;
Result.ArgumentValue = FText();
return Result;
}
FFormatArgumentData UlxFormatDataLibrary::FormatArgumentDataEnum(uint8 Value, const FString &Name, const UObject *PinSubCategoryObject)
{
const UEnum *Enum = Cast<const UEnum>(PinSubCategoryObject);
FFormatArgumentData Result;
if (Enum == nullptr)
{
Result.ArgumentValueType = EFormatArgumentType::Int;
Result.ArgumentName = Name;
Result.ArgumentValueInt = Value;
}
else
{
Result.ArgumentValueType = EFormatArgumentType::Text;
Result.ArgumentName = Name;
Result.ArgumentValue = FText::Format(INVTEXT("<{0}>"), Enum->GetDisplayNameTextByValue(Value));
}
return Result;
}
FText UlxFormatDataLibrary::FormatMessageInternal(const FString &InPattern, TArray<FFormatArgumentData> InArgs)
{
FText InPatternText(FText::FromString(InPattern));
return FTextFormatter::Format(MoveTemp(InPatternText), MoveTemp(InArgs), false, false);
}
void UlxFormatDataLibrary::FormatLogMessageInternal(UObject *Context, ElxFormatLogVerbosity Verbosity, const FString &InPattern, TArray<FFormatArgumentData> InArgs)
{
// For throttled verbosity levels, suppress repeated messages with the
// same format pattern. We key on the blueprint name + format pattern,
// and allow at most one message per second per key.
//
if ((Verbosity == ElxFormatLogVerbosity::ThrottledDisplay) || (Verbosity == ElxFormatLogVerbosity::ThrottledLog))
{
static TMap<FString, double> LastLogTime;
double Now = FPlatformTime::Seconds();
FString Key = Context->GetClass()->GetName() + TEXT("::") + InPattern;
double &Last = LastLogTime.FindOrAdd(Key, 0.0);
if (Now - Last < 2.0)
{
return;
}
Last = Now;
}
// Generate the formatted string.
//
FText Message = FormatMessageInternal(InPattern, MoveTemp(InArgs));
FString MessageString = Message.ToString();
// Get the blueprint name.
//
// Normally, the log function expects you to pass in a filename, and a log
// category name. We use the blueprint name for both.
//
// Using the blueprint name as a log category name is not technically
// correct. However, there is no correct way to create log categories
// from inside of blueprints. Doing it this way at least produces a reasonable
// message inside the log. What doesn't work correctly is the log message
// suppression system. Ie, console commands like 'log <category> verbose'
// don't have any effect here. The design of the log message suppression
// system is such that there just is no reasonable way to hook into it from
// inside of blueprints.
//
FString BlueprintNameString = Context->GetClass()->GetName();
auto BlueprintNameAnsi = StringCast<ANSICHAR>(*BlueprintNameString);
FLogCategoryName BlueprintNameLogCategory(Context->GetClass()->GetFName());
// Output to Log
//
ELogVerbosity::Type VerbosityValue = ConvertElxFormatLogVerbosity(Verbosity);
if (VerbosityValue <= ELogVerbosity::COMPILED_IN_MINIMUM_VERBOSITY)
{
FMsg::Logf(BlueprintNameAnsi.Get(), 0, BlueprintNameLogCategory, VerbosityValue, TEXT("%s"), *MessageString);
}
}