Lots of work on focus management

This commit is contained in:
2026-04-19 03:01:23 -04:00
parent fd970f20c3
commit dabb5b8f0b
8 changed files with 266 additions and 84 deletions

View File

@@ -4,7 +4,7 @@
[/Script/Engine.Engine] [/Script/Engine.Engine]
GameViewportClientClassName=/Script/CommonUI.CommonGameViewportClient GameViewportClientClassName=/Script/Integration.lxViewportClient
[/Script/EngineSettings.GameMapsSettings] [/Script/EngineSettings.GameMapsSettings]
GameDefaultMap=/Game/LpxLevel.LpxLevel GameDefaultMap=/Game/LpxLevel.LpxLevel

Binary file not shown.

View File

@@ -1,10 +1,10 @@
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
// //
// InputEvents.cpp // InputModeRequest.cpp
// //
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
#include "InputEvents.h" #include "InputModeRequest.h"
#include "Common.h" #include "Common.h"
bool FlxInputModeRequest::operator==(const FlxInputModeRequest &Other) const bool FlxInputModeRequest::operator==(const FlxInputModeRequest &Other) const
@@ -12,7 +12,8 @@ bool FlxInputModeRequest::operator==(const FlxInputModeRequest &Other) const
return (Widget == Other.Widget) && return (Widget == Other.Widget) &&
(Focus == Other.Focus) && (Focus == Other.Focus) &&
(ShowPointer == Other.ShowPointer) && (ShowPointer == Other.ShowPointer) &&
(BlockInput == Other.BlockInput); (BlockInput == Other.BlockInput) &&
(EnableInputComponent == Other.EnableInputComponent);
} }
bool FlxInputModeRequests::SanityCheck(const FlxInputModeRequest &Request) bool FlxInputModeRequests::SanityCheck(const FlxInputModeRequest &Request)
@@ -42,60 +43,55 @@ void FlxInputModeRequests::Request(const FlxInputModeRequest &NewRequest)
View High, Low; View High, Low;
SplitHighLow(High, Low); SplitHighLow(High, Low);
// This is a simple test to see if anything is going to change. // Stamp this request with a fresh sequence number.
// If not, we return early and avoid setting the dirty bit. FlxInputModeRequest Stamped = NewRequest;
if (IsHigh) Stamped.SequenceNumber = ++NextSequenceNumber;
{
if ((High.Num() > 0) && (High[0] == NewRequest)) return;
}
else
{
if ((Low.Num() > 0) && (Low[0] == NewRequest)) return;
}
// We're going to build a new version of the requests array. // We're going to build a new version of the requests array.
TArray<FlxInputModeRequest> Updated; TArray<FlxInputModeRequest> Updated;
// Add all high priority requests to the updated array, new request first. // Add all high priority requests to the updated array, new request first.
if (IsHigh) Updated.Add(NewRequest); if (IsHigh) Updated.Add(Stamped);
for (const FlxInputModeRequest &Req : High) for (const FlxInputModeRequest &Req : High)
if (Req.Widget != NewRequest.Widget) Updated.Add(Req); if (Req.Widget != NewRequest.Widget) Updated.Add(Req);
// Add all low priority requests to the updated array, new request first. // Add all low priority requests to the updated array, new request first.
if (!IsHigh) Updated.Add(NewRequest); if (!IsHigh) Updated.Add(Stamped);
for (const FlxInputModeRequest &Req : Low) for (const FlxInputModeRequest &Req : Low)
if (Req.Widget != NewRequest.Widget) Updated.Add(Req); if (Req.Widget != NewRequest.Widget) Updated.Add(Req);
Swap(Requests, Updated); Swap(Requests, Updated);
Dirty = true;
} }
void FlxInputModeRequests::EnsureWidget(UUserWidget *Widget) void FlxInputModeRequests::SetEnableInputComponent(UUserWidget *Widget, bool EnableInputComponent)
{ {
for (const FlxInputModeRequest &Req : Requests) for (FlxInputModeRequest &Req : Requests)
{ {
if (Req.Widget == Widget) return; if (Req.Widget == Widget)
{
Req.EnableInputComponent = EnableInputComponent;
return;
}
} }
Request(FlxInputModeRequest(Widget, nullptr, false, false)); FlxInputModeRequest NewReq;
NewReq.Widget = Widget;
NewReq.EnableInputComponent = EnableInputComponent;
Request(NewReq);
} }
void FlxInputModeRequests::Remove(UUserWidget *Widget) void FlxInputModeRequests::Remove(UUserWidget *Widget)
{ {
int32 N = Requests.Num();
Requests.RemoveAll([Widget](const FlxInputModeRequest &Entry) Requests.RemoveAll([Widget](const FlxInputModeRequest &Entry)
{ {
return Entry.Widget == Widget; return Entry.Widget == Widget;
}); });
if (Requests.Num() < N) Dirty = true;
} }
void FlxInputModeRequests::GarbageCollect() void FlxInputModeRequests::GarbageCollect()
{ {
int32 N = Requests.Num();
Requests.RemoveAll([](const FlxInputModeRequest &Entry) Requests.RemoveAll([](const FlxInputModeRequest &Entry)
{ {
return !IsValid(Entry.Widget); return !IsValid(Entry.Widget);
}); });
if (Requests.Num() < N) Dirty = true;
} }

View File

@@ -1,6 +1,6 @@
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
// //
// InputEvents.h // InputModeRequest.h
// //
// Custom input event dispatching system. Uses Unreal's // Custom input event dispatching system. Uses Unreal's
// built-in input modes (GameOnly / UIOnly) with an // built-in input modes (GameOnly / UIOnly) with an
@@ -12,21 +12,17 @@
#include "CoreMinimal.h" #include "CoreMinimal.h"
#include "Blueprint/UserWidget.h" #include "Blueprint/UserWidget.h"
#include "InputEvents.generated.h" #include "InputModeRequest.generated.h"
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
// //
// FlxInputModeRequest // FlxInputModeRequest
// //
// A widget's declaration of interest in input events. // Using this struct, a Widget can express a need for a
// // particular input mode. These requests go to the player
// Widget: The widget that wants to receive events. // controller, which arbitrates.
// Focus: The widget that should receive keyboard focus
// while this request is active.
// ShowPointer: If true, the mouse pointer should be
// visible when this widget has control.
// BlockInput: If true, input actions should be blocked from
// reaching lower-priority consumers (e.g. the pawn).
// //
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
@@ -36,8 +32,6 @@ struct FlxInputModeRequest
GENERATED_BODY() GENERATED_BODY()
FlxInputModeRequest() = default; FlxInputModeRequest() = default;
FlxInputModeRequest(UUserWidget *InWidget, UWidget *InFocus, bool InShowPointer, bool InBlockInput)
: Widget(InWidget), Focus(InFocus), ShowPointer(InShowPointer), BlockInput(InBlockInput) {}
bool operator == (const FlxInputModeRequest &Other) const; bool operator == (const FlxInputModeRequest &Other) const;
@@ -56,6 +50,13 @@ struct FlxInputModeRequest
UPROPERTY(BlueprintReadWrite) UPROPERTY(BlueprintReadWrite)
bool BlockInput = false; bool BlockInput = false;
UPROPERTY(BlueprintReadWrite)
bool EnableInputComponent = true;
UPROPERTY()
int32 SequenceNumber = 0;
}; };
USTRUCT() USTRUCT()
@@ -65,12 +66,11 @@ struct FlxInputModeRequests
private: private:
UPROPERTY() UPROPERTY()
// High priority requests are always before low-priority. // Sorted by highest priority first, then most recent first.
// Otherwise, these are in order of most recent first.
TArray<FlxInputModeRequest> Requests; TArray<FlxInputModeRequest> Requests;
UPROPERTY() UPROPERTY()
bool Dirty = true; int32 NextSequenceNumber = 0;
public: public:
using View = TArrayView<FlxInputModeRequest>; using View = TArrayView<FlxInputModeRequest>;
@@ -87,22 +87,13 @@ public:
// Apply a request. Replaces any previous request by the same widget. // Apply a request. Replaces any previous request by the same widget.
void Request(const FlxInputModeRequest &NewRequest); void Request(const FlxInputModeRequest &NewRequest);
// Ensure the specified widget has a request in the array. // Find the specified widget, and modify the 'EnableInputComponent'
// If it isn't already present, register a low-priority request for it. // flag. Adds the widget if it's not already present.
void EnsureWidget(UUserWidget *Widget); void SetEnableInputComponent(UUserWidget *Widget, bool EnableInputComponent);
// Remove all requests by the specified widget. // Remove all requests by the specified widget.
void Remove(UUserWidget *Widget); void Remove(UUserWidget *Widget);
// Remove any requests by dead widgets or widgets with no parents. // Remove any requests by dead widgets or widgets with no parents.
void GarbageCollect(); void GarbageCollect();
// Return true if the requests have changed since the last ClearDirty.
bool IsDirty() { return Dirty; }
// Clear the dirty flag.
void ClearDirty() { Dirty = false; }
// Set the dirty flag.
void SetDirty() { Dirty = true; }
}; };

View File

@@ -0,0 +1,62 @@
////////////////////////////////////////////////////////////
//
// LuprexViewportClient.cpp
//
////////////////////////////////////////////////////////////
#include "LuprexViewportClient.h"
#include "Common.h"
#include "PlayerControllerBase.h"
#include "Engine/GameInstance.h"
#include "Framework/Application/SlateApplication.h"
#include "Layout/WidgetPath.h"
#include "Widgets/SViewport.h"
UlxViewportClient::UlxViewportClient(const FObjectInitializer &ObjectInitializer)
: Super(ObjectInitializer)
{
UE_LOG(LogLuprexIntegration, Display, TEXT("UlxViewportClient constructed"));
}
bool UlxViewportClient::InputKey(const FInputKeyEventArgs &EventArgs)
{
UE_LOG(LogLuprexIntegration, Display, TEXT("UlxViewportClient::InputKey key=%s event=%d"),
*EventArgs.Key.ToString(), (int32)EventArgs.Event);
// Only act on left mouse button presses that bubbled up to the
// viewport unhandled. Walk the widget path under the cursor and
// find the nearest focusable ancestor of whatever was hit. If it
// isn't the game viewport itself, hand it to the player controller
// to apply on its next UpdateInputMode pass; that's the point in
// the frame where we can override SViewport's own click-focus
// behaviour without fighting it.
if ((EventArgs.Event == IE_Pressed) && (EventArgs.Key == EKeys::LeftMouseButton))
{
FSlateApplication &Slate = FSlateApplication::Get();
FVector2D MousePos = Slate.GetCursorPos();
FWidgetPath Path = Slate.LocateWindowUnderMouse(
MousePos, Slate.GetInteractiveTopLevelWindows());
if (Path.IsValid())
{
TSharedPtr<SViewport> ViewportWidget = GetGameViewportWidget();
for (int32 Idx = Path.Widgets.Num() - 1; Idx >= 0; --Idx)
{
TSharedRef<SWidget> Widget = Path.Widgets[Idx].Widget;
if (!Widget->SupportsKeyboardFocus()) continue;
if (ViewportWidget.IsValid() && Widget == ViewportWidget) break;
if (UGameInstance *GI = GetGameInstance())
{
if (AlxPlayerControllerBase *PC = Cast<AlxPlayerControllerBase>(
GI->GetFirstLocalPlayerController(GetWorld())))
{
PC->ClickToFocus(Widget);
}
}
break;
}
}
}
return Super::InputKey(EventArgs);
}

View File

@@ -0,0 +1,28 @@
////////////////////////////////////////////////////////////
//
// LuprexViewportClient.h
//
// Custom game viewport client. Implements a project-wide
// click-to-focus rule: when a left-mouse-button click is
// not handled by any widget and bubbles up to the viewport,
// we hit-test under the cursor and focus the nearest
// focusable ancestor of whatever was hit.
//
////////////////////////////////////////////////////////////
#pragma once
#include "CoreMinimal.h"
#include "Engine/GameViewportClient.h"
#include "LuprexViewportClient.generated.h"
UCLASS()
class INTEGRATION_API UlxViewportClient : public UGameViewportClient
{
GENERATED_BODY()
public:
UlxViewportClient(const FObjectInitializer &ObjectInitializer);
virtual bool InputKey(const FInputKeyEventArgs &EventArgs) override;
};

View File

@@ -8,6 +8,8 @@
#include "Engine/GameViewportClient.h" #include "Engine/GameViewportClient.h"
#include "Engine/LevelScriptActor.h" #include "Engine/LevelScriptActor.h"
#include "Engine/LocalPlayer.h" #include "Engine/LocalPlayer.h"
#include "Framework/Application/SlateApplication.h"
#include "Framework/Application/SlateUser.h"
#include "Kismet/GameplayStatics.h" #include "Kismet/GameplayStatics.h"
#include "Widgets/SViewport.h" #include "Widgets/SViewport.h"
@@ -59,6 +61,40 @@ FVector2D AlxPlayerControllerBase::GetLookAtPixel(const UObject *Context)
return ScreenPosition; 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) UInputComponent* AlxPlayerControllerBase::GetWidgetInputComponent(UUserWidget *Widget)
{ {
if (!IsValid(Widget)) return nullptr; if (!IsValid(Widget)) return nullptr;
@@ -75,7 +111,7 @@ UInputComponent* AlxPlayerControllerBase::GetWidgetInputComponent(UUserWidget *W
return Cast<UInputComponent>(Value); 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)) if (!IsValid(Widget))
{ {
@@ -91,7 +127,13 @@ void AlxPlayerControllerBase::WidgetRequestInputMode(UUserWidget *Widget, bool S
*Widget->GetName(), *GetNameSafe(OwningPC)); *Widget->GetName(), *GetNameSafe(OwningPC));
return; 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) void AlxPlayerControllerBase::PushInputComponent(UInputComponent* InInputComponent)
@@ -103,7 +145,7 @@ void AlxPlayerControllerBase::PushInputComponent(UInputComponent* InInputCompone
// focus, pointer visibility, and input blocking across them. // focus, pointer visibility, and input blocking across them.
if (UUserWidget *Widget = Cast<UUserWidget>(InInputComponent->GetOuter())) if (UUserWidget *Widget = Cast<UUserWidget>(InInputComponent->GetOuter()))
{ {
InputModeRequests.EnsureWidget(Widget); InputModeRequests.SetEnableInputComponent(Widget, true);
return; return;
} }
CurrentInputStack.RemoveSingle(InInputComponent); CurrentInputStack.RemoveSingle(InInputComponent);
@@ -115,6 +157,12 @@ bool AlxPlayerControllerBase::PopInputComponent(UInputComponent* InInputComponen
{ {
if (InInputComponent) if (InInputComponent)
{ {
if (UUserWidget *Widget = Cast<UUserWidget>(InInputComponent->GetOuter()))
{
InputModeRequests.SetEnableInputComponent(Widget, false);
InInputComponent->ClearBindingValues();
return true;
}
if (CurrentInputStack.RemoveSingle(InInputComponent) > 0) if (CurrentInputStack.RemoveSingle(InInputComponent) > 0)
{ {
InInputComponent->ClearBindingValues(); InInputComponent->ClearBindingValues();
@@ -197,11 +245,13 @@ void AlxPlayerControllerBase::BuildInputStack(TArray<UInputComponent*>& InputSta
const TArray<FlxInputModeRequest> &Requests = InputModeRequests.GetRequests(); const TArray<FlxInputModeRequest> &Requests = InputModeRequests.GetRequests();
for (int32 Idx = Requests.Num() - 1; Idx >= 0; --Idx) 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 (!Widget->IsInViewport()) continue;
if (UInputComponent *IC = GetWidgetInputComponent(Widget)) if (UInputComponent *IC = GetWidgetInputComponent(Widget))
{ {
IC->bBlockInput = Requests[Idx].BlockInput; IC->bBlockInput = Req.BlockInput;
InputStack.Push(IC); InputStack.Push(IC);
} }
} }
@@ -220,6 +270,8 @@ void AlxPlayerControllerBase::UpdateInputMode()
if (!ViewportWidget.IsValid()) return; if (!ViewportWidget.IsValid()) return;
TSharedRef<SViewport> ViewportWidgetRef = ViewportWidget.ToSharedRef(); TSharedRef<SViewport> ViewportWidgetRef = ViewportWidget.ToSharedRef();
FReply &SlateOperations = LocalPlayer->GetSlateOperations(); FReply &SlateOperations = LocalPlayer->GetSlateOperations();
TSharedPtr<FSlateUser> SlateUser = LocalPlayer->GetSlateUser();
if (!SlateUser.IsValid()) return;
// The first active entry in InputModeRequests dictates the // The first active entry in InputModeRequests dictates the
// pointer / capture / focus state. If there are no requests at all, // pointer / capture / focus state. If there are no requests at all,
@@ -235,41 +287,66 @@ void AlxPlayerControllerBase::UpdateInputMode()
if (Top->ShowPointer) if (Top->ShowPointer)
{ {
// Pointer-visible mode: full macroexpansion of SetInputModeGameAndUI // Only release capture if the viewport is currently holding it
// — the BP wrapper, APlayerController::SetInputMode, and // (e.g. we just came from GameOnly). A blanket ReleaseMouseCapture
// FInputModeGameAndUI::ApplyInputMode all inlined. Defaults: // every tick would yank capture away from widgets mid-gesture
// MouseLockMode=DoNotLock, WidgetToFocus=null, bHideCursorDuringCapture=true. // (scrollbar drags, slider drags, etc.).
if (SlateUser->DoesWidgetHaveAnyCapture(ViewportWidget))
{
SlateOperations.ReleaseMouseCapture();
}
SlateOperations.ReleaseMouseLock(); SlateOperations.ReleaseMouseLock();
SlateOperations.ReleaseMouseCapture();
GameViewportClient->SetMouseLockMode(EMouseLockMode::DoNotLock); GameViewportClient->SetMouseLockMode(EMouseLockMode::DoNotLock);
GameViewportClient->SetHideCursorDuringCapture(false); GameViewportClient->SetHideCursorDuringCapture(false);
GameViewportClient->SetMouseCaptureMode(EMouseCaptureMode::CaptureDuringMouseDown); GameViewportClient->SetMouseCaptureMode(EMouseCaptureMode::CaptureDuringMouseDown);
} }
else else
{ {
// Pointer-invisible mode: full macroexpansion of SetInputModeGameOnly // Also captures the mouse to the viewport.
// — the BP wrapper, APlayerController::SetInputMode, and
// FInputModeGameOnly::ApplyInputMode all inlined.
SlateOperations.UseHighPrecisionMouseMovement(ViewportWidgetRef); SlateOperations.UseHighPrecisionMouseMovement(ViewportWidgetRef);
SlateOperations.LockMouseToWidget(ViewportWidgetRef); SlateOperations.LockMouseToWidget(ViewportWidgetRef);
GameViewportClient->SetMouseLockMode(EMouseLockMode::LockOnCapture); GameViewportClient->SetMouseLockMode(EMouseLockMode::LockOnCapture);
GameViewportClient->SetMouseCaptureMode(EMouseCaptureMode::CapturePermanently); 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); 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() void AlxPlayerControllerBase::UpdateLookAt()

View File

@@ -3,7 +3,7 @@
#include "CoreMinimal.h" #include "CoreMinimal.h"
#include "Engine/HitResult.h" #include "Engine/HitResult.h"
#include "GameFramework/PlayerController.h" #include "GameFramework/PlayerController.h"
#include "InputEvents.h" #include "InputModeRequest.h"
#include "PlayerControllerBase.generated.h" #include "PlayerControllerBase.generated.h"
UCLASS(BlueprintType, Blueprintable) UCLASS(BlueprintType, Blueprintable)
@@ -41,6 +41,20 @@ public:
// eventually reconcile focus, pointer, and capture state. // eventually reconcile focus, pointer, and capture state.
void UpdateInputMode(); void UpdateInputMode();
virtual void BeginPlay() override;
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
private:
FDelegateHandle FocusChangingHandle;
void HandleFocusChanging(
const struct FFocusEvent &FocusEvent,
const class FWeakWidgetPath &OldPath,
const TSharedPtr<class SWidget> &OldFocusedWidget,
const class FWidgetPath &NewPath,
const TSharedPtr<class SWidget> &NewFocusedWidget);
public:
// Input stack overrides: unsorted, append-on-push. // Input stack overrides: unsorted, append-on-push.
virtual void PushInputComponent(UInputComponent* InInputComponent) override; virtual void PushInputComponent(UInputComponent* InInputComponent) override;
virtual bool PopInputComponent(UInputComponent* InInputComponent) override; virtual bool PopInputComponent(UInputComponent* InInputComponent) override;
@@ -55,8 +69,8 @@ public:
// (thanks to DefaultToSelf + HideSelfPin): the widget self-binds, // (thanks to DefaultToSelf + HideSelfPin): the widget self-binds,
// we find its owning PlayerController, and register the request. // we find its owning PlayerController, and register the request.
UFUNCTION(BlueprintCallable, Category = "Luprex|Input Mode", UFUNCTION(BlueprintCallable, Category = "Luprex|Input Mode",
meta = (DefaultToSelf = "Widget", HideSelfPin = "true")) meta = (DefaultToSelf = "Widget", HideSelfPin = "true", EnableInputComponent = "true"))
static void WidgetRequestInputMode(class UUserWidget *Widget, bool ShowPointer, bool BlockInput, class UWidget *Focus); static void WidgetRequestInputMode(class UUserWidget *Widget, bool ShowPointer, bool BlockInput, class UWidget *Focus, bool EnableInputComponent);
// Get the player controller, cast to AlxPlayerControllerBase. // Get the player controller, cast to AlxPlayerControllerBase.
static AlxPlayerControllerBase *FromContext(const UObject *Context); static AlxPlayerControllerBase *FromContext(const UObject *Context);
@@ -64,8 +78,22 @@ public:
UPROPERTY() UPROPERTY()
FHitResult CurrentLookAt; FHitResult CurrentLookAt;
// Input mode requests - see InputModeRequest.h for an explanation.
UPROPERTY() UPROPERTY()
FlxInputModeRequests InputModeRequests; FlxInputModeRequests InputModeRequests;
// The last input mode request whose focus request was granted.
UPROPERTY()
int32 LastRequestGrantedFocus = 0;
// The viewport client uses this to notify us that the user
// clicked on a focusable widget.
void ClickToFocus(TSharedRef<SWidget> Widget);
private:
TWeakPtr<SWidget> ClickToFocusTarget;
public:
bool MustCallLookAtChanged = false; bool MustCallLookAtChanged = false;
}; };