diff --git a/Source/StevesUEHelpers/Private/StevesUI/RichTextBlockInputImageDecorator.cpp b/Source/StevesUEHelpers/Private/StevesUI/RichTextBlockInputImageDecorator.cpp new file mode 100644 index 0000000..eb11ea0 --- /dev/null +++ b/Source/StevesUEHelpers/Private/StevesUI/RichTextBlockInputImageDecorator.cpp @@ -0,0 +1,192 @@ +#include "StevesUI/RichTextBlockInputImageDecorator.h" + + +#include "StevesHelperCommon.h" +#include "StevesUEHelpers.h" +#include "Fonts/FontMeasure.h" +#include "Misc/DefaultValueHelper.h" +#include "Widgets/Layout/SScaleBox.h" +#include "Widgets/Images/SImage.h" + + +// Slat SNew only supports 5 custom arguments so we need to batch things up +struct FInputImageParams +{ + /// What type of an input binding this image should look up + EInputBindingType BindingType; + /// If BindingType is Action/Axis, the name of it + FName ActionOrAxisName; + /// If BindingType is Key, the key + FKey Key; + /// Player index, if binding type is action or axis + int PlayerIndex; + /// Theme, if any + UUiTheme* CustomTheme; + UWorld* WorldContext; +}; +// Basically the same as SRichInlineImage but I can't re-use that since private +class SRichInlineInputImage : public SCompoundWidget +{ +protected: + /// What type of an input binding this image should look up + EInputBindingType BindingType; + /// If BindingType is Action/Axis, the name of it + FName ActionOrAxisName; + /// If BindingType is Key, the key + FKey Key; + /// Player index, if binding type is action or axis + int PlayerIndex; + /// Theme, if any + UUiTheme* CustomTheme; + UWorld* WorldContext; + + FSlateBrush Brush; + +public: + SLATE_BEGIN_ARGS(SRichInlineInputImage) + {} + SLATE_END_ARGS() + +public: + + void Construct(const FArguments& InArgs, FInputImageParams InParams, + const FTextBlockStyle& TextStyle, TOptional Width, TOptional Height, EStretch::Type Stretch) + { + BindingType = InParams.BindingType; + ActionOrAxisName = InParams.ActionOrAxisName; + Key = InParams.Key; + PlayerIndex = InParams.PlayerIndex; + CustomTheme = InParams.CustomTheme; + WorldContext = InParams.WorldContext; + + const TSharedRef FontMeasure = FSlateApplication::Get().GetRenderer()->GetFontMeasureService(); + float IconHeight = FMath::Min((float)FontMeasure->GetMaxCharacterHeight(TextStyle.Font, 1.0f), Brush.ImageSize.Y); + float IconWidth = IconHeight; + + if (Width.IsSet()) + { + IconWidth = Width.GetValue(); + } + + if (Height.IsSet()) + { + IconHeight = Height.GetValue(); + } + + auto GS = GetStevesGameSubsystem(WorldContext); + if (GS) + { + auto Sprite = GS->GetInputImageSprite(BindingType, ActionOrAxisName, Key, PlayerIndex, CustomTheme); + GS->SetBrushFromAtlas(&Brush, Sprite, true); + } + + ChildSlot + [ + SNew(SBox) + .HeightOverride(IconHeight) + .WidthOverride(IconWidth) + [ + SNew(SScaleBox) + .Stretch(Stretch) + .StretchDirection(EStretchDirection::DownOnly) + .VAlign(VAlign_Center) + [ + SNew(SImage) + .Image(&Brush) + ] + ] + ]; + } +}; + +// Again, wish I could just subclass FRichInlineImage here, le sigh +class FRichInlineInputImage : public FRichTextDecorator +{ +public: + FRichInlineInputImage(URichTextBlock* InOwner, URichTextBlockInputImageDecorator* InDecorator) + : FRichTextDecorator(InOwner) + , Decorator(InDecorator) + { + } + + virtual bool Supports(const FTextRunParseResults& RunParseResult, const FString& Text) const override + { + if (RunParseResult.Name == TEXT("input")) + { + return RunParseResult.MetaData.Contains(TEXT("key")) || + RunParseResult.MetaData.Contains(TEXT("action")) || + RunParseResult.MetaData.Contains(TEXT("axis")); + } + + return false; + } + +protected: + + virtual TSharedPtr CreateDecoratorWidget(const FTextRunInfo& RunInfo, const FTextBlockStyle& TextStyle) const override + { + FInputImageParams Params; + Params.PlayerIndex = 0; + Params.BindingType = EInputBindingType::Key; + Params.Key = EKeys::AnyKey; + + if (const FString* PlayerStr = RunInfo.MetaData.Find(TEXT("player"))) + { + int PTemp; + Params.PlayerIndex = FDefaultValueHelper::ParseInt(*PlayerStr, PTemp) ? PTemp : 0; + } + + if (const FString* KeyStr = RunInfo.MetaData.Find(TEXT("key"))) + { + Params.BindingType = EInputBindingType::Key; + Params.Key = FKey(**KeyStr); + } + else if (const FString* ActionStr = RunInfo.MetaData.Find(TEXT("action"))) + { + Params.BindingType = EInputBindingType::Action; + Params.ActionOrAxisName = **ActionStr; + } + else if (const FString* AxisStr = RunInfo.MetaData.Find(TEXT("axis"))) + { + Params.BindingType = EInputBindingType::Axis; + Params.ActionOrAxisName = **AxisStr; + } + + // Support the same width/height/stretch overrides as standard rich text images + TOptional Width; + if (const FString* WidthString = RunInfo.MetaData.Find(TEXT("width"))) + { + int32 WidthTemp; + Width = FDefaultValueHelper::ParseInt(*WidthString, WidthTemp) ? WidthTemp : TOptional(); + } + + TOptional Height; + if (const FString* HeightString = RunInfo.MetaData.Find(TEXT("height"))) + { + int32 HeightTemp; + Height = FDefaultValueHelper::ParseInt(*HeightString, HeightTemp) ? HeightTemp : TOptional(); + } + + EStretch::Type Stretch = EStretch::ScaleToFit; + if (const FString* SstretchString = RunInfo.MetaData.Find(TEXT("stretch"))) + { + static const UEnum* StretchEnum = StaticEnum(); + int64 StretchValue = StretchEnum->GetValueByNameString(*SstretchString); + if (StretchValue != INDEX_NONE) + { + Stretch = static_cast(StretchValue); + } + } + + // SNew only supports 5 custom arguments! Thats why we batch up in struct + return SNew(SRichInlineInputImage, Params, TextStyle, Width, Height, Stretch); + } + +private: + URichTextBlockInputImageDecorator* Decorator; +}; + +TSharedPtr URichTextBlockInputImageDecorator::CreateDecorator(URichTextBlock* InOwner) +{ + return MakeShareable(new FRichInlineInputImage(InOwner, this)); +} diff --git a/Source/StevesUEHelpers/Public/StevesUI/RichTextBlockInputImageDecorator.h b/Source/StevesUEHelpers/Public/StevesUI/RichTextBlockInputImageDecorator.h new file mode 100644 index 0000000..08b98f3 --- /dev/null +++ b/Source/StevesUEHelpers/Public/StevesUI/RichTextBlockInputImageDecorator.h @@ -0,0 +1,25 @@ +#pragma once + +#include "CoreMinimal.h" +#include "Components/RichTextBlockDecorator.h" + +#include "RichTextBlockInputImageDecorator.generated.h" + +class UUiTheme; + +UCLASS() +class STEVESUEHELPERS_API URichTextBlockInputImageDecorator : public URichTextBlockDecorator +{ + GENERATED_BODY() + +protected: + /// Custom theme to use for this input image set; if not supplied will use UStevesGameSubsystem::DefaultUiTheme + UPROPERTY(EditAnywhere, BlueprintReadOnly) + UUiTheme* CustomTheme; + +public: + + virtual TSharedPtr CreateDecorator(URichTextBlock* InOwner) override; + + virtual UUiTheme* GetCustomTheme() const { return CustomTheme; } +};