lyra_game_ue/Source/LyraGame/UI/LyraHUDLayout.cpp
Goran Lazarevski 3bcab085f8 Initial commit
2025-03-20 11:06:26 +01:00

192 lines
7.3 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "LyraHUDLayout.h"
#include "CommonUIExtensions.h"
#include "CommonUISettings.h"
#include "GameFramework/InputDeviceSubsystem.h"
#include "GameFramework/InputSettings.h"
#include "GenericPlatform/GenericPlatformInputDeviceMapper.h"
#include "Input/CommonUIInputTypes.h"
#include "ICommonUIModule.h"
#include "LyraLogChannels.h"
#include "NativeGameplayTags.h"
#include "UI/Foundation/LyraControllerDisconnectedScreen.h"
#include "UI/LyraActivatableWidget.h"
#if WITH_EDITOR
#include "CommonUIVisibilitySubsystem.h"
#endif // WITH_EDITOR
#include UE_INLINE_GENERATED_CPP_BY_NAME(LyraHUDLayout)
UE_DEFINE_GAMEPLAY_TAG_STATIC(TAG_UI_LAYER_MENU, "UI.Layer.Menu");
UE_DEFINE_GAMEPLAY_TAG_STATIC(TAG_UI_ACTION_ESCAPE, "UI.Action.Escape");
UE_DEFINE_GAMEPLAY_TAG_STATIC(TAG_Platform_Trait_Input_PrimarlyController, "Platform.Trait.Input.PrimarlyController");
ULyraHUDLayout::ULyraHUDLayout(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
, SpawnedControllerDisconnectScreen(nullptr)
{
// By default, only primarily controller platforms require a disconnect screen.
PlatformRequiresControllerDisconnectScreen.AddTag(TAG_Platform_Trait_Input_PrimarlyController);
}
void ULyraHUDLayout::NativeOnInitialized()
{
Super::NativeOnInitialized();
RegisterUIActionBinding(FBindUIActionArgs(FUIActionTag::ConvertChecked(TAG_UI_ACTION_ESCAPE), false, FSimpleDelegate::CreateUObject(this, &ThisClass::HandleEscapeAction)));
// If we can display a controller disconnect screen, then listen for the controller state change delegates
if (ShouldPlatformDisplayControllerDisconnectScreen())
{
// Bind to when input device connections change
IPlatformInputDeviceMapper& DeviceMapper = IPlatformInputDeviceMapper::Get();
DeviceMapper.GetOnInputDeviceConnectionChange().AddUObject(this, &ThisClass::HandleInputDeviceConnectionChanged);
DeviceMapper.GetOnInputDevicePairingChange().AddUObject(this, &ThisClass::HandleInputDevicePairingChanged);
}
}
void ULyraHUDLayout::NativeDestruct()
{
Super::NativeDestruct();
// Remove bindings to input device connection changing
IPlatformInputDeviceMapper& DeviceMapper = IPlatformInputDeviceMapper::Get();
DeviceMapper.GetOnInputDeviceConnectionChange().RemoveAll(this);
DeviceMapper.GetOnInputDevicePairingChange().RemoveAll(this);
if (RequestProcessControllerStateHandle.IsValid())
{
FTSTicker::GetCoreTicker().RemoveTicker(RequestProcessControllerStateHandle);
RequestProcessControllerStateHandle.Reset();
}
}
void ULyraHUDLayout::HandleEscapeAction()
{
if (ensure(!EscapeMenuClass.IsNull()))
{
UCommonUIExtensions::PushStreamedContentToLayer_ForPlayer(GetOwningLocalPlayer(), TAG_UI_LAYER_MENU, EscapeMenuClass);
}
}
void ULyraHUDLayout::HandleInputDeviceConnectionChanged(EInputDeviceConnectionState NewConnectionState, FPlatformUserId PlatformUserId, FInputDeviceId InputDeviceId)
{
const FPlatformUserId OwningLocalPlayerId = GetOwningLocalPlayer()->GetPlatformUserId();
ensure(OwningLocalPlayerId.IsValid());
// This device connection change happened to a different player, ignore it for us.
if (PlatformUserId != OwningLocalPlayerId)
{
return;
}
NotifyControllerStateChangeForDisconnectScreen();
}
void ULyraHUDLayout::HandleInputDevicePairingChanged(FInputDeviceId InputDeviceId, FPlatformUserId NewUserPlatformId, FPlatformUserId OldUserPlatformId)
{
const FPlatformUserId OwningLocalPlayerId = GetOwningLocalPlayer()->GetPlatformUserId();
ensure(OwningLocalPlayerId.IsValid());
// If this pairing change was related to our local player, notify of a change.
if (NewUserPlatformId == OwningLocalPlayerId || OldUserPlatformId == OwningLocalPlayerId)
{
NotifyControllerStateChangeForDisconnectScreen();
}
}
bool ULyraHUDLayout::ShouldPlatformDisplayControllerDisconnectScreen() const
{
// We only want this menu on primarily controller platforms
bool bHasAllRequiredTags = ICommonUIModule::GetSettings().GetPlatformTraits().HasAll(PlatformRequiresControllerDisconnectScreen);
// Check the tags that we may be emulating in the editor too
#if WITH_EDITOR
const FGameplayTagContainer& PlatformEmulationTags = UCommonUIVisibilitySubsystem::Get(GetOwningLocalPlayer())->GetVisibilityTags();
bHasAllRequiredTags |= PlatformEmulationTags.HasAll(PlatformRequiresControllerDisconnectScreen);
#endif // WITH_EDITOR
return bHasAllRequiredTags;
}
void ULyraHUDLayout::NotifyControllerStateChangeForDisconnectScreen()
{
// We should only ever get here if we have bound to the controller state change delegates
ensure(ShouldPlatformDisplayControllerDisconnectScreen());
// If we haven't already, queue the processing of device state for next tick.
if (!RequestProcessControllerStateHandle.IsValid())
{
RequestProcessControllerStateHandle = FTSTicker::GetCoreTicker().AddTicker(FTickerDelegate::CreateWeakLambda(this, [this](float DeltaTime)
{
RequestProcessControllerStateHandle.Reset();
ProcessControllerDevicesHavingChangedForDisconnectScreen();
return false;
}));
}
}
void ULyraHUDLayout::ProcessControllerDevicesHavingChangedForDisconnectScreen()
{
// We should only ever get here if we have bound to the controller state change delegates
ensure(ShouldPlatformDisplayControllerDisconnectScreen());
const FPlatformUserId OwningLocalPlayerId = GetOwningLocalPlayer()->GetPlatformUserId();
ensure(OwningLocalPlayerId.IsValid());
// Get all input devices mapped to our player
const IPlatformInputDeviceMapper& DeviceMapper = IPlatformInputDeviceMapper::Get();
TArray<FInputDeviceId> MappedInputDevices;
const int32 NumDevicesMappedToUser = DeviceMapper.GetAllInputDevicesForUser(OwningLocalPlayerId, OUT MappedInputDevices);
// Check if there are any other connected GAMEPAD devices mapped to this platform user.
bool bHasConnectedController = false;
for (const FInputDeviceId MappedDevice : MappedInputDevices)
{
if (DeviceMapper.GetInputDeviceConnectionState(MappedDevice) == EInputDeviceConnectionState::Connected)
{
const FHardwareDeviceIdentifier HardwareInfo = UInputDeviceSubsystem::Get()->GetInputDeviceHardwareIdentifier(MappedDevice);
if (HardwareInfo.PrimaryDeviceType == EHardwareDevicePrimaryType::Gamepad)
{
bHasConnectedController = true;
}
}
}
// If there are no gamepad input devices mapped to this user, then we want to pop the toast saying to re-connect them
if (!bHasConnectedController)
{
DisplayControllerDisconnectedMenu();
}
// Otherwise we can hide the screen if it is currently being shown
else if (SpawnedControllerDisconnectScreen)
{
HideControllerDisconnectedMenu();
}
}
void ULyraHUDLayout::DisplayControllerDisconnectedMenu_Implementation()
{
UE_LOG(LogLyra, Log, TEXT("[%hs] Display controller disconnected menu!"), __func__);
if (ControllerDisconnectedScreen)
{
// Push the "controller disconnected" widget to the menu layer
SpawnedControllerDisconnectScreen = UCommonUIExtensions::PushContentToLayer_ForPlayer(GetOwningLocalPlayer(), TAG_UI_LAYER_MENU, ControllerDisconnectedScreen);
}
}
void ULyraHUDLayout::HideControllerDisconnectedMenu_Implementation()
{
UE_LOG(LogLyra, Log, TEXT("[%hs] Hide controller disconnected menu!"), __func__);
UCommonUIExtensions::PopContentFromLayer(SpawnedControllerDisconnectScreen);
SpawnedControllerDisconnectScreen = nullptr;
}