577 lines
29 KiB
C++
577 lines
29 KiB
C++
|
|
// Copyright Epic Games, Inc. All Rights Reserved.
|
||
|
|
|
||
|
|
#include "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 "Runtime/Engine/Classes/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<IPropertyHandle>& InWindowPositionProperty, const TSharedRef<IPropertyHandle>& 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<IPropertyHandle>& InWindowHeightProperty, const TSharedRef<IPropertyHandle>& 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<ULevelEditorPlaySettings>();
|
||
|
|
|
||
|
|
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<ULevelEditorPlaySettings>();
|
||
|
|
// 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 FEditorStyle::Get().GetBrush("UMGEditor.OrientPortrait");
|
||
|
|
}
|
||
|
|
return FEditorStyle::Get().GetBrush("UMGEditor.OrientLandscape");
|
||
|
|
}
|
||
|
|
|
||
|
|
void SScreenResolutionCustomization::OnSizeChanged()
|
||
|
|
{
|
||
|
|
GetMutableDefault<ULevelEditorPlaySettings>()->UpdateCustomSafeZones();
|
||
|
|
}
|
||
|
|
|
||
|
|
FUIAction SScreenResolutionCustomization::GetResolutionMenuAction(const FPlayScreenResolution& ScreenResolution)
|
||
|
|
{
|
||
|
|
return FUIAction(FExecuteAction::CreateRaw(this, &SScreenResolutionCustomization::HandleCommonResolutionSelected, ScreenResolution));
|
||
|
|
}
|
||
|
|
|
||
|
|
TSharedRef<SWidget> SScreenResolutionCustomization::GetResolutionsMenu()
|
||
|
|
{
|
||
|
|
UCommonResolutionMenuContext* CommonResolutionMenuContext = NewObject<UCommonResolutionMenuContext>();
|
||
|
|
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<TSharedRef<IPropertyHandle>> PIECategoryProperties;
|
||
|
|
PlayInEditorCategory.GetDefaultProperties(PIECategoryProperties, true, false);
|
||
|
|
|
||
|
|
TSharedPtr<IPropertyHandle> PIEEnableSoundHandle = LayoutBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(ULevelEditorPlaySettings, EnableGameSound));
|
||
|
|
PIESoundQualityLevelHandle = LayoutBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(ULevelEditorPlaySettings, PlayInEditorSoundQualityLevel));
|
||
|
|
PIESoundQualityLevelHandle->MarkHiddenByCustomization();
|
||
|
|
|
||
|
|
for (TSharedRef<IPropertyHandle>& 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<TSharedPtr<FString>>)
|
||
|
|
.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<IPropertyHandle> WindowHeightHandle = LayoutBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(ULevelEditorPlaySettings, NewWindowHeight));
|
||
|
|
TSharedRef<IPropertyHandle> WindowWidthHandle = LayoutBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(ULevelEditorPlaySettings, NewWindowWidth));
|
||
|
|
TSharedRef<IPropertyHandle> WindowPositionHandle = LayoutBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(ULevelEditorPlaySettings, NewWindowPosition));
|
||
|
|
TSharedRef<IPropertyHandle> CenterNewWindowHandle = LayoutBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(ULevelEditorPlaySettings, CenterNewWindow));
|
||
|
|
TSharedRef<IPropertyHandle> 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<IPropertyHandle> 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<TSharedRef<IPropertyHandle>> AllProperties;
|
||
|
|
NetworkCategory.GetDefaultProperties(AllProperties);
|
||
|
|
|
||
|
|
{
|
||
|
|
// Add all the default properties in before we add custom rows at the bottom.
|
||
|
|
for (TSharedRef<IPropertyHandle> DefaultProperty: AllProperties)
|
||
|
|
{
|
||
|
|
NetworkCategory.AddProperty(DefaultProperty);
|
||
|
|
}
|
||
|
|
|
||
|
|
// client window size
|
||
|
|
TSharedRef<IPropertyHandle> WindowHeightHandle = LayoutBuilder.GetProperty("ClientWindowHeight");
|
||
|
|
TSharedRef<IPropertyHandle> 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<bool>(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<IDetailCustomization> FLevelEditorPlaySettingsCustomization::MakeInstance()
|
||
|
|
{
|
||
|
|
return MakeShareable(new FLevelEditorPlaySettingsCustomization());
|
||
|
|
}
|
||
|
|
|
||
|
|
FText FLevelEditorPlaySettingsCustomization::HandleMultiplayerOptionsDescription() const
|
||
|
|
{
|
||
|
|
const ULevelEditorPlaySettings* PlayInSettings = GetDefault<ULevelEditorPlaySettings>();
|
||
|
|
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 UE4 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:<port>\" to connect. ").ToString();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
Desc += LOCTEXT("MultiplayerDescription_MultiProcess", "The following will run with multiple UE4 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:<port>\" 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<ULevelEditorPlaySettings>()->IsClientWindowSizeActive();
|
||
|
|
}
|
||
|
|
|
||
|
|
bool FLevelEditorPlaySettingsCustomization::HandleGameOptionsIsEnabled() const
|
||
|
|
{
|
||
|
|
return GetDefault<ULevelEditorPlaySettings>()->IsAdditionalServerGameOptionsActive();
|
||
|
|
}
|
||
|
|
|
||
|
|
bool FLevelEditorPlaySettingsCustomization::HandleRerouteInputToSecondWindowEnabled() const
|
||
|
|
{
|
||
|
|
return GetDefault<ULevelEditorPlaySettings>()->IsRouteGamepadToSecondWindowActive();
|
||
|
|
}
|
||
|
|
|
||
|
|
EVisibility FLevelEditorPlaySettingsCustomization::HandleRerouteInputToSecondWindowVisibility() const
|
||
|
|
{
|
||
|
|
return GetDefault<ULevelEditorPlaySettings>()->GetRouteGamepadToSecondWindowVisibility();
|
||
|
|
}
|
||
|
|
|
||
|
|
void FLevelEditorPlaySettingsCustomization::HandleQualityLevelComboBoxOpening()
|
||
|
|
{
|
||
|
|
const UAudioSettings* AudioSettings = GetDefault<UAudioSettings>();
|
||
|
|
AvailableQualityLevels.Empty(AudioSettings->QualityLevels.Num());
|
||
|
|
for (const FAudioQualitySettings& AQSettings: AudioSettings->QualityLevels)
|
||
|
|
{
|
||
|
|
AvailableQualityLevels.Add(MakeShareable(new FString(AQSettings.DisplayName.ToString())));
|
||
|
|
}
|
||
|
|
QualityLevelComboBox->RefreshOptions();
|
||
|
|
}
|
||
|
|
|
||
|
|
TSharedRef<SWidget> FLevelEditorPlaySettingsCustomization::HandleQualityLevelComboBoxGenerateWidget(TSharedPtr<FString> InItem)
|
||
|
|
{
|
||
|
|
return SNew(STextBlock)
|
||
|
|
.Text(FText::FromString(*InItem));
|
||
|
|
}
|
||
|
|
|
||
|
|
void FLevelEditorPlaySettingsCustomization::HandleQualityLevelSelectionChanged(TSharedPtr<FString> InSelection, ESelectInfo::Type SelectInfo)
|
||
|
|
{
|
||
|
|
if (InSelection.IsValid())
|
||
|
|
{
|
||
|
|
const UAudioSettings* AudioSettings = GetDefault<UAudioSettings>();
|
||
|
|
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<UAudioSettings>();
|
||
|
|
return (QualityLevel >= 0 && QualityLevel < AudioSettings->QualityLevels.Num() ? AudioSettings->QualityLevels[QualityLevel].DisplayName : FText::GetEmpty());
|
||
|
|
}
|
||
|
|
|
||
|
|
FText FLevelEditorPlaySettingsCustomization::GetPreviewText() const
|
||
|
|
{
|
||
|
|
const ULevelEditorPlaySettings* LevelEditorPlaySettings = GetDefault<ULevelEditorPlaySettings>();
|
||
|
|
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<ULevelEditorPlaySettings>();
|
||
|
|
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
|