Files
UnrealEngine/Engine/Source/Runtime/UMG/Private/Blueprint/GameViewportSubsystem.cpp
2025-05-18 13:04:45 +08:00

333 lines
10 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "Blueprint/GameViewportSubsystem.h"
#include "Blueprint/UserWidget.h"
#include "Blueprint/WidgetLayoutLibrary.h"
#include "Components/Widget.h"
#include "Engine/Engine.h"
#include "Engine/GameViewportClient.h"
#include "Engine/LocalPlayer.h"
#include "Engine/World.h"
#include "Templates/Tuple.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(GameViewportSubsystem)
/*
*
*/
namespace UE::UMG::Private
{
TPair<FMargin, bool> CalculateOffsetArgument(const FGameViewportWidgetSlot& Slot)
{
// If the size is zero, and we're not stretched, then use the desired size.
FVector2D FinalSize = FVector2D(Slot.Offsets.Right, Slot.Offsets.Bottom);
bool bUseAutoSize = FinalSize.IsZero() && !Slot.Anchors.IsStretchedVertical() && !Slot.Anchors.IsStretchedHorizontal();
return TPair<FMargin, bool>(Slot.Offsets, bUseAutoSize);
}
}
/*
*
*/
void UGameViewportSubsystem::Initialize(FSubsystemCollectionBase& Collection)
{
Super::Initialize(Collection);
//FWorldDelegates::LevelRemovedFromWorld.AddUObject(this, &ThisClass::HandleLevelRemovedFromWorld);
FWorldDelegates::OnWorldCleanup.AddUObject(this, &ThisClass::HandleWorldCleanup);
FWorldDelegates::OnWorldBeginTearDown.AddUObject(this, &ThisClass::HandleRemoveWorld);
FWorldDelegates::OnPreWorldFinishDestroy.AddUObject(this, &ThisClass::HandleRemoveWorld);
}
void UGameViewportSubsystem::Deinitialize()
{
//FWorldDelegates::LevelRemovedFromWorld.RemoveAll(this);
FWorldDelegates::OnPreWorldFinishDestroy.RemoveAll(this);
FWorldDelegates::OnWorldBeginTearDown.RemoveAll(this);
FWorldDelegates::OnWorldCleanup.RemoveAll(this);
if (ViewportWidgets.Num() > 0)
{
using FElement = TTuple<UWidget*, TWeakPtr<SConstraintCanvas>, TWeakObjectPtr<ULocalPlayer>>;
FMemMark Mark(FMemStack::Get());
TArray<FElement, TMemStackAllocator<>> TmpViewportWidgets;
TmpViewportWidgets.Reserve(ViewportWidgets.Num());
for (auto& Itt : ViewportWidgets)
{
if (UWidget* Widget = Itt.Key.ResolveObjectPtr())
{
TmpViewportWidgets.Emplace(Widget, Itt.Value.FullScreenWidget, Itt.Value.LocalPlayer);
}
}
ViewportWidgets.Empty();
for (FElement& Element : TmpViewportWidgets)
{
if (Element.Get<0>()->bIsManagedByGameViewportSubsystem)
{
RemoveWidgetInternal(Element.Get<0>(), Element.Get<1>(), Element.Get<2>());
}
}
}
Super::Deinitialize();
}
UGameViewportSubsystem* UGameViewportSubsystem::Get(UWorld* World)
{
return Get();
}
UGameViewportSubsystem* UGameViewportSubsystem::Get()
{
return GEngine->GetEngineSubsystem<UGameViewportSubsystem>();
}
bool UGameViewportSubsystem::IsWidgetAdded(const UWidget* Widget) const
{
if (!Widget)
{
FFrame::KismetExecutionMessage(TEXT("The Widget is invalid."), ELogVerbosity::Warning);
return false;
}
if (Widget->bIsManagedByGameViewportSubsystem)
{
TObjectKey<UWidget> WidgetKey = Widget;
if (const FSlotInfo* FoundSlot = ViewportWidgets.Find(WidgetKey))
{
return FoundSlot->FullScreenWidget.IsValid();
}
}
return false;
}
bool UGameViewportSubsystem::AddWidget(UWidget* Widget, FGameViewportWidgetSlot Slot)
{
return AddToScreen(Widget, nullptr, Slot);
}
bool UGameViewportSubsystem::AddWidgetForPlayer(UWidget* Widget, ULocalPlayer* Player, FGameViewportWidgetSlot Slot)
{
if (!Player)
{
FFrame::KismetExecutionMessage(TEXT("The Player is invalid."), ELogVerbosity::Warning);
return false;
}
return AddToScreen(Widget, Player, Slot);
}
bool UGameViewportSubsystem::AddToScreen(UWidget* Widget, ULocalPlayer* Player, FGameViewportWidgetSlot& Slot)
{
if (!Widget)
{
FFrame::KismetExecutionMessage(TEXT("The Widget is invalid."), ELogVerbosity::Warning);
return false;
}
if (UPanelWidget* ParentPanel = Widget->GetParent())
{
FFrame::KismetExecutionMessage(*FString::Printf(TEXT("The widget '%s' already has a parent widget. It can't also be added to the viewport!"), *Widget->GetName()), ELogVerbosity::Warning);
return false;
}
// Add the widget to the current worlds viewport.
UWorld* World = Widget->GetWorld();
if (!World || !World->IsGameWorld())
{
FFrame::KismetExecutionMessage(*FString::Printf(TEXT("The widget '%s' does not have a World."), *Widget->GetName()), ELogVerbosity::Warning);
return false;
}
UGameViewportClient* ViewportClient = World->GetGameViewport();
if (!ViewportClient)
{
FFrame::KismetExecutionMessage(TEXT("No game viewport was found."), ELogVerbosity::Warning);
return false;
}
SConstraintCanvas::FSlot* RawSlot = nullptr;
TSharedPtr<SConstraintCanvas> FullScreenCanvas;
{
TObjectKey<UWidget> WidgetKey = Widget;
FSlotInfo& SlotInfo = ViewportWidgets.FindOrAdd(WidgetKey);
Widget->bIsManagedByGameViewportSubsystem = true;
if (SlotInfo.FullScreenWidget.IsValid())
{
FFrame::KismetExecutionMessage(*FString::Printf(TEXT("The widget '%s' was already added to the screen."), *Widget->GetName()), ELogVerbosity::Warning);
return false;
}
TPair<FMargin, bool> OffsetArgument = UE::UMG::Private::CalculateOffsetArgument(Slot);
FullScreenCanvas = SNew(SConstraintCanvas)
+ SConstraintCanvas::Slot()
.Offset(OffsetArgument.Get<0>())
.AutoSize(OffsetArgument.Get<1>())
.Anchors(Slot.Anchors)
.Alignment(Slot.Alignment)
.Expose(RawSlot);
SlotInfo.Slot = Slot;
SlotInfo.LocalPlayer = Player;
SlotInfo.FullScreenWidget = FullScreenCanvas;
SlotInfo.FullScreenWidgetSlot = RawSlot;
}
check(RawSlot);
RawSlot->AttachWidget(Widget->TakeWidget());
if (Player)
{
ViewportClient->AddViewportWidgetForPlayer(Player, FullScreenCanvas.ToSharedRef(), Slot.ZOrder);
}
else
{
// We add 10 to the zorder when adding to the viewport to avoid
// displaying below any built-in controls, like the virtual joysticks on mobile builds.
ViewportClient->AddViewportWidgetContent(FullScreenCanvas.ToSharedRef(), Slot.ZOrder + 10);
}
OnWidgetAdded.Broadcast(Widget, Player);
return true;
}
void UGameViewportSubsystem::RemoveWidget(UWidget* Widget)
{
if (Widget && Widget->bIsManagedByGameViewportSubsystem)
{
FSlotInfo SlotInfo;
TObjectKey<UWidget> WidgetKey = Widget;
ViewportWidgets.RemoveAndCopyValue(WidgetKey, SlotInfo);
RemoveWidgetInternal(Widget, SlotInfo.FullScreenWidget, SlotInfo.LocalPlayer);
OnWidgetRemoved.Broadcast(Widget);
}
}
void UGameViewportSubsystem::RemoveWidgetInternal(UWidget* Widget, const TWeakPtr<SConstraintCanvas>& FullScreenWidget, const TWeakObjectPtr<ULocalPlayer>& LocalPlayer)
{
Widget->bIsManagedByGameViewportSubsystem = false;
if (TSharedPtr<SWidget> WidgetHost = FullScreenWidget.Pin())
{
// If this is a game world remove the widget from the current world's viewport.
UWorld* World = Widget->GetWorld();
if (World && World->IsGameWorld())
{
if (UGameViewportClient* ViewportClient = World->GetGameViewport())
{
TSharedRef<SWidget> WidgetHostRef = WidgetHost.ToSharedRef();
ViewportClient->RemoveViewportWidgetContent(WidgetHostRef);
// We may no longer have access to our owning player if the player controller was destroyed
// Passing nullptr to RemoveViewportWidgetForPlayer will search all player layers for this widget
ViewportClient->RemoveViewportWidgetForPlayer(LocalPlayer.Get(), WidgetHostRef);
}
}
}
}
FGameViewportWidgetSlot UGameViewportSubsystem::GetWidgetSlot(const UWidget* Widget) const
{
if (Widget && Widget->bIsManagedByGameViewportSubsystem)
{
TObjectKey<UWidget> WidgetKey = Widget;
const FSlotInfo* SlotInfo = ViewportWidgets.Find(WidgetKey);
ensureMsgf(SlotInfo != nullptr, TEXT("The bIsManagedByGameViewportSubsystem should matches the ViewportWidgets state."));
if (SlotInfo)
{
return SlotInfo->Slot;
}
}
return FGameViewportWidgetSlot();
}
void UGameViewportSubsystem::SetWidgetSlot(UWidget* Widget, FGameViewportWidgetSlot Slot)
{
if (Widget && !Widget->HasAnyFlags(RF_BeginDestroyed))
{
TObjectKey<UWidget> WidgetKey = Widget;
FSlotInfo& SlotInfo = ViewportWidgets.FindOrAdd(WidgetKey);
Widget->bIsManagedByGameViewportSubsystem = true;
SlotInfo.Slot = Slot;
if (TSharedPtr<SConstraintCanvas> WidgetHost = SlotInfo.FullScreenWidget.Pin())
{
check(SlotInfo.FullScreenWidgetSlot);
TPair<FMargin, bool> OffsetArgument = UE::UMG::Private::CalculateOffsetArgument(Slot);
SlotInfo.FullScreenWidgetSlot->SetOffset(OffsetArgument.Get<0>());
SlotInfo.FullScreenWidgetSlot->SetAutoSize(OffsetArgument.Get<1>());
SlotInfo.FullScreenWidgetSlot->SetAnchors(Slot.Anchors);
SlotInfo.FullScreenWidgetSlot->SetAlignment(Slot.Alignment);
SlotInfo.FullScreenWidgetSlot->SetZOrder(static_cast<float>(Slot.ZOrder));
WidgetHost->Invalidate(EInvalidateWidgetReason::Layout);
}
}
}
FGameViewportWidgetSlot UGameViewportSubsystem::SetWidgetSlotPosition(FGameViewportWidgetSlot Slot, const UWidget* Widget, FVector2D Position, bool bRemoveDPIScale)
{
if (bRemoveDPIScale)
{
const float Scale = UWidgetLayoutLibrary::GetViewportScale(Widget);
Position /= Scale;
}
const FVector2f NewPosition = UE::Slate::CastToVector2f(Position);
Slot.Offsets.Left = NewPosition.X;
Slot.Offsets.Top = NewPosition.Y;
Slot.Anchors = FAnchors(0.f, 0.f);
return Slot;
}
FGameViewportWidgetSlot UGameViewportSubsystem::SetWidgetSlotDesiredSize(FGameViewportWidgetSlot Slot, FVector2D Size)
{
const FVector2f NewSize = UE::Slate::CastToVector2f(Size);
Slot.Offsets.Right = NewSize.X;
Slot.Offsets.Bottom = NewSize.Y;
Slot.Anchors = FAnchors(0.f, 0.f);
return Slot;
}
void UGameViewportSubsystem::HandleWorldCleanup(UWorld* InWorld, bool bSessionEnded, bool bCleanupResoures)
{
HandleRemoveWorld(InWorld);
}
void UGameViewportSubsystem::HandleRemoveWorld(UWorld* InWorld)
{
TArray<UWidget*, TInlineAllocator<16>> WidgetsToRemove;
for (FViewportWidgetList::TIterator Itt = ViewportWidgets.CreateIterator(); Itt; ++Itt)
{
if (UWidget* Widget = Itt.Key().ResolveObjectPtr())
{
if (Itt.Value().Slot.bAutoRemoveOnWorldRemoved && InWorld == Widget->GetWorld())
{
WidgetsToRemove.Add(Widget);
}
}
else
{
Itt.RemoveCurrent();
}
}
for (UWidget* Widget : WidgetsToRemove)
{
Widget->RemoveFromParent();
}
}
void UGameViewportSubsystem::HandleLevelRemovedFromWorld(ULevel* InLevel, UWorld* InWorld)
{
// If the InLevel is null, it's a signal that the entire world is about to disappear, so
// go ahead and remove this widget from the viewport, it could be holding onto too many
// dangerous actor references that won't carry over into the next world.
if (InLevel == nullptr)
{
HandleRemoveWorld(InWorld);
}
}