From 9087034b92ad1605c601eb42097da6e61fe28c13 Mon Sep 17 00:00:00 2001 From: Ryan DowlingSoka Date: Tue, 31 May 2022 23:10:05 -0700 Subject: [PATCH] RedEditorIconWidget and Customization --- .../RedEditorIconPathCustomization.cpp | 188 ++++++++++++++++++ .../Private/RedEditorIconWidget.cpp | 49 +++++ .../RedEditorIconPathCustomization.h | 49 +++++ .../Public/RedEditorIconWidget.h | 66 ++++++ 4 files changed, 352 insertions(+) create mode 100644 Source/RedTechArtToolsEditor/Private/Customization/RedEditorIconPathCustomization.cpp create mode 100644 Source/RedTechArtToolsEditor/Private/RedEditorIconWidget.cpp create mode 100644 Source/RedTechArtToolsEditor/Public/Customization/RedEditorIconPathCustomization.h create mode 100644 Source/RedTechArtToolsEditor/Public/RedEditorIconWidget.h diff --git a/Source/RedTechArtToolsEditor/Private/Customization/RedEditorIconPathCustomization.cpp b/Source/RedTechArtToolsEditor/Private/Customization/RedEditorIconPathCustomization.cpp new file mode 100644 index 0000000..e27a0a4 --- /dev/null +++ b/Source/RedTechArtToolsEditor/Private/Customization/RedEditorIconPathCustomization.cpp @@ -0,0 +1,188 @@ +// MIT License +// +// Copyright (c) 2022 Ryan DowlingSoka +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#include "Customization/RedEditorIconPathCustomization.h" +#include "DetailWidgetRow.h" +#include "RedEditorIconWidget.h" +#include "RedDeveloperSettings.h" +#include "SSearchableComboBox.h" +#include "Brushes/SlateImageBrush.h" + +TSharedRef FRedEditorIconPathCustomization::MakeInstance() +{ + // Create the instance and returned a SharedRef + return MakeShareable(new FRedEditorIconPathCustomization()); +} + +void FRedEditorIconPathCustomization::CustomizeHeader(TSharedRef StructPropertyHandle, + class FDetailWidgetRow& HeaderRow, + IPropertyTypeCustomizationUtils& StructCustomizationUtils) +{ + void* StructData = nullptr; + const FPropertyAccess::Result Result = StructPropertyHandle.Get().GetValueData(StructData); + if (Result == FPropertyAccess::Success) + { + check(StructData); + FRedEditorIconPath* CurrentValue = static_cast(StructData); + const FString CurrentValuePath = CurrentValue->Path; + const FString ItemName = FPaths::GetCleanFilename(*CurrentValuePath); + const auto CurrentSelectionText = SNew(STextBlock) + .Text_Lambda([=]() { return FText::FromString(FPaths::GetCleanFilename(CurrentValue->Path)); }); + HeaderRow.NameContent()[StructPropertyHandle->CreatePropertyNameWidget()] + .ValueContent() + [ + SNew(SSearchableComboBox) + .OptionsSource(GetIconOptionsPointer()) + .OnGenerateWidget(this, &FRedEditorIconPathCustomization::HandleGenerateWidget) + .OnSelectionChanged_Lambda([=](TSharedPtr NewChoice, ESelectInfo::Type SelectType) + { + if (NewChoice.IsValid()) + { + StructPropertyHandle.Get().NotifyPreChange(); + CurrentValue->Path = *NewChoice; + const FString ChoiceItemName = FPaths::GetCleanFilename(*NewChoice); + StructPropertyHandle.Get().NotifyPostChange( + EPropertyChangeType::ValueSet); + } + }) + .ToolTip(SNew(SToolTip).Text_Lambda([=]() + { + return FText::FromString( + FPaths::ConvertRelativePathToFull(CurrentValue->Path)); + })) + .Content() + [ + CurrentSelectionText + ] + ]; + } +} + +TSharedRef FRedEditorIconPathCustomization::HandleGenerateWidget(TSharedPtr InItem) +{ + const int EngineIndex = InItem->Find(TEXT("/Engine/")); + const int EditorIndex = InItem->Find(TEXT("/Editor/")); + int RightChopIndex = 0; + if (EngineIndex >= 0 || EditorIndex >= 0) + { + RightChopIndex = FMath::Max(EngineIndex + 7, EditorIndex + 7); + } + + static FVector2D IconSize = FVector2D(24, 24); + const FString AbsolutePath = FPaths::ConvertRelativePathToFull(*InItem); + const FString ItemName = FPaths::GetCleanFilename(*InItem); + const FString Category = InItem->RightChop(RightChopIndex).LeftChop(ItemName.Len() + 1). + Replace(TEXT("/Content/"),TEXT("/")). + Replace(TEXT("/Slate/"),TEXT("/")). + Replace(TEXT("/Plugins/"),TEXT("/")). + Replace(TEXT("/Experimental/"),TEXT("/")).RightChop(1); + + if (!InItem->IsEmpty() && IFileManager::Get().FileExists(*AbsolutePath)) + { + TUniquePtr& Item = GeneratedBrushes.FindOrAdd(*InItem); + if (!Item.IsValid()) + { + if (const FString Ext = FPaths::GetExtension(*InItem); Ext == "svg") + { + Item.Reset(new FSlateVectorImageBrush(*InItem, IconSize)); + } + else if (Ext == "png") + { + Item.Reset(new FSlateDynamicImageBrush(FName(**InItem), IconSize)); + } + else + { + return SNew(STextBlock).Text(FText::FromString(FString("Path is not a .png or .svg file."))); + } + } + return SNew(SHorizontalBox) + .ToolTip(SNew(SToolTip).Text(FText::FromString(*AbsolutePath))) + + SHorizontalBox::Slot() + .AutoWidth() + .VAlign(EVerticalAlignment::VAlign_Center) + [ + SNew(SImage) + .Image(Item.Get()) + ] + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(16.0f, 1.0f) + .VAlign(EVerticalAlignment::VAlign_Center) + [ + SNew(STextBlock) + .Text(FText::FromString(ItemName)) + .MinDesiredWidth(200.0f) + ] + + SHorizontalBox::Slot() + .FillWidth(1.0f) + .Padding(16.0f, 1.0f, 0.0f, 1.0f) + .VAlign(EVerticalAlignment::VAlign_Center) + .HAlign(EHorizontalAlignment::HAlign_Right) + [ + SNew(STextBlock) + .Text(FText::FromString(Category)) + .MinDesiredWidth(300.0f) + .ColorAndOpacity(FLinearColor(0.5, 0.5, 0.5, 0.5)) + .Justification(ETextJustify::Right) + ]; + } + + return SNew(STextBlock).Text(FText::FromString(FString("Path doesn't exist."))); +} + +void FRedEditorIconPathCustomization::CustomizeChildren(TSharedRef StructPropertyHandle, + class IDetailChildrenBuilder& StructBuilder, + IPropertyTypeCustomizationUtils& StructCustomizationUtils) +{ +} + +TArray FRedEditorIconPathCustomization::GetIconOptionsFromPath() +{ + TArray OutFoundIcons; + const URedDeveloperSettings* RedSettings = GetDefault(); + for (const FString& Path : RedSettings->EditorIconWidgetSearchPaths) + { + GetIconsFromPath(Path, OutFoundIcons); + } + return OutFoundIcons; +} + +TArray>* FRedEditorIconPathCustomization::GetIconOptionsPointer() +{ + if (CachedIconOptions.Num() == 0) + { + Algo::Transform(GetIconOptionsFromPath(), CachedIconOptions, [](const FString& InString) + { + return MakeShared(InString); + }); + } + + return &CachedIconOptions; +} + + +void FRedEditorIconPathCustomization::GetIconsFromPath(const FString& InPath, TArray& OutFoundIcons) +{ + const FString SearchDirectory = FPaths::EngineDir() / InPath; + IFileManager::Get().FindFilesRecursive(OutFoundIcons, *SearchDirectory, TEXT("*.png"), true, false, false); + IFileManager::Get().FindFilesRecursive(OutFoundIcons, *SearchDirectory, TEXT("*.svg"), true, false, false); +} diff --git a/Source/RedTechArtToolsEditor/Private/RedEditorIconWidget.cpp b/Source/RedTechArtToolsEditor/Private/RedEditorIconWidget.cpp new file mode 100644 index 0000000..6902253 --- /dev/null +++ b/Source/RedTechArtToolsEditor/Private/RedEditorIconWidget.cpp @@ -0,0 +1,49 @@ +// MIT License +// +// Copyright (c) 2022 Ryan DowlingSoka +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#include "RedEditorIconWidget.h" + +#include "Brushes/SlateImageBrush.h" +#include "Components/Image.h" + + +void URedEditorIconWidget::SynchronizeProperties() +{ + Super::SynchronizeProperties(); + if(!IconPath.Path.IsEmpty() && IFileManager::Get().FileExists(*FPaths::ConvertRelativePathToFull(*IconPath.Path))) + { + if(const FString Ext = FPaths::GetExtension(IconPath.Path); Ext == "svg") + { + IconBrush.Reset(new FSlateVectorImageBrush( IconPath.Path, IconSize, Brush.TintColor, Brush.Tiling )); + } + else if(Ext == "png") + { + IconBrush.Reset(new FSlateDynamicImageBrush(FName(*IconPath.Path), IconSize, Brush.TintColor.GetSpecifiedColor(), Brush.Tiling )); + } + if(IconBrush != nullptr) + { + SetBrush(*IconBrush.Get()); + } + } +} + + diff --git a/Source/RedTechArtToolsEditor/Public/Customization/RedEditorIconPathCustomization.h b/Source/RedTechArtToolsEditor/Public/Customization/RedEditorIconPathCustomization.h new file mode 100644 index 0000000..7f7d4c1 --- /dev/null +++ b/Source/RedTechArtToolsEditor/Public/Customization/RedEditorIconPathCustomization.h @@ -0,0 +1,49 @@ +// MIT License +// +// Copyright (c) 2022 Ryan DowlingSoka +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#pragma once + +class FRedEditorIconPathCustomization : public IPropertyTypeCustomization +{ +public: + static TSharedRef MakeInstance(); + + // BEGIN IPropertyTypeCustomization interface + virtual void CustomizeHeader(TSharedRef StructPropertyHandle, + class FDetailWidgetRow& HeaderRow, + IPropertyTypeCustomizationUtils& StructCustomizationUtils) override; + virtual void CustomizeChildren(TSharedRef StructPropertyHandle, + class IDetailChildrenBuilder& StructBuilder, + IPropertyTypeCustomizationUtils& StructCustomizationUtils) override; + // END IPropertyTypeCustomization interface + +private: + TSharedRef HandleGenerateWidget(TSharedPtr InItem); + + TArray>* GetIconOptionsPointer(); + TArray> CachedIconOptions; + + static TArray GetIconOptionsFromPath(); + static void GetIconsFromPath(const FString& InPath, TArray& OutFoundIcons); + + TMap> GeneratedBrushes; +}; diff --git a/Source/RedTechArtToolsEditor/Public/RedEditorIconWidget.h b/Source/RedTechArtToolsEditor/Public/RedEditorIconWidget.h new file mode 100644 index 0000000..0a9fc04 --- /dev/null +++ b/Source/RedTechArtToolsEditor/Public/RedEditorIconWidget.h @@ -0,0 +1,66 @@ +// MIT License +// +// Copyright (c) 2022 Ryan DowlingSoka +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#pragma once + +#include "CoreMinimal.h" +#include "Blueprint/UserWidget.h" +#include "Components/Image.h" +#include "Styling/SlateBrush.h" +#include "RedEditorIconWidget.generated.h" + +class UImage; + +USTRUCT() +struct FRedEditorIconPath +{ + GENERATED_BODY() + + UPROPERTY(EditAnywhere) + FString Path; +}; + + +/** + * Convenience class for Editor Utility Widgets to access the Editor Slate Icons. + * Not available at runtime. + */ +UCLASS(DisplayName="RED Editor Icon Widget") +class REDTECHARTTOOLSEDITOR_API URedEditorIconWidget : public UImage +{ + GENERATED_BODY() + +public: + UPROPERTY(EditAnywhere, Category = "Editor Icon Widget") + FVector2D IconSize = FVector2D(20.f, 20.f); + + UPROPERTY(EditAnywhere, Category = "Editor Icon Widget") + FRedEditorIconPath IconPath; + +protected: + virtual void SynchronizeProperties() override; + +private: + TUniquePtr IconBrush; + + +};