Compare commits

...

2 Commits

Author SHA1 Message Date
78c85660c9 Prompt widget is pretty good now 2026-05-04 15:52:05 -04:00
3e7e6a2ae4 More progress on prompt 2026-05-04 15:27:28 -04:00
10 changed files with 156 additions and 90 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -127,14 +127,14 @@ enum class ElxLuaSyntaxCheck : uint8 {
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
// //
// ElxInputMode // ElxControllerType
// //
// The three input modes recognized by the game. // The three types of controller recognized by luprex.
// //
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
UENUM(BlueprintType) UENUM(BlueprintType)
enum class ElxInputMode : uint8 { enum class ElxControllerType : uint8 {
KeyboardMouse, KeyboardMouse,
XboxGamepad, XboxGamepad,
PlayStationGamepad, PlayStationGamepad,

View File

@@ -248,25 +248,6 @@ void AlxPlayerControllerBase::BuildInputStack(TArray<UInputComponent*>& InputSta
} }
} }
static ElxInputMode DetectInputMode(const ULocalPlayer *LocalPlayer)
{
UInputDeviceSubsystem *IDS = GEngine->GetEngineSubsystem<UInputDeviceSubsystem>();
if (!IDS) return ElxInputMode::KeyboardMouse;
FHardwareDeviceIdentifier Device = IDS->GetMostRecentlyUsedHardwareDevice(LocalPlayer->GetPlatformUserId());
if (Device.PrimaryDeviceType != EHardwareDevicePrimaryType::Gamepad)
{
return ElxInputMode::KeyboardMouse;
}
FString DeviceName = Device.HardwareDeviceIdentifier.ToString();
if (DeviceName.Contains(TEXT("PS4")) || DeviceName.Contains(TEXT("PS5")) || DeviceName.Contains(TEXT("PlayStation")))
{
return ElxInputMode::PlayStationGamepad;
}
return ElxInputMode::XboxGamepad;
}
void AlxPlayerControllerBase::UpdateInputMode() void AlxPlayerControllerBase::UpdateInputMode()
{ {
// Get all the various objects we need to be able to manipulate // Get all the various objects we need to be able to manipulate
@@ -323,13 +304,6 @@ void AlxPlayerControllerBase::UpdateInputMode()
GameViewportClient->SetIgnoreInput(false); GameViewportClient->SetIgnoreInput(false);
ElxInputMode NewInputMode = DetectInputMode(LocalPlayer);
if (NewInputMode != CurrentInputMode)
{
CurrentInputMode = NewInputMode;
OnInputModeChanged.Broadcast(CurrentInputMode);
}
// We always put keyboard focus on whatever user widget is in // We always put keyboard focus on whatever user widget is in
// front. If the front widget doesn't want keyboard focus, // front. If the front widget doesn't want keyboard focus,
// then we put keyboard focus on the viewport. // then we put keyboard focus on the viewport.
@@ -346,11 +320,6 @@ void AlxPlayerControllerBase::UpdateInputMode()
} }
} }
ElxInputMode AlxPlayerControllerBase::GetInputMode(const UObject *Context)
{
return FromContext(Context)->CurrentInputMode;
}
void AlxPlayerControllerBase::UpdateLookAt() void AlxPlayerControllerBase::UpdateLookAt()
{ {
UlxTangibleManager *TM = GetGameInstance()->GetSubsystem<UlxTangibleManager>(); UlxTangibleManager *TM = GetGameInstance()->GetSubsystem<UlxTangibleManager>();

View File

@@ -7,8 +7,6 @@
#include "UObject/ObjectKey.h" #include "UObject/ObjectKey.h"
#include "PlayerControllerBase.generated.h" #include "PlayerControllerBase.generated.h"
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FlxInputModeChanged, ElxInputMode, NewMode);
class UlxRootCanvasPanel; class UlxRootCanvasPanel;
class UWidget; class UWidget;
@@ -43,12 +41,6 @@ public:
// Called by GameMode each tick. // Called by GameMode each tick.
void UpdateLookAt(); void UpdateLookAt();
UFUNCTION(BlueprintPure, meta = (WorldContext = "Context"), Category = "Luprex|Input Mode")
static ElxInputMode GetInputMode(const UObject *Context);
UPROPERTY(BlueprintAssignable, Category = "Luprex|Input Mode")
FlxInputModeChanged OnInputModeChanged;
// Called by GameMode each tick. GCs dead requests and will // Called by GameMode each tick. GCs dead requests and will
// eventually reconcile focus, pointer, and capture state. // eventually reconcile focus, pointer, and capture state.
void UpdateInputMode(); void UpdateInputMode();
@@ -97,7 +89,4 @@ public:
UlxRootCanvasPanel *RootCanvas = nullptr; UlxRootCanvasPanel *RootCanvas = nullptr;
bool MustCallLookAtChanged = false; bool MustCallLookAtChanged = false;
UPROPERTY()
ElxInputMode CurrentInputMode = ElxInputMode::KeyboardMouse;
}; };

View File

@@ -1,5 +1,10 @@
#include "PromptWidget.h" #include "PromptWidget.h"
#include "UtilityLibrary.h"
#include "PlayerControllerBase.h" #include "PlayerControllerBase.h"
#include "Engine/Engine.h"
#include "GameFramework/InputDeviceSubsystem.h"
#include "InputAction.h"
#include "InputMappingContext.h"
#include "Widgets/SOverlay.h" #include "Widgets/SOverlay.h"
#include "Widgets/Images/SImage.h" #include "Widgets/Images/SImage.h"
#include "Widgets/Text/STextBlock.h" #include "Widgets/Text/STextBlock.h"
@@ -57,25 +62,30 @@ int32 UlxPromptWidget::ChooseIcon(bool Playstation, FKey Key) const
void UlxPromptWidget::ChooseAppearance(int32 &OutIcon, FString &OutGlyph) void UlxPromptWidget::ChooseAppearance(int32 &OutIcon, FString &OutGlyph)
{ {
UWorld* World = GetWorld(); FKey Key = (ControllerType == ElxControllerType::KeyboardMouse) ? KeyboardKey : GamepadKey;
APlayerController* PC = World ? World->GetFirstPlayerController() : nullptr; OutIcon = ChooseIcon(ControllerType == ElxControllerType::PlayStationGamepad, Key);
AlxPlayerControllerBase* LPC = Cast<AlxPlayerControllerBase>(PC);
ElxInputMode Mode = LPC ? LPC->CurrentInputMode : ElxInputMode::KeyboardMouse;
FKey Key = (Mode == ElxInputMode::KeyboardMouse) ? KeyboardKey : GamepadKey;
OutIcon = ChooseIcon(Mode == ElxInputMode::PlayStationGamepad, Key);
if (OutIcon == 0) OutGlyph = Key.GetDisplayName().ToString(); if (OutIcon == 0) OutGlyph = Key.GetDisplayName().ToString();
else OutGlyph = TEXT(""); else OutGlyph = TEXT("");
} }
void UlxPromptWidget::OnHardwareDeviceChanged(const FPlatformUserId UserId, const FInputDeviceId DeviceId)
{
ElxControllerType Type = UlxUtilityLibrary::DetectControllerType(GetOwningLocalPlayer());
if (ControllerType != Type)
{
ControllerType = Type;
SynchronizeProperties();
}
}
FBox2f UlxPromptWidget::GetIconUVs(int32 IconIndex) FBox2f UlxPromptWidget::GetIconUVs(int32 IconIndex)
{ {
const float ColWidth = 1.0f / 4.0f; const float ColWidth = 1.0f / 4.0f;
const float RowHeight = 1.0f / 8.0f; const float RowHeight = 1.0f / 8.0f;
float U = (IconIndex % 4) * ColWidth; int32 Col = IconIndex % 4;
float V = (IconIndex / 4) * RowHeight; int32 Row = IconIndex / 4;
float U = Col * ColWidth;
float V = Row * RowHeight;
return FBox2f(FVector2f(U, V), FVector2f(U + ColWidth, V + RowHeight)); return FBox2f(FVector2f(U, V), FVector2f(U + ColWidth, V + RowHeight));
} }
@@ -99,8 +109,12 @@ void UlxPromptWidget::SynchronizeProperties()
MyBrush.SetUVRegion(GetIconUVs(Icon)); MyBrush.SetUVRegion(GetIconUVs(Icon));
MyImage->InvalidateImage(); MyImage->InvalidateImage();
MyImage->SetDesiredSizeOverride(Size); MyBox->SetWidthOverride(Size.X);
MyGlyphSlot->SetPadding(GetScaledMargins()); MyBox->SetHeightOverride(Size.Y);
const float Extra = Depressed ? 0.05f : 0.0f;
const FMargin ExtraPadding(Extra * Size.X, Extra * Size.Y, Extra * Size.X, Extra * Size.Y);
MyImageSlot->SetPadding(ExtraPadding);
MyGlyphSlot->SetPadding(GetScaledMargins() + ExtraPadding);
MyGlyph->SetColorAndOpacity(GlyphColor); MyGlyph->SetColorAndOpacity(GlyphColor);
if (!Glyph.IsEmpty()) if (!Glyph.IsEmpty())
@@ -141,6 +155,15 @@ void UlxPromptWidget::SetGlyphColor(FLinearColor InColor)
} }
} }
void UlxPromptWidget::SetDepressed(bool InDepressed)
{
if (Depressed != InDepressed)
{
Depressed = InDepressed;
SynchronizeProperties();
}
}
void UlxPromptWidget::SetKeys(FKey InGamepadKey, FKey InKeyboardKey) void UlxPromptWidget::SetKeys(FKey InGamepadKey, FKey InKeyboardKey)
{ {
if ((GamepadKey != InGamepadKey) || (KeyboardKey != InKeyboardKey)) if ((GamepadKey != InGamepadKey) || (KeyboardKey != InKeyboardKey))
@@ -151,42 +174,82 @@ void UlxPromptWidget::SetKeys(FKey InGamepadKey, FKey InKeyboardKey)
} }
} }
void UlxPromptWidget::SetKeysFromBindings(const UInputMappingContext* InputMappingContext, const UInputAction* EnhancedInputAction)
{
check(InputMappingContext);
check(EnhancedInputAction);
FKey NewFirstKey;
FKey NewGamepadKey;
FKey NewKeyboardKey;
for (const FEnhancedActionKeyMapping& Mapping : InputMappingContext->GetMappings())
{
if (Mapping.Action != EnhancedInputAction) continue;
FKey Key = Mapping.Key;
if (Key.IsDigital())
{
if (!NewFirstKey.IsValid()) NewFirstKey = Mapping.Key;
if (Key.IsTouch()) { /* not supported */ }
else if (Key.IsGesture()) { /* not supported */ }
else if (Key.IsGamepadKey()) { if (!NewGamepadKey.IsValid()) NewGamepadKey = Key; }
else { if (!NewKeyboardKey.IsValid()) NewKeyboardKey = Key; }
}
}
if (!NewGamepadKey.IsValid()) NewGamepadKey = NewFirstKey;
if (!NewKeyboardKey.IsValid()) NewKeyboardKey = NewFirstKey;
SetKeys(NewGamepadKey, NewKeyboardKey);
}
TSharedRef<SWidget> UlxPromptWidget::RebuildWidget() TSharedRef<SWidget> UlxPromptWidget::RebuildWidget()
{ {
int32 Icon = 0; if (!IsDesignTime())
FString Glyph; {
ChooseAppearance(Icon, Glyph); ControllerType = UlxUtilityLibrary::DetectControllerType(GetOwningLocalPlayer());
if (UInputDeviceSubsystem* IDS = GEngine->GetEngineSubsystem<UInputDeviceSubsystem>())
{
IDS->OnInputHardwareDeviceChanged.AddDynamic(this, &UlxPromptWidget::OnHardwareDeviceChanged);
}
}
MyBrush.SetResourceObject(ButtonAtlas); MyBrush.SetResourceObject(ButtonAtlas.Get());
MyBrush.ImageSize = Size;
MyBrush.SetUVRegion(GetIconUVs(Icon));
SAssignNew(MyImage, SImage).Image(&MyBrush); SAssignNew(MyImage, SImage).Image(&MyBrush);
EVisibility GlyphVisibility = Glyph.IsEmpty() ? EVisibility::Collapsed : EVisibility::HitTestInvisible; SAssignNew(MyGlyph, STextBlock);
SAssignNew(MyGlyph, STextBlock)
.Text(FText::FromString(Glyph))
.ColorAndOpacity(GlyphColor)
.Visibility(GlyphVisibility);
SAssignNew(MyScaleBox, SScaleBox) SAssignNew(MyScaleBox, SScaleBox)
.Stretch(EStretch::ScaleToFit) .Stretch(EStretch::ScaleToFit)
[ MyGlyph.ToSharedRef() ]; [ MyGlyph.ToSharedRef() ];
MyOverlay = SNew(SOverlay) MyOverlay = SNew(SOverlay);
+ SOverlay::Slot() [ MyImage.ToSharedRef() ]; MyOverlay->AddSlot().Expose(MyImageSlot)
[ MyImage.ToSharedRef() ];
MyOverlay->AddSlot().Padding(GetScaledMargins()).HAlign(HAlign_Fill).VAlign(VAlign_Fill).Expose(MyGlyphSlot) MyOverlay->AddSlot().HAlign(HAlign_Fill).VAlign(VAlign_Fill).Expose(MyGlyphSlot)
[ MyScaleBox.ToSharedRef() ]; [ MyScaleBox.ToSharedRef() ];
return MyOverlay.ToSharedRef(); SAssignNew(MyBox, SBox)
[ MyOverlay.ToSharedRef() ];
SynchronizeProperties();
return MyBox.ToSharedRef();
} }
void UlxPromptWidget::ReleaseSlateResources(bool bReleaseChildren) void UlxPromptWidget::ReleaseSlateResources(bool bReleaseChildren)
{ {
Super::ReleaseSlateResources(bReleaseChildren); Super::ReleaseSlateResources(bReleaseChildren);
if (!IsDesignTime())
{
if (UInputDeviceSubsystem* IDS = GEngine->GetEngineSubsystem<UInputDeviceSubsystem>())
{
IDS->OnInputHardwareDeviceChanged.RemoveDynamic(this, &UlxPromptWidget::OnHardwareDeviceChanged);
}
}
MyBox.Reset();
MyOverlay.Reset(); MyOverlay.Reset();
MyImage.Reset(); MyImage.Reset();
MyScaleBox.Reset(); MyScaleBox.Reset();
MyGlyph.Reset(); MyGlyph.Reset();
MyImageSlot = nullptr;
MyGlyphSlot = nullptr; MyGlyphSlot = nullptr;
} }

View File

@@ -4,9 +4,14 @@
#include "Common.h" #include "Common.h"
#include "Components/Widget.h" #include "Components/Widget.h"
#include "InputCoreTypes.h" #include "InputCoreTypes.h"
#include "Widgets/SOverlay.h"
#include "Widgets/Layout/SBox.h"
#include "Widgets/Layout/SScaleBox.h" #include "Widgets/Layout/SScaleBox.h"
#include "PromptWidget.generated.h" #include "PromptWidget.generated.h"
class UInputAction;
class UInputMappingContext;
UCLASS(BlueprintType, Blueprintable) UCLASS(BlueprintType, Blueprintable)
class INTEGRATION_API UlxPromptWidget : public UWidget class INTEGRATION_API UlxPromptWidget : public UWidget
@@ -17,8 +22,14 @@ public:
UPROPERTY(EditAnywhere, Category="Prompt") UPROPERTY(EditAnywhere, Category="Prompt")
TObjectPtr<UTexture2D> ButtonAtlas; TObjectPtr<UTexture2D> ButtonAtlas;
UFUNCTION(BlueprintCallable, Category="Prompt") UPROPERTY(EditAnywhere, Category="Prompt")
void SetKeys(FKey InGamepadKey, FKey InKeyboardKey); ElxControllerType ControllerType = ElxControllerType::KeyboardMouse;
UPROPERTY(EditAnywhere, Category="Prompt")
FKey GamepadKey = EKeys::Gamepad_FaceButton_Left;
UPROPERTY(EditAnywhere, Category="Prompt")
FKey KeyboardKey = EKeys::Z;
UPROPERTY(EditAnywhere, Setter, Category="Prompt") UPROPERTY(EditAnywhere, Setter, Category="Prompt")
FVector2D Size = FVector2D(64, 64); FVector2D Size = FVector2D(64, 64);
@@ -29,6 +40,15 @@ public:
UPROPERTY(EditAnywhere, Setter, Category="Prompt") UPROPERTY(EditAnywhere, Setter, Category="Prompt")
FLinearColor GlyphColor = FLinearColor::White; FLinearColor GlyphColor = FLinearColor::White;
UPROPERTY(EditAnywhere, Setter, Category="Prompt")
bool Depressed = false;
public:
UFUNCTION(BlueprintCallable, Category="Prompt")
void SetKeys(FKey InGamepadKey, FKey InKeyboardKey);
UFUNCTION(BlueprintCallable, Category="Prompt")
void SetKeysFromBindings(const UInputMappingContext* InputMappingContext, const UInputAction* EnhancedInputAction);
UFUNCTION(BlueprintCallable, Category="Prompt") UFUNCTION(BlueprintCallable, Category="Prompt")
void SetGlyphMargins(FMargin InMargins); void SetGlyphMargins(FMargin InMargins);
@@ -36,27 +56,28 @@ public:
UFUNCTION(BlueprintCallable, Category="Prompt") UFUNCTION(BlueprintCallable, Category="Prompt")
void SetGlyphColor(FLinearColor InColor); void SetGlyphColor(FLinearColor InColor);
UFUNCTION(BlueprintCallable, Category="Prompt") UFUNCTION(BlueprintCallable, Category="Prompt")
void SetSize(FVector2D InSize); void SetSize(FVector2D InSize);
UFUNCTION(BlueprintCallable, Category="Prompt")
void SetDepressed(bool InDepressed);
UFUNCTION()
void OnHardwareDeviceChanged(const FPlatformUserId UserId, const FInputDeviceId DeviceId);
protected: protected:
virtual TSharedRef<SWidget> RebuildWidget() override; virtual TSharedRef<SWidget> RebuildWidget() override;
virtual void SynchronizeProperties() override; virtual void SynchronizeProperties() override;
virtual void ReleaseSlateResources(bool bReleaseChildren) override; virtual void ReleaseSlateResources(bool bReleaseChildren) override;
UPROPERTY(EditAnywhere, Category="Prompt")
FKey GamepadKey = EKeys::Gamepad_FaceButton_Left;
UPROPERTY(EditAnywhere, Category="Prompt")
FKey KeyboardKey = EKeys::Z;
private: private:
FSlateBrush MyBrush; FSlateBrush MyBrush;
TSharedPtr<SBox> MyBox;
TSharedPtr<SOverlay> MyOverlay; TSharedPtr<SOverlay> MyOverlay;
TSharedPtr<SImage> MyImage; TSharedPtr<SImage> MyImage;
TSharedPtr<SScaleBox> MyScaleBox; TSharedPtr<SScaleBox> MyScaleBox;
TSharedPtr<STextBlock> MyGlyph; TSharedPtr<STextBlock> MyGlyph;
SOverlay::FOverlaySlot* MyImageSlot = nullptr;
SOverlay::FOverlaySlot* MyGlyphSlot = nullptr; SOverlay::FOverlaySlot* MyGlyphSlot = nullptr;
FBox2f GetIconUVs(int32 IconIndex); FBox2f GetIconUVs(int32 IconIndex);

View File

@@ -14,6 +14,8 @@
#include "EnhancedInputComponent.h" #include "EnhancedInputComponent.h"
#include "Animation/AnimSequenceBase.h" #include "Animation/AnimSequenceBase.h"
#include "GameFramework/Pawn.h" #include "GameFramework/Pawn.h"
#include "GameFramework/InputDeviceSubsystem.h"
#include "GameFramework/InputSettings.h"
#define LOCTEXT_NAMESPACE "Luprex Utility" #define LOCTEXT_NAMESPACE "Luprex Utility"
@@ -275,3 +277,23 @@ void UlxUtilityLibrary::ValidateLuaExpr(
FlxLockedWrapper w; FlxLockedWrapper w;
Status = w.ValidateLuaExpr(Code, ErrorMessage); Status = w.ValidateLuaExpr(Code, ErrorMessage);
} }
ElxControllerType UlxUtilityLibrary::DetectControllerType(ULocalPlayer *Player)
{
UInputDeviceSubsystem *IDS = GEngine->GetEngineSubsystem<UInputDeviceSubsystem>();
if (!IDS) return ElxControllerType::KeyboardMouse;
FHardwareDeviceIdentifier Device = IDS->GetMostRecentlyUsedHardwareDevice(Player->GetPlatformUserId());
if (Device.PrimaryDeviceType != EHardwareDevicePrimaryType::Gamepad)
{
return ElxControllerType::KeyboardMouse;
}
FString DeviceName = Device.HardwareDeviceIdentifier.ToString();
if (DeviceName.Contains(TEXT("PS4")) || DeviceName.Contains(TEXT("PS5")) || DeviceName.Contains(TEXT("PlayStation")))
{
return ElxControllerType::PlayStationGamepad;
}
return ElxControllerType::XboxGamepad;
}

View File

@@ -179,4 +179,9 @@ public:
// //
UFUNCTION(BlueprintCallable, meta = (WorldContext = "context"), Category = "Luprex|Utility") UFUNCTION(BlueprintCallable, meta = (WorldContext = "context"), Category = "Luprex|Utility")
static void ValidateLuaExpr(ElxLuaSyntaxCheck &Status, FString &ErrorMessage, UObject *context, const FString &Code); static void ValidateLuaExpr(ElxLuaSyntaxCheck &Status, FString &ErrorMessage, UObject *context, const FString &Code);
// Determine what type of controller the game is currently using
//
UFUNCTION(BlueprintCallable, category="Luprex|Utility")
static ElxControllerType DetectControllerType(ULocalPlayer *Player);
}; };