371 lines
14 KiB
C++
371 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;
|
|
}
|
|
|
|
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 InPatternText(FText::FromString(InPattern));
|
|
FText Message = FTextFormatter::Format(MoveTemp(InPatternText), MoveTemp(InArgs), false, false);
|
|
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);
|
|
}
|
|
}
|