// Copyright Epic Games, Inc. All Rights Reserved. #include "GameUIPolicy.h" #include "CommonActivatableWidget.h" #include "Engine/LocalPlayer.h" #include "GameUIManagerSubsystem.h" #include "CommonLocalPlayer.h" #include "PrimaryGameLayout.h" #include "Engine/Engine.h" #include "LogCommonGame.h" // Static UGameUIPolicy* UGameUIPolicy::GetGameUIPolicy(const UObject* WorldContextObject) { if (UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull)) { if (UGameInstance* GameInstance = World->GetGameInstance()) { if (UGameUIManagerSubsystem* UIManager = UGameInstance::GetSubsystem(GameInstance)) { return UIManager->GetCurrentUIPolicy(); } } } return nullptr; } UGameUIManagerSubsystem* UGameUIPolicy::GetOwningUIManager() const { return CastChecked(GetOuter()); } UWorld* UGameUIPolicy::GetWorld() const { return GetOwningUIManager()->GetGameInstance()->GetWorld(); } UPrimaryGameLayout* UGameUIPolicy::GetRootLayout(const UCommonLocalPlayer* LocalPlayer) const { const FRootViewportLayoutInfo* LayoutInfo = RootViewportLayouts.FindByKey(LocalPlayer); return LayoutInfo ? LayoutInfo->RootLayout : nullptr; } void UGameUIPolicy::NotifyPlayerAdded(UCommonLocalPlayer* LocalPlayer) { LocalPlayer->OnPlayerControllerSet.AddWeakLambda(this, [this](UCommonLocalPlayer* LocalPlayer, APlayerController* PlayerController) { NotifyPlayerRemoved(LocalPlayer); if (FRootViewportLayoutInfo* LayoutInfo = RootViewportLayouts.FindByKey(LocalPlayer)) { AddLayoutToViewport(LocalPlayer, LayoutInfo->RootLayout); LayoutInfo->bAddedToViewport = true; } else { CreateLayoutWidget(LocalPlayer); } }); if (FRootViewportLayoutInfo* LayoutInfo = RootViewportLayouts.FindByKey(LocalPlayer)) { AddLayoutToViewport(LocalPlayer, LayoutInfo->RootLayout); LayoutInfo->bAddedToViewport = true; } else { CreateLayoutWidget(LocalPlayer); } } void UGameUIPolicy::NotifyPlayerRemoved(UCommonLocalPlayer* LocalPlayer) { if (FRootViewportLayoutInfo* LayoutInfo = RootViewportLayouts.FindByKey(LocalPlayer)) { RemoveLayoutFromViewport(LocalPlayer, LayoutInfo->RootLayout); LayoutInfo->bAddedToViewport = false; if (LocalMultiplayerInteractionMode == ELocalMultiplayerInteractionMode::SingleToggle && !LocalPlayer->IsPrimaryPlayer()) { UPrimaryGameLayout* RootLayout = LayoutInfo->RootLayout; if (RootLayout && !RootLayout->IsDormant()) { // We're removing a secondary player's root while it's in control - transfer control back to the primary player's root RootLayout->SetIsDormant(true); for (const FRootViewportLayoutInfo& RootLayoutInfo : RootViewportLayouts) { if (RootLayoutInfo.LocalPlayer->IsPrimaryPlayer()) { if (UPrimaryGameLayout* PrimaryRootLayout = RootLayoutInfo.RootLayout) { PrimaryRootLayout->SetIsDormant(false); } } } } } } } void UGameUIPolicy::NotifyPlayerDestroyed(UCommonLocalPlayer* LocalPlayer) { NotifyPlayerRemoved(LocalPlayer); LocalPlayer->OnPlayerControllerSet.RemoveAll(this); const int32 LayoutInfoIdx = RootViewportLayouts.IndexOfByKey(LocalPlayer); if (LayoutInfoIdx != INDEX_NONE) { UPrimaryGameLayout* Layout = RootViewportLayouts[LayoutInfoIdx].RootLayout; RootViewportLayouts.RemoveAt(LayoutInfoIdx); RemoveLayoutFromViewport(LocalPlayer, Layout); OnRootLayoutReleased(LocalPlayer, Layout); } } void UGameUIPolicy::AddLayoutToViewport(UCommonLocalPlayer* LocalPlayer, UPrimaryGameLayout* Layout) { UE_LOG(LogCommonGame, Log, TEXT("[%s] is adding player [%s]'s root layout [%s] to the viewport"), *GetName(), *GetNameSafe(LocalPlayer), *GetNameSafe(Layout)); Layout->SetPlayerContext(FLocalPlayerContext(LocalPlayer)); Layout->AddToPlayerScreen(1000); OnRootLayoutAddedToViewport(LocalPlayer, Layout); } void UGameUIPolicy::RemoveLayoutFromViewport(UCommonLocalPlayer* LocalPlayer, UPrimaryGameLayout* Layout) { TWeakPtr LayoutSlateWidget = Layout->GetCachedWidget(); if (LayoutSlateWidget.IsValid()) { UE_LOG(LogCommonGame, Log, TEXT("[%s] is removing player [%s]'s root layout [%s] from the viewport"), *GetName(), *GetNameSafe(LocalPlayer), *GetNameSafe(Layout)); Layout->RemoveFromViewport(); if (LayoutSlateWidget.IsValid()) { UE_LOG(LogCommonGame, Log, TEXT("Player [%s]'s root layout [%s] has been removed from the viewport, but other references to its underlying Slate widget still exist. Noting in case we leak it."), *GetNameSafe(LocalPlayer), *GetNameSafe(Layout)); } OnRootLayoutRemovedFromViewport(LocalPlayer, Layout); } } void UGameUIPolicy::OnRootLayoutAddedToViewport(UCommonLocalPlayer* LocalPlayer, UPrimaryGameLayout* Layout) { #if WITH_EDITOR if (GIsEditor && LocalPlayer->IsPrimaryPlayer()) { // So our controller will work in PIE without needing to click in the viewport FSlateApplication::Get().SetUserFocusToGameViewport(0); } #endif } void UGameUIPolicy::OnRootLayoutRemovedFromViewport(UCommonLocalPlayer* LocalPlayer, UPrimaryGameLayout* Layout) { } void UGameUIPolicy::OnRootLayoutReleased(UCommonLocalPlayer* LocalPlayer, UPrimaryGameLayout* Layout) { } void UGameUIPolicy::RequestPrimaryControl(UPrimaryGameLayout* Layout) { if (LocalMultiplayerInteractionMode == ELocalMultiplayerInteractionMode::SingleToggle && Layout->IsDormant()) { for (const FRootViewportLayoutInfo& LayoutInfo : RootViewportLayouts) { UPrimaryGameLayout* RootLayout = LayoutInfo.RootLayout; if (RootLayout && !RootLayout->IsDormant()) { RootLayout->SetIsDormant(true); break; } } Layout->SetIsDormant(false); } } void UGameUIPolicy::CreateLayoutWidget(UCommonLocalPlayer* LocalPlayer) { if (APlayerController* PlayerController = LocalPlayer->GetPlayerController(GetWorld())) { TSubclassOf LayoutWidgetClass = GetLayoutWidgetClass(LocalPlayer); if (ensure(LayoutWidgetClass && !LayoutWidgetClass->HasAnyClassFlags(CLASS_Abstract))) { UPrimaryGameLayout* NewLayoutObject = CreateWidget(PlayerController, LayoutWidgetClass); RootViewportLayouts.Emplace(LocalPlayer, NewLayoutObject, true); AddLayoutToViewport(LocalPlayer, NewLayoutObject); } } } TSubclassOf UGameUIPolicy::GetLayoutWidgetClass(UCommonLocalPlayer* LocalPlayer) { return LayoutClass.LoadSynchronous(); }