Compare commits
4 Commits
ae1ad7640d
...
d396f394ab
| Author | SHA1 | Date | |
|---|---|---|---|
| d396f394ab | |||
| f19e8ccb72 | |||
| 6b057d1514 | |||
| 392faff205 |
@@ -19,10 +19,10 @@ class UWing_GraphNode_SearchTypes : public UWingHandler
|
|||||||
GENERATED_BODY()
|
GENERATED_BODY()
|
||||||
|
|
||||||
public:
|
public:
|
||||||
UPROPERTY(EditAnywhere, meta=(Description="Query string, can contain * wildcards"))
|
UPROPERTY(EditAnywhere, meta=(Description="Array of query strings; each may contain * wildcards"))
|
||||||
FString Query;
|
FWingJsonArray Queries;
|
||||||
|
|
||||||
UPROPERTY(EditAnywhere, meta=(Optional, Description="Maximum number of results (default 50)"))
|
UPROPERTY(EditAnywhere, meta=(Optional, Description="Maximum number of results per query (default 50)"))
|
||||||
int32 MaxResults = 50;
|
int32 MaxResults = 50;
|
||||||
|
|
||||||
UPROPERTY(EditAnywhere, meta=(Description="Target graph"))
|
UPROPERTY(EditAnywhere, meta=(Description="Target graph"))
|
||||||
@@ -40,20 +40,38 @@ public:
|
|||||||
UEdGraph* TargetGraph = F.Walk(Graph).Cast<UEdGraph>();
|
UEdGraph* TargetGraph = F.Walk(Graph).Cast<UEdGraph>();
|
||||||
if (!TargetGraph) return;
|
if (!TargetGraph) return;
|
||||||
|
|
||||||
FWingGraphActions GraphActions(TargetGraph);
|
// Validate all entries are strings before running any searches.
|
||||||
TArray<FWingGraphAction*> Results = GraphActions.Search(Query, MaxResults, false);
|
TArray<FString> QueryStrings;
|
||||||
for (const FWingGraphAction* Action : Results)
|
QueryStrings.Reserve(Queries.Array.Num());
|
||||||
|
for (const TSharedPtr<FJsonValue>& QueryVal : Queries.Array)
|
||||||
{
|
{
|
||||||
WingOut::Stdout.Printf(TEXT("%s\n"), *Action->Name);
|
FString QueryStr;
|
||||||
|
if (!QueryVal->TryGetString(QueryStr))
|
||||||
|
{
|
||||||
|
WingOut::Stdout.Print(TEXT("ERROR: Queries must be an array of strings.\n"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
QueryStrings.Add(QueryStr);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Results.Num() == 0)
|
FWingGraphActions GraphActions(TargetGraph);
|
||||||
|
for (const FString& Query : QueryStrings)
|
||||||
{
|
{
|
||||||
WingOut::Stdout.Print(TEXT("No matching node types found.\n"));
|
WingOut::Stdout.Printf(TEXT("\n=== %s ===\n\n"), *Query);
|
||||||
}
|
TArray<FWingGraphAction*> Results = GraphActions.Search(Query, MaxResults, false);
|
||||||
else if (Results.Num() >= MaxResults)
|
for (const FWingGraphAction* Action : Results)
|
||||||
{
|
{
|
||||||
WingOut::Stdout.Printf(TEXT("WARNING: Reached limit of %d results. You may specify MaxResults.\n"), MaxResults);
|
WingOut::Stdout.Printf(TEXT("%s\n"), *Action->Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Results.Num() == 0)
|
||||||
|
{
|
||||||
|
WingOut::Stdout.Print(TEXT("No matching node types found.\n"));
|
||||||
|
}
|
||||||
|
else if (Results.Num() >= MaxResults)
|
||||||
|
{
|
||||||
|
WingOut::Stdout.Printf(TEXT("WARNING: Reached limit of %d results. You may specify MaxResults.\n"), MaxResults);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
40
Plugins/UEWingman/Source/UEWingman/Handlers/Sequence.h
Normal file
40
Plugins/UEWingman/Source/UEWingman/Handlers/Sequence.h
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "WingServer.h"
|
||||||
|
#include "WingBasics.h"
|
||||||
|
#include "Sequence.generated.h"
|
||||||
|
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
UCLASS()
|
||||||
|
class UWing_Sequence : public UWingHandler
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
UPROPERTY(EditAnywhere, meta=(Description=
|
||||||
|
"Array of subcommand JSON objects to execute in order. Each must contain 'command' and its parameters."))
|
||||||
|
FWingJsonArray Subcommands;
|
||||||
|
|
||||||
|
virtual void Register() override
|
||||||
|
{
|
||||||
|
UWingServer::AddHandler(this,
|
||||||
|
TEXT("Execute multiple commands in one request. Each subcommand produces its own content block in the response. "
|
||||||
|
"Nested Sequence commands are not allowed."));
|
||||||
|
}
|
||||||
|
virtual void Handle() override
|
||||||
|
{
|
||||||
|
// The actual code that implements Sequence is hardwired into
|
||||||
|
// WingServer. Because of that, this handler is never actually called
|
||||||
|
// under normal conditions. The handler exists for two reasons: to
|
||||||
|
// provide documentation, and also to catch the case where somebody
|
||||||
|
// nests a sequence inside another sequence (WingServer doesn't catch
|
||||||
|
// that).
|
||||||
|
//
|
||||||
|
WingOut::Stdout.Print(TEXT("ERROR: Sequence inside a Sequence is not allowed.\n"));
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -17,10 +17,10 @@ class UWing_Widget_SearchTypes : public UWingHandler
|
|||||||
GENERATED_BODY()
|
GENERATED_BODY()
|
||||||
|
|
||||||
public:
|
public:
|
||||||
UPROPERTY(EditAnywhere, meta=(Description="Query string, can contain *"))
|
UPROPERTY(EditAnywhere, meta=(Description="Array of query strings; each may contain *"))
|
||||||
FString Query;
|
FWingJsonArray Queries;
|
||||||
|
|
||||||
UPROPERTY(EditAnywhere, meta=(Optional, Description="Maximum number of results (default 50)"))
|
UPROPERTY(EditAnywhere, meta=(Optional, Description="Maximum number of results per query (default 50)"))
|
||||||
int32 MaxResults = 50;
|
int32 MaxResults = 50;
|
||||||
|
|
||||||
virtual void Register() override
|
virtual void Register() override
|
||||||
@@ -31,20 +31,38 @@ public:
|
|||||||
}
|
}
|
||||||
virtual void Handle() override
|
virtual void Handle() override
|
||||||
{
|
{
|
||||||
WingWidgets Widgets;
|
// Validate all entries are strings before running any searches.
|
||||||
TArray<WingWidgets::Type> Results = Widgets.Search(Query, MaxResults, false);
|
TArray<FString> QueryStrings;
|
||||||
for (const WingWidgets::Type& Entry : Results)
|
QueryStrings.Reserve(Queries.Array.Num());
|
||||||
|
for (const TSharedPtr<FJsonValue>& QueryVal : Queries.Array)
|
||||||
{
|
{
|
||||||
WingOut::Stdout.Printf(TEXT("%s\n"), *Entry.MenuName);
|
FString QueryStr;
|
||||||
|
if (!QueryVal->TryGetString(QueryStr))
|
||||||
|
{
|
||||||
|
WingOut::Stdout.Print(TEXT("ERROR: Queries must be an array of strings.\n"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
QueryStrings.Add(QueryStr);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Results.Num() == 0)
|
WingWidgets Widgets;
|
||||||
|
for (const FString& Query : QueryStrings)
|
||||||
{
|
{
|
||||||
WingOut::Stdout.Print(TEXT("No matching widget types found.\n"));
|
WingOut::Stdout.Printf(TEXT("\n=== %s ===\n\n"), *Query);
|
||||||
}
|
TArray<WingWidgets::Type> Results = Widgets.Search(Query, MaxResults, false);
|
||||||
else if (Results.Num() >= MaxResults)
|
for (const WingWidgets::Type& Entry : Results)
|
||||||
{
|
{
|
||||||
WingOut::Stdout.Printf(TEXT("WARNING: Reached limit of %d results. You may specify MaxResults.\n"), MaxResults);
|
WingOut::Stdout.Printf(TEXT("%s\n"), *Entry.MenuName);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Results.Num() == 0)
|
||||||
|
{
|
||||||
|
WingOut::Stdout.Print(TEXT("No matching widget types found.\n"));
|
||||||
|
}
|
||||||
|
else if (Results.Num() >= MaxResults)
|
||||||
|
{
|
||||||
|
WingOut::Stdout.Printf(TEXT("WARNING: Reached limit of %d results. You may specify MaxResults.\n"), MaxResults);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -170,6 +170,70 @@ TStatId UWingServer::GetStatId() const
|
|||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
FString UWingServer::HandleRequest(const FString& Line)
|
FString UWingServer::HandleRequest(const FString& Line)
|
||||||
|
{
|
||||||
|
// Parse the request as JSON before doing anything else.
|
||||||
|
TSharedPtr<FJsonValue> Value;
|
||||||
|
TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(Line);
|
||||||
|
if (!FJsonSerializer::Deserialize(Reader, Value))
|
||||||
|
return PackageResponses({TEXT("Invalid Json")});
|
||||||
|
|
||||||
|
const TSharedPtr<FJsonObject>* RequestPtr = nullptr;
|
||||||
|
if (!Value->TryGetObject(RequestPtr))
|
||||||
|
return PackageResponses({TEXT("Json must be an object")});
|
||||||
|
TSharedPtr<FJsonObject> Request = *RequestPtr;
|
||||||
|
|
||||||
|
FString Command;
|
||||||
|
Request->TryGetStringField(TEXT("command"), Command);
|
||||||
|
if (Command == TEXT("Sequence"))
|
||||||
|
{
|
||||||
|
const TArray<TSharedPtr<FJsonValue>>* Subcommands = nullptr;
|
||||||
|
if (!Request->TryGetArrayField(TEXT("subcommands"), Subcommands))
|
||||||
|
return PackageResponses({TEXT("Sequence requires a 'subcommands' array.")});
|
||||||
|
|
||||||
|
TArray<FString> Responses;
|
||||||
|
Responses.Reserve(Subcommands->Num());
|
||||||
|
for (const TSharedPtr<FJsonValue>& Sub : *Subcommands)
|
||||||
|
{
|
||||||
|
const TSharedPtr<FJsonObject>* SubObjPtr = nullptr;
|
||||||
|
if (!Sub->TryGetObject(SubObjPtr))
|
||||||
|
Responses.Add(TEXT("Subcommand must be a JSON object."));
|
||||||
|
else
|
||||||
|
Responses.Add(HandleJsonRequest(*SubObjPtr));
|
||||||
|
}
|
||||||
|
return PackageResponses(Responses);
|
||||||
|
}
|
||||||
|
|
||||||
|
return PackageResponses({HandleJsonRequest(Request)});
|
||||||
|
}
|
||||||
|
|
||||||
|
FString UWingServer::PackageResponses(const TArray<FString>& Responses)
|
||||||
|
{
|
||||||
|
TArray<TSharedPtr<FJsonValue>> Blocks;
|
||||||
|
Blocks.Reserve(Responses.Num());
|
||||||
|
for (const FString& Response : Responses)
|
||||||
|
{
|
||||||
|
// Unreal's JSON writer terminates string serialization at the first
|
||||||
|
// embedded null byte rather than escaping it, which would silently
|
||||||
|
// truncate output. Sanitize null bytes to spaces.
|
||||||
|
FString Sanitized = Response;
|
||||||
|
for (int32 i = 0; i < Sanitized.Len(); ++i)
|
||||||
|
{
|
||||||
|
if (Sanitized[i] == TEXT('\0')) Sanitized[i] = TEXT(' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
TSharedPtr<FJsonObject> Block = MakeShared<FJsonObject>();
|
||||||
|
Block->SetStringField(TEXT("type"), TEXT("text"));
|
||||||
|
Block->SetStringField(TEXT("text"), Sanitized);
|
||||||
|
Blocks.Add(MakeShared<FJsonValueObject>(Block));
|
||||||
|
}
|
||||||
|
|
||||||
|
FString OutJson;
|
||||||
|
TSharedRef<TJsonWriter<>> Writer = TJsonWriterFactory<>::Create(&OutJson);
|
||||||
|
FJsonSerializer::Serialize(Blocks, Writer);
|
||||||
|
return OutJson;
|
||||||
|
}
|
||||||
|
|
||||||
|
FString UWingServer::HandleJsonRequest(TSharedPtr<FJsonObject> Request)
|
||||||
{
|
{
|
||||||
LogCapture.CapturedErrors.Empty();
|
LogCapture.CapturedErrors.Empty();
|
||||||
LogCapture.bEnabled = true;
|
LogCapture.bEnabled = true;
|
||||||
@@ -178,7 +242,7 @@ FString UWingServer::HandleRequest(const FString& Line)
|
|||||||
bSuggestHandlerHelp = false;
|
bSuggestHandlerHelp = false;
|
||||||
LastHandler = nullptr;
|
LastHandler = nullptr;
|
||||||
|
|
||||||
TryCallHandler(Line);
|
TryCallHandler(Request);
|
||||||
|
|
||||||
Notifier.SendNotifications();
|
Notifier.SendNotifications();
|
||||||
LogCapture.bEnabled = false;
|
LogCapture.bEnabled = false;
|
||||||
@@ -202,25 +266,11 @@ FString UWingServer::HandleRequest(const FString& Line)
|
|||||||
}
|
}
|
||||||
FString Result = WingOut::StdoutBuffer.ToString();
|
FString Result = WingOut::StdoutBuffer.ToString();
|
||||||
WingOut::StdoutBuffer.Reset();
|
WingOut::StdoutBuffer.Reset();
|
||||||
for (int32 i = 0; i < Result.Len(); ++i)
|
|
||||||
{
|
|
||||||
if (Result[i] == TEXT('\0')) Result[i] = TEXT(' ');
|
|
||||||
}
|
|
||||||
return Result;
|
return Result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void UWingServer::TryCallHandler(const FString &Line)
|
void UWingServer::TryCallHandler(TSharedPtr<FJsonObject> Request)
|
||||||
{
|
{
|
||||||
// Turn the request string into a JSON tree.
|
|
||||||
TSharedPtr<FJsonObject> Request;
|
|
||||||
TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(Line);
|
|
||||||
FJsonSerializer::Deserialize(Reader, Request);
|
|
||||||
if (!Request.IsValid())
|
|
||||||
{
|
|
||||||
WingOut::Stdout.Printf(TEXT("Request is not valid JSON"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract the command from the request.
|
// Extract the command from the request.
|
||||||
FString Command;
|
FString Command;
|
||||||
if (!Request->TryGetStringField(TEXT("command"), Command))
|
if (!Request->TryGetStringField(TEXT("command"), Command))
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
#include "WingServer.generated.h"
|
#include "WingServer.generated.h"
|
||||||
|
|
||||||
class FSocket;
|
class FSocket;
|
||||||
|
class FJsonObject;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* UWingServer — editor subsystem that listens on a TCP socket and dispatches
|
* UWingServer — editor subsystem that listens on a TCP socket and dispatches
|
||||||
@@ -61,6 +62,9 @@ public:
|
|||||||
static void AddHandler(UObject* Obj, const FString& Name, UObject* Config, EWingHandlerKind Kind, UClass* FactoryClass, const FString& Documentation);
|
static void AddHandler(UObject* Obj, const FString& Name, UObject* Config, EWingHandlerKind Kind, UClass* FactoryClass, const FString& Documentation);
|
||||||
static const TArray<FWingHandlerConfig>& AllHandlers() { return GWingServer->WingHandlerRegistry; }
|
static const TArray<FWingHandlerConfig>& AllHandlers() { return GWingServer->WingHandlerRegistry; }
|
||||||
|
|
||||||
|
/** Package a list of response texts into a single serialized JSON content-block array. */
|
||||||
|
static FString PackageResponses(const TArray<FString>& Responses);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static UWingServer* GWingServer;
|
static UWingServer* GWingServer;
|
||||||
|
|
||||||
@@ -78,7 +82,8 @@ private:
|
|||||||
|
|
||||||
// Handle a complete JSON line and return the response JSON
|
// Handle a complete JSON line and return the response JSON
|
||||||
FString HandleRequest(const FString& Line);
|
FString HandleRequest(const FString& Line);
|
||||||
void TryCallHandler(const FString &Line);
|
FString HandleJsonRequest(TSharedPtr<FJsonObject> Request);
|
||||||
|
void TryCallHandler(TSharedPtr<FJsonObject> Request);
|
||||||
|
|
||||||
// ----- TCP server -----
|
// ----- TCP server -----
|
||||||
FSocket* ListenSocket = nullptr;
|
FSocket* ListenSocket = nullptr;
|
||||||
|
|||||||
@@ -128,11 +128,14 @@ def handle_message(msg):
|
|||||||
arguments = params.get("arguments", {})
|
arguments = params.get("arguments", {})
|
||||||
result = forward_to_editor(arguments)
|
result = forward_to_editor(arguments)
|
||||||
if isinstance(result, dict) and "error" in result:
|
if isinstance(result, dict) and "error" in result:
|
||||||
text = result["error"]
|
content = [{"type": "text", "text": result["error"]}]
|
||||||
else:
|
else:
|
||||||
text = result
|
try:
|
||||||
|
content = json.loads(result)
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
content = [{"type": "text", "text": "Malformed response from editor."}]
|
||||||
return make_jsonrpc(msg_id, {
|
return make_jsonrpc(msg_id, {
|
||||||
"content": [{"type": "text", "text": text}],
|
"content": content,
|
||||||
})
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -57,9 +57,21 @@ def main():
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
parsed = json.loads(result)
|
parsed = json.loads(result)
|
||||||
print(json.dumps(parsed, indent=2))
|
|
||||||
except json.JSONDecodeError:
|
except json.JSONDecodeError:
|
||||||
print(result)
|
print("Error: response is not valid JSON.")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
if not isinstance(parsed, list):
|
||||||
|
print("Error: response is not a list of content blocks.")
|
||||||
|
sys.exit(1)
|
||||||
|
for block in parsed:
|
||||||
|
if not (isinstance(block, dict)
|
||||||
|
and block.get("type") == "text"
|
||||||
|
and isinstance(block.get("text"), str)):
|
||||||
|
print("Error: response contains a non-text block.")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
print("\n---\n".join(block["text"] for block in parsed))
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|||||||
@@ -172,7 +172,6 @@ void ALuprexGameModeBase::OnWorldPostActorTick(UWorld* InWorld, ELevelTick InLev
|
|||||||
if (PC != nullptr)
|
if (PC != nullptr)
|
||||||
{
|
{
|
||||||
PC->UpdateLookAt();
|
PC->UpdateLookAt();
|
||||||
PC->UpdateEventDispatch();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ void UlxUserWidget::BackupInputComponent()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void UlxUserWidget::DisableEventBinding(const UInputAction* InputAction)
|
void UlxUserWidget::DisableInputAction(const UInputAction* InputAction)
|
||||||
{
|
{
|
||||||
UEnhancedInputComponent* EIC = Cast<UEnhancedInputComponent>(InputComponent);
|
UEnhancedInputComponent* EIC = Cast<UEnhancedInputComponent>(InputComponent);
|
||||||
if (!EIC) return;
|
if (!EIC) return;
|
||||||
@@ -40,9 +40,9 @@ void UlxUserWidget::DisableEventBinding(const UInputAction* InputAction)
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void UlxUserWidget::RestoreInputBinding(const UInputAction* InputAction)
|
void UlxUserWidget::RestoreInputAction(const UInputAction* InputAction)
|
||||||
{
|
{
|
||||||
DisableEventBinding(InputAction);
|
DisableInputAction(InputAction);
|
||||||
|
|
||||||
UEnhancedInputComponent* EIC = Cast<UEnhancedInputComponent>(InputComponent);
|
UEnhancedInputComponent* EIC = Cast<UEnhancedInputComponent>(InputComponent);
|
||||||
if (!EIC) return;
|
if (!EIC) return;
|
||||||
@@ -59,7 +59,7 @@ void UlxUserWidget::RestoreInputBinding(const UInputAction* InputAction)
|
|||||||
|
|
||||||
void UlxUserWidget::RedirectInputAction(const UInputAction* From, const UInputAction* To)
|
void UlxUserWidget::RedirectInputAction(const UInputAction* From, const UInputAction* To)
|
||||||
{
|
{
|
||||||
DisableEventBinding(From);
|
DisableInputAction(From);
|
||||||
|
|
||||||
UEnhancedInputComponent* EIC = Cast<UEnhancedInputComponent>(InputComponent);
|
UEnhancedInputComponent* EIC = Cast<UEnhancedInputComponent>(InputComponent);
|
||||||
if (!EIC) return;
|
if (!EIC) return;
|
||||||
|
|||||||
@@ -22,20 +22,18 @@ public:
|
|||||||
// from the component and reinstated without losing their delegates.
|
// from the component and reinstated without losing their delegates.
|
||||||
void BackupInputComponent();
|
void BackupInputComponent();
|
||||||
|
|
||||||
// Remove every live event binding whose action is InputAction.
|
// Removes all handlers for 'InputAction'. That includes temporarily
|
||||||
// No-op if there are none, or if InputComponent isn't enhanced.
|
// deactivating event graph nodes that handle 'InputAction'.
|
||||||
UFUNCTION(BlueprintCallable, Category="Luprex|Widget Enhanced Input")
|
UFUNCTION(BlueprintCallable, Category="Luprex|Widget Enhanced Input")
|
||||||
void DisableEventBinding(const UInputAction* InputAction);
|
void DisableInputAction(const UInputAction* InputAction);
|
||||||
|
|
||||||
// Replace any live bindings for InputAction with fresh clones of every
|
// Reactivates any graph nodes that handle 'InputAction', and
|
||||||
// saved binding for that action. Leaves the backup array intact so this
|
// removes any other handlers for 'InputAction'.
|
||||||
// can be called repeatedly.
|
|
||||||
UFUNCTION(BlueprintCallable, Category="Luprex|Widget Enhanced Input")
|
UFUNCTION(BlueprintCallable, Category="Luprex|Widget Enhanced Input")
|
||||||
void RestoreInputBinding(const UInputAction* InputAction);
|
void RestoreInputAction(const UInputAction* InputAction);
|
||||||
|
|
||||||
// Install live bindings on From that, when fired, dispatch through a
|
// Any event graph nodes that handle 'to' are made to also
|
||||||
// clone of each saved binding for To. Clears any pre-existing live
|
// handle 'From' events. Any other handlers of 'From' are removed.
|
||||||
// bindings on From first. Backup array is untouched.
|
|
||||||
UFUNCTION(BlueprintCallable, Category="Luprex|Widget Enhanced Input")
|
UFUNCTION(BlueprintCallable, Category="Luprex|Widget Enhanced Input")
|
||||||
void RedirectInputAction(const UInputAction* From, const UInputAction* To);
|
void RedirectInputAction(const UInputAction* From, const UInputAction* To);
|
||||||
|
|
||||||
|
|||||||
@@ -4,24 +4,6 @@
|
|||||||
#include "TangibleManager.h"
|
#include "TangibleManager.h"
|
||||||
#include "Kismet/GameplayStatics.h"
|
#include "Kismet/GameplayStatics.h"
|
||||||
#include "Engine/GameInstance.h"
|
#include "Engine/GameInstance.h"
|
||||||
#include "Engine/GameViewportClient.h"
|
|
||||||
#include "Framework/Application/SlateApplication.h"
|
|
||||||
#include "Widgets/SViewport.h"
|
|
||||||
#include "Slate/SObjectWidget.h"
|
|
||||||
|
|
||||||
FString AlxPlayerControllerBase::GetUserWidgetName(SWidget *W)
|
|
||||||
{
|
|
||||||
while (W)
|
|
||||||
{
|
|
||||||
if (W->GetType() == FName("SObjectWidget"))
|
|
||||||
{
|
|
||||||
UUserWidget *UW = static_cast<SObjectWidget*>(W)->GetWidgetObject();
|
|
||||||
if (UW) return UW->GetClass()->GetName();
|
|
||||||
}
|
|
||||||
W = W->GetParentWidget().Get();
|
|
||||||
}
|
|
||||||
return TEXT("Unknown Widget");
|
|
||||||
}
|
|
||||||
|
|
||||||
AlxPlayerControllerBase *AlxPlayerControllerBase::FromContext(const UObject *Context)
|
AlxPlayerControllerBase *AlxPlayerControllerBase::FromContext(const UObject *Context)
|
||||||
{
|
{
|
||||||
@@ -71,96 +53,6 @@ FVector2D AlxPlayerControllerBase::GetLookAtPixel(const UObject *Context)
|
|||||||
return ScreenPosition;
|
return ScreenPosition;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AlxPlayerControllerBase::BeginPlay()
|
|
||||||
{
|
|
||||||
Super::BeginPlay();
|
|
||||||
HotkeyInputComponent = NewObject<UInputComponent>(this);
|
|
||||||
HotkeyInputComponent->bBlockInput = false;
|
|
||||||
PushInputComponent(HotkeyInputComponent);
|
|
||||||
}
|
|
||||||
|
|
||||||
void AlxPlayerControllerBase::UpdateEventDispatch()
|
|
||||||
{
|
|
||||||
EventRequests.GarbageCollect();
|
|
||||||
|
|
||||||
// If we're in GameOnly mode, check that focus is still on the viewport.
|
|
||||||
if (CurrentInputMode == InputMode::GameOnly)
|
|
||||||
{
|
|
||||||
UGameViewportClient *GVC = GetWorld() ? GetWorld()->GetGameViewport() : nullptr;
|
|
||||||
if (GVC)
|
|
||||||
{
|
|
||||||
TSharedPtr<SViewport> ViewportWidget = GVC->GetGameViewportWidget();
|
|
||||||
if (ViewportWidget.IsValid())
|
|
||||||
{
|
|
||||||
TSharedPtr<SWidget> Focused = FSlateApplication::Get().GetKeyboardFocusedWidget();
|
|
||||||
if (Focused.Get() != ViewportWidget.Get())
|
|
||||||
{
|
|
||||||
UE_LOG(LogLuprexIntegration, Error, TEXT("In GameOnly mode, keyboard focus must stay on viewport, but was stolen by: %s. Restoring."), *GetUserWidgetName(Focused.Get()));
|
|
||||||
EventRequests.SetDirty();
|
|
||||||
}
|
|
||||||
if (!ViewportWidget->HasMouseCapture())
|
|
||||||
{
|
|
||||||
UE_LOG(LogLuprexIntegration, Error, TEXT("In GameOnly mode, viewport must have mouse capture, but lost it. Restoring."));
|
|
||||||
EventRequests.SetDirty();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!EventRequests.IsDirty()) return;
|
|
||||||
EventRequests.ClearDirty();
|
|
||||||
|
|
||||||
CurrentInputMode = EventRequests.GetRequestedMode();
|
|
||||||
const TArray<FlxEventRequest> &Requests = EventRequests.GetRequests();
|
|
||||||
|
|
||||||
if (CurrentInputMode == InputMode::UIOnly)
|
|
||||||
{
|
|
||||||
SetInputMode(FInputModeUIOnly().SetWidgetToFocus(Requests[0].Widget->GetCachedWidget()));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
SetInputMode(FInputModeGameOnly());
|
|
||||||
|
|
||||||
HotkeyInputComponent->KeyBindings.Empty();
|
|
||||||
TSet<FKey> BoundKeys;
|
|
||||||
for (const FlxEventRequest &Req : Requests)
|
|
||||||
{
|
|
||||||
for (const FKey &Key : Req.Hotkeys)
|
|
||||||
{
|
|
||||||
if (!BoundKeys.Contains(Key))
|
|
||||||
{
|
|
||||||
BoundKeys.Add(Key);
|
|
||||||
HotkeyInputComponent->BindKey(Key, IE_Pressed, this, &AlxPlayerControllerBase::ForwardKeyEvent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void AlxPlayerControllerBase::ForwardKeyEvent(FKey Key)
|
|
||||||
{
|
|
||||||
// TODO: implement
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void AlxPlayerControllerBase::RequestEvents(const FlxEventRequest &Request)
|
|
||||||
{
|
|
||||||
if (!FlxEventRequests::SanityCheck(Request)) return;
|
|
||||||
AlxPlayerControllerBase *PC = FromContext(Request.Widget);
|
|
||||||
PC->EventRequests.Request(Request);
|
|
||||||
}
|
|
||||||
|
|
||||||
void AlxPlayerControllerBase::UnRequestEvents(UUserWidget *Widget)
|
|
||||||
{
|
|
||||||
if (Widget == nullptr)
|
|
||||||
{
|
|
||||||
UE_LOG(LogLuprexIntegration, Error, TEXT("UnRequestEvents called with null widget."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
AlxPlayerControllerBase *PC = FromContext(Widget);
|
|
||||||
PC->EventRequests.Remove(Widget);
|
|
||||||
}
|
|
||||||
|
|
||||||
void AlxPlayerControllerBase::UpdateLookAt()
|
void AlxPlayerControllerBase::UpdateLookAt()
|
||||||
{
|
{
|
||||||
UlxTangibleManager *TM = GetGameInstance()->GetSubsystem<UlxTangibleManager>();
|
UlxTangibleManager *TM = GetGameInstance()->GetSubsystem<UlxTangibleManager>();
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
#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 "PlayerControllerBase.generated.h"
|
#include "PlayerControllerBase.generated.h"
|
||||||
|
|
||||||
UCLASS(BlueprintType, Blueprintable)
|
UCLASS(BlueprintType, Blueprintable)
|
||||||
@@ -12,8 +11,6 @@ class INTEGRATION_API AlxPlayerControllerBase : public APlayerController
|
|||||||
GENERATED_BODY()
|
GENERATED_BODY()
|
||||||
|
|
||||||
public:
|
public:
|
||||||
using InputMode = FlxEventRequests::InputMode;
|
|
||||||
|
|
||||||
UFUNCTION(BlueprintCallable, meta = (WorldContext = "Context"), Category = "Luprex|Look-At Detection")
|
UFUNCTION(BlueprintCallable, meta = (WorldContext = "Context"), Category = "Luprex|Look-At Detection")
|
||||||
static void SetLookAt(const UObject *Context, const FHitResult &HitResult);
|
static void SetLookAt(const UObject *Context, const FHitResult &HitResult);
|
||||||
|
|
||||||
@@ -29,12 +26,6 @@ public:
|
|||||||
UFUNCTION(BlueprintCallable, meta = (WorldContext = "Context"), Category = "Luprex|Look-At Detection")
|
UFUNCTION(BlueprintCallable, meta = (WorldContext = "Context"), Category = "Luprex|Look-At Detection")
|
||||||
static void SetLookAtChanged(const UObject *Context);
|
static void SetLookAtChanged(const UObject *Context);
|
||||||
|
|
||||||
UFUNCTION(BlueprintCallable, Category = "Luprex|Input Events")
|
|
||||||
static void RequestEvents(const FlxEventRequest &Request);
|
|
||||||
|
|
||||||
UFUNCTION(BlueprintCallable, Category = "Luprex|Input Events")
|
|
||||||
static void UnRequestEvents(UUserWidget *Widget);
|
|
||||||
|
|
||||||
// Blueprint events
|
// Blueprint events
|
||||||
UFUNCTION(BlueprintImplementableEvent, Category = "Luprex|Look-At Detection")
|
UFUNCTION(BlueprintImplementableEvent, Category = "Luprex|Look-At Detection")
|
||||||
void CalculateLookAt();
|
void CalculateLookAt();
|
||||||
@@ -42,35 +33,14 @@ public:
|
|||||||
UFUNCTION(BlueprintImplementableEvent, Category = "Luprex|Look-At Detection")
|
UFUNCTION(BlueprintImplementableEvent, Category = "Luprex|Look-At Detection")
|
||||||
void LookAtChanged();
|
void LookAtChanged();
|
||||||
|
|
||||||
virtual void BeginPlay() override;
|
|
||||||
|
|
||||||
// Called by GameMode each tick.
|
// Called by GameMode each tick.
|
||||||
void UpdateLookAt();
|
void UpdateLookAt();
|
||||||
|
|
||||||
// Rebuild input component and switch input mode.
|
|
||||||
void UpdateEventDispatch();
|
|
||||||
|
|
||||||
// Handler for GameOnly mode hotkey presses.
|
|
||||||
void ForwardKeyEvent(FKey Key);
|
|
||||||
|
|
||||||
// Walk up from a Slate widget to find the nearest UMG widget class name.
|
|
||||||
static FString GetUserWidgetName(SWidget *W);
|
|
||||||
|
|
||||||
// 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);
|
||||||
|
|
||||||
UPROPERTY()
|
UPROPERTY()
|
||||||
FHitResult CurrentLookAt;
|
FHitResult CurrentLookAt;
|
||||||
|
|
||||||
UPROPERTY()
|
|
||||||
FlxEventRequests EventRequests;
|
|
||||||
|
|
||||||
// Input component for GameOnly mode: catches hotkeys only.
|
|
||||||
UPROPERTY()
|
|
||||||
UInputComponent *HotkeyInputComponent = nullptr;
|
|
||||||
|
|
||||||
// Current input mode.
|
|
||||||
InputMode CurrentInputMode = InputMode::GameOnly;
|
|
||||||
|
|
||||||
bool MustCallLookAtChanged = false;
|
bool MustCallLookAtChanged = false;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user