Initial work on prompt widget
This commit is contained in:
@@ -125,6 +125,21 @@ enum class ElxLuaSyntaxCheck : uint8 {
|
||||
InvalidLua,
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
//
|
||||
// ElxInputMode
|
||||
//
|
||||
// The three input modes recognized by the game.
|
||||
//
|
||||
////////////////////////////////////////////////////////////
|
||||
|
||||
UENUM(BlueprintType)
|
||||
enum class ElxInputMode : uint8 {
|
||||
KeyboardMouse,
|
||||
XboxGamepad,
|
||||
PlayStationGamepad,
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Log Categories
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#include "PlayerControllerBase.h"
|
||||
#include "Common.h"
|
||||
#include "GameFramework/InputDeviceSubsystem.h"
|
||||
#include "GameFramework/InputSettings.h"
|
||||
#include "RootCanvas.h"
|
||||
#include "Tangible.h"
|
||||
#include "TangibleManager.h"
|
||||
@@ -246,6 +248,25 @@ 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()
|
||||
{
|
||||
// Get all the various objects we need to be able to manipulate
|
||||
@@ -302,6 +323,13 @@ void AlxPlayerControllerBase::UpdateInputMode()
|
||||
|
||||
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
|
||||
// front. If the front widget doesn't want keyboard focus,
|
||||
// then we put keyboard focus on the viewport.
|
||||
@@ -318,6 +346,11 @@ void AlxPlayerControllerBase::UpdateInputMode()
|
||||
}
|
||||
}
|
||||
|
||||
ElxInputMode AlxPlayerControllerBase::GetInputMode(const UObject *Context)
|
||||
{
|
||||
return FromContext(Context)->CurrentInputMode;
|
||||
}
|
||||
|
||||
void AlxPlayerControllerBase::UpdateLookAt()
|
||||
{
|
||||
UlxTangibleManager *TM = GetGameInstance()->GetSubsystem<UlxTangibleManager>();
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Common.h"
|
||||
#include "Engine/HitResult.h"
|
||||
#include "GameFramework/PlayerController.h"
|
||||
#include "UObject/ObjectKey.h"
|
||||
#include "PlayerControllerBase.generated.h"
|
||||
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FlxInputModeChanged, ElxInputMode, NewMode);
|
||||
|
||||
class UlxRootCanvasPanel;
|
||||
class UWidget;
|
||||
|
||||
@@ -40,6 +43,12 @@ public:
|
||||
// Called by GameMode each tick.
|
||||
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
|
||||
// eventually reconcile focus, pointer, and capture state.
|
||||
void UpdateInputMode();
|
||||
@@ -88,4 +97,7 @@ public:
|
||||
UlxRootCanvasPanel *RootCanvas = nullptr;
|
||||
|
||||
bool MustCallLookAtChanged = false;
|
||||
|
||||
UPROPERTY()
|
||||
ElxInputMode CurrentInputMode = ElxInputMode::KeyboardMouse;
|
||||
};
|
||||
|
||||
189
Source/Integration/PromptWidget.cpp
Normal file
189
Source/Integration/PromptWidget.cpp
Normal file
@@ -0,0 +1,189 @@
|
||||
#include "PromptWidget.h"
|
||||
#include "PlayerControllerBase.h"
|
||||
#include "Widgets/SOverlay.h"
|
||||
#include "Widgets/Images/SImage.h"
|
||||
#include "Widgets/Text/STextBlock.h"
|
||||
#include "Widgets/Layout/SScaleBox.h"
|
||||
|
||||
//
|
||||
// Atlas contents:
|
||||
//
|
||||
// Row 1: Keyboard Button no Glyph, Left Mouse, Middle Mouse, Right Mouse
|
||||
// Row 2: Left Trigger, Right Trigger, Left Shoulder, Right Shoulder
|
||||
// Row 3: XBFace Down, XBFace Left, XBFace Up, XBFace Right
|
||||
// Row 4: PSFace Down, PSFace Left, PSFace Up, PSFace Right
|
||||
// Row 5: Arrow Down, Arrow Left, Arrow Up, Arrow Right
|
||||
// Row 6: DPad Down, DPad Left, DPad Up, DPad Right
|
||||
// Row 7: Space Bar, unused, unused, unused
|
||||
// Row 8: unused, unused, unused, unused.
|
||||
|
||||
void UlxPromptWidget::CalcAppearance(int32& OutIcon, FString& OutGlyph) const
|
||||
{
|
||||
UWorld* World = GetWorld();
|
||||
APlayerController* PC = World ? World->GetFirstPlayerController() : nullptr;
|
||||
AlxPlayerControllerBase* LPC = Cast<AlxPlayerControllerBase>(PC);
|
||||
|
||||
ElxInputMode Mode = LPC ? LPC->CurrentInputMode : ElxInputMode::KeyboardMouse;
|
||||
if (Mode == ElxInputMode::KeyboardMouse)
|
||||
{
|
||||
CalcKeyboardAppearance(OutIcon, OutGlyph);
|
||||
}
|
||||
else
|
||||
{
|
||||
CalcGamepadAppearance(Mode, OutIcon, OutGlyph);
|
||||
}
|
||||
}
|
||||
|
||||
void UlxPromptWidget::CalcKeyboardAppearance(int32& OutIcon, FString& OutGlyph) const
|
||||
{
|
||||
// Fixed-icon keys: no glyph needed.
|
||||
if (KeyboardKey == EKeys::LeftMouseButton) { OutIcon = 1; OutGlyph = TEXT(""); return; }
|
||||
if (KeyboardKey == EKeys::MiddleMouseButton) { OutIcon = 2; OutGlyph = TEXT(""); return; }
|
||||
if (KeyboardKey == EKeys::RightMouseButton) { OutIcon = 3; OutGlyph = TEXT(""); return; }
|
||||
if (KeyboardKey == EKeys::SpaceBar) { OutIcon = 24; OutGlyph = TEXT(""); return; }
|
||||
if (KeyboardKey == EKeys::Down) { OutIcon = 16; OutGlyph = TEXT(""); return; }
|
||||
if (KeyboardKey == EKeys::Left) { OutIcon = 17; OutGlyph = TEXT(""); return; }
|
||||
if (KeyboardKey == EKeys::Up) { OutIcon = 18; OutGlyph = TEXT(""); return; }
|
||||
if (KeyboardKey == EKeys::Right) { OutIcon = 19; OutGlyph = TEXT(""); return; }
|
||||
|
||||
// Generic keyboard key: blank icon with the key name as glyph.
|
||||
OutIcon = 0;
|
||||
OutGlyph = KeyboardKey.GetDisplayName().ToString();
|
||||
}
|
||||
|
||||
void UlxPromptWidget::CalcGamepadAppearance(ElxInputMode Mode, int32& OutIcon, FString& OutGlyph) const
|
||||
{
|
||||
OutGlyph = TEXT("");
|
||||
|
||||
// Triggers and shoulders.
|
||||
if (GamepadKey == EKeys::Gamepad_LeftTrigger) { OutIcon = 4; return; }
|
||||
if (GamepadKey == EKeys::Gamepad_RightTrigger) { OutIcon = 5; return; }
|
||||
if (GamepadKey == EKeys::Gamepad_LeftShoulder) { OutIcon = 6; return; }
|
||||
if (GamepadKey == EKeys::Gamepad_RightShoulder) { OutIcon = 7; return; }
|
||||
|
||||
// Face buttons — Xbox vs PlayStation.
|
||||
bool bPS = (Mode == ElxInputMode::PlayStationGamepad);
|
||||
if (GamepadKey == EKeys::Gamepad_FaceButton_Bottom) { OutIcon = bPS ? 12 : 8; return; }
|
||||
if (GamepadKey == EKeys::Gamepad_FaceButton_Left) { OutIcon = bPS ? 13 : 9; return; }
|
||||
if (GamepadKey == EKeys::Gamepad_FaceButton_Top) { OutIcon = bPS ? 14 : 10; return; }
|
||||
if (GamepadKey == EKeys::Gamepad_FaceButton_Right) { OutIcon = bPS ? 15 : 11; return; }
|
||||
|
||||
// DPad.
|
||||
if (GamepadKey == EKeys::Gamepad_DPad_Down) { OutIcon = 20; return; }
|
||||
if (GamepadKey == EKeys::Gamepad_DPad_Left) { OutIcon = 21; return; }
|
||||
if (GamepadKey == EKeys::Gamepad_DPad_Up) { OutIcon = 22; return; }
|
||||
if (GamepadKey == EKeys::Gamepad_DPad_Right) { OutIcon = 23; return; }
|
||||
|
||||
// Fallback: blank key with name.
|
||||
OutIcon = 0;
|
||||
OutGlyph = GamepadKey.GetDisplayName().ToString();
|
||||
}
|
||||
|
||||
FBox2f UlxPromptWidget::GetIconUVs(int32 IconIndex)
|
||||
{
|
||||
const float ColWidth = 1.0f / 4.0f;
|
||||
const float RowHeight = 1.0f / 8.0f;
|
||||
float U = (IconIndex % 4) * ColWidth;
|
||||
float V = (IconIndex / 4) * RowHeight;
|
||||
return FBox2f(FVector2f(U, V), FVector2f(U + ColWidth, V + RowHeight));
|
||||
}
|
||||
|
||||
FMargin UlxPromptWidget::GetScaledMargins() const
|
||||
{
|
||||
return FMargin(
|
||||
GlyphMargins.Left * Size.X,
|
||||
GlyphMargins.Top * Size.Y,
|
||||
GlyphMargins.Right * Size.X,
|
||||
GlyphMargins.Bottom * Size.Y);
|
||||
}
|
||||
|
||||
void UlxPromptWidget::SynchronizeProperties()
|
||||
{
|
||||
Super::SynchronizeProperties();
|
||||
if (!MyImage.IsValid()) return;
|
||||
|
||||
int32 Icon = 0;
|
||||
FString Glyph;
|
||||
CalcAppearance(Icon, Glyph);
|
||||
|
||||
MyBrush.SetUVRegion(GetIconUVs(Icon));
|
||||
MyImage->InvalidateImage();
|
||||
MyImage->SetDesiredSizeOverride(Size);
|
||||
MyGlyphSlot->SetPadding(GetScaledMargins());
|
||||
MyGlyph->SetColorAndOpacity(GlyphColor);
|
||||
|
||||
if (!Glyph.IsEmpty())
|
||||
{
|
||||
MyGlyph->SetText(FText::FromString(Glyph));
|
||||
MyGlyph->SetVisibility(EVisibility::HitTestInvisible);
|
||||
}
|
||||
else
|
||||
{
|
||||
MyGlyph->SetVisibility(EVisibility::Collapsed);
|
||||
}
|
||||
}
|
||||
|
||||
void UlxPromptWidget::SetSize(FVector2D InSize)
|
||||
{
|
||||
Size = InSize;
|
||||
SynchronizeProperties();
|
||||
}
|
||||
|
||||
void UlxPromptWidget::SetGlyphMargins(FMargin InMargins)
|
||||
{
|
||||
GlyphMargins = InMargins;
|
||||
SynchronizeProperties();
|
||||
}
|
||||
|
||||
void UlxPromptWidget::SetGlyphColor(FLinearColor InColor)
|
||||
{
|
||||
GlyphColor = InColor;
|
||||
SynchronizeProperties();
|
||||
}
|
||||
|
||||
void UlxPromptWidget::SetKeys(FKey InGamepadKey, FKey InKeyboardKey)
|
||||
{
|
||||
GamepadKey = InGamepadKey;
|
||||
KeyboardKey = InKeyboardKey;
|
||||
SynchronizeProperties();
|
||||
}
|
||||
|
||||
TSharedRef<SWidget> UlxPromptWidget::RebuildWidget()
|
||||
{
|
||||
int32 Icon = 0;
|
||||
FString Glyph;
|
||||
CalcAppearance(Icon, Glyph);
|
||||
|
||||
MyBrush.SetResourceObject(ButtonAtlas);
|
||||
MyBrush.ImageSize = Size;
|
||||
MyBrush.SetUVRegion(GetIconUVs(Icon));
|
||||
|
||||
SAssignNew(MyImage, SImage).Image(&MyBrush);
|
||||
|
||||
EVisibility GlyphVisibility = Glyph.IsEmpty() ? EVisibility::Collapsed : EVisibility::HitTestInvisible;
|
||||
SAssignNew(MyGlyph, STextBlock)
|
||||
.Text(FText::FromString(Glyph))
|
||||
.ColorAndOpacity(GlyphColor)
|
||||
.Visibility(GlyphVisibility);
|
||||
SAssignNew(MyScaleBox, SScaleBox)
|
||||
.Stretch(EStretch::ScaleToFit)
|
||||
[ MyGlyph.ToSharedRef() ];
|
||||
|
||||
MyOverlay = SNew(SOverlay)
|
||||
+ SOverlay::Slot() [ MyImage.ToSharedRef() ];
|
||||
|
||||
MyOverlay->AddSlot().Padding(GetScaledMargins()).HAlign(HAlign_Fill).VAlign(VAlign_Fill).Expose(MyGlyphSlot)
|
||||
[ MyScaleBox.ToSharedRef() ];
|
||||
|
||||
return MyOverlay.ToSharedRef();
|
||||
}
|
||||
|
||||
void UlxPromptWidget::ReleaseSlateResources(bool bReleaseChildren)
|
||||
{
|
||||
Super::ReleaseSlateResources(bReleaseChildren);
|
||||
MyOverlay.Reset();
|
||||
MyImage.Reset();
|
||||
MyScaleBox.Reset();
|
||||
MyGlyph.Reset();
|
||||
MyGlyphSlot = nullptr;
|
||||
}
|
||||
67
Source/Integration/PromptWidget.h
Normal file
67
Source/Integration/PromptWidget.h
Normal file
@@ -0,0 +1,67 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Common.h"
|
||||
#include "Components/Widget.h"
|
||||
#include "InputCoreTypes.h"
|
||||
#include "Widgets/Layout/SScaleBox.h"
|
||||
#include "PromptWidget.generated.h"
|
||||
|
||||
|
||||
UCLASS(BlueprintType, Blueprintable)
|
||||
class INTEGRATION_API UlxPromptWidget : public UWidget
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UPROPERTY(EditAnywhere, Category="Prompt")
|
||||
TObjectPtr<UTexture2D> ButtonAtlas;
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category="Prompt")
|
||||
void SetKeys(FKey InGamepadKey, FKey InKeyboardKey);
|
||||
|
||||
UPROPERTY(EditAnywhere, Setter, Category="Prompt")
|
||||
FVector2D Size = FVector2D(64, 64);
|
||||
|
||||
UPROPERTY(EditAnywhere, Setter, Category="Prompt")
|
||||
FMargin GlyphMargins = FMargin(0.0f, 0.1f, 0.0f, 0.1f);
|
||||
|
||||
UPROPERTY(EditAnywhere, Setter, Category="Prompt")
|
||||
FLinearColor GlyphColor = FLinearColor::White;
|
||||
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category="Prompt")
|
||||
void SetGlyphMargins(FMargin InMargins);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category="Prompt")
|
||||
void SetGlyphColor(FLinearColor InColor);
|
||||
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category="Prompt")
|
||||
void SetSize(FVector2D InSize);
|
||||
|
||||
protected:
|
||||
virtual TSharedRef<SWidget> RebuildWidget() override;
|
||||
virtual void SynchronizeProperties() 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:
|
||||
FSlateBrush MyBrush;
|
||||
TSharedPtr<SOverlay> MyOverlay;
|
||||
TSharedPtr<SImage> MyImage;
|
||||
TSharedPtr<SScaleBox> MyScaleBox;
|
||||
TSharedPtr<STextBlock> MyGlyph;
|
||||
SOverlay::FOverlaySlot* MyGlyphSlot = nullptr;
|
||||
|
||||
FBox2f GetIconUVs(int32 IconIndex);
|
||||
FMargin GetScaledMargins() const;
|
||||
void CalcAppearance(int32& OutIcon, FString& OutGlyph) const;
|
||||
void CalcKeyboardAppearance(int32& OutIcon, FString& OutGlyph) const;
|
||||
void CalcGamepadAppearance(ElxInputMode Mode, int32& OutIcon, FString& OutGlyph) const;
|
||||
};
|
||||
Reference in New Issue
Block a user