Files
UnrealEngine/Engine/Source/Editor/DetailCustomizations/Private/CameraDetails.cpp
2025-05-18 13:04:45 +08:00

350 lines
13 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 "IDetailGroup.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;
}
}
}
IDetailCategoryBuilder& CurrentCameraSettingsCategory = 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)
]
]
];
IDetailGroup& OverscanGroup = CameraCategory.AddGroup(TEXT("Overscan"), LOCTEXT("OverscanGroup", "Overscan"));
OverscanGroup.AddPropertyRow(DetailLayout.GetProperty(GET_MEMBER_NAME_CHECKED(UCameraComponent, Overscan)));
OverscanGroup.AddPropertyRow(DetailLayout.GetProperty(GET_MEMBER_NAME_CHECKED(UCameraComponent, bScaleResolutionWithOverscan)));
OverscanGroup.AddPropertyRow(DetailLayout.GetProperty(GET_MEMBER_NAME_CHECKED(UCameraComponent, bCropOverscan)));
}
else
{
IDetailGroup& OverscanGroup = CurrentCameraSettingsCategory.AddGroup(TEXT("Overscan"), LOCTEXT("OverscanGroup", "Overscan"));
OverscanGroup.AddPropertyRow(DetailLayout.GetProperty(GET_MEMBER_NAME_CHECKED(UCameraComponent, Overscan)));
OverscanGroup.AddPropertyRow(DetailLayout.GetProperty(GET_MEMBER_NAME_CHECKED(UCameraComponent, bScaleResolutionWithOverscan)));
OverscanGroup.AddPropertyRow(DetailLayout.GetProperty(GET_MEMBER_NAME_CHECKED(UCameraComponent, bCropOverscan)));
}
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