// Copyright Epic Games, Inc. All Rights Reserved. #include "Kismet2/DebuggerCommands.h" #include "Misc/Paths.h" #include "Misc/MessageDialog.h" #include "Misc/App.h" #include "Modules/ModuleManager.h" #include "Framework/Application/SlateApplication.h" #include "Widgets/Text/STextBlock.h" #include "Framework/MultiBox/MultiBoxExtender.h" #include "Framework/MultiBox/MultiBoxBuilder.h" #include "Widgets/Input/SSpinBox.h" #include "Framework/Docking/TabManager.h" #include "EditorStyleSet.h" #include "Classes/EditorStyleSettings.h" #include "GameFramework/Actor.h" #include "Settings/LevelEditorPlaySettings.h" #include "Editor/UnrealEdEngine.h" #include "Settings/EditorExperimentalSettings.h" #include "GameFramework/PlayerStart.h" #include "Components/CapsuleComponent.h" #include "LevelEditorViewport.h" #include "UnrealEdGlobals.h" #include "EditorAnalytics.h" #include "Kismet2/KismetEditorUtilities.h" #include "Kismet2/KismetDebugUtilities.h" #include "Interfaces/TargetDeviceId.h" #include "Interfaces/ITargetPlatform.h" #include "Interfaces/ITargetPlatformManagerModule.h" #include "ITargetDeviceProxy.h" #include "ITargetDeviceServicesModule.h" #include "ISettingsModule.h" #include "Interfaces/IMainFrameModule.h" #include "EngineAnalytics.h" #include "AnalyticsEventAttribute.h" #include "Interfaces/IAnalyticsProvider.h" #include "GameProjectGenerationModule.h" #include "Interfaces/IProjectTargetPlatformEditorModule.h" #include "PlatformInfo.h" #include "IHeadMountedDisplay.h" #include "IXRTrackingSystem.h" #include "Editor.h" //@TODO: Remove this dependency #include "EngineGlobals.h" #include "LevelEditor.h" #include "IAssetViewport.h" #include "Logging/TokenizedMessage.h" #include "Logging/MessageLog.h" #include "Interfaces/IProjectManager.h" #include "InstalledPlatformInfo.h" #include "PIEPreviewDeviceProfileSelectorModule.h" #include "IDesktopPlatform.h" #include "DesktopPlatformModule.h" #include "IAndroidDeviceDetectionModule.h" #include "IAndroidDeviceDetection.h" #include "CookerSettings.h" #include "HAL/PlatformFilemanager.h" #include "SourceControlHelpers.h" #include "ISourceControlModule.h" #include "Framework/Notifications/NotificationManager.h" #include "Widgets/Notifications/SNotificationList.h" #include "ToolMenus.h" #include "SBlueprintEditorToolbar.h" #include "SEnumCombobox.h" #define LOCTEXT_NAMESPACE "DebuggerCommands" void SGlobalPlayWorldActions::Construct(const FArguments& InArgs) { // Always keep track of the current active play world actions widget so we later set user focus on it FPlayWorldCommands::SetActiveGlobalPlayWorldActionsWidget(SharedThis(this)); ChildSlot [InArgs._Content.Widget]; } FReply SGlobalPlayWorldActions::OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent) { // Always keep track of the current active play world actions widget so we later set user focus on it FPlayWorldCommands::SetActiveGlobalPlayWorldActionsWidget(SharedThis(this)); if (FPlayWorldCommands::GlobalPlayWorldActions->ProcessCommandBindings(InKeyEvent)) { return FReply::Handled(); } else { FPlayWorldCommands::SetActiveGlobalPlayWorldActionsWidget(TSharedPtr()); return FReply::Unhandled(); } } bool SGlobalPlayWorldActions::SupportsKeyboardFocus() const { return true; } // Put internal callbacks that we don't need to expose here in order to avoid unnecessary build dependencies outside of this module class FInternalPlayWorldCommandCallbacks: public FPlayWorldCommandCallbacks { public: // Play In static void RepeatLastPlay_Clicked(); static bool RepeatLastPlay_CanExecute(); static FText GetRepeatLastPlayToolTip(); static FSlateIcon GetRepeatLastPlayIcon(); static void Simulate_Clicked(); static bool Simulate_CanExecute(); static bool Simulate_IsChecked(); static void PlayInViewport_Clicked(); static bool PlayInViewport_CanExecute(); static void PlayInEditorFloating_Clicked(); static bool PlayInEditorFloating_CanExecute(); static void PlayInNewProcess_Clicked(EPlayModeType PlayModeType); static bool PlayInNewProcess_CanExecute(); static void PlayInVR_Clicked(); static bool PlayInVR_CanExecute(); static bool PlayInModeIsChecked(EPlayModeType PlayMode); static void PlayInNewProcessPreviewDevice_Clicked(FString PIEPreviewDeviceName); static bool PlayInModeAndPreviewDeviceIsChecked(FString PIEPreviewDeviceName); static bool PlayInLocation_CanExecute(EPlayModeLocations Location); static void PlayInLocation_Clicked(EPlayModeLocations Location); static bool PlayInLocation_IsChecked(EPlayModeLocations Location); static void PlayInSettings_Clicked(); // Launch On static void HandleLaunchOnDeviceActionExecute(FString DevicedId, FString DeviceName); static bool HandleLaunchOnDeviceActionCanExecute(FString DeviceName); static bool HandleLaunchOnDeviceActionIsChecked(FString DeviceName); // No Device static void HandleNoDeviceFoundActionExecute() {} static bool HandleNoDeviceFoundActionCanExecute() { return false; } static void HandleShowSDKTutorial(FString PlatformName, FString NotInstalledDocLink); static void RepeatLastLaunch_Clicked(); static bool RepeatLastLaunch_CanExecute(); static FText GetRepeatLastLaunchToolTip(); static FSlateIcon GetRepeatLastLaunchIcon(); static void OpenProjectLauncher_Clicked(); static void OpenDeviceManager_Clicked(); static FSlateIcon GetResumePlaySessionImage(); static FText GetResumePlaySessionToolTip(); static void StopPlaySession_Clicked(); static void LateJoinSession_Clicked(); static void SingleFrameAdvance_Clicked(); static void ShowCurrentStatement_Clicked(); static void StepInto_Clicked(); static void StepOver_Clicked(); static void StepOut_Clicked(); static void TogglePlayPause_Clicked(); // Mouse control static void GetMouseControlExecute(); static void PossessEjectPlayer_Clicked(); static bool CanPossessEjectPlayer(); static FText GetPossessEjectLabel(); static FText GetPossessEjectTooltip(); static FSlateIcon GetPossessEjectImage(); static bool CanLateJoin(); static bool CanShowLateJoinButton(); static bool IsStoppedAtBreakpoint(); static bool CanShowNonPlayWorldOnlyActions(); static bool CanShowVulkanNonPlayWorldOnlyActions(); static bool CanShowVROnlyActions(); static int32 GetNumberOfClients(); static void SetNumberOfClients(int32 NumClients, ETextCommit::Type CommitInfo = ETextCommit::Default); static int32 GetNetPlayMode(); static void SetNetPlayMode(int32 Value, ESelectInfo::Type CommitInfo); protected: static void PlayInNewProcess(EPlayModeType PlayModeType, FString PIEPreviewDeviceName); /** * Adds a message to the message log. * * @param Text The main message text. * @param Detail The detailed description. * @param TutorialLink A link to an associated tutorial. * @param DocumentationLink A link to documentation. */ static void AddMessageLog(const FText& Text, const FText& Detail, const FString& TutorialLink, const FString& DocumentationLink); /** * Checks whether the specified platform has a default device that can be launched on. * * @param PlatformName - The name of the platform to check. * * @return true if the platform can be played on, false otherwise. */ static bool CanLaunchOnDevice(const FString& DeviceName); /** * Starts a game session on the default device of the specified platform. * * @param PlatformName - The name of the platform to play the game on. */ static void LaunchOnDevice(const FString& DeviceId, const FString& DeviceName); /** Get the player start location to use when starting PIE */ static EPlayModeLocations GetPlayModeLocation(); /** checks to see if we have everything needed to launch a build to device */ static bool IsReadyToLaunchOnDevice(FString DeviceId); }; /** * Called to leave K2 debugging mode */ static void LeaveDebuggingMode() { if (GUnrealEd->PlayWorld != NULL) { GUnrealEd->PlayWorld->bDebugPauseExecution = false; } // Determine whether or not we are resuming play. const bool bIsResumingPlay = !FKismetDebugUtilities::IsSingleStepping() && !GEditor->ShouldEndPlayMap(); if (FSlateApplication::Get().InKismetDebuggingMode() && bIsResumingPlay) { // Focus the game view port when resuming from debugging. FModuleManager::GetModuleChecked("LevelEditor").FocusPIEViewport(); } // Tell the application to stop ticking in this stack frame. The parameter controls whether or not to recapture the mouse to the game viewport. FSlateApplication::Get().LeaveDebuggingMode(!bIsResumingPlay); } ////////////////////////////////////////////////////////////////////////// // FPlayWorldCommands TSharedPtr FPlayWorldCommands::GlobalPlayWorldActions; TWeakPtr FPlayWorldCommands::ActiveGlobalPlayWorldActionsWidget; TWeakPtr FPlayWorldCommands::GetActiveGlobalPlayWorldActionsWidget() { return FPlayWorldCommands::ActiveGlobalPlayWorldActionsWidget; } void FPlayWorldCommands::SetActiveGlobalPlayWorldActionsWidget(TWeakPtr ActiveWidget) { FPlayWorldCommands::ActiveGlobalPlayWorldActionsWidget = ActiveWidget; } FPlayWorldCommands::FPlayWorldCommands() : TCommands("PlayWorld", LOCTEXT("PlayWorld", "Play World (PIE/SIE)"), "MainFrame", FEditorStyle::GetStyleSetName()) { ULevelEditorPlaySettings* PlaySettings = GetMutableDefault(); // initialize default Play device if (PlaySettings->LastExecutedLaunchName.IsEmpty()) { FString RunningPlatformName = GetTargetPlatformManagerRef().GetRunningTargetPlatform()->PlatformName(); FString PlayPlatformName; if (RunningPlatformName == TEXT("Windows")) { PlayPlatformName = TEXT("WindowsNoEditor"); } else if (RunningPlatformName == TEXT("Mac")) { PlayPlatformName = TEXT("MacNoEditor"); } else if (RunningPlatformName == TEXT("Linux")) { PlayPlatformName = TEXT("LinuxNoEditor"); } else if (RunningPlatformName == TEXT("LinuxAArch64")) { PlayPlatformName = TEXT("LinuxAArch64NoEditor"); } if (!PlayPlatformName.IsEmpty()) { ITargetPlatform* PlayPlatform = GetTargetPlatformManagerRef().FindTargetPlatform(PlayPlatformName); if (PlayPlatform != nullptr) { ITargetDevicePtr PlayDevice = PlayPlatform->GetDefaultDevice(); if (PlayDevice.IsValid()) { PlaySettings->LastExecutedLaunchDevice = PlayDevice->GetId().ToString(); PlaySettings->LastExecutedLaunchName = PlayDevice->GetName(); PlaySettings->SaveConfig(); } } } } } void FPlayWorldCommands::RegisterCommands() { // SIE UI_COMMAND(Simulate, "Simulate", "Start simulating the game", EUserInterfaceActionType::Check, FInputChord(EKeys::S, EModifierKey::Alt)); // PIE UI_COMMAND(RepeatLastPlay, "Play", "Launches a game preview session in the same mode as the last game preview session launched from the Game Preview Modes dropdown next to the Play button on the level editor toolbar", EUserInterfaceActionType::Button, FInputChord(EKeys::P, EModifierKey::Alt)) UI_COMMAND(PlayInViewport, "Selected Viewport", "Play this level in the active level editor viewport", EUserInterfaceActionType::Check, FInputChord()); UI_COMMAND(PlayInEditorFloating, "New Editor Window (PIE)", "Play this level in a new window", EUserInterfaceActionType::Check, FInputChord()); UI_COMMAND(PlayInVR, "VR Preview", "Play this level in VR", EUserInterfaceActionType::Check, FInputChord()); UI_COMMAND(PlayInMobilePreview, "Mobile Preview ES3.1 (PIE)", "Play this level as a mobile device preview in ES3.1 mode (runs in its own process)", EUserInterfaceActionType::Check, FInputChord()); UI_COMMAND(PlayInVulkanPreview, "Vulkan Mobile Preview (PIE)", "Play this level using mobile Vulkan rendering (runs in its own process)", EUserInterfaceActionType::Check, FInputChord()); UI_COMMAND(PlayInNewProcess, "Standalone Game", "Play this level in a new window that runs in its own process", EUserInterfaceActionType::Check, FInputChord()); UI_COMMAND(PlayInCameraLocation, "Current Camera Location", "Spawn the player at the current camera location", EUserInterfaceActionType::RadioButton, FInputChord()); UI_COMMAND(PlayInDefaultPlayerStart, "Default Player Start", "Spawn the player at the map's default player start", EUserInterfaceActionType::RadioButton, FInputChord()); UI_COMMAND(PlayInNetworkSettings, "Network Settings...", "Open the settings for the 'Play In' feature", EUserInterfaceActionType::Button, FInputChord()); UI_COMMAND(PlayInSettings, "Advanced Settings...", "Open the settings for the 'Play In' feature", EUserInterfaceActionType::Button, FInputChord()); // SIE & PIE controls UI_COMMAND(StopPlaySession, "Stop", "Stop simulation", EUserInterfaceActionType::Button, FInputChord()); UI_COMMAND(ResumePlaySession, "Resume", "Resume simulation", EUserInterfaceActionType::Button, FInputChord()); UI_COMMAND(PausePlaySession, "Pause", "Pause simulation", EUserInterfaceActionType::Button, FInputChord()); UI_COMMAND(GetMouseControl, "Mouse Control", "Get mouse cursor while in PIE", EUserInterfaceActionType::Button, FInputChord(EModifierKey::Shift, EKeys::F1)); UI_COMMAND(LateJoinSession, "Add Client", "Add another client", EUserInterfaceActionType::Button, FInputChord()); UI_COMMAND(SingleFrameAdvance, "Skip", "Advances a single frame", EUserInterfaceActionType::Button, FInputChord()); UI_COMMAND(TogglePlayPauseOfPlaySession, "Toggle Play/Pause", "Resume playing if paused, or pause if playing", EUserInterfaceActionType::Button, FInputChord(EKeys::Pause)); UI_COMMAND(PossessEjectPlayer, "Possess or Eject Player", "Possesses or ejects the player from the camera", EUserInterfaceActionType::Button, FInputChord(EKeys::F8)); UI_COMMAND(ShowCurrentStatement, "Locate", "Locate the currently active node", EUserInterfaceActionType::Button, FInputChord()); UI_COMMAND(StepInto, "Step Into", "Step Into the next node to be executed", EUserInterfaceActionType::Button, PLATFORM_MAC ? FInputChord(EModifierKey::Control, EKeys::F11) : FInputChord(EKeys::F11)); UI_COMMAND(StepOver, "Step Over", "Step to the next node to be executed in the current graph", EUserInterfaceActionType::Button, FInputChord(EKeys::F10)); UI_COMMAND(StepOut, "Step Out", "Step Out to the next node to be executed in the parent graph", EUserInterfaceActionType::Button, FInputChord(EModifierKey::Alt | EModifierKey::Shift, EKeys::F11)); // Launch UI_COMMAND(RepeatLastLaunch, "Launch", "Launches the game on the device as the last session launched from the dropdown next to the Play on Device button on the level editor toolbar", EUserInterfaceActionType::Button, FInputChord(EKeys::P, EModifierKey::Alt | EModifierKey::Shift)) UI_COMMAND(OpenProjectLauncher, "Project Launcher...", "Open the Project Launcher for advanced packaging, deploying and launching of your projects", EUserInterfaceActionType::Button, FInputChord()); UI_COMMAND(OpenDeviceManager, "Device Manager...", "View and manage connected devices.", EUserInterfaceActionType::Button, FInputChord()); // PIE mobile preview devices. AddPIEPreviewDeviceCommands(); } void FPlayWorldCommands::AddPIEPreviewDeviceCommands() { auto PIEPreviewDeviceModule = FModuleManager::LoadModulePtr(TEXT("PIEPreviewDeviceProfileSelector")); if (PIEPreviewDeviceModule) { TArray>& TargetedMobilePreviewDeviceCommands = PlayInTargetedMobilePreviewDevices; const TArray& Devices = PIEPreviewDeviceModule->GetPreviewDeviceContainer().GetDeviceSpecificationsLocalizedName(); PlayInTargetedMobilePreviewDevices.SetNum(Devices.Num()); for (int32 DeviceIndex = 0; DeviceIndex < Devices.Num(); DeviceIndex++) { FFormatNamedArguments Args; Args.Add(TEXT("Device"), FText::FromString(Devices[DeviceIndex])); const FText CommandLabel = FText::Format(LOCTEXT("DevicePreviewLaunchCommandLabel", "{Device}"), Args); const FText CommandDesc = FText::Format(LOCTEXT("DevicePreviewLaunchCommandDesc", "Launch on this computer using {Device}'s settings."), Args); FUICommandInfo::MakeCommandInfo( this->AsShared(), TargetedMobilePreviewDeviceCommands[DeviceIndex], FName(*CommandLabel.ToString()), CommandLabel, CommandDesc, FSlateIcon(FEditorStyle::GetStyleSetName(), "PlayWorld.PlayInMobilePreview"), EUserInterfaceActionType::Check, FInputChord()); } } } void FPlayWorldCommands::BindGlobalPlayWorldCommands() { check(!GlobalPlayWorldActions.IsValid()); GlobalPlayWorldActions = MakeShareable(new FUICommandList); const FPlayWorldCommands& Commands = FPlayWorldCommands::Get(); FUICommandList& ActionList = *GlobalPlayWorldActions; // SIE ActionList.MapAction(Commands.Simulate, FExecuteAction::CreateStatic(&FInternalPlayWorldCommandCallbacks::Simulate_Clicked), FCanExecuteAction::CreateStatic(&FInternalPlayWorldCommandCallbacks::Simulate_CanExecute), FIsActionChecked::CreateStatic(&FInternalPlayWorldCommandCallbacks::PlayInModeIsChecked, PlayMode_Simulate), FIsActionButtonVisible::CreateStatic(&FInternalPlayWorldCommandCallbacks::CanShowNonPlayWorldOnlyActions)); // PIE ActionList.MapAction(Commands.RepeatLastPlay, FExecuteAction::CreateStatic(&FInternalPlayWorldCommandCallbacks::RepeatLastPlay_Clicked), FCanExecuteAction::CreateStatic(&FInternalPlayWorldCommandCallbacks::RepeatLastPlay_CanExecute), FIsActionChecked(), FIsActionButtonVisible::CreateStatic(&FInternalPlayWorldCommandCallbacks::CanShowNonPlayWorldOnlyActions)); ActionList.MapAction(Commands.PlayInViewport, FExecuteAction::CreateStatic(&FInternalPlayWorldCommandCallbacks::PlayInViewport_Clicked), FCanExecuteAction::CreateStatic(&FInternalPlayWorldCommandCallbacks::PlayInViewport_CanExecute), FIsActionChecked::CreateStatic(&FInternalPlayWorldCommandCallbacks::PlayInModeIsChecked, PlayMode_InViewPort), FIsActionButtonVisible::CreateStatic(&FInternalPlayWorldCommandCallbacks::CanShowNonPlayWorldOnlyActions)); ActionList.MapAction(Commands.PlayInEditorFloating, FExecuteAction::CreateStatic(&FInternalPlayWorldCommandCallbacks::PlayInEditorFloating_Clicked), FCanExecuteAction::CreateStatic(&FInternalPlayWorldCommandCallbacks::PlayInEditorFloating_CanExecute), FIsActionChecked::CreateStatic(&FInternalPlayWorldCommandCallbacks::PlayInModeIsChecked, PlayMode_InEditorFloating), FIsActionButtonVisible::CreateStatic(&FInternalPlayWorldCommandCallbacks::CanShowNonPlayWorldOnlyActions)); ActionList.MapAction(Commands.PlayInVR, FExecuteAction::CreateStatic(&FInternalPlayWorldCommandCallbacks::PlayInVR_Clicked), FCanExecuteAction::CreateStatic(&FInternalPlayWorldCommandCallbacks::PlayInVR_CanExecute), FIsActionChecked::CreateStatic(&FInternalPlayWorldCommandCallbacks::PlayInModeIsChecked, PlayMode_InVR), FIsActionButtonVisible::CreateStatic(&FInternalPlayWorldCommandCallbacks::CanShowVROnlyActions)); ActionList.MapAction(Commands.PlayInMobilePreview, FExecuteAction::CreateStatic(&FInternalPlayWorldCommandCallbacks::PlayInNewProcess_Clicked, PlayMode_InMobilePreview), FCanExecuteAction::CreateStatic(&FInternalPlayWorldCommandCallbacks::PlayInNewProcess_CanExecute), FIsActionChecked::CreateStatic(&FInternalPlayWorldCommandCallbacks::PlayInModeIsChecked, PlayMode_InMobilePreview), FIsActionButtonVisible::CreateStatic(&FInternalPlayWorldCommandCallbacks::CanShowNonPlayWorldOnlyActions)); ActionList.MapAction(Commands.PlayInVulkanPreview, FExecuteAction::CreateStatic(&FInternalPlayWorldCommandCallbacks::PlayInNewProcess_Clicked, PlayMode_InVulkanPreview), FCanExecuteAction::CreateStatic(&FInternalPlayWorldCommandCallbacks::PlayInNewProcess_CanExecute), FIsActionChecked::CreateStatic(&FInternalPlayWorldCommandCallbacks::PlayInModeIsChecked, PlayMode_InVulkanPreview), FIsActionButtonVisible::CreateStatic(&FInternalPlayWorldCommandCallbacks::CanShowVulkanNonPlayWorldOnlyActions)); ActionList.MapAction(Commands.PlayInNewProcess, FExecuteAction::CreateStatic(&FInternalPlayWorldCommandCallbacks::PlayInNewProcess_Clicked, PlayMode_InNewProcess), FCanExecuteAction::CreateStatic(&FInternalPlayWorldCommandCallbacks::PlayInNewProcess_CanExecute), FIsActionChecked::CreateStatic(&FInternalPlayWorldCommandCallbacks::PlayInModeIsChecked, PlayMode_InNewProcess), FIsActionButtonVisible::CreateStatic(&FInternalPlayWorldCommandCallbacks::CanShowNonPlayWorldOnlyActions)); ActionList.MapAction(Commands.PlayInCameraLocation, FExecuteAction::CreateStatic(&FInternalPlayWorldCommandCallbacks::PlayInLocation_Clicked, PlayLocation_CurrentCameraLocation), FCanExecuteAction::CreateStatic(&FInternalPlayWorldCommandCallbacks::PlayInLocation_CanExecute, PlayLocation_CurrentCameraLocation), FIsActionChecked::CreateStatic(&FInternalPlayWorldCommandCallbacks::PlayInLocation_IsChecked, PlayLocation_CurrentCameraLocation), FIsActionButtonVisible::CreateStatic(&FInternalPlayWorldCommandCallbacks::CanShowNonPlayWorldOnlyActions)); ActionList.MapAction(Commands.PlayInDefaultPlayerStart, FExecuteAction::CreateStatic(&FInternalPlayWorldCommandCallbacks::PlayInLocation_Clicked, PlayLocation_DefaultPlayerStart), FCanExecuteAction::CreateStatic(&FInternalPlayWorldCommandCallbacks::PlayInLocation_CanExecute, PlayLocation_DefaultPlayerStart), FIsActionChecked::CreateStatic(&FInternalPlayWorldCommandCallbacks::PlayInLocation_IsChecked, PlayLocation_DefaultPlayerStart), FIsActionButtonVisible::CreateStatic(&FInternalPlayWorldCommandCallbacks::CanShowNonPlayWorldOnlyActions)); ActionList.MapAction(Commands.PlayInSettings, FExecuteAction::CreateStatic(&FInternalPlayWorldCommandCallbacks::PlayInSettings_Clicked)); // Launch ActionList.MapAction(Commands.OpenProjectLauncher, FExecuteAction::CreateStatic(&FInternalPlayWorldCommandCallbacks::OpenProjectLauncher_Clicked)); ActionList.MapAction(Commands.OpenDeviceManager, FExecuteAction::CreateStatic(&FInternalPlayWorldCommandCallbacks::OpenDeviceManager_Clicked)); ActionList.MapAction(Commands.RepeatLastLaunch, FExecuteAction::CreateStatic(&FInternalPlayWorldCommandCallbacks::RepeatLastLaunch_Clicked), FCanExecuteAction::CreateStatic(&FInternalPlayWorldCommandCallbacks::RepeatLastLaunch_CanExecute), FIsActionChecked(), FIsActionButtonVisible::CreateStatic(&FInternalPlayWorldCommandCallbacks::CanShowNonPlayWorldOnlyActions)); // Stop play session ActionList.MapAction(Commands.StopPlaySession, FExecuteAction::CreateStatic(&FInternalPlayWorldCommandCallbacks::StopPlaySession_Clicked), FCanExecuteAction::CreateStatic(&FInternalPlayWorldCommandCallbacks::HasPlayWorld), FIsActionChecked(), FIsActionButtonVisible::CreateStatic(&FInternalPlayWorldCommandCallbacks::HasPlayWorld)); // Late join session ActionList.MapAction(Commands.LateJoinSession, FExecuteAction::CreateStatic(&FInternalPlayWorldCommandCallbacks::LateJoinSession_Clicked), FCanExecuteAction::CreateStatic(&FInternalPlayWorldCommandCallbacks::CanLateJoin), FIsActionChecked(), FIsActionButtonVisible::CreateStatic(&FInternalPlayWorldCommandCallbacks::CanShowLateJoinButton)); // Play, Pause, Toggle between play and pause ActionList.MapAction(Commands.ResumePlaySession, FExecuteAction::CreateStatic(&FPlayWorldCommandCallbacks::ResumePlaySession_Clicked), FCanExecuteAction::CreateStatic(&FPlayWorldCommandCallbacks::HasPlayWorldAndPaused), FIsActionChecked(), FIsActionButtonVisible::CreateStatic(&FPlayWorldCommandCallbacks::HasPlayWorldAndPaused)); ActionList.MapAction(Commands.PausePlaySession, FExecuteAction::CreateStatic(&FPlayWorldCommandCallbacks::PausePlaySession_Clicked), FCanExecuteAction::CreateStatic(&FPlayWorldCommandCallbacks::HasPlayWorldAndRunning), FIsActionChecked(), FIsActionButtonVisible::CreateStatic(&FPlayWorldCommandCallbacks::HasPlayWorldAndRunning)); ActionList.MapAction(Commands.SingleFrameAdvance, FExecuteAction::CreateStatic(&FInternalPlayWorldCommandCallbacks::SingleFrameAdvance_Clicked), FCanExecuteAction::CreateStatic(&FPlayWorldCommandCallbacks::HasPlayWorldAndPaused), FIsActionChecked(), FIsActionChecked::CreateStatic(&FPlayWorldCommandCallbacks::HasPlayWorldAndPaused)); ActionList.MapAction(Commands.TogglePlayPauseOfPlaySession, FExecuteAction::CreateStatic(&FInternalPlayWorldCommandCallbacks::TogglePlayPause_Clicked), FCanExecuteAction::CreateStatic(&FInternalPlayWorldCommandCallbacks::HasPlayWorld), FIsActionChecked(), FIsActionButtonVisible::CreateStatic(&FInternalPlayWorldCommandCallbacks::HasPlayWorld)); // Get mouse control from PIE ActionList.MapAction(Commands.GetMouseControl, FExecuteAction::CreateStatic(&FInternalPlayWorldCommandCallbacks::GetMouseControlExecute), FCanExecuteAction::CreateStatic(&FInternalPlayWorldCommandCallbacks::HasPlayWorld), FIsActionChecked(), FIsActionChecked::CreateStatic(&FInternalPlayWorldCommandCallbacks::HasPlayWorld)); // Toggle PIE/SIE, Eject (PIE->SIE), and Possess (SIE->PIE) ActionList.MapAction(Commands.PossessEjectPlayer, FExecuteAction::CreateStatic(&FInternalPlayWorldCommandCallbacks::PossessEjectPlayer_Clicked), FCanExecuteAction::CreateStatic(&FInternalPlayWorldCommandCallbacks::CanPossessEjectPlayer), FIsActionChecked(), FIsActionButtonVisible::CreateStatic(&FInternalPlayWorldCommandCallbacks::CanPossessEjectPlayer)); // Breakpoint-only commands ActionList.MapAction(Commands.ShowCurrentStatement, FExecuteAction::CreateStatic(&FInternalPlayWorldCommandCallbacks::ShowCurrentStatement_Clicked), FCanExecuteAction::CreateStatic(&FInternalPlayWorldCommandCallbacks::IsStoppedAtBreakpoint), FIsActionChecked(), FIsActionChecked::CreateStatic(&FInternalPlayWorldCommandCallbacks::IsStoppedAtBreakpoint)); ActionList.MapAction(Commands.StepInto, FExecuteAction::CreateStatic(&FInternalPlayWorldCommandCallbacks::StepInto_Clicked), FCanExecuteAction::CreateStatic(&FInternalPlayWorldCommandCallbacks::IsStoppedAtBreakpoint), FIsActionChecked(), FIsActionChecked::CreateStatic(&FInternalPlayWorldCommandCallbacks::IsStoppedAtBreakpoint)); ActionList.MapAction(Commands.StepOver, FExecuteAction::CreateStatic(&FInternalPlayWorldCommandCallbacks::StepOver_Clicked), FCanExecuteAction::CreateStatic(&FInternalPlayWorldCommandCallbacks::IsStoppedAtBreakpoint), FIsActionChecked(), FIsActionChecked::CreateStatic(&FInternalPlayWorldCommandCallbacks::IsStoppedAtBreakpoint)); ActionList.MapAction(Commands.StepOut, FExecuteAction::CreateStatic(&FInternalPlayWorldCommandCallbacks::StepOut_Clicked), FCanExecuteAction::CreateStatic(&FInternalPlayWorldCommandCallbacks::IsStoppedAtBreakpoint), FIsActionChecked(), FIsActionChecked::CreateStatic(&FInternalPlayWorldCommandCallbacks::IsStoppedAtBreakpoint)); AddPIEPreviewDeviceActions(Commands, ActionList); } void FPlayWorldCommands::AddPIEPreviewDeviceActions(const FPlayWorldCommands& Commands, FUICommandList& ActionList) { // PIE preview devices. auto PIEPreviewDeviceModule = FModuleManager::LoadModulePtr(TEXT("PIEPreviewDeviceProfileSelector")); if (PIEPreviewDeviceModule) { const TArray>& TargetedMobilePreviewDeviceCommands = Commands.PlayInTargetedMobilePreviewDevices; const TArray& Devices = PIEPreviewDeviceModule->GetPreviewDeviceContainer().GetDeviceSpecifications(); for (int32 DeviceIndex = 0; DeviceIndex < Devices.Num(); DeviceIndex++) { ActionList.MapAction(TargetedMobilePreviewDeviceCommands[DeviceIndex], FExecuteAction::CreateStatic(&FInternalPlayWorldCommandCallbacks::PlayInNewProcessPreviewDevice_Clicked, Devices[DeviceIndex]), FCanExecuteAction::CreateStatic(&FInternalPlayWorldCommandCallbacks::PlayInNewProcess_CanExecute), FIsActionChecked::CreateStatic(&FInternalPlayWorldCommandCallbacks::PlayInModeAndPreviewDeviceIsChecked, Devices[DeviceIndex]), FIsActionButtonVisible::CreateStatic(&FInternalPlayWorldCommandCallbacks::CanShowNonPlayWorldOnlyActions)); } } } void FPlayWorldCommands::BuildToolbar(FToolMenuSection& InSection, bool bIncludeLaunchButtonAndOptions) { // Play InSection.AddEntry(FToolMenuEntry::InitToolBarButton( FPlayWorldCommands::Get().RepeatLastPlay, LOCTEXT("RepeatLastPlay", "Play"), TAttribute::Create(TAttribute::FGetter::CreateStatic(&FInternalPlayWorldCommandCallbacks::GetRepeatLastPlayToolTip)), TAttribute::Create(TAttribute::FGetter::CreateStatic(&FInternalPlayWorldCommandCallbacks::GetRepeatLastPlayIcon)), FName(TEXT("LevelToolbarPlay")))); // Play combo box FUIAction SpecialPIEOptionsMenuAction; SpecialPIEOptionsMenuAction.IsActionVisibleDelegate = FIsActionButtonVisible::CreateStatic(&FInternalPlayWorldCommandCallbacks::CanShowNonPlayWorldOnlyActions); InSection.AddEntry(FToolMenuEntry::InitComboButton( "PlayCombo", SpecialPIEOptionsMenuAction, FOnGetContent::CreateStatic(&GeneratePlayMenuContent, GlobalPlayWorldActions.ToSharedRef()), LOCTEXT("PlayCombo_Label", "Active Play Mode"), LOCTEXT("PIEComboToolTip", "Change Play Mode and Play Settings"), FSlateIcon(FEditorStyle::GetStyleSetName(), "LevelEditor.RepeatLastPlay"), true)); if (bIncludeLaunchButtonAndOptions) { InSection.AddDynamicEntry("LaunchButtons", FNewToolMenuSectionDelegate::CreateLambda([](FToolMenuSection& InDynamicSection) { if (GetDefault()->bShowLaunchMenus) { // Launch InDynamicSection.AddEntry(FToolMenuEntry::InitToolBarButton( FPlayWorldCommands::Get().RepeatLastLaunch, LOCTEXT("RepeatLastLaunch", "Launch"), TAttribute::Create(TAttribute::FGetter::CreateStatic(&FInternalPlayWorldCommandCallbacks::GetRepeatLastLaunchToolTip)), TAttribute::Create(TAttribute::FGetter::CreateStatic(&FInternalPlayWorldCommandCallbacks::GetRepeatLastLaunchIcon)), FName(TEXT("RepeatLastLaunch")))); // Launch combo box FUIAction LaunchMenuAction; LaunchMenuAction.IsActionVisibleDelegate = FIsActionButtonVisible::CreateStatic(&FInternalPlayWorldCommandCallbacks::CanShowNonPlayWorldOnlyActions); InDynamicSection.AddEntry(FToolMenuEntry::InitComboButton( "LaunchCombo", LaunchMenuAction, FOnGetContent::CreateStatic(&GenerateLaunchMenuContent, GlobalPlayWorldActions.ToSharedRef()), LOCTEXT("LaunchCombo_Label", "Launch Options"), LOCTEXT("PODComboToolTip", "Options for launching on a device"), FSlateIcon(FEditorStyle::GetStyleSetName(), "LevelEditor.RepeatLastLaunch"), true)); } })); } // Resume/pause toggle (only one will be visible, and only in PIE/SIE) InSection.AddEntry(FToolMenuEntry::InitToolBarButton(FPlayWorldCommands::Get().ResumePlaySession, TAttribute(), TAttribute::Create(TAttribute::FGetter::CreateStatic(&FInternalPlayWorldCommandCallbacks::GetResumePlaySessionToolTip)), TAttribute::Create(TAttribute::FGetter::CreateStatic(&FInternalPlayWorldCommandCallbacks::GetResumePlaySessionImage)), FName(TEXT("ResumePlaySession")))); InSection.AddEntry(FToolMenuEntry::InitToolBarButton(FPlayWorldCommands::Get().PausePlaySession, TAttribute(), TAttribute(), TAttribute(), FName(TEXT("PausePlaySession")))); InSection.AddEntry(FToolMenuEntry::InitToolBarButton(FPlayWorldCommands::Get().SingleFrameAdvance, TAttribute(), TAttribute(), TAttribute(), FName(TEXT("SingleFrameAdvance")))); // Stop InSection.AddEntry(FToolMenuEntry::InitToolBarButton(FPlayWorldCommands::Get().StopPlaySession, TAttribute(), TAttribute(), TAttribute(), FName(TEXT("StopPlaySession")))); // Late Join InSection.AddEntry(FToolMenuEntry::InitToolBarButton(FPlayWorldCommands::Get().LateJoinSession, TAttribute(), TAttribute(), TAttribute(), FName(TEXT("LateJoinSession")))); // Eject/possess toggle InSection.AddEntry(FToolMenuEntry::InitToolBarButton(FPlayWorldCommands::Get().PossessEjectPlayer, TAttribute::Create(TAttribute::FGetter::CreateStatic(&FInternalPlayWorldCommandCallbacks::GetPossessEjectLabel)), TAttribute::Create(TAttribute::FGetter::CreateStatic(&FInternalPlayWorldCommandCallbacks::GetPossessEjectTooltip)), TAttribute::Create(TAttribute::FGetter::CreateStatic(&FInternalPlayWorldCommandCallbacks::GetPossessEjectImage)), FName(TEXT("PossessEjectPlayer")))); // Single-stepping only buttons InSection.AddEntry(FToolMenuEntry::InitToolBarButton(FPlayWorldCommands::Get().ShowCurrentStatement, TAttribute(), TAttribute(), TAttribute(), FName(TEXT("ShowCurrentStatement")))); InSection.AddEntry(FToolMenuEntry::InitToolBarButton(FPlayWorldCommands::Get().StepInto, TAttribute(), TAttribute(), TAttribute(), FName(TEXT("StepInto")))); InSection.AddEntry(FToolMenuEntry::InitToolBarButton(FPlayWorldCommands::Get().StepOver, TAttribute(), TAttribute(), TAttribute(), FName(TEXT("StepOver")))); InSection.AddEntry(FToolMenuEntry::InitToolBarButton(FPlayWorldCommands::Get().StepOut, TAttribute(), TAttribute(), TAttribute(), FName(TEXT("StepOut")))); } // function will enumerate available Android devices that can export their profile to a json file // called (below) from AddAndroidConfigExportMenu() static void AddAndroidConfigExportSubMenus(FMenuBuilder& InMenuBuilder) { IAndroidDeviceDetection* DeviceDetection = FModuleManager::LoadModuleChecked("AndroidDeviceDetection").GetAndroidDeviceDetection(); TMap AndroidDeviceMap; // lock device map and copy its contents { FCriticalSection* DeviceLock = DeviceDetection->GetDeviceMapLock(); FScopeLock Lock(DeviceLock); AndroidDeviceMap = DeviceDetection->GetDeviceMap(); } for (auto& Pair: AndroidDeviceMap) { FAndroidDeviceInfo& DeviceInfo = Pair.Value; FString ModelName = DeviceInfo.Model + TEXT("[") + DeviceInfo.DeviceBrand + TEXT("]"); // lambda function called to open the save dialog and trigger device export auto LambdaSaveConfigFile = [DeviceName = Pair.Key, DefaultFileName = ModelName, DeviceDetection]() { TArray OutputFileName; FString DefaultFolder = FPaths::EngineContentDir() + TEXT("Editor/PIEPreviewDeviceSpecs/Android/"); bool bResult = FDesktopPlatformModule::Get()->SaveFileDialog( FSlateApplication::Get().FindBestParentWindowHandleForDialogs(nullptr), LOCTEXT("PackagePluginDialogTitle", "Save platform configuration...").ToString(), DefaultFolder, DefaultFileName, TEXT("Json config file (*.json)|*.json"), 0, OutputFileName); if (bResult && OutputFileName.Num()) { DeviceDetection->ExportDeviceProfile(OutputFileName[0], DeviceName); } }; InMenuBuilder.AddMenuEntry( FText::FromString(ModelName), FText(), FSlateIcon(FEditorStyle::GetStyleSetName(), "AssetEditor.SaveAsset"), FUIAction(FExecuteAction::CreateLambda(LambdaSaveConfigFile))); } } // function adds a sub-menu that will enumerate Android devices whose profiles can be exported json files static void AddAndroidConfigExportMenu(FMenuBuilder& InMenuBuilder) { InMenuBuilder.AddMenuSeparator(); InMenuBuilder.AddSubMenu( LOCTEXT("loc_AddAndroidConfigExportMenu", "Export device settings"), LOCTEXT("loc_tip_AddAndroidConfigExportMenu", "Export device settings to a Json file."), FNewMenuDelegate::CreateStatic(&AddAndroidConfigExportSubMenus), false, FSlateIcon(FEditorStyle::GetStyleSetName(), "MainFrame.SaveAll")); } static void MakePreviewDeviceMenu(FMenuBuilder& MenuBuilder) { struct FLocal { static void AddDevicePreviewSubCategories(FMenuBuilder& MenuBuilderIn, TSharedPtr PreviewDeviceCategory) { const TArray>& TargetedMobilePreviewDeviceCommands = FPlayWorldCommands::Get().PlayInTargetedMobilePreviewDevices; int32 StartIndex = PreviewDeviceCategory->GetDeviceStartIndex(); int32 EndIndex = StartIndex + PreviewDeviceCategory->GetDeviceCount(); for (int32 Device = StartIndex; Device < EndIndex; Device++) { MenuBuilderIn.AddMenuEntry(TargetedMobilePreviewDeviceCommands[Device]); } static FText AndroidCategory = FText::FromString(TEXT("Android")); static FText IOSCategory = FText::FromString(TEXT("IOS")); // Android devices can export their profile to a json file which then can be used for PIE device simulations const FText& CategoryDisplayName = PreviewDeviceCategory->GetCategoryDisplayName(); if (CategoryDisplayName.CompareToCaseIgnored(AndroidCategory) == 0) { // check to see if we have any connected devices bool bHasAndroidDevices = false; { IAndroidDeviceDetection* DeviceDetection = FModuleManager::LoadModuleChecked("AndroidDeviceDetection").GetAndroidDeviceDetection(); FCriticalSection* DeviceLock = DeviceDetection->GetDeviceMapLock(); FScopeLock Lock(DeviceLock); bHasAndroidDevices = DeviceDetection->GetDeviceMap().Num() > 0; } // add the config. export menu if (bHasAndroidDevices) { AddAndroidConfigExportMenu(MenuBuilderIn); } } for (TSharedPtr SubCategory: PreviewDeviceCategory->GetSubCategories()) { MenuBuilderIn.AddSubMenu( SubCategory->GetCategoryDisplayName(), SubCategory->GetCategoryToolTip(), FNewMenuDelegate::CreateStatic(&FLocal::AddDevicePreviewSubCategories, SubCategory)); } } }; const TArray>& TargetedMobilePreviewDeviceCommands = FPlayWorldCommands::Get().PlayInTargetedMobilePreviewDevices; auto PIEPreviewDeviceModule = FModuleManager::LoadModulePtr(TEXT("PIEPreviewDeviceProfileSelector")); if (PIEPreviewDeviceModule) { const FPIEPreviewDeviceContainer& DeviceContainer = PIEPreviewDeviceModule->GetPreviewDeviceContainer(); MenuBuilder.BeginSection("LevelEditorPlayModesPreviewDevice", LOCTEXT("PreviewDevicePlayButtonModesSection", "Preview Devices")); FLocal::AddDevicePreviewSubCategories(MenuBuilder, DeviceContainer.GetRootCategory()); MenuBuilder.EndSection(); } } TSharedRef FPlayWorldCommands::GeneratePlayMenuContent(TSharedRef InCommandList) { static const FName MenuName("UnrealEd.PlayWorldCommands.PlayMenu"); if (!UToolMenus::Get()->IsMenuRegistered(MenuName)) { UToolMenu* Menu = UToolMenus::Get()->RegisterMenu(MenuName); struct FLocal { static void AddPlayModeMenuEntry(FToolMenuSection& Section, EPlayModeType PlayMode) { TSharedPtr PlayModeCommand; switch (PlayMode) { case PlayMode_InEditorFloating: PlayModeCommand = FPlayWorldCommands::Get().PlayInEditorFloating; break; case PlayMode_InMobilePreview: PlayModeCommand = FPlayWorldCommands::Get().PlayInMobilePreview; break; case PlayMode_InVulkanPreview: PlayModeCommand = FPlayWorldCommands::Get().PlayInVulkanPreview; break; case PlayMode_InNewProcess: PlayModeCommand = FPlayWorldCommands::Get().PlayInNewProcess; break; case PlayMode_InViewPort: PlayModeCommand = FPlayWorldCommands::Get().PlayInViewport; break; case PlayMode_InVR: PlayModeCommand = FPlayWorldCommands::Get().PlayInVR; break; case PlayMode_Simulate: PlayModeCommand = FPlayWorldCommands::Get().Simulate; break; } if (PlayModeCommand.IsValid()) { Section.AddMenuEntry(PlayModeCommand); } } }; // play in view port { FToolMenuSection& Section = Menu->AddSection("LevelEditorPlayModes", LOCTEXT("PlayButtonModesSection", "Modes")); FLocal::AddPlayModeMenuEntry(Section, PlayMode_InViewPort); FLocal::AddPlayModeMenuEntry(Section, PlayMode_InMobilePreview); if (GetDefault()->bMobilePIEPreviewDeviceLaunch) { Section.AddSubMenu( "TargetedMobilePreview", LOCTEXT("TargetedMobilePreviewSubMenu", "Mobile Preview (PIE)"), LOCTEXT("TargetedMobilePreviewSubMenu_ToolTip", "Play this level using a specified mobile device preview (runs in its own process)"), FNewMenuDelegate::CreateStatic(&MakePreviewDeviceMenu), false, FSlateIcon(FEditorStyle::GetStyleSetName(), "PlayWorld.PlayInMobilePreview")); } FLocal::AddPlayModeMenuEntry(Section, PlayMode_InVulkanPreview); FLocal::AddPlayModeMenuEntry(Section, PlayMode_InEditorFloating); FLocal::AddPlayModeMenuEntry(Section, PlayMode_InVR); FLocal::AddPlayModeMenuEntry(Section, PlayMode_InNewProcess); FLocal::AddPlayModeMenuEntry(Section, PlayMode_Simulate); } // tip section { FToolMenuSection& Section = Menu->AddSection("LevelEditorPlayTip"); Section.AddEntry(FToolMenuEntry::InitWidget( "PlayIn", SNew(STextBlock) .ColorAndOpacity(FSlateColor::UseSubduedForeground()) .Text(LOCTEXT("PlayInTip", "Launching a game preview with a different mode will change your default 'Play' mode in the toolbar")) .WrapTextAt(250), FText::GetEmpty())); } // player start selection { FToolMenuSection& Section = Menu->AddSection("LevelEditorPlayPlayerStart", LOCTEXT("PlayButtonLocationSection", "Spawn player at...")); Section.AddMenuEntry(FPlayWorldCommands::Get().PlayInCameraLocation); Section.AddMenuEntry(FPlayWorldCommands::Get().PlayInDefaultPlayerStart); } // Basic network options const ULevelEditorPlaySettings* PlayInSettings = GetDefault(); { FToolMenuSection& Section = Menu->AddSection("LevelEditorPlayInWindowNetwork", LOCTEXT("LevelEditorPlayInWindowNetworkSection", "Multiplayer Options")); // Num Clients { TSharedRef NumPlayers = SNew(SSpinBox) // Copy limits from PlayNumberOfClients meta data .MinValue(1) .MaxValue(64) .MinSliderValue(1) .MaxSliderValue(4) .Delta(1) .ToolTipText(LOCTEXT("NumberOfClientsToolTip", "How many client instances do you want to create? The first instance respects the Play Mode location (PIE/PINW) and additional instances respect the RunUnderOneProcess setting.")) .Value_Static(&FInternalPlayWorldCommandCallbacks::GetNumberOfClients) .OnValueCommitted_Static(&FInternalPlayWorldCommandCallbacks::SetNumberOfClients) .OnValueChanged_Lambda([](int32 InNumClients) { FInternalPlayWorldCommandCallbacks::SetNumberOfClients(InNumClients, ETextCommit::Default); }); Section.AddEntry(FToolMenuEntry::InitWidget("NumPlayers", NumPlayers, LOCTEXT("NumberOfClientsMenuWidget", "Number of Players"))); } // Net Mode { const UEnum* PlayNetModeEnum = FindObject(ANY_PACKAGE, TEXT("EPlayNetMode")); TSharedRef NetMode = SNew(SEnumComboBox, PlayNetModeEnum) .CurrentValue(TAttribute::Create(TAttribute::FGetter::CreateStatic(&FInternalPlayWorldCommandCallbacks::GetNetPlayMode))) .ButtonStyle(FEditorStyle::Get(), "FlatButton.Light") .ContentPadding(FMargin(2, 0)) .Font(FEditorStyle::GetFontStyle("Sequencer.AnimationOutliner.RegularFont")) .OnEnumSelectionChanged(SEnumComboBox::FOnEnumSelectionChanged::CreateStatic(&FInternalPlayWorldCommandCallbacks::SetNetPlayMode)) .ToolTipText(LOCTEXT("NetworkModeToolTip", "Which network mode should the clients launch in? A server will automatically be started if needed.")); Section.AddEntry(FToolMenuEntry::InitWidget("NetMode", NetMode, LOCTEXT("NetworkModeMenuWidget", "Net Mode"))); } } // settings { FToolMenuSection& Section = Menu->AddSection("LevelEditorPlaySettings"); Section.AddMenuEntry(FPlayWorldCommands::Get().PlayInSettings); } } // Get all menu extenders for this context menu from the level editor module FLevelEditorModule& LevelEditorModule = FModuleManager::GetModuleChecked(TEXT("LevelEditor")); TSharedPtr MenuExtender = LevelEditorModule.AssembleExtenders(InCommandList, LevelEditorModule.GetAllLevelEditorToolbarPlayMenuExtenders()); FToolMenuContext MenuContext(InCommandList, MenuExtender); return UToolMenus::Get()->GenerateWidget(MenuName, MenuContext); } /* * Create an All__devices_on_ submenu * can be extended to any othe All aggregate proxy */ static void MakeAllDevicesSubMenu(FMenuBuilder& InMenuBuilder, const PlatformInfo::FPlatformInfo* InPlatformInfo, const TSharedPtr DeviceProxy) { ITargetDeviceServicesModule* TargetDeviceServicesModule = static_cast(FModuleManager::Get().LoadModule(TEXT("TargetDeviceServices"))); IProjectTargetPlatformEditorModule& ProjectTargetPlatformEditorModule = FModuleManager::LoadModuleChecked("ProjectTargetPlatformEditor"); TArray PlatformVariants; DeviceProxy->GetVariants(PlatformVariants); for (auto It = PlatformVariants.CreateIterator(); It; ++It) { FName Variant = *It; // for an aggregate (All__devices_on_) proxy, allow only the "Android_" variants const PlatformInfo::FPlatformInfo* platformInfo = PlatformInfo::FindPlatformInfo(Variant); if (DeviceProxy->IsAggregated() && platformInfo != NULL && (Variant == platformInfo->VanillaPlatformName || platformInfo->PlatformType != EBuildTargetType::Game)) { continue; } FString DeviceListStr; bool bVariantHasDevices = false; const TSet& TargetDeviceIds = DeviceProxy->GetTargetDeviceIds(Variant); for (TSet::TConstIterator ItDeviceId(TargetDeviceIds); ItDeviceId; ++ItDeviceId) { TSharedPtr PhysicalDeviceProxy = TargetDeviceServicesModule->GetDeviceProxyManager()->FindProxyDeviceForTargetDevice(*ItDeviceId); if (PhysicalDeviceProxy.IsValid()) { DeviceListStr.AppendChar('\n'); DeviceListStr.Append(*PhysicalDeviceProxy->GetName()); bVariantHasDevices = true; } } if (!bVariantHasDevices) { continue; } FString PlatformVariantStr = Variant.ToString(); FString PlatformId = PlatformVariantStr + TEXT("@") + PlatformVariantStr; // create an action FUIAction LaunchDeviceAction( FExecuteAction::CreateStatic(&FInternalPlayWorldCommandCallbacks::HandleLaunchOnDeviceActionExecute, PlatformId, PlatformVariantStr), FCanExecuteAction::CreateStatic(&FInternalPlayWorldCommandCallbacks::HandleLaunchOnDeviceActionCanExecute, PlatformVariantStr), FIsActionChecked::CreateStatic(&FInternalPlayWorldCommandCallbacks::HandleLaunchOnDeviceActionIsChecked, PlatformVariantStr)); // generate display label FText Label = FText::FromString(PlatformVariantStr); // generate tooltip text with the devices' list FFormatNamedArguments TooltipArguments; TooltipArguments.Add(TEXT("DeviceList"), FText::FromString(DeviceListStr)); FText Tooltip = FText::Format(LOCTEXT("LaunchDeviceToolTipText_LaunchOn", "Launch the game on:\n {DeviceList}"), TooltipArguments); // add a submenu entry InMenuBuilder.AddMenuEntry( LaunchDeviceAction, ProjectTargetPlatformEditorModule.MakePlatformMenuItemWidget(*InPlatformInfo, true, Label), NAME_None, Tooltip, EUserInterfaceActionType::Check); } } void PopulateLaunchMenu(UToolMenu* Menu) { TArray VanillaPlatforms = PlatformInfo::BuildPlatformHierarchy(PlatformInfo::EPlatformFilter::All); VanillaPlatforms.Sort([](const PlatformInfo::FVanillaPlatformEntry& One, const PlatformInfo::FVanillaPlatformEntry& Two) -> bool { return One.PlatformInfo->DisplayName.CompareTo(Two.PlatformInfo->DisplayName) < 0; }); // shared devices section ITargetDeviceServicesModule* TargetDeviceServicesModule = static_cast(FModuleManager::Get().LoadModule(TEXT("TargetDeviceServices"))); IProjectTargetPlatformEditorModule& ProjectTargetPlatformEditorModule = FModuleManager::LoadModuleChecked("ProjectTargetPlatformEditor"); TArray PlatformsToMaybeInstallLinksFor; PlatformsToMaybeInstallLinksFor.Add(TEXT("Android")); PlatformsToMaybeInstallLinksFor.Add(TEXT("IOS")); PlatformsToMaybeInstallLinksFor.Add(TEXT("Linux")); PlatformsToMaybeInstallLinksFor.Add(TEXT("Lumin")); TArray PlatformsToCheckFlavorsFor; PlatformsToCheckFlavorsFor.Add(TEXT("Android")); PlatformsToCheckFlavorsFor.Add(TEXT("IOS")); TArray PlatformsWithNoDevices; TArray PlatformsToAddInstallLinksFor; EProjectType ProjectType = FGameProjectGenerationModule::Get().ProjectHasCodeFiles() ? EProjectType::Code : EProjectType::Content; { FToolMenuSection& Section = Menu->AddSection("LevelEditorLaunchDevices", LOCTEXT("LaunchButtonDevicesSection", "Devices")); for (const PlatformInfo::FVanillaPlatformEntry& VanillaPlatform: VanillaPlatforms) { // for the Editor we are only interested in launching standalone games if (VanillaPlatform.PlatformInfo->PlatformType != EBuildTargetType::Game || !VanillaPlatform.PlatformInfo->bEnabledForUse || !FInstalledPlatformInfo::Get().CanDisplayPlatform(VanillaPlatform.PlatformInfo->BinaryFolderName, ProjectType)) { continue; } if (VanillaPlatform.PlatformInfo->SDKStatus == PlatformInfo::EPlatformSDKStatus::Installed) { // for each platform... TArray> DeviceProxies; // the list of proxies include the "Al_Android" entry TargetDeviceServicesModule->GetDeviceProxyManager()->GetAllProxies(VanillaPlatform.PlatformInfo->VanillaPlatformName, DeviceProxies); // if this platform had no devices, but we want to show an extra option if not installed right if (DeviceProxies.Num() == 0) { if (PlatformsWithNoDevices.Find(VanillaPlatform.PlatformInfo->VanillaPlatformName) == INDEX_NONE) { // add an entry with a no devices found PlatformsWithNoDevices.Add(VanillaPlatform.PlatformInfo->VanillaPlatformName); } } else { // for each proxy... for (auto DeviceProxyIt = DeviceProxies.CreateIterator(); DeviceProxyIt; ++DeviceProxyIt) { TSharedPtr DeviceProxy = *DeviceProxyIt; // create an All__devices_on_ submenu if (DeviceProxy->IsAggregated()) { FString AggregateDevicedName(FString::Printf(TEXT(" %s"), *DeviceProxy->GetName())); // align with the other menu entries FSlateIcon AggregateDeviceIcon(FEditorStyle::GetStyleSetName(), VanillaPlatform.PlatformInfo->GetIconStyleName(PlatformInfo::EPlatformIconSize::Normal)); Section.AddSubMenu( NAME_None, FText::FromString(AggregateDevicedName), FText::FromString(AggregateDevicedName), FNewMenuDelegate::CreateStatic(&MakeAllDevicesSubMenu, VanillaPlatform.PlatformInfo, DeviceProxy), false, AggregateDeviceIcon, true); continue; } // ... create an action... FUIAction LaunchDeviceAction( FExecuteAction::CreateStatic(&FInternalPlayWorldCommandCallbacks::HandleLaunchOnDeviceActionExecute, DeviceProxy->GetTargetDeviceId(NAME_None), DeviceProxy->GetName()), FCanExecuteAction::CreateStatic(&FInternalPlayWorldCommandCallbacks::HandleLaunchOnDeviceActionCanExecute, DeviceProxy->GetName()), FIsActionChecked::CreateStatic(&FInternalPlayWorldCommandCallbacks::HandleLaunchOnDeviceActionIsChecked, DeviceProxy->GetName())); // ... generate display label... FFormatNamedArguments LabelArguments; LabelArguments.Add(TEXT("DeviceName"), FText::FromString(DeviceProxy->GetName())); if (!DeviceProxy->IsConnected()) { LabelArguments.Add(TEXT("HostUser"), LOCTEXT("DisconnectedHint", " [Disconnected]")); } else if (DeviceProxy->GetHostUser() != FPlatformProcess::UserName(false)) { LabelArguments.Add(TEXT("HostUser"), FText::FromString(DeviceProxy->GetHostUser())); } else { LabelArguments.Add(TEXT("HostUser"), FText::GetEmpty()); } FText Label = FText::Format(LOCTEXT("LaunchDeviceLabel", "{DeviceName}{HostUser}"), LabelArguments); // ... generate tooltip text FFormatNamedArguments TooltipArguments; TooltipArguments.Add(TEXT("DeviceID"), FText::FromString(DeviceProxy->GetName())); TooltipArguments.Add(TEXT("DisplayName"), VanillaPlatform.PlatformInfo->DisplayName); FText Tooltip = FText::Format(LOCTEXT("LaunchDeviceToolTipText_ThisDevice", "Launch the game on this {DisplayName} device ({DeviceID})"), TooltipArguments); if (!DeviceProxy->IsAuthorized()) { Tooltip = FText::Format(LOCTEXT("LaunchDeviceToolTipText_UnauthorizedOrLocked", "{DisplayName} device ({DeviceID}) is unauthorized or locked"), TooltipArguments); } FProjectStatus ProjectStatus; if (IProjectManager::Get().QueryStatusForCurrentProject(ProjectStatus) && !ProjectStatus.IsTargetPlatformSupported(VanillaPlatform.PlatformInfo->VanillaPlatformName)) { FText TooltipLine2 = FText::Format(LOCTEXT("LaunchDevicePlatformWarning", "{DisplayName} is not listed as a target platform for this project, so may not run as expected."), TooltipArguments); Tooltip = FText::Format(FText::FromString(TEXT("{0}\n\n{1}")), Tooltip, TooltipLine2); } // ... and add a menu entry FToolMenuEntry& Entry = Section.AddEntry(FToolMenuEntry::InitMenuEntry( NAME_None, LaunchDeviceAction, ProjectTargetPlatformEditorModule.MakePlatformMenuItemWidget(*VanillaPlatform.PlatformInfo, true, Label))); Entry.ToolTip = Tooltip; Entry.UserInterfaceActionType = EUserInterfaceActionType::Check; } } } else { // if the platform wasn't installed, we'll add a menu item later (we never care about code in this case, since we don't compile) if (PlatformsToMaybeInstallLinksFor.Find(VanillaPlatform.PlatformInfo->VanillaPlatformName.ToString()) != INDEX_NONE) { PlatformsToAddInstallLinksFor.Add(*(VanillaPlatform.PlatformInfo)); } } } } TWeakObjectPtr CookerSettings = GetMutableDefault(); { FToolMenuSection& Section = Menu->AddSection("CookerSettings"); FUIAction UIAction; UIAction.ExecuteAction = FExecuteAction::CreateLambda([CookerSettings] { CookerSettings->bCookOnTheFlyForLaunchOn = !CookerSettings->bCookOnTheFlyForLaunchOn; CookerSettings->Modify(true); // Update source control FString ConfigPath = FPaths::ConvertRelativePathToFull(CookerSettings->GetDefaultConfigFilename()); if (FPlatformFileManager::Get().GetPlatformFile().FileExists(*ConfigPath)) { if (ISourceControlModule::Get().IsEnabled()) { FText ErrorMessage; if (!SourceControlHelpers::CheckoutOrMarkForAdd(ConfigPath, FText::FromString(ConfigPath), NULL, ErrorMessage)) { FNotificationInfo Info(ErrorMessage); Info.ExpireDuration = 3.0f; FSlateNotificationManager::Get().AddNotification(Info); } } else { if (!FPlatformFileManager::Get().GetPlatformFile().SetReadOnly(*ConfigPath, false)) { FNotificationInfo Info(FText::Format(LOCTEXT("FailedToMakeWritable", "Could not make {0} writable."), FText::FromString(ConfigPath))); Info.ExpireDuration = 3.0f; FSlateNotificationManager::Get().AddNotification(Info); } } } // Save settings CookerSettings->UpdateSinglePropertyInConfigFile(CookerSettings->GetClass()->FindPropertyByName(GET_MEMBER_NAME_CHECKED(UCookerSettings, bCookOnTheFlyForLaunchOn)), CookerSettings->GetDefaultConfigFilename()); }); UIAction.GetActionCheckState = FGetActionCheckState::CreateLambda([CookerSettings] { return CookerSettings->bCookOnTheFlyForLaunchOn ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; }); Section.AddMenuEntry( "CookOnTheFlyOnLaunch", LOCTEXT("CookOnTheFlyOnLaunch", "Enable cooking on the fly"), LOCTEXT("CookOnTheFlyOnLaunchDescription", "Cook on the fly instead of cooking upfront when launching"), FSlateIcon(), UIAction, EUserInterfaceActionType::ToggleButton); } if (PlatformsWithNoDevices.Num() > 0) { { FToolMenuSection& Section = Menu->AddSection("NoDevices"); for (int32 PlatformIndex = 0; PlatformIndex < PlatformsWithNoDevices.Num(); PlatformIndex++) { const PlatformInfo::FPlatformInfo* PlatformInfo = PlatformInfo::FindVanillaPlatformInfo(PlatformsWithNoDevices[PlatformIndex]); // ... generate display label... FFormatNamedArguments LabelArguments; LabelArguments.Add(TEXT("DisplayName"), PlatformInfo->DisplayName); FText Label = FText::Format(LOCTEXT("NoDeviceLabel", "{DisplayName} - No Devices Found"), LabelArguments); // ... create an action... FUIAction NoDeviceAction( FExecuteAction::CreateStatic(&FInternalPlayWorldCommandCallbacks::HandleNoDeviceFoundActionExecute), FCanExecuteAction::CreateStatic(&FInternalPlayWorldCommandCallbacks::HandleNoDeviceFoundActionCanExecute)); // ... generate tooltip text FFormatNamedArguments TooltipArguments; TooltipArguments.Add(TEXT("DisplayName"), PlatformInfo->DisplayName); FText Tooltip = FText::Format(LOCTEXT("LaunchNoDevicesToolTipText", "Found no connected devices for {DisplayName}"), TooltipArguments); // ... and add a menu entry FToolMenuEntry& Entry = Section.AddEntry(FToolMenuEntry::InitMenuEntry( NAME_None, NoDeviceAction, ProjectTargetPlatformEditorModule.MakePlatformMenuItemWidget(*PlatformInfo, true, Label))); Entry.ToolTip = Tooltip; Entry.UserInterfaceActionType = EUserInterfaceActionType::Check; } } } // tip section { FToolMenuSection& Section = Menu->AddSection("LevelEditorLaunchHint"); Section.AddEntry(FToolMenuEntry::InitWidget( "LevelEditorLaunchHint", SNew(STextBlock) .ColorAndOpacity(FSlateColor::UseSubduedForeground()) .Text(LOCTEXT("ZoomToFitHorizontal", "Launching a game on a different device will change your default 'Launch' device in the toolbar")) .WrapTextAt(250), FText::GetEmpty())); } if (PlatformsToAddInstallLinksFor.Num() > 0) { { FToolMenuSection& Section = Menu->AddSection("SDKUninstalledTutorials"); for (int32 PlatformIndex = 0; PlatformIndex < PlatformsToAddInstallLinksFor.Num(); PlatformIndex++) { const PlatformInfo::FPlatformInfo& Platform = PlatformsToAddInstallLinksFor[PlatformIndex]; FUIAction Action(FExecuteAction::CreateStatic(&FInternalPlayWorldCommandCallbacks::HandleShowSDKTutorial, Platform.DisplayName.ToString(), Platform.SDKTutorial)); FFormatNamedArguments LabelArguments; LabelArguments.Add(TEXT("PlatformName"), Platform.DisplayName); FText Label = FText::Format(LOCTEXT("LaunchPlatformLabel", "{PlatformName} Support"), LabelArguments); Section.AddMenuEntry( NAME_None, Label, LOCTEXT("PlatformSDK", "Show information on setting up the platform tools"), FSlateIcon(FEditorStyle::GetStyleSetName(), "LevelEditor.BrowseDocumentation"), Action, EUserInterfaceActionType::Button); } } } // options section { FToolMenuSection& Section = Menu->AddSection("LevelEditorLaunchOptions"); Section.AddMenuEntry(FPlayWorldCommands::Get().OpenProjectLauncher, TAttribute(), TAttribute(), FSlateIcon(FEditorStyle::GetStyleSetName(), "Launcher.TabIcon")); Section.AddMenuEntry(FPlayWorldCommands::Get().OpenDeviceManager, TAttribute(), TAttribute(), FSlateIcon(FEditorStyle::GetStyleSetName(), "DeviceDetails.TabIcon")); Section.AddDynamicEntry("OpenProjectTargetPlatform", FNewToolMenuDelegateLegacy::CreateLambda([](FMenuBuilder& MenuBuilder, UToolMenu* ToolMenu) { FModuleManager::LoadModuleChecked("ProjectTargetPlatformEditor").AddOpenProjectTargetPlatformEditorMenuItem(MenuBuilder); })); } } TSharedRef FPlayWorldCommands::GenerateLaunchMenuContent(TSharedRef InCommandList) { static const FName MenuName("UnrealEd.PlayWorldCommands.LaunchMenu"); if (!UToolMenus::Get()->IsMenuRegistered(MenuName)) { UToolMenu* Menu = UToolMenus::Get()->RegisterMenu(MenuName); Menu->AddDynamicSection("DynamicSection", FNewToolMenuDelegate::CreateStatic(&PopulateLaunchMenu)); } FToolMenuContext MenuContext(InCommandList); return UToolMenus::Get()->GenerateWidget(MenuName, MenuContext); } ////////////////////////////////////////////////////////////////////////// // FPlayWorldCommandCallbacks void FPlayWorldCommandCallbacks::StartPlayFromHere() { // Is a PIE session already running? If so we close it first if (GUnrealEd->PlayWorld != NULL) { GUnrealEd->EndPlayMap(); } FRequestPlaySessionParams SessionParams; UClass* const PlayerStartClass = GUnrealEd->PlayFromHerePlayerStartClass ? (UClass*)GUnrealEd->PlayFromHerePlayerStartClass : APlayerStart::StaticClass(); // Figure out the start location of the player UCapsuleComponent* DefaultCollisionComponent = CastChecked(PlayerStartClass->GetDefaultObject()->GetRootComponent()); FVector CollisionExtent = FVector(DefaultCollisionComponent->GetScaledCapsuleRadius(), DefaultCollisionComponent->GetScaledCapsuleRadius(), DefaultCollisionComponent->GetScaledCapsuleHalfHeight()); SessionParams.StartLocation = GEditor->UnsnappedClickLocation + GEditor->ClickPlane * (FVector::BoxPushOut(GEditor->ClickPlane, CollisionExtent) + 0.1f); FLevelEditorModule& LevelEditorModule = FModuleManager::GetModuleChecked("LevelEditor"); TSharedPtr ActiveLevelViewport = LevelEditorModule.GetFirstActiveViewport(); if (ActiveLevelViewport.IsValid() && ActiveLevelViewport->GetAssetViewportClient().IsPerspective()) { // If there is no level viewport, a new window will be spawned to play in. SessionParams.DestinationSlateViewport = ActiveLevelViewport; SessionParams.StartRotation = ActiveLevelViewport->GetAssetViewportClient().GetViewRotation(); } GUnrealEd->RequestPlaySession(SessionParams); } void FPlayWorldCommandCallbacks::ResumePlaySession_Clicked() { if (HasPlayWorld()) { LeaveDebuggingMode(); GUnrealEd->PlaySessionResumed(); uint32 UserIndex = 0; FSlateApplication::Get().SetUserFocusToGameViewport(UserIndex); } } void FPlayWorldCommandCallbacks::PausePlaySession_Clicked() { if (HasPlayWorld()) { GUnrealEd->PlayWorld->bDebugPauseExecution = true; GUnrealEd->PlaySessionPaused(); if (IsInPIE()) { FSlateApplication::Get().ClearKeyboardFocus(EFocusCause::SetDirectly); FSlateApplication::Get().ResetToDefaultInputSettings(); TWeakPtr ActiveGlobalPlayWorldWidget = FPlayWorldCommands::GetActiveGlobalPlayWorldActionsWidget(); if (ActiveGlobalPlayWorldWidget.IsValid()) { uint32 UserIndex = 0; FSlateApplication::Get().SetUserFocus(UserIndex, ActiveGlobalPlayWorldWidget.Pin()); } } } } void FPlayWorldCommandCallbacks::SingleFrameAdvance_Clicked() { if (HasPlayWorld()) { FInternalPlayWorldCommandCallbacks::SingleFrameAdvance_Clicked(); } } bool FPlayWorldCommandCallbacks::IsInSIE() { return GEditor->bIsSimulatingInEditor; } bool FPlayWorldCommandCallbacks::IsInPIE() { return (GEditor->PlayWorld != NULL) && (!GEditor->bIsSimulatingInEditor); } bool FPlayWorldCommandCallbacks::IsInSIE_AndRunning() { return IsInSIE() && ((GEditor->PlayWorld == NULL) || !(GEditor->PlayWorld->bDebugPauseExecution)); } bool FPlayWorldCommandCallbacks::IsInPIE_AndRunning() { return IsInPIE() && ((GEditor->PlayWorld == NULL) || !(GEditor->PlayWorld->bDebugPauseExecution)); } bool FPlayWorldCommandCallbacks::HasPlayWorld() { return GEditor->PlayWorld != NULL; } bool FPlayWorldCommandCallbacks::HasPlayWorldAndPaused() { return HasPlayWorld() && GUnrealEd->PlayWorld->bDebugPauseExecution; } bool FPlayWorldCommandCallbacks::HasPlayWorldAndRunning() { return HasPlayWorld() && !GUnrealEd->PlayWorld->bDebugPauseExecution; } ////////////////////////////////////////////////////////////////////////// // FInternalPlayWorldCommandCallbacks FText FInternalPlayWorldCommandCallbacks::GetPossessEjectLabel() { if (IsInPIE()) { return LOCTEXT("EjectLabel", "Eject"); } else if (IsInSIE()) { return LOCTEXT("PossessLabel", "Possess"); } else { return LOCTEXT("ToggleBetweenPieAndSIELabel", "Toggle Between PIE and SIE"); } } FText FInternalPlayWorldCommandCallbacks::GetPossessEjectTooltip() { if (IsInPIE()) { return LOCTEXT("EjectToolTip", "Detaches from the player controller, allowing regular editor controls"); } else if (IsInSIE()) { return LOCTEXT("PossessToolTip", "Attaches to the player controller, allowing normal gameplay controls"); } else { return LOCTEXT("ToggleBetweenPieAndSIEToolTip", "Toggles the current play session between play in editor and simulate in editor"); } } FSlateIcon FInternalPlayWorldCommandCallbacks::GetPossessEjectImage() { if (IsInPIE()) { return FSlateIcon(FEditorStyle::GetStyleSetName(), "PlayWorld.EjectFromPlayer"); } else if (IsInSIE()) { return FSlateIcon(FEditorStyle::GetStyleSetName(), "PlayWorld.PossessPlayer"); } else { return FSlateIcon(); } } bool FInternalPlayWorldCommandCallbacks::CanLateJoin() { return HasPlayWorld(); } bool FInternalPlayWorldCommandCallbacks::CanShowLateJoinButton() { return GetDefault()->bAllowLateJoinInPIE && HasPlayWorld(); } void SetLastExecutedPlayMode(EPlayModeType PlayMode) { ULevelEditorPlaySettings* PlaySettings = GetMutableDefault(); PlaySettings->LastExecutedPlayModeType = PlayMode; FPropertyChangedEvent PropChangeEvent(ULevelEditorPlaySettings::StaticClass()->FindPropertyByName(GET_MEMBER_NAME_CHECKED(ULevelEditorPlaySettings, LastExecutedPlayModeType))); PlaySettings->PostEditChangeProperty(PropChangeEvent); PlaySettings->SaveConfig(); } void FInternalPlayWorldCommandCallbacks::Simulate_Clicked() { // Is a simulation session already running? If so, do nothing if (HasPlayWorld() && GUnrealEd->bIsSimulatingInEditor) { return; } FLevelEditorModule& LevelEditorModule = FModuleManager::GetModuleChecked(TEXT("LevelEditor")); TSharedPtr ActiveLevelViewport = LevelEditorModule.GetFirstActiveViewport(); if (ActiveLevelViewport.IsValid()) { // Start a new simulation session! if (!HasPlayWorld()) { if (FEngineAnalytics::IsAvailable()) { FEngineAnalytics::GetProvider().RecordEvent(TEXT("Editor.Usage.SimulateInEditor")); } SetLastExecutedPlayMode(PlayMode_Simulate); FRequestPlaySessionParams SessionParams; SessionParams.WorldType = EPlaySessionWorldType::SimulateInEditor; SessionParams.DestinationSlateViewport = ActiveLevelViewport; GUnrealEd->RequestPlaySession(SessionParams); } else if (ActiveLevelViewport->HasPlayInEditorViewport()) { GUnrealEd->RequestToggleBetweenPIEandSIE(); } } } bool FInternalPlayWorldCommandCallbacks::Simulate_CanExecute() { // Can't simulate while already simulating; PIE is fine as we toggle to simulate return !(HasPlayWorld() && GUnrealEd->bIsSimulatingInEditor) && !GEditor->IsLightingBuildCurrentlyRunning(); } bool FInternalPlayWorldCommandCallbacks::Simulate_IsChecked() { return HasPlayWorld() && GUnrealEd->bIsSimulatingInEditor; } const TSharedRef GetLastPlaySessionCommand() { const ULevelEditorPlaySettings* PlaySettings = GetDefault(); const FPlayWorldCommands& Commands = FPlayWorldCommands::Get(); TSharedRef Command = Commands.PlayInViewport.ToSharedRef(); switch (PlaySettings->LastExecutedPlayModeType) { case PlayMode_InViewPort: Command = Commands.PlayInViewport.ToSharedRef(); break; case PlayMode_InEditorFloating: Command = Commands.PlayInEditorFloating.ToSharedRef(); break; case PlayMode_InMobilePreview: Command = Commands.PlayInMobilePreview.ToSharedRef(); break; case PlayMode_InTargetedMobilePreview: { // Scan through targeted mobile preview commands to find our match. for (auto PreviewerCommand: Commands.PlayInTargetedMobilePreviewDevices) { FName LastExecutedPIEPreviewDevice = FName(*PlaySettings->LastExecutedPIEPreviewDevice); if (PreviewerCommand->GetCommandName() == LastExecutedPIEPreviewDevice) { Command = PreviewerCommand.ToSharedRef(); break; } } break; } case PlayMode_InVulkanPreview: Command = Commands.PlayInVulkanPreview.ToSharedRef(); break; case PlayMode_InNewProcess: Command = Commands.PlayInNewProcess.ToSharedRef(); break; case PlayMode_InVR: Command = Commands.PlayInVR.ToSharedRef(); break; case PlayMode_Simulate: Command = Commands.Simulate.ToSharedRef(); } return Command; } /** Report PIE usage to engine analytics */ void RecordLastExecutedPlayMode() { if (FEngineAnalytics::IsAvailable()) { const ULevelEditorPlaySettings* PlaySettings = GetDefault(); // play location FString PlayLocationString; switch (PlaySettings->LastExecutedPlayModeLocation) { case PlayLocation_CurrentCameraLocation: PlayLocationString = TEXT("CurrentCameraLocation"); break; case PlayLocation_DefaultPlayerStart: PlayLocationString = TEXT("DefaultPlayerStart"); break; default: PlayLocationString = TEXT(""); } // play mode FString PlayModeString; switch (PlaySettings->LastExecutedPlayModeType) { case PlayMode_InViewPort: PlayModeString = TEXT("InViewPort"); break; case PlayMode_InEditorFloating: PlayModeString = TEXT("InEditorFloating"); break; case PlayMode_InMobilePreview: PlayModeString = TEXT("InMobilePreview"); break; case PlayMode_InTargetedMobilePreview: PlayModeString = TEXT("InTargetedMobilePreview"); break; case PlayMode_InVulkanPreview: PlayModeString = TEXT("InVulkanPreview"); break; case PlayMode_InNewProcess: PlayModeString = TEXT("InNewProcess"); break; case PlayMode_InVR: PlayModeString = TEXT("InVR"); break; case PlayMode_Simulate: PlayModeString = TEXT("Simulate"); break; default: PlayModeString = TEXT(""); } FEngineAnalytics::GetProvider().RecordEvent(TEXT("Editor.Usage.PIE"), TEXT("PlayLocation"), PlayLocationString); FEngineAnalytics::GetProvider().RecordEvent(TEXT("Editor.Usage.PIE"), TEXT("PlayMode"), PlayModeString); } } void SetLastExecutedLaunchMode(ELaunchModeType LaunchMode) { ULevelEditorPlaySettings* PlaySettings = GetMutableDefault(); PlaySettings->LastExecutedLaunchModeType = LaunchMode; PlaySettings->PostEditChange(); PlaySettings->SaveConfig(); } void FInternalPlayWorldCommandCallbacks::RepeatLastPlay_Clicked() { // Let a game have a go at settings before we play ULevelEditorPlaySettings* PlaySettings = GetMutableDefault(); PlaySettings->PostEditChange(); // Grab the play command and execute it TSharedRef LastCommand = GetLastPlaySessionCommand(); UE_LOG(LogTemp, Log, TEXT("Repeating last play command: %s"), *LastCommand->GetLabel().ToString()); FPlayWorldCommands::GlobalPlayWorldActions->ExecuteAction(LastCommand); } bool FInternalPlayWorldCommandCallbacks::RepeatLastPlay_CanExecute() { return FPlayWorldCommands::GlobalPlayWorldActions->CanExecuteAction(GetLastPlaySessionCommand()); } FText FInternalPlayWorldCommandCallbacks::GetRepeatLastPlayToolTip() { return GetLastPlaySessionCommand()->GetDescription(); } FSlateIcon FInternalPlayWorldCommandCallbacks::GetRepeatLastPlayIcon() { return GetLastPlaySessionCommand()->GetIcon(); } void FInternalPlayWorldCommandCallbacks::PlayInViewport_Clicked() { FLevelEditorModule& LevelEditorModule = FModuleManager::GetModuleChecked(TEXT("LevelEditor")); /** Set PlayInViewPort as the last executed play command */ const FPlayWorldCommands& Commands = FPlayWorldCommands::Get(); SetLastExecutedPlayMode(PlayMode_InViewPort); RecordLastExecutedPlayMode(); TSharedPtr ActiveLevelViewport = LevelEditorModule.GetFirstActiveViewport(); const bool bAtPlayerStart = (GetPlayModeLocation() == PlayLocation_DefaultPlayerStart); FRequestPlaySessionParams SessionParams; // Make sure we can find a path to the view port. This will fail in cases where the view port widget // is in a backgrounded tab, etc. We can't currently support starting PIE in a backgrounded tab // due to how PIE manages focus and requires event forwarding from the application. if (ActiveLevelViewport.IsValid() && FSlateApplication::Get().FindWidgetWindow(ActiveLevelViewport->AsWidget()).IsValid()) { SessionParams.DestinationSlateViewport = ActiveLevelViewport; if (!bAtPlayerStart) { // Start the player where the camera is if not forcing from player start SessionParams.StartLocation = ActiveLevelViewport->GetAssetViewportClient().GetViewLocation(); SessionParams.StartRotation = ActiveLevelViewport->GetAssetViewportClient().GetViewRotation(); } } if (!HasPlayWorld()) { // If there is an active level view port, play the game in it, otherwise make a new window. GUnrealEd->RequestPlaySession(SessionParams); } else { // There is already a play world active which means simulate in editor is happening // Toggle to PIE check(!GIsPlayInEditorWorld); GUnrealEd->RequestToggleBetweenPIEandSIE(); } } bool FInternalPlayWorldCommandCallbacks::PlayInViewport_CanExecute() { // Disallow PIE when compiling in the editor if (GEditor->bIsCompiling) { return false; } // Allow PIE if we don't already have a play session or the play session is simulate in editor (which we can toggle to PIE) return (!GEditor->IsPlaySessionInProgress() && !HasPlayWorld() && !GEditor->IsLightingBuildCurrentlyRunning()) || GUnrealEd->IsSimulateInEditorInProgress(); } void FInternalPlayWorldCommandCallbacks::PlayInEditorFloating_Clicked() { FLevelEditorModule& LevelEditorModule = FModuleManager::GetModuleChecked(TEXT("LevelEditor")); SetLastExecutedPlayMode(PlayMode_InEditorFloating); FRequestPlaySessionParams SessionParams; // Is a PIE session already running? If not, then we'll kick off a new one if (!HasPlayWorld()) { RecordLastExecutedPlayMode(); const bool bAtPlayerStart = (GetPlayModeLocation() == PlayLocation_DefaultPlayerStart); if (!bAtPlayerStart) { TSharedPtr ActiveLevelViewport = LevelEditorModule.GetFirstActiveViewport(); // Make sure we can find a path to the view port. This will fail in cases where the view port widget // is in a backgrounded tab, etc. We can't currently support starting PIE in a backgrounded tab // due to how PIE manages focus and requires event forwarding from the application. if (ActiveLevelViewport.IsValid() && FSlateApplication::Get().FindWidgetWindow(ActiveLevelViewport->AsWidget()).IsValid()) { // Start the player where the camera is if not forcing from player start SessionParams.StartLocation = ActiveLevelViewport->GetAssetViewportClient().GetViewLocation(); SessionParams.StartRotation = ActiveLevelViewport->GetAssetViewportClient().GetViewRotation(); } } // Spawn a new window to play in. GUnrealEd->RequestPlaySession(SessionParams); } else { // Terminate existing session. This is deferred because we could be processing this from the play world and we should not clear the play world while in it. GUnrealEd->RequestEndPlayMap(); } } bool FInternalPlayWorldCommandCallbacks::PlayInEditorFloating_CanExecute() { return (!HasPlayWorld() || !GUnrealEd->bIsSimulatingInEditor) && !GEditor->IsLightingBuildCurrentlyRunning(); } void FInternalPlayWorldCommandCallbacks::PlayInVR_Clicked() { FLevelEditorModule& LevelEditorModule = FModuleManager::GetModuleChecked(TEXT("LevelEditor")); SetLastExecutedPlayMode(PlayMode_InVR); FRequestPlaySessionParams SessionParams; // Is a PIE session already running? If not, then we'll kick off a new one if (!HasPlayWorld()) { RecordLastExecutedPlayMode(); const bool bAtPlayerStart = (GetPlayModeLocation() == PlayLocation_DefaultPlayerStart); if (!bAtPlayerStart) { TSharedPtr ActiveLevelViewport = LevelEditorModule.GetFirstActiveViewport(); // Make sure we can find a path to the view port. This will fail in cases where the view port widget // is in a backgrounded tab, etc. We can't currently support starting PIE in a backgrounded tab // due to how PIE manages focus and requires event forwarding from the application. if (ActiveLevelViewport.IsValid() && FSlateApplication::Get().FindWidgetWindow(ActiveLevelViewport->AsWidget()).IsValid()) { // Start the player where the camera is if not forcing from player start SessionParams.StartLocation = ActiveLevelViewport->GetAssetViewportClient().GetViewLocation(); SessionParams.StartRotation = ActiveLevelViewport->GetAssetViewportClient().GetViewRotation(); } } SessionParams.SessionPreviewTypeOverride = EPlaySessionPreviewType::VRPreview; // Spawn a new window to play in. GUnrealEd->RequestPlaySession(SessionParams); } } bool FInternalPlayWorldCommandCallbacks::PlayInVR_CanExecute() { return (!HasPlayWorld() || !GUnrealEd->bIsSimulatingInEditor) && !GEditor->IsLightingBuildCurrentlyRunning() && GEngine && GEngine->XRSystem.IsValid(); } void SetLastExecutedPIEPreviewDevice(FString PIEPreviewDevice) { ULevelEditorPlaySettings* PlaySettings = GetMutableDefault(); PlaySettings->LastExecutedPIEPreviewDevice = PIEPreviewDevice; FPropertyChangedEvent PropChangeEvent(ULevelEditorPlaySettings::StaticClass()->FindPropertyByName(GET_MEMBER_NAME_CHECKED(ULevelEditorPlaySettings, LastExecutedPIEPreviewDevice))); PlaySettings->PostEditChangeProperty(PropChangeEvent); PlaySettings->SaveConfig(); } void FInternalPlayWorldCommandCallbacks::PlayInNewProcessPreviewDevice_Clicked(FString PIEPreviewDeviceName) { SetLastExecutedPIEPreviewDevice(PIEPreviewDeviceName); PlayInNewProcess_Clicked(PlayMode_InTargetedMobilePreview); } void FInternalPlayWorldCommandCallbacks::PlayInNewProcess_Clicked(EPlayModeType PlayModeType) { check(PlayModeType == PlayMode_InNewProcess || PlayModeType == PlayMode_InMobilePreview || PlayModeType == PlayMode_InTargetedMobilePreview || PlayModeType == PlayMode_InVulkanPreview); SetLastExecutedPlayMode(PlayModeType); FRequestPlaySessionParams SessionParams; if (!HasPlayWorld()) { RecordLastExecutedPlayMode(); const bool bAtPlayerStart = (GetPlayModeLocation() == PlayLocation_DefaultPlayerStart); if (!bAtPlayerStart) { FLevelEditorModule& LevelEditorModule = FModuleManager::GetModuleChecked(TEXT("LevelEditor")); TSharedPtr ActiveLevelViewport = LevelEditorModule.GetFirstActiveViewport(); if (ActiveLevelViewport.IsValid() && FSlateApplication::Get().FindWidgetWindow(ActiveLevelViewport->AsWidget()).IsValid()) { SessionParams.StartLocation = ActiveLevelViewport->GetAssetViewportClient().GetViewLocation(); SessionParams.StartRotation = ActiveLevelViewport->GetAssetViewportClient().GetViewRotation(); } } if (PlayModeType == PlayMode_InMobilePreview || PlayModeType == PlayMode_InTargetedMobilePreview) { if (PlayModeType == PlayMode_InTargetedMobilePreview) { SessionParams.MobilePreviewTargetDevice = GetDefault()->LastExecutedPIEPreviewDevice; } SessionParams.SessionPreviewTypeOverride = EPlaySessionPreviewType::MobilePreview; } else if (PlayModeType == PlayMode_InVulkanPreview) { SessionParams.SessionPreviewTypeOverride = EPlaySessionPreviewType::VulkanPreview; } SessionParams.SessionDestination = EPlaySessionDestinationType::NewProcess; // Spawn a new window to play in. GUnrealEd->RequestPlaySession(SessionParams); } else { GUnrealEd->EndPlayMap(); } } bool FInternalPlayWorldCommandCallbacks::PlayInNewProcess_CanExecute() { return true; } bool FInternalPlayWorldCommandCallbacks::PlayInModeAndPreviewDeviceIsChecked(FString PIEPreviewDeviceName) { return PlayInModeIsChecked(PlayMode_InTargetedMobilePreview) && GetDefault()->LastExecutedPIEPreviewDevice == PIEPreviewDeviceName; } bool FInternalPlayWorldCommandCallbacks::PlayInModeIsChecked(EPlayModeType PlayMode) { return (PlayMode == GetDefault()->LastExecutedPlayModeType); } bool FInternalPlayWorldCommandCallbacks::PlayInLocation_CanExecute(EPlayModeLocations Location) { switch (Location) { case PlayLocation_CurrentCameraLocation: return true; case PlayLocation_DefaultPlayerStart: return (GEditor->CheckForPlayerStart() != nullptr); default: return false; } } void FInternalPlayWorldCommandCallbacks::PlayInLocation_Clicked(EPlayModeLocations Location) { ULevelEditorPlaySettings* PlaySettings = GetMutableDefault(); PlaySettings->LastExecutedPlayModeLocation = Location; PlaySettings->PostEditChange(); PlaySettings->SaveConfig(); } bool FInternalPlayWorldCommandCallbacks::PlayInLocation_IsChecked(EPlayModeLocations Location) { switch (Location) { case PlayLocation_CurrentCameraLocation: return ((GetDefault()->LastExecutedPlayModeLocation == PlayLocation_CurrentCameraLocation) || (GEditor->CheckForPlayerStart() == nullptr)); case PlayLocation_DefaultPlayerStart: return ((GetDefault()->LastExecutedPlayModeLocation == PlayLocation_DefaultPlayerStart) && (GEditor->CheckForPlayerStart() != nullptr)); } return false; } void FInternalPlayWorldCommandCallbacks::PlayInSettings_Clicked() { FModuleManager::LoadModuleChecked("Settings").ShowViewer("Editor", "LevelEditor", "PlayIn"); } void FInternalPlayWorldCommandCallbacks::OpenProjectLauncher_Clicked() { FGlobalTabmanager::Get()->TryInvokeTab(FTabId("ProjectLauncher")); } void FInternalPlayWorldCommandCallbacks::OpenDeviceManager_Clicked() { FGlobalTabmanager::Get()->TryInvokeTab(FTabId("DeviceManager")); } void FInternalPlayWorldCommandCallbacks::RepeatLastLaunch_Clicked() { const ULevelEditorPlaySettings* PlaySettings = GetDefault(); switch (PlaySettings->LastExecutedLaunchModeType) { case LaunchMode_OnDevice: if (IsReadyToLaunchOnDevice(PlaySettings->LastExecutedLaunchDevice)) { LaunchOnDevice(PlaySettings->LastExecutedLaunchDevice, PlaySettings->LastExecutedLaunchName); } break; default: break; } } bool FInternalPlayWorldCommandCallbacks::RepeatLastLaunch_CanExecute() { const ULevelEditorPlaySettings* PlaySettings = GetDefault(); switch (PlaySettings->LastExecutedLaunchModeType) { case LaunchMode_OnDevice: return CanLaunchOnDevice(PlaySettings->LastExecutedLaunchName); default: return false; } } FText FInternalPlayWorldCommandCallbacks::GetRepeatLastLaunchToolTip() { const ULevelEditorPlaySettings* PlaySettings = GetDefault(); switch (PlaySettings->LastExecutedLaunchModeType) { case LaunchMode_OnDevice: if (CanLaunchOnDevice(PlaySettings->LastExecutedLaunchName)) { FFormatNamedArguments Arguments; Arguments.Add(TEXT("DeviceName"), FText::FromString(PlaySettings->LastExecutedLaunchName)); return FText::Format(LOCTEXT("RepeatLaunchTooltip", "Launch this level on {DeviceName}"), Arguments); } break; default: break; } return LOCTEXT("RepeatLaunchSelectOptionToolTip", "Select a play-on target from the combo menu"); } FSlateIcon FInternalPlayWorldCommandCallbacks::GetRepeatLastLaunchIcon() { const ULevelEditorPlaySettings* PlaySettings = GetDefault(); // @todo gmp: add play mode specific icons switch (PlaySettings->LastExecutedLaunchModeType) { case LaunchMode_OnDevice: break; } static FName RepeatLastLaunchIcon("PlayWorld.RepeatLastLaunch"); return FSlateIcon(FEditorStyle::GetStyleSetName(), RepeatLastLaunchIcon); } bool FInternalPlayWorldCommandCallbacks::IsReadyToLaunchOnDevice(FString DeviceId) { int32 Index = 0; DeviceId.FindChar(TEXT('@'), Index); FString PlatformName = DeviceId.Left(Index); const PlatformInfo::FPlatformInfo* const PlatformInfo = PlatformInfo::FindPlatformInfo(FName(*PlatformName)); checkf(PlatformInfo, TEXT("Unable to find PlatformInfo for %s"), *PlatformName); FGameProjectGenerationModule& GameProjectModule = FModuleManager::LoadModuleChecked(TEXT("GameProjectGeneration")); bool bHasCode = GameProjectModule.Get().ProjectHasCodeFiles(); if (PlatformInfo->SDKStatus == PlatformInfo::EPlatformSDKStatus::NotInstalled) { IMainFrameModule& MainFrameModule = FModuleManager::GetModuleChecked(TEXT("MainFrame")); MainFrameModule.BroadcastMainFrameSDKNotInstalled(PlatformInfo->TargetPlatformName.ToString(), PlatformInfo->SDKTutorial); TArray ParamArray; ParamArray.Add(FAnalyticsEventAttribute(TEXT("Time"), 0.0)); FEditorAnalytics::ReportEvent(TEXT("Editor.LaunchOn.Failed"), PlatformInfo->TargetPlatformName.ToString(), bHasCode, EAnalyticsErrorCodes::SDKNotFound, ParamArray); return false; } const ITargetPlatform* Platform = GetTargetPlatformManager()->FindTargetPlatform(PlatformName); if (Platform) { FString NotInstalledTutorialLink; FString DocumentationLink; FText CustomizedLogMessage; EBuildConfiguration BuildConfiguration = GetDefault()->GetLaunchBuildConfiguration(); bool bEnableAssetNativization = false; int32 Result = Platform->CheckRequirements(bHasCode, BuildConfiguration, bEnableAssetNativization, NotInstalledTutorialLink, DocumentationLink, CustomizedLogMessage); // report to analytics FEditorAnalytics::ReportBuildRequirementsFailure(TEXT("Editor.LaunchOn.Failed"), PlatformName, bHasCode, Result); // report to message log bool UnrecoverableError = false; if ((Result & ETargetPlatformReadyStatus::SDKNotFound) != 0) { AddMessageLog( LOCTEXT("SdkNotFoundMessage", "Software Development Kit (SDK) not found."), CustomizedLogMessage.IsEmpty() ? FText::Format(LOCTEXT("SdkNotFoundMessageDetail", "Please install the SDK for the {0} target platform!"), Platform->DisplayName()) : CustomizedLogMessage, NotInstalledTutorialLink, DocumentationLink); UnrecoverableError = true; } if ((Result & ETargetPlatformReadyStatus::LicenseNotAccepted) != 0) { AddMessageLog( LOCTEXT("LicenseNotAcceptedMessage", "License not accepted."), CustomizedLogMessage.IsEmpty() ? LOCTEXT("LicenseNotAcceptedMessageDetail", "License must be accepted in project settings to deploy your app to the device.") : CustomizedLogMessage, NotInstalledTutorialLink, DocumentationLink); UnrecoverableError = true; } if ((Result & ETargetPlatformReadyStatus::ProvisionNotFound) != 0) { AddMessageLog( LOCTEXT("ProvisionNotFoundMessage", "Provision not found."), CustomizedLogMessage.IsEmpty() ? LOCTEXT("ProvisionNotFoundMessageDetail", "A provision is required for deploying your app to the device.") : CustomizedLogMessage, NotInstalledTutorialLink, DocumentationLink); UnrecoverableError = true; } if ((Result & ETargetPlatformReadyStatus::SigningKeyNotFound) != 0) { AddMessageLog( LOCTEXT("SigningKeyNotFoundMessage", "Signing key not found."), CustomizedLogMessage.IsEmpty() ? LOCTEXT("SigningKeyNotFoundMessageDetail", "The app could not be digitally signed, because the signing key is not configured.") : CustomizedLogMessage, NotInstalledTutorialLink, DocumentationLink); UnrecoverableError = true; } if ((Result & ETargetPlatformReadyStatus::ManifestNotFound) != 0) { AddMessageLog( LOCTEXT("ManifestNotFound", "Manifest not found."), CustomizedLogMessage.IsEmpty() ? LOCTEXT("ManifestNotFoundMessageDetail", "The generated application manifest could not be found.") : CustomizedLogMessage, NotInstalledTutorialLink, DocumentationLink); UnrecoverableError = true; } if ((Result & ETargetPlatformReadyStatus::RemoveServerNameEmpty) != 0 && (bHasCode || (Result & ETargetPlatformReadyStatus::CodeBuildRequired) || (!FApp::GetEngineIsPromotedBuild() && !FApp::IsEngineInstalled()))) { AddMessageLog( LOCTEXT("RemoveServerNameNotFound", "Remote compiling requires a server name. "), CustomizedLogMessage.IsEmpty() ? LOCTEXT("RemoveServerNameNotFoundDetail", "Please specify one in the Remote Server Name settings field.") : CustomizedLogMessage, NotInstalledTutorialLink, DocumentationLink); UnrecoverableError = true; } if (UnrecoverableError) { return false; } // report to main frame if ((Result & ETargetPlatformReadyStatus::CodeUnsupported) != 0) { // show the message FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT("NotSupported_CodeBased", "Sorry, launching a code-based project for the selected platform is currently not supported. This feature may be available in a future release.")); return false; } if ((Result & ETargetPlatformReadyStatus::PluginsUnsupported) != 0) { // show the message FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT("NotSupported_Plugins", "Sorry, launching a project with third-party plugins is currently not supported for the selected platform. This feature may be available in a future release.")); return false; } } else { IMainFrameModule& MainFrameModule = FModuleManager::GetModuleChecked(TEXT("MainFrame")); MainFrameModule.BroadcastMainFrameSDKNotInstalled(PlatformInfo->TargetPlatformName.ToString(), PlatformInfo->SDKTutorial); return false; } return true; } void FInternalPlayWorldCommandCallbacks::HandleLaunchOnDeviceActionExecute(FString DeviceId, FString DeviceName) { if (IsReadyToLaunchOnDevice(DeviceId)) { ULevelEditorPlaySettings* PlaySettings = GetMutableDefault(); PlaySettings->LastExecutedLaunchModeType = LaunchMode_OnDevice; PlaySettings->LastExecutedLaunchDevice = DeviceId; PlaySettings->LastExecutedLaunchName = DeviceName; PlaySettings->PostEditChange(); PlaySettings->SaveConfig(); LaunchOnDevice(DeviceId, DeviceName); } } bool FInternalPlayWorldCommandCallbacks::HandleLaunchOnDeviceActionCanExecute(FString DeviceName) { return CanLaunchOnDevice(DeviceName); } bool FInternalPlayWorldCommandCallbacks::HandleLaunchOnDeviceActionIsChecked(FString DeviceName) { return (DeviceName == GetDefault()->LastExecutedLaunchName); } void FInternalPlayWorldCommandCallbacks::HandleShowSDKTutorial(FString PlatformName, FString NotInstalledDocLink) { // broadcast this, and assume someone will pick it up IMainFrameModule& MainFrameModule = FModuleManager::GetModuleChecked(TEXT("MainFrame")); MainFrameModule.BroadcastMainFrameSDKNotInstalled(PlatformName, NotInstalledDocLink); } void FInternalPlayWorldCommandCallbacks::GetMouseControlExecute() { if (IsInPIE()) { FSlateApplication::Get().ClearKeyboardFocus(EFocusCause::SetDirectly); FSlateApplication::Get().ResetToDefaultInputSettings(); TWeakPtr ActiveGlobalPlayWorldWidget = FPlayWorldCommands::GetActiveGlobalPlayWorldActionsWidget(); if (ActiveGlobalPlayWorldWidget.IsValid()) { uint32 UserIndex = 0; FSlateApplication::Get().SetUserFocus(UserIndex, ActiveGlobalPlayWorldWidget.Pin()); } } } FSlateIcon FInternalPlayWorldCommandCallbacks::GetResumePlaySessionImage() { if (IsInPIE()) { return FSlateIcon(FEditorStyle::GetStyleSetName(), "PlayWorld.ResumePlaySession"); } else if (IsInSIE()) { return FSlateIcon(FEditorStyle::GetStyleSetName(), "PlayWorld.Simulate"); } else { return FSlateIcon(); } } FText FInternalPlayWorldCommandCallbacks::GetResumePlaySessionToolTip() { if (IsInPIE()) { return LOCTEXT("ResumePIE", "Resume play-in-editor session"); } else if (IsInSIE()) { return LOCTEXT("ResumeSIE", "Resume simulation"); } else { return FText(); } } void FInternalPlayWorldCommandCallbacks::SingleFrameAdvance_Clicked() { // We want to function just like Single stepping where we will stop at a breakpoint if one is encountered but we also want to stop after 1 tick if a breakpoint is not encountered. FKismetDebugUtilities::RequestSingleStepIn(); if (HasPlayWorld()) { GUnrealEd->PlayWorld->bDebugFrameStepExecution = true; LeaveDebuggingMode(); GUnrealEd->PlaySessionSingleStepped(); } } void FInternalPlayWorldCommandCallbacks::StopPlaySession_Clicked() { if (HasPlayWorld()) { GEditor->RequestEndPlayMap(); LeaveDebuggingMode(); } } void FInternalPlayWorldCommandCallbacks::LateJoinSession_Clicked() { if (HasPlayWorld()) { GEditor->RequestLateJoin(); } } void FInternalPlayWorldCommandCallbacks::ShowCurrentStatement_Clicked() { UEdGraphNode* CurrentInstruction = FKismetDebugUtilities::GetCurrentInstruction(); if (CurrentInstruction != NULL) { FKismetEditorUtilities::BringKismetToFocusAttentionOnObject(CurrentInstruction); } } void FInternalPlayWorldCommandCallbacks::StepInto_Clicked() { FKismetDebugUtilities::RequestSingleStepIn(); if (HasPlayWorld()) { LeaveDebuggingMode(); GUnrealEd->PlaySessionSingleStepped(); } } void FInternalPlayWorldCommandCallbacks::StepOver_Clicked() { FKismetDebugUtilities::RequestStepOver(); if (HasPlayWorld()) { LeaveDebuggingMode(); GUnrealEd->PlaySessionSingleStepped(); } } void FInternalPlayWorldCommandCallbacks::StepOut_Clicked() { FKismetDebugUtilities::RequestStepOut(); if (HasPlayWorld()) { LeaveDebuggingMode(); GUnrealEd->PlaySessionSingleStepped(); } } void FInternalPlayWorldCommandCallbacks::TogglePlayPause_Clicked() { if (HasPlayWorld()) { if (GUnrealEd->PlayWorld->IsPaused()) { LeaveDebuggingMode(); GUnrealEd->PlaySessionResumed(); uint32 UserIndex = 0; FSlateApplication::Get().SetUserFocusToGameViewport(UserIndex); } else { GUnrealEd->PlayWorld->bDebugPauseExecution = true; GUnrealEd->PlaySessionPaused(); if (IsInPIE()) { FSlateApplication::Get().ClearKeyboardFocus(EFocusCause::SetDirectly); FSlateApplication::Get().ResetToDefaultInputSettings(); TWeakPtr ActiveGlobalPlayWorldWidget = FPlayWorldCommands::GetActiveGlobalPlayWorldActionsWidget(); if (ActiveGlobalPlayWorldWidget.IsValid()) { uint32 UserIndex = 0; FSlateApplication::Get().SetUserFocus(UserIndex, ActiveGlobalPlayWorldWidget.Pin()); } } } } } bool FInternalPlayWorldCommandCallbacks::CanShowNonPlayWorldOnlyActions() { return !HasPlayWorld(); } bool FInternalPlayWorldCommandCallbacks::CanShowVulkanNonPlayWorldOnlyActions() { return !HasPlayWorld() && GetDefault()->bAllowVulkanPreview && FModuleManager::Get().ModuleExists(TEXT("VulkanRHI")); } bool FInternalPlayWorldCommandCallbacks::CanShowVROnlyActions() { return !HasPlayWorld(); } int32 FInternalPlayWorldCommandCallbacks::GetNumberOfClients() { const ULevelEditorPlaySettings* PlayInSettings = GetDefault(); int32 PlayNumberOfClients(0); PlayInSettings->GetPlayNumberOfClients(PlayNumberOfClients); // Ignore 'state' of option (handled externally) return PlayNumberOfClients; } void FInternalPlayWorldCommandCallbacks::SetNumberOfClients(int32 NumClients, ETextCommit::Type CommitInfo) { ULevelEditorPlaySettings* PlayInSettings = GetMutableDefault(); PlayInSettings->SetPlayNumberOfClients(NumClients); PlayInSettings->PostEditChange(); PlayInSettings->SaveConfig(); } int32 FInternalPlayWorldCommandCallbacks::GetNetPlayMode() { const ULevelEditorPlaySettings* PlayInSettings = GetDefault(); EPlayNetMode NetMode; PlayInSettings->GetPlayNetMode(NetMode); return (int32)NetMode; } void FInternalPlayWorldCommandCallbacks::SetNetPlayMode(int32 Value, ESelectInfo::Type CommitInfo) { ULevelEditorPlaySettings* PlayInSettings = GetMutableDefault(); PlayInSettings->SetPlayNetMode((EPlayNetMode)Value); PlayInSettings->PostEditChange(); PlayInSettings->SaveConfig(); } bool FInternalPlayWorldCommandCallbacks::IsStoppedAtBreakpoint() { return GIntraFrameDebuggingGameThread; } void FInternalPlayWorldCommandCallbacks::PossessEjectPlayer_Clicked() { GEditor->RequestToggleBetweenPIEandSIE(); } bool FInternalPlayWorldCommandCallbacks::CanPossessEjectPlayer() { if ((IsInSIE() || IsInPIE()) && !IsStoppedAtBreakpoint()) { for (auto It = GUnrealEd->SlatePlayInEditorMap.CreateIterator(); It; ++It) { return It.Value().DestinationSlateViewport.IsValid(); } } return false; } void FInternalPlayWorldCommandCallbacks::AddMessageLog(const FText& Text, const FText& Detail, const FString& TutorialLink, const FString& DocumentationLink) { TSharedRef Message = FTokenizedMessage::Create(EMessageSeverity::Error); Message->AddToken(FTextToken::Create(Text)); Message->AddToken(FTextToken::Create(Detail)); if (!TutorialLink.IsEmpty()) { Message->AddToken(FTutorialToken::Create(TutorialLink)); } if (!DocumentationLink.IsEmpty()) { Message->AddToken(FDocumentationToken::Create(DocumentationLink)); } FMessageLog MessageLog("PackagingResults"); MessageLog.AddMessage(Message); MessageLog.Open(); } bool FInternalPlayWorldCommandCallbacks::CanLaunchOnDevice(const FString& DeviceName) { if (!GUnrealEd->IsPlayingViaLauncher()) { static TWeakPtr DeviceProxyManagerPtr; if (!DeviceProxyManagerPtr.IsValid()) { ITargetDeviceServicesModule* TargetDeviceServicesModule = FModuleManager::Get().LoadModulePtr(TEXT("TargetDeviceServices")); if (TargetDeviceServicesModule) { DeviceProxyManagerPtr = TargetDeviceServicesModule->GetDeviceProxyManager(); } } TSharedPtr DeviceProxyManager = DeviceProxyManagerPtr.Pin(); if (DeviceProxyManager.IsValid()) { TSharedPtr DeviceProxy = DeviceProxyManager->FindProxy(DeviceName); if (DeviceProxy.IsValid() && DeviceProxy->IsConnected() && DeviceProxy->IsAuthorized()) { return true; } // check if this is an aggregate proxy TArray> Devices; DeviceProxyManager->GetProxies(FName(*DeviceName), false, Devices); // returns true if the game can be launched al least on 1 device for (auto DevicesIt = Devices.CreateIterator(); DevicesIt; ++DevicesIt) { TSharedPtr DeviceAggregateProxy = *DevicesIt; if (DeviceAggregateProxy.IsValid() && DeviceAggregateProxy->IsConnected() && DeviceAggregateProxy->IsAuthorized()) { return true; } } } } return false; } void FInternalPlayWorldCommandCallbacks::LaunchOnDevice(const FString& DeviceId, const FString& DeviceName) { FTargetDeviceId TargetDeviceId; if (FTargetDeviceId::Parse(DeviceId, TargetDeviceId)) { const PlatformInfo::FPlatformInfo* const PlatformInfo = PlatformInfo::FindPlatformInfo(*TargetDeviceId.GetPlatformName()); check(PlatformInfo); if (FInstalledPlatformInfo::Get().IsPlatformMissingRequiredFile(PlatformInfo->BinaryFolderName)) { if (!FInstalledPlatformInfo::OpenInstallerOptions()) { FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT("MissingPlatformFilesLaunch", "Missing required files to launch on this platform.")); } return; } if (FModuleManager::LoadModuleChecked("ProjectTargetPlatformEditor").ShowUnsupportedTargetWarning(*TargetDeviceId.GetPlatformName())) { GUnrealEd->CancelPlayingViaLauncher(); FRequestPlaySessionParams::FLauncherDeviceInfo DeviceInfo; DeviceInfo.DeviceId = DeviceId; DeviceInfo.DeviceName = DeviceName; FRequestPlaySessionParams SessionParams; SessionParams.SessionDestination = EPlaySessionDestinationType::Launcher; SessionParams.LauncherTargetDevice = DeviceInfo; GUnrealEd->RequestPlaySession(SessionParams); } } } EPlayModeLocations FInternalPlayWorldCommandCallbacks::GetPlayModeLocation() { // We can't use PlayLocation_DefaultPlayerStart without a player start position return GEditor->CheckForPlayerStart() ? static_cast(GetDefault()->LastExecutedPlayModeLocation) : PlayLocation_CurrentCameraLocation; } #undef LOCTEXT_NAMESPACE