Files
UnrealEngine/Engine/Plugins/FX/Niagara/Source/NiagaraEditorWidgets/Private/SNiagaraOverviewInlineParameterBox.cpp
2025-05-18 13:04:45 +08:00

431 lines
16 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "SNiagaraOverviewInlineParameterBox.h"
#include "Engine/Texture2D.h"
#include "INiagaraEditorTypeUtilities.h"
#include "NiagaraEditorWidgetsStyle.h"
#include "NiagaraNodeFunctionCall.h"
#include "NiagaraScriptSource.h"
#include "NiagaraScriptVariable.h"
#include "NiagaraSystem.h"
#include "NiagaraEditorModule.h"
#include "NiagaraEditorUtilities.h"
#include "Styling/StyleColors.h"
#include "ViewModels/NiagaraSystemSelectionViewModel.h"
#include "ViewModels/NiagaraSystemViewModel.h"
#include "ViewModels/Stack/NiagaraStackFunctionInput.h"
#include "Widgets/SNiagaraParameterName.h"
#include "Widgets/SNiagaraPinTypeSelector.h"
#include "Widgets/SToolTip.h"
#include "Widgets/Text/STextBlock.h"
#include "Widgets/Input/SButton.h"
#include "Widgets/Layout/SWrapBox.h"
#include "Widgets/Layout/SScrollBox.h"
#define LOCTEXT_NAMESPACE "SNiagaraOverviewInlineParameterBox"
void SNiagaraOverviewInlineParameterBox::Construct(const FArguments& InArgs, UNiagaraStackModuleItem& InStackModuleItem)
{
ModuleItem = &InStackModuleItem;
ChildSlot
.HAlign(HAlign_Left)
.VAlign(VAlign_Center)
[
SAssignNew(Container, SScrollBox)
.Orientation(EOrientation::Orient_Horizontal)
.ScrollBarThickness(FVector2D(2.f, 2.f))
.ScrollBarVisibility(EVisibility::Collapsed)
.ConsumeMouseWheel(EConsumeMouseWheel::Always)
];
ConstructChildren();
ModuleItem->OnStructureChanged().AddSP(this, &SNiagaraOverviewInlineParameterBox::OnModuleItemStructureChanged);
}
SNiagaraOverviewInlineParameterBox::~SNiagaraOverviewInlineParameterBox()
{
for(TWeakObjectPtr<UNiagaraStackFunctionInput> BoundInput : BoundFunctionInputs)
{
if(BoundInput.IsValid())
{
BoundInput->OnValueChanged().RemoveAll(this);
}
}
BoundFunctionInputs.Empty();
}
FReply SNiagaraOverviewInlineParameterBox::NavigateToStack(TWeakObjectPtr<const UNiagaraStackFunctionInput> FunctionInput)
{
// even if we can't navigate, the button should consume the input
if(!ModuleItem.IsValid() || !FunctionInput.IsValid() || FunctionInput->IsFinalized())
{
return FReply::Handled();
}
ModuleItem->GetSystemViewModel()->GetSelectionViewModel()->UpdateSelectedEntries({ModuleItem.Get()}, {}, true);
// we attempt to find a substitute entry, i.e. if our current input is an inline edit toggle, we try to find the input that the toggle is handling instead
TWeakObjectPtr<const UNiagaraStackFunctionInput> SubstituteEntry = FindSubstituteEntry(FunctionInput.Get());
if(SubstituteEntry.IsValid())
{
FunctionInput = SubstituteEntry;
}
TArray<UNiagaraStackEntry::FStackSearchItem> SearchItems;
FunctionInput->GetSearchItems(SearchItems);
if(SearchItems.Num() > 0)
{
ModuleItem->GetSystemViewModel()->GetSelectionViewModel()->GetSelectionStackViewModel()->SetSearchTextExternal(SearchItems[0].Value);
return FReply::Handled();
}
return FReply::Handled();
}
void SNiagaraOverviewInlineParameterBox::ConstructChildren()
{
Container->ClearChildren();
ImageBrushes.Empty();
for(TWeakObjectPtr<UNiagaraStackFunctionInput> BoundInput : BoundFunctionInputs)
{
if(BoundInput.IsValid())
{
BoundInput->OnValueChanged().RemoveAll(this);
}
}
BoundFunctionInputs.Empty();
TArray<TSharedRef<SWidget>> ParameterWidgets = GenerateParameterWidgets();
for (TSharedRef<SWidget> ParameterWidget : ParameterWidgets)
{
Container->AddSlot()
.VAlign(VAlign_Center)
.HAlign(HAlign_Center)
.Padding(FMargin(1.f, 0.f))
[
ParameterWidget
];
}
SetVisibility(ParameterWidgets.Num() > 0 ? EVisibility::Visible : EVisibility::Collapsed);
Container->SetEnabled(ModuleItem->GetIsEnabledAndOwnerIsEnabled());
}
TArray<TSharedRef<SWidget>> SNiagaraOverviewInlineParameterBox::GenerateParameterWidgets()
{
TArray<TSharedRef<SWidget>> ParameterWidgets;
// the stack module item should be valid at this point, but we check to make sure
if(!ModuleItem.IsValid())
{
return ParameterWidgets;
}
UNiagaraScriptSource* ScriptSource = CastChecked<UNiagaraScriptSource>(ModuleItem->GetModuleNode().GetFunctionScriptSource());
// in case the script source is no longer valid (i.e. the asset was deleted), we return immediately
if(ScriptSource == nullptr)
{
return ParameterWidgets;
}
// we cache the sort order of the widgets we create to sort them after all widgets have been created
TMap<TSharedRef<SWidget>, int32> SortOrder;
TArray<UNiagaraStackFunctionInput*> FunctionInputs = ModuleItem->GetInlineParameterInputs();
for(UNiagaraStackFunctionInput* FunctionInput : FunctionInputs)
{
FunctionInput->OnValueChanged().RemoveAll(this);
FunctionInput->OnValueChanged().AddSP(this, &SNiagaraOverviewInlineParameterBox::ConstructChildren);
BoundFunctionInputs.Add(FunctionInput);
TSharedPtr<SWidget> Widget;
switch (FunctionInput->GetValueMode())
{
case UNiagaraStackFunctionInput::EValueMode::Local:
Widget = GenerateParameterWidgetFromLocalValue(FunctionInput);
break;
case UNiagaraStackFunctionInput::EValueMode::Data:
// @todo currently data mode inputs won't be retrieved by GetInlineParameterInputs
Widget = GenerateParameterWidgetFromDataInterface(FunctionInput);
break;
case UNiagaraStackFunctionInput::EValueMode::ObjectAsset:
// @todo currently object asset inputs won't be retrieved by GetInlineParameterInputs
Widget = SNullWidget::NullWidget;
break;
default:
break;
}
if(Widget == SNullWidget::NullWidget)
{
continue;
}
SortOrder.Add(Widget.ToSharedRef(), FunctionInput->GetInputMetaData()->InlineParameterSortPriority);
ParameterWidgets.Add(Widget.ToSharedRef());
}
ParameterWidgets.Sort([&](TSharedRef<SWidget> WidgetA, TSharedRef<SWidget> WidgetB)
{
return SortOrder[WidgetA] < SortOrder[WidgetB];
});
return ParameterWidgets;
}
TSharedRef<SWidget> SNiagaraOverviewInlineParameterBox::GenerateParameterWidgetFromLocalValue(UNiagaraStackFunctionInput* FunctionInput)
{
bool bGenerateProperWidget = true;
FNiagaraTypeDefinition Type = FunctionInput->GetInputType();
TOptional<FNiagaraVariableMetaData> InputMetaData = FunctionInput->GetInputMetaData();
TSharedPtr<INiagaraEditorTypeUtilities> TypeUtilities = FNiagaraEditorModule::Get().GetTypeUtilities(Type);
// we construct a variable with the data from our local struct memory because type utilities don't work on raw data
FNiagaraVariable Variable(Type, FunctionInput->GetInputParameterHandle().GetParameterHandleString());
Variable.SetData(FunctionInput->GetLocalValueStruct()->GetStructMemory());
// by default we display type information. In some cases we have overrides, but even then we want to keep the type information as we continue using it in the tooltips
const FText ValueText = TypeUtilities->GetStackDisplayText(Variable);
FText DisplayedText = ValueText;
const FLinearColor OriginalTypeColor = UEdGraphSchema_Niagara::GetTypeColor(Type);
FLinearColor ActualTypeColor = OriginalTypeColor;
TSharedPtr<IToolTip> TooltipOverride;
// if we have an enum type or integer acting as an enum, we want to override the type color to be gray instead
if(Type.GetEnum() || (Type == FNiagaraTypeDefinition::GetIntDef() && InputMetaData.IsSet() && InputMetaData->WidgetCustomization.WidgetType == ENiagaraInputWidgetType::EnumStyle))
{
ActualTypeColor = FLinearColor(0.02f, 0.02f, 0.02f, 1.f);
}
FLinearColor DisplayedColor = InputMetaData->bOverrideColor ? InputMetaData->InlineParameterColorOverride : ActualTypeColor;
UTexture2D* Icon = nullptr;
if (const UEnum* Enum = Type.GetEnum())
{
int32 EnumIndex = Enum->GetIndexByValue(*(int32*)FunctionInput->GetLocalValueStruct()->GetStructMemory());
if(InputMetaData->InlineParameterEnumOverrides.IsValidIndex(EnumIndex))
{
const FNiagaraEnumParameterMetaData& EnumParameterMetaData = InputMetaData->InlineParameterEnumOverrides[EnumIndex];
// we only want to use the override name if it has been set
DisplayedText = !EnumParameterMetaData.OverrideName.IsNone() ? FText::FromName(EnumParameterMetaData.OverrideName) : DisplayedText;
Icon = EnumParameterMetaData.IconOverride;
DisplayedColor = EnumParameterMetaData.bUseColorOverride ? EnumParameterMetaData.ColorOverride : DisplayedColor;
}
}
else if(Type == FNiagaraTypeDefinition::GetIntDef() && InputMetaData->WidgetCustomization.WidgetType == ENiagaraInputWidgetType::EnumStyle)
{
int32 Value = *(int32*)FunctionInput->GetLocalValueStruct()->GetStructMemory();
if(InputMetaData->WidgetCustomization.EnumStyleDropdownValues.IsValidIndex(Value))
{
FString ValueString = FString::FromInt(Value);
FText DisplayName = InputMetaData->WidgetCustomization.EnumStyleDropdownValues[Value].DisplayName;
FText TooltipActualValueText = FText::FormatOrdered(FText::FromString("({0})"), FText::FromString(ValueString));
DisplayedText = DisplayName.IsEmptyOrWhitespace() == false ? DisplayName : FText::FromString(ValueString);
TSharedRef<SWidget> ValueOverrideTooltip = SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
.HAlign(HAlign_Center)
.VAlign(VAlign_Center)
.Padding(3.f)
[
SNew(STextBlock)
.Text(LOCTEXT("IntAsEnumParameterTooltipTextOverride", "Value: "))
]
+ SHorizontalBox::Slot()
.AutoWidth()
.HAlign(HAlign_Center)
.VAlign(VAlign_Center)
.Padding(3.f)
[
SNew(STextBlock)
.Text(DisplayedText)
]
+ SHorizontalBox::Slot()
.AutoWidth()
.HAlign(HAlign_Center)
.VAlign(VAlign_Center)
.Padding(3.f)
[
SNew(STextBlock)
.Text(TooltipActualValueText)
];
TooltipOverride = FNiagaraParameterUtilities::GetTooltipWidget(Variable, false, ValueOverrideTooltip);
}
}
else if(Type == FNiagaraTypeDefinition::GetBoolDef() && InputMetaData->bEnableBoolOverride)
{
const FNiagaraBoolParameterMetaData& BoolParameterMetaData = InputMetaData->InlineParameterBoolOverride;
int32 Val = *(int32*)FunctionInput->GetLocalValueStruct()->GetStructMemory();
bool bParameterValue = Val == 0 ? false : true;
if(bParameterValue == true)
{
DisplayedText = !BoolParameterMetaData.OverrideNameTrue.IsNone() ? FText::FromName(BoolParameterMetaData.OverrideNameTrue) : DisplayedText;
Icon = BoolParameterMetaData.IconOverrideTrue;
}
else
{
DisplayedText = !BoolParameterMetaData.OverrideNameFalse.IsNone() ? FText::FromName(BoolParameterMetaData.OverrideNameFalse) : DisplayedText;
Icon = BoolParameterMetaData.IconOverrideFalse;
}
if(BoolParameterMetaData.DisplayMode != ENiagaraBoolDisplayMode::DisplayAlways)
{
// in case we only want to display the variable in one case, we make sure to test against the correct value
bool bDisplayBool = BoolParameterMetaData.DisplayMode == ENiagaraBoolDisplayMode::DisplayIfTrue ? true : false;
if(bDisplayBool != bParameterValue)
{
bGenerateProperWidget = false;
}
}
}
else if(Type == FNiagaraTypeDefinition::GetColorDef())
{
DisplayedText = FText::FromName(FunctionInput->GetInputParameterHandle().GetName());
DisplayedColor = *(FLinearColor*)FunctionInput->GetLocalValueStruct()->GetStructMemory();
}
TSharedPtr<SWidget> ParameterWidget;
// we might have cases in which we don't want to display a parameter's data, such as if we only want to display a bool parameter when it is set to True
if(bGenerateProperWidget)
{
TWeakObjectPtr<const UNiagaraStackFunctionInput> FunctionInputWeak = FunctionInput;
TSharedRef<SButton> ParameterWidgetButton = SNew(SButton)
.Text(DisplayedText)
.TextStyle(&FNiagaraEditorWidgetsStyle::Get().GetWidgetStyle<FTextBlockStyle>("NiagaraEditor.SystemOverview.InlineParameterText"))
.ButtonStyle(&FNiagaraEditorWidgetsStyle::Get().GetWidgetStyle<FButtonStyle>("NiagaraEditor.SystemOverview.InlineParameterButton"))
.ContentPadding(FMargin(0.f))
.OnClicked(this, &SNiagaraOverviewInlineParameterBox::NavigateToStack, FunctionInputWeak);
// if we have an icon available, we use it instead of any text. Border color is irrelevant here.
if(Icon != nullptr)
{
FSlateImageBrush& ImageBrush = ImageBrushes.Emplace_GetRef(Icon, FVector2D(16.f, 16.f));
TSharedRef<SImage> ParameterWidgetIcon= SNew(SImage).Image(&ImageBrush);
ParameterWidgetButton->SetContent(ParameterWidgetIcon);
ParameterWidgetButton->SetButtonStyle(&FNiagaraEditorWidgetsStyle::Get().GetWidgetStyle<FButtonStyle>("NiagaraEditor.SystemOverview.InlineParameterButton.Icon"));
}
// otherwise, we use text and set the border to its specified color (type, override, instance override color)
else
{
// if we are using a custom color, we set the button style to use a white base in order to get rid of a tint
if(DisplayedColor != ActualTypeColor)
{
ParameterWidgetButton->SetButtonStyle(&FNiagaraEditorWidgetsStyle::Get().GetWidgetStyle<FButtonStyle>("NiagaraEditor.SystemOverview.InlineParameterButton.NoTint"));
}
// if we are an enum and haven't overridden the color, we use a style that better fits the text
else if(Type.GetEnum())
{
ParameterWidgetButton->SetButtonStyle(&FNiagaraEditorWidgetsStyle::Get().GetWidgetStyle<FButtonStyle>("NiagaraEditor.SystemOverview.InlineParameterButton.Enum"));
}
ParameterWidgetButton->SetBorderBackgroundColor(DisplayedColor);
}
ParameterWidget = ParameterWidgetButton;
}
else
{
return SNullWidget::NullWidget;
}
// we construct a tooltip widget that shows the parameter the value is associated with
if(TooltipOverride != nullptr)
{
ParameterWidget->SetToolTip(TooltipOverride);
}
else
{
TSharedRef<SToolTip> TooltipWidget = FNiagaraParameterUtilities::GetTooltipWidget(Variable);
ParameterWidget->SetToolTip(TooltipWidget);
}
return ParameterWidget.ToSharedRef();
}
TSharedRef<SWidget> SNiagaraOverviewInlineParameterBox::GenerateParameterWidgetFromDataInterface(UNiagaraStackFunctionInput* FunctionInput)
{
// if(DataInterfaceInput.DataInterface.IsValid())
// {
// DataInterfaceInput.OnValueChanged.RemoveAll(this);
// DataInterfaceInput.OnValueChanged.AddSP(this, &SNiagaraOverviewInlineParameterBox::ConstructChildren);
// TArray<FNiagaraDataInterfaceError> Errors;
// TArray<FNiagaraDataInterfaceFeedback> Warnings;
// TArray<FNiagaraDataInterfaceFeedback> Infos;
//
// // these can be nullptr depending on context
// UNiagaraSystem* System = DataInterfaceInput.DataInterface->GetTypedOuter<UNiagaraSystem>();
// UNiagaraComponent* Component = DataInterfaceInput.DataInterface->GetTypedOuter<UNiagaraComponent>();
//
// //@TODO Finish this up.
// //DataInterfaceInput.DataInterface->GetInlineFeedback(System, Component, Errors, Warnings, Infos);
//
// TSharedPtr<SHorizontalBox> InfoBox = SNew(SHorizontalBox);
// for(FNiagaraDataInterfaceFeedback& Feedback : Infos)
// {
// InfoBox->AddSlot()
// .AutoWidth()
// [
// SNew(STextBlock)
// .Text(Feedback.GetFeedbackSummaryText())
// .ToolTipText(Feedback.GetFeedbackText())
// .TextStyle(&FNiagaraEditorWidgetsStyle::Get().GetWidgetStyle<FTextBlockStyle>("NiagaraEditor.SystemOverview.InlineParameterText"))
// ];
// }
//
// return InfoBox.ToSharedRef();
//}
return SNullWidget::NullWidget;
}
TWeakObjectPtr<const UNiagaraStackFunctionInput> SNiagaraOverviewInlineParameterBox::FindSubstituteEntry(const UNiagaraStackFunctionInput* InInput)
{
// there can only be substitute entries for inline edit condition toggles
if(!InInput->GetInputMetaData().IsSet() || !InInput->GetInputMetaData()->bInlineEditConditionToggle)
{
return nullptr;
}
TArray<UNiagaraStackFunctionInput*> AllInputs;
ModuleItem->GetParameterInputs(AllInputs);
// we gather all edit conditions here so we can later map them back
for(const UNiagaraStackFunctionInput* Input : AllInputs)
{
if(Input->GetHasEditCondition())
{
if(Input->GetEditConditionVariable().IsSet())
{
FNiagaraParameterHandle EditHandle(Input->GetEditConditionVariable().GetValue().GetName());
if(InInput->GetInputParameterHandle() == EditHandle)
{
return Input;
}
}
}
}
return nullptr;
}
void SNiagaraOverviewInlineParameterBox::OnModuleItemStructureChanged(ENiagaraStructureChangedFlags)
{
ConstructChildren();
}
#undef LOCTEXT_NAMESPACE