RealtimeStyleTransferRuntime/Plugins/CommonGame/Source/Private/CommonPlayerInputKey.cpp

561 lines
14 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "CommonPlayerInputKey.h"
#include "CommonInputSubsystem.h"
#include "CommonLocalPlayer.h"
#include "CommonPlayerController.h"
#include "Components/SlateWrapperTypes.h"
#include "Containers/UnrealString.h"
#include "Delegates/Delegate.h"
#include "Engine/World.h"
#include "Fonts/FontMeasure.h"
#include "Framework/Application/SlateApplication.h"
#include "Internationalization/Internationalization.h"
#include "Layout/Geometry.h"
#include "Logging/LogCategory.h"
#include "Logging/LogMacros.h"
#include "Materials/Material.h"
#include "Materials/MaterialInstanceDynamic.h"
#include "Materials/MaterialInterface.h"
#include "Math/Color.h"
#include "Math/UnrealMathSSE.h"
#include "Misc/AssertionMacros.h"
#include "Rendering/DrawElements.h"
#include "Rendering/RenderingCommon.h"
#include "Rendering/SlateRenderer.h"
#include "Styling/SlateBrush.h"
#include "Styling/WidgetStyle.h"
#include "Templates/Casts.h"
#include "Templates/SharedPointer.h"
#include "TimerManager.h"
#include "Trace/Detail/Channel.h"
#include "UObject/UnrealNames.h"
#include "Widgets/InvalidateWidgetReason.h"
class FPaintArgs;
class FSlateRect;
#define LOCTEXT_NAMESPACE "CommonKeybindWidget"
DECLARE_LOG_CATEGORY_EXTERN(LogCommonPlayerInput, Log, All);
DEFINE_LOG_CATEGORY(LogCommonPlayerInput);
struct FSlateDrawUtil
{
static void DrawBrushCenterFit(
FSlateWindowElementList& ElementList,
uint32 InLayer,
const FGeometry& InAllottedGeometry,
const FSlateBrush* InBrush,
const FLinearColor& InTint = FLinearColor::White)
{
DrawBrushCenterFitWithOffset
(
ElementList,
InLayer,
InAllottedGeometry,
InBrush,
InTint,
FVector2D(0, 0)
);
}
static void DrawBrushCenterFitWithOffset(
FSlateWindowElementList& ElementList,
uint32 InLayer,
const FGeometry& InAllottedGeometry,
const FSlateBrush* InBrush,
const FLinearColor& InTint,
const FVector2D InOffset)
{
if (!InBrush)
{
return;
}
const FVector2D AreaSize = InAllottedGeometry.GetLocalSize();
const FVector2D ProgressSize = InBrush->GetImageSize();
const float FitScale = FMath::Min(FMath::Min(AreaSize.X / ProgressSize.X, AreaSize.Y / ProgressSize.Y), 1.0f);
const FVector2D FinalSize = FitScale * ProgressSize;
const FVector2D Offset = (InAllottedGeometry.GetLocalSize() * 0.5f) - (FinalSize * 0.5f) + InOffset;
FSlateDrawElement::MakeBox
(
ElementList,
InLayer,
InAllottedGeometry.ToPaintGeometry(Offset, FinalSize),
InBrush,
ESlateDrawEffect::None,
InTint
);
}
};
void FMeasuredText::SetText(const FText& InText)
{
CachedText = InText;
bTextDirty = true;
}
FVector2D FMeasuredText::UpdateTextSize(const FSlateFontInfo &InFontInfo, float FontScale) const
{
if (bTextDirty)
{
bTextDirty = false;
CachedTextSize = FSlateApplication::Get().GetRenderer()->GetFontMeasureService()->Measure(CachedText, InFontInfo, FontScale);
}
return CachedTextSize;
}
UCommonPlayerInputKey::UCommonPlayerInputKey(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
, BoundKeyFallback(EKeys::Invalid)
, InputTypeOverride(ECommonInputType::Count)
{
FrameSize = FVector2D(0, 0);
}
void UCommonPlayerInputKey::NativePreConstruct()
{
Super::NativePreConstruct();
UpdateKeybindWidget();
if (IsDesignTime())
{
ShowHoldBackPlate();
RecalculateDesiredSize();
}
}
void UCommonPlayerInputKey::NativeConstruct()
{
Super::NativeConstruct();
}
void UCommonPlayerInputKey::NativeDestruct()
{
if (ProgressPercentageMID)
{
// Need to restore the material on the brush before we kill off the MID.
HoldProgressBrush.SetResourceObject(ProgressPercentageMID->GetMaterial());
ProgressPercentageMID->MarkAsGarbage();
ProgressPercentageMID = nullptr;
}
Super::NativeDestruct();
}
int32 UCommonPlayerInputKey::NativePaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const
{
int32 MaxLayer = Super::NativePaint(Args, AllottedGeometry, MyCullingRect, OutDrawElements, LayerId, InWidgetStyle, bParentEnabled);
if (bDrawProgress)
{
FSlateDrawUtil::DrawBrushCenterFit
(
OutDrawElements,
++MaxLayer,
AllottedGeometry,
&HoldProgressBrush,
FLinearColor(InWidgetStyle.GetColorAndOpacityTint() * HoldProgressBrush.GetTint(InWidgetStyle))
);
}
if (bDrawCountdownText)
{
const FVector2D CountdownTextOffset = (AllottedGeometry.GetLocalSize() - CountdownText.GetTextSize()) * 0.5f;
FSlateDrawElement::MakeText
(
OutDrawElements,
++MaxLayer,
AllottedGeometry.ToOffsetPaintGeometry(CountdownTextOffset),
CountdownText.GetText(),
CountdownTextFont,
ESlateDrawEffect::None,
FLinearColor(InWidgetStyle.GetColorAndOpacityTint())
);
}
else if (bDrawBrushForKey)
{
// Draw Shadow
FSlateDrawUtil::DrawBrushCenterFitWithOffset
(
OutDrawElements,
++MaxLayer,
AllottedGeometry,
&CachedKeyBrush,
FLinearColor(InWidgetStyle.GetColorAndOpacityTint() * FLinearColor::Black),
FVector2D(1, 1)
);
FSlateDrawUtil::DrawBrushCenterFit
(
OutDrawElements,
++MaxLayer,
AllottedGeometry,
&CachedKeyBrush,
FLinearColor(InWidgetStyle.GetColorAndOpacityTint() * CachedKeyBrush.GetTint(InWidgetStyle))
);
}
else if (KeybindText.GetTextSize().X > 0)
{
const FVector2D FrameOffset = (AllottedGeometry.GetLocalSize() - FrameSize) * 0.5f;
FSlateDrawElement::MakeBox
(
OutDrawElements,
++MaxLayer,
AllottedGeometry.ToPaintGeometry(FrameOffset, FrameSize),
&KeyBindTextBorder,
ESlateDrawEffect::None,
FLinearColor(InWidgetStyle.GetColorAndOpacityTint() * KeyBindTextBorder.GetTint(InWidgetStyle))
);
const FVector2D ActionTextOffset = (AllottedGeometry.GetLocalSize() - KeybindText.GetTextSize()) * 0.5f;
FSlateDrawElement::MakeText
(
OutDrawElements,
++MaxLayer,
AllottedGeometry.ToOffsetPaintGeometry(ActionTextOffset),
KeybindText.GetText(),
KeyBindTextFont,
ESlateDrawEffect::None,
FLinearColor(InWidgetStyle.GetColorAndOpacityTint())
);
}
return MaxLayer;
}
void UCommonPlayerInputKey::StartHoldProgress(FName HoldActionName, float HoldDuration)
{
if (HoldActionName == BoundAction && ensureMsgf(HoldDuration > 0.0f, TEXT("Trying to perform hold action \"%s\" with no HoldDuration"), *BoundAction.ToString()))
{
HoldKeybindDuration = HoldDuration;
HoldKeybindStartTime = GetWorld()->GetRealTimeSeconds();
UpdateHoldProgress();
}
}
void UCommonPlayerInputKey::StopHoldProgress(FName HoldActionName, bool bCompletedSuccessfully)
{
if (HoldActionName == BoundAction)
{
HoldKeybindStartTime = 0.f;
HoldKeybindDuration = 0.f;
if (ensure(ProgressPercentageMID))
{
ProgressPercentageMID->SetScalarParameterValue(PercentageMaterialParameterName, 0.f);
}
if (bDrawCountdownText)
{
bDrawCountdownText = false;
Invalidate(EInvalidateWidget::Paint);
RecalculateDesiredSize();
}
}
}
void UCommonPlayerInputKey::SyncHoldProgress()
{
// If we had an active hold action, stop it
if (HoldKeybindStartTime > 0.f)
{
StopHoldProgress(BoundAction, false);
}
}
void UCommonPlayerInputKey::UpdateHoldProgress()
{
if (HoldKeybindStartTime != 0.f && HoldKeybindDuration > 0.f)
{
const float CurrentTime = GetWorld()->GetRealTimeSeconds();
const float ElapsedTime = FMath::Min(CurrentTime - HoldKeybindStartTime, HoldKeybindDuration);
const float RemainingTime = FMath::Max(0.0f, HoldKeybindDuration - ElapsedTime);
if (ElapsedTime < HoldKeybindDuration && ensure(ProgressPercentageMID))
{
const float HoldKeybindPercentage = ElapsedTime / HoldKeybindDuration;
ProgressPercentageMID->SetScalarParameterValue(PercentageMaterialParameterName, HoldKeybindPercentage);
// Schedule a callback for next tick to update the hold progress again.
GetWorld()->GetTimerManager().SetTimerForNextTick(this, &ThisClass::UpdateHoldProgress);
}
if (bShowTimeCountDown)
{
FNumberFormattingOptions Options;
Options.MinimumFractionalDigits = 1;
Options.MaximumFractionalDigits = 1;
CountdownText.SetText(FText::AsNumber(RemainingTime, &Options));
bDrawCountdownText = true;
Invalidate(EInvalidateWidget::Paint);
RecalculateDesiredSize();
}
}
}
void UCommonPlayerInputKey::UpdateKeybindWidget()
{
if (!GetOwningPlayer<ACommonPlayerController>())
{
bWaitingForPlayerController = true;
return;
}
UCommonInputSubsystem* CommonInputSubsystem = GetInputSubsystem();
if (CommonInputSubsystem && !CommonInputSubsystem->ShouldShowInputKeys())
{
SetVisibility(ESlateVisibility::Collapsed);
return;
}
const bool bIsUsingGamepad = (InputTypeOverride == ECommonInputType::Gamepad) || ((CommonInputSubsystem != nullptr) && (CommonInputSubsystem->GetCurrentInputType() == ECommonInputType::Gamepad)) ;
if (!BoundKey.IsValid())
{
BoundKey = BoundKeyFallback;
}
UE_LOG(LogCommonPlayerInput, Verbose, TEXT("UCommonKeybindWidget::UpdateKeybindWidget: Action: %s Key: %s"), *(BoundAction.ToString()), *(BoundKey.ToString()));
// Must be called before Update, due to the creation of ProgressPercentageMID which will be used in Update
SetupHoldKeybind();
bool NewDrawBrushForKey = false;
bool NeedToRecalcSize = false;
if (BoundKey.IsValid())
{
SetVisibility(ESlateVisibility::HitTestInvisible);
ShowHoldBackPlate();
NeedToRecalcSize = true;
}
else
{
if (bShowUnboundStatus)
{
SetVisibility(ESlateVisibility::HitTestInvisible);
NewDrawBrushForKey = false;
KeybindText.SetText(LOCTEXT("Unbound", "Unbound"));
NeedToRecalcSize = true;
}
else
{
SetVisibility(ESlateVisibility::Collapsed);
}
}
if (bDrawBrushForKey != NewDrawBrushForKey)
{
bDrawBrushForKey = NewDrawBrushForKey;
Invalidate(EInvalidateWidget::Paint);
}
// As RecalculateDesiredSize relies on the bDrawBrushForKey
// we shouldn't call it until that value has been finalized
// for the update
if (NeedToRecalcSize)
{
RecalculateDesiredSize();
}
}
void UCommonPlayerInputKey::SetBoundKey(FKey NewKey)
{
if (NewKey != BoundKey)
{
BoundKeyFallback = NewKey;
BoundAction = NAME_None;
UpdateKeybindWidget();
}
}
void UCommonPlayerInputKey::SetBoundAction(FName NewBoundAction)
{
bool bUpdateWidget = true;
if (BoundAction != NewBoundAction)
{
BoundAction = NewBoundAction;
}
if (bUpdateWidget)
{
UpdateKeybindWidget();
}
}
void UCommonPlayerInputKey::NativeOnInitialized()
{
Super::NativeOnInitialized();
if (UCommonLocalPlayer* CommonLocalPlayer = GetOwningLocalPlayer<UCommonLocalPlayer>())
{
CommonLocalPlayer->OnPlayerControllerSet.AddUObject(this, &ThisClass::HandlePlayerControllerSet);
}
}
void UCommonPlayerInputKey::SetForcedHoldKeybind(bool InForcedHoldKeybind)
{
if (InForcedHoldKeybind)
{
SetForcedHoldKeybindStatus(ECommonKeybindForcedHoldStatus::ForcedHold);
}
else
{
SetForcedHoldKeybindStatus(ECommonKeybindForcedHoldStatus::NoForcedHold);
}
}
void UCommonPlayerInputKey::SetForcedHoldKeybindStatus(ECommonKeybindForcedHoldStatus InForcedHoldKeybindStatus)
{
ForcedHoldKeybindStatus = InForcedHoldKeybindStatus;
UpdateKeybindWidget();
}
void UCommonPlayerInputKey::SetShowProgressCountDown(bool bShow)
{
bShowTimeCountDown = bShow;
}
void UCommonPlayerInputKey::SetupHoldKeybind()
{
ACommonPlayerController* OwningCommonPlayer = Cast<ACommonPlayerController>(GetOwningPlayer());
// Setup the hold
if (ForcedHoldKeybindStatus == ECommonKeybindForcedHoldStatus::ForcedHold)
{
bIsHoldKeybind = true;
}
else if (ForcedHoldKeybindStatus == ECommonKeybindForcedHoldStatus::NeverShowHold)
{
bIsHoldKeybind = false;
}
if (ensure(OwningCommonPlayer))
{
if (bIsHoldKeybind)
{
// Setup the ProgressPercentageMID
if (ProgressPercentageMID == nullptr)
{
if (UMaterialInterface* Material = Cast<UMaterialInterface>(HoldProgressBrush.GetResourceObject()))
{
ProgressPercentageMID = UMaterialInstanceDynamic::Create(Material, this);
HoldProgressBrush.SetResourceObject(ProgressPercentageMID);
}
}
SyncHoldProgress();
}
}
}
void UCommonPlayerInputKey::ShowHoldBackPlate()
{
bool bDirty = false;
if (IsHoldKeybind())
{
float BrushSizeAsValue = 32.0f;
float DesiredBoxSize = BrushSizeAsValue + 10.0f;
if (!bDrawBrushForKey)
{
DesiredBoxSize += 14.0f;
}
const FVector2D NewDesiredBrushSize(DesiredBoxSize, DesiredBoxSize);
if (HoldProgressBrush.GetImageSize() != NewDesiredBrushSize)
{
HoldProgressBrush.SetImageSize(NewDesiredBrushSize);
bDirty = true;
}
if (!bDrawProgress)
{
bDrawProgress = true;
bDirty = true;
}
static const FName BackAlphaName = TEXT("BackAlpha");
static const FName OutlineAlphaName = TEXT("OutlineAlpha");
if (ProgressPercentageMID)
{
ProgressPercentageMID->SetScalarParameterValue(BackAlphaName, 0.2f);
ProgressPercentageMID->SetScalarParameterValue(OutlineAlphaName, 0.4f);
}
}
else
{
if (bDrawProgress)
{
bDrawProgress = false;
bDirty = true;
}
}
if (bDirty)
{
Invalidate(EInvalidateWidget::Paint);
}
}
void UCommonPlayerInputKey::HandlePlayerControllerSet(UCommonLocalPlayer* LocalPlayer, APlayerController* PlayerController)
{
if (bWaitingForPlayerController && GetOwningPlayer<ACommonPlayerController>())
{
UpdateKeybindWidget();
bWaitingForPlayerController = false;
}
}
void UCommonPlayerInputKey::RecalculateDesiredSize()
{
FVector2D MaximumDesiredSize(0, 0);
float LayoutScale = 1;
if (bDrawProgress)
{
MaximumDesiredSize = FVector2D::Max(MaximumDesiredSize, HoldProgressBrush.GetImageSize());
}
if (bDrawCountdownText)
{
MaximumDesiredSize = FVector2D::Max(MaximumDesiredSize, CountdownText.UpdateTextSize(CountdownTextFont, LayoutScale));
}
else if (bDrawBrushForKey)
{
MaximumDesiredSize = FVector2D::Max(MaximumDesiredSize, CachedKeyBrush.GetImageSize());
}
else
{
const FVector2D KeybindTextSize = KeybindText.UpdateTextSize(KeyBindTextFont, LayoutScale);
FrameSize = FVector2D::Max(KeybindTextSize, KeybindFrameMinimumSize) + KeybindTextPadding.GetDesiredSize();
MaximumDesiredSize = FVector2D::Max(MaximumDesiredSize, FrameSize);
}
SetMinimumDesiredSize(MaximumDesiredSize);
}
#undef LOCTEXT_NAMESPACE