// Copyright Epic Games, Inc. All Rights Reserved. #include "StaticMeshComponentDetails.h" #include "Components/StaticMeshComponent.h" #include "DetailCategoryBuilder.h" #include "DetailLayoutBuilder.h" #include "DetailWidgetRow.h" #include "Engine/StaticMesh.h" #include "Fonts/SlateFontInfo.h" #include "HAL/PlatformCrt.h" #include "IDetailPropertyRow.h" #include "Internationalization/Internationalization.h" #include "Internationalization/Text.h" #include "Layout/BasicLayoutWidgetSlot.h" #include "Misc/AssertionMacros.h" #include "Misc/Attribute.h" #include "PropertyEditorModule.h" #include "PropertyHandle.h" #include "SlotBase.h" #include "Templates/Casts.h" #include "Types/SlateEnums.h" #include "UObject/ObjectPtr.h" #include "Widgets/DeclarativeSyntaxSupport.h" #include "Widgets/Input/SNumericEntryBox.h" #include "Widgets/Layout/SWidgetSwitcher.h" #include "Widgets/Text/STextBlock.h" class SWidget; #define LOCTEXT_NAMESPACE "StaticMeshComponentDetails" TSharedRef FStaticMeshComponentDetails::MakeInstance() { return MakeShareable( new FStaticMeshComponentDetails ); } void FStaticMeshComponentDetails::CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) { // Create a category so this is displayed early in the properties DetailBuilder.EditCategory("StaticMesh", FText::GetEmpty(), ECategoryPriority::Important); TSharedRef UseDefaultCollision = DetailBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(UStaticMeshComponent, bUseDefaultCollision)); UseDefaultCollision->MarkHiddenByCustomization(); IDetailCategoryBuilder& LightingCategory = DetailBuilder.EditCategory("Lighting"); // Store off the properties for analysis in later function calls. OverrideLightResProperty = DetailBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(UStaticMeshComponent, OverriddenLightMapRes)); // Add the row that we will be customizing below. IDetailPropertyRow& OverrideLightResRow = LightingCategory.AddProperty(OverrideLightResProperty); // Create the default property value widget, we'll use this in certain circumstances outlined below. TSharedRef ValueWidget = OverrideLightResProperty->CreatePropertyValueWidget(); // Store off the objects that we are editing for analysis in later function calls. DetailBuilder.GetObjectsBeingCustomized(ObjectsCustomized); // We use similar logic here to where it is ultimately used: // UStaticMeshComponent::GetLightMapResolution // If there is a static mesh and the override is enabled, use the real value of the property. // If there is a static mesh and the override is disabled, use the static mesh's resolution value. // If no static mesh is assigned, use 0. // Ultimately, the last case is an error and we need to warn the user. We also need to handle multiple selection appropriately, // thus the widget switcher below. OverrideLightResRow.CustomWidget() .NameContent() [ // Use the default lightmap property name OverrideLightResProperty->CreatePropertyNameWidget() ] .ValueContent() .VAlign(VAlign_Center) [ SNew(SWidgetSwitcher) .WidgetIndex(this, &FStaticMeshComponentDetails::HandleNoticeSwitcherWidgetIndex) + SWidgetSwitcher::Slot() // Slot 0, the single common value for static mesh lightmap resolutions. [ SNew(SNumericEntryBox) .ToolTipText(OverrideLightResProperty->GetToolTipText()) .IsEnabled(false) .Font(IDetailLayoutBuilder::GetDetailFont()) .Value(this, &FStaticMeshComponentDetails::GetStaticMeshLightResValue) ] + SWidgetSwitcher::Slot() // Slot 1, the editor for when overrides are enabled [ ValueWidget ] + SWidgetSwitcher::Slot() // Slot 2, the editor for when we are missing one or more static meshes. .HAlign(HAlign_Center) .VAlign(VAlign_Center) [ SNew(STextBlock) .ToolTipText(OverrideLightResProperty->GetToolTipText()) .IsEnabled(false) .Font(IDetailLayoutBuilder::GetDetailFont()) .Text(LOCTEXT("DetailsMissingStaticMesh", "Missing StaticMesh!")) ] + SWidgetSwitcher::Slot() // Slot 3, the editor for when we have multiple heterogenous values. .HAlign(HAlign_Center) .VAlign(VAlign_Center) [ SNew(STextBlock) .ToolTipText(OverrideLightResProperty->GetToolTipText()) .IsEnabled(false) .Font(IDetailLayoutBuilder::GetDetailFont()) .Text(LOCTEXT("DetailsMultipleValues", "Multiple Values")) ] ]; } TOptional FStaticMeshComponentDetails::GetStaticMeshLightResValue() const { // Go ahead and grab the current value for the static mesh resolution as this shouldn't change during the lifetime // of this customization. TOptional DefaultResolution = 0; for (int32 i = 0; i < ObjectsCustomized.Num() && DefaultResolution == 0; i++) { UStaticMeshComponent* Component = Cast(ObjectsCustomized[i].Get()); if (Component != nullptr && Component->GetStaticMesh() != nullptr) { DefaultResolution = Component->GetStaticMesh()->GetLightMapResolution(); } } return DefaultResolution; } int FStaticMeshComponentDetails::HandleNoticeSwitcherWidgetIndex() const { // Determine if the override enabled is set homogenously or not. bool bOverrideEnabled = false; FPropertyAccess::Result AccessResultOverrideEnabled = FPropertyAccess::Fail; bool bOverrideFound = false; // Loop through all the components being edited to see if any are missing static meshes // and to see if their lightmap resolutions are all the same on those static meshes. FPropertyAccess::Result AccessResultOverride = FPropertyAccess::Success; bool bHasMissingStaticMeshes = false; int32 OverrideLightRes = -1; check(ObjectsCustomized.Num() > 0); for (int32 i = 0; i < ObjectsCustomized.Num(); i++) { UStaticMeshComponent* Component = Cast(ObjectsCustomized[i].Get()); if (Component != nullptr) { if (Component->GetStaticMesh() == nullptr) { bHasMissingStaticMeshes = true; AccessResultOverride = FPropertyAccess::MultipleValues; } else { if (bOverrideFound == false) { AccessResultOverrideEnabled = FPropertyAccess::Success; bOverrideEnabled = Component->bOverrideLightMapRes; bOverrideFound = true; } else if (bOverrideEnabled != Component->bOverrideLightMapRes) { AccessResultOverrideEnabled = FPropertyAccess::MultipleValues; } int32 CurrentStaticLightRes = Component->GetStaticMesh()->GetLightMapResolution(); if (OverrideLightRes == -1) { OverrideLightRes = CurrentStaticLightRes; } else if (CurrentStaticLightRes != OverrideLightRes) { AccessResultOverride = FPropertyAccess::MultipleValues; } } } } // This gets a little hairy. The desired behavior is as follows: // For single item selection: // a) if override is enabled and we have a valid static mesh, display the general editor for the property. // b) if override is disabled and we have a valid static mesh, display the static mesh's value for the property. // c) otherwise, warn the user if they are missing the static mesh. // For multiple selection: // d) if all overrides are enabled, all have valid meshes, and all have the same value, display the general editor. // e) if all overrides are enabled, all have valid meshes, and all have different values, display the general editor (which should say Multiple Values). // f) if all overrides are disabled, all have valid meshes, and all static meshes have the same value for the resolution, display that common resolution // g) if all overrides are disabled, all have valid meshes, and all static meshes have heterogenous values for the resolution, display the multiple values read-only text. // h) if overrides are heterogenous, display the multiple values read-only text. // i) if any of the above have invalid static meshes, warn the user. if (bHasMissingStaticMeshes) // Covers cases c and i above. { return 2; } else if (AccessResultOverrideEnabled == FPropertyAccess::MultipleValues) // covers case h above { return 3; } else if (AccessResultOverride == FPropertyAccess::MultipleValues && bOverrideEnabled == false) // covers case g above { return 3; } else if (bOverrideEnabled == false) // covers cases b and f above { return 0; } else // covers cases a, d, and e above { return 1; } } #undef LOCTEXT_NAMESPACE