Files
UnrealEngine/Engine/Source/Developer/SlateReflector/Private/VisualTreeCapture.cpp
2025-05-18 13:04:45 +08:00

328 lines
9.9 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "VisualTreeCapture.h"
#include "Debugging/SlateDebugging.h"
#include "FastUpdate/SlateInvalidationRoot.h"
#include "Rendering/SlateRenderTransform.h"
#include "Rendering/DrawElements.h"
#include "Widgets/SWidget.h"
#include "Framework/Application/SlateApplication.h"
#include "Types/InvisibleToWidgetReflectorMetaData.h"
static float VectorSign(const FVector2f& Vec, const FVector2f& A, const FVector2f& B)
{
return FMath::Sign((B.X - A.X) * (Vec.Y - A.Y) - (B.Y - A.Y) * (Vec.X - A.X));
}
// Returns true when the point is inside the triangle
// Should not return true when the point is on one of the edges
static bool IsPointInTriangle(const FVector2f& TestPoint, const FVector2f& A, const FVector2f& B, const FVector2f& C)
{
float BA = VectorSign(B, A, TestPoint);
float CB = VectorSign(C, B, TestPoint);
float AC = VectorSign(A, C, TestPoint);
// point is in the same direction of all 3 tri edge lines
// must be inside, regardless of tri winding
return BA == CB && CB == AC;
}
FVisualEntry::FVisualEntry(const TWeakPtr<const SWidget>& InWidget, int32 InElementIndex, EElementType InElementType)
: ElementIndex(InElementIndex)
, ElementType(InElementType)
, bFromCache(false)
, Widget(InWidget)
{
}
FVisualEntry::FVisualEntry(const TSharedRef<const SWidget>& InWidget, const FSlateDrawElement& InElement)
{
const FSlateRenderTransform& Transform = InElement.GetRenderTransform();
const FVector2f LocalSize = InElement.GetLocalSize();
TopLeft = Transform.TransformPoint(FVector2f(0.0f, 0.0f));
TopRight = Transform.TransformPoint(FVector2f(LocalSize.X, 0.0f));
BottomLeft = Transform.TransformPoint(FVector2f(0.0f, LocalSize.Y));
BottomRight = Transform.TransformPoint(LocalSize);
LayerId = InElement.GetLayer();
ClippingIndex = INDEX_NONE;
bFromCache = true;
Widget = InWidget;
}
void FVisualEntry::Resolve(const FSlateWindowElementList& ElementList)
{
if (bFromCache)
{
return;
}
auto ResolveBounds = [&](const auto& Container, uint8 InElementType)
{
if (InElementType == (uint8)ElementType)
{
const FSlateDrawElement& Element = Container[ElementIndex];
const FSlateRenderTransform& Transform = Element.GetRenderTransform();
const FVector2f LocalSize = Element.GetLocalSize();
TopLeft = Transform.TransformPoint(FVector2f(0.0f, 0.0f));
TopRight = Transform.TransformPoint(FVector2f(LocalSize.X, 0.0f));
BottomLeft = Transform.TransformPoint(FVector2f(0.0f, LocalSize.Y));
BottomRight = Transform.TransformPoint(LocalSize);
LayerId = Element.GetLayer();
ClippingIndex = Element.GetPrecachedClippingIndex();
}
};
VisitTupleElements(ResolveBounds, ElementList.GetUncachedDrawElements(), UE::Slate::MakeTupleIndicies<uint8, (uint8)EElementType::ET_Count>());
}
bool FVisualEntry::IsPointInside(const FVector2f& Point) const
{
if (IsPointInTriangle(Point, TopLeft, TopRight, BottomLeft) || IsPointInTriangle(Point, BottomLeft, TopRight, BottomRight))
{
return true;
}
return false;
}
TSharedPtr<const SWidget> FVisualTreeSnapshot::Pick(FVector2f Point)
{
for (int Index = Entries.Num() - 1; Index >= 0; Index--)
{
const FVisualEntry& Entry = Entries[Index];
if (Entry.ClippingIndex != -1)
{
const TArray<FSlateClippingState>& LocalClippingState = Entry.bFromCache ? CachedClippingStates : ClippingStates;
if (ensure(LocalClippingState.IsValidIndex(Entry.ClippingIndex)))
{
if (!LocalClippingState[Entry.ClippingIndex].IsPointInside(Point))
{
continue;
}
}
}
if (!Entry.IsPointInside(Point))
{
continue;
}
return Entry.Widget.Pin();
}
return TSharedPtr<const SWidget>();
}
FVisualTreeCapture::FVisualTreeCapture()
: bIsEnabled(false)
, WindowIsInvalidationRootCounter(0)
, WidgetIsInvalidationRootCounter(0)
, WidgetIsInvisibleToWidgetReflectorCounter(0)
{
}
FVisualTreeCapture::~FVisualTreeCapture()
{
Disable();
}
void FVisualTreeCapture::Enable()
{
#if WITH_SLATE_DEBUGGING
if (ensure(bIsEnabled == false))
{
FSlateApplication::Get().OnWindowBeingDestroyed().AddRaw(this, &FVisualTreeCapture::OnWindowBeingDestroyed);
FSlateDebugging::BeginWindow.AddRaw(this, &FVisualTreeCapture::BeginWindow);
FSlateDebugging::EndWindow.AddRaw(this, &FVisualTreeCapture::EndWindow);
FSlateDebugging::BeginWidgetPaint.AddRaw(this, &FVisualTreeCapture::BeginWidgetPaint);
FSlateDebugging::EndWidgetPaint.AddRaw(this, &FVisualTreeCapture::EndWidgetPaint);
FSlateDebugging::ElementTypeAdded.AddRaw(this, &FVisualTreeCapture::ElementTypeAdded);
bIsEnabled = true;
WindowIsInvalidationRootCounter = 0;
WidgetIsInvalidationRootCounter = 0;
WidgetIsInvisibleToWidgetReflectorCounter = 0;
}
#endif
}
void FVisualTreeCapture::Disable()
{
#if WITH_SLATE_DEBUGGING
if (bIsEnabled)
{
if (FSlateApplication::IsInitialized())
{
FSlateApplication::Get().OnWindowBeingDestroyed().RemoveAll(this);
}
FSlateDebugging::BeginWindow.RemoveAll(this);
FSlateDebugging::EndWindow.RemoveAll(this);
FSlateDebugging::BeginWidgetPaint.RemoveAll(this);
FSlateDebugging::EndWidgetPaint.RemoveAll(this);
FSlateDebugging::ElementTypeAdded.RemoveAll(this);
bIsEnabled = false;
}
#endif
}
void FVisualTreeCapture::Reset()
{
VisualTrees.Reset();
}
TSharedPtr<FVisualTreeSnapshot> FVisualTreeCapture::GetVisualTreeForWindow(SWindow* InWindow)
{
return VisualTrees.FindRef(InWindow);
}
void FVisualTreeCapture::AddInvalidationRootCachedEntries(TSharedRef<FVisualTreeSnapshot> Tree, const FSlateInvalidationRoot* InvalidationRoot)
{
check(InvalidationRoot);
const FSlateCachedElementData& Data = InvalidationRoot->GetCachedElements();
const TArray<TSharedPtr<FSlateCachedElementList>>& CachedElements = Data.GetCachedElementLists();
for (const TSharedPtr<FSlateCachedElementList>& CachedElement : CachedElements)
{
auto AddTypedEntries = [&](auto& Container)
{
const SWidget* Widget = CachedElement->OwningWidget;
// todo, should check if parents has the metadata also
if (Widget && !Widget->GetMetaData<FInvisibleToWidgetReflectorMetaData>())
{
for (const FSlateDrawElement& Element : Container)
{
const int32 EntryIndex = Tree->Entries.Emplace(Widget->AsShared(), Element);
const FSlateClippingState* ClippingState = Element.GetClippingHandle().GetCachedClipState();
if (ClippingState)
{
int32& ClippingRefIndex = Tree->Entries[EntryIndex].ClippingIndex;
ClippingRefIndex = Tree->CachedClippingStates.IndexOfByKey(*ClippingState);
if (ClippingRefIndex == INDEX_NONE)
{
ClippingRefIndex = Tree->CachedClippingStates.Add(*ClippingState);
}
}
}
}
};
VisitTupleElements(AddTypedEntries, CachedElement->DrawElements);
}
}
void FVisualTreeCapture::BeginWindow(const FSlateWindowElementList& ElementList)
{
TSharedPtr<FVisualTreeSnapshot> Tree = VisualTrees.FindRef(ElementList.GetPaintWindow());
if (!Tree.IsValid())
{
Tree = MakeShared<FVisualTreeSnapshot>();
VisualTrees.Add(ElementList.GetPaintWindow(), Tree);
}
Tree->Entries.Reset();
Tree->ClippingStates.Reset();
Tree->CachedClippingStates.Reset();
if (ElementList.GetPaintWindow()->Advanced_IsInvalidationRoot())
{
++WindowIsInvalidationRootCounter;
}
}
void FVisualTreeCapture::EndWindow(const FSlateWindowElementList& ElementList)
{
if (ElementList.GetPaintWindow()->Advanced_IsInvalidationRoot())
{
--WindowIsInvalidationRootCounter;
}
TSharedPtr<FVisualTreeSnapshot> Tree = VisualTrees.FindRef(ElementList.GetPaintWindow());
if (Tree.IsValid())
{
for (FVisualEntry& Entry : Tree->Entries)
{
Entry.Resolve(ElementList);
}
if (ElementList.GetPaintWindow()->Advanced_IsInvalidationRoot())
{
// Add cached elements
const FSlateInvalidationRoot* InvalidationRoot = ElementList.GetPaintWindow()->Advanced_AsInvalidationRoot();
AddInvalidationRootCachedEntries(Tree.ToSharedRef(), InvalidationRoot);
}
Tree->ClippingStates = ElementList.GetClippingManager().GetClippingStates();
Tree->Entries.Sort([](const FVisualEntry& A, const FVisualEntry& B) {
return A.LayerId < B.LayerId;
});
}
}
void FVisualTreeCapture::BeginWidgetPaint(const SWidget* Widget, const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, const FSlateWindowElementList& ElementList, int32 LayerId)
{
TSharedPtr<FVisualTreeSnapshot> Tree = VisualTrees.FindRef(ElementList.GetPaintWindow());
if (Tree.IsValid())
{
Tree->WidgetStack.Push(Widget->AsShared());
if (Widget->Advanced_IsInvalidationRoot())
{
++WidgetIsInvalidationRootCounter;
}
if (Widget->GetMetaData<FInvisibleToWidgetReflectorMetaData>())
{
++WidgetIsInvisibleToWidgetReflectorCounter;
}
}
}
void FVisualTreeCapture::EndWidgetPaint(const SWidget* Widget, const FSlateWindowElementList& ElementList, int32 LayerId)
{
TSharedPtr<FVisualTreeSnapshot> Tree = VisualTrees.FindRef(ElementList.GetPaintWindow());
if (Tree.IsValid())
{
Tree->WidgetStack.Pop();
if (Widget->Advanced_IsInvalidationRoot())
{
--WidgetIsInvalidationRootCounter;
// Add cached elements
const FSlateInvalidationRoot* InvalidationRoot = Widget->Advanced_AsInvalidationRoot();
AddInvalidationRootCachedEntries(Tree.ToSharedRef(), InvalidationRoot);
}
if (Widget->GetMetaData<FInvisibleToWidgetReflectorMetaData>())
{
--WidgetIsInvisibleToWidgetReflectorCounter;
}
}
}
void FVisualTreeCapture::ElementTypeAdded(const FSlateDebuggingElementTypeAddedEventArgs& ElementTypeAddedArgs)
{
if (WindowIsInvalidationRootCounter > 0 || WidgetIsInvalidationRootCounter > 0 || WidgetIsInvisibleToWidgetReflectorCounter > 0)
{
return;
}
TSharedPtr<FVisualTreeSnapshot> Tree = VisualTrees.FindRef(ElementTypeAddedArgs.ElementList.GetPaintWindow());
if (Tree.IsValid())
{
if (Tree->WidgetStack.Num() > 0)
{
// Ignore any element added from a widget that's invisible to the widget reflector.
Tree->Entries.Emplace(Tree->WidgetStack.Top(), ElementTypeAddedArgs.ElementIndex, ElementTypeAddedArgs.ElementType);
}
}
}
void FVisualTreeCapture::OnWindowBeingDestroyed(const SWindow& WindowBeingDestoyed)
{
VisualTrees.Remove(&WindowBeingDestoyed);
}