// Copyright Epic Games, Inc. All Rights Reserved. #include "SMetasoundGraphNode.h" #include "AudioMaterialSlate/SAudioMaterialButton.h" #include "AudioMaterialSlate/SAudioMaterialLabeledKnob.h" #include "AudioMaterialSlate/SAudioMaterialLabeledSlider.h" #include "AudioParameterControllerInterface.h" #include "AudioWidgetsEnums.h" #include "Components/AudioComponent.h" #include "GraphEditorSettings.h" #include "IAudioParameterTransmitter.h" #include "IDocumentation.h" #include "Interfaces/MetasoundFrontendInterfaceRegistry.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 "MetasoundBuilderSubsystem.h" #include "MetasoundDocumentBuilderRegistry.h" #include "MetasoundEditorGraph.h" #include "MetasoundEditorGraphBuilder.h" #include "MetasoundEditorGraphInputNode.h" #include "MetasoundEditorGraphMemberDefaults.h" #include "MetasoundEditorGraphNode.h" #include "MetasoundEditorGraphNodeVisualization.h" #include "MetasoundEditorGraphSchema.h" #include "MetasoundEditorModule.h" #include "MetasoundEditorSettings.h" #include "MetasoundEditorSubsystem.h" #include "MetasoundFrontendNodeTemplateRegistry.h" #include "MetasoundFrontendRegistries.h" #include "MetasoundSettings.h" #include "MetasoundTrace.h" #include "MetasoundTrigger.h" #include "NodeFactory.h" #include "NodeTemplates/MetasoundFrontendNodeTemplateReroute.h" #include "PropertyCustomizationHelpers.h" #include "SAudioRadialSlider.h" #include "SAudioSlider.h" #include "ScopedTransaction.h" #include "SGraphNode.h" #include "SGraphPinComboBox.h" #include "Styling/AppStyle.h" #include "SLevelOfDetailBranchNode.h" #include "SMetasoundGraphEnumPin.h" #include "SMetasoundGraphPin.h" #include "SMetasoundPinValueInspector.h" #include "SPinTypeSelector.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 "MetasoundEditor" namespace Metasound { namespace Editor { SMetaSoundGraphNode::~SMetaSoundGraphNode() { // Clean up input widgets UMetasoundEditorGraphNode& Node = GetMetaSoundNode(); if (UMetasoundEditorGraphMemberNode* MemberNode = Cast(&Node)) { if (UMetasoundEditorGraphMember* GraphMember = MemberNode->GetMember()) { // This may hit if the asset editor is closed while interacting with a widget // (ex. Ctrl-W is pressed mid drag before the value is committed) if (bIsInputWidgetTransacting) { GEditor->EndTransaction(); if (UMetasoundEditorGraph* Graph = GraphMember->GetOwningGraph()) { constexpr bool bPostTransaction = false; GraphMember->UpdateFrontendDefaultLiteral(bPostTransaction); FGraphBuilder::GetOutermostMetaSoundChecked(*Graph).GetModifyContext().AddMemberIDsModified({ GraphMember->GetMemberID() }); } } if (UMetasoundEditorGraphMemberDefaultFloat* DefaultFloat = Cast(GraphMember->GetLiteral())) { DefaultFloat->OnDefaultValueChanged.Remove(InputSliderOnValueChangedDelegateHandle); DefaultFloat->OnRangeChanged.Remove(InputSliderOnRangeChangedDelegateHandle); } else if (UMetasoundEditorGraphMemberDefaultBool* DefaultBool = Cast(GraphMember->GetLiteral())) { DefaultBool->OnDefaultStateChanged.Remove(InputButtonOnStateChangedDelegateHandle); } } } } bool SMetaSoundGraphNode::IsVariableAccessor() const { return ClassType == EMetasoundFrontendClassType::VariableAccessor || ClassType == EMetasoundFrontendClassType::VariableDeferredAccessor; } bool SMetaSoundGraphNode::IsVariableMutator() const { return ClassType == EMetasoundFrontendClassType::VariableMutator; } const FSlateBrush* SMetaSoundGraphNode::GetShadowBrush(bool bSelected) const { if (IsVariableAccessor() || IsVariableMutator()) { return bSelected ? FAppStyle::GetBrush(TEXT("Graph.VarNode.ShadowSelected")) : FAppStyle::GetBrush(TEXT("Graph.VarNode.Shadow")); } return SGraphNode::GetShadowBrush(bSelected); } void SMetaSoundGraphNode::Construct(const FArguments& InArgs, class UEdGraphNode* InNode) { GraphNode = InNode; Frontend::FConstNodeHandle NodeHandle = GetMetaSoundNode().GetConstNodeHandle(); ClassType = NodeHandle->GetClassMetadata().GetType(); SetCursor(EMouseCursor::CardinalCross); UpdateGraphNode(); } void SMetaSoundGraphNode::ExecuteTrigger(UMetasoundEditorGraphMemberDefaultLiteral& Literal) { UMetasoundEditorGraphMember* Member = Literal.FindMember(); 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::CreateSPLambda(AsShared(), [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("MetasoundGraphNode_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() const { return *CastChecked(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 || InPin->PinType.PinCategory == FGraphBuilder::PinCategoryTime) { 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); const FSlateBrush& PinConnectedBrush = Editor::Style::GetSlateBrushSafe("MetasoundEditor.Graph.TriggerPin.Connected"); const FSlateBrush& PinDisconnectedBrush = Editor::Style::GetSlateBrushSafe("MetasoundEditor.Graph.TriggerPin.Disconnected"); PinWidget->SetCustomPinIcon(&PinConnectedBrush, &PinDisconnectedBrush); } } if (!PinWidget.IsValid()) { PinWidget = SNew(SMetasoundGraphPin, InPin); } IMetasoundEditorModule& EditorModule = FModuleManager::GetModuleChecked("MetaSoundEditor"); const FSlateBrush* PinConnectedIcon = nullptr; const FSlateBrush* PinDisconnectedIcon = nullptr; if (EditorModule.GetCustomPinIcons(InPin, PinConnectedIcon, PinDisconnectedIcon)) { PinWidget->SetCustomPinIcon(PinConnectedIcon, PinDisconnectedIcon); } return PinWidget; } void SMetaSoundGraphNode::CreateStandardPinWidget(UEdGraphPin* InPin) { //Set pin hidden if the node has unconnected pins hidden const FMetasoundFrontendNode* FrontendNode = GetMetaSoundNode().GetFrontendNode(); if (FrontendNode) { FMetasoundFrontendNodeStyle Style = FrontendNode->Style; InPin->SafeSetHidden(Style.bUnconnectedPinsHidden); } if (InPin->Direction == EGPD_Input) { const Frontend::FConstInputHandle InputHandle = FGraphBuilder::GetConstInputHandleFromPin(InPin); if (InputHandle->GetMetadata().bIsAdvancedDisplay != InPin->bAdvancedView) { FGraphBuilder::RefreshPinMetadata(*InPin, InputHandle->GetMetadata()); } } else if (InPin->Direction == EGPD_Output) { Frontend::FConstOutputHandle OutputHandle = FGraphBuilder::GetConstOutputHandleFromPin(InPin); if (OutputHandle->GetMetadata().bIsAdvancedDisplay != InPin->bAdvancedView) { FGraphBuilder::RefreshPinMetadata(*InPin, OutputHandle->GetMetadata()); } } const bool bShowPin = ShouldPinBeHidden(InPin); if (bShowPin) { TSharedPtr NewPin = CreatePinWidget(InPin); check(NewPin.IsValid()); Frontend::FConstNodeHandle NodeHandle = GetMetaSoundNode().GetConstNodeHandle(); 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()); } } void SMetaSoundGraphNode::CreateBelowPinControls(TSharedPtr MainBox) { if (MainBox.IsValid()) { UMetasoundEditorGraphNode& MetaSoundNode = GetMetaSoundNode(); const FName NodeClassName = MetaSoundNode.GetBreadcrumb().ClassName.GetFullName(); const FCreateGraphNodeVisualizationWidgetParams CreateParams{ .MetaSoundNode = &MetaSoundNode }; if (TSharedPtr VisualizationWidget = FGraphNodeVisualizationRegistry::Get().CreateVisualizationWidget(NodeClassName, CreateParams)) { MainBox->AddSlot() .Padding(1.0f, 0.0f) [ VisualizationWidget.ToSharedRef() ]; } } } TSharedRef SMetaSoundGraphNode::CreateTitleWidget(TSharedPtr NodeTitle) { Frontend::FConstNodeHandle NodeHandle = GetMetaSoundNode().GetConstNodeHandle(); 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 = FAppStyle::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)); } } } void SMetaSoundGraphNode::OnCommentBubbleToggled(bool bInCommentBubbleVisible) { using namespace Metasound::Engine; UMetasoundEditorGraphNode& EdNode = GetMetaSoundNode(); UObject& MetaSound = EdNode.GetMetasoundChecked(); UMetaSoundBuilderBase& Builder = FDocumentBuilderRegistry::GetChecked().FindOrBeginBuilding(MetaSound); if (const FMetasoundFrontendNode* Node = Builder.GetConstBuilder().FindNode(EdNode.GetNodeID())) { if (bInCommentBubbleVisible != Node->Style.Display.bCommentVisible) { const FScopedTransaction Transaction(LOCTEXT("GraphNodeCommentToggled", "Graph Node Comment Bubble Toggled")); MetaSound.Modify(); EMetaSoundBuilderResult Result; EdNode.bCommentBubbleVisible = bInCommentBubbleVisible; Builder.SetNodeCommentVisible(Node->GetID(), bInCommentBubbleVisible, Result); } } } void SMetaSoundGraphNode::OnCommentTextCommitted(const FText& NewComment, ETextCommit::Type CommitInfo) { using namespace Metasound::Engine; FString NewCommentString = NewComment.ToString(); UMetasoundEditorGraphNode& EdNode = GetMetaSoundNode(); UObject& MetaSound = EdNode.GetMetasoundChecked(); UMetaSoundBuilderBase& Builder = FDocumentBuilderRegistry::GetChecked().FindOrBeginBuilding(MetaSound); if (const FMetasoundFrontendNode* Node = Builder.GetConstBuilder().FindNode(EdNode.GetNodeID())) { if (!Node->Style.Display.Comment.Equals(NewCommentString)) { const FScopedTransaction Transaction(LOCTEXT("GraphNodeCommentChanged", "Graph Node Comment Changed")); MetaSound.Modify(); EMetaSoundBuilderResult Result; EdNode.NodeComment = MoveTemp(NewCommentString); Builder.SetNodeComment(Node->GetID(), EdNode.NodeComment, Result); } } } void SMetaSoundGraphNode::OnAdvancedViewChanged(const ECheckBoxState NewCheckedState) { if (NewCheckedState == ECheckBoxState::Checked) { if (UMetasoundEditorGraphExternalNode* ExternalNode = Cast(&GetMetaSoundNode())) { ExternalNode->HideUnconnectedPins(false); } } SGraphNode::OnAdvancedViewChanged(NewCheckedState); } 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::FConstNodeHandle NodeHandle = GetMetaSoundNode().GetConstNodeHandle(); 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(FAppStyle::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( FAppStyle::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); UMetasoundEditorGraphNode& Node = GetMetaSoundNode(); Node.GetMetasoundChecked().Modify(); Node.UpdateFrontendNodeLocation(NewPosition); Node.SyncLocationFromFrontendNode(); } const FSlateBrush* SMetaSoundGraphNode::GetNodeBodyBrush() const { // TODO: Add tweak & add custom bodies if (GraphNode) { switch (ClassType) { case EMetasoundFrontendClassType::Variable: case EMetasoundFrontendClassType::VariableAccessor: case EMetasoundFrontendClassType::VariableDeferredAccessor: case EMetasoundFrontendClassType::VariableMutator: { return FAppStyle::GetBrush("Graph.VarNode.Body"); } break; case EMetasoundFrontendClassType::Input: case EMetasoundFrontendClassType::Output: default: { } break; } } return FAppStyle::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.GetConstNodeHandle()->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; } TAttribute SMetaSoundGraphNode::GetInputWidgetEnabled() const { return TAttribute::Create([this]() { if (const UMetasoundEditorGraphMemberNode* Node = GetMetaSoundMemberNode()) { return Node->EnableInteractWidgets(); } return false; }); } FText SMetaSoundGraphNode::GetInputWidgetTooltip() const { if (const UMetasoundEditorGraphMemberNode* Node = GetMetaSoundMemberNode()) { return Node->GetTooltipText(); } return FText(); } UMetasoundEditorGraphMemberNode* SMetaSoundGraphNode::GetMetaSoundMemberNode() const { return Cast(&GetMetaSoundNode()); } TSharedPtr SMetaSoundGraphNode::CreateInputNodeContentArea(const FMetaSoundFrontendDocumentBuilder& InBuilder, TSharedRef ContentBox) { using namespace Engine; TSharedPtr OuterContentBox; TWeakObjectPtr GraphMember; { GraphMember = Cast(GetMetaSoundMember()); if (!GraphMember.IsValid()) { return OuterContentBox; } } const UMetasoundEditorSettings* EditorSettings = GetDefault(); const UMetaSoundSettings* MetaSoundSettings = GetDefault(); if (!EditorSettings || !MetaSoundSettings) { return OuterContentBox; } const UMetasoundEditorGraph* OwningGraph = GraphMember->GetOwningGraph(); if (!OwningGraph || !OwningGraph->IsEditable() || GraphMember->GetVertexAccessType() == EMetasoundFrontendVertexAccessType::Unset) { return OuterContentBox; } const bool bUseAudioMaterialWidgets = EditorSettings->bUseAudioMaterialWidgets; TWeakObjectPtr DefaultFloat = Cast(GraphMember->GetLiteral()); if (DefaultFloat.IsValid() && 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); auto OnValueChangedLambda = [DefaultFloat, GraphMember, this](float Value) { if (DefaultFloat.IsValid() && FloatInputWidget.IsValid()) { if (!bIsInputWidgetTransacting) { GEditor->BeginTransaction(LOCTEXT("MetasoundGraphNode_MetasoundSetInputDefault", "Set MetaSound Input Default")); bIsInputWidgetTransacting = true; } FMetaSoundFrontendDocumentBuilder& Builder = GraphMember->GetFrontendBuilderChecked(); Builder.CastDocumentObjectChecked().Modify(); DefaultFloat->Modify(); FMetasoundFrontendLiteral OutputLiteral; OutputLiteral.Set(FloatInputWidget->GetOutputValue(Value)); const FGuid BuildPageID = Builder.GetBuildPageID(); DefaultFloat->SetFromLiteral(OutputLiteral, BuildPageID); DefaultFloat->OnDefaultValueChanged.Broadcast(BuildPageID, Value); constexpr bool bPostTransaction = true; GraphMember->UpdateFrontendDefaultLiteral(bPostTransaction, &BuildPageID); } }; auto OnValueCommittedLambda = [DefaultFloat, GraphMember, this](float Value) { if (DefaultFloat.IsValid() && FloatInputWidget.IsValid()) { bool bPostTransaction = false; FMetasoundFrontendLiteral OutputLiteral; OutputLiteral.Set(FloatInputWidget->GetOutputValue(Value)); const FGuid BuildPageID = GraphMember->GetFrontendBuilderChecked().GetBuildPageID(); DefaultFloat->SetFromLiteral(OutputLiteral, BuildPageID); DefaultFloat->OnDefaultValueChanged.Broadcast(BuildPageID, Value); if (bIsInputWidgetTransacting) { GEditor->EndTransaction(); bIsInputWidgetTransacting = false; } else { bPostTransaction = true; UE_LOG(LogMetaSound, Warning, TEXT("Unmatched MetaSound editor widget transaction.")); } GraphMember->UpdateFrontendDefaultLiteral(bPostTransaction); if (UMetasoundEditorGraph* Graph = GraphMember->GetOwningGraph()) { FGraphBuilder::GetOutermostMetaSoundChecked(*Graph).GetModifyContext().AddMemberIDsModified({ GraphMember->GetMemberID() }); } } }; if (DefaultFloat->WidgetType == EMetasoundMemberDefaultWidget::Slider) { if (bUseAudioMaterialWidgets) { SAssignNew(FloatInputWidget, SAudioMaterialLabeledSlider) .Owner(GraphMember->GetOwningGraph()) .Style(EditorSettings->GetSliderStyle()) .AudioUnitsValueType(DefaultFloat->WidgetUnitValueType) .bUseLinearOutput(DefaultFloat->VolumeWidgetUseLinearOutput) .OnValueChanged_Lambda(OnValueChangedLambda) .OnValueCommitted_Lambda(OnValueCommittedLambda); } else { // Create slider if (DefaultFloat->WidgetUnitValueType == EAudioUnitsValueType::Frequency) { SAssignNew(FloatInputWidget, SAudioFrequencySlider) .OnValueChanged_Lambda(OnValueChangedLambda) .OnValueCommitted_Lambda(OnValueCommittedLambda); } else if (DefaultFloat->WidgetUnitValueType == EAudioUnitsValueType::Volume) { SAssignNew(FloatInputWidget, SAudioVolumeSlider) .OnValueChanged_Lambda(OnValueChangedLambda) .OnValueCommitted_Lambda(OnValueCommittedLambda); StaticCastSharedPtr(FloatInputWidget)->SetUseLinearOutput(DefaultFloat->VolumeWidgetUseLinearOutput); } else { SAssignNew(FloatInputWidget, SAudioSlider) .OnValueChanged_Lambda(OnValueChangedLambda) .OnValueCommitted_Lambda(OnValueCommittedLambda); FloatInputWidget->SetShowUnitsText(false); } } // Slider layout if (DefaultFloat->WidgetOrientation == Orient_Vertical) { SAssignNew(OuterContentBox, SVerticalBox) + SVerticalBox::Slot() .HAlign(HAlign_Right) .VAlign(VAlign_Center) .AutoHeight() [ ContentBox ] + SVerticalBox::Slot() .HAlign(HAlign_Fill) .VAlign(VAlign_Top) .Padding(WidgetPadding, 0.0f, WidgetPadding, WidgetPadding) .AutoHeight() [ FloatInputWidget.ToSharedRef() ]; if (!bUseAudioMaterialWidgets) { FloatInputWidget->SetDesiredSizeOverride(SliderDesiredSizeVertical); } } else // horizontal orientation { UMetasoundEditorGraphMemberNode* MemberNode = GetMetaSoundMemberNode(); TSharedPtr Slot1; TSharedPtr Slot2; if (MemberNode->IsA()) { Slot1 = FloatInputWidget; Slot2 = ContentBox; } else { Slot1 = ContentBox; Slot2 = FloatInputWidget; } SAssignNew(OuterContentBox, SHorizontalBox) + SHorizontalBox::Slot() .HAlign(HAlign_Fill) .VAlign(VAlign_Center) .Padding(WidgetPadding, 0.0f, WidgetPadding, 0.0f) .AutoWidth() [ Slot1.ToSharedRef() ] + SHorizontalBox::Slot() .HAlign(HAlign_Center) .VAlign(VAlign_Fill) .AutoWidth() [ Slot2.ToSharedRef() ]; if (!bUseAudioMaterialWidgets) { FloatInputWidget->SetDesiredSizeOverride(FVector2D(SliderDesiredSizeVertical.Y, SliderDesiredSizeVertical.X)); } } if (bUseAudioMaterialWidgets) { // safe downcast because the ptr was just assigned above StaticCastSharedPtr(FloatInputWidget)->SetOrientation(DefaultFloat->WidgetOrientation); } else { // safe downcast because the ptr was just assigned above StaticCastSharedPtr(FloatInputWidget)->SetOrientation(DefaultFloat->WidgetOrientation); } } else if (DefaultFloat->WidgetType == EMetasoundMemberDefaultWidget::RadialSlider) { auto OnRadialSliderMouseCaptureBeginLambda = [this]() { if (!bIsInputWidgetTransacting) { GEditor->BeginTransaction(LOCTEXT("MetasoundSetRadialSliderInputDefault", "Set MetaSound Input Default")); bIsInputWidgetTransacting = true; } }; auto OnRadialSliderMouseCaptureEndLambda = [this]() { if (bIsInputWidgetTransacting) { GEditor->EndTransaction(); bIsInputWidgetTransacting = false; } else { UE_LOG(LogMetaSound, Warning, TEXT("Unmatched MetaSound editor widget transaction.")); } }; if (bUseAudioMaterialWidgets) { SAssignNew(FloatInputWidget, SAudioMaterialLabeledKnob) .Owner(GraphMember->GetOwningGraph()) .Style(EditorSettings->GetKnobStyle()) .OnValueChanged_Lambda(OnValueChangedLambda) .AudioUnitsValueType(DefaultFloat->WidgetUnitValueType) .bUseLinearOutput(DefaultFloat->VolumeWidgetUseLinearOutput) .OnMouseCaptureBegin_Lambda(OnRadialSliderMouseCaptureBeginLambda) .OnMouseCaptureEnd_Lambda(OnRadialSliderMouseCaptureEndLambda); } else { // Create slider if (DefaultFloat->WidgetUnitValueType == EAudioUnitsValueType::Frequency) { SAssignNew(FloatInputWidget, SAudioFrequencyRadialSlider) .OnValueChanged_Lambda(OnValueChangedLambda) .OnMouseCaptureBegin_Lambda(OnRadialSliderMouseCaptureBeginLambda) .OnMouseCaptureEnd_Lambda(OnRadialSliderMouseCaptureEndLambda); } else if (DefaultFloat->WidgetUnitValueType == EAudioUnitsValueType::Volume) { SAssignNew(FloatInputWidget, SAudioVolumeRadialSlider) .OnValueChanged_Lambda(OnValueChangedLambda) .OnMouseCaptureBegin_Lambda(OnRadialSliderMouseCaptureBeginLambda) .OnMouseCaptureEnd_Lambda(OnRadialSliderMouseCaptureEndLambda); StaticCastSharedPtr(FloatInputWidget)->SetUseLinearOutput(DefaultFloat->VolumeWidgetUseLinearOutput); } else { SAssignNew(FloatInputWidget, SAudioRadialSlider) .OnValueChanged_Lambda(OnValueChangedLambda) .OnMouseCaptureBegin_Lambda(OnRadialSliderMouseCaptureBeginLambda) .OnMouseCaptureEnd_Lambda(OnRadialSliderMouseCaptureEndLambda); FloatInputWidget->SetShowUnitsText(false); } } // Only vertical layout for radial slider SAssignNew(OuterContentBox, SVerticalBox) + SVerticalBox::Slot() .HAlign(HAlign_Right) .VAlign(VAlign_Center) .AutoHeight() [ ContentBox ] + SVerticalBox::Slot() .HAlign(HAlign_Fill) .VAlign(VAlign_Top) .Padding(WidgetPadding, 0.0f, WidgetPadding, WidgetPadding) .AutoHeight() [ FloatInputWidget.ToSharedRef() ]; FloatInputWidget->SetDesiredSizeOverride(RadialSliderDesiredSize); } const FMetasoundFrontendClassInput* ClassInput = InBuilder.FindGraphInput(GraphMember->GetMemberName()); FGuid ResolvedPageID = Frontend::DefaultPageID; if (ensure(ClassInput)) { ResolvedPageID = EditorSettings->ResolveAuditionPage(*ClassInput, InBuilder.GetBuildPageID()); } FloatInputWidget->SetOutputRange(DefaultFloat->GetRange()); FloatInputWidget->SetUnitsTextReadOnly(true); FloatInputWidget->SetSliderValue(FloatInputWidget->GetSliderValue(DefaultFloat->GetDefaultAs(ResolvedPageID))); FloatInputWidget->SetEnabled(GetInputWidgetEnabled()); FloatInputWidget->SetToolTipText(GetInputWidgetTooltip()); // Setup & clear delegate if necessary (ex. if was just saved) if (InputSliderOnValueChangedDelegateHandle.IsValid()) { DefaultFloat->OnDefaultValueChanged.Remove(InputSliderOnValueChangedDelegateHandle); InputSliderOnValueChangedDelegateHandle.Reset(); } InputSliderOnValueChangedDelegateHandle = DefaultFloat->OnDefaultValueChanged.AddLambda([this, Widget = FloatInputWidget](const FGuid& PageID, float Value) { using namespace Metasound::Engine; if (Widget.IsValid()) { UMetasoundEditorGraphNode& EdNode = GetMetaSoundNode(); UObject& MetaSound = EdNode.GetMetasoundChecked(); UMetaSoundBuilderBase& Builder = FDocumentBuilderRegistry::GetChecked().FindOrBeginBuilding(MetaSound); if (Builder.GetConstBuilder().GetBuildPageID() == PageID) { const float SliderValue = Widget->GetSliderValue(Value); Widget->SetSliderValue(SliderValue); } } }); if (InputSliderOnRangeChangedDelegateHandle.IsValid()) { DefaultFloat->OnRangeChanged.Remove(InputSliderOnRangeChangedDelegateHandle); InputSliderOnRangeChangedDelegateHandle.Reset(); } InputSliderOnRangeChangedDelegateHandle = DefaultFloat->OnRangeChanged.AddLambda([Widget = FloatInputWidget](FVector2D Range) { if (Widget.IsValid()) { Widget->SetOutputRange(Range); } }); } else { TWeakObjectPtr DefaultBool = Cast(GraphMember->GetLiteral()); if (DefaultBool.IsValid()) { if (bUseAudioMaterialWidgets) { bool bIsNotTriggerNode = GraphMember->GetDataType() != Metasound::GetMetasoundDataTypeName(); if ((bIsNotTriggerNode && DefaultBool.IsValid()) && DefaultBool->WidgetType != EMetasoundBoolMemberDefaultWidget::None) { constexpr float WidgetPadding = 3.0f; static const FVector2D ButtonDesiredSize = FVector2D(56.0f, 87.0f); auto OnBoolValueChangedLambda = [DefaultBool, GraphMember, this](bool Value) { if (DefaultBool.IsValid() && MaterialButtonWidget.IsValid()) { if (!bIsInputWidgetTransacting) { GEditor->BeginTransaction(FText::Format(LOCTEXT("MetasoundGraphNode_MetasoundSetBoolInput", "Set MetaSound Input '{0}' Boolean Default"), GraphMember->GetDisplayName())); bIsInputWidgetTransacting = true; } FMetaSoundFrontendDocumentBuilder& Builder = GraphMember->GetFrontendBuilderChecked(); Builder.CastDocumentObjectChecked().Modify(); DefaultBool->Modify(); FMetasoundFrontendLiteral OutputLiteral; OutputLiteral.Set(Value); const FGuid BuildPageID = Builder.GetBuildPageID(); DefaultBool->SetFromLiteral(OutputLiteral, BuildPageID); DefaultBool->OnDefaultStateChanged.Broadcast(Value, BuildPageID); constexpr bool bPostTransaction = true; GraphMember->UpdateFrontendDefaultLiteral(bPostTransaction, &BuildPageID); } }; auto OnBoolMouseCaptureEndLambda = [this]() { if (bIsInputWidgetTransacting) { GEditor->EndTransaction(); bIsInputWidgetTransacting = false; } else { UE_LOG(LogMetaSound, Warning, TEXT("Unmatched MetaSound editor widget transaction.")); } }; SAssignNew(MaterialButtonWidget, SAudioMaterialButton) .AudioMaterialButtonStyle(EditorSettings->GetButtonStyle()) .OnBooleanValueChanged_Lambda(OnBoolValueChangedLambda) .OnMouseCaptureEnd_Lambda(OnBoolMouseCaptureEndLambda) .bIsPressedAttribute(DefaultBool->GetDefaultAs()); SAssignNew(OuterContentBox, SVerticalBox) + SVerticalBox::Slot() .HAlign(HAlign_Right) .VAlign(VAlign_Center) .AutoHeight() [ ContentBox ] + SVerticalBox::Slot() .HAlign(HAlign_Fill) .VAlign(VAlign_Top) .Padding(WidgetPadding, 0.0f, WidgetPadding, WidgetPadding) .AutoHeight() [ MaterialButtonWidget.ToSharedRef() ]; MaterialButtonWidget->SetDesiredSizeOverride(ButtonDesiredSize); MaterialButtonWidget->SetEnabled(GetInputWidgetEnabled()); // Setup & clear delegate if necessary (ex. if was just saved) if (InputButtonOnStateChangedDelegateHandle.IsValid()) { DefaultBool->OnDefaultStateChanged.Remove(InputButtonOnStateChangedDelegateHandle); InputButtonOnStateChangedDelegateHandle.Reset(); } InputButtonOnStateChangedDelegateHandle = DefaultBool->OnDefaultStateChanged.AddLambda([this, Widget = MaterialButtonWidget](bool bValue, const FGuid& InPageID) { UMetasoundEditorGraphNode& EdNode = GetMetaSoundNode(); UObject& MetaSound = EdNode.GetMetasoundChecked(); UMetaSoundBuilderBase& Builder = FDocumentBuilderRegistry::GetChecked().FindOrBeginBuilding(MetaSound); const FGuid& BuildPageID = Builder.GetConstBuilder().GetBuildPageID(); if (Widget.IsValid() && BuildPageID == InPageID) { Widget->SetPressedState(bValue); } }); } } } } return OuterContentBox; } TSharedRef SMetaSoundGraphNode::CreateNodeContentArea() { using namespace Engine; using namespace Frontend; FConstNodeHandle NodeHandle = GetMetaSoundNode().GetConstNodeHandle(); const FMetasoundFrontendClassStyleDisplay& StyleDisplay = NodeHandle->GetClassStyle().Display; TSharedRef ContentBox = SNew(SHorizontalBox); UMetasoundEditorGraphNode& EdNode = GetMetaSoundNode(); UObject& MetaSound = EdNode.GetMetasoundChecked(); UMetaSoundBuilderBase& Builder = FDocumentBuilderRegistry::GetChecked().FindOrBeginBuilding(MetaSound); TSharedPtr InputContent = CreateInputNodeContentArea(Builder.GetConstBuilder(), ContentBox); // Gives more space for user to grab a bit easier as variables do not have any title area nor icon const float GrabPadding = IsVariableMutator() ? 28.0f : 0.0f; const EVerticalAlignment PinNodeAlignInput = (!StyleDisplay.bShowInputNames && NodeHandle->GetNumInputs() == 1) ? VAlign_Center : VAlign_Top; ContentBox->AddSlot() .HAlign(HAlign_Left) .VAlign(PinNodeAlignInput) .FillWidth(1.0f) .Padding(0.0f, 0.0f, GrabPadding, 0.0f) [ SAssignNew(LeftNodeBox, SVerticalBox) ]; if (!StyleDisplay.ImageName.IsNone()) { const FSlateBrush& ImageBrush = Metasound::Editor::Style::GetSlateBrushSafe(StyleDisplay.ImageName); ContentBox->AddSlot() .AutoWidth() .HAlign(HAlign_Center) .VAlign(VAlign_Center) [ SNew(SImage) .Image(&ImageBrush) .ColorAndOpacity(FSlateColor::UseForeground()) .DesiredSizeOverride(FVector2D(20, 20)) ]; } const EVerticalAlignment PinNodeAlignOutput = (!StyleDisplay.bShowInputNames && NodeHandle->GetNumOutputs() == 1) ? VAlign_Center : VAlign_Top; ContentBox->AddSlot() .AutoWidth() .HAlign(HAlign_Right) .VAlign(PinNodeAlignOutput) .Padding(GrabPadding, 0.0f, 0.0f, 0.0f) [ SAssignNew(RightNodeBox, SVerticalBox) ]; return SNew(SBorder) .BorderImage(FAppStyle::GetBrush("NoBorder")) .HAlign(HAlign_Fill) .VAlign(VAlign_Fill) .Padding(FMargin(0,3)) [ InputContent.IsValid() ? InputContent.ToSharedRef() : ContentBox ]; } TSharedPtr SMetaSoundGraphNodeKnot::CreatePinWidget(UEdGraphPin* Pin) const { return SNew(SMetaSoundGraphPinKnot, Pin); } void SMetaSoundGraphNodeKnot::Construct(const FArguments& InArgs, class UEdGraphNode* InNode) { GraphNode = InNode; SetCursor(EMouseCursor::CardinalCross); UpdateGraphNode(); } void SMetaSoundGraphNodeKnot::MoveTo(const FVector2D& NewPosition, FNodeSet& NodeFilter, bool bMarkDirty) { SGraphNode::MoveTo(NewPosition, NodeFilter, bMarkDirty); UMetasoundEditorGraphNode& Node = GetMetaSoundNode(); Node.GetMetasoundChecked().Modify(); Node.UpdateFrontendNodeLocation(NewPosition); Node.SyncLocationFromFrontendNode(); } UMetasoundEditorGraphNode& SMetaSoundGraphNodeKnot::GetMetaSoundNode() { return *CastChecked(GraphNode); } const UMetasoundEditorGraphNode& SMetaSoundGraphNodeKnot::GetMetaSoundNode() const { check(GraphNode); return *Cast(GraphNode); } } // namespace Editor } // namespace Metasound #undef LOCTEXT_NAMESPACE // MetasoundEditor