// Copyright Epic Games, Inc. All Rights Reserved. #include "AssetViewerSettingsCustomization.h" #include "AssetViewerSettings.h" #include "Containers/Array.h" #include "Containers/Map.h" #include "Delegates/Delegate.h" #include "DetailCategoryBuilder.h" #include "DetailLayoutBuilder.h" #include "DetailWidgetRow.h" #include "Editor/EditorPerProjectUserSettings.h" #include "Fonts/SlateFontInfo.h" #include "GenericPlatform/GenericPlatformFile.h" #include "HAL/PlatformCrt.h" #include "HAL/PlatformFileManager.h" #include "IDetailGroup.h" #include "IDetailPropertyRow.h" #include "Internationalization/Internationalization.h" #include "Misc/AssertionMacros.h" #include "Misc/Attribute.h" #include "PropertyHandle.h" #include "SSettingsEditorCheckoutNotice.h" #include "ScopedTransaction.h" #include "UObject/NameTypes.h" #include "UObject/UObjectGlobals.h" #include "UObject/UnrealType.h" #include "Widgets/DeclarativeSyntaxSupport.h" #include "Widgets/Input/SEditableTextBox.h" #define LOCTEXT_NAMESPACE "AssetViewerSettingsCustomizations" TSharedRef FAssetViewerSettingsCustomization::MakeInstance() { return MakeShareable(new FAssetViewerSettingsCustomization); } void FAssetViewerSettingsCustomization::CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) { ViewerSettings = UAssetViewerSettings::Get(); // Create the watcher widget for the default config file (checks file status / SCC state) FileWatcherWidget = SNew(SSettingsEditorCheckoutNotice) .ConfigFilePath(this, &FAssetViewerSettingsCustomization::SharedProfileConfigFilePath) .Visibility(this, &FAssetViewerSettingsCustomization::ShowFileWatcherWidget); // Find profiles array property handle and hide it from settings view TSharedPtr ProfileHandle = DetailBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(UAssetViewerSettings, Profiles)); check(ProfileHandle->IsValidHandle()); ProfileHandle->MarkHiddenByCustomization(); // Create new category IDetailCategoryBuilder& CategoryBuilder = DetailBuilder.EditCategory("Settings", LOCTEXT("AssetViewerSettingsCategory", "Settings"), ECategoryPriority::Important); const UEditorPerProjectUserSettings* PerProjectUserSettings = GetDefault(); ensureMsgf(ViewerSettings && ViewerSettings->Profiles.IsValidIndex(PerProjectUserSettings->AssetViewerProfileIndex), TEXT("Invalid default settings pointer or current profile index")); check(ViewerSettings != nullptr); ProfileIndex = ViewerSettings->Profiles.IsValidIndex(PerProjectUserSettings->AssetViewerProfileIndex) ? PerProjectUserSettings->AssetViewerProfileIndex : 0; // Add current active profile property child nodes (rest of profiles remain hidden) TSharedPtr ProfilePropertyHandle = ProfileHandle->GetChildHandle(ProfileIndex); checkf(ProfileHandle->IsValidHandle(), TEXT("Invalid indexing into profiles child properties")); uint32 PropertyCount = 0; ProfilePropertyHandle->GetNumChildren(PropertyCount); TMap SubcategoryToGroupMap; FName NamePropertyName = GET_MEMBER_NAME_CHECKED(FPreviewSceneProfile, ProfileName); FName SharedProfilePropertyName = GET_MEMBER_NAME_CHECKED(FPreviewSceneProfile, bSharedProfile); for (uint32 PropertyIndex = 0; PropertyIndex < PropertyCount; ++PropertyIndex) { TSharedPtr ProfileProperty = ProfilePropertyHandle->GetChildHandle(PropertyIndex); if (ProfileProperty->GetProperty()->GetFName() == NamePropertyName) { NameProperty = ProfileProperty; CategoryBuilder.AddCustomRow(LOCTEXT("PreviewSceneProfileDetails_ProfileNameLabel", "Profile Name")) .NameContent() [ ProfileProperty->CreatePropertyNameWidget() ] .ValueContent() .MaxDesiredWidth(250.0f) [ // PropertyHandle->CreatePropertyValueWidget() SAssignNew(NameEditTextBox, SEditableTextBox) .IsEnabled_Lambda([this]() { return !ViewerSettings->Profiles[ProfileIndex].bIsEngineDefaultProfile; }) .Text(this, &FAssetViewerSettingsCustomization::OnGetProfileName) .OnTextChanged(this, &FAssetViewerSettingsCustomization::OnProfileNameChanged) .OnTextCommitted(this, &FAssetViewerSettingsCustomization::OnProfileNameCommitted) .Font(IDetailLayoutBuilder::GetDetailFont()) ]; } else if (ProfileProperty->GetProperty()->GetFName() == SharedProfilePropertyName) { IDetailPropertyRow& Row = CategoryBuilder.AddProperty(ProfileProperty); Row.EditCondition(TAttribute(this, &FAssetViewerSettingsCustomization::CanSetSharedProfile), nullptr); CategoryBuilder.AddCustomRow(LOCTEXT("PreviewSceneProfileName_CheckoutRow", "Checkout Default Config")) .Visibility(TAttribute(this, &FAssetViewerSettingsCustomization::ShowFileWatcherWidget)) [ FileWatcherWidget->AsShared() ]; } else { FName DefaultCategoryName = ProfileProperty->GetDefaultCategoryName(); IDetailGroup*& Group = SubcategoryToGroupMap.FindOrAdd(DefaultCategoryName); if (!Group) { Group = &CategoryBuilder.AddGroup(DefaultCategoryName, ProfileProperty->GetDefaultCategoryText()); } Group->AddPropertyRow(ProfileProperty.ToSharedRef()); } } } FText FAssetViewerSettingsCustomization::OnGetProfileName() const { return FText::FromString(ViewerSettings->Profiles[ProfileIndex].ProfileName); } void FAssetViewerSettingsCustomization::OnProfileNameChanged(const FText& InNewText) { bValidProfileName = IsProfileNameValid(InNewText.ToString()); if (!bValidProfileName) { NameEditTextBox->SetError(LOCTEXT("PreviewSceneProfileName_NotValid", "This name is already in use")); } else { NameEditTextBox->SetError(FText::GetEmpty()); } } void FAssetViewerSettingsCustomization::OnProfileNameCommitted(const FText& InNewText, ETextCommit::Type InTextCommit) { if (bValidProfileName && InTextCommit != ETextCommit::OnCleared) { const FScopedTransaction Transaction(LOCTEXT("RenameProfile", "Rename Profile")); ViewerSettings->Modify(); ViewerSettings->Profiles[ProfileIndex].ProfileName = InNewText.ToString(); FPropertyChangedEvent PropertyEvent(NameProperty->GetProperty()); ViewerSettings->PostEditChangeProperty(PropertyEvent); } bValidProfileName = false; NameEditTextBox->SetError(FText::GetEmpty()); } const bool FAssetViewerSettingsCustomization::IsProfileNameValid(const FString& NewName) { for (int32 CheckProfileIndex = 0; CheckProfileIndex < ViewerSettings->Profiles.Num(); ++CheckProfileIndex) { if (ProfileIndex != CheckProfileIndex && ViewerSettings->Profiles[CheckProfileIndex].ProfileName == NewName) { return false; } } return true; } bool FAssetViewerSettingsCustomization::CanSetSharedProfile() const { const bool bIsEngineDefaultProfile = ViewerSettings->Profiles[ProfileIndex].bIsEngineDefaultProfile; const bool bIsConfigWriteable = !FPlatformFileManager::Get().GetPlatformFile().IsReadOnly(*SharedProfileConfigFilePath()); return bIsConfigWriteable && !bIsEngineDefaultProfile; } EVisibility FAssetViewerSettingsCustomization::ShowFileWatcherWidget() const { return CanSetSharedProfile() ? EVisibility::Collapsed : EVisibility::Visible; } FString FAssetViewerSettingsCustomization::SharedProfileConfigFilePath() const { if (ViewerSettings != nullptr) { return ViewerSettings->GetDefaultConfigFilename(); } return FString(); } #undef LOCTEXT_NAMESPACE