// Copyright Epic Games, Inc. All Rights Reserved. #include "AsyncDetailViewDiff.h" #include "DetailTreeNode.h" #include "IDetailsViewPrivate.h" #include "DiffUtils.h" static const UObject* GetObject(const TSharedPtr& TreeNode) { if (const IDetailsViewPrivate* DetailsView = TreeNode->GetDetailsView()) { return DetailsView->GetSelectedObjects()[0].Get(); } return nullptr; } static FResolvedProperty GetResolvedProperty(const TSharedPtr& PropertyNode, const UObject* Object) { if (PropertyNode) { const TSharedRef PropertyPath = FPropertyNode::CreatePropertyPath(PropertyNode.ToSharedRef()); if (PropertyPath->IsValid()) { return FPropertySoftPath(*PropertyPath).Resolve(Object); } } return FResolvedProperty(); } template<> bool TreeDiffSpecification::AreValuesEqual>(const TWeakPtr& TreeNodeA, const TWeakPtr& TreeNodeB) { const TSharedPtr PinnedTreeNodeA = TreeNodeA.Pin(); const TSharedPtr PinnedTreeNodeB = TreeNodeB.Pin(); if (!PinnedTreeNodeA || !PinnedTreeNodeB) { return PinnedTreeNodeA == PinnedTreeNodeB; } const FResolvedProperty ResolvedA = GetResolvedProperty(PinnedTreeNodeA->GetPropertyNode(), GetObject(PinnedTreeNodeA)); const FResolvedProperty ResolvedB = GetResolvedProperty(PinnedTreeNodeB->GetPropertyNode(), GetObject(PinnedTreeNodeB)); if (ResolvedA.Property && ResolvedB.Property) { // property nodes if (!ResolvedA.Property->SameType(ResolvedB.Property)) { return false; } const void* DataA = ResolvedA.Property->ContainerPtrToValuePtr(ResolvedA.Object); const void* DataB = ResolvedB.Property->ContainerPtrToValuePtr(ResolvedB.Object); return ResolvedA.Property->Identical(DataA, DataB, PPF_DeepComparison); } if (!ResolvedA.Property && !ResolvedB.Property) { // category nodes return PinnedTreeNodeA->GetNodeName() == PinnedTreeNodeB->GetNodeName(); } return ensure(false); // AreMatching(...) should've stopped this from happening } static bool MapKeysMatch(const TSharedPtr& TreeNodeA, const TSharedPtr& TreeNodeB, int32 KeyIndexA, int32 KeyIndexB) { const TSharedPtr MapPropertyNodeA = TreeNodeA->GetPropertyNode()->GetParentNode()->AsShared(); const TSharedPtr MapPropertyNodeB = TreeNodeB->GetPropertyNode()->GetParentNode()->AsShared(); if (!MapPropertyNodeA || !MapPropertyNodeB) { return false; } const FMapProperty* MapPropertyA = CastField(MapPropertyNodeA->GetProperty()); const FMapProperty* MapPropertyB = CastField(MapPropertyNodeB->GetProperty()); if (!MapPropertyA || !MapPropertyB) { return false; } const FResolvedProperty ResolvedMapA = GetResolvedProperty(MapPropertyNodeA, GetObject(TreeNodeA)); const FResolvedProperty ResolvedMapB = GetResolvedProperty(MapPropertyNodeB, GetObject(TreeNodeB)); FScriptMapHelper MapHelperA(MapPropertyA, MapPropertyA->ContainerPtrToValuePtr(ResolvedMapA.Object)); FScriptMapHelper MapHelperB(MapPropertyB, MapPropertyB->ContainerPtrToValuePtr(ResolvedMapB.Object)); const void* KeyA = MapHelperA.FindNthKeyPtr(KeyIndexA); const void* KeyB = MapHelperB.FindNthKeyPtr(KeyIndexB); if (MapPropertyA->KeyProp->SameType(MapPropertyB->KeyProp)) { return MapPropertyA->KeyProp->Identical(KeyA, KeyB, PPF_DeepComparison); } return false; } static bool SetKeysMatch(const TSharedPtr& TreeNodeA, const TSharedPtr& TreeNodeB, int32 KeyIndexA, int32 KeyIndexB) { const TSharedPtr SetPropertyNodeA = TreeNodeA->GetPropertyNode()->GetParentNode()->AsShared(); const TSharedPtr SetPropertyNodeB = TreeNodeB->GetPropertyNode()->GetParentNode()->AsShared(); if (!SetPropertyNodeA || !SetPropertyNodeB) { return false; } const FSetProperty* SetPropertyA = CastField(SetPropertyNodeA->GetProperty()); const FSetProperty* SetPropertyB = CastField(SetPropertyNodeB->GetProperty()); if (!SetPropertyA || !SetPropertyB) { return false; } const FResolvedProperty ResolvedSetA = GetResolvedProperty(SetPropertyNodeA, GetObject(TreeNodeA)); const FResolvedProperty ResolvedSetB = GetResolvedProperty(SetPropertyNodeB, GetObject(TreeNodeB)); FScriptSetHelper SetHelperA(SetPropertyA, SetPropertyA->ContainerPtrToValuePtr(ResolvedSetA.Object)); FScriptSetHelper SetHelperB(SetPropertyB, SetPropertyB->ContainerPtrToValuePtr(ResolvedSetB.Object)); const void* KeyA = SetHelperA.FindNthElementPtr(KeyIndexA); const void* KeyB = SetHelperB.FindNthElementPtr(KeyIndexB); if (SetPropertyA->ElementProp->SameType(SetPropertyB->ElementProp)) { return SetPropertyA->ElementProp->Identical(KeyA, KeyB, PPF_DeepComparison); } return false; } template<> bool TreeDiffSpecification::AreMatching>(const TWeakPtr& TreeNodeA, const TWeakPtr& TreeNodeB) { const TSharedPtr PinnedTreeNodeA = TreeNodeA.Pin(); const TSharedPtr PinnedTreeNodeB = TreeNodeB.Pin(); if (!PinnedTreeNodeA || !PinnedTreeNodeB) { return PinnedTreeNodeA == PinnedTreeNodeB; } const TSharedPtr PropertyNodeA = PinnedTreeNodeA->GetPropertyNode(); const TSharedPtr PropertyNodeB =PinnedTreeNodeB->GetPropertyNode(); if (PropertyNodeA && PropertyNodeB) { // property nodes const int32 ArrayIndexA = PropertyNodeA->GetArrayIndex(); const int32 ArrayIndexB = PropertyNodeB->GetArrayIndex(); const FProperty* PropertyA = PropertyNodeA->GetProperty(); const FProperty* PropertyB = PropertyNodeB->GetProperty(); if (ArrayIndexA != INDEX_NONE && ArrayIndexB != INDEX_NONE) { const FProperty* ParentPropertyA = PropertyNodeA->GetParentNode()->GetProperty(); const FProperty* ParentPropertyB = PropertyNodeB->GetParentNode()->GetProperty(); // sets and maps are stored by index in the property tree so we need to dig their keys out of the data // and compare those instead if (CastField(ParentPropertyA) || CastField(ParentPropertyB)) { return MapKeysMatch( PinnedTreeNodeA, PinnedTreeNodeB, ArrayIndexA, ArrayIndexB ); } if (ParentPropertyA->IsA() || ParentPropertyB->IsA()) { return SetKeysMatch( PinnedTreeNodeA, PinnedTreeNodeB, ArrayIndexA, ArrayIndexB ); } } if (ArrayIndexA != ArrayIndexB) { return false; } const FName PropertyNameA = PropertyA ? PropertyA->GetFName() : NAME_None; const FName PropertyNameB = PropertyB ? PropertyB->GetFName() : NAME_None; return PropertyNameA == PropertyNameB; } if (!PropertyNodeA && !PropertyNodeB) { // category nodes return PinnedTreeNodeA->GetNodeName() == PinnedTreeNodeB->GetNodeName(); } // node type mismatch return false; } template<> void TreeDiffSpecification::GetChildren>(const TWeakPtr& InParent, TArray>& OutChildren) { const TSharedPtr PinnedParent = InParent.Pin(); if (PinnedParent) { TArray> Children; PinnedParent->GetChildren(Children); for (TSharedRef Child : Children) { OutChildren.Add(Child); } } } template<> bool TreeDiffSpecification::ShouldMatchByValue>(const TWeakPtr& TreeNode) { const TSharedPtr PinnedTreeNode = TreeNode.Pin(); if (!PinnedTreeNode) { return false; } const TSharedPtr PropertyNodeA = PinnedTreeNode->GetPropertyNode(); if (!PropertyNodeA || !PropertyNodeA->GetParentNode()) { return false; } const int32 ArrayIndex = PropertyNodeA->GetArrayIndex(); const FArrayProperty* ParentArrayProperty = CastField(PropertyNodeA->GetParentNode()->GetProperty()); // match array elements by value rather than by index return ParentArrayProperty && ArrayIndex != INDEX_NONE; } FAsyncDetailViewDiff::FAsyncDetailViewDiff(TSharedRef InLeftView, TSharedRef InRightView) : TAsyncTreeDifferences(RootNodesAttribute(InLeftView), RootNodesAttribute(InRightView)) , LeftView(InLeftView) , RightView(InRightView) {} void FAsyncDetailViewDiff::GetPropertyDifferences(TArray& OutDiffEntries) const { ForEach(ETreeTraverseOrder::PreOrder, [&](const TUniquePtr& Node)->ETreeTraverseControl { FPropertyPath PropertyPath; FPropertyPath RightPropertyPath; if (const TSharedPtr LeftTreeNode = Node->ValueA.Pin()) { PropertyPath = LeftTreeNode->GetPropertyPath(); } else if (const TSharedPtr RightTreeNode = Node->ValueB.Pin()) { PropertyPath = RightTreeNode->GetPropertyPath(); } // only include tree nodes with properties if (!PropertyPath.IsValid()) { return ETreeTraverseControl::Continue; } EPropertyDiffType::Type PropertyDiffType; switch(Node->DiffResult) { case ETreeDiffResult::MissingFromTree1: PropertyDiffType = EPropertyDiffType::PropertyAddedToB; break; case ETreeDiffResult::MissingFromTree2: PropertyDiffType = EPropertyDiffType::PropertyAddedToA; break; case ETreeDiffResult::DifferentValues: PropertyDiffType = EPropertyDiffType::PropertyValueChanged; break; default: // only include changes return ETreeTraverseControl::Continue; } OutDiffEntries.Add(FSingleObjectDiffEntry(PropertyPath, PropertyDiffType)); return ETreeTraverseControl::SkipChildren; // only include top-most properties }); } TPair FAsyncDetailViewDiff::ForEachRow(const TFunction&, int32, int32)>& Method) const { const TSharedPtr LeftDetailsView = LeftView.Pin(); const TSharedPtr RightDetailsView = RightView.Pin(); if (!LeftDetailsView || !RightDetailsView) { return {0,0}; } int32 LeftRowNum = 0; int32 RightRowNum = 0; ForEach( ETreeTraverseOrder::PreOrder, [&LeftDetailsView,&RightDetailsView,&Method,&LeftRowNum,&RightRowNum](const TUniquePtr& DiffNode)->ETreeTraverseControl { bool bFoundLeftRow = false; if (const TSharedPtr LeftTreeNode = DiffNode->ValueA.Pin()) { if (!LeftDetailsView->IsAncestorCollapsed(LeftTreeNode.ToSharedRef())) { bFoundLeftRow = true; } } bool bFoundRightRow = false; if (const TSharedPtr RightTreeNode = DiffNode->ValueB.Pin()) { if (!RightDetailsView->IsAncestorCollapsed(RightTreeNode.ToSharedRef())) { bFoundRightRow = true; } } ETreeTraverseControl Control = ETreeTraverseControl::SkipChildren; if (bFoundRightRow || bFoundLeftRow) { Control = Method(DiffNode, LeftRowNum, RightRowNum); } if (bFoundLeftRow) { ++LeftRowNum; } if (bFoundRightRow) { ++RightRowNum; } return Control; } ); return {LeftRowNum, RightRowNum}; } TAttribute>> FAsyncDetailViewDiff::RootNodesAttribute(TWeakPtr DetailsView) { return TAttribute>>::CreateLambda([DetailsView]() { TArray> Result; if (const TSharedPtr Details = StaticCastSharedPtr(DetailsView.Pin())) { Details->GetHeadNodes(Result); } return Result; }); }