Merge branch 'enhanced-input'

This commit is contained in:
Steve Streeting 2023-03-08 14:08:50 +00:00
commit 46e1a18187
14 changed files with 359 additions and 17 deletions

View File

@ -1,5 +1,9 @@
#include "StevesGameSubsystem.h" #include "StevesGameSubsystem.h"
#include "EngineUtils.h"
#include "EnhancedInputSubsystems.h"
#include "StevesGameViewportClientBase.h" #include "StevesGameViewportClientBase.h"
#include "StevesPluginSettings.h"
#include "StevesUEHelpers.h" #include "StevesUEHelpers.h"
#include "Engine/AssetManager.h" #include "Engine/AssetManager.h"
#include "Engine/GameInstance.h" #include "Engine/GameInstance.h"
@ -20,6 +24,7 @@ void UStevesGameSubsystem::Initialize(FSubsystemCollectionBase& Collection)
CreateInputDetector(); CreateInputDetector();
InitTheme(); InitTheme();
InitForegroundCheck(); InitForegroundCheck();
NotifyEnhancedInputMappingsChanged();
#endif #endif
} }
@ -58,6 +63,50 @@ void UStevesGameSubsystem::DestroyInputDetector()
#endif #endif
} }
void UStevesGameSubsystem::NotifyEnhancedInputMappingsChanged()
{
// delay to ensure there's a tick in between which updates the mappings, it's not synchronous
auto DelayedFunc = [this]()
{
OnEnhancedInputMappingsChanged.Broadcast();
};
FTimerHandle TempHandle;
GetWorld()->GetTimerManager().SetTimer(TempHandle, FTimerDelegate::CreateLambda(DelayedFunc), 0.05, false);
}
TSoftObjectPtr<UInputAction> UStevesGameSubsystem::FindEnhancedInputAction(const FString& Name)
{
if (FAssetRegistryModule* AssetRegistryModule = FModuleManager::LoadModulePtr<FAssetRegistryModule>(TEXT("AssetRegistry")))
{
IAssetRegistry& AssetRegistry = AssetRegistryModule->Get();
if (auto Settings = GetDefault<UStevesPluginSettings>())
{
for (const auto& Dir : Settings->EnhancedInputActionSearchDirectories)
{
if (!FPackageName::IsValidPath(Dir.Path))
{
continue;
}
TArray<FAssetData> Assets;
FString Package = FPaths::Combine(Dir.Path, Name);
if (AssetRegistry.GetAssetsByPackageName(FName(*Package), Assets, true))
{
for (const FAssetData& Asset : Assets)
{
if (Asset.GetClass() == UInputAction::StaticClass())
{
return TSoftObjectPtr<UInputAction>(Asset.GetSoftObjectPath());
}
}
}
}
}
}
return nullptr;
}
void UStevesGameSubsystem::InitTheme() void UStevesGameSubsystem::InitTheme()
{ {
DefaultUiTheme = LoadObject<UUiTheme>(nullptr, *DefaultUiThemePath, nullptr); DefaultUiTheme = LoadObject<UUiTheme>(nullptr, *DefaultUiThemePath, nullptr);
@ -224,6 +273,42 @@ UPaperSprite* UStevesGameSubsystem::GetInputImageSpriteFromAxis(const FName& Nam
return nullptr; return nullptr;
} }
UPaperSprite* UStevesGameSubsystem::GetInputImageSpriteFromEnhancedInputAction(UInputAction* Action,
EInputImageDevicePreference DevicePreference,
int PlayerIdx,
APlayerController* PC,
UUiTheme* Theme)
{
if (const UEnhancedInputLocalPlayerSubsystem* Subsystem = ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(PC->GetLocalPlayer()))
{
const TArray<FKey> Keys = Subsystem->QueryKeysMappedToAction(Action);
// For default, prefer mouse for axes
if (DevicePreference == EInputImageDevicePreference::Auto)
{
if (Action->ValueType == EInputActionValueType::Boolean)
{
DevicePreference = EInputImageDevicePreference::Gamepad_Keyboard_Mouse_Button;
}
else
{
DevicePreference = EInputImageDevicePreference::Gamepad_Mouse_Keyboard;
}
}
const EInputMode LastInput = GetLastInputModeUsed(PlayerIdx);
const EInputMode LastButtonInput = GetLastInputButtonPressed(PlayerIdx);
const EInputMode LastAxisInput = GetLastInputAxisMoved(PlayerIdx);
if (const FKey* PreferredKey = GetPreferedKeyMapping(Keys, DevicePreference, LastInput, LastButtonInput, LastAxisInput))
{
return GetInputImageSpriteFromKey(*PreferredKey, PlayerIdx, Theme);
}
}
return nullptr;
}
TSoftObjectPtr<UDataTable> UStevesGameSubsystem::GetGamepadImages(int PlayerIndex, const UUiTheme* Theme) TSoftObjectPtr<UDataTable> UStevesGameSubsystem::GetGamepadImages(int PlayerIndex, const UUiTheme* Theme)
{ {
// TODO: determine type of controller // TODO: determine type of controller

View File

@ -0,0 +1,4 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "StevesPluginSettings.h"

View File

@ -1,5 +1,9 @@
#include "StevesUEHelpers.h" #include "StevesUEHelpers.h"
#include "ISettingsModule.h"
#include "ISettingsSection.h"
#include "StevesPluginSettings.h"
#define LOCTEXT_NAMESPACE "FStevesUEHelpers" #define LOCTEXT_NAMESPACE "FStevesUEHelpers"
DEFINE_LOG_CATEGORY(LogStevesUEHelpers) DEFINE_LOG_CATEGORY(LogStevesUEHelpers)
@ -8,6 +12,19 @@ void FStevesUEHelpers::StartupModule()
{ {
// This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module // This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module
UE_LOG(LogStevesUEHelpers, Log, TEXT("Steve's UE Helpers Module Started")) UE_LOG(LogStevesUEHelpers, Log, TEXT("Steve's UE Helpers Module Started"))
// register settings
ISettingsModule* SettingsModule = FModuleManager::GetModulePtr<ISettingsModule>("Settings");
if (SettingsModule)
{
ISettingsSectionPtr SettingsSection = SettingsModule->RegisterSettings("Project", "Plugins", "StevesUEHelpers",
LOCTEXT("StevesUEHelpersSettingsName", "StevesUEHelpers"),
LOCTEXT("StevesUEHelpersSettingsDescription", "Configure the helpers plug-in."),
GetMutableDefault<UStevesPluginSettings>()
);
}
} }
void FStevesUEHelpers::ShutdownModule() void FStevesUEHelpers::ShutdownModule()

View File

@ -16,7 +16,9 @@ TSharedRef<SWidget> UInputImage::RebuildWidget()
GS->OnInputModeChanged.AddUniqueDynamic(this, &UInputImage::OnInputModeChanged); GS->OnInputModeChanged.AddUniqueDynamic(this, &UInputImage::OnInputModeChanged);
GS->OnButtonInputModeChanged.AddUniqueDynamic(this, &UInputImage::OnInputModeChanged); GS->OnButtonInputModeChanged.AddUniqueDynamic(this, &UInputImage::OnInputModeChanged);
GS->OnAxisInputModeChanged.AddUniqueDynamic(this, &UInputImage::OnInputModeChanged); GS->OnAxisInputModeChanged.AddUniqueDynamic(this, &UInputImage::OnInputModeChanged);
GS->OnEnhancedInputMappingsChanged.AddUniqueDynamic(this, &UInputImage::OnEnhancedInputMappingsChanged);
} }
UpdateImage(); UpdateImage();
return Ret; return Ret;
@ -35,6 +37,11 @@ void UInputImage::OnInputModeChanged(int ChangedPlayerIdx, EInputMode InputMode)
} }
} }
void UInputImage::OnEnhancedInputMappingsChanged()
{
MarkImageDirty();
}
void UInputImage::SetCustomTheme(UUiTheme* Theme) void UInputImage::SetCustomTheme(UUiTheme* Theme)
{ {
CustomTheme = Theme; CustomTheme = Theme;
@ -94,18 +101,51 @@ void UInputImage::SetFromKey(FKey K)
UpdateImage(); UpdateImage();
} }
void UInputImage::SetFromInputAction(UInputAction* Action)
{
BindingType = EInputBindingType::EnhancedInputAction;
InputAction = Action;
UpdateImage();
}
void UInputImage::UpdateImage() void UInputImage::UpdateImage()
{ {
auto GS = GetStevesGameSubsystem(GetWorld()); auto GS = GetStevesGameSubsystem(GetWorld());
if (GS) if (GS)
{ {
auto Sprite = GS->GetInputImageSprite(BindingType, ActionOrAxisName, Key, DevicePreference, PlayerIndex, CustomTheme); UPaperSprite* Sprite = nullptr;
if (BindingType == EInputBindingType::EnhancedInputAction && !InputAction.IsNull())
{
if (auto IA = InputAction.LoadSynchronous())
{
Sprite = GS->GetInputImageSpriteFromEnhancedInputAction(IA, DevicePreference, PlayerIndex, GetOwningPlayer(), CustomTheme);
}
}
else
{
Sprite = GS->GetInputImageSprite(BindingType, ActionOrAxisName, Key, DevicePreference, PlayerIndex, CustomTheme);
}
if (Sprite) if (Sprite)
{ {
if (bHiddenBecauseBlank)
{
SetVisibility(OldVisibility);
bHiddenBecauseBlank = false;
}
// Match size is needed incase size has changed // Match size is needed incase size has changed
// Need to make it update region in case inside a scale box or something else that needs to adjust // Need to make it update region in case inside a scale box or something else that needs to adjust
SetBrushFromAtlasInterface(Sprite, true); SetBrushFromAtlasInterface(Sprite, true);
} }
else
{
if (IsVisible())
{
bHiddenBecauseBlank = true;
OldVisibility = GetVisibility();
SetVisibility(ESlateVisibility::Hidden);
}
}
} }
bIsDirty = false; bIsDirty = false;
DelayUpdate = 0; DelayUpdate = 0;
@ -114,7 +154,7 @@ void UInputImage::UpdateImage()
void UInputImage::MarkImageDirty() void UInputImage::MarkImageDirty()
{ {
bIsDirty = true; bIsDirty = true;
DelayUpdate = 0.5f; DelayUpdate = 0.1f;
} }
// Tickables // Tickables

View File

@ -4,6 +4,7 @@
#include "StevesHelperCommon.h" #include "StevesHelperCommon.h"
#include "StevesUEHelpers.h" #include "StevesUEHelpers.h"
#include "Fonts/FontMeasure.h" #include "Fonts/FontMeasure.h"
#include "Kismet/GameplayStatics.h"
#include "Misc/DefaultValueHelper.h" #include "Misc/DefaultValueHelper.h"
#include "Widgets/Layout/SScaleBox.h" #include "Widgets/Layout/SScaleBox.h"
#include "Widgets/Images/SImage.h" #include "Widgets/Images/SImage.h"
@ -18,6 +19,8 @@ struct FRichTextInputImageParams
FName ActionOrAxisName; FName ActionOrAxisName;
/// If BindingType is Key, the key /// If BindingType is Key, the key
FKey Key; FKey Key;
/// If binding type is EnhancedInputAction, a reference to an enhanced input action
TSoftObjectPtr<UInputAction> InputAction;
/// Player index, if binding type is action or axis /// Player index, if binding type is action or axis
int PlayerIndex; int PlayerIndex;
/// Where there are multiple mappings, which to prefer /// Where there are multiple mappings, which to prefer
@ -38,6 +41,8 @@ protected:
FName ActionOrAxisName; FName ActionOrAxisName;
/// If BindingType is Key, the key /// If BindingType is Key, the key
FKey Key; FKey Key;
/// If binding type is EnhancedInputAction, a reference to an enhanced input action
TSoftObjectPtr<UInputAction> InputAction;
/// Player index, if binding type is action or axis /// Player index, if binding type is action or axis
int PlayerIndex = 0; int PlayerIndex = 0;
/// Where there are multiple mappings, which to prefer /// Where there are multiple mappings, which to prefer
@ -66,6 +71,7 @@ public:
ActionOrAxisName = InParams.ActionOrAxisName; ActionOrAxisName = InParams.ActionOrAxisName;
DevicePreference = InParams.DevicePreference; DevicePreference = InParams.DevicePreference;
Key = InParams.Key; Key = InParams.Key;
InputAction = InParams.InputAction;
PlayerIndex = InParams.PlayerIndex; PlayerIndex = InParams.PlayerIndex;
Decorator = InParams.Decorator; Decorator = InParams.Decorator;
RequestedWidth = Width; RequestedWidth = Width;
@ -77,7 +83,7 @@ public:
// We will need to do the work to update the brush from the main thread later // We will need to do the work to update the brush from the main thread later
// We can use static methods though // We can use static methods though
if (InParams.InitialSprite) if (IsValid(InParams.InitialSprite))
UStevesGameSubsystem::SetBrushFromAtlas(&Brush, InParams.InitialSprite, true); UStevesGameSubsystem::SetBrushFromAtlas(&Brush, InParams.InitialSprite, true);
TimeUntilNextSpriteCheck = 0.25f; TimeUntilNextSpriteCheck = 0.25f;
@ -128,7 +134,19 @@ public:
if (GS) if (GS)
{ {
// Can only support default theme, no way to edit theme in decorator config // Can only support default theme, no way to edit theme in decorator config
auto Sprite = GS->GetInputImageSprite(BindingType, ActionOrAxisName, Key, DevicePreference, PlayerIndex); UPaperSprite* Sprite = nullptr;
if (BindingType == EInputBindingType::EnhancedInputAction && !InputAction.IsNull())
{
if (auto IA = InputAction.LoadSynchronous())
{
auto PC = UGameplayStatics::GetPlayerController(Decorator->GetWorld(), PlayerIndex);
Sprite = GS->GetInputImageSpriteFromEnhancedInputAction(IA, DevicePreference, PlayerIndex, PC);
}
}
else
{
Sprite = GS->GetInputImageSprite(BindingType, ActionOrAxisName, Key, DevicePreference, PlayerIndex);
}
if (Sprite && Brush.GetResourceObject() != Sprite) if (Sprite && Brush.GetResourceObject() != Sprite)
{ {
UStevesGameSubsystem::SetBrushFromAtlas(&Brush, Sprite, true); UStevesGameSubsystem::SetBrushFromAtlas(&Brush, Sprite, true);
@ -175,7 +193,8 @@ public:
{ {
return RunParseResult.MetaData.Contains(TEXT("key")) || return RunParseResult.MetaData.Contains(TEXT("key")) ||
RunParseResult.MetaData.Contains(TEXT("action")) || RunParseResult.MetaData.Contains(TEXT("action")) ||
RunParseResult.MetaData.Contains(TEXT("axis")); RunParseResult.MetaData.Contains(TEXT("axis")) ||
RunParseResult.MetaData.Contains(TEXT("eaction"));
} }
return false; return false;
@ -191,6 +210,8 @@ protected:
Params.Key = EKeys::AnyKey; Params.Key = EKeys::AnyKey;
Params.Decorator = Decorator; Params.Decorator = Decorator;
auto GS = GetStevesGameSubsystem(Decorator->GetWorld());
if (const FString* PlayerStr = RunInfo.MetaData.Find(TEXT("player"))) if (const FString* PlayerStr = RunInfo.MetaData.Find(TEXT("player")))
{ {
int PTemp; int PTemp;
@ -212,6 +233,15 @@ protected:
Params.BindingType = EInputBindingType::Axis; Params.BindingType = EInputBindingType::Axis;
Params.ActionOrAxisName = **AxisStr; Params.ActionOrAxisName = **AxisStr;
} }
else if (const FString* EInputStr = RunInfo.MetaData.Find(TEXT("eaction")))
{
Params.BindingType = EInputBindingType::EnhancedInputAction;
// Try to find the input action
if (GS)
{
Params.InputAction = GS->FindEnhancedInputAction(*EInputStr);
}
}
if (const FString* PreferStr = RunInfo.MetaData.Find(TEXT("prefer"))) if (const FString* PreferStr = RunInfo.MetaData.Find(TEXT("prefer")))
{ {
@ -240,11 +270,21 @@ protected:
// Look up the initial sprite here // Look up the initial sprite here
// The Slate widget can't do it in Construct because World pointer doesn't work (thread issues?) // The Slate widget can't do it in Construct because World pointer doesn't work (thread issues?)
// Also annoying: can't keep Brush on this class because this method is const. UGH // Also annoying: can't keep Brush on this class because this method is const. UGH
auto GS = GetStevesGameSubsystem(Decorator->GetWorld());
if (GS) if (GS)
{ {
// Can only support default theme, no way to edit theme in decorator config if (Params.BindingType == EInputBindingType::EnhancedInputAction && !Params.InputAction.IsNull())
Params.InitialSprite = GS->GetInputImageSprite(Params.BindingType, Params.ActionOrAxisName, Params.Key, Params.DevicePreference, Params.PlayerIndex); {
if (auto IA = Params.InputAction.LoadSynchronous())
{
auto PC = UGameplayStatics::GetPlayerController(Decorator->GetWorld(), Params.PlayerIndex);
Params.InitialSprite = GS->GetInputImageSpriteFromEnhancedInputAction(IA, Params.DevicePreference, Params.PlayerIndex, PC);
}
}
else
{
// Can only support default theme, no way to edit theme in decorator config
Params.InitialSprite = GS->GetInputImageSprite(Params.BindingType, Params.ActionOrAxisName, Params.Key, Params.DevicePreference, Params.PlayerIndex);
}
} }
else else
{ {

View File

@ -49,3 +49,67 @@ void SetWidgetFocusProperly(UWidget* Widget)
Widget->SetFocus(); Widget->SetFocus();
} }
const FKey* GetPreferedKeyMapping(const TArray<FKey>& AllKeys,
EInputImageDevicePreference DevicePreference,
EInputMode LastInputDevice,
EInputMode LastButtonInputDevice,
EInputMode LastAxisInputDevice)
{
// Same as GetPreferedActionOrAxisMapping, just with key directly
const FKey* MouseMapping = nullptr;
const FKey* KeyboardMapping = nullptr;
const FKey* GamepadMapping = nullptr;
for (const FKey& Key : AllKeys)
{
// notice how we take the LAST one in the list as the final version
// this is because UInputSettings::GetActionMappingByName *reverses* the mapping list from Project Settings
if (Key.IsGamepadKey())
{
GamepadMapping = &Key;
}
else if (Key.IsMouseButton()) // registers true for mouse axes too
{
MouseMapping = &Key;
}
else
{
KeyboardMapping = &Key;
}
}
const FKey* Preferred = nullptr;
if (GamepadMapping && LastInputDevice == EInputMode::Gamepad)
{
// Always prefer gamepad if used last
Preferred = GamepadMapping;
}
else
{
switch (DevicePreference)
{
// Auto should be pre-converted to another
case EInputImageDevicePreference::Auto:
UE_LOG(LogStevesUI, Error, TEXT("Device Preference should have been converted before this call"))
break;
case EInputImageDevicePreference::Gamepad_Keyboard_Mouse:
Preferred = KeyboardMapping ? KeyboardMapping : MouseMapping;
break;
case EInputImageDevicePreference::Gamepad_Mouse_Keyboard:
Preferred = MouseMapping ? MouseMapping : KeyboardMapping;
break;
case EInputImageDevicePreference::Gamepad_Keyboard_Mouse_Button:
// Use the latest button press
Preferred = (MouseMapping && (LastButtonInputDevice == EInputMode::Mouse || !KeyboardMapping)) ? MouseMapping : KeyboardMapping;
break;
case EInputImageDevicePreference::Gamepad_Keyboard_Mouse_Axis:
// Use the latest button press
Preferred = (MouseMapping && (LastAxisInputDevice == EInputMode::Mouse || !KeyboardMapping)) ? MouseMapping : KeyboardMapping;
break;
default:
break;
}
}
return Preferred;
}

View File

@ -89,3 +89,10 @@ const T* GetPreferedActionOrAxisMapping(const TArray<T>& AllMappings, const FNam
} }
return Preferred; return Preferred;
} }
const FKey* GetPreferedKeyMapping(const TArray<FKey>& AllKeys,
EInputImageDevicePreference DevicePreference,
EInputMode LastInputDevice,
EInputMode LastButtonInputDevice,
EInputMode LastAxisInputDevice);

View File

@ -8,11 +8,13 @@
#include "StevesHelperCommon.h" #include "StevesHelperCommon.h"
#include "StevesTextureRenderTargetPool.h" #include "StevesTextureRenderTargetPool.h"
#include "StevesUI/FocusSystem.h" #include "StevesUI/FocusSystem.h"
#include "StevesUI/InputImage.h"
#include "StevesUI/UiTheme.h" #include "StevesUI/UiTheme.h"
#include "StevesGameSubsystem.generated.h" #include "StevesGameSubsystem.generated.h"
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnInputModeChanged, int, PlayerIndex, EInputMode, InputMode); DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnInputModeChanged, int, PlayerIndex, EInputMode, InputMode);
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnEnhancedInputMappingsChanged);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnWindowForegroundChanged, bool, bFocussed); DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnWindowForegroundChanged, bool, bFocussed);
/// Entry point for all the top-level features of the helper system /// Entry point for all the top-level features of the helper system
@ -37,7 +39,6 @@ public:
virtual void Initialize(FSubsystemCollectionBase& Collection) override; virtual void Initialize(FSubsystemCollectionBase& Collection) override;
virtual void Deinitialize() override; virtual void Deinitialize() override;
protected: protected:
DECLARE_DELEGATE_TwoParams(FInternalInputModeChanged, int /* PlayerIndex */, EInputMode) DECLARE_DELEGATE_TwoParams(FInternalInputModeChanged, int /* PlayerIndex */, EInputMode)
/** /**
@ -160,6 +161,12 @@ public:
/// last axis moved was still mouse, you'd get this event later /// last axis moved was still mouse, you'd get this event later
UPROPERTY(BlueprintAssignable) UPROPERTY(BlueprintAssignable)
FOnInputModeChanged OnAxisInputModeChanged; FOnInputModeChanged OnAxisInputModeChanged;
/// Event raised justr after the Enhanced Input mappings have changed
/// Right now, this has to be user-triggered via NotifyEnhancedInputMappingsChanged, because the Enhanced Input
/// plugin provides NO events to monitor it (sigh)
UPROPERTY(BlueprintAssignable)
FOnEnhancedInputMappingsChanged OnEnhancedInputMappingsChanged;
/// Event raised when the game window's foreground status changes /// Event raised when the game window's foreground status changes
UPROPERTY(BlueprintAssignable) UPROPERTY(BlueprintAssignable)
@ -209,6 +216,21 @@ public:
int PlayerIndex = 0, int PlayerIndex = 0,
const UUiTheme* Theme = nullptr); const UUiTheme* Theme = nullptr);
/**
* @brief Get an input button / key / axis image as a sprite based on an enhanced input action
* @param Action The input action
* @param DevicePreference The order of preference for images where multiple devices have mappings. In the case of multiple mappings for the same device, the first one will be used.
* @param PlayerIdx The player index to look up the binding for
* @param PC The player controller to look up the binding for
* @param Theme Optional explicit theme, if blank use the default theme
* @return
*/
UPaperSprite* GetInputImageSpriteFromEnhancedInputAction(UInputAction* Action,
EInputImageDevicePreference DevicePreference,
int PlayerIdx,
APlayerController* PC,
UUiTheme* Theme = nullptr);
/** /**
* @brief Get an input button / key image from an action * @brief Get an input button / key image from an action
* @param Name The name of the action * @param Name The name of the action
@ -266,4 +288,16 @@ public:
*/ */
FStevesTextureRenderTargetPoolPtr GetTextureRenderTargetPool(FName Name, bool bAutoCreate = true); FStevesTextureRenderTargetPoolPtr GetTextureRenderTargetPool(FName Name, bool bAutoCreate = true);
/**
* Notify this subsystem that changes have been made to the Enhanced Input mappings, e.g. adding or removing a context.
* Unfortunately, the Enhanced Input plugin currently provides NO WAY for us to monitor context changes automatically,
* so we need the user to tell us when they make a change.
* This call is however slightly delayed before being acted upon, because EI defers the rebuild of mappings until the next tick.
*/
void NotifyEnhancedInputMappingsChanged();
/** Attempt to find an enhanced input action by name in the configured folders.
*/
TSoftObjectPtr<UInputAction> FindEnhancedInputAction(const FString& Name);
}; };

View File

@ -41,12 +41,14 @@ enum class EGamePauseChange : uint8
UENUM(BlueprintType) UENUM(BlueprintType)
enum class EInputBindingType : uint8 enum class EInputBindingType : uint8
{ {
/// A button action, will be looked up based on input mappings /// A legacy button action, will be looked up based on input mappings
Action = 0, Action = 0,
/// An axis action, will be looked up based on input mappings /// An legacy axis action, will be looked up based on input mappings
Axis = 1, Axis = 1,
/// A manually specified FKey (which can be key, button, axis) /// A manually specified FKey (which can be key, button, axis)
Key = 2 Key = 2,
/// An EnhancedInput action
EnhancedInputAction = 3
}; };
/// What order of preference should we return input images where an action/axis has multiple mappings /// What order of preference should we return input images where an action/axis has multiple mappings

View File

@ -0,0 +1,23 @@
#pragma once
#include "CoreMinimal.h"
#include "StevesPluginSettings.generated.h"
/**
* Settings for the plug-in.
*/
UCLASS(config=Engine)
class STEVESUEHELPERS_API UStevesPluginSettings
: public UObject
{
GENERATED_BODY()
public:
/// Which directories to search for Enhanced Input Actions when referenced just by name in e.g. Rich Text Decorator
UPROPERTY(config, EditAnywhere, Category = StevesUEHelpers, meta = (DisplayName = "Directories to search for Enhanced Input Actions", RelativeToGameContentDir, LongPackageName))
TArray<FDirectoryPath> EnhancedInputActionSearchDirectories;
UStevesPluginSettings() {}
};

View File

@ -22,6 +22,10 @@ protected:
/// If BindingType is Action/Axis, the name of it /// If BindingType is Action/Axis, the name of it
UPROPERTY(EditAnywhere) UPROPERTY(EditAnywhere)
FName ActionOrAxisName; FName ActionOrAxisName;
/// If binding type is EnhancedInputAction, a reference to an enhanced input action
UPROPERTY(EditAnywhere) // can't be inside #if
TSoftObjectPtr<UInputAction> InputAction;
/// Where there are multiple mappings, which to prefer /// Where there are multiple mappings, which to prefer
UPROPERTY(EditAnywhere) UPROPERTY(EditAnywhere)
@ -42,6 +46,8 @@ protected:
bool bSubbedToInputEvents = false; bool bSubbedToInputEvents = false;
bool bIsDirty = true; bool bIsDirty = true;
float DelayUpdate = 0; float DelayUpdate = 0;
bool bHiddenBecauseBlank;
ESlateVisibility OldVisibility;
public: public:
@ -57,6 +63,10 @@ public:
UFUNCTION(BlueprintCallable) UFUNCTION(BlueprintCallable)
virtual void SetFromKey(FKey K); virtual void SetFromKey(FKey K);
/// Tell this image to display Enhanced InputAction
UFUNCTION(BlueprintCallable)
virtual void SetFromInputAction(UInputAction* Action);
/// Get the binding type that we'll use to populate the image /// Get the binding type that we'll use to populate the image
UFUNCTION(BlueprintCallable) UFUNCTION(BlueprintCallable)
virtual EInputBindingType GetBindingType() const { return BindingType; } virtual EInputBindingType GetBindingType() const { return BindingType; }
@ -96,5 +106,7 @@ protected:
UFUNCTION() UFUNCTION()
void OnInputModeChanged(int ChangedPlayerIdx, EInputMode InputMode); void OnInputModeChanged(int ChangedPlayerIdx, EInputMode InputMode);
UFUNCTION()
void OnEnhancedInputMappingsChanged();
}; };

View File

@ -25,10 +25,11 @@ public class StevesUEHelpers : ModuleRules
"CoreUObject", "CoreUObject",
"Engine", "Engine",
"InputCore", "InputCore",
"EnhancedInput",
"Slate", "Slate",
"SlateCore", "SlateCore",
"UMG", "UMG",
"Paper2D" "Paper2D",
} }
); );

View File

@ -17,10 +17,15 @@ InputImage requires a [UiTheme](UiTheme.md) to operate, which links to the image
### Binding Type ### Binding Type
* "Action" if the image should display the current mapping for an input action * "Enhanced Input Action" to specify an [Enhanced Input](https://docs.unrealengine.com/5.1/en-US/enhanced-input-in-unreal-engine/) action
* "Axis" to look up an input axis * "Action" if the image should display the current mapping for a legacy input action
* "Axis" to look up a legacy input axis
* "Key" to manually specify a key (which can be gamepad or mouse too) * "Key" to manually specify a key (which can be gamepad or mouse too)
### Enhanced Input Action
Pick an Enhanced Input action from the asset browser interface.
### Action or Axis Name ### Action or Axis Name
The name of the input action or axis that should be looked up to determine the The name of the input action or axis that should be looked up to determine the

View File

@ -33,13 +33,21 @@ related to input controls. There are various options:
## Adding input images to rich text ## Adding input images to rich text
### Input Actions ### Enhanced Input Actions
`<input eaction="IA_MyAction"/>`
This displays the image for a bound Enhanced Input action. The name of the action
should match the action name, which is relative to one of the directories
you specify in Project Settings > Plugins > StevesUEHelpers.
### Legacy Input Actions
`<input action="TheActionName"/>` `<input action="TheActionName"/>`
This displays the image for a bound action input, as configured in project settings. This displays the image for a bound action input, as configured in project settings.
### Input Axes ### Legacy Input Axes
`<input axis="TheAxisName"/>` `<input axis="TheAxisName"/>`