Much work on input mode switching
This commit is contained in:
BIN
Content/Luprex/lxGameMode.uasset
LFS
BIN
Content/Luprex/lxGameMode.uasset
LFS
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,5 +1,6 @@
|
||||
|
||||
* UE Wingman rename functions.
|
||||
* ue Wingman 'structprop' doesn't work for UWingXXXRef types, or for Widget slots. It needs to be implemented on top of getdetails.
|
||||
* In the console, do not allow multi-line lua expressions unless it's something that reasonably should be multi-line, like a function definition or an if-statement.
|
||||
|
||||
* Keyboard Event Handling
|
||||
|
||||
@@ -341,7 +341,11 @@ FString FWingProperty::GetText() const
|
||||
return TEXT("None");
|
||||
}
|
||||
FString Result;
|
||||
Prop->ExportTextItem_InContainer(Result, Container, nullptr, nullptr, PPF_None);
|
||||
// DefaultValue == PropertyValue makes ExportText_Direct's Data==Delta
|
||||
// short-circuit fire for every subfield, so default-valued fields still
|
||||
// get emitted (e.g. SizeRule=Automatic on FSlateChildSize).
|
||||
const void* Value = Prop->ContainerPtrToValuePtr<void>(Container);
|
||||
Prop->ExportTextItem_Direct(Result, Value, /*DefaultValue=*/Value, nullptr, PPF_None);
|
||||
return Result;
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ import socket
|
||||
HOST = "localhost"
|
||||
PORT = 9851
|
||||
CONNECT_TIMEOUT = 2
|
||||
READ_TIMEOUT = 120
|
||||
READ_TIMEOUT = 30
|
||||
|
||||
TOOL_DESCRIPTION = (
|
||||
"Send a command to the Unreal Editor's UE Wingman plugin. "
|
||||
@@ -91,12 +91,6 @@ def forward_to_editor(arguments):
|
||||
return send_and_receive(arguments)
|
||||
except Exception:
|
||||
disconnect()
|
||||
# Retry once in case the connection was stale
|
||||
if connect():
|
||||
try:
|
||||
return send_and_receive(arguments)
|
||||
except Exception:
|
||||
disconnect()
|
||||
return {"error": "Lost connection to Unreal Editor."}
|
||||
|
||||
|
||||
@@ -104,6 +98,28 @@ def make_jsonrpc(msg_id, result):
|
||||
return {"jsonrpc": "2.0", "id": msg_id, "result": result}
|
||||
|
||||
|
||||
def parse_editor_response(result):
|
||||
"""Parse and validate a raw editor response into an MCP content list.
|
||||
|
||||
MCP expects `content` to be a list of objects, each with at least a
|
||||
string "type" field (e.g. {"type": "text", "text": "..."}). Anything
|
||||
else is replaced with a single error item so the client sees a clear
|
||||
message instead of a schema violation.
|
||||
"""
|
||||
try:
|
||||
parsed = json.loads(result)
|
||||
except json.JSONDecodeError:
|
||||
return [{"type": "text", "text": "Malformed response from editor: invalid JSON."}]
|
||||
if not isinstance(parsed, list):
|
||||
return [{"type": "text", "text": "Malformed response from editor: expected a list."}]
|
||||
for item in parsed:
|
||||
if not isinstance(item, dict):
|
||||
return [{"type": "text", "text": "Malformed response from editor: list item is not an object."}]
|
||||
if not isinstance(item.get("type"), str):
|
||||
return [{"type": "text", "text": "Malformed response from editor: item missing string 'type' field."}]
|
||||
return parsed
|
||||
|
||||
|
||||
def handle_message(msg):
|
||||
"""Handle one JSON-RPC message from Claude Code."""
|
||||
msg_id = msg.get("id")
|
||||
@@ -130,10 +146,7 @@ def handle_message(msg):
|
||||
if isinstance(result, dict) and "error" in result:
|
||||
content = [{"type": "text", "text": result["error"]}]
|
||||
else:
|
||||
try:
|
||||
content = json.loads(result)
|
||||
except json.JSONDecodeError:
|
||||
content = [{"type": "text", "text": "Malformed response from editor."}]
|
||||
content = parse_editor_response(result)
|
||||
return make_jsonrpc(msg_id, {
|
||||
"content": content,
|
||||
})
|
||||
|
||||
@@ -7,52 +7,44 @@
|
||||
#include "InputEvents.h"
|
||||
#include "Common.h"
|
||||
|
||||
bool FlxEventRequest::operator==(const FlxEventRequest &Other) const
|
||||
bool FlxInputModeRequest::operator==(const FlxInputModeRequest &Other) const
|
||||
{
|
||||
return (Widget == Other.Widget) &&
|
||||
(UseUIOnly == Other.UseUIOnly) &&
|
||||
(Focus == Other.Focus) &&
|
||||
(ShowPointer == Other.ShowPointer) &&
|
||||
(Hotkeys == Other.Hotkeys);
|
||||
(BlockInput == Other.BlockInput);
|
||||
}
|
||||
|
||||
bool FlxEventRequests::SanityCheck(const FlxEventRequest &Request)
|
||||
bool FlxInputModeRequests::SanityCheck(const FlxInputModeRequest &Request)
|
||||
{
|
||||
if (Request.Widget == nullptr)
|
||||
{
|
||||
UE_LOG(LogLuprexIntegration, Error, TEXT("RequestEvents called with null widget."));
|
||||
return false;
|
||||
}
|
||||
if (Request.ShowPointer && !Request.UseUIOnly)
|
||||
{
|
||||
UE_LOG(LogLuprexIntegration, Error, TEXT("RequestEvents: ShowPointer requires UseUIOnly."));
|
||||
return false;
|
||||
}
|
||||
if (Request.UseUIOnly && !Request.Hotkeys.IsEmpty())
|
||||
{
|
||||
UE_LOG(LogLuprexIntegration, Error, TEXT("RequestEvents: Widget asked for all events, and also, specific events"));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void FlxEventRequests::SplitHighLow(View &High, View &Low)
|
||||
void FlxInputModeRequests::SplitHighLow(View &High, View &Low)
|
||||
{
|
||||
int32 NumHigh = 0;
|
||||
while ((NumHigh < Requests.Num()) && (Requests[NumHigh].UseUIOnly)) NumHigh++;
|
||||
while ((NumHigh < Requests.Num()) && (Requests[NumHigh].IsHighPrio())) NumHigh++;
|
||||
int32 NumLow = Requests.Num() - NumHigh;
|
||||
High = View(Requests.GetData(), NumHigh);
|
||||
Low = View(Requests.GetData() + NumHigh, NumLow);
|
||||
}
|
||||
|
||||
void FlxEventRequests::Request(const FlxEventRequest &NewRequest)
|
||||
void FlxInputModeRequests::Request(const FlxInputModeRequest &NewRequest)
|
||||
{
|
||||
bool IsHigh = NewRequest.IsHighPrio();
|
||||
|
||||
// Divide the array into a high-priority slice and a low-priority slice.
|
||||
View High, Low;
|
||||
SplitHighLow(High, Low);
|
||||
|
||||
// This is a simple test to see if anything is going to change.
|
||||
// If not, we return early and avoid setting the dirty bit.
|
||||
if (NewRequest.UseUIOnly)
|
||||
if (IsHigh)
|
||||
{
|
||||
if ((High.Num() > 0) && (High[0] == NewRequest)) return;
|
||||
}
|
||||
@@ -62,36 +54,45 @@ void FlxEventRequests::Request(const FlxEventRequest &NewRequest)
|
||||
}
|
||||
|
||||
// We're going to build a new version of the requests array.
|
||||
TArray<FlxEventRequest> Updated;
|
||||
TArray<FlxInputModeRequest> Updated;
|
||||
|
||||
// Add all high priority requests to the updated array, new request first.
|
||||
if (NewRequest.UseUIOnly) Updated.Add(NewRequest);
|
||||
for (const FlxEventRequest &Req : High)
|
||||
if (IsHigh) Updated.Add(NewRequest);
|
||||
for (const FlxInputModeRequest &Req : High)
|
||||
if (Req.Widget != NewRequest.Widget) Updated.Add(Req);
|
||||
|
||||
// Add all low priority requests to the updated array, new request first.
|
||||
if (!NewRequest.UseUIOnly) Updated.Add(NewRequest);
|
||||
for (const FlxEventRequest &Req : Low)
|
||||
if (!IsHigh) Updated.Add(NewRequest);
|
||||
for (const FlxInputModeRequest &Req : Low)
|
||||
if (Req.Widget != NewRequest.Widget) Updated.Add(Req);
|
||||
|
||||
Swap(Requests, Updated);
|
||||
Dirty = true;
|
||||
}
|
||||
|
||||
void FlxEventRequests::Remove(UUserWidget *Widget)
|
||||
void FlxInputModeRequests::EnsureWidget(UUserWidget *Widget)
|
||||
{
|
||||
for (const FlxInputModeRequest &Req : Requests)
|
||||
{
|
||||
if (Req.Widget == Widget) return;
|
||||
}
|
||||
Request(FlxInputModeRequest(Widget, nullptr, false, false));
|
||||
}
|
||||
|
||||
void FlxInputModeRequests::Remove(UUserWidget *Widget)
|
||||
{
|
||||
int32 N = Requests.Num();
|
||||
Requests.RemoveAll([Widget](const FlxEventRequest &Entry)
|
||||
Requests.RemoveAll([Widget](const FlxInputModeRequest &Entry)
|
||||
{
|
||||
return Entry.Widget == Widget;
|
||||
});
|
||||
if (Requests.Num() < N) Dirty = true;
|
||||
}
|
||||
|
||||
void FlxEventRequests::GarbageCollect()
|
||||
void FlxInputModeRequests::GarbageCollect()
|
||||
{
|
||||
int32 N = Requests.Num();
|
||||
Requests.RemoveAll([](const FlxEventRequest &Entry)
|
||||
Requests.RemoveAll([](const FlxInputModeRequest &Entry)
|
||||
{
|
||||
UUserWidget *W = Entry.Widget;
|
||||
return W == nullptr || !IsValid(W) || W->GetParent() == nullptr;
|
||||
@@ -99,14 +100,3 @@ void FlxEventRequests::GarbageCollect()
|
||||
if (Requests.Num() < N) Dirty = true;
|
||||
}
|
||||
|
||||
FlxEventRequests::InputMode FlxEventRequests::GetRequestedMode() const
|
||||
{
|
||||
if ((Requests.Num() > 0) && (Requests[0].UseUIOnly))
|
||||
{
|
||||
return InputMode::UIOnly;
|
||||
}
|
||||
else
|
||||
{
|
||||
return InputMode::GameOnly;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,52 +11,55 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "InputCoreTypes.h"
|
||||
#include "Blueprint/UserWidget.h"
|
||||
#include "InputEvents.generated.h"
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
//
|
||||
// FlxEventRequest
|
||||
// FlxInputModeRequest
|
||||
//
|
||||
// A widget's declaration of interest in input events.
|
||||
//
|
||||
// Widget: The widget that wants to receive events.
|
||||
// UseUIOnly: If true, activating this request puts
|
||||
// the system into UIOnly mode.
|
||||
// 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.
|
||||
// Hotkeys: Keys that go to this widget when the
|
||||
// player is in GameOnly mode.
|
||||
// BlockInput: If true, input actions should be blocked from
|
||||
// reaching lower-priority consumers (e.g. the pawn).
|
||||
//
|
||||
////////////////////////////////////////////////////////////
|
||||
|
||||
USTRUCT(BlueprintType)
|
||||
struct FlxEventRequest
|
||||
struct FlxInputModeRequest
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
FlxEventRequest() = default;
|
||||
FlxEventRequest(UUserWidget *InWidget, bool InUseUIOnly, bool InShowPointer, const TArray<FKey> &InHotkeys)
|
||||
: Widget(InWidget), UseUIOnly(InUseUIOnly), ShowPointer(InShowPointer), Hotkeys(InHotkeys) {}
|
||||
FlxInputModeRequest() = default;
|
||||
FlxInputModeRequest(UUserWidget *InWidget, UWidget *InFocus, bool InShowPointer, bool InBlockInput)
|
||||
: Widget(InWidget), Focus(InFocus), ShowPointer(InShowPointer), BlockInput(InBlockInput) {}
|
||||
|
||||
bool operator == (const FlxEventRequest &Other) const;
|
||||
bool operator == (const FlxInputModeRequest &Other) const;
|
||||
|
||||
// True if this request wants any high-priority resource:
|
||||
// keyboard focus, the mouse pointer, or input blocking.
|
||||
bool IsHighPrio() const { return Focus != nullptr || ShowPointer || BlockInput; }
|
||||
|
||||
UPROPERTY(BlueprintReadWrite)
|
||||
UUserWidget* Widget = nullptr;
|
||||
|
||||
UPROPERTY(BlueprintReadWrite)
|
||||
bool UseUIOnly = false;
|
||||
UWidget* Focus = nullptr;
|
||||
|
||||
UPROPERTY(BlueprintReadWrite)
|
||||
bool ShowPointer = false;
|
||||
|
||||
UPROPERTY(BlueprintReadWrite)
|
||||
TArray<FKey> Hotkeys;
|
||||
bool BlockInput = false;
|
||||
};
|
||||
|
||||
USTRUCT()
|
||||
struct FlxEventRequests
|
||||
struct FlxInputModeRequests
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
@@ -64,27 +67,29 @@ private:
|
||||
UPROPERTY()
|
||||
// High priority requests are always before low-priority.
|
||||
// Otherwise, these are in order of most recent first.
|
||||
TArray<FlxEventRequest> Requests;
|
||||
TArray<FlxInputModeRequest> Requests;
|
||||
|
||||
UPROPERTY()
|
||||
bool Dirty = true;
|
||||
|
||||
public:
|
||||
enum class InputMode { UIOnly, GameOnly };
|
||||
|
||||
using View = TArrayView<FlxEventRequest>;
|
||||
using View = TArrayView<FlxInputModeRequest>;
|
||||
|
||||
// Get the requests array.
|
||||
const TArray<FlxEventRequest> &GetRequests() const { return Requests; }
|
||||
const TArray<FlxInputModeRequest> &GetRequests() const { return Requests; }
|
||||
|
||||
// Sanity check a request to see if it is reasonable.
|
||||
static bool SanityCheck(const FlxEventRequest &Request);
|
||||
static bool SanityCheck(const FlxInputModeRequest &Request);
|
||||
|
||||
// Divide Requests into a high-priority slice and a low-priority slice.
|
||||
void SplitHighLow(View &High, View &Low);
|
||||
|
||||
// Apply a request. Replaces any previous request by the same widget.
|
||||
void Request(const FlxEventRequest &NewRequest);
|
||||
void Request(const FlxInputModeRequest &NewRequest);
|
||||
|
||||
// Ensure the specified widget has a request in the array.
|
||||
// If it isn't already present, register a low-priority request for it.
|
||||
void EnsureWidget(UUserWidget *Widget);
|
||||
|
||||
// Remove all requests by the specified widget.
|
||||
void Remove(UUserWidget *Widget);
|
||||
@@ -100,7 +105,4 @@ public:
|
||||
|
||||
// Set the dirty flag.
|
||||
void SetDirty() { Dirty = true; }
|
||||
|
||||
// Get the currently-requested mode.
|
||||
InputMode GetRequestedMode() const;
|
||||
};
|
||||
|
||||
@@ -172,6 +172,7 @@ void ALuprexGameModeBase::OnWorldPostActorTick(UWorld* InWorld, ELevelTick InLev
|
||||
if (PC != nullptr)
|
||||
{
|
||||
PC->UpdateLookAt();
|
||||
PC->UpdateInputMode();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "CoreMinimal.h"
|
||||
#include "Engine/HitResult.h"
|
||||
#include "GameFramework/PlayerController.h"
|
||||
#include "InputEvents.h"
|
||||
#include "PlayerControllerBase.generated.h"
|
||||
|
||||
UCLASS(BlueprintType, Blueprintable)
|
||||
@@ -36,16 +37,35 @@ public:
|
||||
// Called by GameMode each tick.
|
||||
void UpdateLookAt();
|
||||
|
||||
// Called by GameMode each tick. GCs dead requests and will
|
||||
// eventually reconcile focus, pointer, and capture state.
|
||||
void UpdateInputMode();
|
||||
|
||||
// Input stack overrides: unsorted, append-on-push.
|
||||
virtual void PushInputComponent(UInputComponent* InInputComponent) override;
|
||||
virtual bool PopInputComponent(UInputComponent* InInputComponent) override;
|
||||
virtual void BuildInputStack(TArray<UInputComponent*>& InputStack) override;
|
||||
|
||||
// Read UUserWidget::InputComponent via reflection. The field is
|
||||
// protected and has no public accessor; this reaches through the
|
||||
// FProperty so we always see the current value without caching it.
|
||||
static class UInputComponent* GetWidgetInputComponent(class UUserWidget *Widget);
|
||||
|
||||
// Blueprint-facing entry point. Looks like a method on UUserWidget
|
||||
// (thanks to DefaultToSelf + HideSelfPin): the widget self-binds,
|
||||
// we find its owning PlayerController, and register the request.
|
||||
UFUNCTION(BlueprintCallable, Category = "Luprex|Input Mode",
|
||||
meta = (DefaultToSelf = "Widget", HideSelfPin = "true"))
|
||||
static void WidgetRequestInputMode(class UUserWidget *Widget, bool ShowPointer, bool BlockInput, class UWidget *Focus);
|
||||
|
||||
// Get the player controller, cast to AlxPlayerControllerBase.
|
||||
static AlxPlayerControllerBase *FromContext(const UObject *Context);
|
||||
|
||||
UPROPERTY()
|
||||
FHitResult CurrentLookAt;
|
||||
|
||||
UPROPERTY()
|
||||
FlxInputModeRequests InputModeRequests;
|
||||
|
||||
bool MustCallLookAtChanged = false;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user