Lots of work on focus management
This commit is contained in:
@@ -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.
@@ -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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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; }
|
|
||||||
};
|
};
|
||||||
62
Source/Integration/LuprexViewportClient.cpp
Normal file
62
Source/Integration/LuprexViewportClient.cpp
Normal 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);
|
||||||
|
}
|
||||||
28
Source/Integration/LuprexViewportClient.h
Normal file
28
Source/Integration/LuprexViewportClient.h
Normal 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;
|
||||||
|
};
|
||||||
@@ -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()
|
||||||
|
|||||||
@@ -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;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user