Skip to content

Instantly share code, notes, and snippets.

@LuviKunG
Last active April 3, 2024 04:18
Show Gist options
  • Save LuviKunG/febd6b7c10c6de0e93d8cea6e598ad89 to your computer and use it in GitHub Desktop.
Save LuviKunG/febd6b7c10c6de0e93d8cea6e598ad89 to your computer and use it in GitHub Desktop.
Unreal Engine: Display the input action prompt sprite in Rich Text Block depend on binding of input action key mapping of Enhanced input system.
// Copyright by Thanut Panichyotai (@LuviKunG)
// CC BY-SA 4.0
// https://creativecommons.org/licenses/by-sa/4.0/
#include "Widgets/RichTextBlockInputActionDecorator.h"
#include "EnhancedInputSubsystems.h"
#include "InputMappingContext.h"
#include "Fonts/FontMeasure.h"
#include "Kismet/GameplayStatics.h"
#include "UserSettings/EnhancedInputUserSettings.h"
#include "Widgets/Layout/SScaleBox.h"
class SRichInlineImageSet : public SCompoundWidget
{
public:
SLATE_BEGIN_ARGS(SRichInlineImageSet)
{
}
SLATE_END_ARGS()
void Construct(const FArguments& InArgs, const TArray<FSlateBrush*> Brushes, const FTextBlockStyle& TextStyle)
{
const TSharedRef<SHorizontalBox> HorizontalBox = SNew(SHorizontalBox);
for (const FSlateBrush* Brush : Brushes)
{
auto Slot = HorizontalBox->AddSlot();
Slot.AutoWidth();
Slot[CreateBox(InArgs, Brush, TextStyle)];
}
ChildSlot[HorizontalBox];
}
private:
TSharedRef<SBox> CreateBox(const FArguments& InArgs, const FSlateBrush* Brush, const FTextBlockStyle& TextStyle)
{
check(Brush);
const TSharedRef<FSlateFontMeasure> FontMeasure = FSlateApplication::Get().GetRenderer()->GetFontMeasureService();
const float IconHeight = FMath::Min(static_cast<float>(FontMeasure->GetMaxCharacterHeight(TextStyle.Font, 1.0f)), Brush->ImageSize.Y);
const float IconWidth = Brush->ImageSize.X;
return SNew(SBox).HeightOverride(IconHeight).WidthOverride(IconWidth)[SNew(SScaleBox).Stretch(EStretch::ScaleToFit).StretchDirection(EStretchDirection::DownOnly).VAlign(VAlign_Center)[SNew(SImage).Image(Brush)]];
}
};
class FRichTextInputActionDecorator : public FRichTextDecorator
{
public:
FRichTextInputActionDecorator(URichTextBlock* InOwner, URichTextBlockInputActionDecorator* InDecorator);
virtual bool Supports(const FTextRunParseResults& RunParseResult, const FString& Text) const override
{
const FString TagName = TEXT("action");
const FString TagAction = TEXT("name");
return RunParseResult.Name == TagName && RunParseResult.MetaData.Contains(TagAction);
}
protected:
URichTextBlockInputActionDecorator* Decorator;
virtual TSharedPtr<SWidget> CreateDecoratorWidget(const FTextRunInfo& RunInfo, const FTextBlockStyle& TextStyle) const override
{
const FString TagName = TEXT("name");
constexpr bool bWarnIfMissing = true;
TArray<FSlateBrush*> Brushes;
if (Decorator->FindImageInputActions(*RunInfo.MetaData[TagName], Brushes, bWarnIfMissing); Brushes.Num() > 0)
{
return SNew(SRichInlineImageSet, Brushes, TextStyle);
}
return TSharedPtr<SWidget>();
}
};
FRichTextInputActionDecorator::FRichTextInputActionDecorator(URichTextBlock* InOwner, URichTextBlockInputActionDecorator* InDecorator): FRichTextDecorator(InOwner)
{
Decorator = InDecorator;
}
URichTextBlockInputActionDecorator::URichTextBlockInputActionDecorator(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer)
{
bUseDefaultImagePrompt = true;
}
TSharedPtr<ITextDecorator> URichTextBlockInputActionDecorator::CreateDecorator(URichTextBlock* InOwner)
{
return MakeShareable(new FRichTextInputActionDecorator(InOwner, this));
}
void URichTextBlockInputActionDecorator::FindImageInputActions(const FName& InName, TArray<FSlateBrush*>& OutBrushes, bool bWarnIfMissing)
{
FindSlashBrushInputActions(InName, OutBrushes, bWarnIfMissing);
if (bUseDefaultImagePrompt && OutBrushes.IsEmpty())
{
OutBrushes.Add(&DefaultBrush);
}
}
void URichTextBlockInputActionDecorator::FindSlashBrushInputActions(const FName& InName, TArray<FSlateBrush*>& OutBrushes, bool bWarnIfMissing) const
{
OutBrushes.Reset();
if (InName.IsNone())
return;
const UGameInstance* GameInstance = GetWorld()->GetGameInstance();
if (!GameInstance)
return;
const ULocalPlayer* LocalPlayer = GameInstance->GetFirstGamePlayer();
if (!LocalPlayer)
return;
const UEnhancedInputLocalPlayerSubsystem* InputLocalPlayerSubsystem = LocalPlayer->GetSubsystem<UEnhancedInputLocalPlayerSubsystem>();
if (!InputLocalPlayerSubsystem)
return;
const UEnhancedInputUserSettings* UserSettings = InputLocalPlayerSubsystem->GetUserSettings();
if (!UserSettings)
return;
const UEnhancedPlayerMappableKeyProfile* CurrentKeyMap = UserSettings->GetCurrentKeyProfile();
if (!CurrentKeyMap)
return;
const TMap<FName, FKeyMappingRow>& PlayerMappingRows = CurrentKeyMap->GetPlayerMappingRows();
for (TTuple<FName, FKeyMappingRow> MappingRow : PlayerMappingRows)
{
const FKeyMappingRow& KeyMappingRow = MappingRow.Value;
for (const FPlayerKeyMapping& KeyMapping : KeyMappingRow.Mappings)
{
const UInputAction* InputAction = KeyMapping.GetAssociatedInputAction();
if (!InputAction)
continue;
const FName ActionName = InputAction->GetFName();
if (ActionName == InName)
{
FKey Key = KeyMapping.GetCurrentKey();
const FName KeyName = Key.GetFName();
if (FRichImageRow* ImageRow = FindImageRow(KeyName, bWarnIfMissing))
OutBrushes.AddUnique(&ImageRow->Brush);
}
}
}
if (OutBrushes.Num() > 0)
{
// NOTE: There are already configured input mappings for the given action name. Skip checking the registered input mapping contexts.
return;
}
for (const TObjectPtr<const UInputMappingContext> InputMappingContext : UserSettings->GetRegisteredInputMappingContexts())
{
const TArray<FEnhancedActionKeyMapping>& Mappings = InputMappingContext->GetMappings();
for (const FEnhancedActionKeyMapping& Mapping : Mappings)
{
const FName ActionName = Mapping.Action.GetFName();
if (ActionName == InName)
{
FKey Key = Mapping.Key;
const FName KeyName = Key.GetFName();
if (FRichImageRow* ImageRow = FindImageRow(KeyName, bWarnIfMissing))
OutBrushes.AddUnique(&ImageRow->Brush);
}
}
}
}
FRichImageRow* URichTextBlockInputActionDecorator::FindImageRow(const FName& InName, bool bWarnIfMissing) const
{
if (ImageSetArray.IsEmpty())
return nullptr;
for (const TObjectPtr<UDataTable>& ImageSet : ImageSetArray)
{
if (ImageSet)
{
const FString ContextString;
if (FRichImageRow* Row = ImageSet->FindRow<FRichImageRow>(InName, ContextString, bWarnIfMissing))
return Row;
}
}
return nullptr;
}
// Copyright by Thanut Panichyotai (@LuviKunG)
// CC BY-SA 4.0
// https://creativecommons.org/licenses/by-sa/4.0/
#pragma once
#include "CoreMinimal.h"
#include "Components/RichTextBlock.h"
#include "Components/RichTextBlockDecorator.h"
#include "Components/RichTextBlockImageDecorator.h"
#include "Framework/Text/ITextDecorator.h"
#include "UObject/Object.h"
#include "RichTextBlockInputActionDecorator.generated.h"
/**
* Allows you to setup an input action decorator that can be configured.
*/
UCLASS(Abstract, Blueprintable)
class REPLACE_YOUR_API URichTextBlockInputActionDecorator : public URichTextBlockDecorator
{
GENERATED_BODY()
public:
URichTextBlockInputActionDecorator(const FObjectInitializer& ObjectInitializer);
virtual TSharedPtr<ITextDecorator> CreateDecorator(URichTextBlock* InOwner) override;
/**
* Find the image input actions.
* @param InName Name of the input action. This is the same name of the enhanced input action file name.
* @param OutBrushes The brushes to output.
* @param bWarnIfMissing Whether to warn if the input action is missing.
*/
void FindImageInputActions(const FName& InName, TArray<FSlateBrush*>& OutBrushes, bool bWarnIfMissing);
protected:
/** Determines whether to use the default image prompt. */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Configurations", DisplayName = "Use Default Image Prompt")
bool bUseDefaultImagePrompt;
/** The default image prompt slate brush. Must be set if 'bUseDefaultImagePrompt' is true. */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Configurations", DisplayName = "Default Image Prompt Slate Brush", meta = (EditCondition = "bUseDefaultImagePrompt"))
FSlateBrush DefaultBrush;
/** The image set array. The row structure must be 'RichImageRow'. */
UPROPERTY(EditAnywhere, Category = "Appearance", meta = (RequiredAssetDataTags = "RowStructure=/Script/UMG.RichImageRow"))
TArray<TObjectPtr<UDataTable>> ImageSetArray;
/**
* Find the slash brush input actions.
* @param InName Name of the input action. This is the same name of the enhanced input action file name.
* @param OutBrushes The brushes to output.
* @param bWarnIfMissing Whether to warn if the input action is missing.
*/
void FindSlashBrushInputActions(const FName& InName, TArray<FSlateBrush*>& OutBrushes, bool bWarnIfMissing) const;
/**
* Find the image row.
* This will search the image set array.
* @param TagOrId The tag or id of the image row.
* @param bWarnIfMissing Whether to warn if the image row is missing.
* @return The image row if found, otherwise nullptr.
*/
FRichImageRow* FindImageRow(const FName& TagOrId, bool bWarnIfMissing) const;
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment