// 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 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 bConstrainAspectRatioProperty = DetailLayout.GetProperty(GET_MEMBER_NAME_CHECKED(UCameraComponent, bConstrainAspectRatio)); TSharedPtr 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 > Objects; DetailLayout.GetObjectsBeingCustomized(Objects); for (TWeakObjectPtr 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::Create(TAttribute::FGetter::CreateSP( this, &FCameraDetails::ProjectionModeMatches, ProjectionModeProperty, ECameraProjectionMode::Perspective ) ) ); // Orthographic-specific properties TAttribute OrthographicVisibility = TAttribute::Create(TAttribute::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) .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 FCameraDetails::GetAspectRatio() const { // Gets the actual aspect ratio property value float Value = 0.0f; if (AspectRatioProperty->GetValue(Value) == FPropertyAccess::Success) { return Value; } return TOptional(); } 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 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 FCameraDetails::OnGetComboContent() const { // Fill the combo menu with presets of common screen resolutions FMenuBuilder MenuBuilder(true, NULL); TArray 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(this), &FCameraDetails::CommitAspectRatioText, ItemText ) ); MenuBuilder.AddMenuEntry(ItemText, TAttribute(), 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 // 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::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::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 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