/ Source / ReasonablePlanningAIEditor / Private / Slate / SStateTypePropertyMultibind.cpp
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  }