Much work on input mode switching
This commit is contained in:
@@ -2,10 +2,14 @@
|
||||
#include "Common.h"
|
||||
#include "Tangible.h"
|
||||
#include "TangibleManager.h"
|
||||
#include "Blueprint/UserWidget.h"
|
||||
#include "Components/InputComponent.h"
|
||||
#include "Engine/LevelScriptActor.h"
|
||||
#include "Kismet/GameplayStatics.h"
|
||||
#include "Engine/GameInstance.h"
|
||||
#include "Engine/GameViewportClient.h"
|
||||
#include "Engine/LevelScriptActor.h"
|
||||
#include "Engine/LocalPlayer.h"
|
||||
#include "Kismet/GameplayStatics.h"
|
||||
#include "Widgets/SViewport.h"
|
||||
|
||||
AlxPlayerControllerBase *AlxPlayerControllerBase::FromContext(const UObject *Context)
|
||||
{
|
||||
@@ -55,10 +59,53 @@ FVector2D AlxPlayerControllerBase::GetLookAtPixel(const UObject *Context)
|
||||
return ScreenPosition;
|
||||
}
|
||||
|
||||
UInputComponent* AlxPlayerControllerBase::GetWidgetInputComponent(UUserWidget *Widget)
|
||||
{
|
||||
if (!IsValid(Widget)) return nullptr;
|
||||
|
||||
// Cache the FProperty on first call. FProperties are owned by
|
||||
// the native UUserWidget UClass, which lives for the process
|
||||
// lifetime, so the pointer stays valid without us needing to
|
||||
// root anything against GC. Static local init is thread-safe.
|
||||
static FObjectProperty *InputComponentProp = FindFProperty<FObjectProperty>(
|
||||
UUserWidget::StaticClass(), TEXT("InputComponent"));
|
||||
check(InputComponentProp);
|
||||
|
||||
UObject *Value = InputComponentProp->GetObjectPropertyValue_InContainer(Widget);
|
||||
return Cast<UInputComponent>(Value);
|
||||
}
|
||||
|
||||
void AlxPlayerControllerBase::WidgetRequestInputMode(UUserWidget *Widget, bool ShowPointer, bool BlockInput, UWidget *Focus)
|
||||
{
|
||||
if (!IsValid(Widget))
|
||||
{
|
||||
UE_LOG(LogLuprexIntegration, Error, TEXT("WidgetRequestInputMode called with an invalid widget."));
|
||||
return;
|
||||
}
|
||||
APlayerController *OwningPC = Widget->GetOwningPlayer();
|
||||
AlxPlayerControllerBase *PC = Cast<AlxPlayerControllerBase>(OwningPC);
|
||||
if (PC == nullptr)
|
||||
{
|
||||
UE_LOG(LogLuprexIntegration, Error,
|
||||
TEXT("WidgetRequestInputMode: widget '%s' owning player is not an AlxPlayerControllerBase (got %s)."),
|
||||
*Widget->GetName(), *GetNameSafe(OwningPC));
|
||||
return;
|
||||
}
|
||||
PC->InputModeRequests.Request(FlxInputModeRequest(Widget, Focus, ShowPointer, BlockInput));
|
||||
}
|
||||
|
||||
void AlxPlayerControllerBase::PushInputComponent(UInputComponent* InInputComponent)
|
||||
{
|
||||
if (InInputComponent)
|
||||
{
|
||||
// Widgets don't go on the current input stack. Instead, they're
|
||||
// tracked in InputModeRequests, where the PlayerController can arbitrate
|
||||
// focus, pointer visibility, and input blocking across them.
|
||||
if (UUserWidget *Widget = Cast<UUserWidget>(InInputComponent->GetOuter()))
|
||||
{
|
||||
InputModeRequests.EnsureWidget(Widget);
|
||||
return;
|
||||
}
|
||||
CurrentInputStack.RemoveSingle(InInputComponent);
|
||||
CurrentInputStack.Add(InInputComponent);
|
||||
}
|
||||
@@ -121,31 +168,103 @@ void AlxPlayerControllerBase::BuildInputStack(TArray<UInputComponent*>& InputSta
|
||||
InputStack.Push(InputComponent);
|
||||
}
|
||||
|
||||
// Sort the components first by bBlockInput, then by
|
||||
// priority. We don't touch the original array, because
|
||||
// we want to preserve information about which component
|
||||
// was pushed last.
|
||||
TArray<UInputComponent*, TInlineAllocator<20>> Pushed;
|
||||
for (int32 Idx = 0; Idx < CurrentInputStack.Num(); ++Idx)
|
||||
// The current input stack is unlikely to have anything,
|
||||
// given that we've moved widgets to their own mechanism.
|
||||
// But, if there's anything here, sort it and push it.
|
||||
if (!CurrentInputStack.IsEmpty())
|
||||
{
|
||||
UInputComponent* IC = CurrentInputStack[Idx].Get();
|
||||
if (IsValid(IC))
|
||||
TArray<UInputComponent*, TInlineAllocator<20>> Pushed;
|
||||
for (int32 Idx = 0; Idx < CurrentInputStack.Num(); ++Idx)
|
||||
{
|
||||
Pushed.Add(IC);
|
||||
UInputComponent* IC = CurrentInputStack[Idx].Get();
|
||||
if (IsValid(IC)) Pushed.Add(IC);
|
||||
else CurrentInputStack.RemoveAt(Idx--);
|
||||
}
|
||||
else
|
||||
|
||||
Pushed.StableSort([](const UInputComponent& A, const UInputComponent& B)
|
||||
{
|
||||
CurrentInputStack.RemoveAt(Idx--);
|
||||
}
|
||||
if (A.bBlockInput != B.bBlockInput) return !A.bBlockInput;
|
||||
return A.Priority < B.Priority;
|
||||
});
|
||||
|
||||
InputStack.Append(Pushed);
|
||||
}
|
||||
|
||||
Pushed.StableSort([](const UInputComponent& A, const UInputComponent& B)
|
||||
// Push the widget input components from InputModeRequests. Requests
|
||||
// are ordered high-priority first; the input stack is processed
|
||||
// from top (last index) down, so we push back-to-front: low
|
||||
// priority first, high priority last (ending up on top).
|
||||
const TArray<FlxInputModeRequest> &Requests = InputModeRequests.GetRequests();
|
||||
for (int32 Idx = Requests.Num() - 1; Idx >= 0; --Idx)
|
||||
{
|
||||
if (A.bBlockInput != B.bBlockInput) return !A.bBlockInput;
|
||||
return A.Priority < B.Priority;
|
||||
});
|
||||
if (UInputComponent *IC = GetWidgetInputComponent(Requests[Idx].Widget))
|
||||
{
|
||||
IC->bBlockInput = Requests[Idx].BlockInput;
|
||||
InputStack.Push(IC);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
InputStack.Append(Pushed);
|
||||
void AlxPlayerControllerBase::UpdateInputMode()
|
||||
{
|
||||
InputModeRequests.GarbageCollect();
|
||||
|
||||
// Get all the various objects we need to be able to manipulate
|
||||
// the input mode.
|
||||
UGameViewportClient *GameViewportClient = GetWorld()->GetGameViewport();
|
||||
ULocalPlayer *LocalPlayer = Cast<ULocalPlayer>(Player);
|
||||
if (GameViewportClient == nullptr || LocalPlayer == nullptr) return;
|
||||
TSharedPtr<SViewport> ViewportWidget = GameViewportClient->GetGameViewportWidget();
|
||||
if (!ViewportWidget.IsValid()) return;
|
||||
TSharedRef<SViewport> ViewportWidgetRef = ViewportWidget.ToSharedRef();
|
||||
FReply &SlateOperations = LocalPlayer->GetSlateOperations();
|
||||
|
||||
// The first entry in InputModeRequests (highest priority) dictates the
|
||||
// pointer / capture / focus state. If there are no requests at all,
|
||||
// fall back to a static default-constructed request.
|
||||
static const FlxInputModeRequest EmptyRequest;
|
||||
const TArray<FlxInputModeRequest> &Requests = InputModeRequests.GetRequests();
|
||||
const FlxInputModeRequest &Top = Requests.IsEmpty() ? EmptyRequest : Requests[0];
|
||||
|
||||
SetShowMouseCursor(Top.ShowPointer);
|
||||
|
||||
if (Top.ShowPointer)
|
||||
{
|
||||
// Pointer-visible mode: full macroexpansion of SetInputModeGameAndUI
|
||||
// — the BP wrapper, APlayerController::SetInputMode, and
|
||||
// FInputModeGameAndUI::ApplyInputMode all inlined. Defaults:
|
||||
// MouseLockMode=DoNotLock, WidgetToFocus=null, bHideCursorDuringCapture=true.
|
||||
SlateOperations.ReleaseMouseLock();
|
||||
SlateOperations.ReleaseMouseCapture();
|
||||
GameViewportClient->SetMouseLockMode(EMouseLockMode::DoNotLock);
|
||||
GameViewportClient->SetHideCursorDuringCapture(false);
|
||||
GameViewportClient->SetMouseCaptureMode(EMouseCaptureMode::CaptureDuringMouseDown);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Pointer-invisible mode: full macroexpansion of SetInputModeGameOnly
|
||||
// — the BP wrapper, APlayerController::SetInputMode, and
|
||||
// FInputModeGameOnly::ApplyInputMode all inlined.
|
||||
SlateOperations.UseHighPrecisionMouseMovement(ViewportWidgetRef);
|
||||
SlateOperations.LockMouseToWidget(ViewportWidgetRef);
|
||||
GameViewportClient->SetMouseLockMode(EMouseLockMode::LockOnCapture);
|
||||
GameViewportClient->SetMouseCaptureMode(EMouseCaptureMode::CapturePermanently);
|
||||
}
|
||||
|
||||
// UWidget holds its live SWidget in a cached TSharedPtr; we need a
|
||||
// TSharedRef for SetUserFocus. Fall back to the viewport if the
|
||||
// target has never been constructed (cached widget is null).
|
||||
TSharedPtr<SWidget> SlateFocus = Top.Focus ? Top.Focus->GetCachedWidget() : nullptr;
|
||||
if (SlateFocus.IsValid())
|
||||
{
|
||||
SlateOperations.SetUserFocus(SlateFocus.ToSharedRef());
|
||||
}
|
||||
else
|
||||
{
|
||||
SlateOperations.SetUserFocus(ViewportWidgetRef);
|
||||
}
|
||||
|
||||
GameViewportClient->SetIgnoreInput(false);
|
||||
}
|
||||
|
||||
void AlxPlayerControllerBase::UpdateLookAt()
|
||||
|
||||
Reference in New Issue
Block a user