Files
UnrealEngineUWP/Engine/Source/Editor/DetailCustomizations/Private/CameraDetails.cpp
jon cain 9087ca8fab Add additional Orthographic Camera settings for further user control granularity per camera instead of relying on CVars, and manually set the PT_OrthoCamera test to ignore the automatic plane resolution logic to avoid regression.
No serialization is occuring because these should be enabled on all ortho cameras by default, even older ones as old ortho scenes did not work correctly in any sample project, however some edge cases are expected. With serialization, many cameras would need their settings manually updated, whereas without serialization, the edge cases in older cameras are rare because Ortho has not worked for so long and in general these settings will improve those cameras.
SceneCaptures are unaffected as their default logic should always be false due to their much wider use cases.

#jira UE-210297
#rb chris.kulla


#virtualized

[CL 32449978 by jon cain in ue5-main branch]
2024-03-22 16:54:41 -04:00

337 lines
12 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "CameraDetails.h"
#include "Camera/CameraComponent.h"
#include "Containers/Array.h"
#include "Containers/UnrealString.h"
#include "Delegates/Delegate.h"
#include "DetailCategoryBuilder.h"
#include "DetailLayoutBuilder.h"
#include "DetailWidgetRow.h"
#include "EditorCategoryUtils.h"
#include "Fonts/SlateFontInfo.h"
#include "Framework/Commands/UIAction.h"
#include "Framework/MultiBox/MultiBoxBuilder.h"
#include "HAL/PlatformCrt.h"
#include "IDetailPropertyRow.h"
#include "Internationalization/Internationalization.h"
#include "Internationalization/Text.h"
#include "Layout/Margin.h"
#include "Math/UnrealMathSSE.h"
#include "Misc/AssertionMacros.h"
#include "Misc/Attribute.h"
#include "PropertyEditorModule.h"
#include "PropertyHandle.h"
#include "SlateOptMacros.h"
#include "SlotBase.h"
#include "Styling/AppStyle.h"
#include "Styling/SlateColor.h"
#include "Textures/SlateIcon.h"
#include "UObject/Class.h"
#include "UObject/Object.h"
#include "UObject/WeakObjectPtr.h"
#include "UObject/WeakObjectPtrTemplates.h"
#include "Widgets/DeclarativeSyntaxSupport.h"
#include "Widgets/Input/SComboButton.h"
#include "Widgets/Input/SEditableTextBox.h"
#include "Widgets/Input/SNumericEntryBox.h"
#include "Widgets/SBoxPanel.h"
#define LOCTEXT_NAMESPACE "CameraDetails"
const float FCameraDetails::MinAspectRatio = 0.1f;
const float FCameraDetails::MaxAspectRatio = 100.0f;
const float FCameraDetails::LowestCommonAspectRatio = 1.0f;
const float FCameraDetails::HighestCommonAspectRatio = 2.5f;
TSharedRef<IDetailCustomization> FCameraDetails::MakeInstance()
{
return MakeShareable( new FCameraDetails );
}
BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
void FCameraDetails::CustomizeDetails( IDetailLayoutBuilder& DetailLayout )
{
FSlateFontInfo FontStyle = FAppStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"));
LastParsedAspectRatioValue = -1.0f;
TSharedPtr<IPropertyHandle> bConstrainAspectRatioProperty = DetailLayout.GetProperty(GET_MEMBER_NAME_CHECKED(UCameraComponent, bConstrainAspectRatio));
TSharedPtr<IPropertyHandle> ProjectionModeProperty = DetailLayout.GetProperty(GET_MEMBER_NAME_CHECKED(UCameraComponent, ProjectionMode));
AspectRatioProperty = DetailLayout.GetProperty(GET_MEMBER_NAME_CHECKED(UCameraComponent, AspectRatio));
check(AspectRatioProperty.IsValid());
if (AspectRatioProperty.IsValid() && AspectRatioProperty->IsValidHandle())
{
FSimpleDelegate OnAspectRatioChangedDelegate = FSimpleDelegate::CreateSP(this, &FCameraDetails::OnAspectRatioChanged);
AspectRatioProperty->SetOnPropertyValueChanged(OnAspectRatioChangedDelegate);
}
// see if CameraSettings category should be hidden
bool bCameraSettingsHidden = false;
{
TArray< TWeakObjectPtr<UObject> > Objects;
DetailLayout.GetObjectsBeingCustomized(Objects);
for (TWeakObjectPtr<UObject> ObjWP : Objects)
{
UObject* const Obj = ObjWP.Get();
if (Obj && (FEditorCategoryUtils::IsCategoryHiddenFromClass(Obj->GetClass(), TEXT("CameraSettings"))))
{
bCameraSettingsHidden = true;
break;
}
}
}
DetailLayout.EditCategory("Current Camera Settings", FText::GetEmpty(), ECategoryPriority::Important);
if (bCameraSettingsHidden == false)
{
IDetailCategoryBuilder& CameraCategory = DetailLayout.EditCategory( "CameraSettings", FText::GetEmpty(), ECategoryPriority::Important );
// Organize the properties
CameraCategory.AddProperty(ProjectionModeProperty);
// Perspective-specific properties
IDetailPropertyRow& FieldOfViewRow = CameraCategory.AddProperty(DetailLayout.GetProperty(GET_MEMBER_NAME_CHECKED(UCameraComponent, FieldOfView)));
FieldOfViewRow.Visibility( TAttribute<EVisibility>::Create(TAttribute<EVisibility>::FGetter::CreateSP( this, &FCameraDetails::ProjectionModeMatches, ProjectionModeProperty, ECameraProjectionMode::Perspective ) ) );
// Orthographic-specific properties
TAttribute<EVisibility> OrthographicVisibility = TAttribute<EVisibility>::Create(TAttribute<EVisibility>::FGetter::CreateSP(this, &FCameraDetails::ProjectionModeMatches, ProjectionModeProperty, ECameraProjectionMode::Orthographic));
IDetailPropertyRow& OrthoWidthRow = CameraCategory.AddProperty(DetailLayout.GetProperty(GET_MEMBER_NAME_CHECKED(UCameraComponent, OrthoWidth)));
OrthoWidthRow.Visibility(OrthographicVisibility);
IDetailPropertyRow& AutoCalculateOrthoPlanesRow = CameraCategory.AddProperty(DetailLayout.GetProperty(GET_MEMBER_NAME_CHECKED(UCameraComponent, bAutoCalculateOrthoPlanes)));
AutoCalculateOrthoPlanesRow.Visibility(OrthographicVisibility);
IDetailPropertyRow& AutoPlanesShift = CameraCategory.AddProperty(DetailLayout.GetProperty(GET_MEMBER_NAME_CHECKED(UCameraComponent, AutoPlaneShift)));
AutoPlanesShift.Visibility(OrthographicVisibility);
IDetailPropertyRow& OrthoNearClipPlaneRow = CameraCategory.AddProperty(DetailLayout.GetProperty(GET_MEMBER_NAME_CHECKED(UCameraComponent, OrthoNearClipPlane)));
OrthoNearClipPlaneRow.Visibility(OrthographicVisibility);
IDetailPropertyRow& OrthoFarClipPlaneRow = CameraCategory.AddProperty(DetailLayout.GetProperty(GET_MEMBER_NAME_CHECKED(UCameraComponent, OrthoFarClipPlane)));
OrthoFarClipPlaneRow.Visibility(OrthographicVisibility);
IDetailPropertyRow& UpdateOrthoPlanesRow = CameraCategory.AddProperty(DetailLayout.GetProperty(GET_MEMBER_NAME_CHECKED(UCameraComponent, bUpdateOrthoPlanes)));
UpdateOrthoPlanesRow.Visibility(OrthographicVisibility);
IDetailPropertyRow& CameraHeightAsViewTargetRow = CameraCategory.AddProperty(DetailLayout.GetProperty(GET_MEMBER_NAME_CHECKED(UCameraComponent, bUseCameraHeightAsViewTarget)));
CameraHeightAsViewTargetRow.Visibility(OrthographicVisibility);
// Aspect ratio
IDetailPropertyRow& AspectRatioRow = CameraCategory.AddProperty(AspectRatioProperty);
// Provide the special aspect ratio row
AspectRatioRow.CustomWidget()
.NameContent()
[
AspectRatioProperty->CreatePropertyNameWidget()
]
.ValueContent()
[
SNew(SHorizontalBox)
+SHorizontalBox::Slot()
.Padding(0.0f, 2.0f, 5.0f, 2.0f)
[
SNew(SNumericEntryBox<float>)
.AllowSpin(true)
.Value(this, &FCameraDetails::GetAspectRatio)
.Font(FontStyle)
.MinValue(MinAspectRatio)
.MaxValue(MaxAspectRatio)
.MinSliderValue(LowestCommonAspectRatio)
.MaxSliderValue(HighestCommonAspectRatio)
.OnValueChanged(this, &FCameraDetails::OnAspectRatioSpinnerChanged)
.ToolTipText(LOCTEXT("AspectFloatTooltip", "Aspect Ratio (Width/Height)"))
]
+SHorizontalBox::Slot()
[
SNew(SComboButton)
.OnGetMenuContent( this, &FCameraDetails::OnGetComboContent )
.ContentPadding(0.0f)
.ButtonStyle( FAppStyle::Get(), "ToggleButton" )
.ForegroundColor(FSlateColor::UseForeground())
.VAlign(VAlign_Center)
.ButtonContent()
[
SAssignNew(AspectTextBox, SEditableTextBox)
.HintText( LOCTEXT("AspectTextHint", "width x height") )
.ToolTipText( LOCTEXT("AspectTextTooltip", "Enter a ratio in the form \'width x height\' or \'width:height\'") )
.Font(FontStyle)
.OnTextCommitted(this, &FCameraDetails::OnCommitAspectRatioText)
]
]
];
}
IDetailCategoryBuilder& CameraSettingsCategory = DetailLayout.EditCategory( "CameraOptions", FText::GetEmpty(), ECategoryPriority::Important );
CameraSettingsCategory.AddProperty( bConstrainAspectRatioProperty );
CameraSettingsCategory.AddProperty(DetailLayout.GetProperty(GET_MEMBER_NAME_CHECKED(UCameraComponent, bUsePawnControlRotation)));
CameraSettingsCategory.AddProperty(DetailLayout.GetProperty(GET_MEMBER_NAME_CHECKED(UCameraComponent, PostProcessBlendWeight)));
UpdateAspectTextFromProperty();
}
END_SLATE_FUNCTION_BUILD_OPTIMIZATION
void FCameraDetails::OnAspectRatioChanged()
{
// Callback set as the SetOnPropertyValueChanged() on the actual aspect ratio property
UpdateAspectTextFromProperty();
}
TOptional<float> FCameraDetails::GetAspectRatio() const
{
// Gets the actual aspect ratio property value
float Value = 0.0f;
if (AspectRatioProperty->GetValue(Value) == FPropertyAccess::Success)
{
return Value;
}
return TOptional<float>();
}
void FCameraDetails::OnAspectRatioSpinnerChanged(float InValue)
{
// Called when the user inputs a new aspect ratio into the spinbox
AspectRatioProperty->SetValue(InValue);
UpdateAspectTextFromProperty();
}
void FCameraDetails::UpdateAspectTextFromProperty()
{
// Called whenever the actual aspect ratio property changes - clears the text box if the value no longer matches the current text
if (AspectTextBox.IsValid())
{
TOptional<float> Value = GetAspectRatio();
if (!Value.IsSet() || Value.GetValue() < LastParsedAspectRatioValue - DELTA || Value.GetValue() > LastParsedAspectRatioValue + DELTA)
{
LastParsedAspectRatioValue = -1.0f;
if (!AspectTextBox->GetText().IsEmpty())
{
AspectTextBox->SetText(FText::GetEmpty());
}
}
}
}
TSharedRef<SWidget> FCameraDetails::OnGetComboContent() const
{
// Fill the combo menu with presets of common screen resolutions
FMenuBuilder MenuBuilder(true, NULL);
TArray<FText> Items;
Items.Add(LOCTEXT("PresetRatio640x480", "640x480 (4:3, 1.33) SDTV"));
Items.Add(LOCTEXT("PresetRatio852x480", "852x480 (16:9, 1.78) SDTV Widescreen"));
Items.Add(LOCTEXT("PresetRatio1280x720", "1280x720 (16:9, 1.78) HDTV 720"));
Items.Add(LOCTEXT("PresetRatio1920x1080", "1920x1080 (16:9, 1.78) HDTV 1080"));
Items.Add(LOCTEXT("PresetRatio960x544", "960x544 (16:9, 1.76) PS Vita"));
Items.Add(LOCTEXT("PresetRatio1024x640", "1024x640 (1.6)"));
Items.Add(LOCTEXT("PresetRatio1024x76", "1024x768 (4:3, 1.33)"));
Items.Add(LOCTEXT("PresetRatio1366x768", "1366x768 (16:9, 1.78)"));
Items.Add(LOCTEXT("PresetRatio2048x1536", "2048x1536 (4:3, 1.33) iPad 3"));
Items.Add(LOCTEXT("PresetRatio4096x2304", "4096x2304 (16:9, 1.78) 4K"));
for (auto ItemIter = Items.CreateConstIterator(); ItemIter; ++ItemIter)
{
FText ItemText = *ItemIter;
FUIAction ItemAction( FExecuteAction::CreateSP( const_cast<FCameraDetails*>(this), &FCameraDetails::CommitAspectRatioText, ItemText ) );
MenuBuilder.AddMenuEntry(ItemText, TAttribute<FText>(), FSlateIcon(), ItemAction);
}
return MenuBuilder.MakeWidget();
}
void FCameraDetails::CommitAspectRatioText(FText ItemText)
{
// placing new text into the box - so set the actual text then run the 'oncommit' handler
AspectTextBox->SetText(ItemText);
OnCommitAspectRatioText(ItemText, ETextCommit::Default);
}
void FCameraDetails::OnCommitAspectRatioText(const FText& ItemFText, ETextCommit::Type CommitInfo)
{
// Parse the text assuming the following format
// <INTEGER><optional whitespace><x or : or /><optional whitespace><INTEGER><optional extra info>
FString ItemText = ItemFText.ToString();
float ParsedRatio = -1.0f;
int32 DelimIdx = INDEX_NONE;
if (!ItemText.FindChar(TCHAR('x'), DelimIdx))
{
if (!ItemText.FindChar(TCHAR('X'), DelimIdx))
{
if (!ItemText.FindChar(TCHAR(':'), DelimIdx))
{
ItemText.FindChar(TCHAR('/'), DelimIdx);
}
}
}
if (DelimIdx != INDEX_NONE)
{
int32 Width;
TTypeFromString<int32>::FromString(Width, *ItemText.Mid(0, DelimIdx).TrimStartAndEnd());
if (Width > 0)
{
int32 WSIdx;
FString RemainingText = ItemText.Mid(DelimIdx + 1).TrimStart();
if (RemainingText.FindChar(TCHAR(' '), WSIdx))
{
RemainingText.LeftInline(WSIdx, EAllowShrinking::No);
}
int32 Height;
TTypeFromString<int32>::FromString(Height, *RemainingText);
if (Height > 0)
{
ParsedRatio = (float)Width / (float)Height;
}
}
}
if (ParsedRatio < 0.0f)
{
// invalid text - value couldn't be read
}
else if (ParsedRatio < MinAspectRatio)
{
// invalid value - too small
}
else if (ParsedRatio > MaxAspectRatio)
{
// invalid value - too large
}
else
{
// valid ratio parsed from text
LastParsedAspectRatioValue = ParsedRatio;
AspectRatioProperty->SetValue(ParsedRatio);
}
}
EVisibility FCameraDetails::ProjectionModeMatches(TSharedPtr<IPropertyHandle> Property, ECameraProjectionMode::Type DesiredMode) const
{
if (Property.IsValid())
{
uint8 ValueAsByte;
FPropertyAccess::Result Result = Property->GetValue(/*out*/ ValueAsByte);
if (Result == FPropertyAccess::Success)
{
return (((ECameraProjectionMode::Type)ValueAsByte) == DesiredMode) ? EVisibility::Visible : EVisibility::Collapsed;
}
}
// If there are multiple values, show all properties
return EVisibility::Visible;
}
#undef LOCTEXT_NAMESPACE