// 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 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(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, TWeakObjectPtr>; FMemMark Mark(FMemStack::Get()); TArray> 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(); } bool UGameViewportSubsystem::IsWidgetAdded(const UWidget* Widget) const { if (!Widget) { FFrame::KismetExecutionMessage(TEXT("The Widget is invalid."), ELogVerbosity::Warning); return false; } if (Widget->bIsManagedByGameViewportSubsystem) { TObjectKey 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 FullScreenCanvas; { TObjectKey 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 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 WidgetKey = Widget; ViewportWidgets.RemoveAndCopyValue(WidgetKey, SlotInfo); RemoveWidgetInternal(Widget, SlotInfo.FullScreenWidget, SlotInfo.LocalPlayer); OnWidgetRemoved.Broadcast(Widget); } } void UGameViewportSubsystem::RemoveWidgetInternal(UWidget* Widget, const TWeakPtr& FullScreenWidget, const TWeakObjectPtr& LocalPlayer) { Widget->bIsManagedByGameViewportSubsystem = false; if (TSharedPtr 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 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 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 WidgetKey = Widget; FSlotInfo& SlotInfo = ViewportWidgets.FindOrAdd(WidgetKey); Widget->bIsManagedByGameViewportSubsystem = true; SlotInfo.Slot = Slot; if (TSharedPtr WidgetHost = SlotInfo.FullScreenWidget.Pin()) { check(SlotInfo.FullScreenWidgetSlot); TPair 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(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> 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); } }