829 lines
24 KiB
C++
829 lines
24 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "SBlueprintEditorSelectedDebugObjectWidget.h"
|
|
|
|
#include "Components/Widget.h"
|
|
#include "Containers/EnumAsByte.h"
|
|
#include "Containers/IndirectArray.h"
|
|
#include "CoreTypes.h"
|
|
#include "Editor.h"
|
|
#include "Editor/EditorEngine.h"
|
|
#include "Editor/UnrealEdEngine.h"
|
|
#include "Engine/Blueprint.h"
|
|
#include "Engine/Engine.h"
|
|
#include "Engine/EngineBaseTypes.h"
|
|
#include "Engine/EngineTypes.h"
|
|
#include "Engine/GameInstance.h"
|
|
#include "Engine/Level.h"
|
|
#include "Engine/World.h"
|
|
#include "Framework/MultiBox/MultiBoxDefs.h"
|
|
#include "GameFramework/Actor.h"
|
|
#include "HAL/IConsoleManager.h"
|
|
#include "IDocumentation.h"
|
|
#include "Internationalization/Internationalization.h"
|
|
#include "Layout/Children.h"
|
|
#include "Layout/Margin.h"
|
|
#include "Misc/AssertionMacros.h"
|
|
#include "Misc/Attribute.h"
|
|
#include "PreviewScene.h"
|
|
#include "PropertyCustomizationHelpers.h"
|
|
#include "SLevelOfDetailBranchNode.h"
|
|
#include "SlotBase.h"
|
|
#include "Templates/Casts.h"
|
|
#include "Templates/SubclassOf.h"
|
|
#include "UObject/Class.h"
|
|
#include "UObject/Object.h"
|
|
#include "UObject/ObjectMacros.h"
|
|
#include "UObject/ObjectPtr.h"
|
|
#include "UObject/UObjectGlobals.h"
|
|
#include "UObject/UObjectHash.h"
|
|
#include "UObject/UObjectIterator.h"
|
|
#include "UnrealEdGlobals.h"
|
|
#include "Widgets/Input/STextComboBox.h"
|
|
#include "Widgets/SBoxPanel.h"
|
|
#include "Widgets/SToolTip.h"
|
|
#include "Widgets/SWidget.h"
|
|
#include "Widgets/Text/STextBlock.h"
|
|
|
|
class FTagMetaData;
|
|
struct FGeometry;
|
|
|
|
#define LOCTEXT_NAMESPACE "KismetToolbar"
|
|
|
|
static TAutoConsoleVariable<int32> CVarUseFastDebugObjectDiscovery(TEXT("r.UseFastDebugObjectDiscovery"), 1, TEXT("Enable new optimised debug object discovery"));
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// SBlueprintEditorSelectedDebugObjectWidget
|
|
|
|
void SBlueprintEditorSelectedDebugObjectWidget::Construct(const FArguments& InArgs, TSharedPtr<FBlueprintEditor> InBlueprintEditor)
|
|
{
|
|
BlueprintEditor = InBlueprintEditor;
|
|
|
|
GenerateDebugWorldNames(false);
|
|
GenerateDebugObjectInstances(false);
|
|
|
|
LastObjectObserved = nullptr;
|
|
|
|
DebugWorldsComboBox = SNew(STextComboBox)
|
|
.ToolTip(IDocumentation::Get()->CreateToolTip(
|
|
LOCTEXT("BlueprintDebugWorldTooltip", "Select a world to debug, will filter what to debug if no specific object selected"),
|
|
nullptr,
|
|
TEXT("Shared/Editors/BlueprintEditor/BlueprintDebugger"),
|
|
TEXT("DebugWorld")))
|
|
.OptionsSource(&DebugWorldNames)
|
|
.InitiallySelectedItem(GetDebugWorldName())
|
|
.Visibility(this, &SBlueprintEditorSelectedDebugObjectWidget::IsDebugWorldComboVisible)
|
|
.OnComboBoxOpening(this, &SBlueprintEditorSelectedDebugObjectWidget::GenerateDebugWorldNames, true)
|
|
.OnSelectionChanged(this, &SBlueprintEditorSelectedDebugObjectWidget::DebugWorldSelectionChanged)
|
|
.ContentPadding(FMargin(0.f, 4.f));
|
|
|
|
DebugObjectsComboBox = SNew(SComboBox<TSharedPtr<FBlueprintDebugObjectInstance>>)
|
|
.ToolTip(IDocumentation::Get()->CreateToolTip(
|
|
LOCTEXT("BlueprintDebugObjectTooltip", "Select an object to debug, if set to none will debug any object"),
|
|
nullptr,
|
|
TEXT("Shared/Editors/BlueprintEditor/BlueprintDebugger"),
|
|
TEXT("DebugObject")))
|
|
.OptionsSource(&DebugObjects)
|
|
.InitiallySelectedItem(GetDebugObjectInstance())
|
|
.OnComboBoxOpening(this, &SBlueprintEditorSelectedDebugObjectWidget::GenerateDebugObjectInstances, true)
|
|
.OnSelectionChanged(this, &SBlueprintEditorSelectedDebugObjectWidget::DebugObjectSelectionChanged)
|
|
.OnGenerateWidget(this, &SBlueprintEditorSelectedDebugObjectWidget::CreateDebugObjectItemWidget)
|
|
.ContentPadding(FMargin(0.f, 4.f))
|
|
.AddMetaData<FTagMetaData>(TEXT("SelectDebugObjectCobmo"))
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(this, &SBlueprintEditorSelectedDebugObjectWidget::GetSelectedDebugObjectTextLabel)
|
|
];
|
|
|
|
ChildSlot
|
|
[
|
|
SNew(SLevelOfDetailBranchNode)
|
|
.UseLowDetailSlot(FMultiBoxSettings::UseSmallToolBarIcons)
|
|
.OnGetActiveDetailSlotContent(this, &SBlueprintEditorSelectedDebugObjectWidget::OnGetActiveDetailSlotContent)
|
|
];
|
|
}
|
|
|
|
void SBlueprintEditorSelectedDebugObjectWidget::Tick( const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime )
|
|
{
|
|
if (GetBlueprintObj())
|
|
{
|
|
if (UObject* Object = GetBlueprintObj()->GetObjectBeingDebugged())
|
|
{
|
|
if (Object != LastObjectObserved.Get())
|
|
{
|
|
// bRestoreSelection attempts to restore the selection by name,
|
|
// this ensures that if the last object we had selected was
|
|
// regenerated (spawning a new object), then we select that
|
|
// again, even if it is technically a different object
|
|
GenerateDebugObjectInstances(/*bRestoreSelection =*/true);
|
|
|
|
LastObjectObserved = Object;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
LastObjectObserved = nullptr;
|
|
|
|
// If the object name is a name (rather than the 'No debug selected' string then regenerate the names (which will reset the combo box) as the object is invalid.
|
|
TSharedPtr<FBlueprintDebugObjectInstance> CurrentSelection = DebugObjectsComboBox->GetSelectedItem();
|
|
if (CurrentSelection.IsValid() && CurrentSelection->IsEditorObject())
|
|
{
|
|
GenerateDebugObjectInstances(false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
const FString& SBlueprintEditorSelectedDebugObjectWidget::GetNoDebugString() const
|
|
{
|
|
return NSLOCTEXT("BlueprintEditor", "DebugObjectNothingSelected", "No debug object selected").ToString();
|
|
}
|
|
|
|
const FString& SBlueprintEditorSelectedDebugObjectWidget::GetDebugAllWorldsString() const
|
|
{
|
|
return NSLOCTEXT("BlueprintEditor", "DebugWorldNothingSelected", "All Worlds").ToString();
|
|
}
|
|
|
|
TSharedRef<SWidget> SBlueprintEditorSelectedDebugObjectWidget::OnGetActiveDetailSlotContent(bool bChangedToHighDetail)
|
|
{
|
|
const TSharedRef<SWidget> BrowseButton = PropertyCustomizationHelpers::MakeBrowseButton(
|
|
FSimpleDelegate::CreateSP(this, &SBlueprintEditorSelectedDebugObjectWidget::SelectedDebugObject_OnClicked),
|
|
LOCTEXT("DebugSelectActor", "Select and frame the debug actor in the Level Editor."),
|
|
TAttribute<bool>::Create(TAttribute<bool>::FGetter::CreateSP(this, &SBlueprintEditorSelectedDebugObjectWidget::IsDebugObjectSelected))
|
|
);
|
|
|
|
|
|
TSharedRef<SWidget> DebugObjectSelectionWidget =
|
|
SNew(SHorizontalBox)
|
|
+ SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.Padding(FMargin(8.0f, 0.0f, 0.0f, 0.0f))
|
|
[
|
|
DebugObjectsComboBox.ToSharedRef()
|
|
]
|
|
+ SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.HAlign(HAlign_Right)
|
|
.VAlign(VAlign_Center)
|
|
.Padding(4.0f)
|
|
[
|
|
BrowseButton
|
|
];
|
|
|
|
|
|
return
|
|
SNew(SHorizontalBox)
|
|
+ SHorizontalBox::Slot()
|
|
.VAlign(VAlign_Center)
|
|
.Padding(0.0f)
|
|
.AutoWidth()
|
|
[
|
|
DebugWorldsComboBox.ToSharedRef()
|
|
]
|
|
+ SHorizontalBox::Slot()
|
|
.VAlign(VAlign_Center)
|
|
.Padding(0.0f)
|
|
.AutoWidth()
|
|
[
|
|
DebugObjectSelectionWidget
|
|
];
|
|
}
|
|
|
|
void SBlueprintEditorSelectedDebugObjectWidget::OnRefresh()
|
|
{
|
|
if (GetBlueprintObj())
|
|
{
|
|
GenerateDebugWorldNames(false);
|
|
GenerateDebugObjectInstances(false);
|
|
|
|
if (DebugObjectsComboBox.IsValid())
|
|
{
|
|
DebugWorldsComboBox->SetSelectedItem(GetDebugWorldName());
|
|
DebugObjectsComboBox->SetSelectedItem(GetDebugObjectInstance());
|
|
}
|
|
}
|
|
}
|
|
|
|
void SBlueprintEditorSelectedDebugObjectWidget::GenerateDebugWorldNames(bool bRestoreSelection)
|
|
{
|
|
DebugWorldNames.Empty();
|
|
DebugWorlds.Empty();
|
|
|
|
DebugWorlds.Add(nullptr);
|
|
DebugWorldNames.Add(MakeShareable(new FString(GetDebugAllWorldsString())));
|
|
|
|
UWorld* PreviewWorld = BlueprintEditor.Pin()->GetPreviewScene()->GetWorld();
|
|
|
|
for (TObjectIterator<UWorld> It; It; ++It)
|
|
{
|
|
UWorld *TestWorld = *It;
|
|
|
|
// Include only PIE and worlds that own the persistent level (i.e. non-streaming levels).
|
|
const bool bIsValidDebugWorld = (TestWorld != nullptr)
|
|
&& TestWorld->WorldType == EWorldType::PIE
|
|
&& TestWorld->PersistentLevel != nullptr
|
|
&& TestWorld->PersistentLevel->OwningWorld == TestWorld;
|
|
|
|
if (!bIsValidDebugWorld)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
ENetMode NetMode = TestWorld->GetNetMode();
|
|
|
|
FString WorldName;
|
|
|
|
switch (NetMode)
|
|
{
|
|
case NM_Standalone:
|
|
WorldName = NSLOCTEXT("BlueprintEditor", "DebugWorldStandalone", "Standalone").ToString();
|
|
break;
|
|
|
|
case NM_ListenServer:
|
|
WorldName = NSLOCTEXT("BlueprintEditor", "DebugWorldListenServer", "Listen Server").ToString();
|
|
break;
|
|
|
|
case NM_DedicatedServer:
|
|
WorldName = NSLOCTEXT("BlueprintEditor", "DebugWorldDedicatedServer", "Dedicated Server").ToString();
|
|
break;
|
|
|
|
case NM_Client:
|
|
if (FWorldContext* PieContext = GEngine->GetWorldContextFromWorld(TestWorld))
|
|
{
|
|
WorldName = FString::Printf(TEXT("%s %d"), *NSLOCTEXT("BlueprintEditor", "DebugWorldClient", "Client").ToString(), PieContext->PIEInstance - 1);
|
|
}
|
|
break;
|
|
};
|
|
|
|
if (!WorldName.IsEmpty())
|
|
{
|
|
if (FWorldContext* PieContext = GEngine->GetWorldContextFromWorld(TestWorld))
|
|
{
|
|
if (!PieContext->CustomDescription.IsEmpty())
|
|
{
|
|
WorldName += TEXT(" ") + PieContext->CustomDescription;
|
|
}
|
|
}
|
|
|
|
// DebugWorlds & DebugWorldNames need to be the same size (we expect
|
|
// an index in one to correspond to the other) - DebugWorldNames is
|
|
// what populates the dropdown, so it is the authority (if there's
|
|
// no name to present, they can't select from DebugWorlds)
|
|
DebugWorlds.Add(TestWorld);
|
|
DebugWorldNames.Add( MakeShareable(new FString(WorldName)) );
|
|
}
|
|
}
|
|
|
|
if (DebugWorldsComboBox.IsValid())
|
|
{
|
|
// Attempt to restore the old selection
|
|
if (bRestoreSelection)
|
|
{
|
|
TSharedPtr<FString> CurrentDebugWorld = GetDebugWorldName();
|
|
if (CurrentDebugWorld.IsValid())
|
|
{
|
|
DebugWorldsComboBox->SetSelectedItem(CurrentDebugWorld);
|
|
}
|
|
}
|
|
|
|
// Finally ensure we have a valid selection
|
|
TSharedPtr<FString> CurrentSelection = DebugWorldsComboBox->GetSelectedItem();
|
|
if (DebugWorldNames.Find(CurrentSelection) == INDEX_NONE)
|
|
{
|
|
if (DebugWorldNames.Num() > 0)
|
|
{
|
|
DebugWorldsComboBox->SetSelectedItem(DebugWorldNames[0]);
|
|
}
|
|
else
|
|
{
|
|
DebugWorldsComboBox->ClearSelection();
|
|
}
|
|
}
|
|
|
|
DebugWorldsComboBox->RefreshOptions();
|
|
}
|
|
}
|
|
|
|
void SBlueprintEditorSelectedDebugObjectWidget::GenerateDebugObjectInstances(bool bRestoreSelection)
|
|
{
|
|
// Cache the current selection as we may need to restore it
|
|
TSharedPtr<FBlueprintDebugObjectInstance> LastSelection = GetDebugObjectInstance();
|
|
|
|
// Empty the lists of actors and regenerate them
|
|
DebugObjects.Empty();
|
|
DebugObjects.Add(MakeShareable(new FBlueprintDebugObjectInstance(nullptr, GetNoDebugString())));
|
|
|
|
// Grab custom objects that should always be visible, regardless of the world
|
|
TArray<FCustomDebugObject> CustomDebugObjects;
|
|
BlueprintEditor.Pin()->GetCustomDebugObjects(/*inout*/ CustomDebugObjects);
|
|
|
|
for (const FCustomDebugObject& Entry : CustomDebugObjects)
|
|
{
|
|
AddDebugObject(Entry.Object, Entry.NameOverride);
|
|
}
|
|
|
|
// Check for a specific debug world. If DebugWorld=nullptr we take that as "any PIE world"
|
|
UWorld* DebugWorld = nullptr;
|
|
if (DebugWorldsComboBox.IsValid())
|
|
{
|
|
TSharedPtr<FString> CurrentWorldSelection = DebugWorldsComboBox->GetSelectedItem();
|
|
int32 SelectedIndex = INDEX_NONE;
|
|
for (int32 WorldIdx = 0; WorldIdx < DebugWorldNames.Num(); ++WorldIdx)
|
|
{
|
|
if (DebugWorldNames[WorldIdx].IsValid() && CurrentWorldSelection.IsValid()
|
|
&& (*DebugWorldNames[WorldIdx] == *CurrentWorldSelection))
|
|
{
|
|
SelectedIndex = WorldIdx;
|
|
break;
|
|
}
|
|
}
|
|
if (SelectedIndex > 0 && DebugWorldNames.IsValidIndex(SelectedIndex))
|
|
{
|
|
DebugWorld = DebugWorlds[SelectedIndex].Get();
|
|
}
|
|
}
|
|
|
|
UWorld* PreviewWorld = BlueprintEditor.Pin()->GetPreviewScene()->GetWorld();
|
|
|
|
if (!BlueprintEditor.Pin()->OnlyShowCustomDebugObjects())
|
|
{
|
|
const bool bModifiedIterator = CVarUseFastDebugObjectDiscovery.GetValueOnGameThread() == 1;
|
|
UClass* BlueprintClass = GetBlueprintObj()->GeneratedClass;
|
|
|
|
if (bModifiedIterator && BlueprintClass)
|
|
{
|
|
// Experimental new path for debug object discovery
|
|
TArray<UObject*> BlueprintInstances;
|
|
GetObjectsOfClass(BlueprintClass, BlueprintInstances, true);
|
|
|
|
for (auto It = BlueprintInstances.CreateIterator(); It; ++It)
|
|
{
|
|
UObject* TestObject = *It;
|
|
// Skip Blueprint preview objects (don't allow them to be selected for debugging)
|
|
if (PreviewWorld != nullptr && TestObject->IsIn(PreviewWorld))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// check outer chain for pending kill objects
|
|
bool bPendingKill = false;
|
|
UObject* ObjOuter = TestObject;
|
|
do
|
|
{
|
|
bPendingKill = !IsValid(ObjOuter);
|
|
ObjOuter = ObjOuter->GetOuter();
|
|
} while (!bPendingKill && ObjOuter != nullptr);
|
|
|
|
if (!TestObject->HasAnyFlags(RF_ClassDefaultObject) && !bPendingKill)
|
|
{
|
|
ObjOuter = TestObject;
|
|
UWorld *ObjWorld = nullptr;
|
|
static bool bUseNewWorldCode = false;
|
|
do // Run through at least once in case the TestObject is a UGameInstance
|
|
{
|
|
UGameInstance *ObjGameInstance = Cast<UGameInstance>(ObjOuter);
|
|
|
|
ObjOuter = ObjOuter->GetOuter();
|
|
ObjWorld = ObjGameInstance ? ObjGameInstance->GetWorld() : Cast<UWorld>(ObjOuter);
|
|
} while (ObjWorld == nullptr && ObjOuter != nullptr);
|
|
|
|
if (ObjWorld)
|
|
{
|
|
// Make check on owning level (not streaming level)
|
|
if (ObjWorld->PersistentLevel && ObjWorld->PersistentLevel->OwningWorld)
|
|
{
|
|
ObjWorld = ObjWorld->PersistentLevel->OwningWorld;
|
|
}
|
|
|
|
// We have a specific debug world and the object isn't in it
|
|
if (DebugWorld && ObjWorld != DebugWorld)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if ((ObjWorld->WorldType == EWorldType::Editor) && (GUnrealEd->GetPIEViewport() == nullptr))
|
|
{
|
|
AddDebugObject(TestObject);
|
|
}
|
|
else if (ObjWorld->WorldType == EWorldType::PIE)
|
|
{
|
|
AddDebugObject(TestObject);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (TObjectIterator<UObject> It; It; ++It)
|
|
{
|
|
UObject* TestObject = *It;
|
|
|
|
// Skip Blueprint preview objects (don't allow them to be selected for debugging)
|
|
if (PreviewWorld != nullptr && TestObject->IsIn(PreviewWorld))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
const bool bPassesFlags = !TestObject->HasAnyFlags(RF_ClassDefaultObject) && IsValid(TestObject);
|
|
const bool bGeneratedByAnyBlueprint = TestObject->GetClass()->ClassGeneratedBy != nullptr;
|
|
const bool bGeneratedByThisBlueprint = bGeneratedByAnyBlueprint && GetBlueprintObj()->GeneratedClass && TestObject->IsA(GetBlueprintObj()->GeneratedClass);
|
|
|
|
if (bPassesFlags && bGeneratedByThisBlueprint)
|
|
{
|
|
UObject *ObjOuter = TestObject;
|
|
UWorld *ObjWorld = nullptr;
|
|
do // Run through at least once in case the TestObject is a UGameInstance
|
|
{
|
|
UGameInstance *ObjGameInstance = Cast<UGameInstance>(ObjOuter);
|
|
|
|
ObjOuter = ObjOuter->GetOuter();
|
|
ObjWorld = ObjGameInstance ? ObjGameInstance->GetWorld() : Cast<UWorld>(ObjOuter);
|
|
} while (ObjWorld == nullptr && ObjOuter != nullptr);
|
|
|
|
if (ObjWorld)
|
|
{
|
|
// Make check on owning level (not streaming level)
|
|
if (ObjWorld->PersistentLevel && ObjWorld->PersistentLevel->OwningWorld)
|
|
{
|
|
ObjWorld = ObjWorld->PersistentLevel->OwningWorld;
|
|
}
|
|
|
|
// We have a specific debug world and the object isn't in it
|
|
if (DebugWorld && ObjWorld != DebugWorld)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if ((ObjWorld->WorldType == EWorldType::Editor) && (GUnrealEd->GetPIEViewport() == nullptr))
|
|
{
|
|
AddDebugObject(TestObject);
|
|
}
|
|
else if (ObjWorld->WorldType == EWorldType::PIE)
|
|
{
|
|
AddDebugObject(TestObject);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (DebugObjectsComboBox.IsValid())
|
|
{
|
|
if (bRestoreSelection)
|
|
{
|
|
TSharedPtr<FBlueprintDebugObjectInstance> NewSelection = GetDebugObjectInstance();
|
|
if (NewSelection.IsValid() && !NewSelection->IsEmptyObject())
|
|
{
|
|
// If our new selection matches the actual debug object, set it
|
|
DebugObjectsComboBox->SetSelectedItem(NewSelection);
|
|
}
|
|
else if (LastSelection.IsValid() && !LastSelection->IsEditorObject() && !LastSelection->IsEmptyObject())
|
|
{
|
|
// Re-add the desired runtime object if needed, even though it is currently null
|
|
DebugObjects.Add(LastSelection);
|
|
DebugObjectsComboBox->SetSelectedItem(LastSelection);
|
|
}
|
|
}
|
|
|
|
// Finally ensure we have a valid selection, this will set to all objects as a backup
|
|
TSharedPtr<FBlueprintDebugObjectInstance> CurrentSelection = DebugObjectsComboBox->GetSelectedItem();
|
|
if (DebugObjects.Find(CurrentSelection) == INDEX_NONE)
|
|
{
|
|
if (DebugObjects.Num() > 0)
|
|
{
|
|
DebugObjectsComboBox->SetSelectedItem(DebugObjects[0]);
|
|
}
|
|
else
|
|
{
|
|
DebugObjectsComboBox->ClearSelection();
|
|
}
|
|
}
|
|
|
|
DebugObjectsComboBox->RefreshOptions();
|
|
}
|
|
}
|
|
|
|
TSharedPtr<FBlueprintDebugObjectInstance> SBlueprintEditorSelectedDebugObjectWidget::GetDebugObjectInstance() const
|
|
{
|
|
check(GetBlueprintObj());
|
|
const FString& PathToDebug = GetBlueprintObj()->GetObjectPathToDebug();
|
|
if (!PathToDebug.IsEmpty())
|
|
{
|
|
for (int32 ObjectIndex = 0; ObjectIndex < DebugObjects.Num(); ++ObjectIndex)
|
|
{
|
|
if (DebugObjects[ObjectIndex].IsValid() && PathToDebug.Equals(DebugObjects[ObjectIndex]->ObjectPath))
|
|
{
|
|
return DebugObjects[ObjectIndex];
|
|
}
|
|
}
|
|
}
|
|
|
|
if (DebugObjects.Num() > 0)
|
|
{
|
|
return DebugObjects[0];
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
TSharedPtr<FString> SBlueprintEditorSelectedDebugObjectWidget::GetDebugWorldName() const
|
|
{
|
|
check(GetBlueprintObj());
|
|
if (ensure(DebugWorlds.Num() == DebugWorldNames.Num()))
|
|
{
|
|
UWorld* DebugWorld = GetBlueprintObj()->GetWorldBeingDebugged();
|
|
if (DebugWorld != nullptr)
|
|
{
|
|
for (int32 WorldIndex = 0; WorldIndex < DebugWorlds.Num(); ++WorldIndex)
|
|
{
|
|
if (DebugWorlds[WorldIndex].IsValid() && (DebugWorlds[WorldIndex].Get() == DebugWorld))
|
|
{
|
|
return DebugWorldNames[WorldIndex];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
if (DebugWorldNames.Num() > 0)
|
|
{
|
|
return DebugWorldNames[0];
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
void SBlueprintEditorSelectedDebugObjectWidget::DebugWorldSelectionChanged(TSharedPtr<FString> NewSelection, ESelectInfo::Type SelectInfo)
|
|
{
|
|
if (NewSelection != GetDebugWorldName())
|
|
{
|
|
check(DebugWorlds.Num() == DebugWorldNames.Num());
|
|
for (int32 WorldIdx = 0; WorldIdx < DebugWorldNames.Num(); ++WorldIdx)
|
|
{
|
|
if (DebugWorldNames[WorldIdx] == NewSelection)
|
|
{
|
|
GetBlueprintObj()->SetWorldBeingDebugged(DebugWorlds[WorldIdx].Get());
|
|
|
|
GetBlueprintObj()->SetObjectBeingDebugged(nullptr);
|
|
LastObjectObserved.Reset();
|
|
|
|
GenerateDebugObjectInstances(false);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void SBlueprintEditorSelectedDebugObjectWidget::DebugObjectSelectionChanged(TSharedPtr<FBlueprintDebugObjectInstance> NewSelection, ESelectInfo::Type SelectInfo)
|
|
{
|
|
if (NewSelection != GetDebugObjectInstance() && NewSelection.IsValid())
|
|
{
|
|
UObject* DebugObj = NewSelection->ObjectPtr.Get();
|
|
GetBlueprintObj()->SetObjectBeingDebugged(DebugObj);
|
|
|
|
if (TSharedPtr<FBlueprintEditor> SharedBlueprintEditor = BlueprintEditor.Pin())
|
|
{
|
|
SharedBlueprintEditor->RefreshMyBlueprint();
|
|
}
|
|
|
|
LastObjectObserved = DebugObj;
|
|
}
|
|
}
|
|
|
|
bool SBlueprintEditorSelectedDebugObjectWidget::IsDebugObjectSelected() const
|
|
{
|
|
check(GetBlueprintObj());
|
|
if (UObject* DebugObj = GetBlueprintObj()->GetObjectBeingDebugged())
|
|
{
|
|
if (AActor* Actor = Cast<AActor>(DebugObj))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void SBlueprintEditorSelectedDebugObjectWidget::SelectedDebugObject_OnClicked()
|
|
{
|
|
if (UObject* DebugObj = GetBlueprintObj()->GetObjectBeingDebugged())
|
|
{
|
|
if (AActor* Actor = Cast<AActor>(DebugObj))
|
|
{
|
|
GEditor->SelectNone(false, true, false);
|
|
GEditor->SelectActor(Actor, true, true, true);
|
|
GUnrealEd->Exec(Actor->GetWorld(), TEXT("CAMERA ALIGN ACTIVEVIEWPORTONLY"));
|
|
}
|
|
}
|
|
}
|
|
|
|
EVisibility SBlueprintEditorSelectedDebugObjectWidget::IsDebugWorldComboVisible() const
|
|
{
|
|
if (GEditor->PlayWorld != nullptr)
|
|
{
|
|
int32 LocalWorldCount = 0;
|
|
for (const FWorldContext& Context : GEngine->GetWorldContexts())
|
|
{
|
|
if (Context.WorldType == EWorldType::PIE && Context.World() != nullptr)
|
|
{
|
|
++LocalWorldCount;
|
|
}
|
|
}
|
|
|
|
if (LocalWorldCount > 1)
|
|
{
|
|
return EVisibility::Visible;
|
|
}
|
|
}
|
|
|
|
return EVisibility::Collapsed;
|
|
}
|
|
|
|
FString SBlueprintEditorSelectedDebugObjectWidget::MakeDebugObjectLabel(UObject* TestObject, bool bAddContextIfSelectedInEditor, bool bAddSpawnedContext) const
|
|
{
|
|
FString CustomLabelFromEditor = BlueprintEditor.Pin()->GetCustomDebugObjectLabel(TestObject);
|
|
if (!CustomLabelFromEditor.IsEmpty())
|
|
{
|
|
return CustomLabelFromEditor;
|
|
}
|
|
|
|
auto GetActorLabelStringLambda = [](AActor* InActor, bool bIncludeNetModeSuffix, bool bIncludeSelectedSuffix, bool bIncludeSpawnedContext)
|
|
{
|
|
FString Label = InActor->GetActorLabel();
|
|
|
|
FString Context;
|
|
|
|
if (bIncludeNetModeSuffix)
|
|
{
|
|
switch (InActor->GetNetMode())
|
|
{
|
|
case ENetMode::NM_Client:
|
|
{
|
|
Context = NSLOCTEXT("BlueprintEditor", "DebugWorldClient", "Client").ToString();
|
|
|
|
FWorldContext* WorldContext = GEngine->GetWorldContextFromWorld(InActor->GetWorld());
|
|
if (WorldContext != nullptr && WorldContext->PIEInstance > 1)
|
|
{
|
|
Context += TEXT(" ");
|
|
Context += FText::AsNumber(WorldContext->PIEInstance - 1).ToString();
|
|
}
|
|
}
|
|
break;
|
|
|
|
case ENetMode::NM_ListenServer:
|
|
case ENetMode::NM_DedicatedServer:
|
|
Context = NSLOCTEXT("BlueprintEditor", "DebugWorldServer", "Server").ToString();
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (bIncludeSpawnedContext)
|
|
{
|
|
if (!Context.IsEmpty())
|
|
{
|
|
Context += TEXT(", ");
|
|
}
|
|
|
|
Context += NSLOCTEXT("BlueprintEditor", "DebugObjectSpawned", "spawned").ToString();
|
|
}
|
|
|
|
if (bIncludeSelectedSuffix && InActor->IsSelected())
|
|
{
|
|
if (!Context.IsEmpty())
|
|
{
|
|
Context += TEXT(", ");
|
|
}
|
|
|
|
Context += NSLOCTEXT("BlueprintEditor", "DebugObjectSelected", "selected").ToString();
|
|
}
|
|
|
|
if (!Context.IsEmpty())
|
|
{
|
|
Label = FString::Printf(TEXT("%s (%s)"), *Label, *Context);
|
|
}
|
|
|
|
return Label;
|
|
};
|
|
|
|
// Include net mode suffix when "All worlds" is selected.
|
|
const bool bIncludeNetModeSuffix = *GetDebugWorldName() == GetDebugAllWorldsString();
|
|
|
|
FString Label;
|
|
if (AActor* Actor = Cast<AActor>(TestObject))
|
|
{
|
|
Label = GetActorLabelStringLambda(Actor, bIncludeNetModeSuffix, bAddContextIfSelectedInEditor, bAddSpawnedContext);
|
|
}
|
|
else
|
|
{
|
|
if (AActor* ParentActor = TestObject->GetTypedOuter<AActor>())
|
|
{
|
|
// We don't need the full path because it's in the tooltip
|
|
const FString RelativePath = TestObject->GetName();
|
|
Label = FString::Printf(TEXT("%s in %s"), *RelativePath, *GetActorLabelStringLambda(ParentActor, bIncludeNetModeSuffix, bAddContextIfSelectedInEditor, bAddSpawnedContext));
|
|
}
|
|
else
|
|
{
|
|
Label = TestObject->GetName();
|
|
}
|
|
}
|
|
|
|
return Label;
|
|
}
|
|
|
|
void SBlueprintEditorSelectedDebugObjectWidget::FillDebugObjectInstance(TSharedPtr<FBlueprintDebugObjectInstance> Instance)
|
|
{
|
|
check(Instance.IsValid());
|
|
FBlueprintDebugObjectInstance& Ref = *Instance.Get();
|
|
|
|
if (Ref.ObjectPtr.IsValid())
|
|
{
|
|
Ref.ObjectPath = Ref.ObjectPtr->GetPathName();
|
|
|
|
// Compute non-PIE path
|
|
FString OriginalPath = UWorld::RemovePIEPrefix(Ref.ObjectPath);
|
|
|
|
// Look for original object
|
|
UObject* OriginalObject = FindObjectSafe<UObject>(nullptr, *OriginalPath);
|
|
|
|
if (OriginalObject)
|
|
{
|
|
Ref.EditorObjectPath = OriginalPath;
|
|
}
|
|
else
|
|
{
|
|
// No editor path, was dynamically spawned
|
|
Ref.EditorObjectPath = FString();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Ref.ObjectPath = Ref.EditorObjectPath = FString();
|
|
}
|
|
}
|
|
|
|
void SBlueprintEditorSelectedDebugObjectWidget::AddDebugObject(UObject* TestObject, const FString& TestObjectName)
|
|
{
|
|
TSharedPtr<FBlueprintDebugObjectInstance> NewInstance = MakeShareable(new FBlueprintDebugObjectInstance(TestObject, TestObjectName));
|
|
FillDebugObjectInstance(NewInstance);
|
|
|
|
if (TestObjectName.IsEmpty())
|
|
{
|
|
NewInstance->ObjectLabel = MakeDebugObjectLabel(TestObject, true, NewInstance->IsSpawnedObject());
|
|
}
|
|
|
|
if (UWidget* DebugWidget = Cast<UWidget>(TestObject))
|
|
{
|
|
if (!DebugWidget->IsConstructed())
|
|
{
|
|
NewInstance->ObjectLabel += " (No Slate Widget)";
|
|
}
|
|
}
|
|
|
|
DebugObjects.Add(NewInstance);
|
|
}
|
|
|
|
TSharedRef<SWidget> SBlueprintEditorSelectedDebugObjectWidget::CreateDebugObjectItemWidget(TSharedPtr<FBlueprintDebugObjectInstance> InItem)
|
|
{
|
|
FString ItemString;
|
|
FString ItemTooltip;
|
|
|
|
if (InItem.IsValid())
|
|
{
|
|
ItemString = InItem->ObjectLabel;
|
|
ItemTooltip = InItem->ObjectPath;
|
|
}
|
|
|
|
return SNew(STextBlock)
|
|
.Text(FText::FromString(*ItemString))
|
|
.ToolTipText(FText::FromString(*ItemTooltip));
|
|
}
|
|
|
|
FText SBlueprintEditorSelectedDebugObjectWidget::GetSelectedDebugObjectTextLabel() const
|
|
{
|
|
FString Label;
|
|
|
|
TSharedPtr<FBlueprintDebugObjectInstance> DebugInstance = GetDebugObjectInstance();
|
|
if (DebugInstance.IsValid())
|
|
{
|
|
Label = DebugInstance->ObjectLabel;
|
|
|
|
UBlueprint* Blueprint = GetBlueprintObj();
|
|
if (Blueprint != nullptr)
|
|
{
|
|
UObject* DebugObj = Blueprint->GetObjectBeingDebugged();
|
|
if (DebugObj != nullptr)
|
|
{
|
|
// Exclude the editor selection suffix for the combo button's label.
|
|
Label = MakeDebugObjectLabel(DebugObj, false, DebugInstance->IsSpawnedObject());
|
|
}
|
|
}
|
|
}
|
|
|
|
return FText::FromString(Label);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
#undef LOCTEXT_NAMESPACE
|