void FDestructibleMeshDetails::CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) { //we always hide bodysetup as it's not useful in this editor TSharedPtr<IPropertyHandle> BodySetupHandler = DetailBuilder.GetProperty("BodySetup"); if (BodySetupHandler.IsValid()) { DetailBuilder.HideProperty(BodySetupHandler); } //rest of customization is just moving stuff out of DefaultDestructibleParameters so it's nicer to view TSharedPtr<IPropertyHandle> DefaultParams = DetailBuilder.GetProperty("DefaultDestructibleParameters"); if (DefaultParams.IsValid() == false) { return; } AddStructToDetails("Damage", "DefaultDestructibleParameters.DamageParameters", DetailBuilder); AddStructToDetails("Damage", "DefaultDestructibleParameters.AdvancedParameters", DetailBuilder, true, true); AddStructToDetails("Debris", "DefaultDestructibleParameters.DebrisParameters", DetailBuilder); AddStructToDetails("Flags", "DefaultDestructibleParameters.Flags", DetailBuilder); AddStructToDetails("HierarchyDepth", "DefaultDestructibleParameters.SpecialHierarchyDepths", DetailBuilder); AddStructToDetails("HierarchyDepth", "DefaultDestructibleParameters.DepthParameters", DetailBuilder, false, true); //hide the default params as we've taken everything out of it DetailBuilder.HideProperty(DefaultParams); }
void FBlackboardDataDetails::CustomizeDetails( IDetailLayoutBuilder& DetailLayout ) { // First hide all keys DetailLayout.HideProperty(TEXT("Keys")); DetailLayout.HideProperty(TEXT("ParentKeys")); // Now show only the currently selected key bool bIsInherited = false; int32 CurrentSelection = INDEX_NONE; if(OnGetSelectedBlackboardItemIndex.IsBound()) { CurrentSelection = OnGetSelectedBlackboardItemIndex.Execute(bIsInherited); } if(CurrentSelection >= 0) { TSharedPtr<IPropertyHandle> KeysHandle = bIsInherited ? DetailLayout.GetProperty(TEXT("ParentKeys")) : DetailLayout.GetProperty(TEXT("Keys")); check(KeysHandle.IsValid()); uint32 NumChildKeys = 0; KeysHandle->GetNumChildren(NumChildKeys); if((uint32)CurrentSelection < NumChildKeys) { TSharedPtr<IPropertyHandle> KeyHandle = KeysHandle->GetChildHandle((uint32)CurrentSelection); IDetailCategoryBuilder& DetailCategoryBuilder = DetailLayout.EditCategory("Key"); TSharedPtr<IPropertyHandle> EntryNameProperty = KeyHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FBlackboardEntry, EntryName)); DetailCategoryBuilder.AddCustomRow(LOCTEXT("EntryNameLabel", "Entry Name")) .NameContent() [ EntryNameProperty->CreatePropertyNameWidget() ] .ValueContent() [ SNew(SHorizontalBox) .IsEnabled(true) +SHorizontalBox::Slot() [ EntryNameProperty->CreatePropertyValueWidget() ] ]; #if WITH_EDITORONLY_DATA // TSharedPtr<IPropertyHandle> EntryDescriptionHandle = ElementProperty->GetChildHandle("EntryDescription"); TSharedPtr<IPropertyHandle> EntryDescriptionHandle = KeyHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FBlackboardEntry, EntryDescription)); DetailCategoryBuilder.AddProperty(EntryDescriptionHandle); #endif TSharedPtr<IPropertyHandle> KeyTypeProperty = KeyHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FBlackboardEntry, KeyType)); DetailCategoryBuilder.AddProperty(KeyTypeProperty); TSharedPtr<IPropertyHandle> bInstanceSyncedProperty = KeyHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FBlackboardEntry, bInstanceSynced)); DetailCategoryBuilder.AddProperty(bInstanceSyncedProperty); } } }
/** 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 FSpriteDetailsCustomization::BuildTextureSection(IDetailCategoryBuilder& SpriteCategory, IDetailLayoutBuilder& DetailLayout) { // Grab information about the material TSharedPtr<IPropertyHandle> DefaultMaterialProperty = DetailLayout.GetProperty(GET_MEMBER_NAME_CHECKED(UPaperSprite, DefaultMaterial)); FText SourceTextureOverrideLabel; if (DefaultMaterialProperty.IsValid()) { UObject* DefaultMaterialAsObject; if (DefaultMaterialProperty->GetValue(/*out*/ DefaultMaterialAsObject) == FPropertyAccess::Success) { if (UMaterialInterface* DefaultMaterialInterface = Cast<UMaterialInterface>(DefaultMaterialAsObject)) { if (UMaterial* DefaultMaterial = DefaultMaterialInterface->GetMaterial()) { // Get a list of sprite samplers TArray<const UMaterialExpressionSpriteTextureSampler*> SpriteSamplerExpressions; DefaultMaterial->GetAllExpressionsOfType(/*inout*/ SpriteSamplerExpressions); // Turn that into a set of labels for (const UMaterialExpressionSpriteTextureSampler* Sampler : SpriteSamplerExpressions) { if (!Sampler->SlotDisplayName.IsEmpty()) { if (Sampler->bSampleAdditionalTextures) { AdditionalTextureLabels.FindOrAdd(Sampler->AdditionalSlotIndex) = Sampler->SlotDisplayName; } else { SourceTextureOverrideLabel = Sampler->SlotDisplayName; } } } } } } } // Create the base texture widget TSharedPtr<IPropertyHandle> SourceTextureProperty = DetailLayout.GetProperty(GET_MEMBER_NAME_CHECKED(UPaperSprite, SourceTexture)); DetailLayout.HideProperty(SourceTextureProperty); SpriteCategory.AddCustomRow(SourceTextureProperty->GetPropertyDisplayName()) .NameContent() [ CreateTextureNameWidget(SourceTextureProperty, SourceTextureOverrideLabel) ] .ValueContent() .MaxDesiredWidth(TOptional<float>()) [ SourceTextureProperty->CreatePropertyValueWidget() ]; // Create the additional textures widget TSharedPtr<IPropertyHandle> AdditionalSourceTexturesProperty = DetailLayout.GetProperty(GET_MEMBER_NAME_CHECKED(UPaperSprite, AdditionalSourceTextures)); TSharedRef<FDetailArrayBuilder> AdditionalSourceTexturesBuilder = MakeShareable(new FDetailArrayBuilder(AdditionalSourceTexturesProperty.ToSharedRef())); AdditionalSourceTexturesBuilder->OnGenerateArrayElementWidget(FOnGenerateArrayElementWidget::CreateSP(this, &FSpriteDetailsCustomization::GenerateAdditionalTextureWidget)); SpriteCategory.AddCustomBuilder(AdditionalSourceTexturesBuilder); }
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 FConfigPropertyHelperDetails::CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) { TSharedPtr<IPropertyHandle> PropertyHandle = DetailBuilder.GetProperty("EditProperty"); DetailBuilder.HideProperty(PropertyHandle); UObject* PropValue; PropertyHandle->GetValue(PropValue); OriginalProperty = CastChecked<UProperty>(PropValue); // Create a runtime UClass with the provided property as the only member. We will use this in the details view for the config hierarchy. ConfigEditorPropertyViewClass = NewObject<UClass>(GetTransientPackage(), TEXT("TempConfigEditorUClass"), RF_Public|RF_Standalone); // Keep a record of the UProperty we are looking to update ConfigEditorCopyOfEditProperty = DuplicateObject<UProperty>(OriginalProperty, ConfigEditorPropertyViewClass, PropValue->GetFName()); ConfigEditorPropertyViewClass->ClassConfigName = OriginalProperty->GetOwnerClass()->ClassConfigName; ConfigEditorPropertyViewClass->SetSuperStruct(UObject::StaticClass()); ConfigEditorPropertyViewClass->ClassFlags |= (CLASS_DefaultConfig | CLASS_Config); ConfigEditorPropertyViewClass->AddCppProperty(ConfigEditorCopyOfEditProperty); ConfigEditorPropertyViewClass->Bind(); ConfigEditorPropertyViewClass->StaticLink(true); ConfigEditorPropertyViewClass->AssembleReferenceTokenStream(); ConfigEditorPropertyViewClass->AddToRoot(); // Cache the CDO for the object ConfigEditorPropertyViewCDO = ConfigEditorPropertyViewClass->GetDefaultObject(true); ConfigEditorPropertyViewCDO->AddToRoot(); // Get access to all of the config files where this property is configurable. ConfigFilesHandle = DetailBuilder.GetProperty("ConfigFilePropertyObjects"); DetailBuilder.HideProperty(ConfigFilesHandle); // Add the properties to a property table so we can edit these. IDetailCategoryBuilder& ConfigHierarchyCategory = DetailBuilder.EditCategory("ConfigHierarchy"); ConfigHierarchyCategory.AddCustomRow(LOCTEXT("ConfigHierarchy", "ConfigHierarchy")) [ // Create a property table with the values. ConstructPropertyTable(DetailBuilder) ]; // Listen for changes to the properties, we handle these by updating the ini file associated. FCoreUObjectDelegates::OnObjectPropertyChanged.AddSP(this, &FConfigPropertyHelperDetails::OnPropertyValueChanged); }
void FDeviceProfileDetails::CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) { // Hide all the properties apart from the Console Variables. IDetailCategoryBuilder& Category = DetailBuilder.EditCategory("DeviceSettings"); TSharedPtr<IPropertyHandle> DeviceTypeHandle = DetailBuilder.GetProperty("DeviceType"); DetailBuilder.HideProperty(DeviceTypeHandle); TSharedPtr<IPropertyHandle> MeshLODSettingsHandle = DetailBuilder.GetProperty("MeshLODSettings"); DetailBuilder.HideProperty(MeshLODSettingsHandle); TSharedPtr<IPropertyHandle> TextureLODSettingsHandle = DetailBuilder.GetProperty("TextureLODSettings"); DetailBuilder.HideProperty(TextureLODSettingsHandle); // Setup the parent profile panel ParentProfileDetails = MakeShareable(new FDeviceProfileParentPropertyDetails(&DetailBuilder)); ParentProfileDetails->CreateParentPropertyView(); // Setup the console variable editor ConsoleVariablesDetails = MakeShareable(new FDeviceProfileConsoleVariablesPropertyDetails(&DetailBuilder)); ConsoleVariablesDetails->CreateConsoleVariablesPropertyView(); }
void FMacGraphicsSwitchingSettingsDetails::CustomizeDetails( IDetailLayoutBuilder& DetailLayout ) { TSharedRef<IPropertyHandle> PreferredRendererPropertyHandle = DetailLayout.GetProperty("RendererID"); DetailLayout.HideProperty("RendererID"); bool bAllowMultiGPUs = IMacGraphicsSwitchingModule::Get().AllowMultipleGPUs(); bool bAllowAutomaticGraphicsSwitching = IMacGraphicsSwitchingModule::Get().AllowAutomaticGraphicsSwitching(); TSharedRef<IPropertyHandle> MultiGPUPropertyHandle = DetailLayout.GetProperty("bUseMultipleRenderers"); if (!bAllowMultiGPUs) { MultiGPUPropertyHandle->SetValue(false); DetailLayout.HideProperty("bUseMultipleRenderers"); } TSharedRef<IPropertyHandle> SwitchingPropertyHandle = DetailLayout.GetProperty("bAllowAutomaticGraphicsSwitching"); if (!bAllowAutomaticGraphicsSwitching) { SwitchingPropertyHandle->SetValue(false); DetailLayout.HideProperty("bAllowAutomaticGraphicsSwitching"); } IDetailCategoryBuilder& AccessorCategory = DetailLayout.EditCategory( "OpenGL" ); AccessorCategory.AddCustomRow( LOCTEXT("PreferredRenderer", "Preferred Renderer").ToString() ) .NameContent() [ PreferredRendererPropertyHandle->CreatePropertyNameWidget() ] .ValueContent() .MinDesiredWidth(113) .MaxDesiredWidth(113) [ SNew(SMacGraphicsSwitchingWidget) .bLiveSwitching(false) .PreferredRendererPropertyHandle(PreferredRendererPropertyHandle) ]; }
void FSourceCodeAccessSettingsDetails::CustomizeDetails( IDetailLayoutBuilder& DetailLayout ) { TSharedRef<IPropertyHandle> PreferredProviderPropertyHandle = DetailLayout.GetProperty("PreferredAccessor"); DetailLayout.HideProperty("PreferredAccessor"); // regenerate accessors list Accessors.Empty(); const int32 FeatureCount = IModularFeatures::Get().GetModularFeatureImplementationCount("SourceCodeAccessor"); for(int32 FeatureIndex = 0; FeatureIndex < FeatureCount; FeatureIndex++) { IModularFeature* Feature = IModularFeatures::Get().GetModularFeatureImplementation("SourceCodeAccessor", FeatureIndex); check(Feature); ISourceCodeAccessor& Accessor = *static_cast<ISourceCodeAccessor*>(Feature); if(Accessor.GetFName() != FName("None")) { Accessors.Add(MakeShareable(new FAccessorItem(Accessor.GetNameText(), Accessor.GetFName()))); } } IDetailCategoryBuilder& AccessorCategory = DetailLayout.EditCategory( "Accessor" ); AccessorCategory.AddCustomRow( LOCTEXT("PreferredAccessor", "Preferred Accessor").ToString() ) .NameContent() [ PreferredProviderPropertyHandle->CreatePropertyNameWidget() ] .ValueContent() .MinDesiredWidth(113) .MaxDesiredWidth(113) [ SNew(SComboBox< TSharedPtr<FAccessorItem>>) .ToolTipText(LOCTEXT("PreferredAccessorToolTip", "Choose the way to access source code.")) .OptionsSource(&Accessors) .OnSelectionChanged(this, &FSourceCodeAccessSettingsDetails::OnSelectionChanged, PreferredProviderPropertyHandle) .ContentPadding(2) .OnGenerateWidget(this, &FSourceCodeAccessSettingsDetails::OnGenerateWidget) .Content() [ SNew(STextBlock) .Text(this, &FSourceCodeAccessSettingsDetails::GetAccessorText) .Font( IDetailLayoutBuilder::GetDetailFont() ) ] ]; }
void FTransitionPoseEvaluatorNodeDetails::CustomizeDetails( IDetailLayoutBuilder& DetailBuilder ) { const TArray< TWeakObjectPtr<UObject> >& SelectedObjects = DetailBuilder.GetDetailsView().GetSelectedObjects(); for (int32 ObjectIndex = 0; (EvaluatorNode == NULL) && (ObjectIndex < SelectedObjects.Num()); ++ObjectIndex) { const TWeakObjectPtr<UObject>& CurrentObject = SelectedObjects[ObjectIndex]; if (CurrentObject.IsValid()) { EvaluatorNode = Cast<UAnimGraphNode_TransitionPoseEvaluator>(CurrentObject.Get()); } } IDetailCategoryBuilder& PoseCategory = DetailBuilder.EditCategory("Pose", LOCTEXT("PoseCategoryName", "Pose") ); TSharedPtr<IPropertyHandle> FramesToCachePosePropety = DetailBuilder.GetProperty(TEXT("Node.FramesToCachePose")); //@TODO: CONDUIT: try both DetailBuilder.HideProperty(FramesToCachePosePropety); PoseCategory.AddProperty( FramesToCachePosePropety ).Visibility( TAttribute<EVisibility>( this, &FTransitionPoseEvaluatorNodeDetails::GetFramesToCachePoseVisibility ) ); }
void FTODAssetDetails::CustomizeDetails(IDetailLayoutBuilder& DetailLayout) { const IDetailsView& DetailView = DetailLayout.GetDetailsView(); TWeakObjectPtr<UObject> InspectedObject; for (TWeakObjectPtr<UObject> inspObj : DetailView.GetSelectedObjects()) { InspectedObject = inspObj; break; } UTODAsset* TODAsset = Cast<UTODAsset>(InspectedObject.Get()); if (TODAsset) { for (TFieldIterator<UProperty> PropIt(TODAsset->GetClass()); PropIt; ++PropIt) { UProperty* prop = *PropIt; DetailLayout.HideProperty(prop->GetFName()); } } FName CurrentPropertyName = TEXT("SunIntensityCurve");// NAME_None; //if (OnGetCurrentProperty.IsBound()) //{ // CurrentPropertyName = OnGetCurrentProperty.Execute(); //} if (CurrentPropertyName != NAME_None) { TSharedPtr<IPropertyHandle> PropHandle = DetailLayout.GetProperty(CurrentPropertyName); check(PropHandle.IsValid()); IDetailCategoryBuilder& DetailCategoryBuilder = DetailLayout.EditCategory("Property Detail"); DetailCategoryBuilder.AddProperty(PropHandle); } }
void FTileSetDetailsCustomization::CustomizeDetails(IDetailLayoutBuilder& DetailLayout) { MyDetailLayout = &DetailLayout; for (const TWeakObjectPtr<UObject> SelectedObject : DetailLayout.GetSelectedObjects()) { if (UPaperTileSet* TileSet = Cast<UPaperTileSet>(SelectedObject.Get())) { TileSetPtr = TileSet; break; } } IDetailCategoryBuilder& TileSetCategory = DetailLayout.EditCategory("TileSet", FText::GetEmpty()); // Add the width and height in cells of this tile set to the header TileSetCategory.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, &FTileSetDetailsCustomization::GetCellDimensionHeaderText) .ColorAndOpacity(this, &FTileSetDetailsCustomization::GetCellDimensionHeaderColor) .ToolTipText(LOCTEXT("NumCellsTooltip", "Number of tile cells in this tile set")) ] ] ); if (bIsEmbeddedInTileSetEditor) { // Hide the array to start with const FName MetadataArrayName = UPaperTileSet::GetPerTilePropertyName(); TSharedPtr<IPropertyHandle> PerTileArrayProperty = DetailLayout.GetProperty(MetadataArrayName); DetailLayout.HideProperty(PerTileArrayProperty); // this array is potentially huge and has a costly validation overhead. We only ever show one element in the array so there is no need to validate every element. PerTileArrayProperty->SetIgnoreValidation(true); if (SelectedSingleTileIndex != INDEX_NONE) { // Customize for the single tile being edited IDetailCategoryBuilder& SingleTileCategory = DetailLayout.EditCategory("SingleTileEditor", FText::GetEmpty()); uint32 NumChildren; if ((PerTileArrayProperty->GetNumChildren(/*out*/ NumChildren) == FPropertyAccess::Success) && ((uint32)SelectedSingleTileIndex < NumChildren)) { TSharedPtr<IPropertyHandle> OneTileEntry = PerTileArrayProperty->GetChildHandle(SelectedSingleTileIndex); SingleTileCategory.AddProperty(OneTileEntry) .ShouldAutoExpand(true); } // Add a display of the tile index being edited to the header const FText TileIndexHeaderText = FText::Format(LOCTEXT("SingleTileSectionHeader", "Editing Tile #{0}"), FText::AsNumber(SelectedSingleTileIndex)); SingleTileCategory.HeaderContent ( SNew(SBox) .HAlign(HAlign_Right) [ SNew(SHorizontalBox) +SHorizontalBox::Slot() .Padding(FMargin(5.0f, 0.0f)) .AutoWidth() [ SNew(STextBlock) .Font(FEditorStyle::GetFontStyle("TinyText")) .Text(TileIndexHeaderText) ] ] ); } } }
BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION void FLandscapeEditorDetailCustomization_CopyPaste::CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) { if (!IsToolActive("ToolSet_CopyPaste")) { return; } IDetailCategoryBuilder& ToolsCategory = DetailBuilder.EditCategory("Tool Settings"); ToolsCategory.AddCustomRow("Copy Data to Gizmo") [ SNew(SButton) .ToolTipText(LOCTEXT("CopyToGizmo.Tooltip", "Copies the data within the gizmo bounds to the gizmo taking into account any masking from selected regions.")) .Text(LOCTEXT("CopyToGizmo", "Copy Data to Gizmo")) .HAlign(HAlign_Center) .OnClicked_Static(&FLandscapeEditorDetailCustomization_CopyPaste::OnCopyToGizmoButtonClicked) ]; ToolsCategory.AddCustomRow("Fit Gizmo to Selected Regions") [ SNew(SButton) .ToolTipText(LOCTEXT("FitGizmoToSelection.Tooltip", "Positions and resizes the gizmo so that it completely encompasses all region selections.")) .Text(LOCTEXT("FitGizmoToSelection", "Fit Gizmo to Selected Regions")) .HAlign(HAlign_Center) .OnClicked_Static(&FLandscapeEditorDetailCustomization_CopyPaste::OnFitGizmoToSelectionButtonClicked) ]; ToolsCategory.AddCustomRow("Fit Height Values to Gizmo Size") [ SNew(SButton) .ToolTipText(LOCTEXT("FitHeightsToGizmo.Tooltip", "Scales the data in the gizmo to fit the gizmo's Z size")) .Text(LOCTEXT("FitHeightsToGizmo", "Fit Height Values to Gizmo Size")) .HAlign(HAlign_Center) .OnClicked_Static(&FLandscapeEditorDetailCustomization_CopyPaste::OnFitHeightsToGizmoButtonClicked) ]; ToolsCategory.AddCustomRow("Clear Gizmo Data") [ SNew(SButton) .ToolTipText(LOCTEXT("ClearGizmoData.Tooltip", "Clears the gizmo of any copied data.")) .Text(LOCTEXT("ClearGizmoData", "Clear Gizmo Data")) .HAlign(HAlign_Center) .OnClicked_Static(&FLandscapeEditorDetailCustomization_CopyPaste::OnClearGizmoDataButtonClicked) ]; IDetailGroup& GizmoImportExportGroup = ToolsCategory.AddGroup("Gizmo Import / Export", LOCTEXT("ImportExportTitle", "Gizmo Import / Export").ToString(), true); TSharedRef<IPropertyHandle> PropertyHandle_Heightmap = DetailBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(ULandscapeEditorObject, GizmoHeightmapFilenameString)); DetailBuilder.HideProperty(PropertyHandle_Heightmap); GizmoImportExportGroup.AddPropertyRow(PropertyHandle_Heightmap) .CustomWidget() .NameContent() [ PropertyHandle_Heightmap->CreatePropertyNameWidget() ] .ValueContent() .MinDesiredWidth(250.0f) .MaxDesiredWidth(0) [ SNew(SHorizontalBox) + SHorizontalBox::Slot() [ PropertyHandle_Heightmap->CreatePropertyValueWidget() ] + SHorizontalBox::Slot() .AutoWidth() //.Padding(0,0,12,0) // Line up with the other properties due to having no reset to default button [ SNew(SButton) .ContentPadding(FMargin(4, 0)) .Text(NSLOCTEXT("UnrealEd", "GenericOpenDialog", "...")) .OnClicked_Static(&FLandscapeEditorDetailCustomization_CopyPaste::OnGizmoHeightmapFilenameButtonClicked, PropertyHandle_Heightmap) ] ]; TSharedRef<IPropertyHandle> PropertyHandle_ImportSize = DetailBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(ULandscapeEditorObject, GizmoImportSize)); TSharedRef<IPropertyHandle> PropertyHandle_ImportSize_X = PropertyHandle_ImportSize->GetChildHandle("X").ToSharedRef(); TSharedRef<IPropertyHandle> PropertyHandle_ImportSize_Y = PropertyHandle_ImportSize->GetChildHandle("Y").ToSharedRef(); DetailBuilder.HideProperty(PropertyHandle_ImportSize); GizmoImportExportGroup.AddPropertyRow(PropertyHandle_ImportSize) .CustomWidget() .NameContent() [ PropertyHandle_ImportSize->CreatePropertyNameWidget() ] .ValueContent() [ SNew(SHorizontalBox) + SHorizontalBox::Slot() .FillWidth(1) [ SNew(SNumericEntryBox<int32>) .LabelVAlign(VAlign_Center) .Font(DetailBuilder.GetDetailFont()) .MinValue(1) .MaxValue(8192) .MinSliderValue(1) .MaxSliderValue(8192) .AllowSpin(true) .UndeterminedString(NSLOCTEXT("PropertyEditor", "MultipleValues", "Multiple Values")) .Value_Static(&FLandscapeEditorDetailCustomization_Base::OnGetValue, PropertyHandle_ImportSize_X) .OnValueChanged_Static(&FLandscapeEditorDetailCustomization_Base::OnValueChanged, PropertyHandle_ImportSize_X) .OnValueCommitted_Static(&FLandscapeEditorDetailCustomization_Base::OnValueCommitted, PropertyHandle_ImportSize_X) ] + SHorizontalBox::Slot() .AutoWidth() .Padding(2, 0) .VAlign(VAlign_Center) [ SNew(STextBlock) .Font(DetailBuilder.GetDetailFont()) .Text(FString().AppendChar(0xD7)) // Multiply sign ] + SHorizontalBox::Slot() .FillWidth(1) [ SNew(SNumericEntryBox<int32>) .LabelVAlign(VAlign_Center) .Font(DetailBuilder.GetDetailFont()) .MinValue(1) .MaxValue(8192) .MinSliderValue(1) .MaxSliderValue(8192) .AllowSpin(true) .UndeterminedString(NSLOCTEXT("PropertyEditor", "MultipleValues", "Multiple Values")) .Value_Static(&FLandscapeEditorDetailCustomization_Base::OnGetValue, PropertyHandle_ImportSize_Y) .OnValueChanged_Static(&FLandscapeEditorDetailCustomization_Base::OnValueChanged, PropertyHandle_ImportSize_Y) .OnValueCommitted_Static(&FLandscapeEditorDetailCustomization_Base::OnValueCommitted, PropertyHandle_ImportSize_Y) ] ]; TSharedRef<IPropertyHandle> PropertyHandle_ImportLayers = DetailBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(ULandscapeEditorObject, GizmoImportLayers)); DetailBuilder.HideProperty(PropertyHandle_ImportLayers); GizmoImportExportGroup.AddPropertyRow(PropertyHandle_ImportLayers); GizmoImportExportGroup.AddWidgetRow() .FilterString("") [ SNew(SHorizontalBox) + SHorizontalBox::Slot() .AutoWidth() [ SNew(SButton) .Text(LOCTEXT("GizmoImport", "Import")) .IsEnabled(this, &FLandscapeEditorDetailCustomization_CopyPaste::GetGizmoImportButtonIsEnabled) .OnClicked(this, &FLandscapeEditorDetailCustomization_CopyPaste::OnGizmoImportButtonClicked) ] + SHorizontalBox::Slot() .AutoWidth() [ SNew(SButton) .Text(LOCTEXT("GizmoExport", "Export")) .OnClicked(this, &FLandscapeEditorDetailCustomization_CopyPaste::OnGizmoExportButtonClicked) ] ]; //GuessGizmoImportSize(); }
void FActorDetails::CustomizeDetails( IDetailLayoutBuilder& DetailLayout ) { // These details only apply when adding an instance of the actor in a level if( !DetailLayout.GetDetailsView().HasClassDefaultObject() && DetailLayout.GetDetailsView().GetSelectedActorInfo().NumSelected > 0 ) { // Build up a list of unique blueprints in the selection set (recording the first actor in the set for each one) TMap<UBlueprint*, UObject*> UniqueBlueprints; // Per level Actor Counts TMap<ULevel*, int32> ActorsPerLevelCount; bool bHasBillboardComponent = false; const TArray< TWeakObjectPtr<UObject> >& SelectedObjects = DetailLayout.GetDetailsView().GetSelectedObjects(); for (int32 ObjectIndex = 0; ObjectIndex < SelectedObjects.Num(); ++ObjectIndex) { AActor* Actor = Cast<AActor>( SelectedObjects[ObjectIndex].Get() ); if (Actor != NULL) { // Store the selected actors for use later. Its fine to do this when CustomizeDetails is called because if the selected actors changes, CustomizeDetails will be called again on a new instance // and our current resource would be destroyed. SelectedActors.Add( Actor ); // Record the level that contains this actor and increment it's actor count ULevel* Level = Actor->GetTypedOuter<ULevel>(); if (Level != NULL) { int32& ActorCountForThisLevel = ActorsPerLevelCount.FindOrAdd(Level); ++ActorCountForThisLevel; } // Add to the unique blueprint map if the actor is generated from a blueprint if (UBlueprint* Blueprint = Cast<UBlueprint>(Actor->GetClass()->ClassGeneratedBy)) { if (!UniqueBlueprints.Find(Blueprint)) { UniqueBlueprints.Add(Blueprint, Actor); } } if (!bHasBillboardComponent) { bHasBillboardComponent = Actor->FindComponentByClass<UBillboardComponent>() != NULL; } } } if (!bHasBillboardComponent) { // Actor billboard scale is not relevant if the actor doesn't have a billboard component DetailLayout.HideProperty( GET_MEMBER_NAME_CHECKED(AActor, SpriteScale) ); } AddTransformCategory( DetailLayout ); AddMaterialCategory( DetailLayout ); AddActorCategory( DetailLayout, ActorsPerLevelCount ); // Get the list of hidden categories TArray<FString> HideCategories; DetailLayout.GetDetailsView().GetBaseClass()->GetHideCategories(HideCategories); // Add Blueprint category, if not being hidden if (!HideCategories.Contains(TEXT("Blueprint"))) { AddBlueprintCategory(DetailLayout, UniqueBlueprints); } if( GetDefault<UEditorExperimentalSettings>()->bCodeView ) { AddCodeViewCategory( DetailLayout ); } if (!HideCategories.Contains(TEXT("Layers"))) { AddLayersCategory(DetailLayout); } //AddComponentsCategory( DetailLayout ); } }
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 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 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 FTODAssetPropertyDetails::CustomizeDetails(IDetailLayoutBuilder& DetailLayout) { const IDetailsView& DetailView = DetailLayout.GetDetailsView(); //first find asset we are going to edit. TWeakObjectPtr<UObject> InspectedObject; for (TWeakObjectPtr<UObject> inspObj : DetailView.GetSelectedObjects()) { InspectedObject = inspObj; break; } UTODAsset* TODAsset = Cast<UTODAsset>(InspectedObject.Get()); CurrentTODAsset = Cast<UTODAsset>(InspectedObject.Get()); if (TODAsset) { for (TFieldIterator<UProperty> PropIt(TODAsset->GetClass()); PropIt; ++PropIt) { UProperty* prop = *PropIt; DetailLayout.HideProperty(prop->GetFName()); //PropertyHandles.Add(DetailLayout.GetProperty(prop->GetFName())); UStructProperty* structProp = Cast<UStructProperty>(prop); if (structProp) { FRuntimeFloatCurve* floatCurve = structProp->ContainerPtrToValuePtr<FRuntimeFloatCurve>(TODAsset); if (floatCurve) { TSharedPtr<FTODFloatCurveProperty> tempFloatProp = MakeShareable(new FTODFloatCurveProperty()); tempFloatProp->PropertyHandle = DetailLayout.GetProperty(prop->GetFName()); tempFloatProp->TODAsset = TODAsset; tempFloatProp->CategoryName = tempFloatProp->PropertyHandle->GetMetaData(TEXT("Category")); FloatCurves.Add(tempFloatProp); } } } } IDetailCategoryBuilder& DetailCategoryBuilder = DetailLayout.EditCategory("Property Detail"); FDetailWidgetRow& DetailRow = DetailCategoryBuilder.AddCustomRow(FString("Custom Row")); ////now customize each property //FRuntimeFloatCurve* floatCurve; TSharedPtr<IPropertyHandle> hour = DetailLayout.GetProperty(TEXT("Hour")); DetailCategoryBuilder.AddProperty(hour); IDetailCategoryBuilder& SunCategoryBuilder = DetailLayout.EditCategory("Sun"); IDetailCategoryBuilder& AFCategoryBuilder = DetailLayout.EditCategory("Atmospheric Fog"); IDetailCategoryBuilder& HFCategoryBuilder = DetailLayout.EditCategory("Height Fog"); IDetailCategoryBuilder& PPCategoryBuilder = DetailLayout.EditCategory("Post Process"); IDetailCategoryBuilder& SkyLightCategoryBuilder = DetailLayout.EditCategory("SkyLight"); IDetailCategoryBuilder& MoonCategoryBuilder = DetailLayout.EditCategory("Moon"); for (TSharedPtr<FTODFloatCurveProperty> floatCurves : FloatCurves) { if (floatCurves->CategoryName == FString("Sun")) floatCurves->ConstructWidget(SunCategoryBuilder); if (floatCurves->CategoryName == FString("Atmospheric Fog")) floatCurves->ConstructWidget(AFCategoryBuilder); if (floatCurves->CategoryName == FString("Height Fog")) floatCurves->ConstructWidget(HFCategoryBuilder); if (floatCurves->CategoryName == FString("Post Process")) floatCurves->ConstructWidget(PPCategoryBuilder); if (floatCurves->CategoryName == FString("SkyLight")) floatCurves->ConstructWidget(SkyLightCategoryBuilder); if (floatCurves->CategoryName == FString("Moon")) floatCurves->ConstructWidget(MoonCategoryBuilder); } }
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 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); } } } } }
BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION void FBrushDetails::CustomizeDetails( IDetailLayoutBuilder& DetailLayout ) { // Get level editor commands for our menus FLevelEditorModule& LevelEditor = FModuleManager::GetModuleChecked<FLevelEditorModule>( TEXT("LevelEditor") ); TSharedRef<const FUICommandList> CommandBindings = LevelEditor.GetGlobalLevelEditorActions(); const FLevelEditorCommands& Commands = LevelEditor.GetLevelEditorCommands(); // See if we have a volume. If we do - we hide the BSP stuff (solidity, order) bool bHaveAVolume = false; TArray< TWeakObjectPtr<UObject> > SelectedObjects = DetailLayout.GetDetailsView().GetSelectedObjects(); for (int32 ObjIdx = 0; ObjIdx < SelectedObjects.Num(); ObjIdx++) { if (ABrush* Brush = Cast<ABrush>(SelectedObjects[ObjIdx].Get())) { if (AVolume* Volume = Cast<AVolume>(Brush)) { bHaveAVolume = true; } if (!FActorEditorUtils::IsABuilderBrush(Brush)) { // Store the selected actors for use later. Its fine to do this when CustomizeDetails is called because if the selected actors changes, CustomizeDetails will be called again on a new instance // and our current resource would be destroyed. SelectedBrushes.Add(Brush); } } } FMenuBuilder PolygonsMenuBuilder( true, CommandBindings ); { PolygonsMenuBuilder.BeginSection("BrushDetailsPolygons"); { PolygonsMenuBuilder.AddMenuEntry( Commands.MergePolys ); PolygonsMenuBuilder.AddMenuEntry( Commands.SeparatePolys ); } PolygonsMenuBuilder.EndSection(); } FMenuBuilder SolidityMenuBuilder( true, CommandBindings ); { SolidityMenuBuilder.AddMenuEntry( Commands.MakeSolid ); SolidityMenuBuilder.AddMenuEntry( Commands.MakeSemiSolid ); SolidityMenuBuilder.AddMenuEntry( Commands.MakeNonSolid ); } FMenuBuilder OrderMenuBuilder( true, CommandBindings ); { OrderMenuBuilder.AddMenuEntry( Commands.OrderFirst ); OrderMenuBuilder.AddMenuEntry( Commands.OrderLast ); } struct Local { static FReply ExecuteExecCommand(FString InCommand) { GUnrealEd->Exec( GWorld, *InCommand ); return FReply::Handled(); } static TSharedRef<SWidget> GenerateBuildMenuContent(TSharedRef<IPropertyHandle> BrushBuilderHandle, IDetailLayoutBuilder* InDetailLayout) { class FBrushFilter : public IClassViewerFilter { public: virtual bool IsClassAllowed(const FClassViewerInitializationOptions& InInitOptions, const UClass* InClass, TSharedRef< class FClassViewerFilterFuncs > InFilterFuncs ) { return !InClass->HasAnyClassFlags(CLASS_NotPlaceable) && !InClass->HasAnyClassFlags(CLASS_Abstract) && InClass->IsChildOf(UBrushBuilder::StaticClass()); } virtual bool IsUnloadedClassAllowed(const FClassViewerInitializationOptions& InInitOptions, const TSharedRef< const class IUnloadedBlueprintData > InUnloadedClassData, TSharedRef< class FClassViewerFilterFuncs > InFilterFuncs) { return false; } }; FClassViewerInitializationOptions Options; Options.ClassFilter = MakeShareable(new FBrushFilter); Options.Mode = EClassViewerMode::ClassPicker; Options.DisplayMode = EClassViewerDisplayMode::ListView; return FModuleManager::LoadModuleChecked<FClassViewerModule>("ClassViewer").CreateClassViewer(Options, FOnClassPicked::CreateStatic(&Local::OnClassPicked, BrushBuilderHandle, InDetailLayout)); } static void OnClassPicked(UClass* InChosenClass, TSharedRef<IPropertyHandle> BrushBuilderHandle, IDetailLayoutBuilder* InDetailLayout) { FSlateApplication::Get().DismissAllMenus(); TArray<UObject*> OuterObjects; BrushBuilderHandle->GetOuterObjects(OuterObjects); struct FNewBrushBuilder { UBrushBuilder* Builder; ABrush* Brush; }; TArray<FNewBrushBuilder> NewBuilders; TArray<FString> NewObjectPaths; { const FScopedTransaction Transaction(NSLOCTEXT("UnrealEd", "BrushSet", "Brush Set")); for (UObject* OuterObject : OuterObjects) { UBrushBuilder* NewUObject = NewObject<UBrushBuilder>(OuterObject, InChosenClass, NAME_None, RF_Transactional); FNewBrushBuilder NewBuilder; NewBuilder.Builder = NewUObject; NewBuilder.Brush = CastChecked<ABrush>(OuterObject); NewBuilders.Add(NewBuilder); NewObjectPaths.Add(NewUObject->GetPathName()); } BrushBuilderHandle->SetPerObjectValues(NewObjectPaths); // make sure the brushes are rebuilt for (FNewBrushBuilder& NewObject : NewBuilders) { NewObject.Builder->Build(NewObject.Brush->GetWorld(), NewObject.Brush); } GEditor->RebuildAlteredBSP(); } InDetailLayout->ForceRefreshDetails(); } static FText GetBuilderText(TSharedRef<IPropertyHandle> BrushBuilderHandle) { UObject* Object = nullptr; BrushBuilderHandle->GetValue(Object); if(Object != nullptr) { UBrushBuilder* BrushBuilder = CastChecked<UBrushBuilder>(Object); const FText NameText = BrushBuilder->GetClass()->GetDisplayNameText(); if(!NameText.IsEmpty()) { return NameText; } else { return FText::FromString(FName::NameToDisplayString(BrushBuilder->GetClass()->GetName(), false)); } } return LOCTEXT("None", "None"); } }; // Hide the brush builder if it is NULL TSharedRef<IPropertyHandle> BrushBuilderPropertyHandle = DetailLayout.GetProperty(GET_MEMBER_NAME_CHECKED(ABrush, BrushBuilder)); UObject* BrushBuilderObject = nullptr; BrushBuilderPropertyHandle->GetValue(BrushBuilderObject); if(BrushBuilderObject == nullptr) { DetailLayout.HideProperty("BrushBuilder"); } else { BrushBuilderObject->SetFlags( RF_Transactional ); } IDetailCategoryBuilder& BrushBuilderCategory = DetailLayout.EditCategory( "BrushSettings", FText::GetEmpty(), ECategoryPriority::Important ); BrushBuilderCategory.AddProperty( GET_MEMBER_NAME_CHECKED(ABrush, BrushType) ); BrushBuilderCategory.AddCustomRow( LOCTEXT("BrushShape", "Brush Shape") ) .NameContent() [ SNew( STextBlock ) .Text( LOCTEXT("BrushShape", "Brush Shape")) .Font( IDetailLayoutBuilder::GetDetailFont() ) ] .ValueContent() .MinDesiredWidth(113) .MaxDesiredWidth(113) [ SNew(SComboButton) .ToolTipText(LOCTEXT("BspModeBuildTooltip", "Rebuild this brush from a parametric builder.")) .OnGetMenuContent_Static(&Local::GenerateBuildMenuContent, BrushBuilderPropertyHandle, &DetailLayout) .ContentPadding(2) .ButtonContent() [ SNew(STextBlock) .Text_Static(&Local::GetBuilderText, BrushBuilderPropertyHandle) .Font( IDetailLayoutBuilder::GetDetailFont() ) ] ]; BrushBuilderCategory.AddCustomRow( FText::GetEmpty(), true ) [ SNew(SHorizontalBox) + SHorizontalBox::Slot() .FillWidth(1) .Padding(1.0f) [ SNew(SComboButton) .ContentPadding(2) .ButtonContent() [ SNew(STextBlock) .Text(NSLOCTEXT("BrushDetails", "PolygonsMenu", "Polygons")) .ToolTipText(NSLOCTEXT("BrushDetails", "PolygonsMenu_ToolTip", "Polygon options")) .Font(IDetailLayoutBuilder::GetDetailFont()) ] .MenuContent() [ PolygonsMenuBuilder.MakeWidget() ] ] + SHorizontalBox::Slot() .FillWidth(1) .Padding(1.0f) [ SNew(SComboButton) .ContentPadding(2) .Visibility(bHaveAVolume ? EVisibility::Collapsed : EVisibility::Visible) .ButtonContent() [ SNew(STextBlock) .Text(NSLOCTEXT("BrushDetails", "SolidityMenu", "Solidity")) .ToolTipText(NSLOCTEXT("BrushDetails", "SolidityMenu_ToolTip", "Solidity options")) .Font(IDetailLayoutBuilder::GetDetailFont()) ] .MenuContent() [ SolidityMenuBuilder.MakeWidget() ] ] + SHorizontalBox::Slot() .FillWidth(1) .Padding(1.0f) [ SNew(SComboButton) .ContentPadding(2) .Visibility(bHaveAVolume ? EVisibility::Collapsed : EVisibility::Visible) .ButtonContent() [ SNew(STextBlock) .Text(NSLOCTEXT("BrushDetails", "OrderMenu", "Order")) .ToolTipText(NSLOCTEXT("BrushDetails", "OrderMenu_ToolTip", "Order options")) .Font(IDetailLayoutBuilder::GetDetailFont()) ] .MenuContent() [ OrderMenuBuilder.MakeWidget() ] ] ]; TSharedPtr< SHorizontalBox > BrushHorizontalBox; BrushBuilderCategory.AddCustomRow( FText::GetEmpty(), true) [ SAssignNew(BrushHorizontalBox, SHorizontalBox) +SHorizontalBox::Slot() [ SNew( SButton ) .ToolTipText( LOCTEXT("AlignBrushVerts_Tooltip", "Aligns each vertex of the brush to the grid.") ) .OnClicked( FOnClicked::CreateStatic( &Local::ExecuteExecCommand, FString( TEXT("ACTOR ALIGN VERTS") ) ) ) .HAlign( HAlign_Center ) [ SNew( STextBlock ) .Text( LOCTEXT("AlignBrushVerts", "Align Brush Vertices") ) .Font( IDetailLayoutBuilder::GetDetailFont() ) ] ] ]; if (SelectedBrushes.Num() > 0) { BrushHorizontalBox->AddSlot() [ SNew( SButton ) .ToolTipText( LOCTEXT("CreateStaticMeshActor_Tooltip", "Creates a static mesh from selected brushes or volumes and replaces them in the scene with the new static mesh") ) .OnClicked( this, &FBrushDetails::OnCreateStaticMesh ) .HAlign( HAlign_Center ) [ SNew( STextBlock ) .Text( LOCTEXT("CreateStaticMeshActor", "Create Static Mesh") ) .Font( IDetailLayoutBuilder::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); } } } }
BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION void FAnimTransitionNodeDetails::CustomizeDetails( IDetailLayoutBuilder& DetailBuilder ) { // Get a handle to the node we're viewing const TArray< TWeakObjectPtr<UObject> >& SelectedObjects = DetailBuilder.GetDetailsView().GetSelectedObjects(); for (int32 ObjectIndex = 0; !TransitionNode.IsValid() && (ObjectIndex < SelectedObjects.Num()); ++ObjectIndex) { const TWeakObjectPtr<UObject>& CurrentObject = SelectedObjects[ObjectIndex]; if (CurrentObject.IsValid()) { TransitionNode = Cast<UAnimStateTransitionNode>(CurrentObject.Get()); } } bool bTransitionToConduit = false; if (UAnimStateTransitionNode* TransitionNodePtr = TransitionNode.Get()) { UAnimStateNodeBase* NextState = TransitionNodePtr->GetNextState(); bTransitionToConduit = (NextState != NULL) && (NextState->IsA<UAnimStateConduitNode>()); } ////////////////////////////////////////////////////////////////////////// IDetailCategoryBuilder& TransitionCategory = DetailBuilder.EditCategory("Transition", LOCTEXT("TransitionCategoryTitle", "Transition") ); if (bTransitionToConduit) { // Transitions to conduits are just shorthand for some other real transition; // All of the blend related settings are ignored, so hide them. DetailBuilder.HideProperty(GET_MEMBER_NAME_CHECKED(UAnimStateTransitionNode, Bidirectional)); DetailBuilder.HideProperty(GET_MEMBER_NAME_CHECKED(UAnimStateTransitionNode, CrossfadeDuration)); DetailBuilder.HideProperty(GET_MEMBER_NAME_CHECKED(UAnimStateTransitionNode, BlendMode)); DetailBuilder.HideProperty(GET_MEMBER_NAME_CHECKED(UAnimStateTransitionNode, LogicType)); DetailBuilder.HideProperty(GET_MEMBER_NAME_CHECKED(UAnimStateTransitionNode, PriorityOrder)); } else { TransitionCategory.AddCustomRow( LOCTEXT("TransitionEventPropertiesCategoryLabel", "Transition") ) [ SNew( STextBlock ) .Text( LOCTEXT("TransitionEventPropertiesCategoryLabel", "Transition") ) .Font( IDetailLayoutBuilder::GetDetailFontBold() ) ]; TransitionCategory.AddProperty(GET_MEMBER_NAME_CHECKED(UAnimStateTransitionNode, PriorityOrder)).DisplayName(LOCTEXT("PriorityOrderLabel", "Priority Order")); TransitionCategory.AddProperty(GET_MEMBER_NAME_CHECKED(UAnimStateTransitionNode, Bidirectional)).DisplayName(LOCTEXT("BidirectionalLabel", "Bidirectional")); TransitionCategory.AddProperty(GET_MEMBER_NAME_CHECKED(UAnimStateTransitionNode, LogicType)).DisplayName(LOCTEXT("BlendLogicLabel", "Blend Logic") ); UAnimStateTransitionNode* TransNode = TransitionNode.Get(); if (TransitionNode != NULL) { // The sharing option for the rule TransitionCategory.AddCustomRow( LOCTEXT("TransitionRuleSharingLabel", "Transition Rule Sharing") ) [ GetWidgetForInlineShareMenu(TEXT("Transition Rule Sharing"), TransNode->SharedRulesName, TransNode->bSharedRules, FOnClicked::CreateSP(this, &FAnimTransitionNodeDetails::OnPromoteToSharedClick, true), FOnClicked::CreateSP(this, &FAnimTransitionNodeDetails::OnUnshareClick, true), FOnGetContent::CreateSP(this, &FAnimTransitionNodeDetails::OnGetShareableNodesMenu, true)) ]; // TransitionCategory.AddRow() // [ // SNew( STextBlock ) // .Text( TEXT("Crossfade Settings") ) // .Font( IDetailLayoutBuilder::GetDetailFontBold() ) // ]; // Show the rule itself UEdGraphPin* CanExecPin = NULL; if (UAnimationTransitionGraph* TransGraph = Cast<UAnimationTransitionGraph>(TransNode->BoundGraph)) { if (UAnimGraphNode_TransitionResult* ResultNode = TransGraph->GetResultNode()) { CanExecPin = ResultNode->FindPin(TEXT("bCanEnterTransition")); } } // indicate if a native transition rule applies to this UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForNodeChecked(TransitionNode.Get()); if(Blueprint && Blueprint->ParentClass) { UAnimInstance* AnimInstance = CastChecked<UAnimInstance>(Blueprint->ParentClass->GetDefaultObject()); if(AnimInstance) { UEdGraph* ParentGraph = TransitionNode->GetGraph(); UAnimStateNodeBase* PrevState = TransitionNode->GetPreviousState(); UAnimStateNodeBase* NextState = TransitionNode->GetNextState(); if(PrevState != nullptr && NextState != nullptr && ParentGraph != nullptr) { FName FunctionName; if(AnimInstance->HasNativeTransitionBinding(ParentGraph->GetFName(), FName(*PrevState->GetStateName()), FName(*NextState->GetStateName()), FunctionName)) { TransitionCategory.AddCustomRow( LOCTEXT("NativeBindingPresent_Filter", "Transition has native binding") ) [ SNew(STextBlock) .Text(FText::Format(LOCTEXT("NativeBindingPresent", "Transition has native binding to {0}()"), FText::FromName(FunctionName))) .Font( IDetailLayoutBuilder::GetDetailFontBold() ) ]; } } } } TransitionCategory.AddCustomRow( CanExecPin ? CanExecPin->PinFriendlyName : FText::GetEmpty() ) [ SNew(SKismetLinearExpression, CanExecPin) ]; } ////////////////////////////////////////////////////////////////////////// IDetailCategoryBuilder& CrossfadeCategory = DetailBuilder.EditCategory("BlendSettings", LOCTEXT("BlendSettingsCategoryTitle", "BlendSettings") ); if (TransitionNode != NULL) { // The sharing option for the crossfade settings CrossfadeCategory.AddCustomRow( LOCTEXT("TransitionCrossfadeSharingLabel", "Transition Crossfade Sharing") ) [ GetWidgetForInlineShareMenu(TEXT("Transition Crossfade Sharing"), TransNode->SharedCrossfadeName, TransNode->bSharedCrossfade, FOnClicked::CreateSP(this, &FAnimTransitionNodeDetails::OnPromoteToSharedClick, false), FOnClicked::CreateSP(this, &FAnimTransitionNodeDetails::OnUnshareClick, false), FOnGetContent::CreateSP(this, &FAnimTransitionNodeDetails::OnGetShareableNodesMenu, false)) ]; } //@TODO: Gate editing these on shared non-authorative ones CrossfadeCategory.AddProperty(GET_MEMBER_NAME_CHECKED(UAnimStateTransitionNode, CrossfadeDuration)).DisplayName( LOCTEXT("DurationLabel", "Duration") ); CrossfadeCategory.AddProperty(GET_MEMBER_NAME_CHECKED(UAnimStateTransitionNode, BlendMode)).DisplayName( LOCTEXT("ModeLabel", "Mode") ); CrossfadeCategory.AddProperty(GET_MEMBER_NAME_CHECKED(UAnimStateTransitionNode, CustomBlendCurve)).DisplayName(LOCTEXT("CurveLabel", "Custom Blend Curve")); USkeleton* TargetSkeleton = TransitionNode->GetAnimBlueprint()->TargetSkeleton; if(TargetSkeleton) { TSharedPtr<IPropertyHandle> BlendProfileHandle = DetailBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(UAnimStateTransitionNode, BlendProfile)); UObject* BlendProfilePropertyValue = nullptr; BlendProfileHandle->GetValue(BlendProfilePropertyValue); UBlendProfile* CurrentProfile = Cast<UBlendProfile>(BlendProfilePropertyValue); ISkeletonEditorModule& SkeletonEditorModule = FModuleManager::LoadModuleChecked<ISkeletonEditorModule>("SkeletonEditor"); FBlendProfilePickerArgs Args; Args.InitialProfile = CurrentProfile; Args.OnBlendProfileSelected = FOnBlendProfileSelected::CreateSP(this, &FAnimTransitionNodeDetails::OnBlendProfileChanged, BlendProfileHandle); Args.bAllowNew = false; CrossfadeCategory.AddProperty(BlendProfileHandle).CustomWidget(true) .NameContent() [ BlendProfileHandle->CreatePropertyNameWidget() ] .ValueContent() [ SkeletonEditorModule.CreateBlendProfilePicker(TargetSkeleton, Args) ]; } // Add a button that is only visible when blend logic type is custom CrossfadeCategory.AddCustomRow( LOCTEXT("EditBlendGraph", "Edit Blend Graph") ) [ SNew( SHorizontalBox ) +SHorizontalBox::Slot() .HAlign(HAlign_Right) .FillWidth(1) .Padding(0,0,10.0f,0) [ SNew(SButton) .HAlign(HAlign_Right) .OnClicked(this, &FAnimTransitionNodeDetails::OnClickEditBlendGraph) .Visibility( this, &FAnimTransitionNodeDetails::GetBlendGraphButtonVisibility ) .Text(LOCTEXT("EditBlendGraph", "Edit Blend Graph")) ] ]; ////////////////////////////////////////////////////////////////////////// IDetailCategoryBuilder& NotificationCategory = DetailBuilder.EditCategory("Notifications", LOCTEXT("NotificationsCategoryTitle", "Notifications") ); NotificationCategory.AddCustomRow( LOCTEXT("StartTransitionEventPropertiesCategoryLabel", "Start Transition Event") ) [ SNew( STextBlock ) .Text( LOCTEXT("StartTransitionEventPropertiesCategoryLabel", "Start Transition Event") ) .Font( IDetailLayoutBuilder::GetDetailFontBold() ) ]; CreateTransitionEventPropertyWidgets(NotificationCategory, TEXT("TransitionStart")); NotificationCategory.AddCustomRow( LOCTEXT("EndTransitionEventPropertiesCategoryLabel", "End Transition Event" ) ) [ SNew( STextBlock ) .Text( LOCTEXT("EndTransitionEventPropertiesCategoryLabel", "End Transition Event" ) ) .Font( IDetailLayoutBuilder::GetDetailFontBold() ) ]; CreateTransitionEventPropertyWidgets(NotificationCategory, TEXT("TransitionEnd")); NotificationCategory.AddCustomRow( LOCTEXT("InterruptTransitionEventPropertiesCategoryLabel", "Interrupt Transition Event") ) [ SNew( STextBlock ) .Text( LOCTEXT("InterruptTransitionEventPropertiesCategoryLabel", "Interrupt Transition Event") ) .Font( IDetailLayoutBuilder::GetDetailFontBold() ) ]; CreateTransitionEventPropertyWidgets(NotificationCategory, TEXT("TransitionInterrupt")); } DetailBuilder.HideProperty(GET_MEMBER_NAME_CHECKED(UAnimStateTransitionNode, TransitionStart)); DetailBuilder.HideProperty(GET_MEMBER_NAME_CHECKED(UAnimStateTransitionNode, TransitionEnd)); }
void FEditorUtilityInstanceDetails::CustomizeDetails(IDetailLayoutBuilder& DetailLayoutBuilder) { SelectedObjectsList = DetailLayoutBuilder.GetDetailsView().GetSelectedObjects(); // Hide some useless categories //@TODO: How to hide Actors, Layers, etc...? // Build a list of unique selected blutilities TArray<UClass*> UniqueBlutilityClasses; bool bFoundAnyCDOs = false; for (auto SelectedObjectIt = SelectedObjectsList.CreateConstIterator(); SelectedObjectIt; ++SelectedObjectIt) { UObject* Object = (*SelectedObjectIt).Get(); if (!Object->HasAnyFlags(RF_ClassDefaultObject)) { UClass* ObjectClass = Object->GetClass(); if (UEditorUtilityBlueprint* Blutility = Cast<UEditorUtilityBlueprint>(ObjectClass->ClassGeneratedBy)) { UniqueBlutilityClasses.Add(ObjectClass); } } else { bFoundAnyCDOs = true; } } // Run thru each one UniqueBlutilityClasses.Sort(FCompareClassNames()); for (auto ClassIt = UniqueBlutilityClasses.CreateIterator(); ClassIt; ++ClassIt) { UClass* Class = *ClassIt; FString CategoryName = FString::Printf(TEXT("%sActions"), *Class->ClassGeneratedBy->GetName()); IDetailCategoryBuilder& ActionsCategory = DetailLayoutBuilder.EditCategory(*CategoryName); const APlacedEditorUtilityBase* PlacedActorCDO = Cast<const APlacedEditorUtilityBase>(Class->GetDefaultObject()); if (PlacedActorCDO) { ActionsCategory.AddCustomRow( PlacedActorCDO->HelpText ) [ SNew(STextBlock) .Text(PlacedActorCDO->HelpText) ]; } const UGlobalEditorUtilityBase* GlobalBlutilityCDO = Cast<const UGlobalEditorUtilityBase>(Class->GetDefaultObject()); if (GlobalBlutilityCDO) { ActionsCategory.AddCustomRow( GlobalBlutilityCDO->HelpText ) [ SNew(STextBlock) .Text(GlobalBlutilityCDO->HelpText) ]; } TSharedRef<SWrapBox> WrapBox = SNew(SWrapBox).UseAllottedWidth(true); int32 NumButtons = 0; for (TFieldIterator<UFunction> FuncIt(Class, EFieldIteratorFlags::IncludeSuper); FuncIt; ++FuncIt) { UFunction* Function = *FuncIt; const bool bCanExecute = (Function->NumParms == 0) && Function->HasAllFunctionFlags(FUNC_Exec); if (bCanExecute) { ++NumButtons; const FString ButtonCaption = FName::NameToDisplayString(*Function->GetName(), false); //@TODO: Expose the code in UK2Node_CallFunction::GetUserFacingFunctionName / etc... FString Tooltip = Function->GetToolTipText().ToString(); if (Tooltip.IsEmpty()) { Tooltip = Function->GetName(); } TWeakObjectPtr<UFunction> WeakFunctionPtr(Function); WrapBox->AddSlot() [ SNew(SButton) .Text(ButtonCaption) .OnClicked( FOnClicked::CreateSP(this, &FEditorUtilityInstanceDetails::OnExecuteAction, WeakFunctionPtr) ) .ToolTipText(Tooltip) ]; } } if (NumButtons > 0) { ActionsCategory.AddCustomRow(TEXT("")) [ WrapBox ]; } } // Hide the hint property if (!bFoundAnyCDOs) { DetailLayoutBuilder.HideProperty(TEXT("HelpText")); } }