From d5f9cab6b101ab01695f6b9634071c646b308029 Mon Sep 17 00:00:00 2001 From: MaksymKapelianovych Date: Fri, 17 Apr 2026 20:25:05 +0300 Subject: [PATCH] Diff window improvements --- .../Asset/AssetDefinition_FlowAsset.cpp | 16 ++ .../Private/Asset/FlowDiffControl.cpp | 35 ++- Source/FlowEditor/Private/Asset/SFlowDiff.cpp | 202 +++++++++++------- .../FlowEditor/Public/Asset/FlowDiffControl.h | 2 +- Source/FlowEditor/Public/Asset/SFlowDiff.h | 30 ++- 5 files changed, 167 insertions(+), 118 deletions(-) diff --git a/Source/FlowEditor/Private/Asset/AssetDefinition_FlowAsset.cpp b/Source/FlowEditor/Private/Asset/AssetDefinition_FlowAsset.cpp index 276c61faa..721de11b9 100644 --- a/Source/FlowEditor/Private/Asset/AssetDefinition_FlowAsset.cpp +++ b/Source/FlowEditor/Private/Asset/AssetDefinition_FlowAsset.cpp @@ -4,6 +4,7 @@ #include "Asset/SFlowDiff.h" #include "FlowEditorModule.h" #include "Graph/FlowGraphSettings.h" +#include "Graph/FlowGraph.h" #include "FlowAsset.h" @@ -63,6 +64,21 @@ EAssetCommandResult UAssetDefinition_FlowAsset::PerformAssetDiff(const FAssetDif const UFlowAsset* OldFlow = Cast(DiffArgs.OldAsset); const UFlowAsset* NewFlow = Cast(DiffArgs.NewAsset); + auto SetupGraph = [](const UFlowAsset* Asset) + { + if (IsValid(Asset)) + { + UFlowGraph* FlowGraph = Cast(Asset->GetGraph()); + if (IsValid(FlowGraph)) // can it be invalid? + { + FlowGraph->OnLoaded(); // mainly for filling parent nodes for add-ons, because they are transient + } + } + }; + + SetupGraph(OldFlow); + SetupGraph(NewFlow); + // sometimes we're comparing different revisions of one single asset (other // times we're comparing two completely separate assets altogether) const bool bIsSingleAsset = !IsValid(OldFlow) || !IsValid(NewFlow) || (OldFlow->GetName() == NewFlow->GetName()); diff --git a/Source/FlowEditor/Private/Asset/FlowDiffControl.cpp b/Source/FlowEditor/Private/Asset/FlowDiffControl.cpp index c85a47c41..414a2c9ec 100644 --- a/Source/FlowEditor/Private/Asset/FlowDiffControl.cpp +++ b/Source/FlowEditor/Private/Asset/FlowDiffControl.cpp @@ -242,7 +242,7 @@ void FFlowGraphToDiff::GenerateTreeEntries(TArrayParentNodeDiff = FindParentNode(Cast(Node1)); + FlowNodeDiff->ParentNodeDiff = FindParentDiff(Cast(Node1)); if (FlowNodeDiff->ParentNodeDiff.IsValid()) { const TSharedPtr ParentNode = FlowNodeDiff->ParentNodeDiff.Pin(); @@ -341,7 +341,7 @@ TSharedPtr FFlowGraphToDiff::GenerateFlowObjectDiff(const TShar return NewFlowObjectDiff; } -TSharedPtr FFlowGraphToDiff::FindParentNode(UFlowGraphNode* Node) +TSharedPtr FFlowGraphToDiff::FindParentDiff(UFlowGraphNode* Node) { if (!IsValid(Node)) { @@ -349,36 +349,27 @@ TSharedPtr FFlowGraphToDiff::FindParentNode(UFlowGraphNode* Nod } const UFlowGraphNode* ParentNode = Node->GetParentNode(); - for (auto& FlowNodeDiff : FlowObjectDiffsByNodeName) + while ( IsValid( ParentNode ) ) { - //don't allow a pin diff be the parent of anything. - if (FlowNodeDiff.Value->DiffResult->Result.Pin1) + for (auto& FlowNodeDiff : FlowObjectDiffsByNodeName) { - continue; - } - //if parent node is set, use that. - if (IsValid(ParentNode)) - { - if (FlowNodeDiff.Value->DiffResult->Result.Node1 == ParentNode - || FlowNodeDiff.Value->DiffResult->Result.Node2 == ParentNode) + //don't allow a pin diff be the parent of anything. + if (FlowNodeDiff.Value->DiffResult->Result.Pin1) { - return FlowNodeDiff.Value; + continue; } - } - //if parent node is not set (not set in node removal changes for some reason), - //try to find the parent in the SubNodes of known node changes. - else - { - const UFlowGraphNode* NodeToCheck = Cast(FlowNodeDiff.Value->DiffResult->Result.Node1); - if (IsValid(NodeToCheck)) + //if parent node is set, use that. + if (IsValid(ParentNode)) { - const int32 Index = NodeToCheck->SubNodes.Find(Node); - if (Index != INDEX_NONE) + if (FlowNodeDiff.Value->DiffResult->Result.Node1 == ParentNode + || FlowNodeDiff.Value->DiffResult->Result.Node2 == ParentNode) { return FlowNodeDiff.Value; } } } + + ParentNode = ParentNode->GetParentNode(); } return nullptr; diff --git a/Source/FlowEditor/Private/Asset/SFlowDiff.cpp b/Source/FlowEditor/Private/Asset/SFlowDiff.cpp index 2b1be2976..a245802c9 100644 --- a/Source/FlowEditor/Private/Asset/SFlowDiff.cpp +++ b/Source/FlowEditor/Private/Asset/SFlowDiff.cpp @@ -32,6 +32,7 @@ FFlowDiffPanel::FFlowDiffPanel() : FlowAsset(nullptr) , bShowAssetName(false) { + DetailScrollbar = SNew(SLinkableScrollBar); } static int32 GetCurrentIndex(SListView> const& ListView, const TArray>& ListViewSource) @@ -279,6 +280,18 @@ SFlowDiff::~SFlowDiff() } } +void SFlowDiff::Tick( const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime ) +{ + SCompoundWidget::Tick(AllottedGeometry, InCurrentTime, InDeltaTime); + + if (const TSharedPtr DiffControl = ModePanels[CurrentMode].DiffControl) + { + DiffControl->Tick(); + } + + GraphDetailDiff->Tick(); +} + void SFlowDiff::OnCloseAssetEditor(UObject* Asset, const EAssetEditorCloseReason CloseReason) { if (PanelOld.FlowAsset == Asset || PanelNew.FlowAsset == Asset || CloseReason == EAssetEditorCloseReason::CloseAllAssetEditors) @@ -412,8 +425,7 @@ void SFlowDiff::OnDiffListSelectionChanged(TSharedPtr FlowO check(!FlowObjectDiff->DiffResult->Result.OwningObjectPath.IsEmpty()); FocusOnGraphRevisions(FindGraphToDiffEntry(FlowObjectDiff->DiffResult->Result.OwningObjectPath)); - const TSharedPtr ParentFlowNodeDiff = FlowObjectDiff->ParentNodeDiff.Pin(); - const FDiffSingleResult& Result = ParentFlowNodeDiff.IsValid() ? ParentFlowNodeDiff->DiffResult->Result : FlowObjectDiff->DiffResult->Result; + const FDiffSingleResult& Result = FlowObjectDiff->DiffResult->Result; const auto SafeClearSelection = [](TWeakPtr GraphEditor) { @@ -427,20 +439,6 @@ void SFlowDiff::OnDiffListSelectionChanged(TSharedPtr FlowO SafeClearSelection(PanelNew.GraphEditor); SafeClearSelection(PanelOld.GraphEditor); - // PanelDefaultDetailsView can be used for displaying nodes on click. Clear out it's content before potentially trying to show an empty panel. - PanelOld.PanelDefaultDetailsView->SetObject(nullptr); - PanelNew.PanelDefaultDetailsView->SetObject(nullptr); - - //Select the details panel to display below the graphs. - //Show an empty details panel if there is no generated details panel. - const TSharedPtr OldDetailsPanel = FlowObjectDiff->OldDetailsView.IsValid() ? - FlowObjectDiff->OldDetailsView->DetailsWidget() : PanelOld.PanelDefaultDetailsView.ToSharedRef(); - const TSharedPtr NewDetailsPanel = FlowObjectDiff->NewDetailsView.IsValid() ? - FlowObjectDiff->NewDetailsView->DetailsWidget() : PanelNew.PanelDefaultDetailsView.ToSharedRef(); - - GraphDiffSplitter->SetBottomLeftContent(OldDetailsPanel.ToSharedRef()); - GraphDiffSplitter->SetBottomRightContent(NewDetailsPanel.ToSharedRef()); - if (Result.Pin1) { GetDiffPanelForNode(*Result.Pin1->GetOwningNode()).FocusDiff(*Result.Pin1); @@ -451,12 +449,30 @@ void SFlowDiff::OnDiffListSelectionChanged(TSharedPtr FlowO } else if (Result.Node1) { - FlowObjectDiff->OnSelectDiff(FlowObjectDiffArgs->PropertyDiff); + auto ResolvePropertyPath = [](const FSingleObjectDiffEntry& PropertyDiff, const UEdGraphNode* Node) + { + if (const UFlowGraphNode* FlowGraphNode = Cast(Node)) + { + return PropertyDiff.Identifier.ResolvePath(FlowGraphNode->GetFlowNodeBase()); + } - GetDiffPanelForNode(*Result.Node1).FocusDiff(*Result.Node1); + // this is only a Comment node + return PropertyDiff.Identifier.ResolvePath(Node); + }; + + FFlowDiffPanel& OldPanel = GetDiffPanelForNode(*Result.Node1); + OldPanel.FocusDiff(*Result.Node1); + + FPropertyPath OldProperty = ResolvePropertyPath(FlowObjectDiffArgs->PropertyDiff, Result.Node1); + OldPanel.DetailsView->HighlightProperty(OldProperty); + if (Result.Node2) { - GetDiffPanelForNode(*Result.Node2).FocusDiff(*Result.Node2); + FFlowDiffPanel& NewPanel = GetDiffPanelForNode(*Result.Node2); + NewPanel.FocusDiff(*Result.Node2); + + FPropertyPath NewProperty = ResolvePropertyPath(FlowObjectDiffArgs->PropertyDiff, Result.Node2); + NewPanel.DetailsView->HighlightProperty(NewProperty); } } } @@ -529,27 +545,34 @@ void FFlowDiffPanel::GeneratePanel(UEdGraph* Graph, TSharedPtr DetailsView) + { + TArray SelectedObjects; + for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectionSet); NodeIt; ++NodeIt) + { + if (UFlowGraphNode* SelectedNode = Cast(*NodeIt)) + { + SelectedObjects.Emplace(SelectedNode->GetFlowNodeBase()); + } + else + { + SelectedObjects.Add(*NodeIt); + } + } + + DetailsView->SetObjects(SelectedObjects); + }; + const auto ContextMenuHandler = [](UEdGraph* CurrentGraph, const UEdGraphNode* InGraphNode, const UEdGraphPin* InGraphPin, FMenuBuilder* MenuBuilder, bool bIsDebugging) { MenuBuilder->AddMenuEntry(FGenericCommands::Get().Copy); return FActionMenuContent(MenuBuilder->MakeWidget()); }; + InEvents.OnSelectionChanged = SGraphEditor::FOnSelectionChanged::CreateStatic(SelectionChangedHandler, DetailsView); InEvents.OnCreateNodeOrPinMenu = SGraphEditor::FOnCreateNodeOrPinMenu::CreateStatic(ContextMenuHandler); } - // Node single-click path (via SNodePanel) - InEvents.OnNodeSingleClicked = SGraphEditor::FOnNodeSingleClicked::CreateRaw(this, &FFlowDiffPanel::OnNodeClicked); - - // Selection-change path (covers sub-node/AddOn clicks) - InEvents.OnSelectionChanged = SGraphEditor::FOnSelectionChanged::CreateLambda([this](const FGraphPanelSelectionSet& NewSelection) - { - if (NewSelection.Num() == 1) - { - UObject* SelectedObj = NewSelection.Array()[0]; - OnNodeClicked(SelectedObj); - } - }); if (!GraphEditorCommands.IsValid()) { @@ -578,31 +601,6 @@ void FFlowDiffPanel::GeneratePanel(UEdGraph* Graph, TSharedPtrSetContent(Widget.ToSharedRef()); } -void FFlowDiffPanel::OnNodeClicked(UObject* ClickedNode) -{ - UFlowGraphNode* ClickedFlowGraphNode = Cast(ClickedNode); - if (IsValid(ClickedFlowGraphNode)) - { - PanelDefaultDetailsView->SetObject(ClickedFlowGraphNode->GetFlowNodeBase()); - } - else - { - PanelDefaultDetailsView->SetObject(nullptr); - } - - if (GraphDiffSplitter.IsValid()) - { - if (bIsOldPanel) - { - GraphDiffSplitter.Pin()->SetBottomLeftContent(PanelDefaultDetailsView.ToSharedRef()); - } - else - { - GraphDiffSplitter.Pin()->SetBottomRightContent(PanelDefaultDetailsView.ToSharedRef()); - } - } -} - FGraphPanelSelectionSet FFlowDiffPanel::GetSelectedNodes() const { FGraphPanelSelectionSet CurrentSelection; @@ -648,7 +646,17 @@ void FFlowDiffPanel::FocusDiff(const UEdGraphNode& Node) const { if (GraphEditor.IsValid()) { - GraphEditor.Pin()->JumpToNode(&Node, false); + const UEdGraphNode* JumpNode = &Node; + + const UFlowGraphNode* FlowNode = Cast(&Node); + if (FlowNode && FlowNode->IsSubNode()) + { + check(FlowNode->GetParentNode()); + JumpNode = FlowNode->GetRootFlowGraphNode(); + } + + GraphEditor.Pin()->JumpToNode(JumpNode, false, false); + GraphEditor.Pin()->SetNodeSelection(const_cast(&Node), true); } } @@ -723,7 +731,7 @@ void SFlowDiff::GenerateDifferencesList() RealDifferences.Empty(); ModePanels.Empty(); - const auto CreateInspector = [](const UObject* Object) + const auto CreateInspector = [](const UObject* Object, const TSharedPtr& Scrollbar) { FPropertyEditorModule& EditModule = FModuleManager::Get().GetModuleChecked("PropertyEditor"); @@ -734,14 +742,23 @@ void SFlowDiff::GenerateDifferencesList() DetailsViewArgs.bHideSelectionTip = true; DetailsViewArgs.NotifyHook = NotifyHook; DetailsViewArgs.ViewIdentifier = FName("ObjectInspector"); + DetailsViewArgs.ExternalScrollbar = Scrollbar; TSharedRef DetailsView = EditModule.CreateDetailView(DetailsViewArgs); DetailsView->SetObject(const_cast(Object)); + DetailsView->SetIsPropertyEditingEnabledDelegate(FIsPropertyEditingEnabled::CreateStatic([]{ return false; })); return DetailsView; }; - PanelOld.PanelDefaultDetailsView = CreateInspector(nullptr); - PanelNew.PanelDefaultDetailsView = CreateInspector(nullptr); + PanelOld.DetailsView = CreateInspector(PanelOld.FlowAsset, PanelOld.DetailScrollbar); + PanelNew.DetailsView = CreateInspector(PanelOld.FlowAsset, PanelNew.DetailScrollbar); + + GraphDetailDiff = MakeShared( + PanelOld.DetailsView.ToSharedRef(), + PanelNew.DetailsView.ToSharedRef()); + + SLinkableScrollBar::LinkScrollBars(PanelOld.DetailScrollbar.ToSharedRef(), PanelNew.DetailScrollbar.ToSharedRef(), + TAttribute>::CreateRaw(GraphDetailDiff.Get(), &FAsyncDetailViewDiff::GenerateScrollSyncRate)); // Now that we have done the diffs, create the panel widgets ModePanels.Add(DetailsMode, GenerateDetailsPanel()); @@ -784,7 +801,7 @@ SFlowDiff::FDiffControl SFlowDiff::GenerateDetailsPanel() Splitter->AddSlot( SDetailsSplitter::Slot() .Value(0.5f) - .DetailsView(PanelOld.PanelDefaultDetailsView) + .DetailsView(PanelOld.DetailsView) ); } @@ -813,7 +830,7 @@ SFlowDiff::FDiffControl SFlowDiff::GenerateDetailsPanel() Splitter->AddSlot( SDetailsSplitter::Slot() .Value(0.5f) - .DetailsView(PanelNew.PanelDefaultDetailsView) + .DetailsView(PanelNew.DetailsView) ); } @@ -832,25 +849,48 @@ SFlowDiff::FDiffControl SFlowDiff::GenerateGraphPanel() PanelOld.RevisionInfo, PanelNew.RevisionInfo); GraphToDiff->GenerateTreeEntries(PrimaryDifferencesList, RealDifferences); - - SAssignNew(GraphDiffSplitter,SSplitter2x2) - .TopLeft()[ GenerateGraphWidgetForPanel(PanelOld) ] - .TopRight()[ GenerateGraphWidgetForPanel(PanelNew) ] - - .BottomLeft()[ PanelOld.PanelDefaultDetailsView.ToSharedRef() ] - .BottomRight()[ PanelNew.PanelDefaultDetailsView.ToSharedRef() ]; - - //the panels need a pointer to GraphDiffSplitter to update DetailsViews on click of a node. - PanelOld.GraphDiffSplitter = GraphDiffSplitter; - PanelNew.GraphDiffSplitter = GraphDiffSplitter; - - static const FVector2D GraphPercentage = {.5f, .7f}; - static const FVector2D DetailsViewPercentage = {.5f, .3f}; - static FVector2D Percentages[] = {GraphPercentage, DetailsViewPercentage, GraphPercentage, DetailsViewPercentage}; - GraphDiffSplitter->SetSplitterPercentages(MakeArrayView(Percentages, UE_ARRAY_COUNT(Percentages))); FDiffControl Ret; - Ret.Widget = GraphDiffSplitter; + Ret.Widget = SNew(SVerticalBox) + + SVerticalBox::Slot() + .FillHeight(1.f) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .FillWidth(1.f) + [ + //diff window + SNew(SSplitter) + .Orientation(Orient_Vertical) + + SSplitter::Slot() + .Value(.75f) + [ + SAssignNew(DiffGraphSplitter, SSplitter) + .PhysicalSplitterHandleSize(10.0f) + .Orientation(bVerticalSplitGraphMode ? Orient_Horizontal : Orient_Vertical) + + SSplitter::Slot() // Old revision graph slot + [ + GenerateGraphWidgetForPanel(PanelOld) + ] + + SSplitter::Slot() // New revision graph slot + [ + GenerateGraphWidgetForPanel(PanelNew) + ] + ] + + SSplitter::Slot() + .Value(.25f) + [ + SNew(SDetailsSplitter) + + SDetailsSplitter::Slot() + .Value(0.5f) + .DetailsView(PanelOld.DetailsView) + .DifferencesWithRightPanel(GraphDetailDiff) + + SDetailsSplitter::Slot() + .Value(0.5f) + .DetailsView(PanelNew.DetailsView.ToSharedRef()) + ] + ] + ]; return Ret; } @@ -908,6 +948,10 @@ void SFlowDiff::SetCurrentMode(FName NewMode) if (FoundControl) { + // Reset inspector view + PanelOld.DetailsView->SetObjects(TArray()); + PanelNew.DetailsView->SetObjects(TArray()); + ModeContents->SetContent(FoundControl->Widget.ToSharedRef()); } else diff --git a/Source/FlowEditor/Public/Asset/FlowDiffControl.h b/Source/FlowEditor/Public/Asset/FlowDiffControl.h index 55f1747a8..2f9251476 100644 --- a/Source/FlowEditor/Public/Asset/FlowDiffControl.h +++ b/Source/FlowEditor/Public/Asset/FlowDiffControl.h @@ -63,7 +63,7 @@ struct FLOWEDITOR_API FFlowGraphToDiff : public TSharedFromThis GenerateFlowObjectDiff(const TSharedPtr& Differences); - TSharedPtr FindParentNode(class UFlowGraphNode* Node); + TSharedPtr FindParentDiff(class UFlowGraphNode* Node); TMap> FlowObjectDiffsByNodeName; diff --git a/Source/FlowEditor/Public/Asset/SFlowDiff.h b/Source/FlowEditor/Public/Asset/SFlowDiff.h index 2c916d51b..a5d3c3758 100644 --- a/Source/FlowEditor/Public/Asset/SFlowDiff.h +++ b/Source/FlowEditor/Public/Asset/SFlowDiff.h @@ -3,15 +3,14 @@ #include "IDetailsView.h" #include "DiffResults.h" -#include "GraphEditor.h" #include "SDetailsDiff.h" #include "Textures/SlateIcon.h" struct FFlowGraphToDiff; struct FFlowObjectDiffArgs; -class IDetailsView; -class SSplitter2x2; class UFlowAsset; +class SLinkableScrollBar; +class SGraphEditor; enum class EAssetEditorCloseReason : uint8; @@ -49,21 +48,19 @@ struct FLOWEDITOR_API FFlowDiffPanel void FocusDiff(const UEdGraphPin& Pin) const; void FocusDiff(const UEdGraphNode& Node) const; - void OnNodeClicked(UObject* ClickedNode ); - /* The Flow Asset that owns the graph we are showing. */ const UFlowAsset* FlowAsset; /* The box around the graph editor, used to change the content when new graphs are set. */ TSharedPtr GraphEditorBox; - /* using SNullWidget::NullNullWidget can only work for a single widget, since widget instances can only be - * used one at a time. PanelDefaultDetailsView is used for displaying an empty details panel instead, as well - * as if the user selects a node in the graph view. */ - TSharedPtr PanelDefaultDetailsView; + /** The details view associated with the graph editor */ + TSharedPtr DetailsView; + + TSharedPtr DetailScrollbar; /* The graph editor which does the work of displaying the graph. */ - TWeakPtr GraphEditor; + TWeakPtr GraphEditor; /* Revision information for this asset. */ FRevisionInfo RevisionInfo; @@ -74,12 +71,13 @@ struct FLOWEDITOR_API FFlowDiffPanel /* The widget that contains the revision info in graph mode. */ TSharedPtr OverlayGraphRevisionInfo; - TWeakPtr GraphDiffSplitter = nullptr; bool bIsOldPanel = false; private: /* Command list for this diff panel. */ TSharedPtr GraphEditorCommands; + + FPropertyPath PropertyToHighlight; }; /* Visual Diff between two Flow Assets. */ @@ -103,6 +101,8 @@ class FLOWEDITOR_API SFlowDiff : public SCompoundWidget void Construct(const FArguments& InArgs); virtual ~SFlowDiff() override; + virtual void Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime) override; + /* Called when a new Graph is clicked on by user. */ void OnGraphChanged(const FFlowGraphToDiff* Diff); @@ -195,6 +195,9 @@ class FLOWEDITOR_API SFlowDiff : public SCompoundWidget /* The two panels used to show the old & new revision. */ FFlowDiffPanel PanelOld, PanelNew; + /* Diff info about the flow details in graph diff view */ + TSharedPtr GraphDetailDiff; + /* If the two views should be locked. */ bool bLockViews; @@ -210,9 +213,6 @@ class FLOWEDITOR_API SFlowDiff : public SCompoundWidget friend struct FListItemGraphToDiff; - /* We can't use the global tab manager because we need to instance the diff control, so we have our own tab manager. */ - TSharedPtr TabManager; - /* Tree of differences collected across all panels. */ TArray> PrimaryDifferencesList; @@ -228,7 +228,5 @@ class FLOWEDITOR_API SFlowDiff : public SCompoundWidget /* A pointer to the window holding this. */ TWeakPtr WeakParentWindow; - TSharedPtr GraphDiffSplitter = nullptr; - FDelegateHandle AssetEditorCloseDelegate; };