// Copyright Epic Games, Inc. All Rights Reserved. #include "SMassFragmentsView.h" #include "MassDebuggerModel.h" #include "MassEntityManager.h" #include "MassArchetypeData.h" #include "MassProcessingTypes.h" #include "MassProcessor.h" #include "Styling/AppStyle.h" #include "Widgets/Input/SButton.h" #include "Widgets/Views/SHeaderRow.h" #include "Widgets/Text/STextBlock.h" #include "Framework/Views/TableViewMetadata.h" #include "Misc/TextFilter.h" #define LOCTEXT_NAMESPACE "SMassDebugger" //----------------------------------------------------------------------// // SFragmentsView //----------------------------------------------------------------------// namespace UE::MassDebugger::FragmentsView { namespace Private { static const FLazyName ColumnName = TEXT("Name"); static const FLazyName ColumnDebugControls = TEXT("DebugControls"); static const FLazyName ColumnNumArchetypes = TEXT("Archetypes"); static const FLazyName ColumnNumEntities = TEXT("Entities"); } void SFragmentsView::Construct(const FArguments& InArgs, TSharedRef InDebuggerModel) { #if WITH_MASSENTITY_DEBUG Initialize(InDebuggerModel); TSharedRef HeaderRowWidget = SNew(SHeaderRow) + SHeaderRow::Column(Private::ColumnName) .DefaultLabel(LOCTEXT("FragmentColumnName", "Fragment Type")) .FillWidth(0.5f) .SortMode(this, &SFragmentsView::GetColumnSortMode, Private::ColumnName.Resolve()) .OnSort(this, &SFragmentsView::OnColumnSortModeChanged) + SHeaderRow::Column(Private::ColumnDebugControls) .DefaultLabel(LOCTEXT("FragmentColumnDebugControls", "Debug Controls")) .FixedWidth(30.f) .HAlignHeader(HAlign_Center) .HAlignCell(HAlign_Center) .VAlignCell(VAlign_Center) .SortMode(EColumnSortMode::None) + SHeaderRow::Column(Private::ColumnNumArchetypes) .DefaultLabel(LOCTEXT("FragmentColumnArchetypes", "Archetypes")) .FillWidth(0.25f) .SortMode(this, &SFragmentsView::GetColumnSortMode, Private::ColumnNumArchetypes.Resolve()) .OnSort(this, &SFragmentsView::OnColumnSortModeChanged) + SHeaderRow::Column(Private::ColumnNumEntities) .DefaultLabel(LOCTEXT("FragmentColumnEntities", "Entities")) .FillWidth(0.25f) .SortMode(this, &SFragmentsView::GetColumnSortMode, Private::ColumnNumEntities.Resolve()) .OnSort(this, &SFragmentsView::OnColumnSortModeChanged); SAssignNew(FragmentsList, SListView) .ListItemsSource(&FragmentListItemsSource) .OnGenerateRow(this, &SFragmentsView::OnGenerateFragmentRow) .HeaderRow(HeaderRowWidget); ChildSlot [ FragmentsList.ToSharedRef() ]; RefreshFragmentList(); #else // WITH_MASSENTITY_DEBUG ChildSlot [ SNew(STextBlock) .Text(LOCTEXT("MassEntityDebuggingNotEnabled", "Mass Entity Debugging Not Enabled for this configuration")) .Justification(ETextJustify::Center) ]; #endif // WITH_MASSENTITY_DEBUG } TSharedRef SFragmentsView::OnGenerateFragmentRow(FFragmentsTableRowPtr InItem, const TSharedRef& OwnerTable) { return SNew(SFragmentsTableRow, OwnerTable, InItem); } EColumnSortMode::Type SFragmentsView::GetColumnSortMode(FName ColumnId) const { if (SortByColumnId != ColumnId) { return EColumnSortMode::None; } return bSortAscending ? EColumnSortMode::Ascending : EColumnSortMode::Descending; } void SFragmentsView::OnColumnSortModeChanged(const EColumnSortPriority::Type SortPriority, const FName& ColumnId, const EColumnSortMode::Type InSortMode) { if (ColumnId == Private::ColumnDebugControls) { return; } if (SortPriority == EColumnSortPriority::Primary) { SortByColumnId = ColumnId; bSortAscending = InSortMode == EColumnSortMode::Ascending ? true : false; } RefreshFragmentList(); } void SFragmentsView::OnRefresh() { #if WITH_MASSENTITY_DEBUG RefreshFragmentList(); #endif // WITH_MASSENTITY_DEBUG } void SFragmentsView::RefreshFragmentList() { FragmentListItemsSource.Reset(); #if WITH_MASSENTITY_DEBUG if (!DebuggerModel.IsValid()) { return; } FragmentListItemsSource.Reserve(DebuggerModel->CachedFragmentData.Num()); for (TSharedPtr& FragmentData : DebuggerModel->CachedFragmentData) { // filter out unused fragments // @todo: add checkbox to "Show Unused Fragments" if (FragmentData->Archetypes.Num() > 0) { FragmentListItemsSource.Add(FragmentData); } } if (SortByColumnId == Private::ColumnName) { auto SortName = [bAscending = bSortAscending](const TSharedPtr& A, const TSharedPtr& B) { const int32 Compare = A->Name.CompareTo(B->Name) <= 0; return bAscending ? Compare > 0 : Compare <= 0; }; FragmentListItemsSource.StableSort(SortName); } else if (SortByColumnId == Private::ColumnNumArchetypes) { auto SortArchetypes = [bAscending = bSortAscending](const TSharedPtr& A, const TSharedPtr& B) { return bAscending ? A->Archetypes.Num() < B->Archetypes.Num() : A->Archetypes.Num() > B->Archetypes.Num(); }; FragmentListItemsSource.StableSort(SortArchetypes); } else if (SortByColumnId == Private::ColumnNumEntities) { auto SortEntities = [bAscending = bSortAscending](const TSharedPtr& A, const TSharedPtr& B) { return bAscending ? A->NumEntities < B->NumEntities : A->NumEntities > B->NumEntities; }; FragmentListItemsSource.StableSort(SortEntities); } if (FragmentsList.IsValid()) { FragmentsList->RequestListRefresh(); } #endif // WITH_MASSENTITY_DEBUG } //----------------------------------------------------------------------// // SFragmentsTableRow - Represents a single row in the list //----------------------------------------------------------------------// void SFragmentsTableRow::Construct(const FArguments& InArgs, const TSharedRef& InOwnerTableView, FFragmentsTableRowPtr InFragmentDataPtr) { #if WITH_MASSENTITY_DEBUG FragmentDataPtr = InFragmentDataPtr; SMultiColumnTableRow::Construct( SMultiColumnTableRow::FArguments() .ShowSelection(false) , InOwnerTableView); #endif //WITH_MASSENTITY_DEBUG } TSharedRef SFragmentsTableRow::GenerateWidgetForColumn(const FName& InColumnName) { TSharedRef CellWidget = SNullWidget::NullWidget; #if WITH_MASSENTITY_DEBUG if (!FragmentDataPtr.IsValid()) { return CellWidget; } if (InColumnName == Private::ColumnName) { SAssignNew(CellWidget, STextBlock) .Text(FragmentDataPtr->Name) .ToolTipText(FragmentDataPtr->Fragment.IsValid() ? FText::FromString(FragmentDataPtr->Fragment.Pin()->GetPathName()) : LOCTEXT("InvalidFragmentPath", "Invalid Fragment Struct")); } else if (InColumnName == Private::ColumnNumArchetypes) { SAssignNew(CellWidget, STextBlock) .Text(FText::AsNumber(FragmentDataPtr->Archetypes.Num())) .Justification(ETextJustify::Right); } else if (InColumnName == Private::ColumnNumEntities) { SAssignNew(CellWidget, STextBlock) .Text(FText::AsNumber(FragmentDataPtr->NumEntities)) .Justification(ETextJustify::Right); } else if (InColumnName == Private::ColumnDebugControls) { SAssignNew(CellWidget, SButton) .ToolTipText(LOCTEXT("BreakpointTooltip", "Break on writes for selected entity.")) .ButtonStyle(FAppStyle::Get(), "NoBorder") .ContentPadding(0) .OnClicked_Lambda([WeakEntityManager = FragmentDataPtr->EntityManager, Fragment = FragmentDataPtr->Fragment]() { TSharedPtr StrongEntityManager = WeakEntityManager.Pin(); TStrongObjectPtr StrongFragment = Fragment.Pin(); if (StrongEntityManager && StrongFragment) { FMassDebugger::ToggleFragmentWriteBreakForSelectedEntity(*StrongEntityManager, StrongFragment.Get()); } return FReply::Handled(); }) [ SNew(SImage) .ColorAndOpacity_Lambda([WeakEntityManager = FragmentDataPtr->EntityManager, Fragment = FragmentDataPtr->Fragment]() { TSharedPtr StrongEntityManager = WeakEntityManager.Pin(); TStrongObjectPtr StrongFragment = Fragment.Pin(); if (StrongEntityManager && StrongFragment) { if(FMassDebugger::IsFragmentWriteBreakForSelectedEntitySet(*StrongEntityManager, StrongFragment.Get())) { return FSlateColor(FLinearColor(1.f, 0.f, 0.f, 1.f)); } } return FSlateColor(FLinearColor(1.f, 1.f, 1.f, 0.8f)); }) .Image(FAppStyle::GetBrush("Icons.FilledCircle")) ]; } #endif //WITH_MASSENTITY_DEBUG return SNew(SHorizontalBox) + SHorizontalBox::Slot() .Padding(FMargin(4.f, 0.f)) .VAlign(VAlign_Center) [ CellWidget ]; } } // UE::MassDebugger::FragmentsView #undef LOCTEXT_NAMESPACE