Files
UnrealEngine/Engine/Source/Editor/Kismet/Private/UserDefinedStructureEditor.cpp
2025-05-18 13:04:45 +08:00

1377 lines
46 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "UserDefinedStructureEditor.h"
#include "Containers/Array.h"
#include "Containers/EnumAsByte.h"
#include "Delegates/Delegate.h"
#include "DetailCategoryBuilder.h"
#include "DetailLayoutBuilder.h"
#include "DetailWidgetRow.h"
#include "DetailsViewArgs.h"
#include "DragAndDrop/DecoratedDragDropOp.h"
#include "EdGraph/EdGraphNode.h"
#include "EdGraphSchema_K2.h"
#include "StructUtils/UserDefinedStruct.h"
#include "Fonts/SlateFontInfo.h"
#include "Framework/Docking/TabManager.h"
#include "Framework/MultiBox/MultiBoxBuilder.h"
#include "Framework/MultiBox/MultiBoxExtender.h"
#include "Framework/Notifications/NotificationManager.h"
#include "GenericPlatform/ICursor.h"
#include "HAL/PlatformCrt.h"
#include "HAL/PlatformMath.h"
#include "IDetailChildrenBuilder.h"
#include "IDetailCustomNodeBuilder.h"
#include "IDetailCustomization.h"
#include "IDetailDragDropHandler.h"
#include "IDetailsView.h"
#include "Input/DragAndDrop.h"
#include "Internationalization/Internationalization.h"
#include "Kismet2/BlueprintEditorUtils.h"
#include "Kismet2/StructureEditorUtils.h"
#include "Layout/Margin.h"
#include "Layout/Visibility.h"
#include "Misc/AssertionMacros.h"
#include "Misc/Attribute.h"
#include "Misc/Guid.h"
#include "Misc/NotifyHook.h"
#include "Misc/Optional.h"
#include "Modules/ModuleManager.h"
#include "PropertyCustomizationHelpers.h"
#include "PropertyEditorDelegates.h"
#include "PropertyEditorModule.h"
#include "SPinTypeSelector.h"
#include "SPositiveActionButton.h"
#include "SlotBase.h"
#include "Styling/AppStyle.h"
#include "Styling/ISlateStyle.h"
#include "Styling/SlateTypes.h"
#include "Styling/ToolBarStyle.h"
#include "Templates/Casts.h"
#include "Textures/SlateIcon.h"
#include "Toolkits/AssetEditorToolkit.h"
#include "Types/SlateEnums.h"
#include "UObject/Class.h"
#include "UObject/StructOnScope.h"
#include "UObject/UObjectGlobals.h"
#include "UObject/UnrealNames.h"
#include "UObject/UnrealType.h"
#include "UserDefinedStructure/UserDefinedStructEditorData.h"
#include "Widgets/DeclarativeSyntaxSupport.h"
#include "Widgets/Docking/SDockTab.h"
#include "Widgets/Images/SImage.h"
#include "Widgets/Images/SLayeredImage.h"
#include "Widgets/Input/SCheckBox.h"
#include "Widgets/Input/SEditableTextBox.h"
#include "Widgets/Layout/SBox.h"
#include "Widgets/Notifications/SNotificationList.h"
#include "Widgets/SBoxPanel.h"
#include "Widgets/Text/STextBlock.h"
#include "Widgets/Views/STableRow.h"
class UObject;
struct FSlateBrush;
#define LOCTEXT_NAMESPACE "StructureEditor"
///////////////////////////////////////////////////////////////////////////////////////
// FDefaultValueDetails
class FDefaultValueDetails : public IDetailCustomization
{
public:
/** Makes a new instance of this detail layout class for a specific detail view requesting it */
static TSharedRef<IDetailCustomization> MakeInstance(TWeakPtr<class FStructureDefaultValueView> InDefaultValueView, TSharedPtr<FStructOnScope> InStructData)
{
return MakeShareable(new FDefaultValueDetails(InDefaultValueView, InStructData));
}
FDefaultValueDetails(TWeakPtr<class FStructureDefaultValueView> InDefaultValueView, TSharedPtr<FStructOnScope> InStructData)
: DefaultValueView(InDefaultValueView)
, StructData(InStructData)
{}
~FDefaultValueDetails()
{
}
/** IDetailCustomization interface */
virtual void CustomizeDetails(class IDetailLayoutBuilder& DetailLayout) override;
/** Callback when finished changing properties to export the default value from the property to where strings are stored */
void OnFinishedChangingProperties(const FPropertyChangedEvent& PropertyChangedEvent);
private:
TWeakObjectPtr<UUserDefinedStruct> UserDefinedStruct;
TWeakPtr<class FStructureDefaultValueView> DefaultValueView;
TSharedPtr<FStructOnScope> StructData;
IDetailLayoutBuilder* DetailLayoutPtr;
};
void FDefaultValueDetails::CustomizeDetails(class IDetailLayoutBuilder& DetailLayout)
{
DetailLayoutPtr = &DetailLayout;
const TArray<TWeakObjectPtr<UObject>>& Objects = DetailLayout.GetSelectedObjects();
check(Objects.Num() > 0);
if (Objects.Num() == 1)
{
UserDefinedStruct = CastChecked<UUserDefinedStruct>(Objects[0].Get());
if (TSharedPtr<const IDetailsView> DetailsView = DetailLayout.GetDetailsViewSharedPtr())
{
DetailsView->OnFinishedChangingProperties().AddSP(this, &FDefaultValueDetails::OnFinishedChangingProperties);
}
IDetailCategoryBuilder& StructureCategory = DetailLayout.EditCategory("DefaultValues", LOCTEXT("DefaultValues", "Default Values"));
for (TFieldIterator<FProperty> PropertyIter(UserDefinedStruct.Get()); PropertyIter; ++PropertyIter)
{
StructureCategory.AddExternalStructureProperty(StructData, (*PropertyIter)->GetFName());
}
}
}
/////////////////////////////////
// FStructureDefaultValueView
class FStructureDefaultValueView : public FStructureEditorUtils::INotifyOnStructChanged, public TSharedFromThis<FStructureDefaultValueView>, public FNotifyHook
{
public:
FStructureDefaultValueView(UUserDefinedStruct* EditedStruct)
: UserDefinedStruct(EditedStruct)
, PropertyChangeRecursionGuard(0)
{
}
void Initialize()
{
StructData = MakeShareable(new FStructOnScope(UserDefinedStruct.Get()));
UserDefinedStruct.Get()->InitializeDefaultValue(StructData->GetStructMemory());
StructData->SetPackage(UserDefinedStruct->GetOutermost());
FPropertyEditorModule& PropertyModule = FModuleManager::LoadModuleChecked<FPropertyEditorModule>("PropertyEditor");
FDetailsViewArgs ViewArgs;
ViewArgs.bAllowSearch = false;
ViewArgs.bHideSelectionTip = false;
ViewArgs.NameAreaSettings = FDetailsViewArgs::HideNameArea;
ViewArgs.NotifyHook = this;
DetailsView = PropertyModule.CreateDetailView(ViewArgs);
TWeakPtr< FStructureDefaultValueView > LocalWeakThis = SharedThis(this);
FOnGetDetailCustomizationInstance LayoutStructDetails = FOnGetDetailCustomizationInstance::CreateStatic(&FDefaultValueDetails::MakeInstance, LocalWeakThis, StructData);
DetailsView->RegisterInstancedCustomPropertyLayout(UUserDefinedStruct::StaticClass(), LayoutStructDetails);
DetailsView->SetObject(UserDefinedStruct.Get());
}
virtual ~FStructureDefaultValueView()
{
}
UUserDefinedStruct* GetUserDefinedStruct()
{
return UserDefinedStruct.Get();
}
TSharedPtr<class SWidget> GetWidget()
{
return DetailsView;
}
virtual void PreChange(const class UUserDefinedStruct* Struct, FStructureEditorUtils::EStructureEditorChangeInfo Info) override
{
// No need to destroy the struct data if only the default values are changing
if (Info != FStructureEditorUtils::DefaultValueChanged)
{
StructData->Destroy();
DetailsView->SetObject(nullptr);
DetailsView->OnFinishedChangingProperties().Clear();
}
}
virtual void PostChange(const class UUserDefinedStruct* Struct, FStructureEditorUtils::EStructureEditorChangeInfo Info) override
{
// If change is due to default value, then struct data was not destroyed (see PreChange) and therefore does not need to be re-initialized
if (Info != FStructureEditorUtils::DefaultValueChanged)
{
StructData->Initialize(UserDefinedStruct.Get());
// Force the set object call because we may be called multiple times in a row if more than one struct was changed at the same time
DetailsView->SetObject(UserDefinedStruct.Get(), true);
}
UserDefinedStruct.Get()->InitializeDefaultValue(StructData->GetStructMemory());
}
// FNotifyHook interface
virtual void NotifyPreChange( FProperty* PropertyAboutToChange ) override
{
++PropertyChangeRecursionGuard;
}
virtual void NotifyPostChange( const FPropertyChangedEvent& PropertyChangedEvent, FProperty* PropertyThatChanged) override
{
--PropertyChangeRecursionGuard;
}
// End of FNotifyHook interface
/** Returns TRUE when property changes are complete, according to recursion counts */
bool IsPropertyChangeComplete() { return PropertyChangeRecursionGuard == 0; }
private:
/** Struct on scope data that is being viewed in the details panel */
TSharedPtr<FStructOnScope> StructData;
/** Details view being used for viewing the struct */
TSharedPtr<class IDetailsView> DetailsView;
/** User defined struct that is being represented */
const TWeakObjectPtr<UUserDefinedStruct> UserDefinedStruct;
/** Manages recursion in property changing, to ensure we only compile the structure when all properties are done changing */
int32 PropertyChangeRecursionGuard;
};
///////////////////////////////////////////////////////////////////////////////////////
// FDefaultValueDetails
void FDefaultValueDetails::OnFinishedChangingProperties(const FPropertyChangedEvent& PropertyChangedEvent)
{
if (DefaultValueView.Pin()->IsPropertyChangeComplete())
{
UStruct* OwnerStruct = PropertyChangedEvent.MemberProperty->GetOwnerStruct();
check(PropertyChangedEvent.MemberProperty && OwnerStruct);
if ( ensure(OwnerStruct == UserDefinedStruct.Get()) )
{
const FProperty* DirectProperty = PropertyChangedEvent.MemberProperty;
while (DirectProperty && !DirectProperty->GetOwner<const UUserDefinedStruct>())
{
DirectProperty = DirectProperty->GetOwner<const FProperty>();
}
ensure(nullptr != DirectProperty);
if (DirectProperty)
{
FString DefaultValueString;
bool bDefaultValueSet = false;
{
if (StructData.IsValid() && StructData->IsValid())
{
bDefaultValueSet = FBlueprintEditorUtils::PropertyValueToString(DirectProperty, StructData->GetStructMemory(), DefaultValueString, OwnerStruct);
}
}
const FGuid VarGuid = FStructureEditorUtils::GetGuidForProperty(DirectProperty);
if (bDefaultValueSet && VarGuid.IsValid())
{
FStructureEditorUtils::ChangeVariableDefaultValue(UserDefinedStruct.Get(), VarGuid, DefaultValueString);
}
}
}
}
}
///////////////////////////////////////////////////////////////////////////////////////
// FUserDefinedStructureDetails
class FUserDefinedStructureDetails : public IDetailCustomization, FStructureEditorUtils::INotifyOnStructChanged
{
public:
/** Makes a new instance of this detail layout class for a specific detail view requesting it */
static TSharedRef<IDetailCustomization> MakeInstance(TWeakPtr<FUserDefinedStructureEditor> InStructureEditor)
{
return MakeShareable(new FUserDefinedStructureDetails(InStructureEditor));
}
FUserDefinedStructureDetails(TWeakPtr<FUserDefinedStructureEditor> InStructureEditor)
: StructureEditor(InStructureEditor)
{
}
~FUserDefinedStructureDetails()
{
}
UUserDefinedStruct* GetUserDefinedStruct()
{
return UserDefinedStruct.Get();
}
struct FStructVariableDescription* FindStructureFieldByGuid(FGuid Guid)
{
if (auto Struct = GetUserDefinedStruct())
{
return FStructureEditorUtils::GetVarDesc(Struct).FindByPredicate(FStructureEditorUtils::FFindByGuidHelper<FStructVariableDescription>(Guid));
}
return NULL;
}
const TWeakPtr<FUserDefinedStructureEditor>& GetStructureEditor() const
{
return StructureEditor;
}
/** IDetailCustomization interface */
virtual void CustomizeDetails(class IDetailLayoutBuilder& DetailLayout) override;
/** FStructureEditorUtils::INotifyOnStructChanged */
virtual void PreChange(const class UUserDefinedStruct* Struct, FStructureEditorUtils::EStructureEditorChangeInfo Info) override {}
virtual void PostChange(const class UUserDefinedStruct* Struct, FStructureEditorUtils::EStructureEditorChangeInfo Info) override;
private:
TWeakObjectPtr<UUserDefinedStruct> UserDefinedStruct;
TSharedPtr<class FUserDefinedStructureLayout> Layout;
TWeakPtr<FUserDefinedStructureEditor> StructureEditor;
};
///////////////////////////////////////////////////////////////////////////////////////
// FUserDefinedStructureEditor
const FName FUserDefinedStructureEditor::MemberVariablesTabId( TEXT( "UserDefinedStruct_MemberVariablesEditor" ) );
const FName FUserDefinedStructureEditor::DefaultValuesTabId(TEXT("UserDefinedStruct_DefaultValuesEditor"));
const FName FUserDefinedStructureEditor::UserDefinedStructureEditorAppIdentifier( TEXT( "UserDefinedStructEditorApp" ) );
void FUserDefinedStructureEditor::RegisterTabSpawners(const TSharedRef<class FTabManager>& InTabManager)
{
WorkspaceMenuCategory = InTabManager->AddLocalWorkspaceMenuCategory(LOCTEXT("WorkspaceMenu_UserDefinedStructureEditor", "User-Defined Structure Editor"));
FAssetEditorToolkit::RegisterTabSpawners(InTabManager);
InTabManager->RegisterTabSpawner( MemberVariablesTabId, FOnSpawnTab::CreateSP(this, &FUserDefinedStructureEditor::SpawnStructureTab) )
.SetDisplayName( LOCTEXT("MemberVariablesEditor", "Structure") )
.SetGroup(WorkspaceMenuCategory.ToSharedRef())
.SetIcon(FSlateIcon(FAppStyle::Get().GetStyleSetName(), "Kismet.Tabs.Variables"));
InTabManager->RegisterTabSpawner( DefaultValuesTabId, FOnSpawnTab::CreateSP(this, &FUserDefinedStructureEditor::SpawnStructureDefaultValuesTab))
.SetDisplayName(LOCTEXT("DefaultValuesEditor", "Default Values"))
.SetGroup(WorkspaceMenuCategory.ToSharedRef())
.SetIcon(FSlateIcon(FAppStyle::Get().GetStyleSetName(), "Icons.Details"));
}
void FUserDefinedStructureEditor::UnregisterTabSpawners(const TSharedRef<class FTabManager>& InTabManager)
{
FAssetEditorToolkit::UnregisterTabSpawners(InTabManager);
InTabManager->UnregisterTabSpawner( MemberVariablesTabId );
}
void FUserDefinedStructureEditor::InitEditor(const EToolkitMode::Type Mode, const TSharedPtr< class IToolkitHost >& InitToolkitHost, UUserDefinedStruct* Struct)
{
UserDefinedStruct = Struct;
InitialPinType = FEdGraphPinType(UEdGraphSchema_K2::PC_Boolean, NAME_None, nullptr, EPinContainerType::None, false, FEdGraphTerminalType());
const TSharedRef<FTabManager::FLayout> StandaloneDefaultLayout = FTabManager::NewLayout( "Standalone_UserDefinedStructureEditor_Layout_v3" )
->AddArea
(
FTabManager::NewPrimaryArea()
->SetOrientation(Orient_Vertical)
->Split
(
FTabManager::NewSplitter()
->Split
(
FTabManager::NewStack()
->AddTab( MemberVariablesTabId, ETabState::OpenedTab )
->AddTab( DefaultValuesTabId, ETabState::OpenedTab )
->SetForegroundTab(MemberVariablesTabId)
)
)
);
const bool bCreateDefaultStandaloneMenu = true;
const bool bCreateDefaultToolbar = true;
FAssetEditorToolkit::InitAssetEditor( Mode, InitToolkitHost, UserDefinedStructureEditorAppIdentifier, StandaloneDefaultLayout, bCreateDefaultStandaloneMenu, bCreateDefaultToolbar, Struct );
TSharedPtr<FExtender> Extender = MakeShared<FExtender>();
Extender->AddToolBarExtension("Asset", EExtensionHook::After, GetToolkitCommands(),
FToolBarExtensionDelegate::CreateSP(this, &FUserDefinedStructureEditor::FillToolbar));
AddToolbarExtender(Extender);
RegenerateMenusAndToolbars();
}
void FUserDefinedStructureEditor::SetInitialPinType(FEdGraphPinType PinType)
{
InitialPinType = PinType;
}
FUserDefinedStructureEditor::~FUserDefinedStructureEditor()
{
}
FName FUserDefinedStructureEditor::GetToolkitFName() const
{
return FName("UserDefinedStructureEditor");
}
FText FUserDefinedStructureEditor::GetBaseToolkitName() const
{
return LOCTEXT( "AppLabel", "Struct Editor" );
}
FText FUserDefinedStructureEditor::GetToolkitName() const
{
if (1 == GetEditingObjects().Num())
{
return FAssetEditorToolkit::GetToolkitName();
}
return GetBaseToolkitName();
}
FText FUserDefinedStructureEditor::GetToolkitToolTipText() const
{
if (1 == GetEditingObjects().Num())
{
return FAssetEditorToolkit::GetToolkitToolTipText();
}
return GetBaseToolkitName();
}
FString FUserDefinedStructureEditor::GetWorldCentricTabPrefix() const
{
return LOCTEXT("UDStructWorldCentricTabPrefix", "Struct ").ToString();
}
FLinearColor FUserDefinedStructureEditor::GetWorldCentricTabColorScale() const
{
return FLinearColor( 0.0f, 0.0f, 1.0f, 0.5f );
}
TSharedRef<SDockTab> FUserDefinedStructureEditor::SpawnStructureTab(const FSpawnTabArgs& Args)
{
check( Args.GetTabId() == MemberVariablesTabId );
UUserDefinedStruct* EditedStruct = NULL;
const auto& EditingObjs = GetEditingObjects();
if (EditingObjs.Num())
{
EditedStruct = Cast<UUserDefinedStruct>(EditingObjs[ 0 ]);
}
// Create a property view
FPropertyEditorModule& EditModule = FModuleManager::Get().GetModuleChecked<FPropertyEditorModule>("PropertyEditor");
FDetailsViewArgs DetailsViewArgs;
DetailsViewArgs.bAllowSearch = false;
DetailsViewArgs.NameAreaSettings = FDetailsViewArgs::HideNameArea;
DetailsViewArgs.bHideSelectionTip = true;
DetailsViewArgs.bShowOptions = false;
PropertyView = EditModule.CreateDetailView(DetailsViewArgs);
TWeakPtr<FUserDefinedStructureEditor> LocalWeakThis = SharedThis(this);
FOnGetDetailCustomizationInstance LayoutStructDetails = FOnGetDetailCustomizationInstance::CreateStatic(&FUserDefinedStructureDetails::MakeInstance, LocalWeakThis);
PropertyView->RegisterInstancedCustomPropertyLayout(UUserDefinedStruct::StaticClass(), LayoutStructDetails);
PropertyView->SetObject(EditedStruct);
return SNew(SDockTab)
.Label( LOCTEXT("UserDefinedStructureEditor", "Structure") )
.TabColorScale( GetTabColorScale() )
[
PropertyView.ToSharedRef()
];
}
TSharedRef<SDockTab> FUserDefinedStructureEditor::SpawnStructureDefaultValuesTab(const FSpawnTabArgs& Args)
{
check(Args.GetTabId() == DefaultValuesTabId);
UUserDefinedStruct* EditedStruct = NULL;
const auto& EditingObjs = GetEditingObjects();
if (EditingObjs.Num())
{
EditedStruct = Cast<UUserDefinedStruct>(EditingObjs[0]);
}
DefaultValueView = MakeShareable(new FStructureDefaultValueView(EditedStruct));
DefaultValueView->Initialize();
return SNew(SDockTab)
.Label(LOCTEXT("UserDefinedStructureDefaultValuesEditor", "Default Values"))
.TabColorScale(GetTabColorScale())
[
DefaultValueView->GetWidget().ToSharedRef()
];
}
void FUserDefinedStructureEditor::FillToolbar(FToolBarBuilder& ToolbarBuilder)
{
const FToolBarStyle& ToolBarStyle = ToolbarBuilder.GetStyleSet()->GetWidgetStyle<FToolBarStyle>(ToolbarBuilder.GetStyleName());
ToolbarBuilder.BeginSection("UserDefinedStructure");
TSharedPtr<SLayeredImage> CompileStatusImage;
ToolbarBuilder.AddWidget(
SNew(SBox)
.HAlign(HAlign_Center)
.VAlign(VAlign_Center)
.Padding(ToolBarStyle.ButtonPadding)
[
SAssignNew(CompileStatusImage, SLayeredImage)
.Image(FAppStyle::Get().GetBrush("Blueprint.CompileStatus.Background"))
.ToolTipText(this, &FUserDefinedStructureEditor::OnGetStatusTooltip)
]);
CompileStatusImage->AddLayer(TAttribute<const FSlateBrush*>::CreateSP(this, &FUserDefinedStructureEditor::OnGetStructureStatus));
ToolbarBuilder.AddWidget(
SNew(SBox)
.HAlign(HAlign_Center)
.VAlign(VAlign_Fill)
.Padding(ToolBarStyle.ButtonPadding)
[
SNew(SPositiveActionButton)
.Text(LOCTEXT("AddStructVariable", "Add Variable"))
.ToolTipText(LOCTEXT("AddStructVariableToolTip", "Adds a new member variable to the end of this structure"))
.OnClicked(this, &FUserDefinedStructureEditor::OnAddNewField)
]);
ToolbarBuilder.EndSection();
}
FReply FUserDefinedStructureEditor::OnAddNewField()
{
if (UserDefinedStruct.IsValid())
{
FStructureEditorUtils::AddVariable(UserDefinedStruct.Get(), InitialPinType);
// Ensure the member variables tab is topmost so the user can edit the newly-added variable
InvokeTab(MemberVariablesTabId);
}
return FReply::Handled();
}
const FSlateBrush* FUserDefinedStructureEditor::OnGetStructureStatus() const
{
if (UserDefinedStruct.IsValid())
{
switch (UserDefinedStruct->Status.GetValue())
{
case EUserDefinedStructureStatus::UDSS_Error:
return FAppStyle::Get().GetBrush("Blueprint.CompileStatus.Overlay.Error");
case EUserDefinedStructureStatus::UDSS_UpToDate:
return FAppStyle::Get().GetBrush("Blueprint.CompileStatus.Overlay.Good");
default:
return FAppStyle::Get().GetBrush("Blueprint.CompileStatus.Overlay.Unknown");
}
}
return nullptr;
}
FText FUserDefinedStructureEditor::OnGetStatusTooltip() const
{
if (UserDefinedStruct.IsValid())
{
switch (UserDefinedStruct->Status.GetValue())
{
case EUserDefinedStructureStatus::UDSS_Error:
return FText::FromString(UserDefinedStruct->ErrorMessage);
default:
return LOCTEXT("GoodToGo_Status", "Good to go");
}
}
return FText::GetEmpty();
}
///////////////////////////////////////////////////////////////////////////////////////
// FUserDefinedStructureLayout
//Represents single structure (List of fields)
class FUserDefinedStructureLayout : public IDetailCustomNodeBuilder, public TSharedFromThis<FUserDefinedStructureLayout>
{
public:
FUserDefinedStructureLayout(TWeakPtr<class FUserDefinedStructureDetails> InStructureDetails)
: StructureDetails(InStructureDetails)
{}
void OnChanged()
{
OnRegenerateChildren.ExecuteIfBound();
}
FText OnGetTooltipText() const
{
auto StructureDetailsSP = StructureDetails.Pin();
if (StructureDetailsSP.IsValid())
{
if (auto Struct = StructureDetailsSP->GetUserDefinedStruct())
{
return FText::FromString(FStructureEditorUtils::GetTooltip(Struct));
}
}
return FText();
}
void OnTooltipCommitted(const FText& NewText, ETextCommit::Type InTextCommit)
{
auto StructureDetailsSP = StructureDetails.Pin();
if (StructureDetailsSP.IsValid())
{
if (auto Struct = StructureDetailsSP->GetUserDefinedStruct())
{
FStructureEditorUtils::ChangeTooltip(Struct, NewText.ToString());
}
}
}
/** IDetailCustomNodeBuilder Interface*/
virtual void SetOnRebuildChildren( FSimpleDelegate InOnRegenerateChildren ) override
{
OnRegenerateChildren = InOnRegenerateChildren;
}
virtual void GenerateChildContent( IDetailChildrenBuilder& ChildrenBuilder ) override;
virtual void GenerateHeaderRowContent( FDetailWidgetRow& NodeRow ) override {}
virtual void Tick( float DeltaTime ) override {}
virtual bool RequiresTick() const override { return false; }
virtual FName GetName() const override
{
auto StructureDetailsSP = StructureDetails.Pin();
if(StructureDetailsSP.IsValid())
{
if(auto Struct = StructureDetailsSP->GetUserDefinedStruct())
{
return Struct->GetFName();
}
}
return NAME_None;
}
virtual bool InitiallyCollapsed() const override { return false; }
private:
TWeakPtr<class FUserDefinedStructureDetails> StructureDetails;
FSimpleDelegate OnRegenerateChildren;
};
///////////////////////////////////////////////////////////////////////////////////////
// FUserDefinedStructureFieldDragDropOp
/** Provides information about the source row (single field) being dragged */
class FUserDefinedStructureFieldDragDropOp : public FDecoratedDragDropOp
{
public:
DRAG_DROP_OPERATOR_TYPE(FUserDefinedStructureFieldDragDropOp, FDecoratedDragDropOp);
FUserDefinedStructureFieldDragDropOp(TWeakPtr<FUserDefinedStructureDetails> InStructureDetails, const FGuid& InFieldGuid)
: StructureDetails(InStructureDetails)
, FieldGuid(InFieldGuid)
{
MouseCursor = EMouseCursor::GrabHandClosed;
if (TSharedPtr<FUserDefinedStructureDetails> StructureDetailsSP = InStructureDetails.Pin())
{
VariableFriendlyName = FStructureEditorUtils::GetVariableFriendlyName(StructureDetailsSP->GetUserDefinedStruct(), FieldGuid);
}
}
void Init()
{
SetValidTarget(false);
SetupDefaults();
Construct();
}
void SetValidTarget(bool IsValidTarget)
{
FFormatNamedArguments Args;
Args.Add(TEXT("StructVariableName"), FText::FromString(VariableFriendlyName));
if (IsValidTarget)
{
CurrentHoverText = FText::Format(LOCTEXT("MoveVariableHere", "Move '{StructVariableName}' Here"), Args);
CurrentIconBrush = FAppStyle::Get().GetBrush("Graph.ConnectorFeedback.OK");
}
else
{
CurrentHoverText = FText::Format(LOCTEXT("CannotMoveVariableHere", "Cannot Move '{StructVariableName}' Here"), Args);
CurrentIconBrush = FAppStyle::Get().GetBrush("Graph.ConnectorFeedback.Error");
}
}
const TWeakPtr<FUserDefinedStructureDetails>& GetStructureDetails() const
{
return StructureDetails;
}
const FGuid& GetFieldGuid() const
{
return FieldGuid;
}
private:
TWeakPtr<FUserDefinedStructureDetails> StructureDetails;
FGuid FieldGuid;
FString VariableFriendlyName;
};
///////////////////////////////////////////////////////////////////////////////////////
// FUserDefinedStructureFieldDragDropHandler
/** Handles drag-and-drop (as both source and target) for a single field's widget row */
class FUserDefinedStructureFieldDragDropHandler : public IDetailDragDropHandler
{
public:
FUserDefinedStructureFieldDragDropHandler(TWeakPtr<FUserDefinedStructureDetails> InStructureDetails, const FGuid& InFieldGuid)
: StructureDetails(InStructureDetails)
, FieldGuid(InFieldGuid)
{
}
virtual TSharedPtr<FDragDropOperation> CreateDragDropOperation() const override
{
TSharedPtr<FUserDefinedStructureFieldDragDropOp> DragOp = MakeShared<FUserDefinedStructureFieldDragDropOp>(StructureDetails, FieldGuid);
DragOp->Init();
return DragOp;
}
virtual TOptional<EItemDropZone> CanAcceptDrop(const FDragDropEvent& DragDropSource, EItemDropZone DropZone) const override
{
const TSharedPtr<FUserDefinedStructureFieldDragDropOp> DragOp = DragDropSource.GetOperationAs<FUserDefinedStructureFieldDragDropOp>();
if (!DragOp.IsValid())
{
return TOptional<EItemDropZone>();
}
// Struct must match between drag source and drop target
const TSharedPtr<FUserDefinedStructureDetails> OtherStructureDetailsSP = DragOp->GetStructureDetails().Pin();
const TSharedPtr<FUserDefinedStructureDetails> MyStructureDetailsSP = StructureDetails.Pin();
if (!OtherStructureDetailsSP.IsValid() || !MyStructureDetailsSP.IsValid() || OtherStructureDetailsSP->GetUserDefinedStruct() != MyStructureDetailsSP->GetUserDefinedStruct())
{
DragOp->SetValidTarget(false);
return TOptional<EItemDropZone>();
}
// Struct fields must be moved above or below, so don't allow dropping directly onto a row
const EItemDropZone OverrideZone = (DropZone == EItemDropZone::BelowItem) ? EItemDropZone::BelowItem : EItemDropZone::AboveItem;
const FStructureEditorUtils::EMovePosition MovePosition = (OverrideZone == EItemDropZone::BelowItem) ? FStructureEditorUtils::PositionBelow : FStructureEditorUtils::PositionAbove;
if (!FStructureEditorUtils::CanMoveVariable(MyStructureDetailsSP->GetUserDefinedStruct(), DragOp->GetFieldGuid(), FieldGuid, MovePosition))
{
DragOp->SetValidTarget(false);
return TOptional<EItemDropZone>();
}
DragOp->SetValidTarget(true);
return OverrideZone;
}
virtual bool AcceptDrop(const FDragDropEvent& DragDropSource, EItemDropZone DropZone) const override
{
const TSharedPtr<FUserDefinedStructureFieldDragDropOp> DragOp = DragDropSource.GetOperationAs<FUserDefinedStructureFieldDragDropOp>();
if (!DragOp.IsValid())
{
return false;
}
// Struct must match between drag source and drop target
const TSharedPtr<FUserDefinedStructureDetails> OtherStructureDetailsSP = DragOp->GetStructureDetails().Pin();
const TSharedPtr<FUserDefinedStructureDetails> MyStructureDetailsSP = StructureDetails.Pin();
if (!OtherStructureDetailsSP.IsValid() || !MyStructureDetailsSP.IsValid() || OtherStructureDetailsSP->GetUserDefinedStruct() != MyStructureDetailsSP->GetUserDefinedStruct())
{
return false;
}
const FStructureEditorUtils::EMovePosition MovePosition = (DropZone == EItemDropZone::BelowItem) ? FStructureEditorUtils::PositionBelow : FStructureEditorUtils::PositionAbove;
return FStructureEditorUtils::MoveVariable(MyStructureDetailsSP->GetUserDefinedStruct(), DragOp->GetFieldGuid(), FieldGuid, MovePosition);
}
private:
TWeakPtr<FUserDefinedStructureDetails> StructureDetails;
FGuid FieldGuid;
};
///////////////////////////////////////////////////////////////////////////////////////
// FUserDefinedStructureFieldLayout
//Represents single field
class FUserDefinedStructureFieldLayout : public IDetailCustomNodeBuilder, public TSharedFromThis<FUserDefinedStructureFieldLayout>
{
public:
FUserDefinedStructureFieldLayout(TWeakPtr<class FUserDefinedStructureDetails> InStructureDetails, TWeakPtr<class FUserDefinedStructureLayout> InStructureLayout, FGuid InFieldGuid)
: StructureDetails(InStructureDetails)
, StructureLayout(InStructureLayout)
, FieldGuid(InFieldGuid)
{}
void OnChanged()
{
OnRegenerateChildren.ExecuteIfBound();
}
FText OnGetNameText() const
{
auto StructureDetailsSP = StructureDetails.Pin();
if(StructureDetailsSP.IsValid())
{
return FText::FromString(FStructureEditorUtils::GetVariableFriendlyName(StructureDetailsSP->GetUserDefinedStruct(), FieldGuid));
}
return FText::GetEmpty();
}
void OnNameTextCommitted(const FText& NewText, ETextCommit::Type InTextCommit)
{
auto StructureDetailsSP = StructureDetails.Pin();
if(StructureDetailsSP.IsValid())
{
const FString NewNameStr = NewText.ToString();
FStructureEditorUtils::RenameVariable(StructureDetailsSP->GetUserDefinedStruct(), FieldGuid, NewNameStr);
}
}
FEdGraphPinType OnGetPinInfo() const
{
auto StructureDetailsSP = StructureDetails.Pin();
if(StructureDetailsSP.IsValid())
{
if(const FStructVariableDescription* FieldDesc = StructureDetailsSP->FindStructureFieldByGuid(FieldGuid))
{
return FieldDesc->ToPinType();
}
}
return FEdGraphPinType();
}
void PinInfoChanged(const FEdGraphPinType& PinType)
{
auto StructureDetailsSP = StructureDetails.Pin();
if(StructureDetailsSP.IsValid())
{
if (FStructureEditorUtils::ChangeVariableType(StructureDetailsSP->GetUserDefinedStruct(), FieldGuid, PinType))
{
if (TSharedPtr<FUserDefinedStructureEditor> StructureEditorSP = StructureDetailsSP->GetStructureEditor().Pin())
{
StructureEditorSP->SetInitialPinType(PinType);
}
}
else
{
FNotificationInfo NotificationInfo(LOCTEXT("VariableTypeChange_FailureNotification", "Variable type change failed (the selected type may not be compatible with this struct). See log for details."));
NotificationInfo.ExpireDuration = 5.0f;
FSlateNotificationManager::Get().AddNotification(NotificationInfo);
}
}
}
void OnPrePinInfoChange(const FEdGraphPinType& PinType)
{
}
void OnRemovField()
{
auto StructureDetailsSP = StructureDetails.Pin();
if(StructureDetailsSP.IsValid())
{
FStructureEditorUtils::RemoveVariable(StructureDetailsSP->GetUserDefinedStruct(), FieldGuid);
}
}
bool IsRemoveButtonEnabled()
{
auto StructureDetailsSP = StructureDetails.Pin();
if (StructureDetailsSP.IsValid())
{
if (auto UDStruct = StructureDetailsSP->GetUserDefinedStruct())
{
return (FStructureEditorUtils::GetVarDesc(UDStruct).Num() > 1);
}
}
return false;
}
FText OnGetTooltipText() const
{
auto StructureDetailsSP = StructureDetails.Pin();
if (StructureDetailsSP.IsValid())
{
if (const FStructVariableDescription* FieldDesc = StructureDetailsSP->FindStructureFieldByGuid(FieldGuid))
{
return FText::FromString(FieldDesc->ToolTip);
}
}
return FText();
}
void OnTooltipCommitted(const FText& NewText, ETextCommit::Type InTextCommit)
{
auto StructureDetailsSP = StructureDetails.Pin();
if (StructureDetailsSP.IsValid())
{
FStructureEditorUtils::ChangeVariableTooltip(StructureDetailsSP->GetUserDefinedStruct(), FieldGuid, NewText.ToString());
}
}
ECheckBoxState OnGetEditableOnBPInstanceState() const
{
auto StructureDetailsSP = StructureDetails.Pin();
if (StructureDetailsSP.IsValid())
{
if (const FStructVariableDescription* FieldDesc = StructureDetailsSP->FindStructureFieldByGuid(FieldGuid))
{
return !FieldDesc->bDontEditOnInstance ? ECheckBoxState::Checked : ECheckBoxState::Unchecked;
}
}
return ECheckBoxState::Undetermined;
}
void OnEditableOnBPInstanceCommitted(ECheckBoxState InNewState)
{
auto StructureDetailsSP = StructureDetails.Pin();
if (StructureDetailsSP.IsValid())
{
FStructureEditorUtils::ChangeEditableOnBPInstance(StructureDetailsSP->GetUserDefinedStruct(), FieldGuid, ECheckBoxState::Unchecked != InNewState);
}
}
ECheckBoxState OnGetSaveGameState() const
{
auto StructureDetailsSP = StructureDetails.Pin();
if (StructureDetailsSP.IsValid())
{
if (const FStructVariableDescription* FieldDesc = StructureDetailsSP->FindStructureFieldByGuid(FieldGuid))
{
return FieldDesc->bEnableSaveGame ? ECheckBoxState::Checked : ECheckBoxState::Unchecked;
}
}
return ECheckBoxState::Undetermined;
}
void OnSaveGameCommitted(ECheckBoxState InNewState)
{
auto StructureDetailsSP = StructureDetails.Pin();
if (StructureDetailsSP.IsValid() && (ECheckBoxState::Undetermined != InNewState))
{
FStructureEditorUtils::ChangeSaveGameEnabled(StructureDetailsSP->GetUserDefinedStruct(), FieldGuid, InNewState == ECheckBoxState::Checked);
}
}
// Multi-line text
EVisibility IsMultiLineTextOptionVisible() const
{
auto StructureDetailsSP = StructureDetails.Pin();
if (StructureDetailsSP.IsValid())
{
return FStructureEditorUtils::CanEnableMultiLineText(StructureDetailsSP->GetUserDefinedStruct(), FieldGuid) ? EVisibility::Visible : EVisibility::Collapsed;
}
return EVisibility::Collapsed;
}
ECheckBoxState OnGetMultiLineTextEnabled() const
{
auto StructureDetailsSP = StructureDetails.Pin();
if (StructureDetailsSP.IsValid())
{
return FStructureEditorUtils::IsMultiLineTextEnabled(StructureDetailsSP->GetUserDefinedStruct(), FieldGuid) ? ECheckBoxState::Checked : ECheckBoxState::Unchecked;
}
return ECheckBoxState::Undetermined;
}
void OnMultiLineTextEnabledCommitted(ECheckBoxState InNewState)
{
auto StructureDetailsSP = StructureDetails.Pin();
if (StructureDetailsSP.IsValid() && (ECheckBoxState::Undetermined != InNewState))
{
FStructureEditorUtils::ChangeMultiLineTextEnabled(StructureDetailsSP->GetUserDefinedStruct(), FieldGuid, ECheckBoxState::Checked == InNewState);
}
}
// 3D widget
EVisibility Is3dWidgetOptionVisible() const
{
auto StructureDetailsSP = StructureDetails.Pin();
if (StructureDetailsSP.IsValid())
{
return FStructureEditorUtils::CanEnable3dWidget(StructureDetailsSP->GetUserDefinedStruct(), FieldGuid) ? EVisibility::Visible : EVisibility::Collapsed;
}
return EVisibility::Collapsed;
}
ECheckBoxState OnGet3dWidgetEnabled() const
{
auto StructureDetailsSP = StructureDetails.Pin();
if (StructureDetailsSP.IsValid())
{
return FStructureEditorUtils::Is3dWidgetEnabled(StructureDetailsSP->GetUserDefinedStruct(), FieldGuid) ? ECheckBoxState::Checked : ECheckBoxState::Unchecked;
}
return ECheckBoxState::Undetermined;
}
void On3dWidgetEnabledCommitted(ECheckBoxState InNewState)
{
auto StructureDetailsSP = StructureDetails.Pin();
if (StructureDetailsSP.IsValid() && (ECheckBoxState::Undetermined != InNewState))
{
FStructureEditorUtils::Change3dWidgetEnabled(StructureDetailsSP->GetUserDefinedStruct(), FieldGuid, ECheckBoxState::Checked == InNewState);
}
}
// Value Range
EVisibility IsValueRangeOptionVisible() const
{
if (const TSharedPtr<FUserDefinedStructureDetails> StructureDetailsSP = StructureDetails.Pin())
{
return FStructureEditorUtils::CanEditValueRange(StructureDetailsSP->GetUserDefinedStruct(), FieldGuid)
? EVisibility::Visible
: EVisibility::Collapsed;
}
return EVisibility::Collapsed;
}
// Meta Data
FText OnGetMetaData(const FName Key) const
{
const TSharedPtr<FUserDefinedStructureDetails> Details = StructureDetails.Pin();
if (!Details)
{
return FText();
}
const FString* Value = FStructureEditorUtils::GetMetaData(Details->GetUserDefinedStruct(), FieldGuid, Key);
return Value ? FText::FromString(*Value) : FText();
}
void OnMetaDataCommitted(const FText& NewText, ETextCommit::Type, const FName Key)
{
if (const TSharedPtr<FUserDefinedStructureDetails> StructureDetailsSP = StructureDetails.Pin())
{
FStructureEditorUtils::SetMetaData(StructureDetailsSP->GetUserDefinedStruct(), FieldGuid, Key, NewText.ToString());
}
}
/** IDetailCustomNodeBuilder Interface*/
virtual void SetOnRebuildChildren( FSimpleDelegate InOnRegenerateChildren ) override
{
OnRegenerateChildren = InOnRegenerateChildren;
}
EVisibility GetErrorIconVisibility()
{
auto StructureDetailsSP = StructureDetails.Pin();
if(StructureDetailsSP.IsValid())
{
auto FieldDesc = StructureDetailsSP->FindStructureFieldByGuid(FieldGuid);
if (FieldDesc && FieldDesc->bInvalidMember)
{
return EVisibility::Visible;
}
}
return EVisibility::Collapsed;
}
void GetFilteredVariableTypeTree( TArray< TSharedPtr<UEdGraphSchema_K2::FPinTypeTreeInfo> >& TypeTree, ETypeTreeFilter TypeTreeFilter) const
{
auto K2Schema = GetDefault<UEdGraphSchema_K2>();
auto StructureDetailsSP = StructureDetails.Pin();
if(StructureDetailsSP.IsValid() && K2Schema)
{
K2Schema->GetVariableTypeTree(TypeTree, TypeTreeFilter);
}
}
virtual void GenerateHeaderRowContent( FDetailWidgetRow& NodeRow ) override
{
auto K2Schema = GetDefault<UEdGraphSchema_K2>();
TSharedPtr<SImage> ErrorIcon;
const float ValueContentWidth = 200.0f;
NodeRow
.NameContent()
[
SNew(SHorizontalBox)
+SHorizontalBox::Slot()
.AutoWidth()
.HAlign(HAlign_Left)
.VAlign(VAlign_Center)
[
SAssignNew(ErrorIcon, SImage)
.Image(FAppStyle::Get().GetBrush("Icons.Error") )
.ToolTipText(LOCTEXT("MemberVariableErrorToolTip", "Member variable is invalid"))
]
+SHorizontalBox::Slot()
.FillWidth(1)
.VAlign(VAlign_Center)
[
SNew(SEditableTextBox)
.Text( this, &FUserDefinedStructureFieldLayout::OnGetNameText )
.OnTextCommitted( this, &FUserDefinedStructureFieldLayout::OnNameTextCommitted )
.Font( IDetailLayoutBuilder::GetDetailFont() )
]
]
.ValueContent()
.MaxDesiredWidth(ValueContentWidth)
.MinDesiredWidth(ValueContentWidth)
[
SNew(SHorizontalBox)
+SHorizontalBox::Slot()
.VAlign(VAlign_Center)
.Padding(0.0f, 0.0f, 4.0f, 0.0f)
[
SNew(SPinTypeSelector, FGetPinTypeTree::CreateSP(this, &FUserDefinedStructureFieldLayout::GetFilteredVariableTypeTree))
.TargetPinType(this, &FUserDefinedStructureFieldLayout::OnGetPinInfo)
.OnPinTypePreChanged(this, &FUserDefinedStructureFieldLayout::OnPrePinInfoChange)
.OnPinTypeChanged(this, &FUserDefinedStructureFieldLayout::PinInfoChanged)
.Schema(K2Schema)
.TypeTreeFilter(ETypeTreeFilter::None)
.Font( IDetailLayoutBuilder::GetDetailFont() )
]
+SHorizontalBox::Slot()
.AutoWidth()
.HAlign(HAlign_Right)
.VAlign(VAlign_Center)
[
PropertyCustomizationHelpers::MakeEmptyButton(
FSimpleDelegate::CreateSP(this, &FUserDefinedStructureFieldLayout::OnRemovField),
LOCTEXT("RemoveVariable", "Remove member variable"),
TAttribute<bool>::Create(TAttribute<bool>::FGetter::CreateSP(this, &FUserDefinedStructureFieldLayout::IsRemoveButtonEnabled)))
]
]
.DragDropHandler(MakeShared<FUserDefinedStructureFieldDragDropHandler>(StructureDetails, FieldGuid))
;
if (ErrorIcon.IsValid())
{
ErrorIcon->SetVisibility(
TAttribute<EVisibility>::Create(
TAttribute<EVisibility>::FGetter::CreateSP(
this, &FUserDefinedStructureFieldLayout::GetErrorIconVisibility)));
}
}
virtual void GenerateChildContent( IDetailChildrenBuilder& ChildrenBuilder ) override
{
ChildrenBuilder.AddCustomRow(LOCTEXT("Tooltip", "Tooltip"))
.NameContent()
[
SNew(STextBlock)
.Text(LOCTEXT("Tooltip", "Tooltip"))
.Font(IDetailLayoutBuilder::GetDetailFont())
]
.ValueContent()
[
SNew(SEditableTextBox)
.Text(this, &FUserDefinedStructureFieldLayout::OnGetTooltipText)
.OnTextCommitted(this, &FUserDefinedStructureFieldLayout::OnTooltipCommitted)
.Font(IDetailLayoutBuilder::GetDetailFont())
];
ChildrenBuilder.AddCustomRow(LOCTEXT("EditableOnInstance", "EditableOnInstance"))
.NameContent()
[
SNew(STextBlock)
.Text(LOCTEXT("Editable", "Editable"))
.Font(IDetailLayoutBuilder::GetDetailFont())
]
.ValueContent()
[
SNew(SCheckBox)
.ToolTipText(LOCTEXT("EditableOnBPInstance", "Variable can be edited on an instance of a Blueprint."))
.OnCheckStateChanged(this, &FUserDefinedStructureFieldLayout::OnEditableOnBPInstanceCommitted)
.IsChecked(this, &FUserDefinedStructureFieldLayout::OnGetEditableOnBPInstanceState)
];
ChildrenBuilder.AddCustomRow(LOCTEXT("SaveGame", "Save Game"))
.NameContent()
[
SNew(STextBlock)
.Text(LOCTEXT("SaveGameText", "Save Game"))
.Font(IDetailLayoutBuilder::GetDetailFont())
]
.ValueContent()
[
SNew(SCheckBox)
.ToolTipText(LOCTEXT("SaveGame_Tooltip", "Should variable be serialized for saved games"))
.OnCheckStateChanged(this, &FUserDefinedStructureFieldLayout::OnSaveGameCommitted)
.IsChecked(this, &FUserDefinedStructureFieldLayout::OnGetSaveGameState)
];
ChildrenBuilder.AddCustomRow(LOCTEXT("MultiLineText", "Multi-line Text"))
.NameContent()
[
SNew(STextBlock)
.Text(LOCTEXT("MultiLineText", "Multi-line Text"))
.Font(IDetailLayoutBuilder::GetDetailFont())
]
.ValueContent()
[
SNew(SCheckBox)
.ToolTipText(LOCTEXT("MultiLineTextToolTip", "Should this property allow multiple lines of text to be entered?"))
.OnCheckStateChanged(this, &FUserDefinedStructureFieldLayout::OnMultiLineTextEnabledCommitted)
.IsChecked(this, &FUserDefinedStructureFieldLayout::OnGetMultiLineTextEnabled)
]
.Visibility(TAttribute<EVisibility>::Create(TAttribute<EVisibility>::FGetter::CreateSP(this, &FUserDefinedStructureFieldLayout::IsMultiLineTextOptionVisible)));
ChildrenBuilder.AddCustomRow(LOCTEXT("3dWidget", "3D Widget"))
.NameContent()
[
SNew(STextBlock)
.Text(LOCTEXT("3dWidget", "3D Widget"))
.Font(IDetailLayoutBuilder::GetDetailFont())
]
.ValueContent()
[
SNew(SCheckBox)
.OnCheckStateChanged(this, &FUserDefinedStructureFieldLayout::On3dWidgetEnabledCommitted)
.IsChecked(this, &FUserDefinedStructureFieldLayout::OnGet3dWidgetEnabled)
]
.Visibility(TAttribute<EVisibility>::Create(TAttribute<EVisibility>::FGetter::CreateSP(this, &FUserDefinedStructureFieldLayout::Is3dWidgetOptionVisible)));
ChildrenBuilder.AddCustomRow(LOCTEXT("ClampRange", "Value Range"))
.NameContent()
[
SNew(STextBlock)
.Text(LOCTEXT("ValueRangeLabel", "Value Range"))
.Font(IDetailLayoutBuilder::GetDetailFont())
]
.ValueContent()
[
SNew(SHorizontalBox)
+SHorizontalBox::Slot()
.FillWidth(1)
[
SNew(SEditableTextBox)
.OnTextCommitted(this, &FUserDefinedStructureFieldLayout::OnMetaDataCommitted, FStructVariableMetaData::ClampMin)
.Text(this, &FUserDefinedStructureFieldLayout::OnGetMetaData, FStructVariableMetaData::ClampMin)
.Font(IDetailLayoutBuilder::GetDetailFont())
]
+SHorizontalBox::Slot()
.AutoWidth()
[
SNew(STextBlock)
.Text(LOCTEXT("Min .. Max Separator", " .. "))
.Font(IDetailLayoutBuilder::GetDetailFont())
]
+SHorizontalBox::Slot()
.FillWidth(1)
[
SNew(SEditableTextBox)
.OnTextCommitted(this, &FUserDefinedStructureFieldLayout::OnMetaDataCommitted, FStructVariableMetaData::ClampMax)
.Text(this, &FUserDefinedStructureFieldLayout::OnGetMetaData, FStructVariableMetaData::ClampMax)
.Font(IDetailLayoutBuilder::GetDetailFont())
]
]
.Visibility(TAttribute<EVisibility>::Create(TAttribute<EVisibility>::FGetter::CreateSP(this, &FUserDefinedStructureFieldLayout::IsValueRangeOptionVisible)));
ChildrenBuilder.AddCustomRow(LOCTEXT("UIRange", "Slider Range"))
.NameContent()
[
SNew(STextBlock)
.Text(LOCTEXT("SliderRangeLabel", "Slider Range"))
.Font(IDetailLayoutBuilder::GetDetailFont())
]
.ValueContent()
[
SNew(SHorizontalBox)
+SHorizontalBox::Slot()
.FillWidth(1)
[
SNew(SEditableTextBox)
.OnTextCommitted(this, &FUserDefinedStructureFieldLayout::OnMetaDataCommitted, FStructVariableMetaData::UIMin)
.Text(this, &FUserDefinedStructureFieldLayout::OnGetMetaData, FStructVariableMetaData::UIMin)
.Font(IDetailLayoutBuilder::GetDetailFont())
]
+SHorizontalBox::Slot()
.AutoWidth()
[
SNew(STextBlock)
.Text(LOCTEXT("Min .. Max Separator", " .. "))
.Font(IDetailLayoutBuilder::GetDetailFont())
]
+SHorizontalBox::Slot()
.FillWidth(1)
[
SNew(SEditableTextBox)
.OnTextCommitted(this, &FUserDefinedStructureFieldLayout::OnMetaDataCommitted, FStructVariableMetaData::UIMax)
.Text(this, &FUserDefinedStructureFieldLayout::OnGetMetaData, FStructVariableMetaData::UIMax)
.Font(IDetailLayoutBuilder::GetDetailFont())
]
]
.Visibility(TAttribute<EVisibility>::Create(TAttribute<EVisibility>::FGetter::CreateSP(this, &FUserDefinedStructureFieldLayout::IsValueRangeOptionVisible)));
}
virtual void Tick( float DeltaTime ) override {}
virtual bool RequiresTick() const override { return false; }
virtual FName GetName() const override { return FName(*FieldGuid.ToString()); }
virtual bool InitiallyCollapsed() const override { return true; }
private:
TWeakPtr<class FUserDefinedStructureDetails> StructureDetails;
TWeakPtr<class FUserDefinedStructureLayout> StructureLayout;
FGuid FieldGuid;
FSimpleDelegate OnRegenerateChildren;
};
///////////////////////////////////////////////////////////////////////////////////////
// FUserDefinedStructureLayout
void FUserDefinedStructureLayout::GenerateChildContent( IDetailChildrenBuilder& ChildrenBuilder )
{
ChildrenBuilder.AddCustomRow(FText::GetEmpty())
.NameContent()
[
SNew(STextBlock)
.Text(LOCTEXT("Tooltip", "Tooltip"))
.Font(IDetailLayoutBuilder::GetDetailFont())
]
.ValueContent()
.MinDesiredWidth(400.0f)
[
SNew(SEditableTextBox)
.Text(this, &FUserDefinedStructureLayout::OnGetTooltipText)
.OnTextCommitted(this, &FUserDefinedStructureLayout::OnTooltipCommitted)
.Font(IDetailLayoutBuilder::GetDetailFont())
];
auto StructureDetailsSP = StructureDetails.Pin();
if(StructureDetailsSP.IsValid())
{
if(auto Struct = StructureDetailsSP->GetUserDefinedStruct())
{
auto& VarDescArrayRef = FStructureEditorUtils::GetVarDesc(Struct);
for (int32 Index = 0; Index < VarDescArrayRef.Num(); ++Index)
{
auto& VarDesc = VarDescArrayRef[Index];
TSharedRef<class FUserDefinedStructureFieldLayout> VarLayout = MakeShareable(new FUserDefinedStructureFieldLayout(StructureDetails, SharedThis(this), VarDesc.VarGuid));
ChildrenBuilder.AddCustomBuilder(VarLayout);
}
}
}
}
///////////////////////////////////////////////////////////////////////////////////////
// FUserDefinedStructureLayout
/** IDetailCustomization interface */
void FUserDefinedStructureDetails::CustomizeDetails(class IDetailLayoutBuilder& DetailLayout)
{
const TArray<TWeakObjectPtr<UObject>>& Objects = DetailLayout.GetSelectedObjects();
check(Objects.Num() > 0);
if (Objects.Num() == 1)
{
UserDefinedStruct = CastChecked<UUserDefinedStruct>(Objects[0].Get());
IDetailCategoryBuilder& StructureCategory = DetailLayout.EditCategory("Structure", LOCTEXT("StructureCategory", "Structure"));
Layout = MakeShareable(new FUserDefinedStructureLayout(SharedThis(this)));
StructureCategory.AddCustomBuilder(Layout.ToSharedRef());
}
}
/** FStructureEditorUtils::INotifyOnStructChanged */
void FUserDefinedStructureDetails::PostChange(const class UUserDefinedStruct* Struct, FStructureEditorUtils::EStructureEditorChangeInfo Info)
{
if (Struct && (GetUserDefinedStruct() == Struct))
{
if (Layout.IsValid())
{
Layout->OnChanged();
}
}
}
#undef LOCTEXT_NAMESPACE