Lots of work on focus management
This commit is contained in:
@@ -8,6 +8,8 @@
|
||||
#include "Engine/GameViewportClient.h"
|
||||
#include "Engine/LevelScriptActor.h"
|
||||
#include "Engine/LocalPlayer.h"
|
||||
#include "Framework/Application/SlateApplication.h"
|
||||
#include "Framework/Application/SlateUser.h"
|
||||
#include "Kismet/GameplayStatics.h"
|
||||
#include "Widgets/SViewport.h"
|
||||
|
||||
@@ -59,6 +61,40 @@ FVector2D AlxPlayerControllerBase::GetLookAtPixel(const UObject *Context)
|
||||
return ScreenPosition;
|
||||
}
|
||||
|
||||
void AlxPlayerControllerBase::BeginPlay()
|
||||
{
|
||||
Super::BeginPlay();
|
||||
if (FSlateApplication::IsInitialized())
|
||||
{
|
||||
FocusChangingHandle = FSlateApplication::Get().OnFocusChanging().AddUObject(
|
||||
this, &AlxPlayerControllerBase::HandleFocusChanging);
|
||||
}
|
||||
}
|
||||
|
||||
void AlxPlayerControllerBase::EndPlay(const EEndPlayReason::Type EndPlayReason)
|
||||
{
|
||||
if (FocusChangingHandle.IsValid() && FSlateApplication::IsInitialized())
|
||||
{
|
||||
FSlateApplication::Get().OnFocusChanging().Remove(FocusChangingHandle);
|
||||
FocusChangingHandle.Reset();
|
||||
}
|
||||
Super::EndPlay(EndPlayReason);
|
||||
}
|
||||
|
||||
void AlxPlayerControllerBase::HandleFocusChanging(
|
||||
const FFocusEvent &FocusEvent,
|
||||
const FWeakWidgetPath &OldPath,
|
||||
const TSharedPtr<SWidget> &OldFocusedWidget,
|
||||
const FWidgetPath &NewPath,
|
||||
const TSharedPtr<SWidget> &NewFocusedWidget)
|
||||
{
|
||||
UE_LOG(LogLuprexIntegration, Display,
|
||||
TEXT("Focus changing: '%s' -> '%s' (cause: %s)"),
|
||||
OldFocusedWidget.IsValid() ? *OldFocusedWidget->GetTypeAsString() : TEXT("<none>"),
|
||||
NewFocusedWidget.IsValid() ? *NewFocusedWidget->GetTypeAsString() : TEXT("<none>"),
|
||||
*UEnum::GetValueAsString(FocusEvent.GetCause()));
|
||||
}
|
||||
|
||||
UInputComponent* AlxPlayerControllerBase::GetWidgetInputComponent(UUserWidget *Widget)
|
||||
{
|
||||
if (!IsValid(Widget)) return nullptr;
|
||||
@@ -75,7 +111,7 @@ UInputComponent* AlxPlayerControllerBase::GetWidgetInputComponent(UUserWidget *W
|
||||
return Cast<UInputComponent>(Value);
|
||||
}
|
||||
|
||||
void AlxPlayerControllerBase::WidgetRequestInputMode(UUserWidget *Widget, bool ShowPointer, bool BlockInput, UWidget *Focus)
|
||||
void AlxPlayerControllerBase::WidgetRequestInputMode(UUserWidget *Widget, bool ShowPointer, bool BlockInput, UWidget *Focus, bool EnableInputComponent)
|
||||
{
|
||||
if (!IsValid(Widget))
|
||||
{
|
||||
@@ -91,7 +127,13 @@ void AlxPlayerControllerBase::WidgetRequestInputMode(UUserWidget *Widget, bool S
|
||||
*Widget->GetName(), *GetNameSafe(OwningPC));
|
||||
return;
|
||||
}
|
||||
PC->InputModeRequests.Request(FlxInputModeRequest(Widget, Focus, ShowPointer, BlockInput));
|
||||
FlxInputModeRequest Req;
|
||||
Req.Widget = Widget;
|
||||
Req.Focus = Focus;
|
||||
Req.ShowPointer = ShowPointer;
|
||||
Req.BlockInput = BlockInput;
|
||||
Req.EnableInputComponent = EnableInputComponent;
|
||||
PC->InputModeRequests.Request(Req);
|
||||
}
|
||||
|
||||
void AlxPlayerControllerBase::PushInputComponent(UInputComponent* InInputComponent)
|
||||
@@ -103,7 +145,7 @@ void AlxPlayerControllerBase::PushInputComponent(UInputComponent* InInputCompone
|
||||
// focus, pointer visibility, and input blocking across them.
|
||||
if (UUserWidget *Widget = Cast<UUserWidget>(InInputComponent->GetOuter()))
|
||||
{
|
||||
InputModeRequests.EnsureWidget(Widget);
|
||||
InputModeRequests.SetEnableInputComponent(Widget, true);
|
||||
return;
|
||||
}
|
||||
CurrentInputStack.RemoveSingle(InInputComponent);
|
||||
@@ -115,6 +157,12 @@ bool AlxPlayerControllerBase::PopInputComponent(UInputComponent* InInputComponen
|
||||
{
|
||||
if (InInputComponent)
|
||||
{
|
||||
if (UUserWidget *Widget = Cast<UUserWidget>(InInputComponent->GetOuter()))
|
||||
{
|
||||
InputModeRequests.SetEnableInputComponent(Widget, false);
|
||||
InInputComponent->ClearBindingValues();
|
||||
return true;
|
||||
}
|
||||
if (CurrentInputStack.RemoveSingle(InInputComponent) > 0)
|
||||
{
|
||||
InInputComponent->ClearBindingValues();
|
||||
@@ -197,11 +245,13 @@ void AlxPlayerControllerBase::BuildInputStack(TArray<UInputComponent*>& InputSta
|
||||
const TArray<FlxInputModeRequest> &Requests = InputModeRequests.GetRequests();
|
||||
for (int32 Idx = Requests.Num() - 1; Idx >= 0; --Idx)
|
||||
{
|
||||
UUserWidget *Widget = Requests[Idx].Widget;
|
||||
const FlxInputModeRequest &Req = Requests[Idx];
|
||||
if (!Req.EnableInputComponent) continue;
|
||||
UUserWidget *Widget = Req.Widget;
|
||||
if (!Widget->IsInViewport()) continue;
|
||||
if (UInputComponent *IC = GetWidgetInputComponent(Widget))
|
||||
{
|
||||
IC->bBlockInput = Requests[Idx].BlockInput;
|
||||
IC->bBlockInput = Req.BlockInput;
|
||||
InputStack.Push(IC);
|
||||
}
|
||||
}
|
||||
@@ -220,6 +270,8 @@ void AlxPlayerControllerBase::UpdateInputMode()
|
||||
if (!ViewportWidget.IsValid()) return;
|
||||
TSharedRef<SViewport> ViewportWidgetRef = ViewportWidget.ToSharedRef();
|
||||
FReply &SlateOperations = LocalPlayer->GetSlateOperations();
|
||||
TSharedPtr<FSlateUser> SlateUser = LocalPlayer->GetSlateUser();
|
||||
if (!SlateUser.IsValid()) return;
|
||||
|
||||
// The first active entry in InputModeRequests dictates the
|
||||
// pointer / capture / focus state. If there are no requests at all,
|
||||
@@ -235,41 +287,66 @@ void AlxPlayerControllerBase::UpdateInputMode()
|
||||
|
||||
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.
|
||||
// Only release capture if the viewport is currently holding it
|
||||
// (e.g. we just came from GameOnly). A blanket ReleaseMouseCapture
|
||||
// every tick would yank capture away from widgets mid-gesture
|
||||
// (scrollbar drags, slider drags, etc.).
|
||||
if (SlateUser->DoesWidgetHaveAnyCapture(ViewportWidget))
|
||||
{
|
||||
SlateOperations.ReleaseMouseCapture();
|
||||
}
|
||||
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.
|
||||
// Also captures the mouse to the viewport.
|
||||
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);
|
||||
|
||||
// How we handle focus depends on whether we're showing the pointer.
|
||||
// In pointer mode, we set focus to the desired state just once,
|
||||
// and then we let the pointer control it from there on. In
|
||||
// no-pointer mode, we set focus to the desired state and
|
||||
// keep putting it back forever.
|
||||
//
|
||||
// If the user clicks the mouse on a focusable widget, the
|
||||
// viewport client notifies us of that fact. We then focus the
|
||||
// widget if possible.
|
||||
//
|
||||
if ((!Top->ShowPointer) || (LastRequestGrantedFocus != Top->SequenceNumber))
|
||||
{
|
||||
if (Top->Focus)
|
||||
{
|
||||
if (TSharedPtr<SWidget> SlateFocus = Top->Focus->GetCachedWidget())
|
||||
{
|
||||
SlateOperations.SetUserFocus(SlateFocus.ToSharedRef());
|
||||
LastRequestGrantedFocus = Top->SequenceNumber;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
SlateOperations.SetUserFocus(ViewportWidgetRef);
|
||||
LastRequestGrantedFocus = Top->SequenceNumber;
|
||||
}
|
||||
}
|
||||
else if (TSharedPtr<SWidget> ClickedWidget = ClickToFocusTarget.Pin())
|
||||
{
|
||||
SlateOperations.SetUserFocus(ClickedWidget.ToSharedRef());
|
||||
}
|
||||
ClickToFocusTarget.Reset();
|
||||
}
|
||||
|
||||
void AlxPlayerControllerBase::ClickToFocus(TSharedRef<SWidget> Widget)
|
||||
{
|
||||
ClickToFocusTarget = Widget.ToWeakPtr();
|
||||
}
|
||||
|
||||
void AlxPlayerControllerBase::UpdateLookAt()
|
||||
|
||||
Reference in New Issue
Block a user