// Copyright Epic Games, Inc. All Rights Reserved. #include "Settings/LevelEditorPlaySettingsCustomization.h" #include "Misc/Attribute.h" #include "Layout/Margin.h" #include "Layout/Visibility.h" #include "Widgets/DeclarativeSyntaxSupport.h" #include "Widgets/SWidget.h" #include "Widgets/SCompoundWidget.h" #include "Widgets/SBoxPanel.h" #include "Editor.h" #include "Textures/SlateIcon.h" #include "Framework/Commands/UIAction.h" #include "PropertyHandle.h" #include "DetailLayoutBuilder.h" #include "DetailCategoryBuilder.h" #include "Framework/MultiBox/MultiBoxBuilder.h" #include "Widgets/Text/STextBlock.h" #include "Widgets/Images/SImage.h" #include "Widgets/Input/SButton.h" #include "Widgets/Input/SComboButton.h" #include "IDetailPropertyRow.h" #include "DetailWidgetRow.h" #include "Widgets/Input/SComboBox.h" #include "Sound/AudioSettings.h" #include "DeviceProfiles/DeviceProfileManager.h" #include "DeviceProfiles/DeviceProfile.h" #include "ToolMenus.h" #define LOCTEXT_NAMESPACE "FLevelEditorPlaySettingsCustomization" void SScreenPositionCustomization::Construct( const FArguments& InArgs, IDetailLayoutBuilder* LayoutBuilder, const TSharedRef& InWindowPositionProperty, const TSharedRef& InCenterWindowProperty ) { check( LayoutBuilder != NULL ); CenterWindowProperty = InCenterWindowProperty; ChildSlot [ SNew( SVerticalBox ) + SVerticalBox::Slot() .AutoHeight() [ SNew( SHorizontalBox ) + SHorizontalBox::Slot() [ SNew( SVerticalBox ) .IsEnabled( this, &SScreenPositionCustomization::HandleNewWindowPositionPropertyIsEnabled ) + SVerticalBox::Slot() .AutoHeight() [ InWindowPositionProperty->CreatePropertyNameWidget( LOCTEXT( "WindowPosXLabel", "Left Position" ) ) ] + SVerticalBox::Slot() .AutoHeight() [ InWindowPositionProperty->GetChildHandle( 0 )->CreatePropertyValueWidget() ] ] + SHorizontalBox::Slot() .Padding( 8.0f, 0.0f, 0.0f, 0.0f ) [ SNew( SVerticalBox ) .IsEnabled( this, &SScreenPositionCustomization::HandleNewWindowPositionPropertyIsEnabled ) + SVerticalBox::Slot() .AutoHeight() [ InWindowPositionProperty->CreatePropertyNameWidget( LOCTEXT( "TopPositionLabel", "Top Position" ) ) ] + SVerticalBox::Slot() .AutoHeight() [ InWindowPositionProperty->GetChildHandle( 1 )->CreatePropertyValueWidget() ] ] ] + SVerticalBox::Slot() .Padding( 0.0f, 2.0f ) .AutoHeight() [ SNew( SHorizontalBox ) + SHorizontalBox::Slot() .VAlign( VAlign_Center ) .AutoWidth() [ InCenterWindowProperty->CreatePropertyValueWidget() ] + SHorizontalBox::Slot() .AutoWidth() .Padding( 4.0f, 0.0f, 0.0f, 0.0f ) .VAlign( VAlign_Bottom ) [ InWindowPositionProperty->CreatePropertyNameWidget( LOCTEXT( "CenterWindowLabel", "Always center first viewport window to screen" ) ) ] ] ]; } bool SScreenPositionCustomization::HandleNewWindowPositionPropertyIsEnabled() const { bool CenterNewWindow; CenterWindowProperty->GetValue( CenterNewWindow ); return !CenterNewWindow; } void SScreenResolutionCustomization::Construct( const FArguments& InArgs, IDetailLayoutBuilder* LayoutBuilder, const TSharedRef& InWindowHeightProperty, const TSharedRef& InWindowWidthProperty ) { check( LayoutBuilder != NULL ); WindowHeightProperty = InWindowHeightProperty; WindowWidthProperty = InWindowWidthProperty; FSimpleDelegate SizeChangeDelegate = FSimpleDelegate::CreateSP( this, &SScreenResolutionCustomization::OnSizeChanged ); WindowHeightProperty->SetOnPropertyValueChanged( SizeChangeDelegate ); WindowWidthProperty->SetOnPropertyValueChanged( SizeChangeDelegate ); ChildSlot [ SNew( SVerticalBox ) + SVerticalBox::Slot() .VAlign( VAlign_Bottom ) [ SNew( SHorizontalBox ) + SHorizontalBox::Slot() .AutoWidth() .VAlign( VAlign_Center ) [ SNew( SComboButton ) .VAlign( VAlign_Center ) .ButtonContent() [ SNew( STextBlock ) .Font( LayoutBuilder->GetDetailFont() ) .Text( LOCTEXT( "CommonResolutionsButtonText", "Common Resolutions" ) ) ] .ContentPadding(FMargin(6, 2)) .OnGetMenuContent(this, &SScreenResolutionCustomization::GetResolutionsMenu) .ToolTipText( LOCTEXT( "CommonResolutionsButtonTooltip", "Pick from a list of common screen resolutions" ) ) ] + SHorizontalBox::Slot() .Padding( 0, 0, 6, 0 ) .AutoWidth() .VAlign( VAlign_Center ) [ SNew( SButton ) .OnClicked( this, &SScreenResolutionCustomization::HandleSwapAspectRatioClicked ) .ContentPadding( FMargin( 3, 0, 3, 1 ) ) .Content() [ SNew( SImage ) .Image( this, &SScreenResolutionCustomization::GetAspectRatioSwitchImage ) ] .ToolTipText( LOCTEXT( "SwapAspectRatioTooltip", "Swap between portrait and landscape orientation." ) ) ] ] + SVerticalBox::Slot() .AutoHeight() [ SNew( SHorizontalBox ) + SHorizontalBox::Slot() [ SNew( SVerticalBox ) + SVerticalBox::Slot() .AutoHeight() [ WindowWidthProperty->CreatePropertyNameWidget( LOCTEXT( "ViewportWidthLabel", "Viewport Width" ) ) ] + SVerticalBox::Slot() [ WindowWidthProperty->CreatePropertyValueWidget() ] ] + SHorizontalBox::Slot() .Padding( 8, 0, 0, 0 ) [ SNew( SVerticalBox ) + SVerticalBox::Slot() .AutoHeight() [ WindowHeightProperty->CreatePropertyNameWidget( LOCTEXT( "ViewportHeightLabel", "Viewport Height" ) ) ] + SVerticalBox::Slot() .AutoHeight() [ WindowHeightProperty->CreatePropertyValueWidget() ] ] ] ]; } FReply SScreenResolutionCustomization::HandleSwapAspectRatioClicked() { FString HeightString; WindowHeightProperty->GetValueAsDisplayString( HeightString ); FString WidthString; WindowWidthProperty->GetValueAsDisplayString( WidthString ); int32 NewHeight = FCString::Atoi( *WidthString ); int32 NewWidth = FCString::Atoi( *HeightString ); ULevelEditorPlaySettings* PlayInSettings = GetMutableDefault(); const UDeviceProfile* DeviceProfile = UDeviceProfileManager::Get().FindProfile(PlayInSettings->DeviceToEmulate, false); if ( DeviceProfile ) { // Rescale the swapped sizes if we are on Android if ( DeviceProfile && DeviceProfile->DeviceType == TEXT( "Android" ) ) { float ScaleFactor = 1.0f; PlayInSettings->RescaleForMobilePreview( DeviceProfile, NewWidth, NewHeight, ScaleFactor ); } } WindowHeightProperty->SetValue(NewHeight); WindowWidthProperty->SetValue(NewWidth); return FReply::Handled(); } void SScreenResolutionCustomization::HandleCommonResolutionSelected( const FPlayScreenResolution Resolution ) { int32 Width = Resolution.LogicalWidth; int32 Height = Resolution.LogicalHeight; ULevelEditorPlaySettings* PlayInSettings = GetMutableDefault(); // Maintain previous orientation (i.e., swap Width and Height if required) int32 PreviousWidth = -1; int32 PreviousHeight = -1; if ( WindowWidthProperty->GetValue( PreviousWidth ) == FPropertyAccess::Success && WindowHeightProperty->GetValue( PreviousHeight ) == FPropertyAccess::Success ) { const bool bIsOrientationPreserved = (PreviousWidth < PreviousHeight) != (Width < Height); if ( bIsOrientationPreserved ) { Width = Resolution.LogicalHeight; Height = Resolution.LogicalWidth; } } UDeviceProfile* DeviceProfile = UDeviceProfileManager::Get().FindProfile( Resolution.ProfileName, false ); if ( DeviceProfile ) { PlayInSettings->DeviceToEmulate = Resolution.ProfileName; } else { PlayInSettings->DeviceToEmulate = FString(); } if ( (PreviousWidth == Width) && (PreviousHeight == Height) ) { // Since the values are identical, the SetValue on the property handle will not actually change the value and trigger OnPropertyValueChanged, so call our handler directly OnSizeChanged(); } else { WindowHeightProperty->SetValue(Height); WindowWidthProperty->SetValue(Width); } } const FSlateBrush* SScreenResolutionCustomization::GetAspectRatioSwitchImage() const { FString HeightString; WindowHeightProperty->GetValueAsDisplayString( HeightString ); int32 Height = FCString::Atoi( *HeightString ); FString WidthString; WindowWidthProperty->GetValueAsDisplayString( WidthString ); int32 Width = FCString::Atoi( *WidthString ); if ( Height > Width ) { return FAppStyle::Get().GetBrush( "UMGEditor.OrientPortrait" ); } return FAppStyle::Get().GetBrush( "UMGEditor.OrientLandscape" ); } void SScreenResolutionCustomization::OnSizeChanged() { GetMutableDefault()->UpdateCustomSafeZones(); } FUIAction SScreenResolutionCustomization::GetResolutionMenuAction( const FPlayScreenResolution& ScreenResolution ) { return FUIAction( FExecuteAction::CreateRaw( this, &SScreenResolutionCustomization::HandleCommonResolutionSelected, ScreenResolution ) ); } TSharedRef SScreenResolutionCustomization::GetResolutionsMenu() { UCommonResolutionMenuContext* CommonResolutionMenuContext = NewObject(); CommonResolutionMenuContext->GetUIActionFromLevelPlaySettings = UCommonResolutionMenuContext::FGetUIActionFromLevelPlaySettings::CreateRaw(this, &SScreenResolutionCustomization::GetResolutionMenuAction); return UToolMenus::Get()->GenerateWidget(ULevelEditorPlaySettings::GetCommonResolutionsMenuName(), CommonResolutionMenuContext); } /** Virtual destructor. */ FLevelEditorPlaySettingsCustomization::~FLevelEditorPlaySettingsCustomization() { } void FLevelEditorPlaySettingsCustomization::CustomizeDetails( IDetailLayoutBuilder& LayoutBuilder ) { const float MaxPropertyWidth = 400.0f; // play in editor settings IDetailCategoryBuilder& PlayInEditorCategory = LayoutBuilder.EditCategory( "PlayInEditor" ); { TArray> PIECategoryProperties; PlayInEditorCategory.GetDefaultProperties( PIECategoryProperties, true, false ); TSharedPtr PIEEnableSoundHandle = LayoutBuilder.GetProperty( GET_MEMBER_NAME_CHECKED( ULevelEditorPlaySettings, EnableGameSound ) ); PIESoundQualityLevelHandle = LayoutBuilder.GetProperty( GET_MEMBER_NAME_CHECKED( ULevelEditorPlaySettings, PlayInEditorSoundQualityLevel ) ); PIESoundQualityLevelHandle->MarkHiddenByCustomization(); for ( TSharedRef& PropertyHandle : PIECategoryProperties ) { if ( PropertyHandle->GetProperty() != PIESoundQualityLevelHandle->GetProperty() ) { PlayInEditorCategory.AddProperty( PropertyHandle ); } if ( PropertyHandle->GetProperty() == PIEEnableSoundHandle->GetProperty() ) { PlayInEditorCategory.AddCustomRow( PIESoundQualityLevelHandle->GetPropertyDisplayName(), false ) .NameContent() [ PIESoundQualityLevelHandle->CreatePropertyNameWidget() ] .ValueContent() .MaxDesiredWidth( MaxPropertyWidth ) [ SAssignNew( QualityLevelComboBox, SComboBox> ) .OptionsSource( &AvailableQualityLevels ) .OnComboBoxOpening( this, &FLevelEditorPlaySettingsCustomization::HandleQualityLevelComboBoxOpening ) .OnGenerateWidget( this, &FLevelEditorPlaySettingsCustomization::HandleQualityLevelComboBoxGenerateWidget ) .OnSelectionChanged( this, &FLevelEditorPlaySettingsCustomization::HandleQualityLevelSelectionChanged ) [ SNew( STextBlock ) .Text( this, &FLevelEditorPlaySettingsCustomization::GetSelectedQualityLevelName ) ] ]; } } } IDetailCategoryBuilder& GameViewportSettings = LayoutBuilder.EditCategory( "GameViewportSettings" ); { // new window resolution TSharedRef WindowHeightHandle = LayoutBuilder.GetProperty( GET_MEMBER_NAME_CHECKED( ULevelEditorPlaySettings, NewWindowHeight ) ); TSharedRef WindowWidthHandle = LayoutBuilder.GetProperty( GET_MEMBER_NAME_CHECKED( ULevelEditorPlaySettings, NewWindowWidth ) ); TSharedRef WindowPositionHandle = LayoutBuilder.GetProperty( GET_MEMBER_NAME_CHECKED( ULevelEditorPlaySettings, NewWindowPosition ) ); TSharedRef CenterNewWindowHandle = LayoutBuilder.GetProperty( GET_MEMBER_NAME_CHECKED( ULevelEditorPlaySettings, CenterNewWindow ) ); TSharedRef EmulatedDeviceHandle = LayoutBuilder.GetProperty( GET_MEMBER_NAME_CHECKED( ULevelEditorPlaySettings, DeviceToEmulate ) ); WindowHeightHandle->MarkHiddenByCustomization(); WindowWidthHandle->MarkHiddenByCustomization(); WindowPositionHandle->MarkHiddenByCustomization(); CenterNewWindowHandle->MarkHiddenByCustomization(); EmulatedDeviceHandle->MarkHiddenByCustomization(); GameViewportSettings.AddCustomRow( LOCTEXT( "NewViewportResolutionRow", "New Viewport Resolution" ), false ) .NameContent() [ SNew( STextBlock ) .Font( LayoutBuilder.GetDetailFont() ) .Text( LOCTEXT( "NewViewportResolutionName", "New Viewport Resolution" ) ) .ToolTipText( LOCTEXT( "NewWindowSizeTooltip", "Sets the width and height of floating PIE windows (in pixels)" ) ) ] .ValueContent() .MaxDesiredWidth( MaxPropertyWidth ) [ SNew( SScreenResolutionCustomization, &LayoutBuilder, WindowHeightHandle, WindowWidthHandle ) ]; GameViewportSettings.AddCustomRow( LOCTEXT( "NewWindowPositionRow", "New Window Position" ), false ) .NameContent() [ SNew( STextBlock ) .Font( LayoutBuilder.GetDetailFont() ) .Text( LOCTEXT( "NewWindowPositionName", "New Window Position" ) ) .ToolTipText( LOCTEXT( "NewWindowPositionTooltip", "Sets the screen coordinates for the top-left corner of floating PIE windows (in pixels)" ) ) ] .ValueContent() .MaxDesiredWidth( MaxPropertyWidth ) [ SNew( SScreenPositionCustomization, &LayoutBuilder, WindowPositionHandle, CenterNewWindowHandle ) ]; GameViewportSettings.AddCustomRow( LOCTEXT( "SafeZonePreviewName", "Safe Zone Preview" ), false ) .NameContent() [ SNew( STextBlock ) .Font( LayoutBuilder.GetDetailFont() ) .Text( LOCTEXT( "SafeZonePreviewName", "Safe Zone Preview" ) ) ] .ValueContent() [ SNew( STextBlock ) .Font( LayoutBuilder.GetDetailFont() ) .Text( this, &FLevelEditorPlaySettingsCustomization::GetPreviewText ) .ToolTipText(this, &FLevelEditorPlaySettingsCustomization::GetPreviewTextToolTipText) ]; } // play in new window settings IDetailCategoryBuilder& PlayInNewWindowCategory = LayoutBuilder.EditCategory( "PlayInNewWindow" ); { // Mac does not support parenting, do not show #if PLATFORM_MAC PlayInNewWindowCategory.AddProperty( "PIEAlwaysOnTop" ) .DisplayName( LOCTEXT( "PIEAlwaysOnTop", "Always On Top" ) ) .IsEnabled( false ); #else PlayInNewWindowCategory.AddProperty( "PIEAlwaysOnTop" ) .DisplayName( LOCTEXT( "PIEAlwaysOnTop", "Always On Top" ) ); #endif } // play in standalone game settings IDetailCategoryBuilder& PlayInStandaloneCategory = LayoutBuilder.EditCategory( "PlayInStandaloneGame" ); { // command line options TSharedPtr DisableStandaloneSoundProperty = LayoutBuilder.GetProperty( GET_MEMBER_NAME_CHECKED( ULevelEditorPlaySettings, DisableStandaloneSound ) ); DisableStandaloneSoundProperty->MarkHiddenByCustomization(); PlayInStandaloneCategory.AddCustomRow( LOCTEXT( "AdditionalStandaloneDetails", "Additional Options" ), false ) .NameContent() [ SNew( STextBlock ) .Font( LayoutBuilder.GetDetailFont() ) .Text( LOCTEXT( "ClientCmdLineName", "Command Line Options" ) ) .ToolTipText( LOCTEXT( "ClientCmdLineTooltip", "Generates a command line for additional settings that will be passed to the game clients." ) ) ] .ValueContent() .MaxDesiredWidth( MaxPropertyWidth ) [ SNew( SHorizontalBox ) + SHorizontalBox::Slot() .AutoWidth() [ DisableStandaloneSoundProperty->CreatePropertyValueWidget() ] + SHorizontalBox::Slot() .Padding( 0.0f, 2.5f ) .VAlign( VAlign_Center ) .AutoWidth() [ DisableStandaloneSoundProperty->CreatePropertyNameWidget( LOCTEXT( "DisableStandaloneSoundLabel", "Disable Sound (-nosound)" ) ) ] ]; } // multi-player options IDetailCategoryBuilder& NetworkCategory = LayoutBuilder.EditCategory( "Multiplayer Options" ); TArray> AllProperties; NetworkCategory.GetDefaultProperties( AllProperties ); { // Add all the default properties in before we add custom rows at the bottom. for ( TSharedRef DefaultProperty : AllProperties ) { NetworkCategory.AddProperty( DefaultProperty ); } // client window size TSharedRef WindowHeightHandle = LayoutBuilder.GetProperty( "ClientWindowHeight" ); TSharedRef WindowWidthHandle = LayoutBuilder.GetProperty( "ClientWindowWidth" ); WindowHeightHandle->MarkHiddenByCustomization(); WindowWidthHandle->MarkHiddenByCustomization(); NetworkCategory.AddCustomRow( LOCTEXT( "PlayInNetworkViewportSize", "Viewport Size\n(Additional Clients)" ), false ) .NameContent() [ WindowHeightHandle->CreatePropertyNameWidget( LOCTEXT( "ClientViewportSizeName", "Multiplayer Viewport Size (in pixels)" ), LOCTEXT( "ClientWindowSizeTooltip", "Width and Height to use when spawning additional clients. Useful when you need multiple clients connected but only interact with one window." ) ) ] .ValueContent() .MaxDesiredWidth( MaxPropertyWidth ) [ SNew( SScreenResolutionCustomization, &LayoutBuilder, WindowHeightHandle, WindowWidthHandle ) ] .IsEnabled( TAttribute( this, &FLevelEditorPlaySettingsCustomization::HandleClientWindowSizePropertyIsEnabled ) ); NetworkCategory.AddCustomRow( LOCTEXT( "AdditionalMultiplayerDetails", "Additional Options" ), true ) .NameContent() [ SNew( STextBlock ) .Font( LayoutBuilder.GetDetailFont() ) .Text( LOCTEXT( "PlainTextName", "Play In Editor Description" ) ) .ToolTipText( LOCTEXT( "PlainTextToolTip", "A brief description of the multiplayer settings and what to expect if you play with them in the editor." ) ) ] .ValueContent() .MaxDesiredWidth( MaxPropertyWidth ) [ SNew( STextBlock ) .Font( LayoutBuilder.GetDetailFont() ) .Text( this, &FLevelEditorPlaySettingsCustomization::HandleMultiplayerOptionsDescription ) .WrapTextAt( MaxPropertyWidth ) ]; } } TSharedRef FLevelEditorPlaySettingsCustomization::MakeInstance() { return MakeShareable( new FLevelEditorPlaySettingsCustomization() ); } FText FLevelEditorPlaySettingsCustomization::HandleMultiplayerOptionsDescription() const { const ULevelEditorPlaySettings* PlayInSettings = GetDefault(); const bool bLaunchSeparateServer = PlayInSettings->bLaunchSeparateServer; const bool CanRunUnderOneProcess = [&PlayInSettings] { bool RunUnderOneProcess( false ); return (PlayInSettings->GetRunUnderOneProcess( RunUnderOneProcess ) && RunUnderOneProcess); }(); const int32 PlayNumberOfClients = [&PlayInSettings] { int32 NumClients( 0 ); return (PlayInSettings->GetPlayNumberOfClients( NumClients ) ? NumClients : 0); }(); const EPlayNetMode PlayNetMode = [&PlayInSettings] { EPlayNetMode NetMode( PIE_Standalone ); return (PlayInSettings->GetPlayNetMode( NetMode ) ? NetMode : PIE_Standalone); }(); FString Desc; if ( CanRunUnderOneProcess ) { Desc += LOCTEXT( "MultiplayerDescription_OneProcess", "The following will all run under one UE instance:\n" ).ToString(); if ( PlayNetMode == EPlayNetMode::PIE_Client ) { Desc += LOCTEXT( "MultiplayerDescription_DedicatedServerHidden", "A hidden dedicated server instance will run in editor. " ).ToString(); if ( PlayNumberOfClients == 1 ) { Desc += LOCTEXT( "MultiplayerDescription_EditorClient", "The editor will connect as a client. " ).ToString(); } else { Desc += FText::Format( LOCTEXT( "MultiplayerDescription_EditorAndClients", "The editor will connect as a client and {0} additional client window(s) will also connect. " ), FText::AsNumber( PlayNumberOfClients - 1 ) ).ToString(); } } else if ( PlayNetMode == EPlayNetMode::PIE_ListenServer ) { if ( PlayNumberOfClients == 1 ) { Desc += LOCTEXT( "MultiplayerDescription_EditorListenServer", "The editor will run as a listen server. " ).ToString(); } else { Desc += FText::Format( LOCTEXT( "MultiplayerDescription_EditorListenServerAndClients", "The editor will run as a listen server and {0} additional client window(s) will also connect to it. " ), FText::AsNumber( PlayNumberOfClients - 1 ) ).ToString(); } } else { if ( PlayNumberOfClients == 1 ) { Desc += LOCTEXT( "MultiplayerDescription_EditorStandalone", "The editor will run in offline mode. " ).ToString(); } else { Desc += FText::Format( LOCTEXT( "MultiplayerDescription_StandaloneAndClients", "The editor will run offline and {0} additional offline mode window(s) will also open. " ), FText::AsNumber( PlayNumberOfClients - 1 ) ).ToString(); } if ( bLaunchSeparateServer ) { Desc += LOCTEXT( "MultiplayerDescription_StandaloneSeparateServer", "\nAn additional server instance will be launched but not connected to. Use \"open 127.0.0.1:\" to connect. " ).ToString(); } } } else { Desc += LOCTEXT( "MultiplayerDescription_MultiProcess", "The following will run with multiple UE instances:\n" ).ToString(); if ( PlayNetMode == PIE_Standalone ) { if ( PlayNumberOfClients == 1 ) { Desc += LOCTEXT( "MultiplayerDescription_EditorStandalone", "The editor will run in offline mode. " ).ToString(); } else { Desc += FText::Format( LOCTEXT( "MultiplayerDescription_StandaloneAndClients", "The editor will run offline and {0} additional offline mode window(s) will also open. " ), FText::AsNumber( PlayNumberOfClients - 1 ) ).ToString(); } if ( bLaunchSeparateServer ) { Desc += LOCTEXT( "MultiplayerDescription_StandaloneSeparateServer", "\nAn additional server instance will be launched but not connected to. Use \"open 127.0.0.1:\" to connect. " ).ToString(); } } else if ( PlayNetMode == PIE_ListenServer ) { if ( PlayNumberOfClients == 1 ) { Desc += LOCTEXT( "MultiplayerDescription_EditorListenServer", "The editor will run as a listen server. " ).ToString(); } else { Desc += FText::Format( LOCTEXT( "MultiplayerDescription_EditorListenServerAndClients", "The editor will run as a listen server and {0} additional client window(s) will also connect to it. " ), FText::AsNumber( PlayNumberOfClients - 1 ) ).ToString(); } } else { // Client requires additional dedicated server instance Desc += LOCTEXT( "MultiplayerDescription_DedicatedServerNewWindow", "A dedicated server will open in a new window. " ).ToString(); if ( PlayNumberOfClients == 1 ) { Desc += LOCTEXT( "MultiplayerDescription_EditorClient", "The editor will connect as a client. " ).ToString(); } else { Desc += FText::Format( LOCTEXT( "MultiplayerDescription_EditorAndClients", "The editor will connect as a client and {0} additional client window(s) will also connect. " ), FText::AsNumber( PlayNumberOfClients - 1 ) ).ToString(); } } } return FText::FromString( Desc ); } bool FLevelEditorPlaySettingsCustomization::HandleClientWindowSizePropertyIsEnabled() const { return GetDefault()->IsClientWindowSizeActive(); } bool FLevelEditorPlaySettingsCustomization::HandleGameOptionsIsEnabled() const { return GetDefault()->IsAdditionalServerGameOptionsActive(); } bool FLevelEditorPlaySettingsCustomization::HandleRerouteInputToSecondWindowEnabled() const { return GetDefault()->IsRouteGamepadToSecondWindowActive(); } EVisibility FLevelEditorPlaySettingsCustomization::HandleRerouteInputToSecondWindowVisibility() const { return GetDefault()->GetRouteGamepadToSecondWindowVisibility(); } void FLevelEditorPlaySettingsCustomization::HandleQualityLevelComboBoxOpening() { const UAudioSettings* AudioSettings = GetDefault(); AvailableQualityLevels.Empty( AudioSettings->QualityLevels.Num() ); for ( const FAudioQualitySettings& AQSettings : AudioSettings->QualityLevels ) { AvailableQualityLevels.Add( MakeShareable( new FString( AQSettings.DisplayName.ToString() ) ) ); } QualityLevelComboBox->RefreshOptions(); } TSharedRef FLevelEditorPlaySettingsCustomization::HandleQualityLevelComboBoxGenerateWidget( TSharedPtr InItem ) { return SNew( STextBlock ) .Text( FText::FromString( *InItem ) ); } void FLevelEditorPlaySettingsCustomization::HandleQualityLevelSelectionChanged( TSharedPtr InSelection, ESelectInfo::Type SelectInfo ) { if ( InSelection.IsValid() ) { const UAudioSettings* AudioSettings = GetDefault(); for ( int32 QualityLevel = 0; QualityLevel < AudioSettings->QualityLevels.Num(); ++QualityLevel ) { if ( AudioSettings->QualityLevels[QualityLevel].DisplayName.ToString() == *InSelection ) { PIESoundQualityLevelHandle->SetValue( QualityLevel ); break; } } } } FText FLevelEditorPlaySettingsCustomization::GetSelectedQualityLevelName() const { int32 QualityLevel = 0; PIESoundQualityLevelHandle->GetValue( QualityLevel ); const UAudioSettings* AudioSettings = GetDefault(); return (QualityLevel >= 0 && QualityLevel < AudioSettings->QualityLevels.Num() ? AudioSettings->QualityLevels[QualityLevel].DisplayName : FText::GetEmpty()); } FText FLevelEditorPlaySettingsCustomization::GetPreviewText() const { const ULevelEditorPlaySettings* LevelEditorPlaySettings = GetDefault(); float SafeZone = FDisplayMetrics::GetDebugTitleSafeZoneRatio(); if ( FMath::IsNearlyEqual( SafeZone, 1.0f ) ) { if ( LevelEditorPlaySettings->DeviceToEmulate.IsEmpty() || LevelEditorPlaySettings->PIESafeZoneOverride.GetDesiredSize().IsZero() ) { return LOCTEXT( "NoSafeZoneSet", "No Device Safe Zone Set" ); } else { return FText::FromString( LevelEditorPlaySettings->DeviceToEmulate ); } } else { return FText::Format( LOCTEXT( "UniformSafeZone", "Uniform Safe Zone: {0}" ), FText::AsNumber( SafeZone ) ); } } FText FLevelEditorPlaySettingsCustomization::GetPreviewTextToolTipText() const { if (FDisplayMetrics::GetDebugTitleSafeZoneRatio() < 1.0f) { return LOCTEXT("SafeZoneSetFromRatioCvarToolTip", "Uniform Safe Zone was set from cvar (r.DebugSafeZone.TitleRatio)."); } const ULevelEditorPlaySettings* LevelEditorPlaySettings = GetDefault(); if (!LevelEditorPlaySettings->DeviceToEmulate.IsEmpty() && !LevelEditorPlaySettings->PIESafeZoneOverride.GetDesiredSize().IsZero() ) { FMargin PIESafeZoneOverride = LevelEditorPlaySettings->PIESafeZoneOverride; FNumberFormattingOptions FormattingOptions; FormattingOptions.SetMaximumFractionalDigits(2); FormattingOptions.SetMinimumFractionalDigits(2); return FText::Format( LOCTEXT("CustomSafeZoneSetFromDevice", "PIE safe zone override set from device {0} profile.\nLeft: {1}, Top: {2}, Right: {3}, Bottom: {4}"), FText::FromString(LevelEditorPlaySettings->DeviceToEmulate), FText::AsNumber(PIESafeZoneOverride.Left, &FormattingOptions), FText::AsNumber(PIESafeZoneOverride.Top, &FormattingOptions), FText::AsNumber(PIESafeZoneOverride.Right, &FormattingOptions), FText::AsNumber(PIESafeZoneOverride.Bottom, &FormattingOptions) ); } return FText(); } #undef LOCTEXT_NAMESPACE