// Copyright Epic Games, Inc. All Rights Reserved. #include "SNiagaraStack.h" #include "NiagaraEditorModule.h" #include "NiagaraEditorCommands.h" #include "Styling/AppStyle.h" #include "NiagaraEditorWidgetsStyle.h" #include "NiagaraEditorStyle.h" #include "NiagaraEmitterHandle.h" #include "ViewModels/NiagaraEmitterViewModel.h" #include "ViewModels/NiagaraEmitterHandleViewModel.h" #include "ViewModels/NiagaraSystemViewModel.h" #include "ViewModels/Stack/NiagaraStackViewModel.h" #include "ViewModels/Stack/NiagaraStackItemGroup.h" #include "ViewModels/Stack/NiagaraStackModuleItemOutputCollection.h" #include "ViewModels/Stack/NiagaraStackModuleItemLinkedInputCollection.h" #include "ViewModels/Stack/NiagaraStackModuleItemOutput.h" #include "ViewModels/Stack/NiagaraStackErrorItem.h" #include "ViewModels/Stack/NiagaraStackInputCategory.h" #include "ViewModels/Stack/NiagaraStackPropertyRow.h" #include "ViewModels/Stack/NiagaraStackItemFooter.h" #include "ViewModels/Stack/NiagaraStackValueCollection.h" #include "ViewModels/Stack/NiagaraStackModuleItem.h" #include "ViewModels/Stack/NiagaraStackSystemSettingsGroup.h" #include "ViewModels/Stack/NiagaraStackSimulationStageGroup.h" #include "Framework/Commands/GenericCommands.h" #include "IContentBrowserSingleton.h" #include "ContentBrowserModule.h" #include "Widgets/Input/SButton.h" #include "Widgets/Images/SImage.h" #include "Widgets/Input/SComboButton.h" #include "Widgets/Text/SInlineEditableTextBlock.h" #include "Widgets/Input/SCheckBox.h" #include "NiagaraEmitter.h" #include "IDetailTreeNode.h" #include "Stack/NiagaraStackPropertyRowUtilities.h" #include "Stack/SNiagaraStackFunctionInputName.h" #include "Stack/SNiagaraStackValueCollection.h" #include "Stack/SNiagaraStackFunctionInputValue.h" #include "Stack/SNiagaraStackInlineDynamicInput.h" #include "Stack/SNiagaraStackItem.h" #include "Stack/SNiagaraStackItemFooter.h" #include "Stack/SNiagaraStackItemGroup.h" #include "Stack/SNiagaraStackModuleItem.h" #include "Stack/SNiagaraStackTableRow.h" #include "Stack/SNiagaraStackIssueIcon.h" #include "NiagaraEditorWidgetsUtilities.h" #include "DragAndDrop/DecoratedDragDropOp.h" #include "Stack/SNiagaraStackErrorItem.h" #include "Widgets/Layout/SWrapBox.h" #include "ViewModels/Stack/NiagaraStackRoot.h" #include "NiagaraStackCommandContext.h" #include "NiagaraEditorUtilities.h" #include "SNiagaraStackNote.h" #include "Framework/Commands/UICommandList.h" #include "ViewModels/NiagaraOverviewGraphViewModel.h" #include "Widgets/SNiagaraParameterName.h" #include "Styling/StyleColors.h" #include "SResetToDefaultPropertyEditor.h" #include "Stack/SNiagaraStackScriptHierarchyRoot.h" #include "Widgets/Images/SLayeredImage.h" #include "ViewModels/NiagaraSystemSelectionViewModel.h" #include "ViewModels/Stack/NiagaraStackNote.h" #define LOCTEXT_NAMESPACE "NiagaraStack" class SNiagaraStackHeaderButtons : public SCompoundWidget { public: SLATE_BEGIN_ARGS(SNiagaraStackHeaderButtons) {} SLATE_ATTRIBUTE(EVisibility, IssueIconVisibility); SLATE_EVENT(FSimpleDelegate, OnCycleThroughIssues); SLATE_ARGUMENT(TSharedPtr, ParentStack) SLATE_END_ARGS(); void Construct(const FArguments& InArgs, UNiagaraStackEntry* InRootEntry, UNiagaraStackViewModel* InStackViewModel) { StackViewModel = InStackViewModel; OnCycleThroughIssues = InArgs._OnCycleThroughIssues; ParentStackPtr = InArgs._ParentStack; TopLevelViewModel = StackViewModel->GetTopLevelViewModelForEntry(*InRootEntry); ChildSlot [ SNew(SHorizontalBox) // Issue Icon + SHorizontalBox::Slot() .AutoWidth() .HAlign(HAlign_Right) .VAlign(VAlign_Center) .Padding(0, 0, 2, 0) [ SNew(SNiagaraStackIssueIcon, StackViewModel, InRootEntry) .Visibility(InArgs._IssueIconVisibility) .OnClicked(this, &SNiagaraStackHeaderButtons::OnIssueIconClicked) ] // Options button + SHorizontalBox::Slot() .AutoWidth() .HAlign(HAlign_Fill) .VAlign(VAlign_Center) .Padding(0, 0, 2, 0) [ SAssignNew(SettingsAnchor, SMenuAnchor) .Placement(MenuPlacement_MenuLeft) .OnGetMenuContent(this, &SNiagaraStackHeaderButtons::OnGetContent) [ SNew(SButton) .ContentPadding(0) .ForegroundColor(FSlateColor::UseForeground()) .ButtonStyle(FAppStyle::Get(), "SimpleButton") .OnClicked(this, &SNiagaraStackHeaderButtons::OpenSubmenu) .VAlign(VAlign_Center) .HAlign(HAlign_Fill) [ SNew(SImage) .ColorAndOpacity(FSlateColor::UseForeground()) .Image(FAppStyle::Get().GetBrush("DetailsView.ViewOptions")) ] ] ] ]; } private: FReply OnIssueIconClicked() const { StackViewModel->OnCycleThroughIssues(TopLevelViewModel); OnCycleThroughIssues.ExecuteIfBound(); return FReply::Handled(); } FReply OpenSubmenu() { SettingsAnchor->SetIsOpen(!SettingsAnchor->IsOpen()); return FReply::Handled(); } TSharedRef OnGetContent() const { if (ParentStackPtr.IsValid()) { return ParentStackPtr.Pin()->GenerateStackMenu(TopLevelViewModel).ToSharedRef(); } return SNullWidget::NullWidget; } private: TSharedPtr TopLevelViewModel; FSimpleDelegate OnCycleThroughIssues; UNiagaraStackViewModel* StackViewModel; TWeakPtr ParentStackPtr; TSharedPtr SettingsAnchor; }; class SNiagaraStackEmitterHeader : public SCompoundWidget { public: SLATE_BEGIN_ARGS(SNiagaraStackEmitterHeader) {} SLATE_ATTRIBUTE(EVisibility, IssueIconVisibility); SLATE_EVENT(FSimpleDelegate, OnCycleThroughIssues); SLATE_ARGUMENT(TSharedPtr, ParentStack) SLATE_END_ARGS(); void Construct(const FArguments& InArgs, TSharedRef InEmitterHandleViewModel, UNiagaraStackEntry* InRootEntry, UNiagaraStackViewModel* InStackViewModel) { EmitterHandleViewModel = InEmitterHandleViewModel; ChildSlot [ SNew(SBorder) .BorderImage(FAppStyle::GetBrush("WhiteBrush")) .BorderBackgroundColor(FStyleColors::Panel) [ SNew(SVerticalBox) //~ Enable check box, view source emitter button, and external header controls. + SVerticalBox::Slot() .AutoHeight() .HAlign(HAlign_Fill) [ SNew(SHorizontalBox) //~ Enabled + SHorizontalBox::Slot() .AutoWidth() .Padding(3) .VAlign(VAlign_Center) [ SNew(SCheckBox) .ToolTipText(LOCTEXT("EnabledToolTip", "Toggles whether this emitter is enabled. Disabled emitters don't simulate or render.")) .IsChecked(EmitterHandleViewModel.ToSharedRef(), &FNiagaraEmitterHandleViewModel::GetIsEnabledCheckState) .OnCheckStateChanged(EmitterHandleViewModel.ToSharedRef(), &FNiagaraEmitterHandleViewModel::OnIsEnabledCheckStateChanged) ] + SHorizontalBox::Slot() .Padding(2) .HAlign(HAlign_Fill) .VAlign(VAlign_Center) .FillWidth(1.0f) [ // Name and Source Emitter Name SNew(SWrapBox) .Clipping(EWidgetClipping::ClipToBoundsAlways) .UseAllottedSize(true) + SWrapBox::Slot() [ SAssignNew(EmitterNameTextBlock, SInlineEditableTextBlock) .ToolTipText(this, &SNiagaraStackEmitterHeader::GetEmitterNameToolTip) .Style(FAppStyle::Get(), "DetailsView.NameTextBlockStyle") .Clipping(EWidgetClipping::ClipToBoundsAlways) .Text(EmitterHandleViewModel.ToSharedRef(), &FNiagaraEmitterHandleViewModel::GetNameText) .OnTextCommitted(EmitterHandleViewModel.ToSharedRef(), &FNiagaraEmitterHandleViewModel::OnNameTextComitted) .OnVerifyTextChanged(EmitterHandleViewModel.ToSharedRef(), &FNiagaraEmitterHandleViewModel::VerifyNameTextChanged) .IsReadOnly(EmitterHandleViewModel->CanRenameEmitter() == false) ] + SWrapBox::Slot() .Padding(4, 0, 0, 0) [ SNew(STextBlock) .ToolTipText(this, &SNiagaraStackEmitterHeader::GetEmitterNameToolTip) .TextStyle(FNiagaraEditorStyle::Get(), "NiagaraEditor.HeadingTextBlockSubdued") .Clipping(EWidgetClipping::ClipToBoundsAlways) .Text(EmitterHandleViewModel->GetEmitterViewModel(), &FNiagaraEmitterViewModel::GetParentNameText) .Visibility(this, &SNiagaraStackEmitterHeader::GetSourceEmitterNameVisibility) ] ] + SHorizontalBox::Slot() [ SNew(SNiagaraStackHeaderButtons, InRootEntry, InStackViewModel) .IssueIconVisibility(InArgs._IssueIconVisibility) .OnCycleThroughIssues(InArgs._OnCycleThroughIssues) .ParentStack(InArgs._ParentStack) ] ] //~ Stats + SVerticalBox::Slot() .AutoHeight() .HAlign(HAlign_Fill) .Padding(2) [ SNew(STextBlock) .Text(EmitterHandleViewModel->GetEmitterViewModel(), &FNiagaraEmitterViewModel::GetStatsText) .ColorAndOpacity(FSlateColor::UseForeground()) ] ] ]; } void Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime) { if (EmitterHandleViewModel->GetIsRenamePending()) { EmitterHandleViewModel->SetIsRenamePending(false); EmitterNameTextBlock->EnterEditingMode(); } } private: FText GetEmitterNameToolTip() const { if (EmitterHandleViewModel->GetEmitterViewModel()->HasParentEmitter()) { // We are looking at this Emitter in a System Asset and it has a valid parent Emitter return FText::Format(LOCTEXT("EmitterNameAndPath", "{0}\nParent: {1}"), EmitterHandleViewModel->GetNameText(), EmitterHandleViewModel->GetEmitterViewModel()->GetParentPathNameText()); } else { // We are looking at this Emitter in an Emitter Asset or we are looking at this Emitter in a System Asset and it does not have a valid parent Emitter return EmitterHandleViewModel->GetNameText(); } } EVisibility GetSourceEmitterNameVisibility() const { bool bIsRenamed = false; if (EmitterHandleViewModel->GetEmitterViewModel()->HasParentEmitter()) { const FText CurrentNameText = EmitterHandleViewModel->GetNameText(); const FText ParentNameText = EmitterHandleViewModel->GetEmitterViewModel()->GetParentNameText(); bIsRenamed = CurrentNameText.EqualTo(ParentNameText) == false; } return bIsRenamed ? EVisibility::Visible : EVisibility::Collapsed; } EVisibility GetOpenSourceEmitterVisibility() const { return EmitterHandleViewModel->GetEmitterViewModel()->HasParentEmitter() ? EVisibility::Visible : EVisibility::Collapsed; } private: TSharedPtr EmitterHandleViewModel; TSharedPtr EmitterNameTextBlock; }; const float SpacerHeight = 6; void SNiagaraStack::Construct(const FArguments& InArgs, UNiagaraStackViewModel* InStackViewModel) { StackViewModel = InStackViewModel; StackViewModel->OnChangeSearchTextExternal().BindSP(this, &SNiagaraStack::UpdateSearchTextFromExternal); StackViewModel->OnStructureChanged().AddSP(this, &SNiagaraStack::StackStructureChanged); StackViewModel->OnExpansionChanged().AddSP(this, &SNiagaraStack::OnStackExpansionChanged); StackViewModel->OnSearchCompleted().AddSP(this, &SNiagaraStack::OnStackSearchComplete); NameColumnWidth = .3f; ContentColumnWidth = .7f; StackCommandContext = MakeShared(); bSynchronizeExpansionPending = true; ChildSlot [ SNew(SVerticalBox) + SVerticalBox::Slot() .AutoHeight() .Padding(1, 1, 1, 4) [ SNew(SBorder) .BorderImage(FAppStyle::GetBrush("WhiteBrush")) .BorderBackgroundColor(FStyleColors::Panel) .Padding(5) [ ConstructHeaderWidget() ] ] + SVerticalBox::Slot() .Padding(1) [ SAssignNew(StackTree, STreeView) .OnGenerateRow(this, &SNiagaraStack::OnGenerateRowForStackItem) .OnGetChildren(this, &SNiagaraStack::OnGetChildren) .TreeItemsSource(&StackViewModel->GetRootEntryAsArray()) .OnTreeViewScrolled(this, &SNiagaraStack::StackTreeScrolled) .OnSelectionChanged(this, &SNiagaraStack::StackTreeSelectionChanged) .SelectionMode(ESelectionMode::Type::Multi) .OnItemToString_Debug_Static(&FNiagaraStackEditorWidgetsUtilities::StackEntryToStringForListDebug) ] ]; StackTree->SetScrollOffset(StackViewModel->GetLastScrollPosition()); SynchronizeTreeExpansion(); } void SNiagaraStack::SynchronizeTreeExpansion() { bSynchronizeExpansionPending = false; TArray EntriesToProcess(StackViewModel->GetRootEntryAsArray()); while (EntriesToProcess.Num() > 0) { UNiagaraStackEntry* EntryToProcess = EntriesToProcess[0]; EntriesToProcess.RemoveAtSwap(0); if (EntryToProcess->GetIsExpanded()) { StackTree->SetItemExpansion(EntryToProcess, true); EntryToProcess->GetFilteredChildren(EntriesToProcess); } else { StackTree->SetItemExpansion(EntryToProcess, false); } } } TSharedRef SNiagaraStack::ConstructHeaderWidget() { TSharedPtr ViewOptionsImage; TSharedRef Result = SNew(SVerticalBox) //~ Top level object list view + SVerticalBox::Slot() .AutoHeight() .HAlign(HAlign_Fill) [ SAssignNew(HeaderList, SListView>) .ListItemsSource(&StackViewModel->GetTopLevelViewModels()) .SelectionMode(ESelectionMode::None) .OnGenerateRow(this, &SNiagaraStack::OnGenerateRowForTopLevelObject) ] //~ Search, view options + SVerticalBox::Slot() .AutoHeight() .HAlign(HAlign_Fill) .Padding(2, 4, 2, 4) [ // Search box SNew(SHorizontalBox) + SHorizontalBox::Slot() [ SAssignNew(SearchBox, SSearchBox) .HintText(LOCTEXT("StackSearchBoxHint", "Search the stack")) .SearchResultData(this, &SNiagaraStack::GetSearchResultData) .IsSearching(this, &SNiagaraStack::GetIsSearching) .OnTextChanged(this, &SNiagaraStack::OnSearchTextChanged) .DelayChangeNotificationsWhileTyping(true) .OnTextCommitted(this, &SNiagaraStack::OnSearchBoxTextCommitted) .OnSearch(this, &SNiagaraStack::OnSearchBoxSearch) ] // View options + SHorizontalBox::Slot() .AutoWidth() .Padding(4, 0, 0, 0) [ SNew(SComboButton) .ComboButtonStyle(FAppStyle::Get(), "SimpleComboButton") // Use the tool bar item style for this button .HasDownArrow(false) .ToolTipText(LOCTEXT("ViewOptionsToolTip", "View Options")) .OnGetMenuContent(this, &SNiagaraStack::GetViewOptionsMenu) .ContentPadding(1) .MenuPlacement(MenuPlacement_BelowRightAnchor) .ButtonContent() [ SAssignNew(ViewOptionsImage, SLayeredImage) .Image(FAppStyle::Get().GetBrush("DetailsView.ViewOptions")) .ColorAndOpacity(FSlateColor::UseForeground()) ] ] ]; ViewOptionsImage->AddLayer(TAttribute(this, &SNiagaraStack::GetViewOptionsBadgeIcon)); return Result; } void SNiagaraStack::UpdateSearchTextFromExternal(FText NewSearchText) { SearchBox->SetText(NewSearchText); } void SNiagaraStack::OnSearchTextChanged(const FText& SearchText) { StackViewModel->OnSearchTextChanged(SearchText); } static void ExpandAllInPath(const TArray& EntryPath) { for (UNiagaraStackEntry* Entry : EntryPath) { if (!Entry->IsA()) { Entry->SetIsExpanded(true); } } } FReply SNiagaraStack::ScrollToNextMatch() { int NextMatchIndex = StackViewModel->GetCurrentFocusedEntryIndex() + 1; TArray CurrentSearchResults = StackViewModel->GetCurrentSearchResults(); if (CurrentSearchResults.Num() != 0) { if (NextMatchIndex >= CurrentSearchResults.Num()) { NextMatchIndex = 0; } ExpandAllInPath(CurrentSearchResults[NextMatchIndex].EntryPath); } AddSearchScrollOffset(1); return FReply::Handled(); } FReply SNiagaraStack::ScrollToPreviousMatch() { const int PreviousMatchIndex = StackViewModel->GetCurrentFocusedEntryIndex() - 1; TArray CurrentSearchResults = StackViewModel->GetCurrentSearchResults(); if (CurrentSearchResults.Num() != 0) { if (PreviousMatchIndex >= 0) { ExpandAllInPath(CurrentSearchResults[PreviousMatchIndex].EntryPath); } else { ExpandAllInPath(CurrentSearchResults.Last().EntryPath); } } // move current match to the previous one in the StackTree, wrap around AddSearchScrollOffset(-1); return FReply::Handled(); } void SNiagaraStack::AddSearchScrollOffset(int NumberOfSteps) { if (StackViewModel->IsSearching() || StackViewModel->GetCurrentSearchResults().Num() == 0 || NumberOfSteps == 0) { return; } StackViewModel->AddSearchScrollOffset(NumberOfSteps); ScrollFocusedEntryIntoView(); } TOptional SNiagaraStack::GetSearchResultData() const { if (StackViewModel->GetCurrentSearchText().IsEmpty()) { return TOptional(); } return TOptional({ StackViewModel->GetCurrentSearchResults().Num(), StackViewModel->GetCurrentFocusedEntryIndex() + 1 }); } bool SNiagaraStack::GetIsSearching() const { return StackViewModel->IsSearching(); } bool SNiagaraStack::IsEntryFocusedInSearch(UNiagaraStackEntry* Entry) const { if (StackViewModel && Entry && StackViewModel->GetCurrentFocusedEntry() == Entry) { return true; } return false; } void SNiagaraStack::ShowEmitterInContentBrowser(TWeakPtr EmitterHandleViewModelWeak) { TSharedPtr EmitterHandleViewModel = EmitterHandleViewModelWeak.Pin(); if (EmitterHandleViewModel.IsValid()) { FContentBrowserModule& ContentBrowserModule = FModuleManager::Get().LoadModuleChecked(TEXT("ContentBrowser")); TArray Assets; if (EmitterHandleViewModel->GetEmitterViewModel()->HasParentEmitter()) { Assets.Add(FAssetData(EmitterHandleViewModel->GetEmitterViewModel()->GetParentEmitter().Emitter)); ContentBrowserModule.Get().SyncBrowserToAssets(Assets); } } } void CollapseEntriesRecursive(TArray Entries) { for (UNiagaraStackEntry* Entry : Entries) { if (Entry->GetCanExpand()) { Entry->SetIsExpanded(false); } TArray Children; Entry->GetUnfilteredChildren(Children); CollapseEntriesRecursive(Children); } } void SNiagaraStack::CollapseAll() { CollapseEntriesRecursive(StackViewModel->GetRootEntryAsArray()); } TSharedRef SNiagaraStack::GetViewOptionsMenu() const { FMenuBuilder MenuBuilder(false, nullptr); MenuBuilder.AddMenuEntry( LOCTEXT("ShowOnlyModifiedLabel", "Show Only Modified Properties"), LOCTEXT("ShowOnlyModifiedToolTip", "Displays only properties that have been changes from their default."), FSlateIcon(), FUIAction( FExecuteAction::CreateLambda([this]() { StackViewModel->SetShowOnlyModified(!StackViewModel->GetShowOnlyModified()); }), FCanExecuteAction(), FGetActionCheckState::CreateLambda([this]() { return StackViewModel->GetShowOnlyModified() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; })), NAME_None, EUserInterfaceActionType::Check); MenuBuilder.AddMenuEntry( LOCTEXT("ShowAllAdvancedLabel", "Show All Advanced"), LOCTEXT("ShowAllAdvancedToolTip", "Forces all advanced items to be showing in the stack."), FSlateIcon(), FUIAction( FExecuteAction::CreateLambda([this]() { StackViewModel->SetShowAllAdvanced(!StackViewModel->GetShowAllAdvanced()); }), FCanExecuteAction(), FGetActionCheckState::CreateLambda([this]() { return StackViewModel->GetShowAllAdvanced() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; })), NAME_None, EUserInterfaceActionType::Check); MenuBuilder.AddMenuEntry( LOCTEXT("ShowParameterReadsLabel", "Show Parameter Reads"), LOCTEXT("ShowParameterReadsToolTip", "Whether or not to show the parameters that a module reads from."), FSlateIcon(), FUIAction( FExecuteAction::CreateLambda([this]() { StackViewModel->SetShowLinkedInputs(!StackViewModel->GetShowLinkedInputs()); }), FCanExecuteAction(), FGetActionCheckState::CreateLambda([this]() { return StackViewModel->GetShowLinkedInputs() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; })), NAME_None, EUserInterfaceActionType::Check); MenuBuilder.AddMenuEntry( LOCTEXT("ShowParameterWritesLabel", "Show Parameter Writes"), LOCTEXT("ShowParameterWritesToolTip", "Whether or not to show parameters that a module writes to."), FSlateIcon(), FUIAction( FExecuteAction::CreateLambda([this]() { StackViewModel->SetShowOutputs(!StackViewModel->GetShowOutputs()); }), FCanExecuteAction(), FGetActionCheckState::CreateLambda([this]() { return StackViewModel->GetShowOutputs() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; })), NAME_None, EUserInterfaceActionType::Check); MenuBuilder.AddMenuEntry( LOCTEXT("ShowIssuesLabel", "Show Only Issues"), LOCTEXT("ShowIssuesToolTip", "Hides all modules except those that have unresolved issues. This action is only enabled when system and emitter nodes are selected in the graph, as it could potentially hide any selected modules."), FSlateIcon(), FUIAction( FExecuteAction::CreateLambda([this]() { StackViewModel->SetShowOnlyIssues(!StackViewModel->GetShowOnlyIssues()); }), FCanExecuteAction::CreateLambda([this]() { if (UNiagaraStackEntry* RootEntry = StackViewModel->GetRootEntry()) { if (UNiagaraSystemSelectionViewModel* SelectionViewModel = RootEntry->GetSystemViewModel()->GetSelectionViewModel()) { // only allow the user to toggle this option when a system or emitter node is selected TArray SelectedEntries; SelectionViewModel->GetSelectedEntries(SelectedEntries); for (UNiagaraStackEntry* SelectedEntry : SelectedEntries) { if (!SelectedEntry->IsA()) { return false; } } return SelectedEntries.Num() > 0; } } return false; }), FGetActionCheckState::CreateLambda([this]() { return StackViewModel->GetShowOnlyIssues() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; })), NAME_None, EUserInterfaceActionType::Check); return MenuBuilder.MakeWidget(); } FReply SNiagaraStack::OnRowDragDetected(const FGeometry& InGeometry, const FPointerEvent& InPointerEvent, UNiagaraStackEntry* InStackEntry) { if (InStackEntry->CanDrag()) { TArray DraggedEntries; DraggedEntries.Add(InStackEntry); return FReply::Handled().BeginDragDrop(FNiagaraStackEditorWidgetsUtilities::ConstructDragDropOperationForStackEntries(DraggedEntries)); } return FReply::Unhandled(); } void SNiagaraStack::OnRowDragLeave(FDragDropEvent const& InDragDropEvent) { FNiagaraStackEditorWidgetsUtilities::HandleDragLeave(InDragDropEvent); } TOptional SNiagaraStack::OnRowCanAcceptDrop(const FDragDropEvent& InDragDropEvent, EItemDropZone InDropZone, UNiagaraStackEntry* InTargetEntry) { return FNiagaraStackEditorWidgetsUtilities::RequestDropForStackEntry(InDragDropEvent, InDropZone, InTargetEntry, UNiagaraStackEntry::EDropOptions::None); } FReply SNiagaraStack::OnRowAcceptDrop(const FDragDropEvent& InDragDropEvent, EItemDropZone InDropZone, UNiagaraStackEntry* InTargetEntry) { bool bHandled = FNiagaraStackEditorWidgetsUtilities::HandleDropForStackEntry(InDragDropEvent, InDropZone, InTargetEntry, UNiagaraStackEntry::EDropOptions::None); return bHandled ? FReply::Handled() : FReply::Unhandled(); } void SNiagaraStack::OnStackSearchComplete() { ExpandSearchResults(); ScrollFocusedEntryIntoView(); } void SNiagaraStack::ExpandSearchResults() { for (auto SearchResult : StackViewModel->GetCurrentSearchResults()) { ExpandAllInPath(SearchResult.EntryPath); } bSynchronizeExpansionPending = true; } void SNiagaraStack::OnSearchBoxTextCommitted(const FText& NewText, ETextCommit::Type CommitInfo) { if (CommitInfo == ETextCommit::OnEnter) // hasn't been autojumped yet or we hit enter { AddSearchScrollOffset(+1); } } void SNiagaraStack::OnSearchBoxSearch(SSearchBox::SearchDirection Direction) { if (Direction == SSearchBox::Next) { ScrollToNextMatch(); } else if (Direction == SSearchBox::Previous) { ScrollToPreviousMatch(); } } FSlateColor SNiagaraStack::GetTextColorForItem(UNiagaraStackEntry* Item) const { if (IsEntryFocusedInSearch(Item)) { return FSlateColor(FLinearColor(FColor::Orange)); } return FSlateColor::UseForeground(); } void SNiagaraStack::ScrollFocusedEntryIntoView() { StackTree->RequestScrollIntoView(StackViewModel->GetCurrentFocusedEntry()); } TSharedRef SNiagaraStack::OnGenerateRowForStackItem(UNiagaraStackEntry* Item, const TSharedRef& OwnerTable) { TSharedRef Container = ConstructContainerForItem(Item); FRowWidgets RowWidgets = ConstructNameAndValueWidgetsForItem(Item, Container); Container->SetNameAndValueContent(RowWidgets.NameWidget, RowWidgets.ValueWidget); return Container; } TSharedRef SNiagaraStack::OnGenerateRowForTopLevelObject(TSharedRef Item, const TSharedRef& OwnerTable) { TSharedPtr Content; if (Item->SystemViewModel.IsValid()) { Content = SNew(SBorder) .BorderImage(FAppStyle::GetBrush("WhiteBrush")) .BorderBackgroundColor(FStyleColors::Panel) [ SNew(SHorizontalBox) // System name + SHorizontalBox::Slot() [ SNew(STextBlock) .TextStyle(FAppStyle::Get(), "DetailsView.ConstantTextBlockStyle") .Text(Item->SystemViewModel.ToSharedRef(), &FNiagaraSystemViewModel::GetDisplayName) ] + SHorizontalBox::Slot() .AutoWidth() .VAlign(VAlign_Center) .Padding(4, 0, 0, 0) [ SNew(SNiagaraStackHeaderButtons, Item->RootEntry.Get(), StackViewModel) .IssueIconVisibility(this, &SNiagaraStack::GetIssueIconVisibility) .OnCycleThroughIssues(this, &SNiagaraStack::OnCycleThroughIssues) .ParentStack(SharedThis(this)) ] ]; } else if (Item->EmitterHandleViewModel.IsValid()) { Content = SNew(SNiagaraStackEmitterHeader, Item->EmitterHandleViewModel.ToSharedRef(), Item->RootEntry.Get(), StackViewModel) .IssueIconVisibility(this, &SNiagaraStack::GetIssueIconVisibility) .OnCycleThroughIssues(this, &SNiagaraStack::OnCycleThroughIssues) .ParentStack(SharedThis(this)); } return SNew(STableRow>, OwnerTable) [ Content.ToSharedRef() ]; } TSharedPtr SNiagaraStack::GenerateStackMenu(TWeakPtr TopLevelViewModelWeak) { TSharedPtr TopLevelViewModel = TopLevelViewModelWeak.Pin(); if (TopLevelViewModel.IsValid()) { TSharedPtr GraphCommandList = TopLevelViewModel->RootEntry->GetSystemViewModel()->GetOverviewGraphViewModel()->GetCommands(); FMenuBuilder MenuBuilder(true, GraphCommandList); FNiagaraEditorUtilities::AddEmitterContextMenuActions(MenuBuilder, TopLevelViewModel->EmitterHandleViewModel); { MenuBuilder.BeginSection("EmitterEditSection", LOCTEXT("Edit", "Edit")); MenuBuilder.AddMenuEntry(FGenericCommands::Get().Cut); MenuBuilder.AddMenuEntry(FGenericCommands::Get().Copy); MenuBuilder.AddMenuEntry(FGenericCommands::Get().Delete); MenuBuilder.AddMenuEntry(FGenericCommands::Get().Rename); MenuBuilder.EndSection(); } MenuBuilder.BeginSection("StackActions", LOCTEXT("StackActions", "Stack Actions")); { if (StackViewModel->HasDismissedStackIssues()) { MenuBuilder.AddMenuEntry( LOCTEXT("UndismissIssues", "Undismiss All Stack Issues"), LOCTEXT("ShowAssetInContentBrowserToolTip", "Undismiss all issues that were previously dismissed for this stack, if any"), FSlateIcon(), FUIAction(FExecuteAction::CreateUObject(StackViewModel, &UNiagaraStackViewModel::UndismissAllIssues))); } MenuBuilder.AddMenuEntry( LOCTEXT("CollapseStack", "Collapse All"), LOCTEXT("CollapseStackToolTip", "Collapses every row in the stack."), FSlateIcon(), FUIAction(FExecuteAction::CreateSP(this, &SNiagaraStack::CollapseAll))); TSharedPtr CollapseToHeadersCommand = FNiagaraEditorModule::Get().Commands().CollapseStackToHeaders; MenuBuilder.AddMenuEntry( CollapseToHeadersCommand->GetLabel(), CollapseToHeadersCommand->GetDescription(), FSlateIcon(), FUIAction(FExecuteAction::CreateUObject(StackViewModel, &UNiagaraStackViewModel::CollapseToHeaders))); } MenuBuilder.EndSection(); return MenuBuilder.MakeWidget(); } return SNullWidget::NullWidget; } TSharedRef SNiagaraStack::ConstructContainerForItem(UNiagaraStackEntry* Item) { float LeftContentPadding = 0; float RightContentPadding = 0; float ItemContentVerticalPadding = 1; FMargin ContentPadding(LeftContentPadding, 0, RightContentPadding, 0); FSlateColor IndicatorColor = FLinearColor::Transparent; bool bIsCategoryIconHighlighted = false; bool bShowExecutionCategoryIcon = false; const FTableRowStyle* TableRowStyle = nullptr; switch (Item->GetStackRowStyle()) { case UNiagaraStackEntry::EStackRowStyle::GroupHeader: ContentPadding = FMargin(LeftContentPadding, 3, 0, 3); bIsCategoryIconHighlighted = true; bShowExecutionCategoryIcon = true; TableRowStyle = &FNiagaraEditorWidgetsStyle::Get().GetWidgetStyle("NiagaraEditor.Stack.TableViewRow.ItemHeader"); break; case UNiagaraStackEntry::EStackRowStyle::ItemHeader: ContentPadding = FMargin(LeftContentPadding, 2, 2, 2); bShowExecutionCategoryIcon = true; TableRowStyle = &FNiagaraEditorWidgetsStyle::Get().GetWidgetStyle("NiagaraEditor.Stack.TableViewRow.ItemHeader"); break; case UNiagaraStackEntry::EStackRowStyle::ItemContent: ContentPadding = FMargin(LeftContentPadding, ItemContentVerticalPadding, RightContentPadding, ItemContentVerticalPadding); break; case UNiagaraStackEntry::EStackRowStyle::ItemContentAdvanced: ContentPadding = FMargin(LeftContentPadding, ItemContentVerticalPadding, RightContentPadding, ItemContentVerticalPadding); TableRowStyle = &FNiagaraEditorWidgetsStyle::Get().GetWidgetStyle("NiagaraEditor.Stack.TableViewRow.ItemContentAdvanced"); break; case UNiagaraStackEntry::EStackRowStyle::ItemContentNote: ContentPadding = FMargin(LeftContentPadding, ItemContentVerticalPadding, RightContentPadding, ItemContentVerticalPadding); TableRowStyle = &FNiagaraEditorWidgetsStyle::Get().GetWidgetStyle("NiagaraEditor.Stack.TableViewRow.ItemContentNote"); break; case UNiagaraStackEntry::EStackRowStyle::ItemCategory: ContentPadding = FMargin(LeftContentPadding, ItemContentVerticalPadding, RightContentPadding, ItemContentVerticalPadding); TableRowStyle = &FNiagaraEditorWidgetsStyle::Get().GetWidgetStyle("NiagaraEditor.Stack.TableViewRow.ItemHeader"); break; case UNiagaraStackEntry::EStackRowStyle::StackIssue: switch (Item->GetIssueSeverity()) { case EStackIssueSeverity::Error: IndicatorColor = FStyleColors::Error; break; case EStackIssueSeverity::Warning: IndicatorColor = FStyleColors::Warning; break; case EStackIssueSeverity::Info: IndicatorColor = FStyleColors::Foreground; break; default: checkf(false, TEXT("Issue severity not set for stack issue.")); } ContentPadding = FMargin(LeftContentPadding, ItemContentVerticalPadding, RightContentPadding, ItemContentVerticalPadding); break; case UNiagaraStackEntry::EStackRowStyle::Spacer: TableRowStyle = &FNiagaraEditorWidgetsStyle::Get().GetWidgetStyle("NiagaraEditor.Stack.TableViewRow.Spacer"); } if (TableRowStyle == nullptr) { TableRowStyle = &FNiagaraEditorWidgetsStyle::Get().GetWidgetStyle("NiagaraEditor.Stack.TableViewRow.ItemContent"); } return SNew(SNiagaraStackTableRow, StackViewModel, Item, StackCommandContext.ToSharedRef(), StackTree.ToSharedRef()) .Style(TableRowStyle) .ContentPadding(ContentPadding) .IndicatorColor(IndicatorColor) .IsCategoryIconHighlighted(bIsCategoryIconHighlighted) .ShowExecutionCategoryIcon(bShowExecutionCategoryIcon) .NameColumnWidth(this, &SNiagaraStack::GetNameColumnWidth) .OnNameColumnWidthChanged(this, &SNiagaraStack::OnNameColumnWidthChanged) .ValueColumnWidth(this, &SNiagaraStack::GetContentColumnWidth) .OnValueColumnWidthChanged(this, &SNiagaraStack::OnContentColumnWidthChanged) .OnDragDetected(this, &SNiagaraStack::OnRowDragDetected, Item) .OnDragLeave(this, &SNiagaraStack::OnRowDragLeave) .OnCanAcceptDrop(this, &SNiagaraStack::OnRowCanAcceptDrop) .OnAcceptDrop(this, &SNiagaraStack::OnRowAcceptDrop) .IssueIconVisibility(this, &SNiagaraStack::GetIssueIconVisibility); } FReply SNiagaraStack::OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent) { if (StackCommandContext->ProcessCommandBindings(InKeyEvent)) { return FReply::Handled(); } return SCompoundWidget::OnKeyDown(MyGeometry, InKeyEvent); } void SNiagaraStack::Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime) { // Update the stack view model and synchronize the expansion state before the parent tick to ensure that the state is up to date before ticking child widgets. if (StackViewModel) { StackViewModel->Tick(); } if (bSynchronizeExpansionPending) { SynchronizeTreeExpansion(); } SCompoundWidget::Tick(AllottedGeometry, InCurrentTime, InDeltaTime); } SNiagaraStack::FRowWidgets SNiagaraStack::ConstructNameAndValueWidgetsForItem(UNiagaraStackEntry* Item, TSharedRef Container) { if (Item->IsA()) { TSharedRef GroupWidget = SNew(SNiagaraStackItemGroup, *CastChecked(Item), StackViewModel); if (Item->IsA()) { Container->AddFillRowContextMenuHandler(SNiagaraStackTableRow::FOnFillRowContextMenu::CreateStatic( &FNiagaraStackEditorWidgetsUtilities::AddStackSimulationStageGroupContextMenuActions, CastChecked(Item), GroupWidget)); } return FRowWidgets(GroupWidget); } else if (Item->IsA()) { TSharedRef ModuleItemWidget = SNew(SNiagaraStackModuleItem, *CastChecked(Item), StackViewModel); Container->AddFillRowContextMenuHandler(SNiagaraStackTableRow::FOnFillRowContextMenu::CreateSP(ModuleItemWidget, &SNiagaraStackModuleItem::FillRowContextMenu)); return FRowWidgets(ModuleItemWidget); } else if (Item->IsA()) { UNiagaraStackFunctionInput* FunctionInput = CastChecked(Item); TSharedRef FunctionInputNameWidget = SNew(SNiagaraStackFunctionInputName, FunctionInput, StackViewModel) .IsSelected(Container, &SNiagaraStackTableRow::IsSelected); FunctionInputNameWidget->OnRequestReconstructRow().AddSP(this, &SNiagaraStack::ReconstructNameAndValueWidgetsForItem, Item); bool bShowInlineDynamicInput = GbEnableExperimentalInlineDynamicInputs && FunctionInput->GetValueMode() == UNiagaraStackFunctionInput::EValueMode::Dynamic && FunctionInput->GetInlineDisplayMode() != ENiagaraStackEntryInlineDisplayMode::None; TSharedPtr FunctionInputValueWidget = nullptr; if (bShowInlineDynamicInput) { FunctionInputValueWidget = SNew(SNiagaraStackInlineDynamicInput, FunctionInput); } else { FunctionInputValueWidget = SNew(SNiagaraStackFunctionInputValue, FunctionInput); } Container->AddFillRowContextMenuHandler(SNiagaraStackTableRow::FOnFillRowContextMenu::CreateSP(FunctionInputNameWidget, &SNiagaraStackFunctionInputName::FillRowContextMenu)); if (bShowInlineDynamicInput && (FunctionInput->GetInlineDisplayMode() == ENiagaraStackEntryInlineDisplayMode::GraphHorizontal || FunctionInput->GetInlineDisplayMode() == ENiagaraStackEntryInlineDisplayMode::GraphVertical || FunctionInput->GetInlineDisplayMode() == ENiagaraStackEntryInlineDisplayMode::GraphHybrid)) { return FRowWidgets( SNew(SVerticalBox) + SVerticalBox::Slot() .AutoHeight() [ FunctionInputNameWidget ] + SVerticalBox::Slot() .AutoHeight() [ FunctionInputValueWidget.ToSharedRef() ]); } else { return FRowWidgets(FunctionInputNameWidget, FunctionInputValueWidget.ToSharedRef()); } } else if (Item->IsA()) { return FRowWidgets(SNew(SNiagaraStackErrorItem, CastChecked(Item), StackViewModel)); } else if (Item->IsA()) { Container->SetOverrideNameAlignment(EHorizontalAlignment::HAlign_Fill, EVerticalAlignment::VAlign_Center); return FRowWidgets(SNew(STextBlock) .TextStyle(FNiagaraEditorStyle::Get(), "NiagaraEditor.ParameterText") .ToolTipText_UObject(Item, &UNiagaraStackEntry::GetTooltipText) .Text_UObject(Item, &UNiagaraStackEntry::GetDisplayName) .ColorAndOpacity(this, &SNiagaraStack::GetTextColorForItem, Item) .HighlightText_UObject(StackViewModel, &UNiagaraStackViewModel::GetCurrentSearchText) .AutoWrapText(true)); } else if (Item->IsA()) { return FRowWidgets(SNew(SNiagaraStackErrorItemFix, CastChecked(Item), StackViewModel)); } else if(UNiagaraStackNote* StackNote = Cast(Item)) { TSharedRef ContentWidget = SNew(SNiagaraStackNote, *StackNote) .bShowEditTextButtons(Container, &SNiagaraStackTableRow::IsHovered); Container->AddFillRowContextMenuHandler(SNiagaraStackTableRow::FOnFillRowContextMenu::CreateSP(ContentWidget, &SNiagaraStackNote::FillRowContextMenu)); return FRowWidgets(ContentWidget); } else if (Item->IsA()) { UNiagaraStackItemFooter* ItemExpander = CastChecked(Item); return FRowWidgets(SNew(SNiagaraStackItemFooter, *ItemExpander)); } else if (Item->IsA()) { Container->SetOverrideNameAlignment(HAlign_Left, VAlign_Center); return FRowWidgets(SNew(STextBlock) .TextStyle(FNiagaraEditorWidgetsStyle::Get(), "NiagaraEditor.Stack.ParameterText") .ToolTipText_UObject(Item, &UNiagaraStackEntry::GetTooltipText) .Text_UObject(Item, &UNiagaraStackEntry::GetDisplayName) .ColorAndOpacity(this, &SNiagaraStack::GetTextColorForItem, Item) .HighlightText_UObject(StackViewModel, &UNiagaraStackViewModel::GetCurrentSearchText) .IsEnabled_UObject(Item, &UNiagaraStackEntry::GetOwnerIsEnabled), SNullWidget::NullWidget); } else if (Item->IsA()) { UNiagaraStackModuleItemOutput* ModuleItemOutput = CastChecked(Item); return FRowWidgets( SNew(SNiagaraParameterName) .ReadOnlyTextStyle(FNiagaraEditorWidgetsStyle::Get(), "NiagaraEditor.Stack.ItemText") .ToolTipText_UObject(Item, &UNiagaraStackEntry::GetTooltipText) .ParameterName(ModuleItemOutput->GetOutputParameterHandle().GetParameterHandleString()) .HighlightText_UObject(StackViewModel, &UNiagaraStackViewModel::GetCurrentSearchText) .IsEnabled_UObject(Item, &UNiagaraStackEntry::GetOwnerIsEnabled) .IsReadOnly(true) ); } else if (Item->IsA()) { UNiagaraStackValueCollection* InputCollection = CastChecked(Item); return FRowWidgets(SNew(SNiagaraStackValueCollection, InputCollection)); } else if (Item->IsA()) { UNiagaraStackScriptHierarchyRoot* ModuleHierarchyRoot = CastChecked(Item); return FRowWidgets(SNew(SNiagaraStackScriptHierarchyRoot, ModuleHierarchyRoot)); } else if (Item->IsA() || Item->IsA()) { return FRowWidgets( SNew(STextBlock) .TextStyle(FNiagaraEditorWidgetsStyle::Get(), "NiagaraEditor.Stack.ItemText") .ToolTipText_UObject(Item, &UNiagaraStackEntry::GetTooltipText) .Text_UObject(Item, &UNiagaraStackEntry::GetDisplayName) .ColorAndOpacity(this, &SNiagaraStack::GetTextColorForItem, Item) .HighlightText_UObject(StackViewModel, &UNiagaraStackViewModel::GetCurrentSearchText) .IsEnabled_UObject(Item, &UNiagaraStackEntry::GetOwnerIsEnabled), SNullWidget::NullWidget); } else if (Item->IsA()) { UNiagaraStackPropertyRow* PropertyRow = CastChecked(Item); FNodeWidgets PropertyRowWidgets = PropertyRow->GetDetailTreeNode()->CreateNodeWidgets(); TAttribute IsEnabled; IsEnabled.BindUObject(Item, &UNiagaraStackEntry::GetIsEnabledAndOwnerIsEnabled); Container->AddFillRowContextMenuHandler(FNiagaraStackPropertyRowUtilities::CreateOnFillRowContextMenu(*PropertyRow, PropertyRowWidgets.Actions)); if (PropertyRowWidgets.WholeRowWidget.IsValid()) { Container->SetOverrideNameWidth(PropertyRowWidgets.WholeRowWidgetLayoutData.MinWidth, PropertyRowWidgets.WholeRowWidgetLayoutData.MaxWidth); Container->SetOverrideNameAlignment(PropertyRowWidgets.WholeRowWidgetLayoutData.HorizontalAlignment, PropertyRowWidgets.WholeRowWidgetLayoutData.VerticalAlignment); PropertyRowWidgets.WholeRowWidget->SetEnabled(IsEnabled); TSharedRef RowBox = SNew(SHorizontalBox); if (PropertyRowWidgets.EditConditionWidget.IsValid()) { RowBox->AddSlot() .AutoWidth() .Padding(0, 0, 3, 0) [ PropertyRowWidgets.EditConditionWidget.ToSharedRef() ]; } RowBox->AddSlot() [ PropertyRowWidgets.WholeRowWidget.ToSharedRef() ]; // do not add reset widget if it is customized TSharedPtr PropertyHandle = PropertyRow->GetDetailTreeNode()->CreatePropertyHandle(); if (PropertyHandle.IsValid() && !(PropertyHandle->IsResetToDefaultCustomized() || PropertyHandle->HasMetaData(TEXT("NoResetToDefault")))) { RowBox->AddSlot() .AutoWidth() .Padding(3, 0, 0, 0) [ SNew(SResetToDefaultPropertyEditor, PropertyHandle) ]; } return FRowWidgets(RowBox); } else { TSharedPtr Customization = PropertyRow->GetCustomization(); if(Customization) { TOptional> CustomizedNameWidget = Customization->GenerateNameWidget(PropertyRow); if(CustomizedNameWidget.IsSet()) { PropertyRowWidgets.NameWidget = CustomizedNameWidget.GetValue(); } } Container->SetOverrideNameWidth(PropertyRowWidgets.NameWidgetLayoutData.MinWidth, PropertyRowWidgets.NameWidgetLayoutData.MaxWidth); Container->SetOverrideValueWidth(PropertyRowWidgets.ValueWidgetLayoutData.MinWidth, PropertyRowWidgets.ValueWidgetLayoutData.MaxWidth); Container->SetOverrideNameAlignment(HAlign_Left, VAlign_Center); PropertyRowWidgets.NameWidget->SetEnabled(IsEnabled); PropertyRowWidgets.ValueWidget->SetEnabled(IsEnabled); TSharedPtr NameWidget; if (PropertyRowWidgets.EditConditionWidget.IsValid()) { NameWidget = SNew(SHorizontalBox) + SHorizontalBox::Slot() .AutoWidth() .Padding(0, 0, 3, 0) [ PropertyRowWidgets.EditConditionWidget.ToSharedRef() ] + SHorizontalBox::Slot() [ PropertyRowWidgets.NameWidget.ToSharedRef() ]; } else { NameWidget = PropertyRowWidgets.NameWidget; } TSharedRef ValueWidget = SNew(SHorizontalBox) + SHorizontalBox::Slot() .HAlign(HAlign_Left) [ PropertyRowWidgets.ValueWidget.ToSharedRef() ]; // do not add reset widget if it is customized TSharedPtr PropertyHandle = PropertyRow->GetDetailTreeNode()->CreatePropertyHandle(); if (PropertyHandle.IsValid() && !(PropertyHandle->IsResetToDefaultCustomized() || PropertyHandle->HasMetaData(TEXT("NoResetToDefault")))) { ValueWidget->AddSlot() .AutoWidth() .VAlign(VAlign_Center) .Padding(3, 0, 0, 0) [ SNew(SResetToDefaultPropertyEditor, PropertyHandle) ]; } return FRowWidgets(NameWidget.ToSharedRef(), ValueWidget); } } else if (Item->IsA()) { UNiagaraStackItem* StackItem = CastChecked(Item); return FRowWidgets(SNew(SNiagaraStackItem, *StackItem, StackViewModel)); } else if (Item->IsA()) { Container->SetContentPadding(FMargin(5)); UNiagaraStackItemTextContent* ItemTextContent = CastChecked(Item); return FRowWidgets(SNew(STextBlock) .TextStyle(FNiagaraEditorWidgetsStyle::Get(), "NiagaraEditor.Stack.TextContentText") .Text(ItemTextContent->GetDisplayName()) .AutoWrapText(true) .Justification(ETextJustify::Center)); } else if (Item->IsA()) { Container->SetOverrideNameAlignment(HAlign_Left, VAlign_Center); return FRowWidgets(SNew(STextBlock) .TextStyle(&FAppStyle::GetWidgetStyle("DetailsView.CategoryTextStyle")) .ToolTipText_UObject(Item, &UNiagaraStackEntry::GetTooltipText) .Text_UObject(Item, &UNiagaraStackEntry::GetDisplayName) .ColorAndOpacity(this, &SNiagaraStack::GetTextColorForItem, Item) .HighlightText_UObject(StackViewModel, &UNiagaraStackViewModel::GetCurrentSearchText) .IsEnabled_UObject(Item, &UNiagaraStackEntry::GetOwnerIsEnabled), SNullWidget::NullWidget); } else if (Item->IsA()) { UNiagaraStackSpacer* Spacer = CastChecked(Item); return FRowWidgets(SNew(SBox) .HeightOverride(Spacer->GetSpacerHeight())); } else { return FRowWidgets(SNullWidget::NullWidget); } } void SNiagaraStack::ReconstructNameAndValueWidgetsForItem(UNiagaraStackEntry* Item) { TSharedPtr TableRow = StackTree->WidgetFromItem(Item); if (TableRow.IsValid()) { TSharedRef Container = StaticCastSharedRef(TableRow.ToSharedRef()); Container->Reset(); FRowWidgets RowWidgets = ConstructNameAndValueWidgetsForItem(Item, Container); Container->SetNameAndValueContent(RowWidgets.NameWidget, RowWidgets.ValueWidget); } } void SNiagaraStack::OnGetChildren(UNiagaraStackEntry* Item, TArray& Children) { Item->GetFilteredChildren(Children); } void SNiagaraStack::StackTreeScrolled(double ScrollValue) { StackViewModel->SetLastScrollPosition(ScrollValue); } void SNiagaraStack::StackTreeSelectionChanged(UNiagaraStackEntry* InNewSelection, ESelectInfo::Type SelectInfo) { TArray SelectedStackEntries; StackTree->GetSelectedItems(SelectedStackEntries); StackCommandContext->SetSelectedEntries(SelectedStackEntries); } float SNiagaraStack::GetNameColumnWidth() const { return NameColumnWidth; } float SNiagaraStack::GetContentColumnWidth() const { return ContentColumnWidth; } void SNiagaraStack::OnNameColumnWidthChanged(float Width) { NameColumnWidth = Width; } void SNiagaraStack::OnContentColumnWidthChanged(float Width) { ContentColumnWidth = Width; } void SNiagaraStack::OnStackExpansionChanged() { bSynchronizeExpansionPending = true; } void SNiagaraStack::StackStructureChanged(ENiagaraStructureChangedFlags Flags) { bSynchronizeExpansionPending = true; StackTree->RequestTreeRefresh(); HeaderList->RequestListRefresh(); } EVisibility SNiagaraStack::GetIssueIconVisibility() const { return StackViewModel->HasIssues() ? EVisibility::Visible : EVisibility::Collapsed; } FReply SNiagaraStack::OnCycleThroughSystemIssues(TSharedPtr SystemViewModel) { UNiagaraStackEntry* RootEntry = SystemViewModel->GetSystemStackViewModel()->GetRootEntry(); if (RootEntry != nullptr) { TSharedPtr TopLevelViewModel = StackViewModel->GetTopLevelViewModelForEntry(*RootEntry); StackViewModel->OnCycleThroughIssues(TopLevelViewModel); OnCycleThroughIssues(); } return FReply::Handled(); } void SNiagaraStack::OnCycleThroughIssues() { UNiagaraStackEntry* StackEntry = StackViewModel->GetCurrentFocusedIssue(); TArray EntryPath; StackViewModel->GetPathForEntry(StackEntry, EntryPath); EntryPath.Add(StackEntry); ExpandAllInPath(EntryPath); SynchronizeTreeExpansion(); StackTree->RequestScrollIntoView(StackEntry); } const FSlateBrush* SNiagaraStack::GetViewOptionsBadgeIcon() const { // Badge the icon if any view option that narrows down the results is checked bool bHasBadge = (StackViewModel->GetShowOnlyModified() || StackViewModel->GetShowOnlyIssues() || StackViewModel->GetShowAllAdvanced() || StackViewModel->GetShowLinkedInputs() || StackViewModel->GetShowOutputs()); return bHasBadge ? FAppStyle::Get().GetBrush("Icons.BadgeModified") : nullptr; } #undef LOCTEXT_NAMESPACE