SStateTypePropertyMultibind.cpp
1 // Copyright (C) 2025 Radaway Software LLC. All Rights Reserved. 2 3 #include "Slate/SStateTypePropertyMultibind.h" 4 #include "PropertyEditing.h" 5 #include "ClassViewerFilter.h" 6 #include "ClassViewerModule.h" 7 #include "StructViewerModule.h" 8 #include "StructViewerFilter.h" 9 #include "PropertyCustomizationHelpers.h" 10 #include "Core/RpaiTypes.h" 11 #include "SlateCore.h" 12 13 #define LOCTEXT_NAMESPACE "ReasonablePlanningAIEditor" 14 15 class FClassStructViewerFilter : public IClassViewerFilter, public IStructViewerFilter 16 { 17 private: 18 TArray<UClass*> AllowedClasses; 19 20 public: 21 static constexpr EClassFlags DisallowedClassFlags = CLASS_Deprecated | CLASS_Abstract; 22 23 FClassStructViewerFilter() 24 : AllowedClasses() 25 { 26 27 } 28 29 FClassStructViewerFilter(const TArray<UClass*>& InAllowedClasses) 30 : AllowedClasses(InAllowedClasses) 31 { 32 33 } 34 35 //~ Begin IClassViewerFilter 36 virtual bool IsClassAllowed(const FClassViewerInitializationOptions&, const UClass* InClass, TSharedRef<FClassViewerFilterFuncs>) override 37 { 38 if (InClass && !InClass->HasAnyClassFlags(DisallowedClassFlags)) 39 { 40 for (const UClass* C : AllowedClasses) 41 { 42 if (InClass->IsChildOf(C)) 43 { 44 return true; 45 } 46 } 47 return false; 48 } 49 return false; 50 } 51 virtual bool IsUnloadedClassAllowed(const FClassViewerInitializationOptions&, const TSharedRef<const IUnloadedBlueprintData> InUnloadedClassData, TSharedRef<FClassViewerFilterFuncs>) override 52 { 53 if (!InUnloadedClassData->HasAnyClassFlags(DisallowedClassFlags)) 54 { 55 for (const UClass* C : AllowedClasses) 56 { 57 if (InUnloadedClassData->IsChildOf(C)) 58 { 59 return true; 60 } 61 } 62 return false; 63 } 64 return false; 65 } 66 //~ End IClassViewerFilter 67 68 //~ Begin IStructViewerFilter 69 virtual bool IsStructAllowed(const FStructViewerInitializationOptions& InInitOptions, const UScriptStruct* InStruct, TSharedRef<class FStructViewerFilterFuncs> InFilterFuncs) override 70 { 71 return true; 72 } 73 74 virtual bool IsUnloadedStructAllowed(const FStructViewerInitializationOptions& InInitOptions, const FSoftObjectPath& InStructPath, TSharedRef<class FStructViewerFilterFuncs> InFilterFuncs) override 75 { 76 return true; 77 } 78 //~ End IStructViewerFilter 79 }; 80 81 FString SStateTypePropertyMultibind::GetBoundPropertyName (TSharedPtr<IPropertyHandle> Element) 82 { 83 void* OutValue = nullptr; 84 if (Element->GetValueData (OutValue) == FPropertyAccess::Success) 85 { 86 FCachedPropertyPath* PropertyPath = static_cast<FCachedPropertyPath*>(OutValue); 87 return PropertyPath->ToString (); 88 } 89 return FString (); 90 } 91 92 BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION 93 void SStateTypePropertyMultibind::Construct(const FArguments& InArgs) 94 { 95 AllowedClasses = InArgs._AllowedClasses; 96 StructPropertyHandle = InArgs._StructPropertyHandle; 97 98 if (StructPropertyHandle.IsValid()) 99 { 100 TSharedPtr<IPropertyHandle> BoundProps = StructPropertyHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FRpaiStateTypePropertyMultiBind, BoundProperties)); 101 TSharedPtr<IPropertyHandle> TargetBindingClassHandle = StructPropertyHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FRpaiStateTypePropertyMultiBind, TargetBindingClass)); 102 103 check(TargetBindingClassHandle.IsValid()); 104 check(BoundProps.IsValid()); 105 106 TSharedPtr<IPropertyHandleArray> BoundPropsArray = BoundProps->AsArray(); 107 check(BoundPropsArray.IsValid()); 108 109 TSharedRef<SWidget> AddButton = PropertyCustomizationHelpers::MakeAddButton( 110 FSimpleDelegate::CreateSP(this, &SStateTypePropertyMultibind::AddBindingButton_OnClick, BoundPropsArray), 111 TAttribute<FText>(this, &SStateTypePropertyMultibind::GetAddNewBindingTooltip), 112 TAttribute<bool>(this, &SStateTypePropertyMultibind::CanAddNewBinding) 113 ); 114 TSharedRef<SWidget> ClearButton = PropertyCustomizationHelpers::MakeClearButton( 115 FSimpleDelegate::CreateSP(this, &SStateTypePropertyMultibind::ClearBindingButton_OnClick, BoundPropsArray), 116 TAttribute<FText>(this, &SStateTypePropertyMultibind::GetClearBindingTooltip) 117 ); 118 119 FUIAction CopyAction; 120 FUIAction PasteAction; 121 BoundProps->CreateDefaultPropertyCopyPasteActions(CopyAction, PasteAction); 122 123 ChildSlot 124 [ 125 SNew(SHorizontalBox) 126 + SHorizontalBox::Slot() 127 [ 128 SAssignNew(ClassPickerComboButton, SComboButton) 129 .ContentPadding(1) 130 .OnGetMenuContent(this, &SStateTypePropertyMultibind::GenerateClassPicker, TargetBindingClassHandle) 131 .ButtonContent() 132 [ 133 SNew(STextBlock) 134 .Text(LOCTEXT("RpaiPickClass", "Actor")) 135 ] 136 ] 137 + SHorizontalBox::Slot() 138 .Padding(2.0f) 139 .HAlign(HAlign_Center) 140 .VAlign(VAlign_Center) 141 .AutoWidth() 142 [ 143 AddButton 144 ] 145 + SHorizontalBox::Slot() 146 .Padding(2.0f) 147 .HAlign(HAlign_Center) 148 .VAlign(VAlign_Center) 149 .AutoWidth() 150 [ 151 ClearButton 152 ] 153 ]; 154 } 155 } 156 END_SLATE_FUNCTION_BUILD_OPTIMIZATION 157 158 void SStateTypePropertyMultibind::OnClassPicked(UClass* InClassPicked, TSharedPtr<IPropertyHandle> Property) 159 { 160 // SetValue does not support UClass -> UStruct conversions even though they are correct C++ side. 161 // Raw Ptr nonsense it is, don't break! 162 void* ContainerAddress; 163 if (StructPropertyHandle->GetValueData(ContainerAddress) == FPropertyAccess::Success && Property->IsValidHandle()) 164 { 165 FRpaiStateTypePropertyMultiBind* Container = static_cast<FRpaiStateTypePropertyMultiBind*>(ContainerAddress); 166 Property->NotifyPreChange(); 167 Container->TargetBindingClass = InClassPicked; 168 Property->NotifyPostChange(EPropertyChangeType::ValueSet); 169 StructPropertyHandle->RequestRebuildChildren(); 170 } 171 ClassPickerComboButton->SetIsOpen(false); 172 } 173 174 TSharedRef<SWidget> SStateTypePropertyMultibind::GenerateClassPicker( TSharedPtr<IPropertyHandle> Property) 175 { 176 TSharedRef<FClassStructViewerFilter> ViewerFilter = MakeShared<FClassStructViewerFilter>(AllowedClasses); 177 178 FClassViewerInitializationOptions ClassPickerOptions; 179 ClassPickerOptions.ClassFilters.Add(ViewerFilter); 180 ClassPickerOptions.PropertyHandle = Property; 181 ClassPickerOptions.bShowBackgroundBorder = false; 182 ClassPickerOptions.bShowUnloadedBlueprints = true; 183 ClassPickerOptions.bShowNoneOption = true; 184 185 FOnClassPicked OnClassPickedDelegate(FOnClassPicked::CreateSP(this, &SStateTypePropertyMultibind::OnClassPicked, Property)); 186 187 return FModuleManager::LoadModuleChecked<FClassViewerModule>("ClassViewer").CreateClassViewer(ClassPickerOptions, OnClassPickedDelegate); 188 } 189 190 void SStateTypePropertyMultibind::AddBindingButton_OnClick(TSharedPtr<IPropertyHandleArray> Property) 191 { 192 FPropertyAccess::Result Result = Property->AddItem(); 193 if (Result == FPropertyAccess::Success) 194 { 195 StructPropertyHandle->RequestRebuildChildren(); 196 } 197 } 198 199 void SStateTypePropertyMultibind::ClearBindingButton_OnClick(TSharedPtr<IPropertyHandleArray> Property) 200 { 201 FPropertyAccess::Result Result = Property->EmptyArray(); 202 if (Result == FPropertyAccess::Success) 203 { 204 StructPropertyHandle->RequestRebuildChildren(); 205 } 206 } 207 208 FText SStateTypePropertyMultibind::GetAddNewBindingTooltip() const 209 { 210 return LOCTEXT("RpaiAddNewBindingButton", "Add New Binding"); 211 } 212 213 bool SStateTypePropertyMultibind::CanAddNewBinding() const 214 { 215 return true; 216 } 217 218 FText SStateTypePropertyMultibind::GetClearBindingTooltip() const 219 { 220 return LOCTEXT("RpaiClearBindingsButton", "Clear Bindings"); 221 }