// Copyright Epic Games, Inc. All Rights Reserved. #include "SMetasoundGraphNode.h" #include "AudioParameterControllerInterface.h" #include "Components/AudioComponent.h" #include "EditorStyleSet.h" #include "GraphEditorSettings.h" #include "IAudioParameterTransmitter.h" #include "IDocumentation.h" #include "KismetPins/SGraphPinBool.h" #include "KismetPins/SGraphPinExec.h" #include "KismetPins/SGraphPinInteger.h" #include "KismetPins/SGraphPinNum.h" #include "KismetPins/SGraphPinObject.h" #include "KismetPins/SGraphPinString.h" #include "MetasoundEditorGraph.h" #include "MetasoundEditorGraphBuilder.h" #include "MetasoundEditorGraphInputNode.h" #include "MetasoundEditorGraphMemberDefaults.h" #include "MetasoundEditorGraphNode.h" #include "MetasoundEditorGraphSchema.h" #include "MetasoundEditorModule.h" #include "MetasoundFrontendArchetypeRegistry.h" #include "MetasoundFrontendRegistries.h" #include "MetasoundTrigger.h" #include "NodeFactory.h" #include "PropertyCustomizationHelpers.h" #include "SAudioRadialSlider.h" #include "SAudioSlider.h" #include "SCommentBubble.h" #include "ScopedTransaction.h" #include "SGraphNode.h" #include "SGraphPinComboBox.h" #include "SLevelOfDetailBranchNode.h" #include "SMetasoundGraphEnumPin.h" #include "SMetasoundGraphPin.h" #include "Styling/AppStyle.h" #include "Styling/SlateColor.h" #include "Styling/SlateStyleRegistry.h" #include "TutorialMetaData.h" #include "UObject/ScriptInterface.h" #include "Widgets/Images/SImage.h" #include "Widgets/Input/SButton.h" #include "Widgets/Layout/SBox.h" #include "Widgets/Layout/SSpacer.h" #include "Widgets/SBoxPanel.h" #include "Widgets/SOverlay.h" #include "Widgets/SWidget.h" #include "Widgets/Text/SInlineEditableTextBlock.h" #define LOCTEXT_NAMESPACE "MetasoundGraphNode" namespace Metasound { namespace Editor { SMetaSoundGraphNode::~SMetaSoundGraphNode() { UMetasoundEditorGraphNode& Node = GetMetaSoundNode(); if (UMetasoundEditorGraphMemberNode* MemberNode = Cast(&Node)) { if (UMetasoundEditorGraphMember* GraphMember = MemberNode->GetMember()) { if (UMetasoundEditorGraphMemberDefaultFloat* DefaultFloat = Cast(GraphMember->GetLiteral())) { DefaultFloat->OnDefaultValueChanged.Remove(InputSliderOnValueChangedDelegateHandle); DefaultFloat->OnRangeChanged.Remove(InputSliderOnRangeChangedDelegateHandle); } } } } bool SMetaSoundGraphNode::IsVariableAccessor() const { const EMetasoundFrontendClassType ClassType = GetMetaSoundNode().GetNodeHandle()->GetClassMetadata().GetType(); return ClassType == EMetasoundFrontendClassType::VariableAccessor || ClassType == EMetasoundFrontendClassType::VariableDeferredAccessor; } bool SMetaSoundGraphNode::IsVariableMutator() const { const EMetasoundFrontendClassType ClassType = GetMetaSoundNode().GetNodeHandle()->GetClassMetadata().GetType(); return ClassType == EMetasoundFrontendClassType::VariableMutator; } const FSlateBrush* SMetaSoundGraphNode::GetShadowBrush(bool bSelected) const { if (IsVariableAccessor() || IsVariableMutator()) { return bSelected ? FEditorStyle::GetBrush(TEXT("Graph.VarNode.ShadowSelected")) : FEditorStyle::GetBrush(TEXT("Graph.VarNode.Shadow")); } return SGraphNode::GetShadowBrush(bSelected); } void SMetaSoundGraphNode::Construct(const FArguments& InArgs, class UEdGraphNode* InNode) { GraphNode = InNode; SetCursor(EMouseCursor::CardinalCross); UpdateGraphNode(); } void SMetaSoundGraphNode::ExecuteTrigger(UMetasoundEditorGraphMemberDefaultLiteral& Literal) { UMetasoundEditorGraphMember* Member = Cast(Literal.GetOuter()); if (!ensure(Member)) { return; } if (UMetasoundEditorGraph* Graph = Member->GetOwningGraph()) { if (!Graph->IsPreviewing()) { TSharedPtr MetaSoundEditor = FGraphBuilder::GetEditorForMetasound(Graph->GetMetasoundChecked()); if (!MetaSoundEditor.IsValid()) { return; } MetaSoundEditor->Play(); } } if (UAudioComponent* PreviewComponent = GEditor->GetPreviewAudioComponent()) { PreviewComponent->SetTriggerParameter(Member->GetMemberName()); } } TAttribute SMetaSoundGraphNode::GetSimulationVisibilityAttribute() const { return TAttribute::CreateLambda([this]() { using namespace Frontend; if (const UMetasoundEditorGraphMemberNode* Node = Cast(&GetMetaSoundNode())) { if (const UMetasoundEditorGraphVertex* Vertex = Cast(Node->GetMember())) { if (const UMetasoundEditorGraph* Graph = Vertex->GetOwningGraph()) { if (!Graph->IsPreviewing()) { return EVisibility::Hidden; } } // Don't enable trigger simulation widget if its a trigger provided by an interface // that does not support transmission. const FInterfaceRegistryKey Key = GetInterfaceRegistryKey(Vertex->GetInterfaceVersion()); const IInterfaceRegistryEntry* Entry = IInterfaceRegistry::Get().FindInterfaceRegistryEntry(Key); if (Entry && Entry->GetRouterName() != Audio::IParameterTransmitter::RouterName) { return EVisibility::Hidden; } else if (const UMetasoundEditorGraphMemberDefaultLiteral* Literal = Vertex->GetLiteral()) { if (!Literal) { return EVisibility::Hidden; } } } } return EVisibility::Visible; }); } TSharedRef SMetaSoundGraphNode::CreateTriggerSimulationWidget(UMetasoundEditorGraphMemberDefaultLiteral& InputLiteral, TAttribute&& InVisibility, TAttribute&& InEnablement, const FText* InToolTip) { const FText ToolTip = InToolTip ? *InToolTip : LOCTEXT("TriggerTestToolTip", "Executes trigger if currently previewing MetaSound."); TSharedPtr SimulationButton; TSharedRef SimulationWidget = SNew(SHorizontalBox) + SHorizontalBox::Slot() .Padding(2.0f, 0.0f, 0.0f, 0.0f) .HAlign(HAlign_Left) .VAlign(VAlign_Center) [ SAssignNew(SimulationButton, SButton) .ButtonStyle(FAppStyle::Get(), "SimpleButton") .OnClicked_Lambda([LiteralPtr = TWeakObjectPtr(&InputLiteral)]() { if (LiteralPtr.IsValid()) { ExecuteTrigger(*LiteralPtr.Get()); } return FReply::Handled(); }) .ToolTipText(ToolTip) .ForegroundColor(FSlateColor::UseForeground()) .ContentPadding(0) .IsFocusable(false) [ SNew(SImage) .Image(FAppStyle::Get().GetBrush("Icons.CircleArrowDown")) .ColorAndOpacity(FSlateColor::UseForeground()) ] .Visibility(MoveTemp(InVisibility)) ]; SimulationButton->SetEnabled(MoveTemp(InEnablement)); return SimulationWidget; } void SMetaSoundGraphNode::CreateInputSideAddButton(TSharedPtr InputBox) { TSharedRef AddPinButton = AddPinButtonContent( LOCTEXT("MetasoundGraphNode_AddPinInputButton", "Add Input"), LOCTEXT("MetasoundGraphNode_AddPinInputButton_Tooltip", "Add an input to the parent Metasound node.") ); FMargin AddPinPadding = Settings->GetOutputPinPadding(); AddPinPadding.Top += 6.0f; InputBox->AddSlot() .AutoHeight() .VAlign(VAlign_Center) .Padding(AddPinPadding) [ AddPinButton ]; } void SMetaSoundGraphNode::CreateOutputSideAddButton(TSharedPtr OutputBox) { TSharedRef AddPinButton = AddPinButtonContent( LOCTEXT("MetasoundGraphNode_AddPinOutputButton", "Add Output"), LOCTEXT("MetasoundGraphNode_AddPinOutputButton_Tooltip", "Add an output to the parent Metasound node.") ); FMargin AddPinPadding = Settings->GetOutputPinPadding(); AddPinPadding.Top += 6.0f; OutputBox->AddSlot() .AutoHeight() .VAlign(VAlign_Center) .Padding(AddPinPadding) [ AddPinButton ]; } UMetasoundEditorGraphNode& SMetaSoundGraphNode::GetMetaSoundNode() { return *CastChecked(GraphNode); } const UMetasoundEditorGraphNode& SMetaSoundGraphNode::GetMetaSoundNode() const { check(GraphNode); return *Cast(GraphNode); } TSharedPtr SMetaSoundGraphNode::CreatePinWidget(UEdGraphPin* InPin) const { using namespace Frontend; TSharedPtr PinWidget; if (const UMetasoundEditorGraphSchema* GraphSchema = Cast(InPin->GetSchema())) { // Don't show default value field for container types if (InPin->PinType.ContainerType != EPinContainerType::None) { PinWidget = SNew(SMetasoundGraphPin, InPin); } else if (InPin->PinType.PinCategory == FGraphBuilder::PinCategoryAudio) { PinWidget = SNew(SMetasoundGraphPin, InPin); } else if (InPin->PinType.PinCategory == FGraphBuilder::PinCategoryBoolean) { PinWidget = SNew(SMetasoundGraphPinBool, InPin); } else if (InPin->PinType.PinCategory == FGraphBuilder::PinCategoryFloat) { PinWidget = SNew(SMetasoundGraphPinFloat, InPin); } else if (InPin->PinType.PinCategory == FGraphBuilder::PinCategoryInt32) { if (SMetasoundGraphEnumPin::FindEnumInterfaceFromPin(InPin)) { PinWidget = SNew(SMetasoundGraphEnumPin, InPin); } else { PinWidget = SNew(SMetasoundGraphPinInteger, InPin); } } else if (InPin->PinType.PinCategory == FGraphBuilder::PinCategoryObject) { PinWidget = SNew(SMetasoundGraphPinObject, InPin); } else if (InPin->PinType.PinCategory == FGraphBuilder::PinCategoryString) { PinWidget = SNew(SMetasoundGraphPinString, InPin); } else if (InPin->PinType.PinCategory == FGraphBuilder::PinCategoryTrigger) { PinWidget = SNew(SMetasoundGraphPin, InPin); if (const ISlateStyle* MetasoundStyle = FSlateStyleRegistry::FindSlateStyle("MetaSoundStyle")) { const FSlateBrush* PinConnectedBrush = MetasoundStyle->GetBrush(TEXT("MetasoundEditor.Graph.TriggerPin.Connected")); const FSlateBrush* PinDisconnectedBrush = MetasoundStyle->GetBrush(TEXT("MetasoundEditor.Graph.TriggerPin.Disconnected")); PinWidget->SetCustomPinIcon(PinConnectedBrush, PinDisconnectedBrush); } } } if (!PinWidget.IsValid()) { PinWidget = SNew(SMetasoundGraphPin, InPin); } return PinWidget; } void SMetaSoundGraphNode::CreateStandardPinWidget(UEdGraphPin* InPin) { const bool bShowPin = ShouldPinBeHidden(InPin); if (bShowPin) { TSharedPtr NewPin = CreatePinWidget(InPin); check(NewPin.IsValid()); Metasound::Frontend::FNodeHandle NodeHandle = GetMetaSoundNode().GetNodeHandle(); if (InPin->Direction == EGPD_Input) { if (!NodeHandle->GetClassStyle().Display.bShowInputNames) { NewPin->SetShowLabel(false); } } else if (InPin->Direction == EGPD_Output) { if (!NodeHandle->GetClassStyle().Display.bShowOutputNames) { NewPin->SetShowLabel(false); } } AddPin(NewPin.ToSharedRef()); } } TSharedRef SMetaSoundGraphNode::CreateTitleWidget(TSharedPtr NodeTitle) { Metasound::Frontend::FNodeHandle NodeHandle = GetMetaSoundNode().GetNodeHandle(); if (!NodeHandle->GetClassStyle().Display.bShowName) { return SNullWidget::NullWidget; } TSharedPtr TitleBoxWidget = SNew(SHorizontalBox); FSlateIcon NodeIcon = GetMetaSoundNode().GetNodeTitleIcon(); if (const FSlateBrush* IconBrush = NodeIcon.GetIcon()) { if (IconBrush != FStyleDefaults::GetNoBrush()) { TSharedPtr Image; TitleBoxWidget->AddSlot() .AutoWidth() .VAlign(VAlign_Center) .HAlign(HAlign_Right) [ SNew(SHorizontalBox) + SHorizontalBox::Slot() .Padding(0.0f, 0.0f, 4.0f, 0.0f) .VAlign(VAlign_Center) .HAlign(HAlign_Right) [ SAssignNew(Image, SImage) ] ]; Image->SetColorAndOpacity(TAttribute::CreateLambda([this]() { return FSlateColor(GetNodeTitleColorOverride()); })); Image->SetImage(IconBrush); } } TitleBoxWidget->AddSlot() .AutoWidth() .VAlign(VAlign_Center) [ SGraphNode::CreateTitleWidget(NodeTitle) ]; InlineEditableText->SetColorAndOpacity(TAttribute::Create(TAttribute::FGetter::CreateSP(this, &SMetaSoundGraphNode::GetNodeTitleColorOverride))); return TitleBoxWidget.ToSharedRef(); } void SMetaSoundGraphNode::GetOverlayBrushes(bool bSelected, const FVector2D WidgetSize, TArray& Brushes) const { FName CornerIcon = GetMetaSoundNode().GetCornerIcon(); if (CornerIcon != NAME_None) { if (const FSlateBrush* Brush = FEditorStyle::GetBrush(CornerIcon)) { FOverlayBrushInfo OverlayInfo = { Brush }; // Logic copied from SGraphNodeK2Base OverlayInfo.OverlayOffset.X = (WidgetSize.X - (OverlayInfo.Brush->ImageSize.X / 2.f)) - 3.f; OverlayInfo.OverlayOffset.Y = (OverlayInfo.Brush->ImageSize.Y / -2.f) + 2.f; Brushes.Add(MoveTemp(OverlayInfo)); } } } FLinearColor SMetaSoundGraphNode::GetNodeTitleColorOverride() const { FLinearColor ReturnTitleColor = GraphNode->IsDeprecated() ? FLinearColor::Red : GetNodeObj()->GetNodeTitleColor(); if (!GraphNode->IsNodeEnabled() || GraphNode->IsDisplayAsDisabledForced() || GraphNode->IsNodeUnrelated()) { ReturnTitleColor *= FLinearColor(0.5f, 0.5f, 0.5f, 0.4f); } else { ReturnTitleColor.A = FadeCurve.GetLerp(); } return ReturnTitleColor; } void SMetaSoundGraphNode::SetDefaultTitleAreaWidget(TSharedRef DefaultTitleAreaWidget) { SGraphNode::SetDefaultTitleAreaWidget(DefaultTitleAreaWidget); Metasound::Frontend::FNodeHandle NodeHandle = GetMetaSoundNode().GetNodeHandle(); if (NodeHandle->GetClassStyle().Display.bShowName) { DefaultTitleAreaWidget->ClearChildren(); TSharedPtr NodeTitle = SNew(SNodeTitle, GraphNode); DefaultTitleAreaWidget->AddSlot() .HAlign(HAlign_Fill) .VAlign(VAlign_Center) [ SNew(SHorizontalBox) + SHorizontalBox::Slot() .HAlign(HAlign_Fill) [ SNew(SBorder) .BorderImage(FEditorStyle::GetBrush("NoBorder")) [ SNew(SHorizontalBox) + SHorizontalBox::Slot() [ SNew(SVerticalBox) + SVerticalBox::Slot() .AutoHeight() .HAlign(HAlign_Center) [ CreateTitleWidget(NodeTitle) ] + SVerticalBox::Slot() .AutoHeight() [ NodeTitle.ToSharedRef() ] ] ] ] + SHorizontalBox::Slot() .HAlign(HAlign_Right) .VAlign(VAlign_Center) .Padding(0, 0, 5, 0) .AutoWidth() [ CreateTitleRightWidget() ] ]; DefaultTitleAreaWidget->AddSlot() .VAlign(VAlign_Top) [ SNew(SBorder) .Visibility(EVisibility::HitTestInvisible) .BorderImage( FEditorStyle::GetBrush( "Graph.Node.TitleHighlight" ) ) .BorderBackgroundColor( this, &SGraphNode::GetNodeTitleIconColor ) [ SNew(SSpacer) .Size(FVector2D(20,20)) ] ]; } else { DefaultTitleAreaWidget->SetVisibility(EVisibility::Collapsed); } } void SMetaSoundGraphNode::MoveTo(const FVector2D& NewPosition, FNodeSet& NodeFilter, bool bMarkDirty) { SGraphNode::MoveTo(NewPosition, NodeFilter, bMarkDirty); GetMetaSoundNode().UpdatePosition(); } const FSlateBrush* SMetaSoundGraphNode::GetNodeBodyBrush() const { // TODO: Add tweak & add custom bodies if (GraphNode) { const EMetasoundFrontendClassType ClassType = GetMetaSoundNode().GetNodeHandle()->GetClassMetadata().GetType(); switch (ClassType) { case EMetasoundFrontendClassType::Variable: case EMetasoundFrontendClassType::VariableAccessor: case EMetasoundFrontendClassType::VariableDeferredAccessor: case EMetasoundFrontendClassType::VariableMutator: { return FEditorStyle::GetBrush("Graph.VarNode.Body"); } break; case EMetasoundFrontendClassType::Input: case EMetasoundFrontendClassType::Output: default: { } break; } } return FEditorStyle::GetBrush("Graph.Node.Body"); } EVisibility SMetaSoundGraphNode::IsAddPinButtonVisible() const { EVisibility DefaultVisibility = SGraphNode::IsAddPinButtonVisible(); if (DefaultVisibility == EVisibility::Visible) { if (!GetMetaSoundNode().CanAddInputPin()) { return EVisibility::Collapsed; } } return DefaultVisibility; } FReply SMetaSoundGraphNode::OnAddPin() { GetMetaSoundNode().CreateInputPin(); return FReply::Handled(); } FName SMetaSoundGraphNode::GetLiteralDataType() const { using namespace Frontend; FName TypeName; // Just take last type. If more than one, all types are the same. const UMetasoundEditorGraphNode& Node = GetMetaSoundNode(); Node.GetNodeHandle()->IterateConstOutputs([InTypeName = &TypeName](FConstOutputHandle OutputHandle) { *InTypeName = OutputHandle->GetDataType(); }); return TypeName; } TSharedRef SMetaSoundGraphNode::CreateTitleRightWidget() { using namespace Frontend; const FName TypeName = GetLiteralDataType(); if (TypeName == Metasound::GetMetasoundDataTypeName()) { if (UMetasoundEditorGraphMemberNode* Node = Cast(&GetMetaSoundNode())) { if (UMetasoundEditorGraphInput* Input = Cast(Node->GetMember())) { if (UMetasoundEditorGraphMemberDefaultLiteral* Literal = Input->GetLiteral()) { TAttribute SimVisibility = GetSimulationVisibilityAttribute(); TAttribute SimEnablement = true; return CreateTriggerSimulationWidget(*Literal, MoveTemp(SimVisibility), MoveTemp(SimEnablement)); } } } } return SGraphNode::CreateTitleRightWidget(); } UMetasoundEditorGraphMember* SMetaSoundGraphNode::GetMetaSoundMember() { if (UMetasoundEditorGraphMemberNode* MemberNode = GetMetaSoundMemberNode()) { return MemberNode->GetMember(); } return nullptr; } UMetasoundEditorGraphMemberNode* SMetaSoundGraphNode::GetMetaSoundMemberNode() { return Cast(&GetMetaSoundNode()); } TSharedRef SMetaSoundGraphNode::CreateNodeContentArea() { using namespace Frontend; FNodeHandle NodeHandle = GetMetaSoundNode().GetNodeHandle(); const FMetasoundFrontendClassStyleDisplay& StyleDisplay = NodeHandle->GetClassStyle().Display; TSharedPtr ContentBox = SNew(SHorizontalBox); TSharedPtr OuterContentBox; // currently only used for input float nodes to accommodate the input widget // If float input node, check if custom widget required bool IsFloatMemberNode = false; if (UMetasoundEditorGraphMember* GraphMember = GetMetaSoundMember()) { if (UMetasoundEditorGraphMemberDefaultFloat* DefaultFloat = Cast(GraphMember->GetLiteral())) { if (DefaultFloat->WidgetType != EMetasoundMemberDefaultWidget::None) { constexpr float WidgetPadding = 3.0f; static const FVector2D SliderDesiredSizeVertical = FVector2D(30.0f, 250.0f); static const FVector2D RadialSliderDesiredSize = FVector2D(56.0f, 87.0f); IsFloatMemberNode = true; auto OnValueChangedLambda = [DefaultFloat, GraphMember, this](float Value) { if (InputWidget.IsValid()) { constexpr bool bPostTransaction = false; float Output = InputWidget->GetOutputValue(Value); DefaultFloat->SetDefault(Output); GraphMember->UpdateFrontendDefaultLiteral(bPostTransaction); } }; auto OnValueCommittedLambda = [DefaultFloat, GraphMember, this](float Value) { if (InputWidget.IsValid()) { constexpr bool bPostTransaction = true; float Output = InputWidget->GetOutputValue(Value); DefaultFloat->SetDefault(Output); GraphMember->UpdateFrontendDefaultLiteral(bPostTransaction); if (UMetasoundEditorGraph* Graph = GraphMember->GetOwningGraph()) { Graph->SetSynchronizationRequired(); } } }; if (DefaultFloat->WidgetType == EMetasoundMemberDefaultWidget::Slider) { // Create slider if (DefaultFloat->WidgetValueType == EMetasoundMemberDefaultWidgetValueType::Frequency) { SAssignNew(InputWidget, SAudioFrequencySlider) .OnValueChanged_Lambda(OnValueChangedLambda) .OnValueCommitted_Lambda(OnValueCommittedLambda); } else if (DefaultFloat->WidgetValueType == EMetasoundMemberDefaultWidgetValueType::Volume) { SAssignNew(InputWidget, SAudioVolumeSlider) .OnValueChanged_Lambda(OnValueChangedLambda) .OnValueCommitted_Lambda(OnValueCommittedLambda); StaticCastSharedPtr(InputWidget)->SetUseLinearOutput(DefaultFloat->VolumeWidgetUseLinearOutput); } else { SAssignNew(InputWidget, SAudioSlider) .OnValueChanged_Lambda(OnValueChangedLambda) .OnValueCommitted_Lambda(OnValueCommittedLambda); InputWidget->SetShowUnitsText(false); } // Slider layout if (DefaultFloat->WidgetOrientation == Orient_Vertical) { SAssignNew(OuterContentBox, SVerticalBox) + SVerticalBox::Slot() .HAlign(HAlign_Right) .VAlign(VAlign_Center) .AutoHeight() [ ContentBox.ToSharedRef() ] + SVerticalBox::Slot() .HAlign(HAlign_Fill) .VAlign(VAlign_Top) .Padding(WidgetPadding, 0.0f, WidgetPadding, WidgetPadding) .AutoHeight() [ InputWidget.ToSharedRef() ]; InputWidget->SetDesiredSizeOverride(SliderDesiredSizeVertical); } else // horizontal orientation { UMetasoundEditorGraphMemberNode* MemberNode = GetMetaSoundMemberNode(); TSharedPtr Slot1; TSharedPtr Slot2; if (MemberNode->IsA()) { Slot1 = InputWidget; Slot2 = ContentBox; } else { Slot1 = ContentBox; Slot2 = InputWidget; } SAssignNew(OuterContentBox, SHorizontalBox) + SHorizontalBox::Slot() .HAlign(HAlign_Fill) .VAlign(VAlign_Center) .AutoWidth() [ Slot1.ToSharedRef() ] + SHorizontalBox::Slot() .HAlign(HAlign_Center) .VAlign(VAlign_Fill) .Padding(WidgetPadding, 0.0f, 0.0f, WidgetPadding) .AutoWidth() [ Slot2.ToSharedRef() ]; InputWidget->SetDesiredSizeOverride(FVector2D(SliderDesiredSizeVertical.Y, SliderDesiredSizeVertical.X)); } // safe downcast because the ptr was just assigned above StaticCastSharedPtr(InputWidget)->SetOrientation(DefaultFloat->WidgetOrientation); } else if (DefaultFloat->WidgetType == EMetasoundMemberDefaultWidget::RadialSlider) { // Create slider if (DefaultFloat->WidgetValueType == EMetasoundMemberDefaultWidgetValueType::Frequency) { SAssignNew(InputWidget, SAudioFrequencyRadialSlider) .OnValueChanged_Lambda(OnValueChangedLambda); } else if (DefaultFloat->WidgetValueType == EMetasoundMemberDefaultWidgetValueType::Volume) { SAssignNew(InputWidget, SAudioVolumeRadialSlider) .OnValueChanged_Lambda(OnValueChangedLambda); StaticCastSharedPtr(InputWidget)->SetUseLinearOutput(DefaultFloat->VolumeWidgetUseLinearOutput); } else { SAssignNew(InputWidget, SAudioRadialSlider) .OnValueChanged_Lambda(OnValueChangedLambda); InputWidget->SetShowUnitsText(false); } // Only vertical layout for radial slider SAssignNew(OuterContentBox, SVerticalBox) + SVerticalBox::Slot() .HAlign(HAlign_Right) .VAlign(VAlign_Center) .AutoHeight() [ ContentBox.ToSharedRef() ] + SVerticalBox::Slot() .HAlign(HAlign_Fill) .VAlign(VAlign_Top) .Padding(WidgetPadding, 0.0f, WidgetPadding, WidgetPadding) .AutoHeight() [ InputWidget.ToSharedRef() ]; InputWidget->SetDesiredSizeOverride(RadialSliderDesiredSize); } InputWidget->SetOutputRange(DefaultFloat->GetRange()); InputWidget->SetUnitsTextReadOnly(true); InputWidget->SetValue(InputWidget->GetLinValue(DefaultFloat->GetDefault())); InputWidget->SetVisibility(TAttribute::Create([this]() { if (UMetasoundEditorGraphMemberNode* Node = GetMetaSoundMemberNode()) { return Node->EnableInteractWidgets() ? EVisibility::Visible : EVisibility::Collapsed; } return EVisibility::Collapsed; })); // Setup & clear delegate if necessary (ex. if was just saved) if (InputSliderOnValueChangedDelegateHandle.IsValid()) { DefaultFloat->OnDefaultValueChanged.Remove(InputSliderOnValueChangedDelegateHandle); InputSliderOnValueChangedDelegateHandle.Reset(); } InputSliderOnValueChangedDelegateHandle = DefaultFloat->OnDefaultValueChanged.AddLambda([Widget = InputWidget](float Value) { if (Widget.IsValid()) { const float LinValue = Widget->GetLinValue(Value); Widget->SetValue(LinValue); } }); if (InputSliderOnRangeChangedDelegateHandle.IsValid()) { DefaultFloat->OnRangeChanged.Remove(InputSliderOnRangeChangedDelegateHandle); InputSliderOnRangeChangedDelegateHandle.Reset(); } InputSliderOnRangeChangedDelegateHandle = DefaultFloat->OnRangeChanged.AddLambda([Widget = InputWidget](FVector2D Range) { if (Widget.IsValid()) { Widget->SetOutputRange(Range); } }); } } } static const float GrabPadding = 28.0f; // Gives more space for user to grab a bit easier as variables do not have any title area nor icon const float LeftNodeGrabPadding = IsVariableMutator() ? GrabPadding : 0.0f; ContentBox->AddSlot() .HAlign(HAlign_Left) .VAlign(VAlign_Top) .AutoWidth() .Padding(0.0f, 0.0f, LeftNodeGrabPadding, 0.0f) [ SAssignNew(LeftNodeBox, SVerticalBox) ]; if (!StyleDisplay.ImageName.IsNone()) { if (const ISlateStyle* MetasoundStyle = FSlateStyleRegistry::FindSlateStyle("MetaSoundStyle")) { if (const FSlateBrush* ImageBrush = MetasoundStyle->GetBrush(StyleDisplay.ImageName)) { ContentBox->AddSlot() .AutoWidth() .HAlign(HAlign_Center) .VAlign(VAlign_Center) [ SNew(SImage) .Image(ImageBrush) .ColorAndOpacity(FSlateColor::UseForeground()) .DesiredSizeOverride(FVector2D(20, 20)) ]; } } } // Gives more space for user to grab a bit easier as variables do not have any title area nor icon const float RightNodeGrabPadding = IsVariableAccessor() ? GrabPadding : 0.0f; ContentBox->AddSlot() .AutoWidth() .HAlign(HAlign_Right) .VAlign(VAlign_Center) .Padding(RightNodeGrabPadding, 0.0f, 0.0f, 0.0f) [ SAssignNew(RightNodeBox, SVerticalBox) ]; return SNew(SBorder) .BorderImage(FEditorStyle::GetBrush("NoBorder")) .HAlign(HAlign_Fill) .VAlign(VAlign_Fill) .Padding(FMargin(0,3)) [ (IsFloatMemberNode ? OuterContentBox : ContentBox).ToSharedRef() ]; } } // namespace Editor } // namespace Metasound #undef LOCTEXT_NAMESPACE // MetasoundGraphNode