Files
UnrealEngine/Engine/Plugins/Animation/PoseSearch/Source/Editor/Private/PoseSearchDebuggerView.cpp
2025-05-18 13:04:45 +08:00

824 lines
23 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "PoseSearchDebuggerView.h"
#include "Animation/TrajectoryTypes.h"
#include "Editor.h"
#include "IGameplayProvider.h"
#include "Modules/ModuleManager.h"
#include "PoseSearch/PoseSearchDatabase.h"
#include "PoseSearch/PoseSearchDerivedData.h"
#include "PoseSearch/PoseSearchSchema.h"
#include "PoseSearchDatabaseEditor.h"
#include "PoseSearchDebugger.h"
#include "PoseSearchDebuggerDatabaseRowData.h"
#include "PoseSearchDebuggerDatabaseView.h"
#include "PoseSearchDebuggerReflection.h"
#include "PoseSearchDebuggerViewModel.h"
#include "PoseSearchEditor.h"
#include "PropertyEditorModule.h"
#include "Subsystems/AssetEditorSubsystem.h"
#include "Trace/PoseSearchTraceProvider.h"
#include "Widgets/Input/SButton.h"
#include "Widgets/Input/SNumericEntryBox.h"
#include "Widgets/Layout/SWidgetSwitcher.h"
#include "Widgets/SBoxPanel.h"
#include "Widgets/Text/STextBlock.h"
#include "Widgets/Views/SListView.h"
#define LOCTEXT_NAMESPACE "PoseSearchDebugger"
namespace UE::PoseSearch
{
FText GenerateSearchName(const FTraceMotionMatchingStateMessage& MotionMatchingState, const IGameplayProvider* GameplayProvider)
{
TStringBuilder<256> StringBuilder;
if (GameplayProvider)
{
bool bAddDatabasesSeparator = false;
for (const FTraceMotionMatchingStateDatabaseEntry& DbEntry : MotionMatchingState.DatabaseEntries)
{
if (bAddDatabasesSeparator)
{
StringBuilder.Append(" - ");
}
const FObjectInfo& DatabaseObjectInfo = GameplayProvider->GetObjectInfo(DbEntry.DatabaseId);
StringBuilder.Append(DatabaseObjectInfo.Name);
bAddDatabasesSeparator = true;
}
if (MotionMatchingState.Roles.Num() > 1)
{
if (MotionMatchingState.Roles.Num() != MotionMatchingState.SkeletalMeshComponentIds.Num())
{
StringBuilder.Append("Error!");
}
else
{
StringBuilder.Append(" [");
bool bAddRolesSeparator = false;
for (int32 RoleIndex = 0; RoleIndex < MotionMatchingState.Roles.Num(); ++RoleIndex)
{
if (bAddRolesSeparator)
{
StringBuilder.Append(" - ");
}
StringBuilder.Append(MotionMatchingState.Roles[RoleIndex].ToString());
StringBuilder.Append(": ");
const FObjectInfo& SkeletalMeshComponentObjectInfo = GameplayProvider->GetObjectInfo(MotionMatchingState.SkeletalMeshComponentIds[RoleIndex]);
const FObjectInfo& SkeletalMeshComponentOuterObjectInfo = GameplayProvider->GetObjectInfo(SkeletalMeshComponentObjectInfo.GetOuterId());
StringBuilder.Append(SkeletalMeshComponentOuterObjectInfo.Name);
bAddRolesSeparator = true;
}
StringBuilder.Append("]");
}
}
}
else
{
StringBuilder.Append("Error!");
}
return FText::FromString(StringBuilder.ToString());
}
class SDebuggerMessageBox : public SCompoundWidget
{
SLATE_BEGIN_ARGS(SDebuggerMessageBox) {}
SLATE_END_ARGS()
void Construct(const FArguments& InArgs, const FString& Message)
{
ChildSlot
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
.HAlign(HAlign_Center)
.VAlign(VAlign_Center)
[
SNew(STextBlock)
.Text(FText::FromString(Message))
.Font(FAppStyle::Get().GetFontStyle("DetailsView.CategoryFontStyle"))
]
];
}
};
void SDebuggerDetailsView::Construct(const FArguments& InArgs)
{
ParentDebuggerViewPtr = InArgs._Parent;
// Add property editor (detail view) UObject to world root so that it persists when PIE is stopped
Reflection = NewObject<UPoseSearchDebuggerReflection>();
Reflection->AddToRoot();
check(IsValid(Reflection));
// @TODO: Convert this to a custom builder instead of of a standard details view
// Load property module and create details view with our reflection UObject
FPropertyEditorModule& PropPlugin = FModuleManager::LoadModuleChecked<FPropertyEditorModule>("PropertyEditor");
FDetailsViewArgs DetailsViewArgs;
DetailsViewArgs.NameAreaSettings = FDetailsViewArgs::HideNameArea;
DetailsViewArgs.DefaultsOnlyVisibility = EEditDefaultsOnlyNodeVisibility::Hide;
Details = PropPlugin.CreateDetailView(DetailsViewArgs);
Details->SetObject(Reflection);
ChildSlot
[
Details.ToSharedRef()
];
}
void SDebuggerDetailsView::Update(const FTraceMotionMatchingStateMessage& State) const
{
UpdateReflection(State);
}
SDebuggerDetailsView::~SDebuggerDetailsView()
{
// Our previously instantiated object attached to root may be cleaned up at this point
if (UObjectInitialized())
{
Reflection->RemoveFromRoot();
}
}
void SDebuggerDetailsView::UpdateReflection(const FTraceMotionMatchingStateMessage& State) const
{
check(Reflection);
Reflection->InterruptMode = State.InterruptMode;
Reflection->ElapsedPoseSearchTime = State.ElapsedPoseSearchTime;
Reflection->AssetPlayerTime = State.AssetPlayerTime;
Reflection->LastDeltaTime = State.DeltaTime;
Reflection->SimLinearVelocity = State.SimLinearVelocity;
Reflection->SimAngularVelocity = State.SimAngularVelocity;
Reflection->AnimLinearVelocity = State.AnimLinearVelocity;
Reflection->AnimAngularVelocity = State.AnimAngularVelocity;
Reflection->Playrate = State.Playrate;
Reflection->AnimLinearVelocityNoTimescale = State.AnimLinearVelocityNoTimescale;
Reflection->AnimAngularVelocityNoTimescale = State.AnimAngularVelocityNoTimescale;
}
void SDebuggerView::Construct(const FArguments& InArgs, uint64 InAnimInstanceId, int32 InWantedSearchId)
{
ViewModel = InArgs._ViewModel;
OnViewClosed = InArgs._OnViewClosed;
// Validate the existence of the passed getters
check(ViewModel.IsBound())
check(OnViewClosed.IsBound());
AnimInstanceId = InAnimInstanceId;
WantedSearchId = InWantedSearchId;
SelectedSearchId = InWantedSearchId;
ChildSlot
[
SAssignNew(DebuggerView, SVerticalBox)
+ SVerticalBox::Slot()
.FillHeight(1.0f)
.VAlign(VAlign_Fill)
.HAlign(HAlign_Fill)
[
SAssignNew(Switcher, SWidgetSwitcher)
.WidgetIndex(this, &SDebuggerView::SelectView)
// [0] Selection view before node selection is made
+ SWidgetSwitcher::Slot()
.Padding(40.0f)
.HAlign(HAlign_Fill)
.VAlign(VAlign_Fill)
[
SAssignNew(SelectionView, SVerticalBox)
]
// [1] Node selected; node debugger view
+ SWidgetSwitcher::Slot()
[
GenerateNodeDebuggerView()
]
// [2] Occluding message box when stopped (no recording)
+ SWidgetSwitcher::Slot()
[
SNew(SDebuggerMessageBox, "Record gameplay to begin debugging")
]
// [3] Occluding message box when recording
+ SWidgetSwitcher::Slot()
[
SNew(SDebuggerMessageBox, "Recording...")
]
// [4] Occluding message box when there is no data for the selected MM node
+ SWidgetSwitcher::Slot()
[
GenerateNoDataMessageView()
]
]
];
}
void SDebuggerView::SetTimeMarker(double InTimeMarker)
{
if (FDebugger::IsPIESimulating())
{
return;
}
TimeMarker = InTimeMarker;
}
void SDebuggerView::Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime)
{
if (FDebugger::IsPIESimulating())
{
return;
}
const UWorld* DebuggerWorld = FDebugger::GetWorld();
check(DebuggerWorld);
const bool bSameTime = FMath::Abs(TimeMarker - PreviousTimeMarker) < DOUBLE_SMALL_NUMBER;
PreviousTimeMarker = TimeMarker;
TSharedPtr<FDebuggerViewModel> Model = ViewModel.Get();
check(Model.IsValid());
// We haven't reached the update point yet
if (CurrentConsecutiveFrames < ConsecutiveFramesUpdateThreshold)
{
// If we're on the same time marker, it is consecutive
if (bSameTime)
{
++CurrentConsecutiveFrames;
}
}
else
{
// New frame after having updated, reset consecutive frames count and start counting again
if (!bSameTime)
{
CurrentConsecutiveFrames = 0;
bUpdated = false;
}
// Haven't updated since passing through frame gate, update once
else if (!bUpdated)
{
Model->OnUpdate();
if (UpdateNodeSelection())
{
Model->OnUpdateSearchSelection(SelectedSearchId);
UpdateViews();
}
bUpdated = true;
}
}
// Draw features
if (const FTraceMotionMatchingStateMessage* State = Model->GetMotionMatchingState())
{
FRoleToIndex RoleToIndex;
TArray<FChooserEvaluationContext> AnimContextsData;
TArray<FChooserEvaluationContext*> AnimContexts;
TArray<const IPoseHistory*> PoseHistories;
UWorld* World = nullptr;
const int32 NumRoles = State->Roles.Num();
RoleToIndex.Reserve(NumRoles);
AnimContextsData.SetNum(NumRoles);
AnimContexts.SetNum(NumRoles);
PoseHistories.SetNum(NumRoles);
for (int32 RoleIndex = 0; RoleIndex < NumRoles; ++RoleIndex)
{
const uint64 ActorSkeletalMeshComponentId = State->SkeletalMeshComponentIds[RoleIndex];
if (const TWeakObjectPtr<AActor>* ActorPtr = Model->GetDebugDrawActors().Find(ActorSkeletalMeshComponentId))
{
if (ActorPtr->IsValid())
{
const FRole& Role = State->Roles[RoleIndex];
for (UActorComponent* ActorComponent : (*ActorPtr)->GetInstanceComponents())
{
if (UPoseSearchMeshComponent* PoseSearchMeshComponent = Cast<UPoseSearchMeshComponent>(ActorComponent))
{
World = PoseSearchMeshComponent->GetWorld();
RoleToIndex.Add(Role) = RoleIndex;
AnimContextsData[RoleIndex].AddObjectParam(PoseSearchMeshComponent);
AnimContexts[RoleIndex] = &AnimContextsData[RoleIndex];
PoseHistories[RoleIndex] = &State->PoseHistories[RoleIndex];
break;
}
}
}
}
}
// checking if all roles have been resolved properly
if (RoleToIndex.Num() != NumRoles)
{
return;
}
// Draw world space trajectory
#if ENABLE_ANIM_DEBUG
const bool bDrawTrajectory = Model->GetDrawTrajectory();
const bool bDrawHistory = Model->GetDrawHistory();
if (bDrawTrajectory || bDrawHistory)
{
for (int32 RoleIndex = 0; RoleIndex < NumRoles; ++RoleIndex)
{
const FTransformTrajectory& Trajectory = State->PoseHistories[RoleIndex].Trajectory;
if (bDrawTrajectory)
{
UTransformTrajectoryBlueprintLibrary::DebugDrawTrajectory(Trajectory, World);
}
if (bDrawHistory)
{
State->PoseHistories[RoleIndex].DebugDraw(World, FColor::Red);
}
}
}
#endif
// Draw query vector
if (Model->GetDrawQuery())
{
if (const UPoseSearchDatabase* CurrentDatabase = Model->GetCurrentDatabase())
{
for (const FTraceMotionMatchingStateDatabaseEntry& DbEntry : State->DatabaseEntries)
{
const UPoseSearchDatabase* Database = FTraceMotionMatchingStateMessage::GetObjectFromId<UPoseSearchDatabase>(DbEntry.DatabaseId);
if (Database && Database == CurrentDatabase &&
EAsyncBuildIndexResult::Success == FAsyncPoseSearchDatabasesManagement::RequestAsyncBuildIndex(CurrentDatabase, ERequestAsyncBuildFlag::ContinueRequest) &&
DbEntry.QueryVector.Num() == Database->Schema->SchemaCardinality)
{
FDebugDrawParams DrawParams(AnimContexts, PoseHistories, RoleToIndex, CurrentDatabase);
DrawParams.DrawFeatureVector(DbEntry.QueryVector);
break;
}
}
}
}
// Draw selected poses
const TSharedPtr<SListView<TSharedRef<FDebuggerDatabaseRowData>>>& DatabaseRows = DatabaseView->GetDatabaseRows();
TArray<TSharedRef<FDebuggerDatabaseRowData>> SelectedRows = DatabaseRows->GetSelectedItems();
// Draw any selected database vectors
constexpr int32 MaxRowsToDraw = 250;
const int32 NumRowsToDraw = FMath::Min(MaxRowsToDraw, SelectedRows.Num());
for (int32 RowIdx = 0; RowIdx < NumRowsToDraw; ++RowIdx)
{
const TSharedRef<FDebuggerDatabaseRowData>& Row = SelectedRows[RowIdx];
const UPoseSearchDatabase* RowDatabase = Row->SharedData->SourceDatabase.Get();
if (EAsyncBuildIndexResult::Success == FAsyncPoseSearchDatabasesManagement::RequestAsyncBuildIndex(RowDatabase, ERequestAsyncBuildFlag::ContinueRequest))
{
FDebugDrawParams DrawParams(AnimContexts, PoseHistories, RoleToIndex, RowDatabase);
DrawParams.DrawFeatureVector(Row->PoseIdx);
}
}
// Draw active pose
TArray<TSharedRef<FDebuggerDatabaseRowData>> ActiveRows = DatabaseView->GetActiveRow()->GetSelectedItems();
// Active row should only have 0 or 1
check(ActiveRows.Num() < 2);
if (!ActiveRows.IsEmpty())
{
const UPoseSearchDatabase* Database = ActiveRows[0]->SharedData->SourceDatabase.Get();
if (EAsyncBuildIndexResult::Success == FAsyncPoseSearchDatabasesManagement::RequestAsyncBuildIndex(Database, ERequestAsyncBuildFlag::ContinueRequest))
{
// Use the motion-matching state's pose idx, as the active row may be update-throttled at this point
FDebugDrawParams DrawParams(AnimContexts, PoseHistories, RoleToIndex, Database);
DrawParams.DrawFeatureVector(ActiveRows[0]->PoseIdx);
}
}
// Draw continuing pose
TArray<TSharedRef<FDebuggerDatabaseRowData>> ContinuingRows = DatabaseView->GetContinuingPoseRow()->GetSelectedItems();
// ContinuingPose row should only have 0 or 1
check(ContinuingRows.Num() < 2);
if (!ContinuingRows.IsEmpty())
{
const UPoseSearchDatabase* Database = ContinuingRows[0]->SharedData->SourceDatabase.Get();
if (EAsyncBuildIndexResult::Success == FAsyncPoseSearchDatabasesManagement::RequestAsyncBuildIndex(Database, ERequestAsyncBuildFlag::ContinueRequest))
{
FDebugDrawParams DrawParams(AnimContexts, PoseHistories, RoleToIndex, Database);
DrawParams.DrawFeatureVector(ContinuingRows[0]->PoseIdx);
}
}
}
// synchronizing the model DrawQuery state with all the open PoseSearchDatabaseEditor(s)
const bool bDrawQuery = Model->GetDrawQuery();
if (UAssetEditorSubsystem* AssetEditorSS = GEditor->GetEditorSubsystem<UAssetEditorSubsystem>())
{
TArray<UObject*> EditedAssets = AssetEditorSS->GetAllEditedAssets();
for (UObject* EditedAsset : EditedAssets)
{
if (Cast<UPoseSearchDatabase>(EditedAsset))
{
if (IAssetEditorInstance* Editor = AssetEditorSS->FindEditorForAsset(EditedAsset, false))
{
if (Editor->GetEditorName() == FName("PoseSearchDatabaseEditor"))
{
FDatabaseEditor* DatabaseEditor = static_cast<FDatabaseEditor*>(Editor);
DatabaseEditor->SetDrawQueryVector(bDrawQuery);
}
}
}
}
}
}
bool SDebuggerView::UpdateNodeSelection()
{
TSharedPtr<FDebuggerViewModel> Model = ViewModel.Get();
check(Model.IsValid());
const TArray<FTraceMotionMatchingStateMessage>& MotionMatchingStates = Model->GetMotionMatchingStates();
// Update selection view if no node selected
if (SelectedSearchId != InvalidSearchId)
{
if (!MotionMatchingStates.IsEmpty())
{
// making sure SelectedSearchId is still valid. if not, let's pick the first available MotionMatchingStates
for (const FTraceMotionMatchingStateMessage& MotionMatchingState : MotionMatchingStates)
{
if (MotionMatchingState.GetSearchId() == SelectedSearchId)
{
return true;
}
}
if (WantedSearchId == InvalidSearchId)
{
// SelectedSearchId is not valid, and since there's no WantedSearchId, by specifically double clicking the
// search track instead of selecting "Pose Search" track, we reassign SelectedSearchId to the first valid SearchId
SelectedSearchId = MotionMatchingStates[0].GetSearchId();
}
}
return true;
}
// Only one active state, bypass selection view
if (MotionMatchingStates.Num() == 1)
{
SelectedSearchId = MotionMatchingStates[0].GetSearchId();
return true;
}
// Create selection view with buttons for each node, displaying the database name
SelectionView->ClearChildren();
if (!MotionMatchingStates.IsEmpty())
{
IRewindDebugger* RewindDebugger = IRewindDebugger::Instance();
const TraceServices::IAnalysisSession* AnalysisSession = RewindDebugger->GetAnalysisSession();
check(AnalysisSession);
const IGameplayProvider* GameplayProvider = AnalysisSession->ReadProvider<IGameplayProvider>("GameplayProvider");
TraceServices::FAnalysisSessionReadScope SessionReadScope(*AnalysisSession);
for (const FTraceMotionMatchingStateMessage& MotionMatchingState : MotionMatchingStates)
{
Model->OnUpdateSearchSelection(MotionMatchingState.GetSearchId());
SelectionView->AddSlot()
.HAlign(HAlign_Fill)
.VAlign(VAlign_Center)
.Padding(10.0f)
[
SNew(SButton)
.Text(GenerateSearchName(MotionMatchingState, GameplayProvider))
.HAlign(HAlign_Center)
.VAlign(VAlign_Center)
.ContentPadding(10.0f)
.OnClicked(this, &SDebuggerView::OnUpdateSearchSelection, MotionMatchingState.GetSearchId())
];
}
}
return false;
}
void SDebuggerView::UpdateViews() const
{
if (const FTraceMotionMatchingStateMessage* State = ViewModel.Get()->GetMotionMatchingState())
{
DatabaseView->Update(*State);
DetailsView->Update(*State);
}
}
TArray<TSharedRef<FDebuggerDatabaseRowData>> SDebuggerView::GetSelectedDatabaseRows() const
{
return DatabaseView->GetDatabaseRows()->GetSelectedItems();
}
int32 SDebuggerView::SelectView() const
{
// Currently recording
if (FDebugger::IsPIESimulating() && FDebugger::IsRecording())
{
return RecordingMsg;
}
// Data has not been recorded yet
if (FDebugger::GetRecordingDuration() < DOUBLE_SMALL_NUMBER)
{
return StoppedMsg;
}
const TSharedPtr<FDebuggerViewModel> Model = ViewModel.Get();
check(Model.IsValid());
const bool bNoActiveNodes = Model->GetNodesNum() == 0;
const bool bNodeSelectedWithoutData = SelectedSearchId != InvalidSearchId && Model->GetMotionMatchingState() == nullptr;
// No active nodes, or node selected has no data
if (bNoActiveNodes || bNodeSelectedWithoutData)
{
return NoDataMsg;
}
// Node not selected yet, showcase selection view
if (SelectedSearchId == InvalidSearchId)
{
return Selection;
}
// Standard debugger view
return Debugger;
}
void SDebuggerView::OnPoseSelectionChanged(const UPoseSearchDatabase* Database, int32 DbPoseIdx, float Time)
{
const TSharedPtr<FDebuggerViewModel> Model = ViewModel.Get();
check(Model.IsValid());
if (const FTraceMotionMatchingStateMessage* State = Model->GetMotionMatchingState())
{
DetailsView->Update(*State);
}
}
FReply SDebuggerView::OnUpdateSearchSelection(int32 InSelectedSearchId)
{
// InvalidSearchId will backtrack to selection view
SelectedSearchId = InSelectedSearchId;
bUpdated = false;
return FReply::Handled();
}
TSharedRef<SWidget> SDebuggerView::GenerateNoDataMessageView()
{
TSharedRef<SWidget> ReturnButtonView = GenerateReturnButtonView();
ReturnButtonView->SetVisibility(TAttribute<EVisibility>::CreateLambda([this]() -> EVisibility
{
// Hide the return button for the no data message if we have no nodes at all
return ViewModel.Get()->GetNodesNum() > 0 ? EVisibility::Visible : EVisibility::Hidden;
}));
return
SNew(SVerticalBox)
+ SVerticalBox::Slot()
[
SNew(SDebuggerMessageBox, "No recorded data available for the selected frame")
]
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(20.0f)
.HAlign(HAlign_Center)
.VAlign(VAlign_Center)
[
ReturnButtonView
];
}
TSharedRef<SHorizontalBox> SDebuggerView::GenerateReturnButtonView()
{
return SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.Padding(10, 5, 0, 0)
.AutoWidth()
[
SNew(SButton)
.VAlign(VAlign_Center)
.Visibility_Lambda([this] { return ViewModel.Get()->GetNodesNum() > 1 ? EVisibility::Visible : EVisibility::Hidden; })
.ButtonStyle(FAppStyle::Get(), "SimpleButton")
.ContentPadding( FMargin(1, 0) )
.OnClicked(this, &SDebuggerView::OnUpdateSearchSelection, InvalidSearchId)
// Contents of button, icon then text
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.VAlign(VAlign_Center)
[
SNew(SImage)
.Image(FAppStyle::Get().GetBrush("Icons.CircleArrowLeft"))
.ColorAndOpacity(FSlateColor::UseForeground())
]
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(FMargin(8.0f, 0.0f, 0.0f, 0.0f))
.VAlign(VAlign_Center)
[
SNew(STextBlock)
.Text(FText::FromString("Return to Search Selection"))
.Justification(ETextJustify::Center)
]
]
]
+SHorizontalBox::Slot()
.VAlign(VAlign_Top)
.HAlign(HAlign_Left)
.Padding(64, 5, 0, 0)
.AutoWidth()
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(0, 5, 0, 0)
[
SNew(SCheckBox)
.IsChecked_Lambda([this]
{
return ViewModel.Get()->GetDrawQuery() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked;
})
.OnCheckStateChanged_Lambda([this](ECheckBoxState State)
{
ViewModel.Get()->SetDrawQuery(State == ECheckBoxState::Checked);
})
[
SNew(STextBlock)
.Text(LOCTEXT("PoseSearchDebuggerDrawQuery", "Draw Query"))
]
]
]
+SHorizontalBox::Slot()
.VAlign(VAlign_Top)
.HAlign(HAlign_Left)
.Padding(64, 5, 0, 0)
.AutoWidth()
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(0, 5, 0, 0)
[
SNew(SCheckBox)
.IsChecked_Lambda([this]
{
return ViewModel.Get()->GetDrawTrajectory() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked;
})
.OnCheckStateChanged_Lambda([this](ECheckBoxState State)
{
ViewModel.Get()->SetDrawTrajectory(State == ECheckBoxState::Checked);
})
[
SNew(STextBlock)
.Text(LOCTEXT("PoseSearchDebuggerDrawTrajectory", "Draw Trajectory"))
]
]
]
+SHorizontalBox::Slot()
.VAlign(VAlign_Top)
.HAlign(HAlign_Left)
.Padding(64, 5, 0, 0)
.AutoWidth()
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(0, 5, 0, 0)
[
SNew(SCheckBox)
.IsChecked_Lambda([this]
{
return ViewModel.Get()->GetDrawHistory() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked;
})
.OnCheckStateChanged_Lambda([this](ECheckBoxState State)
{
ViewModel.Get()->SetDrawHistory(State == ECheckBoxState::Checked);
})
[
SNew(STextBlock)
.Text(LOCTEXT("PoseSearchDebuggerDrawHistory", "Draw History"))
]
]
]
+ SHorizontalBox::Slot()
.VAlign(VAlign_Top)
.HAlign(HAlign_Left)
.Padding(64, 5, 0, 0)
.AutoWidth()
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(0, 5, 0, 0)
[
SNew(SCheckBox)
.IsChecked_Lambda([this]
{
return ViewModel.Get()->IsVerbose() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked;
})
.OnCheckStateChanged_Lambda([this](ECheckBoxState State)
{
ViewModel.Get()->SetVerbose(State == ECheckBoxState::Checked);
UpdateViews();
})
[
SNew(STextBlock)
.Text(LOCTEXT("PoseSearchDebuggerShowVerbose", "Channels Breakdown"))
]
]
];
}
TSharedRef<SWidget> SDebuggerView::GenerateNodeDebuggerView()
{
return
SNew(SSplitter)
.Orientation(Orient_Vertical)
.ResizeMode(ESplitterResizeMode::Fill)
// Database view
+ SSplitter::Slot()
.Value(0.8f)
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
.AutoHeight()
[
GenerateReturnButtonView()
]
+ SVerticalBox::Slot()
[
SAssignNew(DatabaseView, SDebuggerDatabaseView)
.Parent(SharedThis(this))
.OnPoseSelectionChanged(this, &SDebuggerView::OnPoseSelectionChanged)
]
]
// Details panel view
+ SSplitter::Slot()
.Value(0.2f)
[
SAssignNew(DetailsView, SDebuggerDetailsView)
.Parent(SharedThis(this))
];
}
FName SDebuggerView::GetName() const
{
static const FName DebuggerName("PoseSearchDebugger");
return DebuggerName;
}
uint64 SDebuggerView::GetObjectId() const
{
return AnimInstanceId;
}
SDebuggerView::~SDebuggerView()
{
OnViewClosed.Execute(AnimInstanceId);
}
} // namespace UE::PoseSearch
#undef LOCTEXT_NAMESPACE