void FEditorTutorialDetailsCustomization::CustomizeDetails( IDetailLayoutBuilder& DetailLayout ) { struct Local { static FReply OnLaunchClicked(UEditorTutorial* Tutorial) { FLevelEditorModule& LevelEditorModule = FModuleManager::Get().GetModuleChecked<FLevelEditorModule>("LevelEditor"); IIntroTutorials& IntroTutorials = FModuleManager::Get().GetModuleChecked<IIntroTutorials>("IntroTutorials"); IntroTutorials.LaunchTutorial(Tutorial, true, LevelEditorModule.GetLevelEditorTab()->GetParentWindow()); return FReply::Handled(); } }; TArray<TWeakObjectPtr<UObject>> Objects; DetailLayout.GetObjectsBeingCustomized(Objects); check(Objects.Num() > 0); UEditorTutorial* Tutorial = CastChecked<UEditorTutorial>(Objects[0].Get()); IDetailCategoryBuilder& CategoryBuilder = DetailLayout.EditCategory(TEXT("Testing"), LOCTEXT("TestingSection", "Testing"), ECategoryPriority::Important); CategoryBuilder.AddCustomRow(LOCTEXT("LaunchButtonLabel", "Launch")) .WholeRowContent() .VAlign(VAlign_Center) .HAlign(HAlign_Left) [ SNew(SButton) .OnClicked(FOnClicked::CreateStatic(&Local::OnLaunchClicked, Tutorial)) .Text(LOCTEXT("LaunchButtonLabel", "Launch")) .ToolTipText(LOCTEXT("LaunchButtonTooltip", "Test this tutorial.")) ]; }
void FSpriteComponentDetailsCustomization::CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) { // Create a category so this is displayed early in the properties IDetailCategoryBuilder& SpriteCategory = DetailBuilder.EditCategory("Sprite", FText::GetEmpty(), ECategoryPriority::Important); ObjectsBeingCustomized.Empty(); DetailBuilder.GetObjectsBeingCustomized(/*out*/ ObjectsBeingCustomized); if (ObjectsBeingCustomized.Num() > 1) { // Expose merge buttons FDetailWidgetRow& MergeRow = SpriteCategory.AddCustomRow(LOCTEXT("MergeSearchText", "Merge")) .WholeRowContent() [ SNew(SHorizontalBox) +SHorizontalBox::Slot() .AutoWidth() .Padding(2.0f, 0.0f) .VAlign(VAlign_Center) .HAlign(HAlign_Left) [ SNew(SButton) .Text(LOCTEXT("MergeSprites", "Merge Sprites")) .ToolTipText(LOCTEXT("MergeSprites_Tooltip", "Merges all selected sprite components into entries on a single grouped sprite component")) .OnClicked(this, &FSpriteComponentDetailsCustomization::MergeSprites) ] ]; } }
/** IDetailCustomization interface */ void FSpeedTreeImportDataDetails::CustomizeDetails(IDetailLayoutBuilder& DetailLayout) { CachedDetailBuilder = &DetailLayout; TArray<TWeakObjectPtr<UObject>> EditingObjects; DetailLayout.GetObjectsBeingCustomized(EditingObjects); check(EditingObjects.Num() == 1); SpeedTreeImportData = Cast<USpeedTreeImportData>(EditingObjects[0].Get()); if (SpeedTreeImportData == nullptr) { return; } //We have to hide FilePath category DetailLayout.HideCategory(FName(TEXT("File Path"))); //Mesh category Must be the first category (Important) DetailLayout.EditCategory(FName(TEXT("Mesh")), FText::GetEmpty(), ECategoryPriority::Important); //Get the Materials category IDetailCategoryBuilder& MaterialsCategoryBuilder = DetailLayout.EditCategory(FName(TEXT("Materials"))); TArray<TSharedRef<IPropertyHandle>> MaterialCategoryDefaultProperties; MaterialsCategoryBuilder.GetDefaultProperties(MaterialCategoryDefaultProperties); //We have to make the logic for vertex processing TSharedRef<IPropertyHandle> MakeMaterialsCheckProp = DetailLayout.GetProperty(GET_MEMBER_NAME_CHECKED(USpeedTreeImportData, MakeMaterialsCheck)); MakeMaterialsCheckProp->SetOnPropertyValueChanged(FSimpleDelegate::CreateSP(this, &FSpeedTreeImportDataDetails::OnForceRefresh)); TSharedRef<IPropertyHandle> IncludeVertexProcessingCheckProp = DetailLayout.GetProperty(GET_MEMBER_NAME_CHECKED(USpeedTreeImportData, IncludeVertexProcessingCheck)); IncludeVertexProcessingCheckProp->SetOnPropertyValueChanged(FSimpleDelegate::CreateSP(this, &FSpeedTreeImportDataDetails::OnForceRefresh)); //Hide all properties, we will show them in the correct order with the correct grouping for (TSharedRef<IPropertyHandle> Handle : MaterialCategoryDefaultProperties) { DetailLayout.HideProperty(Handle); } MaterialsCategoryBuilder.AddProperty(MakeMaterialsCheckProp); if (SpeedTreeImportData->MakeMaterialsCheck) { for (TSharedRef<IPropertyHandle> Handle : MaterialCategoryDefaultProperties) { const FString& MetaData = Handle->GetMetaData(TEXT("EditCondition")); if (MetaData.Compare(TEXT("MakeMaterialsCheck")) == 0 && IncludeVertexProcessingCheckProp->GetProperty() != Handle->GetProperty()) { MaterialsCategoryBuilder.AddProperty(Handle); } } IDetailGroup& VertexProcessingGroup = MaterialsCategoryBuilder.AddGroup(FName(TEXT("VertexProcessingGroup")), LOCTEXT("VertexProcessingGroup_DisplayName", "Vertex Processing"), false, true); VertexProcessingGroup.AddPropertyRow(IncludeVertexProcessingCheckProp); for (TSharedRef<IPropertyHandle> Handle : MaterialCategoryDefaultProperties) { const FString& MetaData = Handle->GetMetaData(TEXT("EditCondition")); if (MetaData.Compare(TEXT("IncludeVertexProcessingCheck")) == 0) { VertexProcessingGroup.AddPropertyRow(Handle); } } } }
void FGroupedSpriteComponentDetailsCustomization::CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) { // Create a category so this is displayed early in the properties IDetailCategoryBuilder& SpriteCategory = DetailBuilder.EditCategory("Sprite", FText::GetEmpty(), ECategoryPriority::Important); ObjectsBeingCustomized.Empty(); DetailBuilder.GetObjectsBeingCustomized(/*out*/ ObjectsBeingCustomized); TSharedRef<SWrapBox> ButtonBox = SNew(SWrapBox).UseAllottedWidth(true); const float MinButtonSize = 100.0f; const FMargin ButtonPadding(0.0f, 2.0f, 2.0f, 0.0f); // Split button ButtonBox->AddSlot() .Padding(ButtonPadding) [ SNew(SBox) .MinDesiredWidth(MinButtonSize) [ SNew(SButton) .VAlign(VAlign_Center) .HAlign(HAlign_Center) .Text(LOCTEXT("SplitSprites", "Split Sprites")) .ToolTipText(LOCTEXT("SplitSprites_Tooltip", "Splits all sprite instances into separate sprite actors or components")) .OnClicked(this, &FGroupedSpriteComponentDetailsCustomization::SplitSprites) ] ]; // Sort button ButtonBox->AddSlot() .Padding(ButtonPadding) [ SNew(SBox) .MinDesiredWidth(MinButtonSize) [ SNew(SButton) .VAlign(VAlign_Center) .HAlign(HAlign_Center) .Text(LOCTEXT("SortSprites", "Sort Sprites")) .ToolTipText(LOCTEXT("SortSprites_Tooltip", "Sorts all sprite instances according to the Translucency Sort Axis in the Rendering project settings")) .OnClicked(this, &FGroupedSpriteComponentDetailsCustomization::SortSprites) ] ]; // Add the action buttons FDetailWidgetRow& GroupActionsRow = SpriteCategory.AddCustomRow(LOCTEXT("GroupActionsSearchText", "Split Sort")) .WholeRowContent() [ SNew(SHorizontalBox) +SHorizontalBox::Slot() .FillWidth(1.0f) [ ButtonBox ] ]; }
void FWorldSettingsDetails::CustomizeGameInfoProperty( const FName& PropertyName, IDetailLayoutBuilder& DetailBuilder, IDetailCategoryBuilder& CategoryBuilder ) { // Get the object that we are viewing details of. Expect to only edit one WorldSettings object at a time! TArray< TWeakObjectPtr<UObject> > ObjectsCustomized; DetailBuilder.GetObjectsBeingCustomized(ObjectsCustomized); UObject* ObjectCustomized = (ObjectsCustomized.Num() > 0) ? ObjectsCustomized[0].Get() : NULL; // Allocate customizer object GameInfoModeCustomizer = MakeShareable(new FGameModeInfoCustomizer(ObjectCustomized, PropertyName)); // Then use it to customize GameInfoModeCustomizer->CustomizeGameModeSetting(DetailBuilder, CategoryBuilder); }
void FPluginMetadataCustomization::CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) { TArray<TWeakObjectPtr<UObject>> Objects; DetailBuilder.GetObjectsBeingCustomized(Objects); if(Objects.Num() == 1 && Objects[0].IsValid()) { UPluginMetadataObject* PluginMetadata = Cast<UPluginMetadataObject>(Objects[0].Get()); if(PluginMetadata != nullptr && PluginMetadata->TargetIconPath.Len() > 0) { // Get the current icon path FString CurrentIconPath = PluginMetadata->TargetIconPath; if(!FPaths::FileExists(CurrentIconPath)) { CurrentIconPath = IPluginManager::Get().FindPlugin(TEXT("PluginBrowser"))->GetBaseDir() / TEXT("Resources") / TEXT("DefaultIcon128.png"); } // Add the customization to edit the icon row IDetailCategoryBuilder& ImageCategory = DetailBuilder.EditCategory(TEXT("Icon")); const FText IconDesc(NSLOCTEXT("PluginBrowser", "PluginIcon", "Icon")); ImageCategory.AddCustomRow(IconDesc) .NameContent() [ SNew(SHorizontalBox) +SHorizontalBox::Slot() .Padding( FMargin( 0, 1, 0, 1 ) ) .FillWidth(1.0f) [ SNew(STextBlock) .Text(IconDesc) .Font(DetailBuilder.GetDetailFont()) ] ] .ValueContent() .MaxDesiredWidth(500.0f) .MinDesiredWidth(100.0f) [ SNew(SHorizontalBox) +SHorizontalBox::Slot() .FillWidth(1.0f) .VAlign(VAlign_Center) [ SNew(SExternalImageReference, CurrentIconPath, PluginMetadata->TargetIconPath) .FileDescription(IconDesc) .RequiredSize(FIntPoint(128, 128)) ] ]; } } }
BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION void FBehaviorDecoratorDetails::CustomizeDetails( IDetailLayoutBuilder& DetailLayout ) { FText AbortModeDesc = LOCTEXT("ObserverTitle","Observer aborts"); PropUtils = &(DetailLayout.GetPropertyUtilities().Get()); TArray<TWeakObjectPtr<UObject> > EditedObjects; DetailLayout.GetObjectsBeingCustomized(EditedObjects); for (int32 i = 0; i < EditedObjects.Num(); i++) { UBTDecorator* MyDecorator = Cast<UBTDecorator>(EditedObjects[i].Get()); if (MyDecorator) { MyNode = MyDecorator; break; } } UpdateAllowedAbortModes(); ModeProperty = DetailLayout.GetProperty(GET_MEMBER_NAME_CHECKED(UBTDecorator, FlowAbortMode)); // dynamic FlowAbortMode combo IDetailCategoryBuilder& FlowCategory = DetailLayout.EditCategory( "FlowControl" ); IDetailPropertyRow& AbortModeRow = FlowCategory.AddProperty(ModeProperty); AbortModeRow.IsEnabled(TAttribute<bool>(this, &FBehaviorDecoratorDetails::GetAbortModeEnabled)); AbortModeRow.Visibility(TAttribute<EVisibility>::Create(TAttribute<EVisibility>::FGetter::CreateSP(this, &FBehaviorDecoratorDetails::GetModeVisibility))); AbortModeRow.CustomWidget() .NameContent() [ ModeProperty->CreatePropertyNameWidget(AbortModeDesc) ] .ValueContent() [ SNew(SComboButton) .OnGetMenuContent(this, &FBehaviorDecoratorDetails::OnGetAbortModeContent) .ContentPadding(FMargin( 2.0f, 2.0f )) .ButtonContent() [ SNew(STextBlock) .Text(this, &FBehaviorDecoratorDetails::GetCurrentAbortModeDesc) .Font(IDetailLayoutBuilder::GetDetailFont()) ] ]; InitPropertyValues(); }
void FProceduralFoliageComponentDetails::CustomizeDetails( IDetailLayoutBuilder& DetailBuilder ) { const FName ProceduralFoliageCategoryName("ProceduralFoliage"); IDetailCategoryBuilder& ProceduralFoliageCategory = DetailBuilder.EditCategory(ProceduralFoliageCategoryName); const FText ResimulateText = NSLOCTEXT("ProceduralFoliageComponentDetails","ResimulateButtonText", "Resimulate" ); TArray< TWeakObjectPtr<UObject> > ObjectsBeingCustomized; DetailBuilder.GetObjectsBeingCustomized( ObjectsBeingCustomized ); for( TWeakObjectPtr<UObject>& Object : ObjectsBeingCustomized ) { UProceduralFoliageComponent* Component = Cast<UProceduralFoliageComponent>( Object.Get() ); if( ensure( Component ) ) { SelectedComponents.Add( Component ); } } TArray<TSharedRef<IPropertyHandle>> AllProperties; bool bSimpleProperties = true; bool bAdvancedProperties = false; // Add all properties in the category in order ProceduralFoliageCategory.GetDefaultProperties(AllProperties, bSimpleProperties, bAdvancedProperties); for( auto& Property : AllProperties ) { ProceduralFoliageCategory.AddProperty(Property); } FDetailWidgetRow& NewRow = ProceduralFoliageCategory.AddCustomRow( ResimulateText ); NewRow.ValueContent() .MaxDesiredWidth(120.f) [ SNew(SButton) .OnClicked( this, &FProceduralFoliageComponentDetails::OnResimulateClicked ) .ToolTipText( NSLOCTEXT("ProceduralFoliageComponentDetails","ResimulateButton_Tooltip", "Resimulates the ProceduralFoliage asset and replaces previously spawned instances" ) ) .IsEnabled( this, &FProceduralFoliageComponentDetails::IsResimulateEnabled ) [ SNew( STextBlock ) .Font( IDetailLayoutBuilder::GetDetailFont() ) .Text( ResimulateText ) ] ]; }
END_SLATE_FUNCTION_BUILD_OPTIMIZATION void FBlackboardDecoratorDetails::CacheBlackboardData(IDetailLayoutBuilder& DetailLayout) { TArray<TWeakObjectPtr<UObject> > MyOuters; DetailLayout.GetObjectsBeingCustomized(MyOuters); CachedBlackboardAsset.Reset(); for (int32 i = 0; i < MyOuters.Num(); i++) { UBTNode* NodeOb = Cast<UBTNode>(MyOuters[i].Get()); if (NodeOb) { CachedBlackboardAsset = NodeOb->GetBlackboardAsset(); break; } } }
void FBodySetupDetails::CustomizeDetails( IDetailLayoutBuilder& DetailBuilder ) { // Customize collision section { if ( DetailBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(UBodySetup, DefaultInstance))->IsValidHandle() ) { DetailBuilder.GetObjectsBeingCustomized(ObjectsCustomized); TSharedPtr<IPropertyHandle> BodyInstanceHandler = DetailBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(UBodySetup, DefaultInstance)); const bool bInPhat = ObjectsCustomized.Num() && (Cast<USkeletalBodySetup>(ObjectsCustomized[0].Get()) != nullptr); if (bInPhat) { TSharedRef<IPropertyHandle> AsyncEnabled = BodyInstanceHandler->GetChildHandle(GET_MEMBER_NAME_CHECKED(FBodyInstance, bUseAsyncScene)).ToSharedRef(); AsyncEnabled->MarkHiddenByCustomization(); } BodyInstanceCustomizationHelper = MakeShareable(new FBodyInstanceCustomizationHelper(ObjectsCustomized)); BodyInstanceCustomizationHelper->CustomizeDetails(DetailBuilder, DetailBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(UBodySetup, DefaultInstance))); IDetailCategoryBuilder& CollisionCategory = DetailBuilder.EditCategory("Collision"); DetailBuilder.HideProperty(BodyInstanceHandler); TSharedPtr<IPropertyHandle> CollisionTraceHandler = DetailBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(UBodySetup, CollisionTraceFlag)); DetailBuilder.HideProperty(CollisionTraceHandler); // add physics properties to physics category uint32 NumChildren = 0; BodyInstanceHandler->GetNumChildren(NumChildren); static const FName CollisionCategoryName(TEXT("Collision")); // add all properties of this now - after adding for (uint32 ChildIndex=0; ChildIndex < NumChildren; ++ChildIndex) { TSharedPtr<IPropertyHandle> ChildProperty = BodyInstanceHandler->GetChildHandle(ChildIndex); FName CategoryName = FObjectEditorUtils::GetCategoryFName(ChildProperty->GetProperty()); if (CategoryName == CollisionCategoryName) { CollisionCategory.AddProperty(ChildProperty); } } } } }
void FPrimitiveComponentDetails::CustomizeDetails( IDetailLayoutBuilder& DetailBuilder ) { // Get the objects being customized so we can enable/disable editing of 'Simulate Physics' DetailBuilder.GetObjectsBeingCustomized(ObjectsCustomized); // See if we are hiding Physics category TArray<FString> HideCategories; FEditorCategoryUtils::GetClassHideCategories(DetailBuilder.GetDetailsView().GetBaseClass(), HideCategories); if(!HideCategories.Contains("Materials")) { AddMaterialCategory(DetailBuilder); } TSharedRef<IPropertyHandle> MobilityHandle = DetailBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(UPrimitiveComponent, Mobility), USceneComponent::StaticClass()); MobilityHandle->SetToolTipText(LOCTEXT("PrimitiveMobilityTooltip", "Mobility for primitive components controls how they can be modified in game and therefore how they interact with lighting and physics.\n● A movable primitive component can be changed in game, but requires dynamic lighting and shadowing from lights which have a large performance cost.\n● A static primitive component can't be changed in game, but can have its lighting baked, which allows rendering to be very efficient.")); if(!HideCategories.Contains("Physics")) { AddPhysicsCategory(DetailBuilder); } if (!HideCategories.Contains("Collision")) { AddCollisionCategory(DetailBuilder); } if(!HideCategories.Contains("Lighting")) { AddLightingCategory(DetailBuilder); } AddAdvancedSubCategory( DetailBuilder, "Rendering", "TextureStreaming" ); AddAdvancedSubCategory( DetailBuilder, "Rendering", "LOD"); }
void FBodySetupDetails::CustomizeDetails( IDetailLayoutBuilder& DetailBuilder ) { // Customize collision section { if ( DetailBuilder.GetProperty("DefaultInstance")->IsValidHandle() ) { IDetailCategoryBuilder& PhysicsCategory = DetailBuilder.EditCategory("Physics"); IDetailCategoryBuilder& CollisionCategory = DetailBuilder.EditCategory("Collision"); TSharedPtr<IPropertyHandle> BodyInstanceHandler = DetailBuilder.GetProperty("DefaultInstance"); DetailBuilder.HideProperty(BodyInstanceHandler); TSharedPtr<IPropertyHandle> CollisionTraceHandler = DetailBuilder.GetProperty("CollisionTraceFlag"); DetailBuilder.HideProperty(CollisionTraceHandler); // add physics properties to physics category uint32 NumChildren = 0; BodyInstanceHandler->GetNumChildren(NumChildren); // Get the objects being customized so we can enable/disable editing of 'Simulate Physics' DetailBuilder.GetObjectsBeingCustomized(ObjectsCustomized); PhysicsCategory.AddCustomRow(TEXT("Mass"), false) .NameContent() [ SNew (STextBlock) .Text(NSLOCTEXT("MassInKG", "MassInKG_Name", "Mass in KG")) .ToolTipText(NSLOCTEXT("MassInKG", "MassInKG_ToolTip", "Mass of the body in KG")) .Font( IDetailLayoutBuilder::GetDetailFont() ) ] .ValueContent() [ SNew(SEditableTextBox) .Text(this, &FBodySetupDetails::OnGetBodyMass) .IsReadOnly(this, &FBodySetupDetails::IsBodyMassReadOnly) .Font(IDetailLayoutBuilder::GetDetailFont()) ]; // add all properties of this now - after adding for (uint32 ChildIndex=0; ChildIndex < NumChildren; ++ChildIndex) { TSharedPtr<IPropertyHandle> ChildProperty = BodyInstanceHandler->GetChildHandle(ChildIndex); FString Category = FObjectEditorUtils::GetCategory(ChildProperty->GetProperty()); if (ChildProperty->GetProperty()->GetName() == TEXT("bSimulatePhysics")) { // skip bSimulatePhysics // this is because we don't want bSimulatePhysics to show up // phat editor // staitc mesh already hides everything else not interested in // so phat editor just should not show this option continue; } if (Category == TEXT("Physics")) { PhysicsCategory.AddProperty(ChildProperty); } else if (Category == TEXT("Collision")) { CollisionCategory.AddProperty(ChildProperty); } } } } }
void FSpriteDetailsCustomization::BuildCollisionSection(IDetailCategoryBuilder& CollisionCategory, IDetailLayoutBuilder& DetailLayout) { TSharedPtr<IPropertyHandle> SpriteCollisionDomainProperty = DetailLayout.GetProperty(GET_MEMBER_NAME_CHECKED(UPaperSprite, SpriteCollisionDomain)); CollisionCategory.HeaderContent ( SNew(SBox) .HAlign(HAlign_Right) [ SNew(SHorizontalBox) +SHorizontalBox::Slot() .Padding(FMargin(5.0f, 0.0f)) .AutoWidth() [ SNew(STextBlock) .Font(FEditorStyle::GetFontStyle("TinyText")) .Text(this, &FSpriteDetailsCustomization::GetCollisionHeaderContentText, SpriteCollisionDomainProperty) ] ] ); TAttribute<EVisibility> ParticipatesInPhysics = TAttribute<EVisibility>::Create(TAttribute<EVisibility>::FGetter::CreateSP( this, &FSpriteDetailsCustomization::AnyPhysicsMode, SpriteCollisionDomainProperty)); TAttribute<EVisibility> ParticipatesInPhysics3D = TAttribute<EVisibility>::Create(TAttribute<EVisibility>::FGetter::CreateSP(this, &FSpriteDetailsCustomization::PhysicsModeMatches, SpriteCollisionDomainProperty, ESpriteCollisionMode::Use3DPhysics)); TAttribute<EVisibility> HideWhenInRenderingMode = TAttribute<EVisibility>::Create(TAttribute<EVisibility>::FGetter::CreateSP(this, &FSpriteDetailsCustomization::EditorModeIsNot, ESpriteEditorMode::EditRenderingGeomMode)); TAttribute<EVisibility> ShowWhenInRenderingMode = TAttribute<EVisibility>::Create(TAttribute<EVisibility>::FGetter::CreateSP(this, &FSpriteDetailsCustomization::EditorModeMatches, ESpriteEditorMode::EditRenderingGeomMode)); static const FText EditCollisionInCollisionMode = LOCTEXT("CollisionPropertiesHiddenInRenderingMode", "Switch to 'Edit Collsion' mode\nto edit Collision settings"); CollisionCategory.AddCustomRow(EditCollisionInCollisionMode) .Visibility(ShowWhenInRenderingMode) .WholeRowContent() .HAlign(HAlign_Center) [ SNew(STextBlock) .Font(DetailLayout.GetDetailFontItalic()) .Justification(ETextJustify::Center) .Text(EditCollisionInCollisionMode) ]; CollisionCategory.AddProperty(SpriteCollisionDomainProperty).Visibility(HideWhenInRenderingMode); // Add the collision geometry mode into the parent container (renamed) { // Restrict the diced value TSharedPtr<FPropertyRestriction> PreventDicedRestriction = MakeShareable(new FPropertyRestriction(LOCTEXT("CollisionGeometryDoesNotSupportDiced", "Collision geometry can not be set to Diced"))); const UEnum* const SpritePolygonModeEnum = FindObject<UEnum>(ANY_PACKAGE, TEXT("ESpritePolygonMode")); PreventDicedRestriction->AddDisabledValue(SpritePolygonModeEnum->GetNameStringByValue((uint8)ESpritePolygonMode::Diced)); // Find and add the property const FString CollisionGeometryTypePropertyPath = FString::Printf(TEXT("%s.%s"), GET_MEMBER_NAME_STRING_CHECKED(UPaperSprite, CollisionGeometry), GET_MEMBER_NAME_STRING_CHECKED(FSpriteGeometryCollection, GeometryType)); TSharedPtr<IPropertyHandle> CollisionGeometryTypeProperty = DetailLayout.GetProperty(*CollisionGeometryTypePropertyPath); CollisionGeometryTypeProperty->AddRestriction(PreventDicedRestriction.ToSharedRef()); CollisionCategory.AddProperty(CollisionGeometryTypeProperty) .DisplayName(LOCTEXT("CollisionGeometryType", "Collision Geometry Type")) .Visibility(ParticipatesInPhysics); } // Show the collision thickness only in 3D mode CollisionCategory.AddProperty( DetailLayout.GetProperty(GET_MEMBER_NAME_CHECKED(UPaperSprite, CollisionThickness)) ) .Visibility(ParticipatesInPhysics3D); // Show the default body instance (and only it) from the body setup (if it exists) DetailLayout.HideProperty("BodySetup"); IDetailPropertyRow& BodySetupDefaultInstance = CollisionCategory.AddProperty("BodySetup.DefaultInstance"); TArray<TWeakObjectPtr<UObject>> SpritesBeingEdited; DetailLayout.GetObjectsBeingCustomized(/*out*/ SpritesBeingEdited); TArray<UObject*> BodySetupList; for (auto WeakSpritePtr : SpritesBeingEdited) { if (UPaperSprite* Sprite = Cast<UPaperSprite>(WeakSpritePtr.Get())) { if (UBodySetup* BodySetup = Sprite->BodySetup) { BodySetupList.Add(BodySetup); } } } if (BodySetupList.Num() > 0) { IDetailPropertyRow* DefaultInstanceRow = CollisionCategory.AddExternalObjectProperty(BodySetupList, GET_MEMBER_NAME_CHECKED(UBodySetup, DefaultInstance)); if (DefaultInstanceRow != nullptr) { DefaultInstanceRow->Visibility(ParticipatesInPhysics); } } // Show the collision geometry when not None CollisionCategory.AddProperty(DetailLayout.GetProperty(GET_MEMBER_NAME_CHECKED(UPaperSprite, CollisionGeometry))) .Visibility(ParticipatesInPhysics); // Add the collision polygons into advanced (renamed) const FString CollisionGeometryPolygonsPropertyPath = FString::Printf(TEXT("%s.%s"), GET_MEMBER_NAME_STRING_CHECKED(UPaperSprite, CollisionGeometry), GET_MEMBER_NAME_STRING_CHECKED(FSpriteGeometryCollection, Shapes)); CollisionCategory.AddProperty(DetailLayout.GetProperty(*CollisionGeometryPolygonsPropertyPath), EPropertyLocation::Advanced) .DisplayName(LOCTEXT("CollisionShapes", "Collision Shapes")) .Visibility(ParticipatesInPhysics); }
void FSpriteDetailsCustomization::BuildRenderingSection(IDetailCategoryBuilder& RenderingCategory, IDetailLayoutBuilder& DetailLayout) { TAttribute<EVisibility> HideWhenInCollisionMode = TAttribute<EVisibility>::Create(TAttribute<EVisibility>::FGetter::CreateSP(this, &FSpriteDetailsCustomization::EditorModeIsNot, ESpriteEditorMode::EditCollisionMode)); TAttribute<EVisibility> ShowWhenInCollisionMode = TAttribute<EVisibility>::Create(TAttribute<EVisibility>::FGetter::CreateSP(this, &FSpriteDetailsCustomization::EditorModeMatches, ESpriteEditorMode::EditCollisionMode)); static const FText EditRenderingInRenderingMode = LOCTEXT("RenderingPropertiesHiddenInCollisionMode", "Switch to 'Edit RenderGeom' mode\nto edit Rendering settings"); RenderingCategory.AddCustomRow(EditRenderingInRenderingMode) .Visibility(ShowWhenInCollisionMode) .WholeRowContent() .HAlign(HAlign_Center) [ SNew(STextBlock) .Font(DetailLayout.GetDetailFontItalic()) .Justification(ETextJustify::Center) .Text(EditRenderingInRenderingMode) ]; TArray<TWeakObjectPtr<UObject>> ObjectsBeingCustomized; DetailLayout.GetObjectsBeingCustomized(/*out*/ ObjectsBeingCustomized); if (ObjectsBeingCustomized.Num() > 0) { if (UPaperSprite* SpriteBeingEdited = Cast<UPaperSprite>(ObjectsBeingCustomized[0].Get())) { static const FText TypesOfMaterialsTooltip = LOCTEXT("TypesOfMaterialsTooltip", "Translucent materials can have smooth alpha edges, blending with the background\nMasked materials have on or off alpha, useful for cutouts\nOpaque materials have no transparency but render faster"); RenderingCategory.HeaderContent ( SNew(SBox) .HAlign(HAlign_Right) [ SNew(SHorizontalBox) +SHorizontalBox::Slot() .Padding(FMargin(5.0f, 0.0f)) .AutoWidth() [ SNew(STextBlock) .Font(FEditorStyle::GetFontStyle("TinyText")) .Text(this, &FSpriteDetailsCustomization::GetRenderingHeaderContentText, TWeakObjectPtr<UPaperSprite>(SpriteBeingEdited)) .ToolTipText(TypesOfMaterialsTooltip) ] ] ); } } // Add the rendering geometry mode into the parent container (renamed) const FString RenderGeometryTypePropertyPath = FString::Printf(TEXT("%s.%s"), GET_MEMBER_NAME_STRING_CHECKED(UPaperSprite, RenderGeometry), GET_MEMBER_NAME_STRING_CHECKED(FSpriteGeometryCollection, GeometryType)); TSharedPtr<IPropertyHandle> RenderGeometryTypeProperty = DetailLayout.GetProperty(*RenderGeometryTypePropertyPath); RenderingCategory.AddProperty(RenderGeometryTypeProperty) .DisplayName(LOCTEXT("RenderGeometryType", "Render Geometry Type")) .Visibility(HideWhenInCollisionMode); // Show the alternate material, but only when the mode is Diced TAttribute<EVisibility> ShowWhenModeIsDiced = TAttribute<EVisibility>::Create(TAttribute<EVisibility>::FGetter::CreateSP(this, &FSpriteDetailsCustomization::PolygonModeMatches, RenderGeometryTypeProperty, ESpritePolygonMode::Diced)); TSharedPtr<IPropertyHandle> AlternateMaterialProperty = DetailLayout.GetProperty(GET_MEMBER_NAME_CHECKED(UPaperSprite, AlternateMaterial)); RenderingCategory.AddProperty(AlternateMaterialProperty) .Visibility(ShowWhenModeIsDiced); // Show the rendering geometry settings TSharedRef<IPropertyHandle> RenderGeometry = DetailLayout.GetProperty(GET_MEMBER_NAME_CHECKED(UPaperSprite, RenderGeometry)); IDetailPropertyRow& RenderGeometryProperty = RenderingCategory.AddProperty(RenderGeometry) .Visibility(HideWhenInCollisionMode); // Add the render polygons into advanced (renamed) const FString RenderGeometryPolygonsPropertyPath = FString::Printf(TEXT("%s.%s"), GET_MEMBER_NAME_STRING_CHECKED(UPaperSprite, RenderGeometry), GET_MEMBER_NAME_STRING_CHECKED(FSpriteGeometryCollection, Shapes)); RenderingCategory.AddProperty(DetailLayout.GetProperty(*RenderGeometryPolygonsPropertyPath), EPropertyLocation::Advanced) .DisplayName(LOCTEXT("RenderShapes", "Render Shapes")) .Visibility(HideWhenInCollisionMode); }
void FInternationalizationSettingsModelDetails::CustomizeDetails( IDetailLayoutBuilder& DetailBuilder ) { FInternationalization& I18N = FInternationalization::Get(); TArray< TWeakObjectPtr<UObject> > ObjectsBeingCustomized; DetailBuilder.GetObjectsBeingCustomized(ObjectsBeingCustomized); check(ObjectsBeingCustomized.Num() == 1); if(ObjectsBeingCustomized[0].IsValid()) { Model = Cast<UInternationalizationSettingsModel>(ObjectsBeingCustomized[0].Get()); } check(Model.IsValid()); Model->OnSettingChanged().AddRaw(this, &FInternationalizationSettingsModelDetails::OnSettingsChanged); // If the saved culture is not the same as the actual current culture, a restart is needed to sync them fully and properly. FString SavedCultureName = Model->GetCultureName(); if ( !SavedCultureName.IsEmpty() && SavedCultureName != I18N.GetCurrentCulture()->GetName() ) { SelectedCulture = I18N.GetCulture(SavedCultureName); RequiresRestart = true; } else { SelectedCulture = I18N.GetCurrentCulture(); RequiresRestart = false; } // Populate all our cultures RefreshAvailableCultures(); IDetailCategoryBuilder& CategoryBuilder = DetailBuilder.EditCategory("Internationalization"); const FText LanguageToolTipText = LOCTEXT("EditorLanguageTooltip", "Change the Editor language (requires restart to take effect)"); // For use in the Slate macros below, the type must be typedef'd to compile. typedef FCulturePtr ThreadSafeCulturePtr; CategoryBuilder.AddCustomRow("Language") .NameContent() [ SNew(SHorizontalBox) +SHorizontalBox::Slot() .Padding( FMargin( 0, 1, 0, 1 ) ) .FillWidth(1.0f) [ SNew(STextBlock) .Text(LOCTEXT("EditorLanguageLabel", "Language")) .Font(DetailBuilder.GetDetailFont()) .ToolTipText(LanguageToolTipText) ] ] .ValueContent() .MaxDesiredWidth(300.0f) [ SAssignNew(LanguageComboBox, SComboBox< ThreadSafeCulturePtr > ) .OptionsSource( &AvailableLanguages ) .InitiallySelectedItem(SelectedLanguage) .OnGenerateWidget(this, &FInternationalizationSettingsModelDetails::OnLanguageGenerateWidget, &DetailBuilder) .ToolTipText(LanguageToolTipText) .OnSelectionChanged(this, &FInternationalizationSettingsModelDetails::OnLanguageSelectionChanged) .Content() [ SNew(STextBlock) .Text(this, &FInternationalizationSettingsModelDetails::GetCurrentLanguageText) .Font(DetailBuilder.GetDetailFont()) ] ]; const FText RegionToolTipText = LOCTEXT("EditorRegionTooltip", "Change the Editor region (requires restart to take effect)"); CategoryBuilder.AddCustomRow("Region") .NameContent() [ SNew(SHorizontalBox) +SHorizontalBox::Slot() .Padding( FMargin( 0, 1, 0, 1 ) ) .FillWidth(1.0f) [ SNew(STextBlock) .Text(LOCTEXT("EditorRegionLabel", "Region")) .Font(DetailBuilder.GetDetailFont()) .ToolTipText(RegionToolTipText) ] ] .ValueContent() .MaxDesiredWidth(300.0f) [ SAssignNew(RegionComboBox, SComboBox< ThreadSafeCulturePtr > ) .OptionsSource( &AvailableRegions ) .InitiallySelectedItem(SelectedCulture) .OnGenerateWidget(this, &FInternationalizationSettingsModelDetails::OnRegionGenerateWidget, &DetailBuilder) .ToolTipText(RegionToolTipText) .OnSelectionChanged(this, &FInternationalizationSettingsModelDetails::OnRegionSelectionChanged) .IsEnabled(this, &FInternationalizationSettingsModelDetails::IsRegionSelectionAllowed) .Content() [ SNew(STextBlock) .Text(this, &FInternationalizationSettingsModelDetails::GetCurrentRegionText) .Font(DetailBuilder.GetDetailFont()) ] ]; const FText FieldNamesToolTipText = LOCTEXT("EditorFieldNamesTooltip", "Toggle showing localized field names (requires restart to take effect)"); CategoryBuilder.AddCustomRow("FieldNames") .NameContent() [ SNew(SHorizontalBox) +SHorizontalBox::Slot() .Padding( FMargin( 0, 1, 0, 1 ) ) .FillWidth(1.0f) [ SNew(STextBlock) .Text(LOCTEXT("EditorFieldNamesLabel", "Use Localized Field Names")) .Font(DetailBuilder.GetDetailFont()) .ToolTipText(FieldNamesToolTipText) ] ] .ValueContent() .MaxDesiredWidth(300.0f) [ SAssignNew(FieldNamesCheckBox, SCheckBox) .IsChecked(Model->ShouldLoadLocalizedPropertyNames() ? ESlateCheckBoxState::Checked : ESlateCheckBoxState::Unchecked) .ToolTipText(FieldNamesToolTipText) .OnCheckStateChanged(this, &FInternationalizationSettingsModelDetails::ShoudLoadLocalizedFieldNamesCheckChanged) ]; CategoryBuilder.AddCustomRow("RestartWarning") .Visibility( TAttribute<EVisibility>(this, &FInternationalizationSettingsModelDetails::GetInternationalizationRestartRowVisibility) ) .WholeRowContent() .HAlign(HAlign_Center) [ SNew( SHorizontalBox ) +SHorizontalBox::Slot() .AutoWidth() .VAlign(VAlign_Center) .Padding(2.0f, 0.0f) [ SNew( SImage ) .Image( FCoreStyle::Get().GetBrush("Icons.Warning") ) ] +SHorizontalBox::Slot() .FillWidth(1.0f) .VAlign(VAlign_Center) [ SNew( STextBlock ) .Text( LOCTEXT("RestartWarningText", "Changes require restart to take effect.") ) .Font(DetailBuilder.GetDetailFont()) ] ]; }
void FFbxImportUIDetails::CustomizeDetails( IDetailLayoutBuilder& DetailBuilder ) { CachedDetailBuilder = &DetailBuilder; TArray<TWeakObjectPtr<UObject>> EditingObjects; DetailBuilder.GetObjectsBeingCustomized(EditingObjects); check(EditingObjects.Num() == 1); ImportUI = Cast<UFbxImportUI>(EditingObjects[0].Get()); // Handle mesh category IDetailCategoryBuilder& MeshCategory = DetailBuilder.EditCategory("Mesh", FText::GetEmpty(), ECategoryPriority::Important); IDetailCategoryBuilder& TransformCategory = DetailBuilder.EditCategory("Transform"); TArray<TSharedRef<IPropertyHandle>> CategoryDefaultProperties; TArray<TSharedPtr<IPropertyHandle>> ExtraProperties; // Grab and hide per-type import options TSharedRef<IPropertyHandle> StaticMeshDataProp = DetailBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(UFbxImportUI, StaticMeshImportData)); TSharedRef<IPropertyHandle> SkeletalMeshDataProp = DetailBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(UFbxImportUI, SkeletalMeshImportData)); TSharedRef<IPropertyHandle> AnimSequenceDataProp = DetailBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(UFbxImportUI, AnimSequenceImportData)); DetailBuilder.HideProperty(StaticMeshDataProp); DetailBuilder.HideProperty(SkeletalMeshDataProp); DetailBuilder.HideProperty(AnimSequenceDataProp); MeshCategory.GetDefaultProperties(CategoryDefaultProperties); switch(ImportUI->MeshTypeToImport) { case FBXIT_StaticMesh: CollectChildPropertiesRecursive(StaticMeshDataProp, ExtraProperties); break; case FBXIT_SkeletalMesh: if(ImportUI->bImportMesh) { CollectChildPropertiesRecursive(SkeletalMeshDataProp, ExtraProperties); } else { ImportUI->MeshTypeToImport = FBXIT_Animation; } break; default: break; } EFBXImportType ImportType = ImportUI->MeshTypeToImport; if(ImportUI->OriginalImportType == FBXIT_SkeletalMesh) { TSharedRef<IPropertyHandle> ImportMeshProp = DetailBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(UFbxImportUI, bImportMesh)); ImportMeshProp->SetOnPropertyValueChanged(FSimpleDelegate::CreateSP(this, &FFbxImportUIDetails::ImportMeshToggleChanged)); MeshCategory.AddProperty(ImportMeshProp); } if(ImportType != FBXIT_Animation) { TSharedRef<IPropertyHandle> ImportSkeletalProp = DetailBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(UFbxImportUI, bImportAsSkeletal)); ImportSkeletalProp->SetOnPropertyValueChanged(FSimpleDelegate::CreateSP(this, &FFbxImportUIDetails::MeshImportModeChanged)); MeshCategory.AddProperty(ImportSkeletalProp); } for(TSharedRef<IPropertyHandle> Handle : CategoryDefaultProperties) { FString MetaData = Handle->GetMetaData(TEXT("ImportType")); if(!IsImportTypeMetaDataValid(ImportType, MetaData)) { DetailBuilder.HideProperty(Handle); } } for(TSharedPtr<IPropertyHandle> Handle : ExtraProperties) { FString ImportTypeMetaData = Handle->GetMetaData(TEXT("ImportType")); FString CategoryMetaData = Handle->GetMetaData(TEXT("ImportCategory")); if(IsImportTypeMetaDataValid(ImportType, ImportTypeMetaData)) { // Decide on category if(!CategoryMetaData.IsEmpty()) { // Populate custom categories. IDetailCategoryBuilder& CustomCategory = DetailBuilder.EditCategory(*CategoryMetaData); CustomCategory.AddProperty(Handle); } else { // No override, add to default mesh category IDetailPropertyRow& PropertyRow = MeshCategory.AddProperty(Handle); UProperty* Property = Handle->GetProperty(); if (Property != nullptr) { if (Property->GetFName() == GET_MEMBER_NAME_CHECKED(UFbxStaticMeshImportData, StaticMeshLODGroup)) { SetStaticMeshLODGroupWidget(PropertyRow, Handle); } if (Property->GetFName() == GET_MEMBER_NAME_CHECKED(UFbxStaticMeshImportData, VertexOverrideColor)) { // Cache the VertexColorImportOption property VertexColorImportOptionHandle = StaticMeshDataProp->GetChildHandle(GET_MEMBER_NAME_CHECKED(UFbxStaticMeshImportData, VertexColorImportOption)); PropertyRow.IsEnabled(TAttribute<bool>(this, &FFbxImportUIDetails::GetVertexOverrideColorEnabledState)); } } } } } // Animation Category IDetailCategoryBuilder& AnimCategory = DetailBuilder.EditCategory("Animation", FText::GetEmpty(), ECategoryPriority::Important); CategoryDefaultProperties.Empty(); AnimCategory.GetDefaultProperties(CategoryDefaultProperties); for(TSharedRef<IPropertyHandle> Handle : CategoryDefaultProperties) { FString MetaData = Handle->GetMetaData(TEXT("ImportType")); if(!IsImportTypeMetaDataValid(ImportType, MetaData)) { DetailBuilder.HideProperty(Handle); } } if(ImportType == FBXIT_Animation || ImportType == FBXIT_SkeletalMesh) { ExtraProperties.Empty(); CollectChildPropertiesRecursive(AnimSequenceDataProp, ExtraProperties); // Before we add the import data properties we need to re-add any properties we want to appear above them in the UI TSharedRef<IPropertyHandle> ImportAnimProp = DetailBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(UFbxImportUI, bImportAnimations)); // If we're importing an animation file we really don't need to ask this DetailBuilder.HideProperty(ImportAnimProp); if(ImportType == FBXIT_Animation) { ImportUI->bImportAnimations = true; } else { AnimCategory.AddProperty(ImportAnimProp); } for(TSharedPtr<IPropertyHandle> Handle : ExtraProperties) { FString CategoryMetaData = Handle->GetMetaData(TEXT("ImportCategory")); if(Handle->GetProperty()->GetOuter() == UFbxAnimSequenceImportData::StaticClass() && CategoryMetaData.IsEmpty()) { // Add to default anim category if no override specified IDetailPropertyRow& PropertyRow = AnimCategory.AddProperty(Handle); } else if(ImportType == FBXIT_Animation && !CategoryMetaData.IsEmpty()) { // Override category is available IDetailCategoryBuilder& CustomCategory = DetailBuilder.EditCategory(*CategoryMetaData); CustomCategory.AddProperty(Handle); } } } else { // Hide animation options CategoryDefaultProperties.Empty(); AnimCategory.GetDefaultProperties(CategoryDefaultProperties); for(TSharedRef<IPropertyHandle> Handle : CategoryDefaultProperties) { DetailBuilder.HideProperty(Handle); } } // Material Category IDetailCategoryBuilder& MaterialCategory = DetailBuilder.EditCategory("Material"); if(ImportType == FBXIT_Animation) { // In animation-only mode, hide the material display CategoryDefaultProperties.Empty(); MaterialCategory.GetDefaultProperties(CategoryDefaultProperties); for(TSharedRef<IPropertyHandle> Handle : CategoryDefaultProperties) { DetailBuilder.HideProperty(Handle); } } else { TSharedRef<IPropertyHandle> TextureDataProp = DetailBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(UFbxImportUI, TextureImportData)); DetailBuilder.HideProperty(TextureDataProp); ExtraProperties.Empty(); CollectChildPropertiesRecursive(TextureDataProp, ExtraProperties); for(TSharedPtr<IPropertyHandle> Handle : ExtraProperties) { // We ignore base import data for this window. if(Handle->GetProperty()->GetOuter() == UFbxTextureImportData::StaticClass()) { MaterialCategory.AddProperty(Handle); } } } }
void FSpriteDetailsCustomization::BuildCollisionSection(IDetailCategoryBuilder& CollisionCategory, IDetailLayoutBuilder& DetailLayout) { TSharedPtr<IPropertyHandle> SpriteCollisionDomainProperty = DetailLayout.GetProperty(GET_MEMBER_NAME_CHECKED(UPaperSprite, SpriteCollisionDomain)); CollisionCategory.HeaderContent ( SNew(SBox) .HAlign(HAlign_Right) [ SNew(SHorizontalBox) +SHorizontalBox::Slot() .Padding(FMargin(5.0f, 0.0f)) .AutoWidth() [ SNew(STextBlock) .Font(FEditorStyle::GetFontStyle("TinyText")) .Text(this, &FSpriteDetailsCustomization::GetCollisionHeaderContentText, SpriteCollisionDomainProperty) ] ] ); TAttribute<EVisibility> ParticipatesInPhysics = TAttribute<EVisibility>::Create(TAttribute<EVisibility>::FGetter::CreateSP( this, &FSpriteDetailsCustomization::AnyPhysicsMode, SpriteCollisionDomainProperty)); TAttribute<EVisibility> ParticipatesInPhysics3D = TAttribute<EVisibility>::Create(TAttribute<EVisibility>::FGetter::CreateSP(this, &FSpriteDetailsCustomization::PhysicsModeMatches, SpriteCollisionDomainProperty, ESpriteCollisionMode::Use3DPhysics)); TAttribute<EVisibility> ParticipatesInPhysics2D = TAttribute<EVisibility>::Create(TAttribute<EVisibility>::FGetter::CreateSP(this, &FSpriteDetailsCustomization::PhysicsModeMatches, SpriteCollisionDomainProperty, ESpriteCollisionMode::Use2DPhysics)); TAttribute<EVisibility> HideWhenInRenderingMode = TAttribute<EVisibility>::Create(TAttribute<EVisibility>::FGetter::CreateSP(this, &FSpriteDetailsCustomization::EditorModeIsNot, ESpriteEditorMode::EditRenderingGeomMode)); TAttribute<EVisibility> ShowWhenInRenderingMode = TAttribute<EVisibility>::Create(TAttribute<EVisibility>::FGetter::CreateSP(this, &FSpriteDetailsCustomization::EditorModeMatches, ESpriteEditorMode::EditRenderingGeomMode)); static const FText EditCollisionInCollisionMode = LOCTEXT("CollisionPropertiesHiddenInRenderingMode", "Switch to 'Edit Collsion' mode\nto edit Collision settings"); CollisionCategory.AddCustomRow(EditCollisionInCollisionMode) .Visibility(ShowWhenInRenderingMode) .WholeRowContent() .HAlign(HAlign_Center) [ SNew(STextBlock) .Font(DetailLayout.GetDetailFontItalic()) .Justification(ETextJustify::Center) .Text(EditCollisionInCollisionMode) ]; CollisionCategory.AddProperty(SpriteCollisionDomainProperty).Visibility(HideWhenInRenderingMode); // Add a warning bar about 2D collision being experimental FText WarningFor2D = LOCTEXT("Experimental2DPhysicsWarning", "2D collision support is *experimental*"); FText TooltipFor2D = LOCTEXT("Experimental2DPhysicsWarningTooltip", "2D collision support is *experimental* and should not be relied on yet.\n\nRigid body collision detection and response works, but there are only precompiled libraries for Windows currently.\n\nRaycasts are partially supported (and need to be enabled in project settings), but queries, sweeps, or overlap tests are not implemented yet."); GenerateWarningRow(CollisionCategory, /*bExperimental=*/ true, WarningFor2D, TooltipFor2D, TEXT("Shared/Editors/SpriteEditor"), TEXT("CollisionDomain2DWarning")) .Visibility(ParticipatesInPhysics2D); // Add a warning bar if 2D collision queries aren't enabled TAttribute<EVisibility> WarnAbout2DQueriesBeingDisabledVisibility = TAttribute<EVisibility>::Create(TAttribute<EVisibility>::FGetter::CreateSP(this, &FSpriteDetailsCustomization::Get2DPhysicsNotEnabledWarningVisibility, SpriteCollisionDomainProperty)); FText QueryWarningFor2D = LOCTEXT("Query2DPhysicsWarning", "2D collision queries are disabled"); FText QueryTooltipFor2D = LOCTEXT("Query2DPhysicsWarningTooltip", "You can enable 2D queries in Project Settings..Physics by setting bEnable2DPhysics to true, otherwise only collision detection and response will work.\n\nNote: Only raycasts are partially supported; other queries, sweeps, and overlap tests are not implemented yet."); GenerateWarningRow(CollisionCategory, /*bExperimental=*/ false, QueryWarningFor2D, QueryTooltipFor2D, TEXT("Shared/Editors/SpriteEditor"), TEXT("Disabled2DCollisionQueriesWarning")) .Visibility(WarnAbout2DQueriesBeingDisabledVisibility); // Add the collision geometry mode into the parent container (renamed) { // Restrict the diced value TSharedPtr<FPropertyRestriction> PreventDicedRestriction = MakeShareable(new FPropertyRestriction(LOCTEXT("CollisionGeometryDoesNotSupportDiced", "Collision geometry can not be set to Diced"))); const UEnum* const SpritePolygonModeEnum = FindObject<UEnum>(ANY_PACKAGE, TEXT("ESpritePolygonMode")); PreventDicedRestriction->AddDisabledValue(SpritePolygonModeEnum->GetEnumNameStringByValue((uint8)ESpritePolygonMode::Diced)); // Find and add the property const FString CollisionGeometryTypePropertyPath = FString::Printf(TEXT("%s.%s"), GET_MEMBER_NAME_STRING_CHECKED(UPaperSprite, CollisionGeometry), GET_MEMBER_NAME_STRING_CHECKED(FSpriteGeometryCollection, GeometryType)); TSharedPtr<IPropertyHandle> CollisionGeometryTypeProperty = DetailLayout.GetProperty(*CollisionGeometryTypePropertyPath); CollisionGeometryTypeProperty->AddRestriction(PreventDicedRestriction.ToSharedRef()); CollisionCategory.AddProperty(CollisionGeometryTypeProperty) .DisplayName(LOCTEXT("CollisionGeometryType", "Collision Geometry Type")) .Visibility(ParticipatesInPhysics); } // Show the collision thickness only in 3D mode CollisionCategory.AddProperty( DetailLayout.GetProperty(GET_MEMBER_NAME_CHECKED(UPaperSprite, CollisionThickness)) ) .Visibility(ParticipatesInPhysics3D); // Show the default body instance (and only it) from the body setup (if it exists) DetailLayout.HideProperty("BodySetup"); IDetailPropertyRow& BodySetupDefaultInstance = CollisionCategory.AddProperty("BodySetup.DefaultInstance"); TArray<TWeakObjectPtr<UObject>> SpritesBeingEdited; DetailLayout.GetObjectsBeingCustomized(/*out*/ SpritesBeingEdited); TArray<UObject*> BodySetupList; for (auto WeakSpritePtr : SpritesBeingEdited) { if (UPaperSprite* Sprite = Cast<UPaperSprite>(WeakSpritePtr.Get())) { if (UBodySetup* BodySetup = Sprite->BodySetup) { BodySetupList.Add(BodySetup); } } } if (BodySetupList.Num() > 0) { IDetailPropertyRow* DefaultInstanceRow = CollisionCategory.AddExternalProperty(BodySetupList, GET_MEMBER_NAME_CHECKED(UBodySetup, DefaultInstance)); if (DefaultInstanceRow != nullptr) { DefaultInstanceRow->Visibility(ParticipatesInPhysics); } } // Show the collision geometry when not None CollisionCategory.AddProperty(DetailLayout.GetProperty(GET_MEMBER_NAME_CHECKED(UPaperSprite, CollisionGeometry))) .Visibility(ParticipatesInPhysics); // Add the collision polygons into advanced (renamed) const FString CollisionGeometryPolygonsPropertyPath = FString::Printf(TEXT("%s.%s"), GET_MEMBER_NAME_STRING_CHECKED(UPaperSprite, CollisionGeometry), GET_MEMBER_NAME_STRING_CHECKED(FSpriteGeometryCollection, Shapes)); CollisionCategory.AddProperty(DetailLayout.GetProperty(*CollisionGeometryPolygonsPropertyPath), EPropertyLocation::Advanced) .DisplayName(LOCTEXT("CollisionShapes", "Collision Shapes")) .Visibility(ParticipatesInPhysics); }
void FSkeletalBodySetupDetails::CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) { DetailBuilder.GetObjectsBeingCustomized(ObjectsCustomized); IDetailCategoryBuilder& Cat = DetailBuilder.EditCategory(TEXT("PhysicalAnimation")); const TArray<TWeakObjectPtr<UObject>>& ObjectsCustomizedLocal = ObjectsCustomized; auto PhysAnimEditable = [ObjectsCustomizedLocal]() -> bool { bool bVisible = ObjectsCustomizedLocal.Num() > 0; for (TWeakObjectPtr<UObject> WeakObj : ObjectsCustomizedLocal) { if (USkeletalBodySetup* BS = Cast<USkeletalBodySetup>(WeakObj.Get())) { if (!BS->FindPhysicalAnimationProfile(BS->GetCurrentPhysicalAnimationProfileName())) { bVisible = false; break; } } else { bVisible = false; break; } } return bVisible; }; auto AddProfileLambda = [ObjectsCustomizedLocal]() { const FScopedTransaction Transaction(LOCTEXT("AddProfile", "Add Physical Animation Profile")); bool bVisible = ObjectsCustomizedLocal.Num() > 0; for (TWeakObjectPtr<UObject> WeakObj : ObjectsCustomizedLocal) { if (USkeletalBodySetup* BS = Cast<USkeletalBodySetup>(WeakObj.Get())) { FName ProfileName = BS->GetCurrentPhysicalAnimationProfileName(); if (!BS->FindPhysicalAnimationProfile(ProfileName)) { BS->CurrentPhysicalAnimationProfile = FPhysicalAnimationProfile(); BS->AddPhysicalAnimationProfile(ProfileName); } } } return FReply::Handled();; }; auto DeleteProfileLambda = [ObjectsCustomizedLocal]() { const FScopedTransaction Transaction(LOCTEXT("RemoveProfile", "Remove Physical Animation Profile")); bool bVisible = ObjectsCustomizedLocal.Num() > 0; for (TWeakObjectPtr<UObject> WeakObj : ObjectsCustomizedLocal) { if (USkeletalBodySetup* BS = Cast<USkeletalBodySetup>(WeakObj.Get())) { FName ProfileName = BS->GetCurrentPhysicalAnimationProfileName(); BS->RemovePhysicalAnimationProfile(ProfileName); } } return FReply::Handled();; }; TAttribute<EVisibility> PhysAnimVisible = TAttribute<EVisibility>::Create(TAttribute<EVisibility>::FGetter::CreateLambda([PhysAnimEditable]() { return PhysAnimEditable() == true ? EVisibility::Visible : EVisibility::Collapsed; })); TAttribute<EVisibility> NewPhysAnimButtonVisible = TAttribute<EVisibility>::Create(TAttribute<EVisibility>::FGetter::CreateLambda([PhysAnimEditable]() { return PhysAnimEditable() == true ? EVisibility::Collapsed : EVisibility::Visible; })); TAttribute<bool> PhysAnimButtonEnabled = TAttribute<bool>::Create(TAttribute<bool>::FGetter::CreateLambda([ObjectsCustomizedLocal]() { for (TWeakObjectPtr<UObject> WeakObj : ObjectsCustomizedLocal) { if (USkeletalBodySetup* BS = Cast<USkeletalBodySetup>(WeakObj.Get())) { if (BS->GetCurrentPhysicalAnimationProfileName() == NAME_None) { return false; } } } return true; })); auto CurrentProfileTextLambda = [ObjectsCustomizedLocal]() -> FText { for (TWeakObjectPtr<UObject> WeakObj : ObjectsCustomizedLocal) { if (USkeletalBodySetup* BS = Cast<USkeletalBodySetup>(WeakObj.Get())) { return FText::FromName(BS->GetCurrentPhysicalAnimationProfileName()); } } return FText::FromName(NAME_None); }; TAttribute<FText> CreateProfileLabel = TAttribute<FText>::Create(TAttribute<FText>::FGetter::CreateLambda([CurrentProfileTextLambda]() { return FText::Format(LOCTEXT("CreatePhysAnimLabel", "Add to profile: <RichTextBlock.Bold>{0}</>"), CurrentProfileTextLambda()); })); Cat.AddCustomRow(LOCTEXT("NewPhysAnim", "NewPhysicalAnimationProfile")) .Visibility(NewPhysAnimButtonVisible) [ SNew(SVerticalBox) + SVerticalBox::Slot() .HAlign(HAlign_Center) .Padding(0, 10, 0, 10) [ SNew(SBox) .MinDesiredWidth(180) .HeightOverride(32) [ SNew(SButton) .HAlign(HAlign_Center) .IsEnabled(PhysAnimButtonEnabled) .OnClicked_Lambda(AddProfileLambda) [ SNew(SRichTextBlock) .Text(CreateProfileLabel) .DecoratorStyleSet(&FEditorStyle::Get()) .ToolTipText(LOCTEXT("NewPhysAnimButtonToolTip", "Add to current physical animation profile.")) ] ] ] + SVerticalBox::Slot() .HAlign(HAlign_Center) [ SNew(STextBlock) .Text(LOCTEXT("NewPhysAnimLabel", "At least one body is not in the current physical animation profile.")) .Font(IDetailLayoutBuilder::GetDetailFont()) .ToolTipText(LOCTEXT("NewPhysAnimLabelTooltip", "Could not found in the current physical animation profile.")) ] ]; TSharedPtr<IPropertyHandle> PhysicalAnimationProfile = DetailBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(USkeletalBodySetup, CurrentPhysicalAnimationProfile)); PhysicalAnimationProfile->MarkHiddenByCustomization(); uint32 NumChildren = 0; TSharedPtr<IPropertyHandle> ProfileData = PhysicalAnimationProfile->GetChildHandle(GET_MEMBER_NAME_CHECKED(FPhysicalAnimationProfile, PhysicalAnimationData)); ProfileData->GetNumChildren(NumChildren); for(uint32 ChildIdx = 0; ChildIdx < NumChildren; ++ChildIdx) { TSharedPtr<IPropertyHandle> Child = ProfileData->GetChildHandle(ChildIdx); if(!Child->IsCustomized()) { Cat.AddProperty(Child) .Visibility(PhysAnimVisible); } } TAttribute<FText> DeleteProfileLabel = TAttribute<FText>::Create(TAttribute<FText>::FGetter::CreateLambda([CurrentProfileTextLambda]() { return FText::Format(LOCTEXT("DeletePhysAnimLabel", "Remove from profile: <RichTextBlock.Bold>{0}</>"), CurrentProfileTextLambda()); })); Cat.AddCustomRow(LOCTEXT("DeletePhysAnim", "DeletePhysicalAnimationProfile")) .Visibility(PhysAnimVisible) [ SNew(SVerticalBox) + SVerticalBox::Slot() .Padding(0, 10, 0, 10) .HAlign(HAlign_Center) [ SNew(SBox) .MinDesiredWidth(180) [ SNew(SButton) .HAlign(HAlign_Center) .OnClicked_Lambda(DeleteProfileLambda) [ SNew(SRichTextBlock) .Text(DeleteProfileLabel) .DecoratorStyleSet(&FEditorStyle::Get()) .ToolTipText(LOCTEXT("DeletePhysAnimButtonToolTip", "Removes the selected body from the current physical animation profile.")) ] ] ] ]; }
void FSpriteDetailsCustomization::BuildCollisionSection(IDetailCategoryBuilder& CollisionCategory, IDetailLayoutBuilder& DetailLayout) { TSharedPtr<IPropertyHandle> SpriteCollisionDomainProperty = DetailLayout.GetProperty(GET_MEMBER_NAME_CHECKED(UPaperSprite, SpriteCollisionDomain)); TAttribute<EVisibility> ParticipatesInPhysics = TAttribute<EVisibility>::Create( TAttribute<EVisibility>::FGetter::CreateSP( this, &FSpriteDetailsCustomization::AnyPhysicsMode, SpriteCollisionDomainProperty) ) ; TAttribute<EVisibility> ParticipatesInPhysics3D = TAttribute<EVisibility>::Create(TAttribute<EVisibility>::FGetter::CreateSP(this, &FSpriteDetailsCustomization::PhysicsModeMatches, SpriteCollisionDomainProperty, ESpriteCollisionMode::Use3DPhysics)); TAttribute<EVisibility> ParticipatesInPhysics2D = TAttribute<EVisibility>::Create(TAttribute<EVisibility>::FGetter::CreateSP(this, &FSpriteDetailsCustomization::PhysicsModeMatches, SpriteCollisionDomainProperty, ESpriteCollisionMode::Use2DPhysics)); CollisionCategory.AddProperty(SpriteCollisionDomainProperty); // Add a warning bar about 2D collision being experimental FText WarningFor2D = LOCTEXT("Experimental2DPhysicsWarning", "2D collision support is *experimental*"); FText TooltipFor2D = LOCTEXT("Experimental2DPhysicsWarningTooltip", "2D collision support is *experimental* and should not be relied on yet.\n\nRigid body collision detection and response works, but there are only precompiled libraries for Windows currently.\n\nRaycasts are partially supported (and need to be enabled in project settings), but queries, sweeps, or overlap tests are not implemented yet."); GenerateWarningRow(CollisionCategory, /*bExperimental=*/ true, WarningFor2D, TooltipFor2D, TEXT("Shared/Editors/SpriteEditor"), TEXT("CollisionDomain2DWarning")) .Visibility(ParticipatesInPhysics2D); // Add a warning bar if 2D collision queries aren't enabled TAttribute<EVisibility> WarnAbout2DQueriesBeingDisabledVisibility = TAttribute<EVisibility>::Create(TAttribute<EVisibility>::FGetter::CreateSP(this, &FSpriteDetailsCustomization::Get2DPhysicsNotEnabledWarningVisibility, SpriteCollisionDomainProperty)); FText QueryWarningFor2D = LOCTEXT("Query2DPhysicsWarning", "2D collision queries are disabled"); FText QueryTooltipFor2D = LOCTEXT("Query2DPhysicsWarningTooltip", "You can enable 2D queries in Project Settings..Physics by setting bEnable2DPhysics to true, otherwise only collision detection and response will work.\n\nNote: Only raycasts are partially supported; other queries, sweeps, and overlap tests are not implemented yet."); GenerateWarningRow(CollisionCategory, /*bExperimental=*/ false, QueryWarningFor2D, QueryTooltipFor2D, TEXT("Shared/Editors/SpriteEditor"), TEXT("Disabled2DCollisionQueriesWarning")) .Visibility(WarnAbout2DQueriesBeingDisabledVisibility); // Add the collision geometry mode into the parent container (renamed) { // Restrict the diced value TSharedPtr<FPropertyRestriction> PreventDicedRestriction = MakeShareable(new FPropertyRestriction(LOCTEXT("CollisionGeometryDoesNotSupportDiced", "Collision geometry can not be set to Diced"))); PreventDicedRestriction->AddValue(TEXT("Diced")); // Find and add the property const FString CollisionGeometryTypePropertyPath = FString::Printf(TEXT("%s.%s"), GET_MEMBER_NAME_STRING_CHECKED(UPaperSprite, CollisionGeometry), GET_MEMBER_NAME_STRING_CHECKED(FSpritePolygonCollection, GeometryType)); TSharedPtr<IPropertyHandle> CollisionGeometryTypeProperty = DetailLayout.GetProperty(*CollisionGeometryTypePropertyPath); CollisionGeometryTypeProperty->AddRestriction(PreventDicedRestriction.ToSharedRef()); CollisionCategory.AddProperty(CollisionGeometryTypeProperty) .DisplayName(LOCTEXT("CollisionGeometryType", "Collision Geometry Type")) .Visibility(ParticipatesInPhysics); } // Show the collision geometry when not None CollisionCategory.AddProperty( DetailLayout.GetProperty(GET_MEMBER_NAME_CHECKED(UPaperSprite, CollisionGeometry)) ) .Visibility(ParticipatesInPhysics); // Show the collision thickness only in 3D mode CollisionCategory.AddProperty( DetailLayout.GetProperty(GET_MEMBER_NAME_CHECKED(UPaperSprite, CollisionThickness)) ) .Visibility(ParticipatesInPhysics3D); // Add the collision polygons into advanced (renamed) const FString CollisionGeometryPolygonsPropertyPath = FString::Printf(TEXT("%s.%s"), GET_MEMBER_NAME_STRING_CHECKED(UPaperSprite, CollisionGeometry), GET_MEMBER_NAME_STRING_CHECKED(FSpritePolygonCollection, Polygons)); CollisionCategory.AddProperty(DetailLayout.GetProperty(*CollisionGeometryPolygonsPropertyPath), EPropertyLocation::Advanced) .DisplayName(LOCTEXT("CollisionPolygons", "Collision Polygons")) .Visibility(ParticipatesInPhysics); // Show the default body instance (and only it) from the body setup (if it exists) DetailLayout.HideProperty("BodySetup"); IDetailPropertyRow& BodySetupDefaultInstance = CollisionCategory.AddProperty("BodySetup.DefaultInstance"); TArray<TWeakObjectPtr<UObject>> SpritesBeingEdited; DetailLayout.GetObjectsBeingCustomized(/*out*/ SpritesBeingEdited); TArray<UObject*> BodySetupList; for (auto WeakSpritePtr : SpritesBeingEdited) { if (UPaperSprite* Sprite = Cast<UPaperSprite>(WeakSpritePtr.Get())) { if (UBodySetup* BodySetup = Sprite->BodySetup) { BodySetupList.Add(BodySetup); } } } if (BodySetupList.Num() > 0) { IDetailPropertyRow* DefaultInstanceRow = CollisionCategory.AddExternalProperty(BodySetupList, GET_MEMBER_NAME_CHECKED(UBodySetup, DefaultInstance)); if (DefaultInstanceRow != nullptr) { DefaultInstanceRow->Visibility(ParticipatesInPhysics); } } }
void FBodySetupDetails::CustomizeDetails( IDetailLayoutBuilder& DetailBuilder ) { // Customize collision section { if ( DetailBuilder.GetProperty("DefaultInstance")->IsValidHandle() ) { IDetailCategoryBuilder& PhysicsCategory = DetailBuilder.EditCategory("Physics"); IDetailCategoryBuilder& CollisionCategory = DetailBuilder.EditCategory("Collision"); TSharedPtr<IPropertyHandle> BodyInstanceHandler = DetailBuilder.GetProperty("DefaultInstance"); DetailBuilder.HideProperty(BodyInstanceHandler); TSharedPtr<IPropertyHandle> CollisionTraceHandler = DetailBuilder.GetProperty("CollisionTraceFlag"); DetailBuilder.HideProperty(CollisionTraceHandler); // add physics properties to physics category uint32 NumChildren = 0; BodyInstanceHandler->GetNumChildren(NumChildren); // Get the objects being customized so we can enable/disable editing of 'Simulate Physics' DetailBuilder.GetObjectsBeingCustomized(ObjectsCustomized); // add all properties of this now - after adding for (uint32 ChildIndex=0; ChildIndex < NumChildren; ++ChildIndex) { TSharedPtr<IPropertyHandle> ChildProperty = BodyInstanceHandler->GetChildHandle(ChildIndex); FString Category = FObjectEditorUtils::GetCategory(ChildProperty->GetProperty()); if (ChildProperty->GetProperty()->GetName() == TEXT("bSimulatePhysics") || ChildProperty->GetProperty()->GetName() == TEXT("bAutoWeld")) { // skip bSimulatePhysics // this is because we don't want bSimulatePhysics to show up // phat editor // staitc mesh already hides everything else not interested in // so phat editor just should not show this option //also hide bAutoWeld for phat continue; } else if (ChildProperty->GetProperty()->GetName() == TEXT("MassInKg")) { PhysicsCategory.AddCustomRow(TEXT("Mass"), false) .IsEnabled(TAttribute<bool>(this, &FBodySetupDetails::IsBodyMassEnabled)) .NameContent() [ ChildProperty->CreatePropertyNameWidget() ] .ValueContent() [ SNew(SVerticalBox) + SVerticalBox::Slot() .AutoHeight() [ SNew(SEditableTextBox) .Text(this, &FBodySetupDetails::OnGetBodyMass) .IsReadOnly(this, &FBodySetupDetails::IsBodyMassReadOnly) .Font(IDetailLayoutBuilder::GetDetailFont()) .Visibility(this, &FBodySetupDetails::IsMassVisible, false) ] + SVerticalBox::Slot() .AutoHeight() [ SNew(SVerticalBox) .Visibility(this, &FBodySetupDetails::IsMassVisible, true) + SVerticalBox::Slot() .AutoHeight() [ ChildProperty->CreatePropertyValueWidget() ] ] ]; continue; } if (Category == TEXT("Physics")) { PhysicsCategory.AddProperty(ChildProperty); } else if (Category == TEXT("Collision")) { CollisionCategory.AddProperty(ChildProperty); } } } } }
void FPrimitiveComponentDetails::CustomizeDetails( IDetailLayoutBuilder& DetailBuilder ) { TSharedRef<IPropertyHandle> MobilityHandle = DetailBuilder.GetProperty("Mobility", USceneComponent::StaticClass()); MobilityHandle->SetToolTipText(LOCTEXT("PrimitiveMobilityTooltip", "Mobility for primitive components controls how they can be modified in game and therefore how they interact with lighting and physics.\n● A movable primitive component can be changed in game, but requires dynamic lighting and shadowing from lights which have a large performance cost.\n● A static primitive component can't be changed in game, but can have its lighting baked, which allows rendering to be very efficient.").ToString()); if ( DetailBuilder.GetProperty("BodyInstance")->IsValidHandle() ) { TSharedPtr<IPropertyHandle> BodyInstanceHandler = DetailBuilder.GetProperty("BodyInstance"); uint32 NumChildren = 0; BodyInstanceHandler->GetNumChildren(NumChildren); // See if we are hiding Physics category TArray<FString> HideCategories; FEditorCategoryUtils::GetClassHideCategories(DetailBuilder.GetDetailsView().GetBaseClass(), HideCategories); if (!HideCategories.Contains(TEXT("Physics"))) { IDetailCategoryBuilder& PhysicsCategory = DetailBuilder.EditCategory("Physics"); // Get the objects being customized so we can enable/disable editing of 'Simulate Physics' DetailBuilder.GetObjectsBeingCustomized(ObjectsCustomized); bool bDisplayMass = true; bool bDisplayMassOverride = true; for (int32 i = 0; i < ObjectsCustomized.Num(); ++i) { if (ObjectsCustomized[i].IsValid() && ObjectsCustomized[i]->IsA(UDestructibleComponent::StaticClass())) { bDisplayMass = false; bDisplayMassOverride = false; } if (ObjectsCustomized[i].IsValid() && ObjectsCustomized[i]->IsA(USkeletalMeshComponent::StaticClass())) { bDisplayMassOverride = false; } } // add all physics properties now - after adding mass for (uint32 ChildIndex = 0; ChildIndex < NumChildren; ++ChildIndex) { TSharedPtr<IPropertyHandle> ChildProperty = BodyInstanceHandler->GetChildHandle(ChildIndex); FString Category = FObjectEditorUtils::GetCategory(ChildProperty->GetProperty()); FString PropName = ChildProperty->GetProperty()->GetName(); if (Category == TEXT("Physics")) { // Only permit modifying bSimulatePhysics when the body has some geometry. if (PropName == TEXT("bSimulatePhysics")) { PhysicsCategory.AddProperty(ChildProperty).EditCondition(TAttribute<bool>(this, &FPrimitiveComponentDetails::IsSimulatePhysicsEditable), NULL); } else if (PropName == TEXT("bUseAsyncScene")) { //we only enable bUseAsyncScene if the project uses an AsyncScene PhysicsCategory.AddProperty(ChildProperty).EditCondition(TAttribute<bool>(this, &FPrimitiveComponentDetails::IsUseAsyncEditable), NULL); } else if (PropName == TEXT("LockedAxisMode")) { LockedAxisProperty = ChildProperty; PhysicsCategory.AddProperty(ChildProperty); } else if (PropName == TEXT("CustomLockedAxis")) { //we only enable bUseAsyncScene if the project uses an AsyncScene PhysicsCategory.AddProperty(ChildProperty).Visibility(TAttribute<EVisibility>(this, &FPrimitiveComponentDetails::IsCustomLockedAxisSelected)); } else if (PropName == TEXT("bAutoWeld")) { PhysicsCategory.AddProperty(ChildProperty).Visibility(TAttribute<EVisibility>(this, &FPrimitiveComponentDetails::IsAutoWeldVisible)) .EditCondition(TAttribute<bool>(this, &FPrimitiveComponentDetails::IsAutoWeldEditable), NULL); } else if (PropName == TEXT("bOverrideMass")) { if (bDisplayMassOverride) { PhysicsCategory.AddProperty(ChildProperty); } } else if (PropName == TEXT("MassInKg")) { if (bDisplayMass) { PhysicsCategory.AddCustomRow(TEXT("Mass"), false) .IsEnabled(TAttribute<bool>(this, &FPrimitiveComponentDetails::IsBodyMassEnabled)) .NameContent() [ ChildProperty->CreatePropertyNameWidget() ] .ValueContent() [ SNew(SVerticalBox) + SVerticalBox::Slot() .AutoHeight() [ SNew(SEditableTextBox) .Text(this, &FPrimitiveComponentDetails::OnGetBodyMass) .IsReadOnly(this, &FPrimitiveComponentDetails::IsBodyMassReadOnly) .Font(IDetailLayoutBuilder::GetDetailFont()) .Visibility(this, &FPrimitiveComponentDetails::IsMassVisible, false) ] + SVerticalBox::Slot() .AutoHeight() [ SNew(SVerticalBox) .Visibility(this, &FPrimitiveComponentDetails::IsMassVisible, true) + SVerticalBox::Slot() .AutoHeight() [ ChildProperty->CreatePropertyValueWidget() ] ] ]; } } else { PhysicsCategory.AddProperty(ChildProperty); } } } } // Collision { IDetailCategoryBuilder& CollisionCategory = DetailBuilder.EditCategory("Collision"); // add all collision properties for (uint32 ChildIndex = 0; ChildIndex < NumChildren; ++ChildIndex) { TSharedPtr<IPropertyHandle> ChildProperty = BodyInstanceHandler->GetChildHandle(ChildIndex); FString Category = FObjectEditorUtils::GetCategory(ChildProperty->GetProperty()); if (Category == TEXT("Collision")) { CollisionCategory.AddProperty(ChildProperty); } } } } AddAdvancedSubCategory( DetailBuilder, "Rendering", "TextureStreaming" ); AddAdvancedSubCategory( DetailBuilder, "Rendering", "LOD"); }
void FWheeledVehicleMovementComponent4WDetails::CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) { DetailBuilder.GetObjectsBeingCustomized(SelectedObjects); //we only do fancy customization if we have one vehicle component selected if (SelectedObjects.Num() != 1) { return; } else if (UWheeledVehicleMovementComponent4W * VehicleComponent = Cast<UWheeledVehicleMovementComponent4W>(SelectedObjects[0].Get())) { SteeringCurveEditor = FSteeringCurveEditor(VehicleComponent); TorqueCurveEditor = FTorqueCurveEditor(VehicleComponent); } else { return; } //Torque curve { IDetailCategoryBuilder& MechanicalCategory = DetailBuilder.EditCategory("MechanicalSetup"); TSharedRef<IPropertyHandle> TorqueCurveHandle = DetailBuilder.GetProperty("EngineSetup.TorqueCurve"); MechanicalCategory.AddProperty(TorqueCurveHandle).CustomWidget() .NameContent() [ TorqueCurveHandle->CreatePropertyNameWidget() ] .ValueContent() .MinDesiredWidth(125.f * 3.f) [ SAssignNew(TorqueCurveWidget, SCurveEditor) .ViewMinInput(0.f) .ViewMaxInput(70000.f) .ViewMinOutput(0.f) .ViewMaxOutput(1.f) .TimelineLength(7000.f) .HideUI(false) .DesiredSize(FVector2D(512, 128)) ]; TorqueCurveWidget->SetCurveOwner(&TorqueCurveEditor); } //Steering curve { IDetailCategoryBuilder& SteeringCategory = DetailBuilder.EditCategory("SteeringSetup"); TSharedRef<IPropertyHandle> SteeringCurveHandle = DetailBuilder.GetProperty("SteeringCurve"); SteeringCategory.AddProperty(SteeringCurveHandle).CustomWidget() .NameContent() [ SteeringCurveHandle->CreatePropertyNameWidget() ] .ValueContent() .MinDesiredWidth(125.f * 3.f) [ SAssignNew(SteeringCurveWidget, SCurveEditor) .ViewMinInput(0.f) .ViewMaxInput(150.f) .ViewMinOutput(0.f) .ViewMaxOutput(1.f) .TimelineLength(150) .HideUI(false) .ZoomToFitVertical(false) .ZoomToFitHorizontal(false) .DesiredSize(FVector2D(512, 128)) ]; SteeringCurveWidget->SetCurveOwner(&SteeringCurveEditor); } }
BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION void FEnvQueryTestDetails::CustomizeDetails( IDetailLayoutBuilder& DetailLayout ) { AllowWriting = false; TArray<TWeakObjectPtr<UObject> > EditedObjects; DetailLayout.GetObjectsBeingCustomized(EditedObjects); for (int32 i = 0; i < EditedObjects.Num(); i++) { const UEnvQueryTest* EditedTest = Cast<const UEnvQueryTest>(EditedObjects[i].Get()); if (EditedTest) { MyTest = EditedTest; break; } } // Initialize all handles FilterTypeHandle = DetailLayout.GetProperty(GET_MEMBER_NAME_CHECKED(UEnvQueryTest, FilterType)); ScoreEquationHandle = DetailLayout.GetProperty(GET_MEMBER_NAME_CHECKED(UEnvQueryTest, ScoringEquation)); TestPurposeHandle = DetailLayout.GetProperty(GET_MEMBER_NAME_CHECKED(UEnvQueryTest, TestPurpose)); ScoreHandle = DetailLayout.GetProperty(GET_MEMBER_NAME_CHECKED(UEnvQueryTest, ScoringFactor)); ClampMinTypeHandle = DetailLayout.GetProperty(GET_MEMBER_NAME_CHECKED(UEnvQueryTest, ClampMinType)); ClampMaxTypeHandle = DetailLayout.GetProperty(GET_MEMBER_NAME_CHECKED(UEnvQueryTest, ClampMaxType)); ScoreClampMinHandle = DetailLayout.GetProperty(GET_MEMBER_NAME_CHECKED(UEnvQueryTest, ScoreClampMin)); FloatValueMinHandle = DetailLayout.GetProperty(GET_MEMBER_NAME_CHECKED(UEnvQueryTest, FloatValueMin)); ScoreClampMaxHandle = DetailLayout.GetProperty(GET_MEMBER_NAME_CHECKED(UEnvQueryTest, ScoreClampMax)); FloatValueMaxHandle = DetailLayout.GetProperty(GET_MEMBER_NAME_CHECKED(UEnvQueryTest, FloatValueMax)); // Build combo box data values BuildFilterTestValues(); BuildScoreEquationValues(); BuildScoreClampingTypeValues(true, ClampMinTypeValues); BuildScoreClampingTypeValues(false, ClampMaxTypeValues); IDetailCategoryBuilder& TestCategory = DetailLayout.EditCategory("Test"); IDetailPropertyRow& TestPurposeRow = TestCategory.AddProperty(TestPurposeHandle); IDetailCategoryBuilder& FilterCategory = DetailLayout.EditCategory("Filter"); IDetailPropertyRow& FilterTypeRow = FilterCategory.AddProperty(FilterTypeHandle); FilterTypeRow.CustomWidget() .NameContent() [ FilterTypeHandle->CreatePropertyNameWidget() ] .ValueContent() [ SNew(SComboButton) .OnGetMenuContent(this, &FEnvQueryTestDetails::OnGetFilterTestContent) .ContentPadding(FMargin( 2.0f, 2.0f )) .ButtonContent() [ SNew(STextBlock) .Text(this, &FEnvQueryTestDetails::GetCurrentFilterTestDesc) .Font(IDetailLayoutBuilder::GetDetailFont()) ] ]; FilterTypeRow.Visibility(TAttribute<EVisibility>::Create(TAttribute<EVisibility>::FGetter::CreateSP(this, &FEnvQueryTestDetails::GetFloatFilterVisibility))); // filters IDetailPropertyRow& FloatValueMinRow = FilterCategory.AddProperty(DetailLayout.GetProperty(GET_MEMBER_NAME_CHECKED(UEnvQueryTest, FloatValueMin))); FloatValueMinRow.Visibility(TAttribute<EVisibility>::Create(TAttribute<EVisibility>::FGetter::CreateSP(this, &FEnvQueryTestDetails::GetVisibilityOfFloatValueMin))); IDetailPropertyRow& FloatValueMaxRow = FilterCategory.AddProperty(DetailLayout.GetProperty(GET_MEMBER_NAME_CHECKED(UEnvQueryTest, FloatValueMax))); FloatValueMaxRow.Visibility(TAttribute<EVisibility>::Create(TAttribute<EVisibility>::FGetter::CreateSP(this, &FEnvQueryTestDetails::GetVisibilityOfFloatValueMax))); IDetailPropertyRow& BoolValueRow = FilterCategory.AddProperty(DetailLayout.GetProperty(GET_MEMBER_NAME_CHECKED(UEnvQueryTest, BoolValue))); BoolValueRow.Visibility(TAttribute<EVisibility>::Create(TAttribute<EVisibility>::FGetter::CreateSP(this, &FEnvQueryTestDetails::GetBoolValueVisibility))); // required when it's created for "scoring only" tests IDetailGroup& HackToEnsureFilterCategoryIsVisible = FilterCategory.AddGroup("HackForVisibility", FText::GetEmpty()); HackToEnsureFilterCategoryIsVisible.HeaderRow().Visibility(EVisibility::Hidden); // Scoring IDetailCategoryBuilder& ScoreCategory = DetailLayout.EditCategory("Score"); //---------------------------- // BEGIN Scoring: Clamping IDetailGroup& ClampingGroup = ScoreCategory.AddGroup("Clamping", LOCTEXT("ClampingLabel", "Clamping")); ClampingGroup.HeaderRow() [ SNew(STextBlock) .Visibility(TAttribute<EVisibility>::Create(TAttribute<EVisibility>::FGetter::CreateSP(this, &FEnvQueryTestDetails::GetClampingVisibility))) .Text(FText::FromString("Clamping")) .Font(IDetailLayoutBuilder::GetDetailFont()) ]; // Drop-downs for setting type of lower and upper bound normalization IDetailPropertyRow& ClampMinTypeRow = ClampingGroup.AddPropertyRow(ClampMinTypeHandle.ToSharedRef()); ClampMinTypeRow.CustomWidget() .NameContent() [ ClampMinTypeHandle->CreatePropertyNameWidget() ] .ValueContent() [ SNew(SComboButton) .OnGetMenuContent(this, &FEnvQueryTestDetails::OnGetClampMinTypeContent) .ContentPadding(FMargin( 2.0f, 2.0f )) .ButtonContent() [ SNew(STextBlock) .Text(this, &FEnvQueryTestDetails::GetClampMinTypeDesc) .Font(IDetailLayoutBuilder::GetDetailFont()) ] ]; ClampMinTypeRow.Visibility(TAttribute<EVisibility>::Create(TAttribute<EVisibility>::FGetter::CreateSP(this, &FEnvQueryTestDetails::GetFloatScoreVisibility))); // Lower Bound for normalization of score if specified independently of filtering. IDetailPropertyRow& ScoreClampMinRow = ClampingGroup.AddPropertyRow(ScoreClampMinHandle.ToSharedRef()); ScoreClampMinRow.Visibility(TAttribute<EVisibility>::Create(TAttribute<EVisibility>::FGetter::CreateSP(this, &FEnvQueryTestDetails::GetVisibilityOfScoreClampMinimum))); // Lower Bound for scoring when tied to filter minimum. if (FloatValueMinHandle->IsValidHandle()) { IDetailPropertyRow& FloatValueMinForClampingRow = ClampingGroup.AddPropertyRow(FloatValueMinHandle.ToSharedRef()); FloatValueMinForClampingRow.Visibility(TAttribute<EVisibility>::Create(TAttribute<EVisibility>::FGetter::CreateSP(this, &FEnvQueryTestDetails::GetVisibilityOfValueMinForScoreClamping))); FloatValueMinForClampingRow.ToolTip(LOCTEXT("FloatFilterMinForClampingRowToolTip", "See Filter Thresholds under the Filter tab. Values lower than this (before clamping) cause the item to be thrown out as invalid. Values are normalized with this value as the minimum, so items with this value will have a normalized score of 0.")); FloatValueMinForClampingRow.EditCondition(TAttribute<bool>(this, &FEnvQueryTestDetails::AllowWritingToFiltersFromScore), NULL); } if (ClampMaxTypeHandle->IsValidHandle()) { IDetailPropertyRow& ClampMaxTypeRow = ClampingGroup.AddPropertyRow(ClampMaxTypeHandle.ToSharedRef()); ClampMaxTypeRow.CustomWidget() .NameContent() [ ClampMaxTypeHandle->CreatePropertyNameWidget() ] .ValueContent() [ SNew(SComboButton) .OnGetMenuContent(this, &FEnvQueryTestDetails::OnGetClampMaxTypeContent) .ContentPadding(FMargin( 2.0f, 2.0f )) .ButtonContent() [ SNew(STextBlock) .Text(this, &FEnvQueryTestDetails::GetClampMaxTypeDesc) .Font(IDetailLayoutBuilder::GetDetailFont()) ] ]; ClampMaxTypeRow.Visibility(TAttribute<EVisibility>::Create(TAttribute<EVisibility>::FGetter::CreateSP(this, &FEnvQueryTestDetails::GetFloatScoreVisibility))); } // Upper Bound for normalization of score if specified independently of filtering. if (ScoreClampMaxHandle->IsValidHandle()) { IDetailPropertyRow& ScoreClampMaxRow = ClampingGroup.AddPropertyRow(ScoreClampMaxHandle.ToSharedRef()); ScoreClampMaxRow.Visibility(TAttribute<EVisibility>::Create(TAttribute<EVisibility>::FGetter::CreateSP(this, &FEnvQueryTestDetails::GetVisibilityOfScoreClampMaximum))); } if (FloatValueMaxHandle->IsValidHandle()) { // Upper Bound for scoring when tied to filter maximum. IDetailPropertyRow& FloatValueMaxForClampingRow = ClampingGroup.AddPropertyRow(FloatValueMaxHandle.ToSharedRef()); FloatValueMaxForClampingRow.Visibility(TAttribute<EVisibility>::Create(TAttribute<EVisibility>::FGetter::CreateSP(this, &FEnvQueryTestDetails::GetVisibilityOfValueMaxForScoreClamping))); FloatValueMaxForClampingRow.ToolTip(LOCTEXT("FloatFilterMaxForClampingRowToolTip", "See Filter Thresholds under the Filter tab. Values higher than this (before normalization) cause the item to be thrown out as invalid. Values are normalized with this value as the maximum, so items with this value will have a normalized score of 1.")); FloatValueMaxForClampingRow.EditCondition(TAttribute<bool>(this, &FEnvQueryTestDetails::AllowWritingToFiltersFromScore), NULL); } // END Scoring: Clamping, continue Scoring //---------------------------- IDetailPropertyRow& BoolScoreTestRow = ScoreCategory.AddProperty(DetailLayout.GetProperty(GET_MEMBER_NAME_CHECKED(UEnvQueryTest, BoolValue))); BoolScoreTestRow.Visibility(TAttribute<EVisibility>::Create(TAttribute<EVisibility>::FGetter::CreateSP(this, &FEnvQueryTestDetails::GetBoolValueVisibilityForScoring))); BoolScoreTestRow.DisplayName(LOCTEXT("BoolMatchLabel", "Bool Match")); BoolScoreTestRow.ToolTip(LOCTEXT("BoolMatchToolTip", "Boolean value to match in order to grant score of 'ScoringFactor'. Not matching this value will not change score.")); // IDetailPropertyRow& ScoreMirrorNormalizedScoreRow = ScoreCategory.AddProperty(DetailLayout.GetProperty(GET_MEMBER_NAME_CHECKED(UEnvQueryTest, bMirrorNormalizedScore))); // ScoreMirrorNormalizedScoreRow.Visibility(TAttribute<EVisibility>::Create(TAttribute<EVisibility>::FGetter::CreateSP(this, &FEnvQueryTestDetails::GetFloatScoreVisibility))); IDetailPropertyRow& ScoreEquationTypeRow = ScoreCategory.AddProperty(ScoreEquationHandle); ScoreEquationTypeRow.CustomWidget() .NameContent() .VAlign(VAlign_Top) [ ScoreEquationHandle->CreatePropertyNameWidget() ] .ValueContent().MaxDesiredWidth(600) [ SNew(SVerticalBox) +SVerticalBox::Slot() .AutoHeight() .HAlign(HAlign_Left) [ SNew(SComboButton) .OnGetMenuContent(this, &FEnvQueryTestDetails::OnGetEquationValuesContent) .ContentPadding(FMargin( 2.0f, 2.0f )) .ButtonContent() [ SNew(STextBlock) .Text(this, &FEnvQueryTestDetails::GetEquationValuesDesc) .Font(IDetailLayoutBuilder::GetDetailFont()) ] ] +SVerticalBox::Slot() .Padding(0, 2, 0, 0) .AutoHeight() [ SNew(STextBlock) .IsEnabled(false) .Text(this, &FEnvQueryTestDetails::GetScoreEquationInfo) .Font( IDetailLayoutBuilder::GetDetailFont() ) ] ]; ScoreEquationTypeRow.Visibility(TAttribute<EVisibility>::Create(TAttribute<EVisibility>::FGetter::CreateSP(this, &FEnvQueryTestDetails::GetFloatScoreVisibility))); IDetailPropertyRow& ScoreFactorRow = ScoreCategory.AddProperty(DetailLayout.GetProperty(GET_MEMBER_NAME_CHECKED(UEnvQueryTest, ScoringFactor))); ScoreFactorRow.Visibility(TAttribute<EVisibility>::Create(TAttribute<EVisibility>::FGetter::CreateSP(this, &FEnvQueryTestDetails::GetScoreVisibility))); // scoring & filter function preview IDetailCategoryBuilder& PreviewCategory = DetailLayout.EditCategory("Preview"); PreviewCategory.AddCustomRow(LOCTEXT("Preview", "Preview")).WholeRowWidget [ SAssignNew(PreviewWidget, STestFunctionWidget) ]; FSimpleDelegate OnGraphPreviewDataChangedDelegate = FSimpleDelegate::CreateSP(this, &FEnvQueryTestDetails::UpdateTestFunctionPreview); TestPurposeHandle->SetOnPropertyValueChanged(OnGraphPreviewDataChangedDelegate); FilterTypeHandle->SetOnPropertyValueChanged(OnGraphPreviewDataChangedDelegate); ClampMaxTypeHandle->SetOnPropertyValueChanged(OnGraphPreviewDataChangedDelegate); ClampMinTypeHandle->SetOnPropertyValueChanged(OnGraphPreviewDataChangedDelegate); ScoreEquationHandle->SetOnPropertyValueChanged(OnGraphPreviewDataChangedDelegate); ScoreHandle->SetOnPropertyValueChanged(OnGraphPreviewDataChangedDelegate); UpdateTestFunctionPreview(); }