// Copyright Epic Games, Inc. All Rights Reserved. #include "STimerTreeView.h" #include "EditorStyleSet.h" #include "Framework/Commands/Commands.h" #include "Framework/Commands/UICommandList.h" #include "Framework/Application/SlateApplication.h" #include "Framework/MultiBox/MultiBoxBuilder.h" #include "HAL/PlatformApplicationMisc.h" #include "SlateOptMacros.h" #include "Widgets/Layout/SScrollBox.h" #include "Widgets/SToolTip.h" // Insights #include "Insights/Table/ViewModels/Table.h" #include "Insights/Table/ViewModels/TableColumn.h" #include "Insights/Table/ViewModels/TreeNodeSorting.h" #include "Insights/TimingProfilerManager.h" #include "Insights/ViewModels/TimerButterflyAggregation.h" #include "Insights/ViewModels/TimersViewColumnFactory.h" #include "Insights/Widgets/SAggregatorStatus.h" #include "Insights/Widgets/STimersViewTooltip.h" #include "Insights/Widgets/STimerTableRow.h" #define LOCTEXT_NAMESPACE "STimerTreeView" //////////////////////////////////////////////////////////////////////////////////////////////////// // FTimerTreeViewCommands //////////////////////////////////////////////////////////////////////////////////////////////////// class FTimerTreeViewCommands: public TCommands { public: FTimerTreeViewCommands() : TCommands(TEXT("FTimerTreeViewCommands"), NSLOCTEXT("FTimerTreeViewCommands", "Timer Tree View Commands", "Timer Tree View Commands"), NAME_None, FEditorStyle::Get().GetStyleSetName()) { } virtual ~FTimerTreeViewCommands() { } // UI_COMMAND takes long for the compiler to optimize PRAGMA_DISABLE_OPTIMIZATION virtual void RegisterCommands() override { UI_COMMAND(Command_CopyToClipboard, "Copy To Clipboard", "Copies selection to clipboard", EUserInterfaceActionType::Button, FInputChord(EModifierKey::Control, EKeys::C)); } PRAGMA_ENABLE_OPTIMIZATION TSharedPtr Command_CopyToClipboard; }; //////////////////////////////////////////////////////////////////////////////////////////////////// // STimerTreeView //////////////////////////////////////////////////////////////////////////////////////////////////// STimerTreeView::STimerTreeView() : Table(MakeShared()), ViewName(), TreeView(nullptr), TreeViewHeaderRow(nullptr), ExternalScrollbar(nullptr), HoveredColumnId(), HoveredNodePtr(nullptr), HighlightedNodeName(), TreeNodes(), AvailableSorters(), CurrentSorter(nullptr), ColumnBeingSorted(GetDefaultColumnBeingSorted()), ColumnSortMode(GetDefaultColumnSortMode()), StatsStartTime(0.0), StatsEndTime(0.0), StatsTimerId(0) { } //////////////////////////////////////////////////////////////////////////////////////////////////// STimerTreeView::~STimerTreeView() { } //////////////////////////////////////////////////////////////////////////////////////////////////// void STimerTreeView::InitCommandList() { FTimerTreeViewCommands::Register(); CommandList = MakeShared(); CommandList->MapAction(FTimerTreeViewCommands::Get().Command_CopyToClipboard, FExecuteAction::CreateSP(this, &STimerTreeView::ContextMenu_CopySelectedToClipboard_Execute), FCanExecuteAction::CreateSP(this, &STimerTreeView::ContextMenu_CopySelectedToClipboard_CanExecute)); } //////////////////////////////////////////////////////////////////////////////////////////////////// BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION void STimerTreeView::Construct(const FArguments& InArgs, const FText& InViewName) { ViewName = InViewName; TSharedRef TimerButterflyAggregator = FTimingProfilerManager::Get()->GetTimerButterflyAggregator(); SAssignNew(ExternalScrollbar, SScrollBar) .AlwaysShowScrollbar(true); ChildSlot [SNew(SHorizontalBox) + SHorizontalBox::Slot() .FillWidth(1.0f) .Padding(0.0f) [SNew(SScrollBox) .Orientation(Orient_Horizontal) + SScrollBox::Slot() [SNew(SOverlay) + SOverlay::Slot() .HAlign(HAlign_Fill) .VAlign(VAlign_Fill) [ // SNew(SBorder) //.BorderImage(FEditorStyle::GetBrush("ToolPanel.GroupBorder")) //.Padding(0.0f) //[ SAssignNew(TreeView, STreeView) .ExternalScrollbar(ExternalScrollbar) .SelectionMode(ESelectionMode::Multi) .TreeItemsSource(&TreeNodes) .OnGetChildren(this, &STimerTreeView::TreeView_OnGetChildren) .OnGenerateRow(this, &STimerTreeView::TreeView_OnGenerateRow) //.OnSelectionChanged(this, &STimerTreeView::TreeView_OnSelectionChanged) //.OnMouseButtonDoubleClick(this, &STimerTreeView::TreeView_OnMouseButtonDoubleClick) .OnContextMenuOpening(FOnContextMenuOpening::CreateSP(this, &STimerTreeView::TreeView_GetMenuContent)) .ItemHeight(12.0f) .HeaderRow( SAssignNew(TreeViewHeaderRow, SHeaderRow) .Visibility(EVisibility::Visible)) //] ] + SOverlay::Slot() .HAlign(HAlign_Right) .VAlign(VAlign_Bottom) .Padding(16.0f) [SAssignNew(AggregatorStatus, Insights::SAggregatorStatus, TimerButterflyAggregator)]]] + SHorizontalBox::Slot() .AutoWidth() .Padding(0.0f) [SNew(SBox) .WidthOverride(FOptionalSize(13.0f)) [ExternalScrollbar.ToSharedRef()]]]; InitializeAndShowHeaderColumns(); CreateSortings(); InitCommandList(); } END_SLATE_FUNCTION_BUILD_OPTIMIZATION //////////////////////////////////////////////////////////////////////////////////////////////////// TSharedPtr STimerTreeView::TreeView_GetMenuContent() { const TArray SelectedNodes = TreeView->GetSelectedItems(); const int32 NumSelectedNodes = SelectedNodes.Num(); FTimerNodePtr SelectedNode = NumSelectedNodes ? SelectedNodes[0] : nullptr; const TSharedPtr HoveredColumnPtr = Table->FindColumn(HoveredColumnId); FText SelectionStr; FText PropertyName; FText PropertyValue; if (NumSelectedNodes == 0) { SelectionStr = LOCTEXT("NothingSelected", "Nothing selected"); } else if (NumSelectedNodes == 1) { if (HoveredColumnPtr != nullptr) { PropertyName = HoveredColumnPtr->GetShortName(); PropertyValue = HoveredColumnPtr->GetValueAsTooltipText(*SelectedNode); } FString ItemName = SelectedNode->GetName().ToString(); const int32 MaxStringLen = 64; if (ItemName.Len() > MaxStringLen) { ItemName = ItemName.Left(MaxStringLen) + TEXT("..."); } SelectionStr = FText::FromString(ItemName); } else { SelectionStr = LOCTEXT("MultipleSelection", "Multiple selection"); } const bool bShouldCloseWindowAfterMenuSelection = true; FMenuBuilder MenuBuilder(bShouldCloseWindowAfterMenuSelection, CommandList.ToSharedRef()); // Selection menu MenuBuilder.BeginSection("Selection", LOCTEXT("ContextMenu_Header_Selection", "Selection")); { struct FLocal { static bool ReturnFalse() { return false; } }; FUIAction DummyUIAction; DummyUIAction.CanExecuteAction = FCanExecuteAction::CreateStatic(&FLocal::ReturnFalse); MenuBuilder.AddMenuEntry( SelectionStr, LOCTEXT("ContextMenu_Selection", "Currently selected items"), FSlateIcon(FEditorStyle::GetStyleSetName(), "@missing.icon"), DummyUIAction, NAME_None, EUserInterfaceActionType::Button); } MenuBuilder.EndSection(); MenuBuilder.BeginSection("Misc", LOCTEXT("ContextMenu_Header_Misc", "Miscellaneous")); { MenuBuilder.AddMenuEntry( FTimerTreeViewCommands::Get().Command_CopyToClipboard, NAME_None, TAttribute(), TAttribute(), FSlateIcon(FEditorStyle::GetStyleSetName(), "Profiler.Misc.CopyToClipboard")); MenuBuilder.AddSubMenu( LOCTEXT("ContextMenu_Header_Misc_Sort", "Sort By"), LOCTEXT("ContextMenu_Header_Misc_Sort_Desc", "Sort by column"), FNewMenuDelegate::CreateSP(this, &STimerTreeView::TreeView_BuildSortByMenu), false, FSlateIcon(FEditorStyle::GetStyleSetName(), "Profiler.Misc.SortBy")); } MenuBuilder.EndSection(); MenuBuilder.BeginSection("Columns", LOCTEXT("ContextMenu_Header_Columns", "Columns")); { MenuBuilder.AddSubMenu( LOCTEXT("ContextMenu_Header_Columns_View", "View Column"), LOCTEXT("ContextMenu_Header_Columns_View_Desc", "Hides or shows columns"), FNewMenuDelegate::CreateSP(this, &STimerTreeView::TreeView_BuildViewColumnMenu), false, FSlateIcon(FEditorStyle::GetStyleSetName(), "Profiler.EventGraph.ViewColumn")); FUIAction Action_ShowAllColumns( FExecuteAction::CreateSP(this, &STimerTreeView::ContextMenu_ShowAllColumns_Execute), FCanExecuteAction::CreateSP(this, &STimerTreeView::ContextMenu_ShowAllColumns_CanExecute)); MenuBuilder.AddMenuEntry( LOCTEXT("ContextMenu_Header_Columns_ShowAllColumns", "Show All Columns"), LOCTEXT("ContextMenu_Header_Columns_ShowAllColumns_Desc", "Resets tree view to show all columns"), FSlateIcon(FEditorStyle::GetStyleSetName(), "Profiler.EventGraph.ResetColumn"), Action_ShowAllColumns, NAME_None, EUserInterfaceActionType::Button); FUIAction Action_ResetColumns( FExecuteAction::CreateSP(this, &STimerTreeView::ContextMenu_ResetColumns_Execute), FCanExecuteAction::CreateSP(this, &STimerTreeView::ContextMenu_ResetColumns_CanExecute)); MenuBuilder.AddMenuEntry( LOCTEXT("ContextMenu_Header_Columns_ResetColumns", "Reset Columns to Default"), LOCTEXT("ContextMenu_Header_Columns_ResetColumns_Desc", "Resets columns to default"), FSlateIcon(FEditorStyle::GetStyleSetName(), "Profiler.EventGraph.ResetColumn"), Action_ResetColumns, NAME_None, EUserInterfaceActionType::Button); } MenuBuilder.EndSection(); return MenuBuilder.MakeWidget(); } //////////////////////////////////////////////////////////////////////////////////////////////////// void STimerTreeView::TreeView_BuildSortByMenu(FMenuBuilder& MenuBuilder) { MenuBuilder.BeginSection("ColumnName", LOCTEXT("ContextMenu_Header_Misc_ColumnName", "Column Name")); for (const TSharedRef& ColumnRef: Table->GetColumns()) { const Insights::FTableColumn& Column = *ColumnRef; if (Column.IsVisible() && Column.CanBeSorted()) { FUIAction Action_SortByColumn( FExecuteAction::CreateSP(this, &STimerTreeView::ContextMenu_SortByColumn_Execute, Column.GetId()), FCanExecuteAction::CreateSP(this, &STimerTreeView::ContextMenu_SortByColumn_CanExecute, Column.GetId()), FIsActionChecked::CreateSP(this, &STimerTreeView::ContextMenu_SortByColumn_IsChecked, Column.GetId())); MenuBuilder.AddMenuEntry( Column.GetTitleName(), Column.GetDescription(), FSlateIcon(), Action_SortByColumn, NAME_None, EUserInterfaceActionType::RadioButton); } } MenuBuilder.EndSection(); //----------------------------------------------------------------------------- MenuBuilder.BeginSection("SortMode", LOCTEXT("ContextMenu_Header_Misc_Sort_SortMode", "Sort Mode")); { FUIAction Action_SortAscending( FExecuteAction::CreateSP(this, &STimerTreeView::ContextMenu_SortMode_Execute, EColumnSortMode::Ascending), FCanExecuteAction::CreateSP(this, &STimerTreeView::ContextMenu_SortMode_CanExecute, EColumnSortMode::Ascending), FIsActionChecked::CreateSP(this, &STimerTreeView::ContextMenu_SortMode_IsChecked, EColumnSortMode::Ascending)); MenuBuilder.AddMenuEntry( LOCTEXT("ContextMenu_Header_Misc_Sort_SortAscending", "Sort Ascending"), LOCTEXT("ContextMenu_Header_Misc_Sort_SortAscending_Desc", "Sorts ascending"), FSlateIcon(FEditorStyle::GetStyleSetName(), "Profiler.Misc.SortAscending"), Action_SortAscending, NAME_None, EUserInterfaceActionType::RadioButton); FUIAction Action_SortDescending( FExecuteAction::CreateSP(this, &STimerTreeView::ContextMenu_SortMode_Execute, EColumnSortMode::Descending), FCanExecuteAction::CreateSP(this, &STimerTreeView::ContextMenu_SortMode_CanExecute, EColumnSortMode::Descending), FIsActionChecked::CreateSP(this, &STimerTreeView::ContextMenu_SortMode_IsChecked, EColumnSortMode::Descending)); MenuBuilder.AddMenuEntry( LOCTEXT("ContextMenu_Header_Misc_Sort_SortDescending", "Sort Descending"), LOCTEXT("ContextMenu_Header_Misc_Sort_SortDescending_Desc", "Sorts descending"), FSlateIcon(FEditorStyle::GetStyleSetName(), "Profiler.Misc.SortDescending"), Action_SortDescending, NAME_None, EUserInterfaceActionType::RadioButton); } MenuBuilder.EndSection(); } //////////////////////////////////////////////////////////////////////////////////////////////////// void STimerTreeView::TreeView_BuildViewColumnMenu(FMenuBuilder& MenuBuilder) { MenuBuilder.BeginSection("ViewColumn", LOCTEXT("ContextMenu_Header_Columns_View", "View Column")); for (const TSharedRef& ColumnRef: Table->GetColumns()) { const Insights::FTableColumn& Column = *ColumnRef; FUIAction Action_ToggleColumn( FExecuteAction::CreateSP(this, &STimerTreeView::ToggleColumnVisibility, Column.GetId()), FCanExecuteAction::CreateSP(this, &STimerTreeView::CanToggleColumnVisibility, Column.GetId()), FIsActionChecked::CreateSP(this, &STimerTreeView::IsColumnVisible, Column.GetId())); MenuBuilder.AddMenuEntry( Column.GetTitleName(), Column.GetDescription(), FSlateIcon(), Action_ToggleColumn, NAME_None, EUserInterfaceActionType::ToggleButton); } MenuBuilder.EndSection(); } //////////////////////////////////////////////////////////////////////////////////////////////////// void STimerTreeView::InitializeAndShowHeaderColumns() { // Create columns. TArray> Columns; FTimersViewColumnFactory::CreateTimerTreeViewColumns(Columns); if (ensure(Columns.Num() > 0 && Columns[0]->IsHierarchy())) { Columns[0]->SetShortName(ViewName); Columns[0]->SetTitleName(ViewName); } Table->SetColumns(Columns); // Show columns. for (const TSharedRef& ColumnRef: Table->GetColumns()) { if (ColumnRef->ShouldBeVisible()) { ShowColumn(ColumnRef->GetId()); } } } //////////////////////////////////////////////////////////////////////////////////////////////////// FText STimerTreeView::GetColumnHeaderText(const FName ColumnId) const { const Insights::FTableColumn& Column = *Table->FindColumnChecked(ColumnId); return Column.GetShortName(); } //////////////////////////////////////////////////////////////////////////////////////////////////// TSharedRef STimerTreeView::TreeViewHeaderRow_GenerateColumnMenu(const Insights::FTableColumn& Column) { bool bIsMenuVisible = false; const bool bShouldCloseWindowAfterMenuSelection = true; FMenuBuilder MenuBuilder(bShouldCloseWindowAfterMenuSelection, NULL); { if (Column.CanBeHidden()) { MenuBuilder.BeginSection("Column", LOCTEXT("TreeViewHeaderRow_Header_Column", "Column")); FUIAction Action_HideColumn( FExecuteAction::CreateSP(this, &STimerTreeView::HideColumn, Column.GetId()), FCanExecuteAction::CreateSP(this, &STimerTreeView::CanHideColumn, Column.GetId())); MenuBuilder.AddMenuEntry( LOCTEXT("TreeViewHeaderRow_HideColumn", "Hide"), LOCTEXT("TreeViewHeaderRow_HideColumn_Desc", "Hides the selected column"), FSlateIcon(), Action_HideColumn, NAME_None, EUserInterfaceActionType::Button); bIsMenuVisible = true; MenuBuilder.EndSection(); } if (Column.CanBeSorted()) { MenuBuilder.BeginSection("SortMode", LOCTEXT("ContextMenu_Header_Misc_Sort_SortMode", "Sort Mode")); FUIAction Action_SortAscending( FExecuteAction::CreateSP(this, &STimerTreeView::HeaderMenu_SortMode_Execute, Column.GetId(), EColumnSortMode::Ascending), FCanExecuteAction::CreateSP(this, &STimerTreeView::HeaderMenu_SortMode_CanExecute, Column.GetId(), EColumnSortMode::Ascending), FIsActionChecked::CreateSP(this, &STimerTreeView::HeaderMenu_SortMode_IsChecked, Column.GetId(), EColumnSortMode::Ascending)); MenuBuilder.AddMenuEntry( LOCTEXT("ContextMenu_Header_Misc_Sort_SortAscending", "Sort Ascending"), LOCTEXT("ContextMenu_Header_Misc_Sort_SortAscending_Desc", "Sorts ascending"), FSlateIcon(FEditorStyle::GetStyleSetName(), "Profiler.Misc.SortAscending"), Action_SortAscending, NAME_None, EUserInterfaceActionType::RadioButton); FUIAction Action_SortDescending( FExecuteAction::CreateSP(this, &STimerTreeView::HeaderMenu_SortMode_Execute, Column.GetId(), EColumnSortMode::Descending), FCanExecuteAction::CreateSP(this, &STimerTreeView::HeaderMenu_SortMode_CanExecute, Column.GetId(), EColumnSortMode::Descending), FIsActionChecked::CreateSP(this, &STimerTreeView::HeaderMenu_SortMode_IsChecked, Column.GetId(), EColumnSortMode::Descending)); MenuBuilder.AddMenuEntry( LOCTEXT("ContextMenu_Header_Misc_Sort_SortDescending", "Sort Descending"), LOCTEXT("ContextMenu_Header_Misc_Sort_SortDescending_Desc", "Sorts descending"), FSlateIcon(FEditorStyle::GetStyleSetName(), "Profiler.Misc.SortDescending"), Action_SortDescending, NAME_None, EUserInterfaceActionType::RadioButton); bIsMenuVisible = true; MenuBuilder.EndSection(); } } return bIsMenuVisible ? MenuBuilder.MakeWidget() : (TSharedRef)SNullWidget::NullWidget; } //////////////////////////////////////////////////////////////////////////////////////////////////// // TreeView //////////////////////////////////////////////////////////////////////////////////////////////////// void STimerTreeView::TreeView_Refresh() { if (TreeView.IsValid()) { TreeView->RequestTreeRefresh(); } } //////////////////////////////////////////////////////////////////////////////////////////////////// void STimerTreeView::TreeView_OnSelectionChanged(FTimerNodePtr SelectedItem, ESelectInfo::Type SelectInfo) { } //////////////////////////////////////////////////////////////////////////////////////////////////// void STimerTreeView::TreeView_OnGetChildren(FTimerNodePtr InParent, TArray& OutChildren) { constexpr bool bUseFiltering = false; if (bUseFiltering) { const TArray& Children = InParent->GetFilteredChildren(); OutChildren.Reset(Children.Num()); for (const Insights::FBaseTreeNodePtr& Child: Children) { OutChildren.Add(StaticCastSharedPtr(Child)); } } else { const TArray& Children = InParent->GetChildren(); OutChildren.Reset(Children.Num()); for (const Insights::FBaseTreeNodePtr& Child: Children) { OutChildren.Add(StaticCastSharedPtr(Child)); } } } //////////////////////////////////////////////////////////////////////////////////////////////////// void STimerTreeView::TreeView_OnMouseButtonDoubleClick(FTimerNodePtr TimerNodePtr) { // if (TimerNodePtr->IsGroup()) if (TimerNodePtr->GetChildren().Num() > 0) { const bool bIsGroupExpanded = TreeView->IsItemExpanded(TimerNodePtr); TreeView->SetItemExpansion(TimerNodePtr, !bIsGroupExpanded); } } //////////////////////////////////////////////////////////////////////////////////////////////////// // Tree View's Table Row //////////////////////////////////////////////////////////////////////////////////////////////////// TSharedRef STimerTreeView::TreeView_OnGenerateRow(FTimerNodePtr TimerNodePtr, const TSharedRef& OwnerTable) { TSharedRef TableRow = SNew(STimerTableRow, OwnerTable) .OnShouldBeEnabled(this, &STimerTreeView::TableRow_ShouldBeEnabled) .OnIsColumnVisible(this, &STimerTreeView::IsColumnVisible) .OnSetHoveredCell(this, &STimerTreeView::TableRow_SetHoveredCell) .OnGetColumnOutlineHAlignmentDelegate(this, &STimerTreeView::TableRow_GetColumnOutlineHAlignment) .HighlightText(this, &STimerTreeView::TableRow_GetHighlightText) .HighlightedNodeName(this, &STimerTreeView::TableRow_GetHighlightedNodeName) .TablePtr(Table) .TimerNodePtr(TimerNodePtr); return TableRow; } //////////////////////////////////////////////////////////////////////////////////////////////////// bool STimerTreeView::TableRow_ShouldBeEnabled(FTimerNodePtr NodePtr) const { return true; } //////////////////////////////////////////////////////////////////////////////////////////////////// void STimerTreeView::TableRow_SetHoveredCell(TSharedPtr InTablePtr, TSharedPtr InColumnPtr, FTimerNodePtr InNodePtr) { HoveredColumnId = InColumnPtr ? InColumnPtr->GetId() : FName(); const bool bIsAnyMenusVisible = FSlateApplication::Get().AnyMenusVisible(); if (!HasMouseCapture() && !bIsAnyMenusVisible) { HoveredNodePtr = InNodePtr; } } //////////////////////////////////////////////////////////////////////////////////////////////////// EHorizontalAlignment STimerTreeView::TableRow_GetColumnOutlineHAlignment(const FName ColumnId) const { const TIndirectArray& Columns = TreeViewHeaderRow->GetColumns(); const int32 LastColumnIdx = Columns.Num() - 1; // First column if (Columns[0].ColumnId == ColumnId) { return HAlign_Left; } // Last column else if (Columns[LastColumnIdx].ColumnId == ColumnId) { return HAlign_Right; } // Middle columns { return HAlign_Center; } } //////////////////////////////////////////////////////////////////////////////////////////////////// FText STimerTreeView::TableRow_GetHighlightText() const { return FText::GetEmpty(); } //////////////////////////////////////////////////////////////////////////////////////////////////// FName STimerTreeView::TableRow_GetHighlightedNodeName() const { return HighlightedNodeName; } //////////////////////////////////////////////////////////////////////////////////////////////////// // Sorting //////////////////////////////////////////////////////////////////////////////////////////////////// const FName STimerTreeView::GetDefaultColumnBeingSorted() { return FTimersViewColumns::TotalInclusiveTimeColumnID; } //////////////////////////////////////////////////////////////////////////////////////////////////// const EColumnSortMode::Type STimerTreeView::GetDefaultColumnSortMode() { return EColumnSortMode::Descending; } //////////////////////////////////////////////////////////////////////////////////////////////////// void STimerTreeView::CreateSortings() { AvailableSorters.Reset(); CurrentSorter = nullptr; for (const TSharedRef& ColumnRef: Table->GetColumns()) { if (ColumnRef->CanBeSorted()) { TSharedPtr SorterPtr = ColumnRef->GetValueSorter(); if (ensure(SorterPtr.IsValid())) { AvailableSorters.Add(SorterPtr); } } } UpdateCurrentSortingByColumn(); } //////////////////////////////////////////////////////////////////////////////////////////////////// void STimerTreeView::UpdateCurrentSortingByColumn() { TSharedPtr ColumnPtr = Table->FindColumn(ColumnBeingSorted); CurrentSorter = ColumnPtr.IsValid() ? ColumnPtr->GetValueSorter() : nullptr; } //////////////////////////////////////////////////////////////////////////////////////////////////// void STimerTreeView::SortTreeNodes() { if (CurrentSorter.IsValid()) { for (FTimerNodePtr& Root: TreeNodes) { SortTreeNodesRec(*Root, *CurrentSorter); } } } //////////////////////////////////////////////////////////////////////////////////////////////////// void STimerTreeView::SortTreeNodesRec(FTimerNode& Node, const Insights::ITableCellValueSorter& Sorter) { if (ColumnSortMode == EColumnSortMode::Type::Descending) { Node.SortChildrenDescending(Sorter); } else // if (ColumnSortMode == EColumnSortMode::Type::Ascending) { Node.SortChildrenAscending(Sorter); } for (Insights::FBaseTreeNodePtr ChildPtr: Node.GetChildren()) { // if (ChildPtr->IsGroup()) if (ChildPtr->GetChildren().Num() > 0) { SortTreeNodesRec(*StaticCastSharedPtr(ChildPtr), Sorter); } } } //////////////////////////////////////////////////////////////////////////////////////////////////// EColumnSortMode::Type STimerTreeView::GetSortModeForColumn(const FName ColumnId) const { if (ColumnBeingSorted != ColumnId) { return EColumnSortMode::None; } return ColumnSortMode; } //////////////////////////////////////////////////////////////////////////////////////////////////// void STimerTreeView::SetSortModeForColumn(const FName& ColumnId, const EColumnSortMode::Type SortMode) { ColumnBeingSorted = ColumnId; ColumnSortMode = SortMode; UpdateCurrentSortingByColumn(); SortTreeNodes(); // ApplyFiltering(); } //////////////////////////////////////////////////////////////////////////////////////////////////// void STimerTreeView::OnSortModeChanged(const EColumnSortPriority::Type SortPriority, const FName& ColumnId, const EColumnSortMode::Type SortMode) { SetSortModeForColumn(ColumnId, SortMode); TreeView_Refresh(); } //////////////////////////////////////////////////////////////////////////////////////////////////// // SortMode action (HeaderMenu) //////////////////////////////////////////////////////////////////////////////////////////////////// bool STimerTreeView::HeaderMenu_SortMode_IsChecked(const FName ColumnId, const EColumnSortMode::Type InSortMode) { return ColumnBeingSorted == ColumnId && ColumnSortMode == InSortMode; } //////////////////////////////////////////////////////////////////////////////////////////////////// bool STimerTreeView::HeaderMenu_SortMode_CanExecute(const FName ColumnId, const EColumnSortMode::Type InSortMode) const { const Insights::FTableColumn& Column = *Table->FindColumnChecked(ColumnId); return Column.CanBeSorted(); } //////////////////////////////////////////////////////////////////////////////////////////////////// void STimerTreeView::HeaderMenu_SortMode_Execute(const FName ColumnId, const EColumnSortMode::Type InSortMode) { SetSortModeForColumn(ColumnId, InSortMode); TreeView_Refresh(); } //////////////////////////////////////////////////////////////////////////////////////////////////// // SortMode action (ContextMenu) //////////////////////////////////////////////////////////////////////////////////////////////////// bool STimerTreeView::ContextMenu_SortMode_IsChecked(const EColumnSortMode::Type InSortMode) { return ColumnSortMode == InSortMode; } //////////////////////////////////////////////////////////////////////////////////////////////////// bool STimerTreeView::ContextMenu_SortMode_CanExecute(const EColumnSortMode::Type InSortMode) const { return true; // ColumnSortMode != InSortMode; } //////////////////////////////////////////////////////////////////////////////////////////////////// void STimerTreeView::ContextMenu_SortMode_Execute(const EColumnSortMode::Type InSortMode) { SetSortModeForColumn(ColumnBeingSorted, InSortMode); TreeView_Refresh(); } //////////////////////////////////////////////////////////////////////////////////////////////////// // SortByColumn action (ContextMenu) //////////////////////////////////////////////////////////////////////////////////////////////////// bool STimerTreeView::ContextMenu_SortByColumn_IsChecked(const FName ColumnId) { return ColumnId == ColumnBeingSorted; } //////////////////////////////////////////////////////////////////////////////////////////////////// bool STimerTreeView::ContextMenu_SortByColumn_CanExecute(const FName ColumnId) const { return true; // ColumnId != ColumnBeingSorted; } //////////////////////////////////////////////////////////////////////////////////////////////////// void STimerTreeView::ContextMenu_SortByColumn_Execute(const FName ColumnId) { SetSortModeForColumn(ColumnId, EColumnSortMode::Descending); TreeView_Refresh(); } //////////////////////////////////////////////////////////////////////////////////////////////////// // ShowColumn action //////////////////////////////////////////////////////////////////////////////////////////////////// bool STimerTreeView::CanShowColumn(const FName ColumnId) const { return true; } //////////////////////////////////////////////////////////////////////////////////////////////////// void STimerTreeView::ShowColumn(const FName ColumnId) { Insights::FTableColumn& Column = *Table->FindColumnChecked(ColumnId); Column.Show(); SHeaderRow::FColumn::FArguments ColumnArgs; ColumnArgs .ColumnId(Column.GetId()) .DefaultLabel(Column.GetShortName()) .HAlignHeader(HAlign_Fill) .VAlignHeader(VAlign_Fill) .HeaderContentPadding(FMargin(2.0f)) .HAlignCell(HAlign_Fill) .VAlignCell(VAlign_Fill) .SortMode(this, &STimerTreeView::GetSortModeForColumn, Column.GetId()) .OnSort(this, &STimerTreeView::OnSortModeChanged) .ManualWidth(Column.GetInitialWidth()) .FixedWidth(Column.IsFixedWidth() ? Column.GetInitialWidth() : TOptional()) .HeaderContent() [SNew(SBox) .ToolTip(STimersViewTooltip::GetColumnTooltip(Column)) .HAlign(Column.GetHorizontalAlignment()) .VAlign(VAlign_Center) [SNew(STextBlock) .Text(this, &STimerTreeView::GetColumnHeaderText, Column.GetId())]] .MenuContent() [TreeViewHeaderRow_GenerateColumnMenu(Column)]; int32 ColumnIndex = 0; const int32 NewColumnPosition = Table->GetColumnPositionIndex(ColumnId); const int32 NumColumns = TreeViewHeaderRow->GetColumns().Num(); for (; ColumnIndex < NumColumns; ColumnIndex++) { const SHeaderRow::FColumn& CurrentColumn = TreeViewHeaderRow->GetColumns()[ColumnIndex]; const int32 CurrentColumnPosition = Table->GetColumnPositionIndex(CurrentColumn.ColumnId); if (NewColumnPosition < CurrentColumnPosition) { break; } } TreeViewHeaderRow->InsertColumn(ColumnArgs, ColumnIndex); } //////////////////////////////////////////////////////////////////////////////////////////////////// // HideColumn action //////////////////////////////////////////////////////////////////////////////////////////////////// bool STimerTreeView::CanHideColumn(const FName ColumnId) const { const Insights::FTableColumn& Column = *Table->FindColumnChecked(ColumnId); return Column.CanBeHidden(); } //////////////////////////////////////////////////////////////////////////////////////////////////// void STimerTreeView::HideColumn(const FName ColumnId) { Insights::FTableColumn& Column = *Table->FindColumnChecked(ColumnId); Column.Hide(); TreeViewHeaderRow->RemoveColumn(ColumnId); } //////////////////////////////////////////////////////////////////////////////////////////////////// // ToggleColumn action //////////////////////////////////////////////////////////////////////////////////////////////////// bool STimerTreeView::IsColumnVisible(const FName ColumnId) { const Insights::FTableColumn& Column = *Table->FindColumnChecked(ColumnId); return Column.IsVisible(); } //////////////////////////////////////////////////////////////////////////////////////////////////// bool STimerTreeView::CanToggleColumnVisibility(const FName ColumnId) const { const Insights::FTableColumn& Column = *Table->FindColumnChecked(ColumnId); return !Column.IsVisible() || Column.CanBeHidden(); } //////////////////////////////////////////////////////////////////////////////////////////////////// void STimerTreeView::ToggleColumnVisibility(const FName ColumnId) { const Insights::FTableColumn& Column = *Table->FindColumnChecked(ColumnId); if (Column.IsVisible()) { HideColumn(ColumnId); } else { ShowColumn(ColumnId); } } //////////////////////////////////////////////////////////////////////////////////////////////////// // "Show All Columns" action (ContextMenu) //////////////////////////////////////////////////////////////////////////////////////////////////// bool STimerTreeView::ContextMenu_ShowAllColumns_CanExecute() const { return true; } //////////////////////////////////////////////////////////////////////////////////////////////////// void STimerTreeView::ContextMenu_ShowAllColumns_Execute() { ColumnBeingSorted = GetDefaultColumnBeingSorted(); ColumnSortMode = GetDefaultColumnSortMode(); UpdateCurrentSortingByColumn(); for (const TSharedRef& ColumnRef: Table->GetColumns()) { const Insights::FTableColumn& Column = *ColumnRef; if (!Column.IsVisible()) { ShowColumn(Column.GetId()); } } } //////////////////////////////////////////////////////////////////////////////////////////////////// // ResetColumns action (ContextMenu) //////////////////////////////////////////////////////////////////////////////////////////////////// bool STimerTreeView::ContextMenu_ResetColumns_CanExecute() const { return true; } //////////////////////////////////////////////////////////////////////////////////////////////////// void STimerTreeView::ContextMenu_ResetColumns_Execute() { ColumnBeingSorted = GetDefaultColumnBeingSorted(); ColumnSortMode = GetDefaultColumnSortMode(); UpdateCurrentSortingByColumn(); for (const TSharedRef& ColumnRef: Table->GetColumns()) { const Insights::FTableColumn& Column = *ColumnRef; if (Column.ShouldBeVisible() && !Column.IsVisible()) { ShowColumn(Column.GetId()); } else if (!Column.ShouldBeVisible() && Column.IsVisible()) { HideColumn(Column.GetId()); } } } //////////////////////////////////////////////////////////////////////////////////////////////////// void STimerTreeView::Reset() { TreeNodes.Reset(); TreeView_Refresh(); } //////////////////////////////////////////////////////////////////////////////////////////////////// void STimerTreeView::SetTree(const Trace::FTimingProfilerButterflyNode& Root) { TreeNodes.Reset(); FTimerNodePtr RootTimerNodePtr = CreateTimerNodeRec(Root); if (RootTimerNodePtr) { // Mark the hot path. The child nodes are already sorted by InclTime (descending), so we just follow the first child. FTimerNodePtr TimerNodePtr = RootTimerNodePtr; while (TimerNodePtr.IsValid()) { TimerNodePtr->SetIsHotPath(true); const TArray& Children = TimerNodePtr->GetChildren(); TimerNodePtr = Children.Num() > 0 ? StaticCastSharedPtr(Children[0]) : nullptr; } TreeNodes.Add(RootTimerNodePtr); } SortTreeNodes(); TreeView_Refresh(); if (RootTimerNodePtr) { ExpandNodesRec(RootTimerNodePtr, 0); } } //////////////////////////////////////////////////////////////////////////////////////////////////// FTimerNodePtr STimerTreeView::CreateTimerNodeRec(const Trace::FTimingProfilerButterflyNode& Node) { if (Node.Timer == nullptr) { return nullptr; // return MakeShared(0, TEXT("!!!!!"), ETimerNodeType::InvalidOrMax); } const ETimerNodeType Type = Node.Timer->IsGpuTimer ? ETimerNodeType::GpuScope : ETimerNodeType::CpuScope; FTimerNodePtr TimerNodePtr = MakeShared(Node.Timer->Id, Node.Timer->Name, Type); Trace::FTimingProfilerAggregatedStats AggregatedStats; AggregatedStats.InstanceCount = Node.Count; AggregatedStats.TotalInclusiveTime = Node.InclusiveTime; AggregatedStats.TotalExclusiveTime = Node.ExclusiveTime; TimerNodePtr->SetAggregatedStats(AggregatedStats); for (const Trace::FTimingProfilerButterflyNode* ChildNodePtr: Node.Children) { if (ChildNodePtr != nullptr) { FTimerNodePtr ChildTimerNodePtr = CreateTimerNodeRec(*ChildNodePtr); if (ChildTimerNodePtr) { TimerNodePtr->AddChildAndSetGroupPtr(ChildTimerNodePtr); } } } // Sort children by InclTime (descending). TArray& Children = const_cast&>(TimerNodePtr->GetChildren()); Children.Sort([](const Insights::FBaseTreeNodePtr& A, const Insights::FBaseTreeNodePtr& B) -> bool { const double InclTimeA = StaticCastSharedPtr(A)->GetAggregatedStats().TotalInclusiveTime; const double InclTimeB = StaticCastSharedPtr(B)->GetAggregatedStats().TotalInclusiveTime; return InclTimeA >= InclTimeB; }); return TimerNodePtr; } //////////////////////////////////////////////////////////////////////////////////////////////////// void STimerTreeView::ExpandNodesRec(FTimerNodePtr NodePtr, int32 Depth) { // constexpr int32 MaxDepth = 3; TreeView->SetItemExpansion(NodePtr, NodePtr->IsHotPath()); // expand only the hot path // if (Depth < MaxDepth) { for (const Insights::FBaseTreeNodePtr& ChildPtr: NodePtr->GetChildren()) { ExpandNodesRec(StaticCastSharedPtr(ChildPtr), Depth + 1); } } } //////////////////////////////////////////////////////////////////////////////////////////////////// bool STimerTreeView::ContextMenu_CopySelectedToClipboard_CanExecute() const { const TArray SelectedNodes = TreeView->GetSelectedItems(); return SelectedNodes.Num() > 0; } //////////////////////////////////////////////////////////////////////////////////////////////////// void STimerTreeView::ContextMenu_CopySelectedToClipboard_Execute() { if (!Table->IsValid()) { return; } TArray SelectedNodes; for (FTimerNodePtr TimerPtr: TreeView->GetSelectedItems()) { SelectedNodes.Add(TimerPtr); } if (SelectedNodes.Num() == 0) { return; } FString ClipboardText; if (CurrentSorter.IsValid()) { CurrentSorter->Sort(SelectedNodes, ColumnSortMode == EColumnSortMode::Ascending ? Insights::ESortMode::Ascending : Insights::ESortMode::Descending); } Table->GetVisibleColumnsData(SelectedNodes, ClipboardText); if (ClipboardText.Len() > 0) { FPlatformApplicationMisc::ClipboardCopy(*ClipboardText); } } //////////////////////////////////////////////////////////////////////////////////////////////////// FReply STimerTreeView::OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent) { return CommandList->ProcessCommandBindings(InKeyEvent) == true ? FReply::Handled() : FReply::Unhandled(); } //////////////////////////////////////////////////////////////////////////////////////////////////// #undef LOCTEXT_NAMESPACE