Working on new root canvas stuff
This commit is contained in:
@@ -1,117 +0,0 @@
|
||||
////////////////////////////////////////////////////////////
|
||||
//
|
||||
// InputModeRequest.cpp
|
||||
//
|
||||
////////////////////////////////////////////////////////////
|
||||
|
||||
#include "InputModeRequest.h"
|
||||
#include "Common.h"
|
||||
|
||||
bool FlxInputModeRequest::operator<(const FlxInputModeRequest &Other) const
|
||||
{
|
||||
// The highest priority request goes to the front of the array.
|
||||
// Therefore, in this context, 'less than' means 'higher priority'.
|
||||
// It's a little confusing.
|
||||
if (ShowPointer != Other.ShowPointer) return ShowPointer > Other.ShowPointer;
|
||||
return SequenceNumber > Other.SequenceNumber;
|
||||
}
|
||||
|
||||
bool FlxInputModeRequest::operator==(const FlxInputModeRequest &Other) const
|
||||
{
|
||||
return (Widget == Other.Widget) &&
|
||||
(Focus == Other.Focus) &&
|
||||
(ShowPointer == Other.ShowPointer) &&
|
||||
(BlockInput == Other.BlockInput) &&
|
||||
(EnableInputComponent == Other.EnableInputComponent);
|
||||
}
|
||||
|
||||
bool FlxInputModeRequests::SanityCheck(const FlxInputModeRequest &Request)
|
||||
{
|
||||
if (Request.Widget == nullptr)
|
||||
{
|
||||
UE_LOG(LogLuprexIntegration, Error, TEXT("RequestEvents called with null widget."));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
int32 FlxInputModeRequests::FindWidget(UUserWidget *Widget)
|
||||
{
|
||||
for (const FlxInputModeRequest &Req : Requests)
|
||||
{
|
||||
if (Req.Widget == Widget) return &Req - Requests.GetData();
|
||||
}
|
||||
return Requests.Num();
|
||||
}
|
||||
|
||||
void FlxInputModeRequests::BubbleItem(int32 Index)
|
||||
{
|
||||
while ((Index > 0) && (Requests[Index] < Requests[Index - 1]))
|
||||
{
|
||||
Swap(Requests[Index], Requests[Index - 1]);
|
||||
--Index;
|
||||
}
|
||||
while ((Index < Requests.Num() - 1) && (Requests[Index + 1] < Requests[Index]))
|
||||
{
|
||||
Swap(Requests[Index], Requests[Index + 1]);
|
||||
++Index;
|
||||
}
|
||||
}
|
||||
|
||||
void FlxInputModeRequests::Request(const FlxInputModeRequest &NewRequest, bool UpdateSequence)
|
||||
{
|
||||
int32 Index = FindWidget(NewRequest.Widget);
|
||||
|
||||
if (Index == Requests.Num())
|
||||
{
|
||||
Requests.Emplace(NewRequest);
|
||||
Requests[Index].SequenceNumber = ++NextSequenceNumber;
|
||||
}
|
||||
else
|
||||
{
|
||||
int32 SequenceNumber = Requests[Index].SequenceNumber;
|
||||
if (UpdateSequence) SequenceNumber = ++NextSequenceNumber;
|
||||
Requests[Index] = NewRequest;
|
||||
Requests[Index].SequenceNumber = SequenceNumber;
|
||||
}
|
||||
|
||||
BubbleItem(Index);
|
||||
}
|
||||
|
||||
void FlxInputModeRequests::SetEnableInputComponent(UUserWidget *Widget, bool EnableInputComponent)
|
||||
{
|
||||
int32 Index = FindWidget(Widget);
|
||||
|
||||
if (Index == Requests.Num())
|
||||
{
|
||||
FlxInputModeRequest NewReq;
|
||||
NewReq.Widget = Widget;
|
||||
NewReq.EnableInputComponent = EnableInputComponent;
|
||||
NewReq.SequenceNumber = ++NextSequenceNumber;
|
||||
Requests.Emplace(NewReq);
|
||||
}
|
||||
else
|
||||
{
|
||||
Requests[Index].EnableInputComponent = EnableInputComponent;
|
||||
}
|
||||
|
||||
BubbleItem(Index);
|
||||
}
|
||||
|
||||
void FlxInputModeRequests::Remove(UUserWidget *Widget)
|
||||
{
|
||||
Requests.RemoveAll([Widget](const FlxInputModeRequest &Entry)
|
||||
{
|
||||
return Entry.Widget == Widget;
|
||||
});
|
||||
}
|
||||
|
||||
void FlxInputModeRequests::GarbageCollect()
|
||||
{
|
||||
Requests.RemoveAll([](const FlxInputModeRequest &Entry)
|
||||
{
|
||||
return !IsValid(Entry.Widget);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,98 +0,0 @@
|
||||
////////////////////////////////////////////////////////////
|
||||
//
|
||||
// InputModeRequest.h
|
||||
//
|
||||
// Custom input event dispatching system. Uses Unreal's
|
||||
// built-in input modes (GameOnly / UIOnly) with an
|
||||
// enhanced input component for GameOnly mode hotkeys.
|
||||
//
|
||||
////////////////////////////////////////////////////////////
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Blueprint/UserWidget.h"
|
||||
#include "InputModeRequest.generated.h"
|
||||
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
//
|
||||
// FlxInputModeRequest
|
||||
//
|
||||
// Using this struct, a Widget can express a need for a
|
||||
// particular input mode. These requests go to the player
|
||||
// controller, which arbitrates.
|
||||
//
|
||||
////////////////////////////////////////////////////////////
|
||||
|
||||
USTRUCT(BlueprintType)
|
||||
struct FlxInputModeRequest
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
FlxInputModeRequest() = default;
|
||||
|
||||
bool operator == (const FlxInputModeRequest &Other) const;
|
||||
bool operator < (const FlxInputModeRequest &Other) const;
|
||||
|
||||
UPROPERTY(BlueprintReadWrite)
|
||||
UUserWidget* Widget = nullptr;
|
||||
|
||||
UPROPERTY(BlueprintReadWrite)
|
||||
UWidget* Focus = nullptr;
|
||||
|
||||
UPROPERTY(BlueprintReadWrite)
|
||||
bool ShowPointer = false;
|
||||
|
||||
UPROPERTY(BlueprintReadWrite)
|
||||
bool BlockInput = false;
|
||||
|
||||
UPROPERTY(BlueprintReadWrite)
|
||||
bool EnableInputComponent = true;
|
||||
|
||||
UPROPERTY()
|
||||
int32 SequenceNumber = 0;
|
||||
|
||||
};
|
||||
|
||||
USTRUCT()
|
||||
struct FlxInputModeRequests
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
private:
|
||||
UPROPERTY()
|
||||
// Sorted by highest priority first, then most recent first.
|
||||
TArray<FlxInputModeRequest> Requests;
|
||||
|
||||
UPROPERTY()
|
||||
int32 NextSequenceNumber = 0;
|
||||
|
||||
public:
|
||||
// Get the requests array.
|
||||
const TArray<FlxInputModeRequest> &GetRequests() const { return Requests; }
|
||||
|
||||
// Sanity check a request to see if it is reasonable.
|
||||
static bool SanityCheck(const FlxInputModeRequest &Request);
|
||||
|
||||
// Apply a request. Replaces any previous request by the same widget.
|
||||
void Request(const FlxInputModeRequest &NewRequest, bool UpdateSequence = true);
|
||||
|
||||
// Find the specified widget, and modify the 'EnableInputComponent'
|
||||
// flag. Adds the widget if it's not already present.
|
||||
void SetEnableInputComponent(UUserWidget *Widget, bool EnableInputComponent);
|
||||
|
||||
// Remove all requests by the specified widget.
|
||||
void Remove(UUserWidget *Widget);
|
||||
|
||||
// Remove any requests by dead widgets or widgets with no parents.
|
||||
void GarbageCollect();
|
||||
|
||||
private:
|
||||
// Find specified widget. If not present, returns Requests.Num()
|
||||
int32 FindWidget(UUserWidget *Widget);
|
||||
|
||||
// Move item at Index to its proper place in the array by priority.
|
||||
void BubbleItem(int32 Index);
|
||||
};
|
||||
@@ -1,8 +1,10 @@
|
||||
#include "PlayerControllerBase.h"
|
||||
#include "Common.h"
|
||||
#include "RootCanvas.h"
|
||||
#include "Tangible.h"
|
||||
#include "TangibleManager.h"
|
||||
#include "Blueprint/UserWidget.h"
|
||||
#include "Blueprint/WidgetTree.h"
|
||||
#include "Components/InputComponent.h"
|
||||
#include "Engine/GameInstance.h"
|
||||
#include "Engine/GameViewportClient.h"
|
||||
@@ -63,7 +65,17 @@ FVector2D AlxPlayerControllerBase::GetLookAtPixel(const UObject *Context)
|
||||
|
||||
void AlxPlayerControllerBase::BeginPlay()
|
||||
{
|
||||
// Build the root UMG stack BEFORE Super::BeginPlay. Super calls
|
||||
// ReceiveBeginPlay, which fires the Blueprint Event BeginPlay;
|
||||
// BP code there may immediately try to add widgets to RootCanvas,
|
||||
// so the canvas must already exist.
|
||||
RootWidget = CreateWidget<UlxRootWidget>(this);
|
||||
RootCanvas = RootWidget->WidgetTree->ConstructWidget<UlxRootCanvasPanel>();
|
||||
RootWidget->WidgetTree->RootWidget = RootCanvas;
|
||||
RootWidget->AddToViewport(0);
|
||||
|
||||
Super::BeginPlay();
|
||||
|
||||
if (FSlateApplication::IsInitialized())
|
||||
{
|
||||
FocusChangingHandle = FSlateApplication::Get().OnFocusChanging().AddUObject(
|
||||
@@ -78,6 +90,14 @@ void AlxPlayerControllerBase::EndPlay(const EEndPlayReason::Type EndPlayReason)
|
||||
FSlateApplication::Get().OnFocusChanging().Remove(FocusChangingHandle);
|
||||
FocusChangingHandle.Reset();
|
||||
}
|
||||
|
||||
if (IsValid(RootWidget))
|
||||
{
|
||||
RootWidget->RemoveFromParent();
|
||||
}
|
||||
RootWidget = nullptr;
|
||||
RootCanvas = nullptr;
|
||||
|
||||
Super::EndPlay(EndPlayReason);
|
||||
}
|
||||
|
||||
@@ -111,11 +131,18 @@ UInputComponent* AlxPlayerControllerBase::GetWidgetInputComponent(UUserWidget *W
|
||||
return Cast<UInputComponent>(Value);
|
||||
}
|
||||
|
||||
void AlxPlayerControllerBase::WidgetRequestInputMode(UUserWidget *Widget, bool ShowPointer, bool BlockInput, UWidget *Focus, bool EnableInputComponent)
|
||||
void AlxPlayerControllerBase::RestoreFocusToFrontWidget(const UObject *Context)
|
||||
{
|
||||
// This will trigger UpdateInputMode to shift focus back to
|
||||
// the front window, if the front window wants focus.
|
||||
FromContext(Context)->LastWidgetGrantedFocus = nullptr;
|
||||
}
|
||||
|
||||
void AlxPlayerControllerBase::AddWidgetToRoot(UUserWidget *Widget)
|
||||
{
|
||||
if (!IsValid(Widget))
|
||||
{
|
||||
UE_LOG(LogLuprexIntegration, Error, TEXT("WidgetRequestInputMode called with an invalid widget."));
|
||||
UE_LOG(LogLuprexIntegration, Error, TEXT("AddWidgetToRoot called with an invalid widget."));
|
||||
return;
|
||||
}
|
||||
APlayerController *OwningPC = Widget->GetOwningPlayer();
|
||||
@@ -123,53 +150,21 @@ void AlxPlayerControllerBase::WidgetRequestInputMode(UUserWidget *Widget, bool S
|
||||
if (PC == nullptr)
|
||||
{
|
||||
UE_LOG(LogLuprexIntegration, Error,
|
||||
TEXT("WidgetRequestInputMode: widget '%s' owning player is not an AlxPlayerControllerBase (got %s)."),
|
||||
TEXT("AddWidgetToRoot: widget '%s' owning player is not an AlxPlayerControllerBase (got %s)."),
|
||||
*Widget->GetName(), *GetNameSafe(OwningPC));
|
||||
return;
|
||||
}
|
||||
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)
|
||||
{
|
||||
if (InInputComponent)
|
||||
if (PC->RootCanvas == nullptr)
|
||||
{
|
||||
// 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.SetEnableInputComponent(Widget, true);
|
||||
return;
|
||||
}
|
||||
CurrentInputStack.RemoveSingle(InInputComponent);
|
||||
CurrentInputStack.Add(InInputComponent);
|
||||
UE_LOG(LogLuprexIntegration, Error,
|
||||
TEXT("AddWidgetToRoot: root canvas is not initialized, this is probably an initialization order issue"));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
bool AlxPlayerControllerBase::PopInputComponent(UInputComponent* InInputComponent)
|
||||
{
|
||||
if (InInputComponent)
|
||||
{
|
||||
if (UUserWidget *Widget = Cast<UUserWidget>(InInputComponent->GetOuter()))
|
||||
{
|
||||
InputModeRequests.SetEnableInputComponent(Widget, false);
|
||||
InInputComponent->ClearBindingValues();
|
||||
return true;
|
||||
}
|
||||
if (CurrentInputStack.RemoveSingle(InInputComponent) > 0)
|
||||
{
|
||||
InInputComponent->ClearBindingValues();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
if (Widget->GetParent() == PC->RootCanvas) return;
|
||||
|
||||
UlxRootCanvasSlot *Slot = PC->RootCanvas->AddChildToRootCanvas(Widget);
|
||||
Slot->SetZOrder(0);
|
||||
}
|
||||
|
||||
void AlxPlayerControllerBase::BuildInputStack(TArray<UInputComponent*>& InputStack)
|
||||
@@ -216,51 +211,51 @@ void AlxPlayerControllerBase::BuildInputStack(TArray<UInputComponent*>& InputSta
|
||||
InputStack.Push(InputComponent);
|
||||
}
|
||||
|
||||
// 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())
|
||||
// Get the widget slots.
|
||||
TArray<UlxRootCanvasSlot*> WidgetSlots = RootCanvas->GetSortedUserWidgets();
|
||||
|
||||
// Generate a set of input components that are being managed by the WidgetSlots.
|
||||
TSet<UInputComponent*> WidgetManagedComponents;
|
||||
for (UlxRootCanvasSlot *Slot : WidgetSlots)
|
||||
{
|
||||
TArray<UInputComponent*, TInlineAllocator<20>> Pushed;
|
||||
for (int32 Idx = 0; Idx < CurrentInputStack.Num(); ++Idx)
|
||||
{
|
||||
UInputComponent* IC = CurrentInputStack[Idx].Get();
|
||||
if (IsValid(IC)) Pushed.Add(IC);
|
||||
else CurrentInputStack.RemoveAt(Idx--);
|
||||
}
|
||||
|
||||
Pushed.StableSort([](const UInputComponent& A, const UInputComponent& B)
|
||||
{
|
||||
if (A.bBlockInput != B.bBlockInput) return !A.bBlockInput;
|
||||
return A.Priority < B.Priority;
|
||||
});
|
||||
|
||||
InputStack.Append(Pushed);
|
||||
UUserWidget *Widget = Cast<UUserWidget>(Slot->GetContent());
|
||||
UInputComponent *IC = GetWidgetInputComponent(Widget);
|
||||
if (IC) WidgetManagedComponents.Add(IC);
|
||||
}
|
||||
|
||||
// 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)
|
||||
// Add components in the CurrentInputStack, *unless* they are being managed
|
||||
// by widgets. If they're being managed by widgets, they get added later.
|
||||
for (int32 Idx=0; Idx<CurrentInputStack.Num(); ++Idx)
|
||||
{
|
||||
const FlxInputModeRequest &Req = Requests[Idx];
|
||||
if (!Req.EnableInputComponent) continue;
|
||||
UUserWidget *Widget = Req.Widget;
|
||||
if (!Widget->IsInViewport()) continue;
|
||||
if (UInputComponent *IC = GetWidgetInputComponent(Widget))
|
||||
UInputComponent* IC = CurrentInputStack[Idx].Get();
|
||||
if (IsValid(IC))
|
||||
{
|
||||
IC->bBlockInput = Req.BlockInput;
|
||||
InputStack.Push(IC);
|
||||
if (!WidgetManagedComponents.Contains(IC)) InputStack.Push(IC);
|
||||
}
|
||||
else
|
||||
{
|
||||
CurrentInputStack.RemoveAt(Idx--);
|
||||
}
|
||||
}
|
||||
|
||||
// Now add the widget-managed input components.
|
||||
for (UlxRootCanvasSlot *Slot : ReverseIterate(WidgetSlots))
|
||||
{
|
||||
if (Slot->EnableEnhancedInput)
|
||||
{
|
||||
UUserWidget *Widget = Cast<UUserWidget>(Slot->GetContent());
|
||||
UInputComponent *IC = GetWidgetInputComponent(Widget);
|
||||
if (IC)
|
||||
{
|
||||
IC->bBlockInput = Slot->BlockInput;
|
||||
InputStack.Push(IC);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AlxPlayerControllerBase::UpdateInputMode()
|
||||
{
|
||||
InputModeRequests.GarbageCollect();
|
||||
|
||||
// Get all the various objects we need to be able to manipulate
|
||||
// the input mode.
|
||||
UGameViewportClient *GameViewportClient = GetWorld()->GetGameViewport();
|
||||
@@ -273,19 +268,24 @@ void AlxPlayerControllerBase::UpdateInputMode()
|
||||
TSharedPtr<FSlateUser> SlateUser = LocalPlayer->GetSlateUser();
|
||||
if (!SlateUser.IsValid()) return;
|
||||
|
||||
// The first active entry in InputModeRequests dictates the
|
||||
// pointer / capture / focus state. If there are no requests at all,
|
||||
// fall back to a static default-constructed request.
|
||||
static const FlxInputModeRequest EmptyRequest;
|
||||
const FlxInputModeRequest *Top = &EmptyRequest;
|
||||
for (const FlxInputModeRequest &Req : InputModeRequests.GetRequests())
|
||||
|
||||
// Get the desired configuration from the first widget.
|
||||
// TODO: Maybe we don't have to sort the whole array.
|
||||
TArray<UlxRootCanvasSlot*> WidgetSlots = RootCanvas->GetSortedUserWidgets();
|
||||
UUserWidget *Widget = nullptr;
|
||||
UWidget *Focus = nullptr;
|
||||
bool ShowPointer = false;
|
||||
if (!WidgetSlots.IsEmpty())
|
||||
{
|
||||
if (Req.Widget->IsInViewport()) { Top = &Req; break; }
|
||||
UlxRootCanvasSlot *Top = WidgetSlots[0];
|
||||
Widget = Cast<UUserWidget>(Top->GetContent());
|
||||
Focus = Widget->GetDesiredFocusWidget();
|
||||
ShowPointer = Top->ShowPointer;
|
||||
}
|
||||
|
||||
SetShowMouseCursor(Top->ShowPointer);
|
||||
SetShowMouseCursor(ShowPointer);
|
||||
|
||||
if (Top->ShowPointer)
|
||||
if (ShowPointer)
|
||||
{
|
||||
// Only release capture if the viewport is currently holding it
|
||||
// (e.g. we just came from GameOnly). A blanket ReleaseMouseCapture
|
||||
@@ -321,20 +321,20 @@ void AlxPlayerControllerBase::UpdateInputMode()
|
||||
// viewport client notifies us of that fact. We then focus the
|
||||
// widget if possible.
|
||||
//
|
||||
if ((!Top->ShowPointer) || (LastRequestGrantedFocus != Top->SequenceNumber))
|
||||
if ((!ShowPointer) || (LastWidgetGrantedFocus != Focus))
|
||||
{
|
||||
if (Top->Focus)
|
||||
if (Focus)
|
||||
{
|
||||
if (TSharedPtr<SWidget> SlateFocus = Top->Focus->GetCachedWidget())
|
||||
if (TSharedPtr<SWidget> SlateFocus = Focus->GetCachedWidget())
|
||||
{
|
||||
SlateOperations.SetUserFocus(SlateFocus.ToSharedRef());
|
||||
LastRequestGrantedFocus = Top->SequenceNumber;
|
||||
LastWidgetGrantedFocus = Focus;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
SlateOperations.SetUserFocus(ViewportWidgetRef);
|
||||
LastRequestGrantedFocus = Top->SequenceNumber;
|
||||
LastWidgetGrantedFocus = nullptr;
|
||||
}
|
||||
}
|
||||
else if (TSharedPtr<SWidget> ClickedWidget = ClickToFocusTarget.Pin())
|
||||
|
||||
@@ -3,9 +3,12 @@
|
||||
#include "CoreMinimal.h"
|
||||
#include "Engine/HitResult.h"
|
||||
#include "GameFramework/PlayerController.h"
|
||||
#include "InputModeRequest.h"
|
||||
#include "UObject/ObjectKey.h"
|
||||
#include "PlayerControllerBase.generated.h"
|
||||
|
||||
class UlxRootCanvasPanel;
|
||||
class UWidget;
|
||||
|
||||
UCLASS(BlueprintType, Blueprintable)
|
||||
class INTEGRATION_API AlxPlayerControllerBase : public APlayerController
|
||||
{
|
||||
@@ -55,9 +58,6 @@ private:
|
||||
|
||||
public:
|
||||
|
||||
// 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
|
||||
@@ -65,12 +65,14 @@ public:
|
||||
// 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", EnableInputComponent = "true"))
|
||||
static void WidgetRequestInputMode(class UUserWidget *Widget, bool ShowPointer, bool BlockInput, class UWidget *Focus, bool EnableInputComponent);
|
||||
// Restore focus back to the window that is in front, if it wants focus.
|
||||
UFUNCTION(BlueprintCallable, meta = (WorldContext = "Context"), Category = "Luprex|Root Canvas")
|
||||
static void RestoreFocusToFrontWidget(const UObject *Context);
|
||||
|
||||
// Add a widget to the root canvas at ZOrder 0 with default slot flags.
|
||||
UFUNCTION(BlueprintCallable, Category = "Luprex|Root Canvas",
|
||||
meta = (DefaultToSelf = "Widget", HideSelfPin = "true"))
|
||||
static void AddWidgetToRoot(class UUserWidget *Widget);
|
||||
|
||||
// Get the player controller, cast to AlxPlayerControllerBase.
|
||||
static AlxPlayerControllerBase *FromContext(const UObject *Context);
|
||||
@@ -78,13 +80,19 @@ public:
|
||||
UPROPERTY()
|
||||
FHitResult CurrentLookAt;
|
||||
|
||||
// Input mode requests - see InputModeRequest.h for an explanation.
|
||||
UPROPERTY()
|
||||
FlxInputModeRequests InputModeRequests;
|
||||
// The last widget whose focus request was granted.
|
||||
TObjectKey<UWidget> LastWidgetGrantedFocus;
|
||||
|
||||
// The last input mode request whose focus request was granted.
|
||||
// The single top-level UUserWidget added to the viewport. All
|
||||
// top-level UI widgets are children of RootCanvas inside it.
|
||||
UPROPERTY()
|
||||
int32 LastRequestGrantedFocus = 0;
|
||||
UUserWidget *RootWidget = nullptr;
|
||||
|
||||
// The root canvas panel inside RootWidget. Children of this
|
||||
// canvas are the top-level widgets; their slots carry both
|
||||
// layout and input-mode configuration.
|
||||
UPROPERTY()
|
||||
UlxRootCanvasPanel *RootCanvas = nullptr;
|
||||
|
||||
// The viewport client uses this to notify us that the user
|
||||
// clicked on a focusable widget.
|
||||
|
||||
101
Source/Integration/RootCanvas.cpp
Normal file
101
Source/Integration/RootCanvas.cpp
Normal file
@@ -0,0 +1,101 @@
|
||||
////////////////////////////////////////////////////////////
|
||||
//
|
||||
// RootCanvas.cpp
|
||||
//
|
||||
////////////////////////////////////////////////////////////
|
||||
|
||||
#include "RootCanvas.h"
|
||||
#include "Common.h"
|
||||
#include "Blueprint/UserWidget.h"
|
||||
|
||||
UlxRootCanvasSlot::UlxRootCanvasSlot(const FObjectInitializer& ObjectInitializer)
|
||||
: Super(ObjectInitializer)
|
||||
{
|
||||
// Children of the root canvas default to filling the parent,
|
||||
// matching the viewport's "stretch" behavior. Individual widgets
|
||||
// can still override anchors/offsets after being added.
|
||||
SetAnchors(FAnchors(0.0f, 0.0f, 1.0f, 1.0f));
|
||||
SetOffsets(FMargin(0.0f, 0.0f, 0.0f, 0.0f));
|
||||
}
|
||||
|
||||
UClass* UlxRootCanvasPanel::GetSlotClass() const
|
||||
{
|
||||
return UlxRootCanvasSlot::StaticClass();
|
||||
}
|
||||
|
||||
UlxRootCanvasSlot *UlxRootCanvasPanel::GetRootCanvasSlot(UUserWidget *Widget, ElxSuccessOrWrongType &Result)
|
||||
{
|
||||
if (IsValid(Widget))
|
||||
{
|
||||
if (UlxRootCanvasSlot *Slot = Cast<UlxRootCanvasSlot>(Widget->Slot))
|
||||
{
|
||||
Result = ElxSuccessOrWrongType::Success;
|
||||
return Slot;
|
||||
}
|
||||
}
|
||||
Result = ElxSuccessOrWrongType::WrongType;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
UlxRootCanvasSlot* UlxRootCanvasPanel::AddChildToRootCanvas(UWidget* Content)
|
||||
{
|
||||
return Cast<UlxRootCanvasSlot>(Super::AddChild(Content));
|
||||
}
|
||||
|
||||
int32 UlxRootCanvasPanel::GetMaxZOrder() const
|
||||
{
|
||||
int32 MaxZOrder = 0;
|
||||
for (UPanelSlot *PanelSlot : Slots)
|
||||
{
|
||||
UlxRootCanvasSlot *TypedSlot = Cast<UlxRootCanvasSlot>(PanelSlot);
|
||||
check(TypedSlot);
|
||||
MaxZOrder = FMath::Max(MaxZOrder, TypedSlot->GetZOrder());
|
||||
}
|
||||
return MaxZOrder;
|
||||
}
|
||||
|
||||
TArray<UlxRootCanvasSlot*> UlxRootCanvasPanel::GetSortedUserWidgets()
|
||||
{
|
||||
TArray<UlxRootCanvasSlot*> Result;
|
||||
Result.Reserve(Slots.Num());
|
||||
for (UPanelSlot *PanelSlot : Slots)
|
||||
{
|
||||
UlxRootCanvasSlot *TypedSlot = Cast<UlxRootCanvasSlot>(PanelSlot);
|
||||
check(TypedSlot);
|
||||
if (Cast<UUserWidget>(TypedSlot->Content) == nullptr) continue;
|
||||
Result.Add(TypedSlot);
|
||||
}
|
||||
Result.StableSort([](const UlxRootCanvasSlot &A, const UlxRootCanvasSlot &B)
|
||||
{
|
||||
return A.GetZOrder() > B.GetZOrder();
|
||||
});
|
||||
return Result;
|
||||
}
|
||||
|
||||
|
||||
void UlxRootCanvasPanel::SetWidgetWindowManagement(class UUserWidget *Widget,
|
||||
bool ShowPointer, bool BlockInput, bool EnableEnhancedInput, bool BringToFront, UWidget *DesiredFocusWidget)
|
||||
{
|
||||
if (!IsValid(Widget))
|
||||
{
|
||||
UE_LOG(LogLuprexIntegration, Error, TEXT("ManageRootWidget called with an invalid widget."));
|
||||
return;
|
||||
}
|
||||
UlxRootCanvasSlot *Slot = Cast<UlxRootCanvasSlot>(Widget->Slot);
|
||||
if (Slot == nullptr)
|
||||
{
|
||||
UE_LOG(LogLuprexIntegration, Error, TEXT("Widget is not yet a root widget, use 'AddWidgetToRoot' first"));
|
||||
return;
|
||||
}
|
||||
UlxRootCanvasPanel *Panel = Cast<UlxRootCanvasPanel>(Slot->Parent);
|
||||
check(Panel);
|
||||
Slot->ShowPointer = ShowPointer;
|
||||
Slot->BlockInput = BlockInput;
|
||||
Slot->EnableEnhancedInput = EnableEnhancedInput;
|
||||
Widget->SetDesiredFocusWidget(DesiredFocusWidget);
|
||||
if (BringToFront)
|
||||
{
|
||||
Slot->SetZOrder(Panel->GetMaxZOrder() + 1);
|
||||
}
|
||||
}
|
||||
|
||||
136
Source/Integration/RootCanvas.h
Normal file
136
Source/Integration/RootCanvas.h
Normal file
@@ -0,0 +1,136 @@
|
||||
////////////////////////////////////////////////////////////
|
||||
//
|
||||
// RootCanvas.h
|
||||
//
|
||||
// UlxRootCanvasPanel is a UCanvasPanel subclass whose
|
||||
// slots (UlxRootCanvasSlot) carry input-mode configuration
|
||||
// in addition to layout. The PlayerController scans these
|
||||
// slots, sorted by ZOrder, to arbitrate pointer visibility,
|
||||
// capture, focus, and input-component blocking for
|
||||
// top-level widgets. ZOrder therefore serves double duty:
|
||||
// it determines draw order AND input priority.
|
||||
//
|
||||
////////////////////////////////////////////////////////////
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Common.h"
|
||||
#include "Blueprint/UserWidget.h"
|
||||
#include "Components/CanvasPanel.h"
|
||||
#include "Components/CanvasPanelSlot.h"
|
||||
#include "RootCanvas.generated.h"
|
||||
|
||||
class UWidget;
|
||||
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
//
|
||||
// UlxRootCanvasSlot
|
||||
//
|
||||
// Luprex provides a "window management system" for root widgets.
|
||||
// This system is documented in Docs/Keyboard-Focus-and-Input-Modes.md
|
||||
// The Root Canvas Slot is how widgets ask the window management system
|
||||
// to engage certain behaviors.
|
||||
//
|
||||
////////////////////////////////////////////////////////////
|
||||
UCLASS()
|
||||
class INTEGRATION_API UlxRootCanvasSlot : public UCanvasPanelSlot
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
|
||||
UlxRootCanvasSlot(const FObjectInitializer& ObjectInitializer);
|
||||
|
||||
// When this window is in front, the mouse pointer is shown and the
|
||||
// viewport does not capture the mouse.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Luprex|Input Mode")
|
||||
bool ShowPointer = false;
|
||||
|
||||
// When this window is in front, this window's input component blocks
|
||||
// lower-priority input components in the input stack.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Luprex|Input Mode")
|
||||
bool BlockInput = false;
|
||||
|
||||
// If true, this widget's input component is enabled, which is to say,
|
||||
// that enhanced input events are enabled. If false, enhanced input
|
||||
// events in the Event Graph are deactivated.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Luprex|Input Mode")
|
||||
bool EnableEnhancedInput = true;
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
//
|
||||
// UlxRootCanvasPanel
|
||||
//
|
||||
// A UCanvasPanel that uses UlxRootCanvasSlot for its
|
||||
// children instead of the plain UCanvasPanelSlot. Layout
|
||||
// behavior is identical to UCanvasPanel; only the slot
|
||||
// type differs.
|
||||
//
|
||||
////////////////////////////////////////////////////////////
|
||||
UCLASS()
|
||||
class INTEGRATION_API UlxRootCanvasPanel : public UCanvasPanel
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
|
||||
// Convenience wrapper around AddChild that returns the
|
||||
// derived slot type, so callers don't have to cast.
|
||||
UFUNCTION()
|
||||
UlxRootCanvasSlot* AddChildToRootCanvas(UWidget* Content);
|
||||
|
||||
// Find children of type UserWidget. Return them in a sorted
|
||||
// order, with the highest Zorder first.
|
||||
TArray<UlxRootCanvasSlot*> GetSortedUserWidgets();
|
||||
|
||||
// Return the largest ZOrder across all slots, or 0 if empty.
|
||||
// Used as the basis for placing new widgets on top.
|
||||
int32 GetMaxZOrder() const;
|
||||
|
||||
// This function updates several window-management-related properties
|
||||
// which are stored in the UserWidget and the lxRootCanvasSlot. Note that
|
||||
// it is perfectly legal to edit these properties by other means: this
|
||||
// function is one of many valid setters. See the documentation in
|
||||
// Docs/Keyboard-Focus-and-Input-Modes.md for information about how Luprex
|
||||
// window management works.
|
||||
UFUNCTION(BlueprintCallable, Category = "Luprex|Window Management",
|
||||
meta = (DefaultToSelf = "Widget", EnableEnhancedInput = "true"))
|
||||
static void SetWidgetWindowManagement(class UUserWidget *Widget,
|
||||
bool ShowPointer, bool BlockInput, bool EnableEnhancedInput,
|
||||
bool BringToFront, UWidget *DesiredFocusWidget);
|
||||
|
||||
// Fetch the UlxRootCanvasSlot for a widget that is parented to a
|
||||
// UlxRootCanvasPanel. Returns nullptr via the WrongType exec pin
|
||||
// if the widget isn't a root widget (no slot, or slot is not a
|
||||
// UlxRootCanvasSlot).
|
||||
UFUNCTION(BlueprintCallable, Category = "Luprex|Window Management",
|
||||
meta = (DefaultToSelf = "Widget", ExpandEnumAsExecs = "Result"))
|
||||
static UlxRootCanvasSlot *GetRootCanvasSlot(class UUserWidget *Widget, ElxSuccessOrWrongType &Result);
|
||||
|
||||
protected:
|
||||
|
||||
// UPanelWidget
|
||||
virtual UClass* GetSlotClass() const override;
|
||||
};
|
||||
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
//
|
||||
// UlxRootWidget
|
||||
//
|
||||
// A trivial concrete UUserWidget subclass used by the
|
||||
// PlayerController to host the root UlxRootCanvasPanel.
|
||||
// UUserWidget itself is marked Abstract in UMG, so we need
|
||||
// a non-abstract class to instantiate.
|
||||
//
|
||||
////////////////////////////////////////////////////////////
|
||||
UCLASS()
|
||||
class INTEGRATION_API UlxRootWidget : public UUserWidget
|
||||
{
|
||||
GENERATED_BODY()
|
||||
};
|
||||
Reference in New Issue
Block a user