├── .gitignore ├── Config ├── DefaultEditor.ini ├── DefaultEngine.ini └── DefaultGame.ini ├── Content └── Articles │ ├── A1_GatherSubclasses │ ├── A1_Map.umap │ ├── BP_A1_GatherSubclasses.uasset │ └── Blueprints │ │ ├── BP_SMActorBase.uasset │ │ └── BP_SMActorDerived.uasset │ └── A2_DetailsCustomization │ └── BP_A2_DetailsCustomization.uasset ├── KantanCodeExamples.uproject ├── LICENSE ├── README.md └── Source ├── A1_GatherSubclasses └── Runtime │ ├── A1_GatherSubclasses.Build.cs │ ├── A1_GatherSubclasses.cpp │ ├── A1_GatherSubclasses.h │ ├── A1_GatherSubclassesModule.cpp │ └── A1_GatherSubclassesPCH.h ├── A2_DetailsCustomization ├── Editor │ ├── A2_DetailsCustomizationEditor.Build.cs │ ├── A2_DetailsCustomizationEditorModule.cpp │ ├── A2_DetailsCustomizationEditorPCH.h │ ├── A2_MyClassCustomization.cpp │ └── A2_MyClassCustomization.h └── Runtime │ ├── A2_DetailsCustomization.Build.cs │ ├── A2_DetailsCustomizationModule.cpp │ ├── A2_DetailsCustomizationPCH.h │ └── A2_MyClass.h ├── Common └── Runtime │ ├── KantanCodeExamplesCommon.Build.cs │ ├── KantanCodeExamplesCommon.h │ ├── KantanCodeExamplesCommonPCH.h │ └── KantanCodeExamplesModule.cpp ├── KantanCodeExamples.Target.cs └── KantanCodeExamplesEditor.Target.cs /.gitignore: -------------------------------------------------------------------------------- 1 | # Visual Studio 2015 user specific files 2 | .vs/ 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | 22 | # Compiled Static libraries 23 | *.lai 24 | *.la 25 | *.a 26 | *.lib 27 | 28 | # Executables 29 | *.exe 30 | *.out 31 | *.app 32 | *.ipa 33 | 34 | # These project files can be generated by the engine 35 | *.xcodeproj 36 | *.sln 37 | *.suo 38 | *.opensdf 39 | *.sdf 40 | *.VC.opendb 41 | *.VC.db 42 | 43 | # Precompiled Assets 44 | SourceArt/**/*.png 45 | SourceArt/**/*.tga 46 | 47 | # Binary Files 48 | Binaries/* 49 | 50 | # Builds 51 | Build/* 52 | 53 | # Don't ignore icon files in Build 54 | !Build/**/*.ico 55 | 56 | # Configuration files generated by the Editor 57 | Saved/* 58 | 59 | # Compiled source files for the engine to use 60 | Intermediate/* 61 | 62 | # Cache files for the editor to use 63 | DerivedDataCache/* 64 | -------------------------------------------------------------------------------- /Config/DefaultEditor.ini: -------------------------------------------------------------------------------- 1 | [EditoronlyBP] 2 | bAllowClassAndBlueprintPinMatching=true 3 | bReplaceBlueprintWithClass=true 4 | bDontLoadBlueprintOutsideEditor=true 5 | bBlueprintIsNotBlueprintType=true 6 | -------------------------------------------------------------------------------- /Config/DefaultEngine.ini: -------------------------------------------------------------------------------- 1 | [URL] 2 | 3 | [/Script/HardwareTargeting.HardwareTargetingSettings] 4 | TargetedHardwareClass=Desktop 5 | AppliedTargetedHardwareClass=Desktop 6 | DefaultGraphicsPerformance=Maximum 7 | AppliedDefaultGraphicsPerformance=Maximum 8 | 9 | [/Script/EngineSettings.GameMapsSettings] 10 | EditorStartupMap=None 11 | GameDefaultMap=/Game/Articles/A1_GatherSubclasses/A1_Map.A1_Map 12 | 13 | [/Script/Engine.Engine] 14 | +ActiveClassRedirects=(OldClassName="Article1_GatherSubclasses",NewClassName="/Script/A1_GatherSubclasses.A1_GatherSubclasses") 15 | 16 | [/Script/Engine.PhysicsSettings] 17 | DefaultGravityZ=-980.000000 18 | DefaultTerminalVelocity=4000.000000 19 | DefaultFluidFriction=0.300000 20 | SimulateScratchMemorySize=262144 21 | RagdollAggregateThreshold=4 22 | TriangleMeshTriangleMinAreaThreshold=5.000000 23 | bEnableAsyncScene=False 24 | bEnableShapeSharing=False 25 | bEnablePCM=True 26 | bEnableStabilization=False 27 | bWarnMissingLocks=True 28 | bEnable2DPhysics=False 29 | LockedAxis=Invalid 30 | DefaultDegreesOfFreedom=Full3D 31 | BounceThresholdVelocity=200.000000 32 | FrictionCombineMode=Average 33 | RestitutionCombineMode=Average 34 | MaxAngularVelocity=3600.000000 35 | MaxDepenetrationVelocity=0.000000 36 | ContactOffsetMultiplier=0.010000 37 | MinContactOffset=0.000100 38 | MaxContactOffset=1.000000 39 | bSimulateSkeletalMeshOnDedicatedServer=True 40 | DefaultShapeComplexity=CTF_UseSimpleAndComplex 41 | bDefaultHasComplexCollision=True 42 | bSuppressFaceRemapTable=False 43 | bSupportUVFromHitResults=False 44 | bDisableActiveActors=False 45 | bDisableCCD=False 46 | MaxPhysicsDeltaTime=0.033333 47 | bSubstepping=False 48 | bSubsteppingAsync=False 49 | MaxSubstepDeltaTime=0.016667 50 | MaxSubsteps=6 51 | SyncSceneSmoothingFactor=0.000000 52 | AsyncSceneSmoothingFactor=0.990000 53 | InitialAverageFrameRate=0.016667 54 | 55 | 56 | -------------------------------------------------------------------------------- /Config/DefaultGame.ini: -------------------------------------------------------------------------------- 1 | [/Script/EngineSettings.GeneralProjectSettings] 2 | ProjectID=B9852C49485ED233ED011C955F23EFFD 3 | 4 | [/Script/UnrealEd.ProjectPackagingSettings] 5 | BuildConfiguration=PPBC_Development 6 | StagingDirectory=(Path="C:/Users/Cameron/Desktop/Packaged/KantanCodeExamples") 7 | FullRebuild=False 8 | ForDistribution=False 9 | IncludeDebugFiles=False 10 | bNativizeBlueprintAssets=False 11 | UsePakFile=True 12 | bGenerateChunks=False 13 | bChunkHardReferencesOnly=False 14 | bBuildHttpChunkInstallData=False 15 | HttpChunkInstallDataDirectory=(Path="") 16 | HttpChunkInstallDataVersion= 17 | IncludePrerequisites=True 18 | ApplocalPrerequisitesDirectory=(Path="") 19 | IncludeCrashReporter=True 20 | InternationalizationPreset=English 21 | -CulturesToStage=en 22 | +CulturesToStage=en 23 | DefaultCulture=en 24 | bCookAll=False 25 | bCookMapsOnly=False 26 | bCompressed=False 27 | bEncryptIniFiles=False 28 | bSkipEditorContent=False 29 | -MapsToCook=(FilePath="/Game/Articles/A1_GatherSubclasses/A1_Map") 30 | +MapsToCook=(FilePath="/Game/Articles/A1_GatherSubclasses/A1_Map") 31 | -DirectoriesToAlwaysCook=(Path="") 32 | +DirectoriesToAlwaysCook=(Path="Articles/A1_GatherSubclasses/Blueprints") 33 | 34 | -------------------------------------------------------------------------------- /Content/Articles/A1_GatherSubclasses/A1_Map.umap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kamrann/KantanCodeExamples/6d3789babfeefec9c0ccfc697e5aa28462aab0d6/Content/Articles/A1_GatherSubclasses/A1_Map.umap -------------------------------------------------------------------------------- /Content/Articles/A1_GatherSubclasses/BP_A1_GatherSubclasses.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kamrann/KantanCodeExamples/6d3789babfeefec9c0ccfc697e5aa28462aab0d6/Content/Articles/A1_GatherSubclasses/BP_A1_GatherSubclasses.uasset -------------------------------------------------------------------------------- /Content/Articles/A1_GatherSubclasses/Blueprints/BP_SMActorBase.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kamrann/KantanCodeExamples/6d3789babfeefec9c0ccfc697e5aa28462aab0d6/Content/Articles/A1_GatherSubclasses/Blueprints/BP_SMActorBase.uasset -------------------------------------------------------------------------------- /Content/Articles/A1_GatherSubclasses/Blueprints/BP_SMActorDerived.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kamrann/KantanCodeExamples/6d3789babfeefec9c0ccfc697e5aa28462aab0d6/Content/Articles/A1_GatherSubclasses/Blueprints/BP_SMActorDerived.uasset -------------------------------------------------------------------------------- /Content/Articles/A2_DetailsCustomization/BP_A2_DetailsCustomization.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kamrann/KantanCodeExamples/6d3789babfeefec9c0ccfc697e5aa28462aab0d6/Content/Articles/A2_DetailsCustomization/BP_A2_DetailsCustomization.uasset -------------------------------------------------------------------------------- /KantanCodeExamples.uproject: -------------------------------------------------------------------------------- 1 | { 2 | "FileVersion": 3, 3 | "EngineAssociation": "4.18", 4 | "Category": "", 5 | "Description": "", 6 | "Modules": [ 7 | { 8 | "Name": "KantanCodeExamplesCommon", 9 | "Type": "Runtime", 10 | "LoadingPhase": "Default" 11 | }, 12 | { 13 | "Name": "A1_GatherSubclasses", 14 | "Type": "Runtime", 15 | "LoadingPhase": "Default" 16 | }, 17 | { 18 | "Name": "A2_DetailsCustomization", 19 | "Type": "Runtime", 20 | "LoadingPhase": "Default" 21 | }, 22 | { 23 | "Name": "A2_DetailsCustomizationEditor", 24 | "Type": "Editor", 25 | "LoadingPhase": "Default" 26 | } 27 | ] 28 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Cameron Angus 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Kantan Code Examples 2 | UE4 project containing full code examples to accompany [articles on the website](http://kantandev.com/articles). 3 | Code is organised into separate modules for each article. 4 | 5 | Articles 6 | --- 7 | 1. [Finding all classes/blueprints with a given base](http://kantandev.com/articles/finding-all-classes-blueprints-with-a-given-base) 8 | 2. [Details Panel Customization](http://kantandev.com/articles/details-panel-customization) 9 | -------------------------------------------------------------------------------- /Source/A1_GatherSubclasses/Runtime/A1_GatherSubclasses.Build.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Cameron Angus 2 | 3 | using UnrealBuildTool; 4 | 5 | public class A1_GatherSubclasses : ModuleRules 6 | { 7 | public A1_GatherSubclasses(ReadOnlyTargetRules Target) : base(Target) 8 | { 9 | PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; 10 | bEnforceIWYU = false; 11 | 12 | PublicDependencyModuleNames.AddRange(new string[] { 13 | "Core", 14 | "CoreUObject", 15 | "Engine", 16 | "InputCore", 17 | 18 | "KantanCodeExamplesCommon", 19 | }); 20 | 21 | if(Target.bBuildEditor) 22 | { 23 | PrivateDependencyModuleNames.Add("UnrealEd"); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Source/A1_GatherSubclasses/Runtime/A1_GatherSubclasses.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Cameron Angus 2 | 3 | #include "A1_GatherSubclasses.h" 4 | #include "KantanCodeExamplesCommon.h" 5 | 6 | #include "AssetRegistryModule.h" 7 | #include "ARFilter.h" 8 | 9 | #if WITH_EDITOR 10 | #include "KismetEditorUtilities.h" 11 | #endif 12 | 13 | 14 | namespace A1_GatherSubclasses 15 | { 16 | namespace Detail 17 | { 18 | void GetAllNativeSubclasses(TArray< TAssetSubclassOf< UObject > >& Subclasses, TSubclassOf< UObject > Base, bool bAllowAbstract) 19 | { 20 | check(Base); 21 | 22 | if(!Base->IsNative()) 23 | { 24 | return; 25 | } 26 | 27 | for(TObjectIterator< UClass > ClassIt; ClassIt; ++ClassIt) 28 | { 29 | UClass* Class = *ClassIt; 30 | 31 | // Only interested in native C++ classes 32 | if(!Class->IsNative()) 33 | { 34 | continue; 35 | } 36 | 37 | // Ignore deprecated 38 | if(Class->HasAnyClassFlags(CLASS_Deprecated | CLASS_NewerVersionExists)) 39 | { 40 | continue; 41 | } 42 | 43 | #if WITH_EDITOR 44 | // Ignore skeleton classes (semi-compiled versions that only exist in-editor) 45 | if(FKismetEditorUtilities::IsClassABlueprintSkeleton(Class)) 46 | { 47 | continue; 48 | } 49 | #endif 50 | 51 | // Optionally ignore abstract classes 52 | if(!bAllowAbstract && Class->HasAnyClassFlags(CLASS_Abstract)) 53 | { 54 | continue; 55 | } 56 | 57 | // Check this class is a subclass of Base 58 | if(!Class->IsChildOf(Base)) 59 | { 60 | continue; 61 | } 62 | 63 | Subclasses.Add(Class); 64 | } 65 | } 66 | 67 | void GetAllBlueprintSubclasses(TArray< TAssetSubclassOf< UObject > >& Subclasses, TSubclassOf< UObject > Base, bool bAllowAbstract, FString const& Path) 68 | { 69 | /* 70 | For blueprint classes, things are complicated by the fact that the UClass may not have been loaded into memory yet. 71 | The approach taken here is a bit more complicated than it has to be, but allows us to gather the list of subclasses 72 | without force loading anything. 73 | */ 74 | 75 | static const FName GeneratedClassTag = TEXT("GeneratedClass"); 76 | static const FName ClassFlagsTag = TEXT("ClassFlags"); 77 | 78 | check(Base); 79 | 80 | // Load the asset registry module 81 | FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked< FAssetRegistryModule >(FName("AssetRegistry")); 82 | IAssetRegistry& AssetRegistry = AssetRegistryModule.Get(); 83 | // The asset registry is populated asynchronously at startup, so there's no guarantee it has finished. 84 | // This simple approach just runs a synchronous scan on the entire content directory. 85 | // Better solutions would be to specify only the path to where the relevant blueprints are, 86 | // or to register a callback with the asset registry to be notified of when it's finished populating. 87 | TArray< FString > ContentPaths; 88 | ContentPaths.Add(TEXT("/Game")); 89 | AssetRegistry.ScanPathsSynchronous(ContentPaths); 90 | 91 | FName BaseClassName = Base->GetFName(); 92 | 93 | // Use the asset registry to get the set of all class names deriving from Base 94 | TSet< FName > DerivedNames; 95 | { 96 | TArray< FName > BaseNames; 97 | BaseNames.Add(BaseClassName); 98 | 99 | TSet< FName > Excluded; 100 | AssetRegistry.GetDerivedClassNames(BaseNames, Excluded, DerivedNames); 101 | } 102 | 103 | // Set up a filter and then pull asset data for all blueprints in the specified path from the asset registry. 104 | // Note that this works in packaged builds too. Even though the blueprint itself cannot be loaded, its asset data 105 | // still exists and is tied to the UBlueprint type. 106 | FARFilter Filter; 107 | Filter.ClassNames.Add(UBlueprint::StaticClass()->GetFName()); 108 | Filter.bRecursiveClasses = true; 109 | if(!Path.IsEmpty()) 110 | { 111 | Filter.PackagePaths.Add(*Path); 112 | } 113 | Filter.bRecursivePaths = true; 114 | 115 | TArray< FAssetData > AssetList; 116 | AssetRegistry.GetAssets(Filter, AssetList); 117 | 118 | // Iterate over retrieved blueprint assets 119 | for(auto const& Asset : AssetList) 120 | { 121 | // Get the the class this blueprint generates (this is stored as a full path) 122 | if(auto GeneratedClassPathPtr = Asset.TagsAndValues.Find(GeneratedClassTag)) 123 | { 124 | // Optionally ignore abstract classes 125 | // As of 4.12 I do not believe blueprints can be marked as abstract, but this may change so included for completeness. 126 | if(!bAllowAbstract) 127 | { 128 | if(auto ClassFlagsPtr = Asset.TagsAndValues.Find(ClassFlagsTag)) 129 | { 130 | auto ClassFlags = FCString::Atoi(**ClassFlagsPtr); 131 | if((ClassFlags & CLASS_Abstract) != 0) 132 | { 133 | continue; 134 | } 135 | } 136 | } 137 | 138 | // Convert path to just the name part 139 | const FString ClassObjectPath = FPackageName::ExportTextPathToObjectPath(*GeneratedClassPathPtr); 140 | const FString ClassName = FPackageName::ObjectPathToObjectName(ClassObjectPath); 141 | 142 | // Check if this class is in the derived set 143 | if(!DerivedNames.Contains(*ClassName)) 144 | { 145 | continue; 146 | } 147 | 148 | // Store using the path to the generated class 149 | Subclasses.Add(TAssetSubclassOf< UObject >(FStringAssetReference(ClassObjectPath))); 150 | } 151 | } 152 | } 153 | } 154 | } 155 | 156 | 157 | void UA1_GatherSubclasses::PrintSubclasses() 158 | { 159 | if(BaseClass) 160 | { 161 | UE_LOG(LogKantanCode, Log, TEXT("[%s] Subclasses gathered with base '%s':"), *GetName(), *BaseClass->GetName()); 162 | for(auto& AssetClass : DerivedSet) 163 | { 164 | auto LoadedClass = AssetClass.LoadSynchronous(); 165 | UE_LOG(LogKantanCode, Log, TEXT("%s %s"), AssetClass.IsNull() ? TEXT("[NONE]") : *AssetClass.GetUniqueID().ToString(), !AssetClass.IsNull() && !LoadedClass ? TEXT("_LOAD FAILED_") : TEXT("")); 166 | } 167 | } 168 | } 169 | 170 | void UA1_GatherSubclasses::Gather() 171 | { 172 | DerivedSet.Empty(); 173 | 174 | if(BaseClass) 175 | { 176 | A1_GatherSubclasses::GetAllNativeSubclasses(DerivedSet, BaseClass, bAllowAbstract); 177 | A1_GatherSubclasses::GetAllBlueprintSubclasses(DerivedSet, BaseClass, bAllowAbstract, FString()); 178 | } 179 | } 180 | 181 | void UA1_GatherSubclasses::PostLoad() 182 | { 183 | Super::PostLoad(); 184 | 185 | if(!IsTemplate()) 186 | { 187 | // Gather(); 188 | } 189 | } 190 | 191 | 192 | #if WITH_EDITOR 193 | 194 | void UA1_GatherSubclasses::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) 195 | { 196 | static const FName NAME_Base = GET_MEMBER_NAME_CHECKED(UA1_GatherSubclasses, BaseClass); 197 | static const FName NAME_AllowAbstract = GET_MEMBER_NAME_CHECKED(UA1_GatherSubclasses, bAllowAbstract); 198 | 199 | if(PropertyChangedEvent.Property != nullptr) 200 | { 201 | const FName PropName = PropertyChangedEvent.MemberProperty->GetFName(); 202 | if(PropName == NAME_Base || PropName == NAME_AllowAbstract) 203 | { 204 | // Gather(); 205 | } 206 | } 207 | 208 | Super::PostEditChangeProperty(PropertyChangedEvent); 209 | } 210 | 211 | #endif 212 | 213 | -------------------------------------------------------------------------------- /Source/A1_GatherSubclasses/Runtime/A1_GatherSubclasses.h: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Cameron Angus 2 | 3 | #pragma once 4 | 5 | #include "Object.h" 6 | #include "ScriptMacros.h" 7 | #include "A1_GatherSubclasses.generated.h" 8 | 9 | 10 | namespace A1_GatherSubclasses 11 | { 12 | namespace Detail 13 | { 14 | void GetAllNativeSubclasses(TArray< TAssetSubclassOf< UObject > >& OutSubclasses, TSubclassOf< UObject > Base, bool bAllowAbstract); 15 | void GetAllBlueprintSubclasses(TArray< TAssetSubclassOf< UObject > >& OutSubclasses, TSubclassOf< UObject > Base, bool bAllowAbstract, FString const& Path); 16 | } 17 | 18 | template < typename TBase > 19 | void GetAllNativeSubclasses(TArray< TAssetSubclassOf< TBase > >& Subclasses, TSubclassOf< TBase > Base, bool bAllowAbstract) 20 | { 21 | TArray< TAssetSubclassOf< UObject > > Classes; 22 | Detail::GetAllNativeSubclasses(Classes, Base, bAllowAbstract); 23 | for(auto const& Cls : Classes) 24 | { 25 | Subclasses.Add(Cls); 26 | } 27 | } 28 | 29 | template < typename TBase > 30 | void GetAllBlueprintSubclasses(TArray< TAssetSubclassOf< TBase > >& Subclasses, TSubclassOf< TBase > Base, bool bAllowAbstract, FString const& Path) 31 | { 32 | TArray< TAssetSubclassOf< UObject > > Classes; 33 | Detail::GetAllBlueprintSubclasses(Classes, Base, bAllowAbstract, Path); 34 | for(auto const& Cls : Classes) 35 | { 36 | Subclasses.Add(Cls); 37 | } 38 | } 39 | } 40 | 41 | 42 | /** 43 | * Example class using properties to demonstrate gathering of all subclasses (native and blueprint) of a given class. 44 | */ 45 | UCLASS(Blueprintable) 46 | class UA1_GatherSubclasses: public UObject 47 | { 48 | GENERATED_BODY() 49 | 50 | public: 51 | /** The base class, for which we want to find all derived classes/blueprints */ 52 | UPROPERTY(EditAnywhere, Category = "Example", Meta = (AllowAbstract = "true")) 53 | TSubclassOf< UObject > BaseClass; 54 | 55 | /** Whether to include abstract classes in the results */ 56 | UPROPERTY(EditAnywhere, Category = "Example") 57 | bool bAllowAbstract; 58 | 59 | /** Generated results */ 60 | UPROPERTY(Transient, VisibleAnywhere, Category = "Example") 61 | TArray< TAssetSubclassOf< UObject > > DerivedSet; 62 | 63 | public: 64 | UFUNCTION(BlueprintCallable, Category = "Example") 65 | void Gather(); 66 | 67 | UFUNCTION(BlueprintCallable, Category = "Example") 68 | void PrintSubclasses(); 69 | 70 | public: 71 | virtual void PostLoad() override; 72 | 73 | #if WITH_EDITOR 74 | virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override; 75 | #endif 76 | }; 77 | 78 | 79 | -------------------------------------------------------------------------------- /Source/A1_GatherSubclasses/Runtime/A1_GatherSubclassesModule.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Cameron Angus 2 | 3 | #include "ModuleManager.h" 4 | 5 | 6 | IMPLEMENT_MODULE(FDefaultGameModuleImpl, A1_GatherSubclasses); 7 | 8 | -------------------------------------------------------------------------------- /Source/A1_GatherSubclasses/Runtime/A1_GatherSubclassesPCH.h: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Cameron Angus 2 | 3 | #pragma once 4 | 5 | #include "Engine.h" 6 | 7 | #include "KantanCodeExamplesCommon.h" 8 | -------------------------------------------------------------------------------- /Source/A2_DetailsCustomization/Editor/A2_DetailsCustomizationEditor.Build.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Cameron Angus 2 | 3 | using UnrealBuildTool; 4 | 5 | public class A2_DetailsCustomizationEditor : ModuleRules 6 | { 7 | public A2_DetailsCustomizationEditor(ReadOnlyTargetRules Target) : base(Target) 8 | { 9 | PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; 10 | bEnforceIWYU = false; 11 | 12 | PublicDependencyModuleNames.AddRange(new string[] { 13 | "Core", 14 | "CoreUObject", 15 | "Engine", 16 | "InputCore", 17 | "Slate", 18 | "SlateCore", 19 | "UnrealEd", 20 | "PropertyEditor", 21 | 22 | "KantanCodeExamplesCommon", 23 | "A2_DetailsCustomization", 24 | }); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Source/A2_DetailsCustomization/Editor/A2_DetailsCustomizationEditorModule.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Cameron Angus 2 | 3 | #include "ModuleManager.h" 4 | #include "PropertyEditorModule.h" 5 | #include "A2_MyClassCustomization.h" 6 | 7 | 8 | /* 9 | Module implementation 10 | */ 11 | class FA2EditorModule: public FDefaultGameModuleImpl 12 | { 13 | public: 14 | /** IModuleInterface implementation */ 15 | virtual void StartupModule() override; 16 | virtual void ShutdownModule() override; 17 | }; 18 | 19 | void FA2EditorModule::StartupModule() 20 | { 21 | // Register detail customizations 22 | { 23 | auto& PropertyModule = FModuleManager::LoadModuleChecked< FPropertyEditorModule >("PropertyEditor"); 24 | 25 | PropertyModule.RegisterCustomClassLayout( 26 | "A2_MyClass", 27 | FOnGetDetailCustomizationInstance::CreateStatic(&A2_DetailsCustomization::FMyClassCustomization::MakeInstance) 28 | ); 29 | 30 | PropertyModule.NotifyCustomizationModuleChanged(); 31 | } 32 | } 33 | 34 | void FA2EditorModule::ShutdownModule() 35 | { 36 | if(FModuleManager::Get().IsModuleLoaded("PropertyEditor")) 37 | { 38 | auto& PropertyModule = FModuleManager::LoadModuleChecked("PropertyEditor"); 39 | 40 | PropertyModule.UnregisterCustomClassLayout("A2_MyClass"); 41 | } 42 | } 43 | 44 | IMPLEMENT_MODULE(FA2EditorModule, A2_DetailsCustomizationEditor); 45 | 46 | -------------------------------------------------------------------------------- /Source/A2_DetailsCustomization/Editor/A2_DetailsCustomizationEditorPCH.h: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Cameron Angus 2 | 3 | #pragma once 4 | 5 | #include "Engine.h" 6 | #include "UnrealEd.h" 7 | 8 | #include "KantanCodeExamplesCommon.h" 9 | 10 | -------------------------------------------------------------------------------- /Source/A2_DetailsCustomization/Editor/A2_MyClassCustomization.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Cameron Angus 2 | 3 | #include "A2_MyClassCustomization.h" 4 | #include "A2_MyClass.h" 5 | #include "PropertyEditing.h" 6 | 7 | #define LOCTEXT_NAMESPACE "A2" 8 | 9 | 10 | namespace A2_DetailsCustomization 11 | { 12 | TSharedRef< IDetailCustomization > FMyClassCustomization::MakeInstance() 13 | { 14 | return MakeShareable(new FMyClassCustomization); 15 | } 16 | 17 | void FMyClassCustomization::CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) 18 | { 19 | TArray< TWeakObjectPtr< UObject > > Objects; 20 | DetailBuilder.GetObjectsBeingCustomized(Objects); 21 | 22 | if(Objects.Num() != 1) 23 | { 24 | return; 25 | } 26 | 27 | TWeakObjectPtr< UA2_MyClass > MyObject = Cast< UA2_MyClass >(Objects[0].Get()); 28 | 29 | auto& Cat = DetailBuilder.EditCategory(TEXT("Custom")); 30 | 31 | /* 32 | Showing a warning message about invalid property values. 33 | (Note that customizations can also be used to enforce validation on user-entered property values). 34 | */ 35 | auto OnGetWarningVisibility = [MyObject] 36 | { 37 | return MyObject.IsValid() && MyObject->BaseString.IsEmpty() ? EVisibility::Visible : EVisibility::Collapsed; 38 | }; 39 | auto WarningVisibilityAttr = TAttribute< EVisibility >::Create(TAttribute< EVisibility >::FGetter::CreateLambda(OnGetWarningVisibility)); 40 | 41 | Cat.AddCustomRow(LOCTEXT("MyWarningRowFilterString", "Search Filter Keywords")) 42 | .Visibility(WarningVisibilityAttr) 43 | .WholeRowContent() 44 | [ 45 | SNew(STextBlock) 46 | .Text(LOCTEXT("MyWarningTest", "BaseString should not be empty!")) 47 | ]; 48 | 49 | /* 50 | Displaying a button that triggers editor-time processing. 51 | */ 52 | auto OnRegenerate = [MyObject] 53 | { 54 | if(MyObject.IsValid()) 55 | { 56 | MyObject->GeneratedList.Empty(); 57 | for(int32 i = 0; i < MyObject->Count; ++i) 58 | { 59 | MyObject->GeneratedList.Add(MyObject->BaseString + TEXT("_") + FString::FromInt(i + 1)); 60 | } 61 | } 62 | 63 | return FReply::Handled(); 64 | }; 65 | 66 | Cat.AddCustomRow(LOCTEXT("MyButtonRowFilterString", "Search Filter Keywords")) 67 | .WholeRowContent().HAlign(EHorizontalAlignment::HAlign_Left) 68 | [ 69 | SNew(SButton) 70 | .Text(LOCTEXT("RegenerateBtnText", "Regenerate List")) 71 | .OnClicked_Lambda(OnRegenerate) 72 | ]; 73 | } 74 | } 75 | 76 | 77 | #undef LOCTEXT_NAMESPACE 78 | 79 | -------------------------------------------------------------------------------- /Source/A2_DetailsCustomization/Editor/A2_MyClassCustomization.h: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Cameron Angus 2 | 3 | #pragma once 4 | 5 | #include "IDetailCustomization.h" 6 | 7 | 8 | namespace A2_DetailsCustomization 9 | { 10 | class FMyClassCustomization: public IDetailCustomization 11 | { 12 | public: 13 | // IDetailCustomization interface 14 | virtual void CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) override; 15 | // 16 | 17 | static TSharedRef< IDetailCustomization > MakeInstance(); 18 | }; 19 | } 20 | 21 | -------------------------------------------------------------------------------- /Source/A2_DetailsCustomization/Runtime/A2_DetailsCustomization.Build.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Cameron Angus 2 | 3 | using UnrealBuildTool; 4 | 5 | public class A2_DetailsCustomization : ModuleRules 6 | { 7 | public A2_DetailsCustomization(ReadOnlyTargetRules Target) : base(Target) 8 | { 9 | PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; 10 | bEnforceIWYU = false; 11 | 12 | PublicDependencyModuleNames.AddRange(new string[] { 13 | "Core", 14 | "CoreUObject", 15 | "Engine", 16 | "InputCore", 17 | 18 | "KantanCodeExamplesCommon", 19 | }); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Source/A2_DetailsCustomization/Runtime/A2_DetailsCustomizationModule.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Cameron Angus 2 | 3 | #include "ModuleManager.h" 4 | 5 | 6 | IMPLEMENT_MODULE(FDefaultGameModuleImpl, A2_DetailsCustomization); 7 | 8 | -------------------------------------------------------------------------------- /Source/A2_DetailsCustomization/Runtime/A2_DetailsCustomizationPCH.h: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Cameron Angus 2 | 3 | #pragma once 4 | 5 | #include "Engine.h" 6 | 7 | #include "KantanCodeExamplesCommon.h" 8 | -------------------------------------------------------------------------------- /Source/A2_DetailsCustomization/Runtime/A2_MyClass.h: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Cameron Angus 2 | 3 | #pragma once 4 | 5 | #include "Object.h" 6 | #include "ScriptMacros.h" 7 | #include "A2_MyClass.generated.h" 8 | 9 | 10 | /** 11 | * Example class that will be customized in the editor module. 12 | */ 13 | UCLASS(Blueprintable) 14 | class A2_DETAILSCUSTOMIZATION_API UA2_MyClass: public UObject 15 | { 16 | GENERATED_BODY() 17 | 18 | public: 19 | UPROPERTY(EditAnywhere, Category = "Cat A") 20 | FString BaseString; 21 | 22 | UPROPERTY(EditAnywhere, Category = "Cat A") 23 | int32 Count; 24 | 25 | UPROPERTY(VisibleAnywhere, Category = "Cat B") 26 | TArray< FString > GeneratedList; 27 | }; 28 | 29 | 30 | -------------------------------------------------------------------------------- /Source/Common/Runtime/KantanCodeExamplesCommon.Build.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Cameron Angus 2 | 3 | using UnrealBuildTool; 4 | 5 | public class KantanCodeExamplesCommon : ModuleRules 6 | { 7 | public KantanCodeExamplesCommon(ReadOnlyTargetRules Target): base(Target) 8 | { 9 | PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; 10 | bEnforceIWYU = false; 11 | 12 | PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore" }); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Source/Common/Runtime/KantanCodeExamplesCommon.h: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Cameron Angus 2 | 3 | #pragma once 4 | 5 | 6 | KANTANCODEEXAMPLESCOMMON_API DECLARE_LOG_CATEGORY_EXTERN(LogKantanCode, Log, All); 7 | 8 | -------------------------------------------------------------------------------- /Source/Common/Runtime/KantanCodeExamplesCommonPCH.h: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Cameron Angus 2 | 3 | #pragma once 4 | 5 | #include "Engine.h" 6 | #include "KantanCodeExamplesCommon.h" 7 | 8 | -------------------------------------------------------------------------------- /Source/Common/Runtime/KantanCodeExamplesModule.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Cameron Angus 2 | 3 | #include "KantanCodeExamplesCommon.h" 4 | 5 | 6 | IMPLEMENT_PRIMARY_GAME_MODULE( FDefaultGameModuleImpl, KantanCodeExamplesCommon, "KantanCodeExamplesCommon" ); 7 | 8 | DEFINE_LOG_CATEGORY(LogKantanCode); 9 | 10 | -------------------------------------------------------------------------------- /Source/KantanCodeExamples.Target.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Cameron Angus 2 | 3 | using UnrealBuildTool; 4 | using System.Collections.Generic; 5 | 6 | public class KantanCodeExamplesTarget : TargetRules 7 | { 8 | public KantanCodeExamplesTarget(TargetInfo Target): base(Target) 9 | { 10 | Type = TargetType.Game; 11 | 12 | ExtraModuleNames.AddRange( new string[] { 13 | "KantanCodeExamplesCommon", 14 | 15 | "A1_GatherSubclasses", 16 | 17 | "A2_DetailsCustomization", 18 | }); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Source/KantanCodeExamplesEditor.Target.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Cameron Angus 2 | 3 | using UnrealBuildTool; 4 | using System.Collections.Generic; 5 | 6 | public class KantanCodeExamplesEditorTarget : TargetRules 7 | { 8 | public KantanCodeExamplesEditorTarget(TargetInfo Target): base(Target) 9 | { 10 | Type = TargetType.Editor; 11 | 12 | ExtraModuleNames.AddRange( new string[] { 13 | "KantanCodeExamplesCommon", 14 | 15 | "A1_GatherSubclasses", 16 | 17 | "A2_DetailsCustomization", 18 | "A2_DetailsCustomizationEditor", 19 | }); 20 | } 21 | } 22 | --------------------------------------------------------------------------------