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

445 lines
17 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "KismetNodes/SGraphNodeMakeStruct.h"
#include "Widgets/SBoxPanel.h"
#include "Widgets/Images/SImage.h"
#include "Widgets/Layout/SBox.h"
#include "Widgets/Input/SButton.h"
#include "Widgets/Input/SComboBox.h"
#include "SGraphPin.h"
#include "EdGraphSchema_K2.h"
#include "K2Node.h"
#include "K2Node_MakeStruct.h"
#include "NodeFactory.h"
#include "ScopedTransaction.h"
#include "Kismet2/BlueprintEditorUtils.h"
#define LOCTEXT_NAMESPACE "SGraphNodeMakeStruct"
// Enable this option to allow for the deprecated states to be selectable options in the dropdown.
// Warning: These states are no longer being supported but were possible states for optional pins prior to 4.11
#define ALLOW_DEPRECATED_STATES 0
class SOptionalPinStateView : public SCompoundWidget
{
public:
SLATE_BEGIN_ARGS(SOptionalPinStateView) {}
SLATE_END_ARGS()
void Construct(const FArguments& InArgs, UEdGraphPin* InPin, FOptionalPinFromProperty& InPropertyEntry);
protected:
/** Generates items for the ComboBox's list */
TSharedRef<SWidget> OnGenerateWidget(TSharedPtr<FOptionalPinOverrideState> InItem);
/** Callback for display of the current icon for the pin's status */
const FSlateBrush* GetPinOverrideIcon(FOptionalPinFromProperty* InPropertyEntry) const;
/** Callback for display of the current icon color for the pin's status */
FLinearColor GetPinOverrideIconColor(FOptionalPinFromProperty* InPropertyEntry) const;
/**
* Updates the optional pin's data with the current selection (cached)
*
* @param InGraphPin The pin being updated
* @param InPropertyEntry The optional pin data associated with the pin
*/
void UpdateOptionalPin(UEdGraphPin* InGraphPin, FOptionalPinFromProperty* InPropertyEntry);
/**
* Callback after selecting a new item in the ComboBox
*
* @param InItem Selected item
* @param InSelectInfo Info about the method of selection
* @param InGraphPin The pin being updated
* @param InPropertyEntry The optional pin data associated with the pin
*/
void OnOverrideStateSelected(TSharedPtr<FOptionalPinOverrideState> InItem, ESelectInfo::Type InSelectInfo, UEdGraphPin* InGraphPin, FOptionalPinFromProperty* InPropertyEntry);
/**
* Toggle button's selection event.
*
* @param InGraphPin The pin being updated
* @param InPropertyEntry The optional pin data associated with the pin
*/
FReply OnOverrideStateToggled(UEdGraphPin* InGraphPin, FOptionalPinFromProperty* InPropertyEntry);
/**
* Retrieves the icon for display of the current status of the optional pin
*
* @param bInIsOverrideEnabled TRUE if the optional pin's override is enabled
* @param bInIsSetValuePinVisible TRUE if the optional pin's value pin is visible
* @param bInIsOverridePinVisible TRUE if the optional pin's override is visible
* @param OutBrush Returns the brush
*/
void GetIconForOptionalPinOverrideState(bool bInIsOverrideEnabled, bool bIsSetValuePinVisible, bool bInIsOverridePinVisible, const FSlateBrush*& OutBrush) const;
/**
* Retrieves the icon's color for display of the current status of the optional pin
*
* @param bInIsOverrideEnabled TRUE if the optional pin's override is enabled
* @param bInIsSetValuePinVisible TRUE if the optional pin's value pin is visible
* @param bInIsOverridePinVisible TRUE if the optional pin's override is visible
* @param OutColor Returns the color
*/
void GetIconColorForOptionalPinOverrideState(bool bInIsOverrideEnabled, bool bIsSetValuePinVisible, bool bInIsOverridePinVisible, FLinearColor& OutColor) const;
/** Generates the selection widget, either a SComboBox or an SButton, used to select valid states for the pin */
TSharedRef<SWidget> CreateSelectionWidget(UEdGraphPin* InPin, FOptionalPinFromProperty& InPropertyEntry, TSharedPtr<FOptionalPinOverrideState> InInitiallySelectedItem);
/** Returns the current tooltip for the status of the pin */
FText GetCurrentTooltip() const;
protected:
/** Wrapper widget that holds either the SComboBox or the SButton based on the number of items allowed for selection */
TWeakPtr<SBox> WrapperWidget;
/** The Blueprint that is being targetted by this optional pin change */
UBlueprint* TargetBlueprint;
/** The current item selected for this optional pin, used for displaying tooltips */
TSharedPtr<FOptionalPinOverrideState> CurrentSelection;
/** List of items available for selection by this pin */
TArray<TSharedPtr<FOptionalPinOverrideState>> ListItems;
};
void SOptionalPinStateView::Construct(const FArguments& InArgs, UEdGraphPin* InPin, FOptionalPinFromProperty& InPropertyEntry)
{
UK2Node_MakeStruct* SetFieldsNode = CastChecked<UK2Node_MakeStruct>(InPin->GetOwningNode());
TargetBlueprint = SetFieldsNode->GetBlueprint();
TArray<TSharedPtr<FOptionalPinOverrideState>> AllListItems;
FOptionalPinOverrideState CurrentState(FText::GetEmpty(), FText::GetEmpty(), InPropertyEntry.bIsOverridePinVisible, InPropertyEntry.bIsOverrideEnabled, InPropertyEntry.bIsSetValuePinVisible);
TSharedPtr<FOptionalPinOverrideState> NoOverrideNoValue = MakeShareable(new FOptionalPinOverrideState(
LOCTEXT("DisableOverride", "Disable Override"),
LOCTEXT("DisableOverride_Tooltip", "Disables the override value from being used without modifying the value stored in the struct."),
true, false, false));
if (SetFieldsNode->GetClass() != UK2Node_MakeStruct::StaticClass() || ALLOW_DEPRECATED_STATES)
{
ListItems.Add(NoOverrideNoValue);
}
else
{
AllListItems.Add(NoOverrideNoValue);
}
ListItems.Add(MakeShareable(new FOptionalPinOverrideState(
LOCTEXT("EnableOverride", "Override"),
LOCTEXT("EnableOverride_Tooltip", "Enables the override to use the value currently stored in the struct."),
true, true, false)));
ListItems.Add(MakeShareable(new FOptionalPinOverrideState(
LOCTEXT("EnableOverrideSetValue", "Override and Set Value"),
LOCTEXT("EnableOverrideSetValue_Tooltip", "Overrides and updates the value in the struct."),
true, true, true)));
#if ALLOW_DEPRECATED_STATES == 0
AllListItems.Append(ListItems);
#endif
// Can enable the extra two states that were previously supported when the override and value were two separate pins. This functionality is not officially supported anymore and will incur warnings.
AllListItems.Add(MakeShareable(new FOptionalPinOverrideState(
LOCTEXT("SetValue", "Set Value"),
LOCTEXT("SetValue_Tooltip", "Updates only the value inside the struct without changing whether the override is enabled or not.\nWarning: This setting is no longer a supported workflow and it is advised that you refactor your Blueprint to not use it!"),
false, false, true)));
AllListItems.Add(MakeShareable(new FOptionalPinOverrideState(
LOCTEXT("DisableOverrideSetValue", "Disable Override and Set Value"),
LOCTEXT("DisableOverrideSetValue_Tooltip", "Disables the override and updates the stored value.\nWarning: This setting is no longer a supported workflow and it is advised that you refactor your Blueprint to not use it!"),
true, false, true)));
#if ALLOW_DEPRECATED_STATES
ListItems.Append(AllListItems);
#endif
TSharedPtr<FOptionalPinOverrideState> InitiallySelectedItem;
for (TSharedPtr<FOptionalPinOverrideState> State : AllListItems)
{
if (CurrentState == *State.Get())
{
CurrentSelection = State;
if (ListItems.Find(CurrentSelection) != INDEX_NONE)
{
InitiallySelectedItem = CurrentSelection;
}
break;
}
}
ChildSlot
[
SAssignNew(WrapperWidget, SBox)
.HAlign(HAlign_Center)
.VAlign(VAlign_Center)
[
CreateSelectionWidget(InPin, InPropertyEntry, InitiallySelectedItem)
]
];
}
TSharedRef<SWidget> SOptionalPinStateView::CreateSelectionWidget(UEdGraphPin* InPin, FOptionalPinFromProperty& InPropertyEntry, TSharedPtr<FOptionalPinOverrideState> InInitiallySelectedItem)
{
if (ListItems.Num() > 2 || ListItems.Find(InInitiallySelectedItem) == INDEX_NONE)
{
return
SNew(SComboBox<TSharedPtr<FOptionalPinOverrideState>>)
.ButtonStyle(FAppStyle::Get(), "NoBorder")
.ForegroundColor(FLinearColor::White)
.ContentPadding(FMargin(0.0f, 0.0f, 0.0f, 0.0f))
.OptionsSource(&ListItems)
.InitiallySelectedItem(InInitiallySelectedItem)
.OnGenerateWidget(this, &SOptionalPinStateView::OnGenerateWidget)
.OnSelectionChanged(this, &SOptionalPinStateView::OnOverrideStateSelected, InPin, &InPropertyEntry)
[
SNew(SBorder)
.Padding(0.0f)
.BorderImage(FAppStyle::GetBrush("NoBorder"))
.ColorAndOpacity(this, &SOptionalPinStateView::GetPinOverrideIconColor, &InPropertyEntry)
[
SNew(SImage)
.Image(this, &SOptionalPinStateView::GetPinOverrideIcon, &InPropertyEntry)
.ToolTipText(this, &SOptionalPinStateView::GetCurrentTooltip)
]
];
}
return
SNew(SButton)
.ButtonStyle(FAppStyle::Get(), "NoBorder")
.OnClicked(this, &SOptionalPinStateView::OnOverrideStateToggled, InPin, &InPropertyEntry)
.ContentPadding(FMargin(0.0f, 0.0f, 0.0f, 0.0f))
[
SNew(SBorder)
.Padding(0.0f)
.BorderImage(FAppStyle::GetBrush("NoBorder"))
.ColorAndOpacity(this, &SOptionalPinStateView::GetPinOverrideIconColor, &InPropertyEntry)
[
SNew(SImage)
.Image(this, &SOptionalPinStateView::GetPinOverrideIcon, &InPropertyEntry)
.ToolTipText(this, &SOptionalPinStateView::GetCurrentTooltip)
]
];
}
TSharedRef<SWidget> SOptionalPinStateView::OnGenerateWidget(TSharedPtr<FOptionalPinOverrideState> InItem)
{
const FSlateBrush* Brush = nullptr;
FLinearColor Color = FColor(64, 64, 64).ReinterpretAsLinear();
GetIconForOptionalPinOverrideState(InItem->bIsOverrideEnabled, InItem->bIsValuePinVisible, InItem->bIsOverridePinVisible, Brush);
GetIconColorForOptionalPinOverrideState(InItem->bIsOverrideEnabled, InItem->bIsValuePinVisible, InItem->bIsOverridePinVisible, Color);
return
SNew(SHorizontalBox)
+SHorizontalBox::Slot()
.AutoWidth()
.HAlign(HAlign_Left)
[
SNew(SBorder)
.Padding(0.0f)
.BorderImage(FAppStyle::GetBrush("NoBorder"))
.ColorAndOpacity(Color)
[
SNew(SImage)
.Image(Brush)
.ToolTipText(InItem->TooltipText)
]
]
+ SHorizontalBox::Slot()
.AutoWidth()
.HAlign(HAlign_Left)
[
SNew(STextBlock)
.Text(InItem->DisplayText)
.ToolTipText(InItem->TooltipText)
];
}
void SOptionalPinStateView::GetIconForOptionalPinOverrideState(bool bInIsOverrideEnabled, bool bIsSetValuePinVisible, bool bInIsOverridePinVisible, const FSlateBrush*& OutBrush) const
{
const FSlateBrush* Brush = nullptr;
if (!bInIsOverridePinVisible || (bInIsOverridePinVisible && !bInIsOverrideEnabled && bIsSetValuePinVisible))
{
Brush = FAppStyle::GetBrush("Icons.Warning");
}
else if (!bInIsOverrideEnabled && !bIsSetValuePinVisible)
{
Brush = FAppStyle::GetBrush("Kismet.VariableList.HideForInstance");
}
else
{
Brush = FAppStyle::GetBrush("Kismet.VariableList.ExposeForInstance");
}
OutBrush = Brush;
}
void SOptionalPinStateView::GetIconColorForOptionalPinOverrideState(bool bInIsOverrideEnabled, bool bIsSetValuePinVisible, bool bInIsOverridePinVisible, FLinearColor& OutColor) const
{
FLinearColor Color = FColor(64, 64, 64).ReinterpretAsLinear();
if (!bInIsOverridePinVisible || (bInIsOverridePinVisible && !bInIsOverrideEnabled && bIsSetValuePinVisible))
{
Color = FLinearColor::White;
}
else if (!bInIsOverrideEnabled && !bIsSetValuePinVisible)
{
}
else
{
if (bInIsOverrideEnabled && !bIsSetValuePinVisible)
{
Color = FColor(215, 219, 119).ReinterpretAsLinear();
}
else
{
Color = FColor(130, 219, 119).ReinterpretAsLinear();
}
}
OutColor = Color;
}
const FSlateBrush* SOptionalPinStateView::GetPinOverrideIcon(FOptionalPinFromProperty* InPropertyEntry) const
{
const FSlateBrush* Brush = nullptr;
GetIconForOptionalPinOverrideState(InPropertyEntry->bIsOverrideEnabled, InPropertyEntry->bIsSetValuePinVisible, InPropertyEntry->bIsOverridePinVisible, Brush);
return Brush;
}
FLinearColor SOptionalPinStateView::GetPinOverrideIconColor(FOptionalPinFromProperty* InPropertyEntry) const
{
FLinearColor Color = FColor(64, 64, 64).ReinterpretAsLinear();
GetIconColorForOptionalPinOverrideState(InPropertyEntry->bIsOverrideEnabled, InPropertyEntry->bIsSetValuePinVisible, InPropertyEntry->bIsOverridePinVisible, Color);
return Color;
}
void SOptionalPinStateView::UpdateOptionalPin(UEdGraphPin* InGraphPin, FOptionalPinFromProperty* InPropertyEntry)
{
InGraphPin->bNotConnectable = !CurrentSelection->bIsValuePinVisible;
InPropertyEntry->bIsOverrideEnabled = CurrentSelection->bIsOverrideEnabled;
InPropertyEntry->bIsSetValuePinVisible = CurrentSelection->bIsValuePinVisible;
InPropertyEntry->bIsOverridePinVisible = CurrentSelection->bIsOverridePinVisible;
InGraphPin->bDefaultValueIsIgnored = !InPropertyEntry->bIsSetValuePinVisible;
FBlueprintEditorUtils::MarkBlueprintAsModified(TargetBlueprint);
}
void SOptionalPinStateView::OnOverrideStateSelected(TSharedPtr<FOptionalPinOverrideState> InItem, ESelectInfo::Type InSelectInfo, UEdGraphPin* InGraphPin, FOptionalPinFromProperty* InPropertyEntry)
{
if (InItem.IsValid())
{
FScopedTransaction ScopedTransaction(LOCTEXT("PinOverrideStateChanged", "Pin Override State Changed"));
InGraphPin->GetOwningNode()->Modify();
InGraphPin->Modify();
CurrentSelection = InItem;
UpdateOptionalPin(InGraphPin, InPropertyEntry);
if (ListItems.Num() == 2 && ListItems.Find(CurrentSelection) != INDEX_NONE)
{
WrapperWidget.Pin()->SetContent(CreateSelectionWidget(InGraphPin, *InPropertyEntry, CurrentSelection));
}
}
}
FReply SOptionalPinStateView::OnOverrideStateToggled(UEdGraphPin* InGraphPin, FOptionalPinFromProperty* InPropertyEntry)
{
FScopedTransaction ScopedTransaction(LOCTEXT("PinOverrideStateChanged", "Pin Override State Changed"));
InGraphPin->GetOwningNode()->Modify();
InGraphPin->Modify();
if (CurrentSelection == ListItems[0])
{
CurrentSelection = ListItems[1];
}
else
{
CurrentSelection = ListItems[0];
}
UpdateOptionalPin(InGraphPin, InPropertyEntry);
return FReply::Handled();
}
FText SOptionalPinStateView::GetCurrentTooltip() const
{
if (CurrentSelection.IsValid())
{
return CurrentSelection->TooltipText;
}
return FText::GetEmpty();
}
//////////////////////////////////////////////////////////////////////////
// SGraphNodeMakeStruct
void SGraphNodeMakeStruct::Construct(const FArguments& InArgs, UK2Node_MakeStruct* InNode)
{
GraphNode = InNode;
SetCursor( EMouseCursor::CardinalCross );
UpdateGraphNode();
}
TSharedPtr<SGraphPin> SGraphNodeMakeStruct::CreatePinWidget(UEdGraphPin* Pin) const
{
UK2Node_MakeStruct* SetFieldsNode = CastChecked<UK2Node_MakeStruct>(GetNodeObj());
TSharedPtr<SGraphPin> ResultPin = FNodeFactory::CreatePinWidget(Pin);
if (!Pin->PinType.bIsReference && Pin->PinType.PinCategory != UEdGraphSchema_K2::PC_Exec && Pin->Direction != EGPD_Output)
{
// Attempt to find a matching optional pin entry that is bound to an edit condition as a potential override.
FOptionalPinFromProperty* BoundPropertyEntryPtr = SetFieldsNode->ShowPinForProperties.FindByPredicate([Pin](const FOptionalPinFromProperty& PropertyEntry)
{
return PropertyEntry.bHasOverridePin && PropertyEntry.PropertyName == Pin->PinName;
});
if (BoundPropertyEntryPtr)
{
FOptionalPinFromProperty& PropertyEntry = *BoundPropertyEntryPtr;
// Find the member property that's bound to the input pin.
const FProperty* BoundProperty = FindFProperty<FProperty>(SetFieldsNode->StructType, Pin->PinName);
check(BoundProperty);
// If this member is also bound to an override condition, insert the widget that allows the user to toggle input on/off for the override value.
if (const FBoolProperty* OverrideProperty = SetFieldsNode->GetOverrideConditionForProperty(BoundProperty))
{
TWeakPtr<SHorizontalBox> HorizontalPin = ResultPin->GetFullPinHorizontalRowWidget();
if (HorizontalPin.IsValid())
{
// Setup the pin's editable state to be dependent on whether the override is enabled
TAttribute<bool> IsEditableAttribute;
TAttribute<bool>::FGetter IsEditableGetter;
IsEditableGetter.BindSP(this, &SGraphNodeMakeStruct::IsPinEnabled, &PropertyEntry);
IsEditableAttribute.Bind(IsEditableGetter);
ResultPin->SetIsEditable(IsEditableAttribute);
HorizontalPin.Pin()->InsertSlot(1)
.Padding(0.0f, 0.0f, 2.0f, 0.0f)
[
SNew(SOptionalPinStateView, Pin, PropertyEntry)
];
}
}
}
}
return ResultPin;
}
void SGraphNodeMakeStruct::CreatePinWidgets()
{
// Create Pin widgets for each of the pins.
for (int32 PinIndex = 0; PinIndex < GraphNode->Pins.Num(); ++PinIndex)
{
UEdGraphPin* CurPin = GraphNode->Pins[PinIndex];
if (ShouldPinBeHidden(CurPin))
{
AddPin(CreatePinWidget(CurPin).ToSharedRef());
}
}
}
bool SGraphNodeMakeStruct::IsPinEnabled(FOptionalPinFromProperty* InPropertyEntry) const
{
return InPropertyEntry->bIsSetValuePinVisible;
}
#undef LOCTEXT_NAMESPACE